単眼カメラでオブジェクトの画像をキャプチャしようとしています:
- まず、カメラの通常のキャリブレーション
- 次に、ワールド システムとカメラ システムの両方で既知のオブジェクト座標を使用して、カメラの透視キャリブレーションを行います。
これらの 2 つの手順の後、フレーム内で検出されたオブジェクトのワールド座標を取得できるはずです。このリンクは、私がやろうとしていることをより詳細なパターンで説明していますが、リンクとは異なり、OpenCV を Python ではなく Java で使用しています。
これまでのところ、通常のキャリブレーションを行うことができ、カメラの固有パラメーターと回転/並進ベクトルを取得できました。これらのパラメーターを solvePnPRansac() OpenCv 関数で使用して、カメラの外部行列を取得しました。これにより、射影行列を構築して、点をワールド座標から画像座標に変換できました。得られたパラメータは次のとおりです。
このステップから、最初のリンクに示されている 2 つの操作を実行するために必要なものがすべて揃っています。
ここからが複雑になります。ワールド システム座標を使用すると、オブジェクト検出アルゴリズム (+- 数ピクセル) で取得した正しいイメージ システム座標が得られます。しかし、2回目の操作をしようとすると、得られた結果はまったく意味がありません。最初に、外部パラメーターを取得するために使用したコードを次に示します。
double[] cx = this.optMat.get(0, 2);
double[] cy = this.optMat.get(1, 2);
int realCenterX = 258;
int realCenterY = 250;
int realCenterZ = 453;
MatOfPoint3f worldPoints = new MatOfPoint3f();
MatOfPoint2f imagePoints = new MatOfPoint2f();
List<Point3> objPoints = new ArrayList<Point3>();
objPoints.add(new Point3(realCenterX,realCenterY,realCenterZ));
objPoints.add(new Point3(154,169,475));
objPoints.add(new Point3(244,169,470));
objPoints.add(new Point3(337,169,470));
objPoints.add(new Point3(154,240,469));
objPoints.add(new Point3(244,240,452));
objPoints.add(new Point3(337,240,462));
objPoints.add(new Point3(154,310,472));
objPoints.add(new Point3(244,310,460));
objPoints.add(new Point3(337,310,468));
worldPoints.fromList(objPoints);
List<Point> imgPoints = new ArrayList<Point>();
imgPoints.add(new Point(cx[0],cy[0]));
imgPoints.add(new Point(569,99));
imgPoints.add(new Point(421,100));
imgPoints.add(new Point(272,100));
imgPoints.add(new Point(571,212));
imgPoints.add(new Point(422,213));
imgPoints.add(new Point(273,214));
imgPoints.add(new Point(574,327));
imgPoints.add(new Point(423,328));
imgPoints.add(new Point(273,330));
imagePoints.fromList(imgPoints);
for(int i= 0;i<worldPoints.rows();i++) {
for(int j=0;j<worldPoints.cols();j++) {
double[] pointI = worldPoints.get(i, j);
double wX = pointI[0]-realCenterX;
double wY = pointI[1]-realCenterY;
double wD = pointI[2];
double D1 = Math.sqrt((wX*wX)+(wY+wY));
double wZ = Math.sqrt((wD*wD)+(D1*D1));
pointI[2] = wZ;
worldPoints.put(i, j, pointI);
}
}
Mat optMatInv = new Mat();
Core.invert(this.optMat, optMatInv);
Calib3d.solvePnPRansac(worldPoints, imagePoints, optMat, distCoeffs, rvecsPnP, tvecsPnP, true, 100, (float) 0.5, 0.99, new Mat(), Calib3d.SOLVEPNP_ITERATIVE);
Calib3d.Rodrigues(this.rvecsPnP, this.rodriguesVecs);
this.rodriguesVecs.copyTo(this.extrinsicMat);
List<Mat> concat = new ArrayList<Mat>();
concat.add(this.rodriguesVecs);
concat.add(this.tvecsPnP);
Core.hconcat(concat, this.extrinsicMat);
Core.gemm(this.optMat, this.extrinsicMat, 1, new Mat(), 0, this.projectionMat);
int nbOfElements = worldPoints.rows() * worldPoints.cols();
List<Double> sDescribe = new ArrayList<Double>();
最初の操作 (ワールド システムからイメージ システムへ) の場合:
for(int i= 0;i<nbOfElements;i++) {
double[] pointArray = worldPoints.get(i,0);
Mat pointI = new Mat(1,4,CvType.CV_64F);
pointI.put(0, 0, pointArray[0]);
pointI.put(0, 1, pointArray[1]);
pointI.put(0, 2, pointArray[2]);
pointI.put(0, 3, 1);
Mat transPointI = new Mat(4,1,CvType.CV_64F);
Core.transpose(pointI,transPointI);
Mat sUV = new Mat(3,1,CvType.CV_64F);
Core.gemm(projectionMat, transPointI, 1, new Mat(), 0, sUV);
double[] sArray0 = sUV.get(2,0);
double s = sArray0[0];
Mat UV = new Mat();
Core.multiply(sUV, new Scalar(1/s,1/s,1/s), UV);
sDescribe.add(i, s);
}
(154,169,475)のように、かなり良い結果が得られました。得られた結果は次のとおりです。
2 番目の操作 (イメージ システムからワールド システムへ) のコード:
for(int i= 0;i<nbOfElements;i++) {
double[] pointArray = imagePoints.get(i, 0);
double sPoint = sDescribe.get(i);
Mat pointI = new Mat(3,1,CvType.CV_64F);
pointI.put(0, 0, pointArray[0]);
pointI.put(1, 0, pointArray[1]);
pointI.put(2, 0, 1);
Mat transPointI = new Mat();
Core.transpose(pointI, transPointI);
Mat sUV = new Mat(3,1,CvType.CV_64F);
Core.multiply(transPointI, new Scalar(sPoint,sPoint,sPoint), sUV);
Mat invCameraMatrix = new Mat(3,3,CvType.CV_64F);
Core.invert(this.optMat, invCameraMatrix);
Mat tmp1 = new Mat();
Core.gemm(sUV,invCameraMatrix, 1, new Mat(), 0, tmp1);
Mat tVecsInv = new Mat();
Core.transpose(this.tvecsPnP, tVecsInv);
Mat tmp2 = new Mat();
Core.subtract(tmp1, tVecsInv, tmp2);
Mat XYZ = new Mat();
Mat inverseRMat = new Mat();
Core.invert(this.rodriguesVecs, inverseRMat);
Core.gemm(tmp2, inverseRMat, 1, new Mat(), 0, XYZ);
}
同じポイントに対して、次のような座標が返されました:
問題がどこから来ているのか、私は本当に迷っています。何度もコードを修正しましたが、アルゴリズムは間違っていないようです。ただし、取得した外部パラメーター、特にカメラ/ワールドの設定によると X 値に近いはずの tVecsPnP の Z 値が高すぎるのではないかと疑っていますが、修正方法がわかりません。誰かがこれを克服する方法について手がかりを持っている場合は、私に知らせてください:)ありがとう!