书接上文:双目相机和立体视觉
视差
打炮的时候用大拇指测距是有它的原理的,如封面所示,先只闭左眼,再只闭右眼,估计远方物体在该视差范围内的宽度,则该物体距你大概为该宽度的十倍。
我们可以使用两个相机来模拟我们的双眼,你需要保证这两个相机水平位置相同,接着从不同角度同时拍摄同一个物体,如下图所示:
图中圈选的两点称为同名点,在理想情况下,它们在垂直方向上的位置是相同的,在水平方向上的位置会有一定的差距,称为视差,越近的物体视差越大,越远的物体视差越小,这一点你可以通过交替闭上左右眼来验证。
标定与校正
上篇文章说了双目立体视觉的数学原理,但整篇都是基于针孔相机模型来讲解的,但实际上为了更好的成像效果我们使用的都是透镜相机,二者之间的原理类似,但透镜相机会产生透视失真,也就是畸变。所以我们需要校正透镜相机拍摄的图片,才能保证同名点垂直方向上的位置是相同的。
校正方法是用一张尺寸固定的棋盘格,如下图所示:
明显可以观察到棋盘格的边界已经弯曲了,通过检测棋盘格的角点(图中用彩色标记的部分),计算它们的畸变系数,就可以对图片进行校正了,校正后的结果如下图所示:
可以看到,每一个同名点对的垂直方向上的位置都保持一致了,只存在水平方向上的位置的差距。
代码实现
这里使用 OpenCV Python 来演示,如果你习惯 OpenCV C++ 也是一样的。
标定
先来看立体标定的函数签名。
def stereoCalibrate(
objectPoints,
imagePoints1,
imagePoints2,
cameraMatrix1,
distCoeffs1,
cameraMatrix2,
distCoeffs2,
imageSize
[, R[, T[, E[, F[, flags[, criteria]]]]]]
) -> (
retval,
cameraMatrix1,
distCoeffs1,
cameraMatrix2,
distCoeffs2,
R, T, E, F
)
复制代码
接下来我们一个一个的说明这些参数和返回值的意义:
参数
- objectPoints:棋盘格角点的世界坐标,这里规定每个格子边长为 1 个单位。
w, h = 9, 6 # 棋盘格两个方向的角点数量
def obj_points():
points = []
for x in range(w):
for y in range(h):
points.append([x, y, 0])
return [points]*len(imgs)
# 还有一种简洁的写法,与上面等价
def obj_ponits():
points = np.zeros((np.prod((w, h)), 3), np.float32)
points[:, :2] = np.indices((w, h)).T.reshape(-1, 2)
return [points]*len(imgs)
复制代码
- imagePoints:棋盘格角点的图像坐标,需要通过角点检测来得到。
def detect_corners(img):
ok, corners = cv2.findChessboardCorners(img, self.board_size)
assert ok
cv2.cornerSubPix(img, corners, (11, 11), (-1, -1), (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 0.01))
return corners
img_points = [detect_corners(img).reshape(-1, 2) for img in imgs]
复制代码
- cameraMatrix:相机内参矩阵,也就是上篇文章提到的 矩阵,需要通过 objectPoints 和 imagePoints 来初始化,这里得到的 只是一个初始值,是不精确的还需要多次迭代计算。
K = cv2.initCameraMatrix2D(obj_ponits, img_points, img_size, 0)
复制代码
-
distCoeffs:畸变参数,需要传入初始值用于后续迭代计算,也可以直接传空。
-
imageSize:图像的大小 ,例如 。
-
R、T、E、F:传出参数,不需要传。
-
flags:
- CALIB_FIX_INTRINSIC:固定传入的 cameraMatrix 和 distCoeffs,只计算 R、T、E、F。
- CALIB_USE_INTRINSIC_GUESS:以传入的 cameraMatrix 和 distCoeffs 为初始值开始迭代。
- CALIB_FIX_PRINCIPAL_POINT:迭代时固定主点。
- CALIB_FIX_FOCAL_LENGTH:迭代时固定焦距。
- CALIB_FIX_ASPECT_RATIO:固定 和 比值。
- CALIB_SAME_FOCAL_LENGTH:强制保持两个相机焦距相同。
- CALIB_ZERO_TANGENT_DIST:切向畸变保持为零。
- CALIB_RATIONAL_MODEL:使用更精确的畸变模型。
- CALIB_FIX_K*:迭代时不改变相应的畸变参数。
-
criteria:迭代的终止条件。
criteria = (
cv2.TERM_CRITERIA_EPS | # 达到精度
cv2.TERM_CRITERIA_COUNT, # 达到迭代次数
100, # 迭代次数
1e-5 # 精度
)
复制代码
返回值
- retval:误差(root mean square 本文用不到)。
- cameraMatrix: 矩阵。
- distCoeffs:畸变参数。
- R:相机间的旋转矩阵(上篇文章提过的 )。
- T:相机间的平移矩阵(上篇文章提过的 )。
- E:本质矩阵(essential matrix 本文用不到)。
- F:基础矩阵(fundamental matrix 本文用不到)。
校正
先来看立体校正的函数签名。
def stereoRectify(
cameraMatrix1,
distCoeffs1,
cameraMatrix2,
distCoeffs2,
imageSize,
R,
T
[, R1[, R2[, P1[, P2[, Q[, flags[, alpha[, newImageSize]]]]]]]]
) -> (
R1,
R2,
P1,
P2,
Q,
validPixROI1,
validPixROI2
)
复制代码
还是一个一个的说明这些参数和返回值,上面说过的就不再重复了。
参数
- flags:
- CALIB_ZERO_DISPARITY:让两个像主点在校正后有相同的图像坐标。
- alpha:
- 0:校正后的图像被改变,没有黑色区域。
- 1:保留拍摄的所有像素,会有黑边。
返回值
- R:旋转矩阵(上篇文章提过)。
- P:投影矩阵(上篇文章提过)。
- Q:视差深度映射矩阵(disparity-to-depth mapping matrix 本文用不到)。
- validPixROI:有效区域,如果alpha = 0,有效区域即全部像素。
End
上面的两个函数,计算出了 这四个矩阵,用它们就可以不失真的(指没有畸变)、校正的(指同名点垂直方向坐标一致)的图像。
本文只是说了说函数的用法(具体算法是怎么运行的我其实一概不知),关于函数的参数的说明其实官方文档都有,如果本文有不专业或不准确的地方万望指出。
本文代码地址:github.com/suqinglee/C…