使用OpenCV求解Essential Matrix出现异常的问题排查
我正在编写一个简单的Python程序,通过一对任意摆放的双目图像计算目标的距离。两张图像由已知内参K的单台相机拍摄,相机位置和姿态任意,但已知拍摄位置间的基线距离。
我认为在使用OpenCV求解Essential Matrix时存在问题,但无法确定是结果本身错误还是解读方式有误。示例图像对:
当前图像由iPhone 12相机拍摄,该相机几乎肯定会自动完成去畸变。不过我仍对相机进行了标定,但脚本中未执行任何去畸变操作。(是否需要执行?)
方法
我当前采用的几何计算流程如下:
- 执行关键点检测与匹配,得到两组对应图像点列表
- 使用OpenCV的5点算法
cv2.findEssentialMat()计算Essential Matrix E,并通过RANSAC进行外点过滤 - 使用OpenCV的
cv2.recoverPose()从E中恢复旋转矩阵R和平移单位向量t,并将t乘以基线距离得到真实世界尺度
问题
计算得到的旋转矩阵和平移向量与预期结果不符。我可以确定关键点检测与匹配的效果很好,几乎不可能是问题根源。左图为正面拍摄,右图为在左侧相机右侧7米处拍摄并朝向场景倾斜,因此预期从左相机到右相机的平移向量为t = [7, 0, 0],但cv2.recoverPose()给出的结果为t = [-6.597, 0.256, 2.324]。是否是我对结果的坐标系解读错误?这会不会是从右相机到左相机的平移?或者cv2.recoverPose()收敛到了错误解(可能性较低)?
对极线可视化也存在问题:
我了解到8点算法求解基础矩阵对噪声敏感,因此通过F = Kinv.T @ E @ Kinv反解基础矩阵,其中Kinv是内参矩阵K的逆。观察得到的对应对极线,发现两张图像中“另一台相机”似乎都在成像相机的左侧,这显然不合理。此外,更改算法或RANSAC参数有时会导致结果图像发生巨大变化:
极点不应在任何一张图像中可见,此时计算得到的R和t明显错误。
性能不佳是系统噪声导致的吗?我可以轻松得到效果很好的单应矩阵(cv2.findHomography()),可视化效果见:
且这对图像是我为获得最佳性能精心挑选的(拍摄墙面)。系统中少量的视差是否会增加噪声?单应矩阵已明显滤除噪声,且我向5点算法传入了近1500对匹配点。
最终我需要得到视角间准确的R和t,而这似乎需要先获得更准确的Essential Matrix E。恳请提供任何建议,提前感谢!
代码
注意:假设pts0和pts1已正确填充有效的对应点(分别对应左图和右图)。
# matched points in each image pts0 = np.int32(<left image points here>) pts1 = np.int32(<corresponding right image points here>) E, mask = cv2.findEssentialMat(pts0, pts1, K, cv2.RANSAC, prob=.99999, threshold=.1) F = Kinv.T @ E @ Kinv # solve for fundamental matrix # recover pose to verify calculated transform to actual camera placement _, R, t, _ = cv2.recoverPose(E, pts0, pts1, K, mask=mask) t *= scale # convert to real-world scale pts0 = pts0[mask.ravel() == 1] pts1 = pts1[mask.ravel() == 1] """ followed tutorial at <https://docs.opencv.org/4.x/da/de9/tutorial_py_epipolar_geometry.html> for displaying epilines """ llines = cv2.computeCorrespondEpilines(pts1.reshape(-1, 1, 2), 2, F) llines = llines.reshape(-1, 3) out02, _ = drawlines(left_img, right_img, llines, pts0, pts1) rlines = cv2.computeCorrespondEpilines(pts0.reshape(-1, 1, 2), 1, F) rlines = rlines.reshape(-1, 3) out01, _ = drawlines(right_img, left_img, rlines, pts1, pts0) cv2.imshow('epilines', np.hstack((out02, out01))) cv2.waitKey(0) cv2.destroyAllWindows()
内容的提问来源于stack exchange,提问作者Evan

