我正在尝试使用单目相机和以下方式捕获物体的图像:
- 首先是相机的正常校准
- 然后使用世界系统和相机系统中的已知对象坐标对相机进行透视校准
在这两个步骤之后,我应该能够获得框架中任何检测到的对象的世界坐标。此链接以更详细的模式解释了我正在尝试做的事情,但与链接不同的是,我没有将 OpenCV 与 Python 一起使用,而是与 Java 一起使用。
到目前为止,我已经设法进行了正常校准,获得了相机内在参数和旋转/平移向量。我在solvePnPRansac() OpenCv 函数中使用这些参数来获得相机的外在矩阵,它允许构建投影矩阵以将点从世界坐标转换为图像坐标。以下是获得的参数:
从这一步开始,我拥有了执行第一个链接中显示的两个操作所需的一切:
现在这就是事情变得复杂的地方。当我使用世界系统坐标时,我获得了使用对象检测算法获得的正确图像系统坐标(+ - 几个像素)。但是,当我尝试进行第二次操作时,得到的结果完全没有意义。首先是我用来获取外部参数的代码:
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),得到的结果是:
第二次操作的代码(从图像系统到世界系统):
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);
}
对于同一点返回的坐标如下:
我真的不知道问题可能来自哪里。我多次修改代码,但算法似乎没有错。然而,我怀疑获得的外部参数,尤其是 tVecsPnP 的 Z 值,考虑到根据我的相机/世界设置它应该接近其 X 值,它太高了,但我不知道如何修复它。如果有人知道如何克服这个问题,请告诉我:) 谢谢!