Apples Visionフレームワークの機能/キーポイントを使用して、OpenCVのcv::solvePNPを介してポーズ推定(画像上の2Dポイントで3Dモデルのキーポイントを修正してポーズに一致させる)に取り組んできました。
TL-DR:
私のシーン キット モデルは変換されており、solvePnP からの変換ベクトルと回転ベクトルをイントロスペクションすると単位は正しく見えます (つまり、それらは正しい大きさの順序です) が、変換の座標系はずれているように見えます:
SolvePnP wrt to Metal / OpenGL 座標系とカメラ投影行列を使用して、座標系の要件を理解しようとしています。
SCNCamera が solvePnP に渡された画像ベースの座標系と一致させるために必要な「projectionMatrix」は何ですか?
私が読んだ/私が考慮していると信じているいくつかのこと。
- OpenCV と OpenGL (つまり、Metal) には、行優先と列優先の違いがあります。
- 3D 用の OpenCV の座標系は、OpenGL (つまり、Metal) とは異なります。
より長いコード:
私のワークフローは次のとおりです。
ステップ 1 - 3D モデル ツールを使用して 3D モデルのポイントをイントロスペクトし、検出された 2D フィーチャの主要なキー ポイントのオブジェクトの頂点位置を取得します。左瞳、右瞳、鼻先、顎先、左目尻、右目尻を使用しています。
ステップ 2 - ビジョン リクエストを実行し、画像空間内のポイントのリストを抽出し (OpenCV の左上座標系に変換)、2D ポイントの同じ順序付きリストを抽出します。
ステップ 3 - 入力画像のサイズを使用してカメラ マトリックスを作成します。
ステップ 4 - cv::solvePnP を実行し、cv::Rodrigues を使用して回転ベクトルを行列に変換します。
ステップ 5 - 結果の変換の座標系を GPU に適したものに変換します。y 軸と z 軸を反転し、平行移動と回転を単一の 4x4 行列に結合してから、OpenGL / Metal の適切なメジャーネスに合わせて転置します。
ステップ 6 - 結果の変換を次の方法で Scenekit に適用します。
let faceNodeTransform = openCVWrapper.transform(for: landmarks, imageSize: size)
self.destinationView.pointOfView?.transform = SCNMatrix4Invert(faceNodeTransform)
以下は、ビジョン ランドマークのサブセットと、見ている画像の実際のピクセル サイズを取り込む Obj-C++ OpenCV ラッパーです。
/ https://answers.opencv.org/question/23089/opencv-opengl-proper-camera-pose-using-solvepnp/
- (SCNMatrix4) transformFor:(VNFaceLandmarks2D*)landmarks imageSize:(CGSize)imageSize
{
// 1 convert landmarks to image points in image space (pixels) to vector of cv::Point2f's :
// Note that this translates the point coordinate system to be top left oriented for OpenCV's image coordinates:
std::vector<cv::Point2f > imagePoints = [self imagePointsForLandmarks:landmarks imageSize:imageSize];
// 2 Load Model Points
std::vector<cv::Point3f > modelPoints = [self modelPoints];
// 3 create our camera extrinsic matrix
// TODO - see if this is sane?
double max_d = fmax(imageSize.width, imageSize.height);
cv::Mat cameraMatrix = (cv::Mat_<double>(3,3) << max_d, 0, imageSize.width/2.0,
0, max_d, imageSize.height/2.0,
0, 0, 1.0);
// 4 Run solvePnP
double distanceCoef[] = {0,0,0,0};
cv::Mat distanceCoefMat = cv::Mat(1 ,4 ,CV_64FC1,distanceCoef);
// Output Matrixes
std::vector<double> rv(3);
cv::Mat rotationOut = cv::Mat(rv);
std::vector<double> tv(3);
cv::Mat translationOut = cv::Mat(tv);
cv::solvePnP(modelPoints, imagePoints, cameraMatrix, distanceCoefMat, rotationOut, translationOut, false, cv::SOLVEPNP_EPNP);
// 5 Convert rotation matrix (actually a vector)
// To a real 4x4 rotation matrix:
cv::Mat viewMatrix = cv::Mat::zeros(4, 4, CV_64FC1);
cv::Mat rotation;
cv::Rodrigues(rotationOut, rotation);
// Append our transforms to our matrix and set final to identity:
for(unsigned int row=0; row<3; ++row)
{
for(unsigned int col=0; col<3; ++col)
{
viewMatrix.at<double>(row, col) = rotation.at<double>(row, col);
}
viewMatrix.at<double>(row, 3) = translationOut.at<double>(row, 0);
}
viewMatrix.at<double>(3, 3) = 1.0f;
// Transpose OpenCV to OpenGL coords
cv::Mat cvToGl = cv::Mat::zeros(4, 4, CV_64FC1);
cvToGl.at<double>(0, 0) = 1.0f;
cvToGl.at<double>(1, 1) = -1.0f; // Invert the y axis
cvToGl.at<double>(2, 2) = -1.0f; // invert the z axis
cvToGl.at<double>(3, 3) = 1.0f;
viewMatrix = cvToGl * viewMatrix;
// Finally transpose to get correct SCN / OpenGL Matrix :
cv::Mat glViewMatrix = cv::Mat::zeros(4, 4, CV_64FC1);
cv::transpose(viewMatrix , glViewMatrix);
return [self convertCVMatToMatrix4:glViewMatrix];
}
- (SCNMatrix4) convertCVMatToMatrix4:(cv::Mat)matrix
{
SCNMatrix4 scnMatrix = SCNMatrix4Identity;
scnMatrix.m11 = matrix.at<double>(0, 0);
scnMatrix.m12 = matrix.at<double>(0, 1);
scnMatrix.m13 = matrix.at<double>(0, 2);
scnMatrix.m14 = matrix.at<double>(0, 3);
scnMatrix.m21 = matrix.at<double>(1, 0);
scnMatrix.m22 = matrix.at<double>(1, 1);
scnMatrix.m23 = matrix.at<double>(1, 2);
scnMatrix.m24 = matrix.at<double>(1, 3);
scnMatrix.m31 = matrix.at<double>(2, 0);
scnMatrix.m32 = matrix.at<double>(2, 1);
scnMatrix.m33 = matrix.at<double>(2, 2);
scnMatrix.m34 = matrix.at<double>(2, 3);
scnMatrix.m41 = matrix.at<double>(3, 0);
scnMatrix.m42 = matrix.at<double>(3, 1);
scnMatrix.m43 = matrix.at<double>(3, 2);
scnMatrix.m44 = matrix.at<double>(3, 3);
return (scnMatrix);
}
いくつかの質問:
SCNNode には、行列を投げるだけの modelViewMatrix (私が理解しているように、modelMatrix である変換) がありません。クローズ結果が得られるようです。このアプローチが正しいことを確認したいと思います。
もし私がmodelViewMatrixとprojectionMatrixを持っているなら、適切なmodelMatrixを計算できるはずですか? これは私が取るべきアプローチですか?
SceneKitシーンにどのprojectionMatrixを使用する必要があるのか 、それが結果に関係があるかどうかはわかりません。ビューポートと画像サイズを正確に一致させるにはピクセルが必要ですか? SolvePnP の座標系の一致を確保するために SCNCamera を適切に構成するにはどうすればよいですか?
どうもありがとうございました!