1. 双目相机标定与图像校正
任务目标
本案例使用棋盘格标定板对双目相机进行标定,获取左右目相机的内参矩阵、畸变系数,以及双目立体校正所需的旋转矩阵、平移向量和重投影矩阵,最终输出校正后的双目图像对。校正后的图像对应满足极线平行且行对齐,即左右图像中同一物点位于同一像素行。
环境与依赖
需要 OpenCV (4.x) 和 NumPy。若使用真实双目相机,需准备10~20张不同位姿的棋盘格图像对;若仅验证流程,可使用下方代码生成的合成棋盘格图像对。
关键完整代码
import cv2
import numpy as np
# ========== 1. 合成棋盘格图像对(用于无相机时的流程验证) ==========
def make_synth_pair(rows=6, cols=9, square=0.03, img_size=(640, 480)):
"""生成一对带轻微旋转/平移差异的棋盘格图像,模拟双目视图"""
pattern = np.zeros((rows*square, cols*square), np.float32)
pattern[::2, ::2] = 1.0
pattern[1::2, 1::2] = 1.0
pattern_img = (pattern * 255).astype(np.uint8)
pattern_img = cv2.resize(pattern_img, img_size)
pattern_img = cv2.cvtColor(pattern_img, cv2.COLOR_GRAY2BGR)
# 左图:原图;右图:轻微透视变换模拟右目视角
h, w = img_size
src_pts = np.float32([[0,0],[w-1,0],[0,h-1],[w-1,h-1]])
dst_pts = np.float32([[20,10],[w-30,5],[10,h-20],[w-15,h-25]])
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
right_img = cv2.warpPerspective(pattern_img, M, (w,h))
return pattern_img, right_img
# ========== 2. 角点提取与标定 ==========
def calibrate_stereo(img_pairs, pattern_size=(9,6), square_size=0.03):
"""img_pairs: [(left_img, right_img), ...]"""
objp = np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) * square_size
obj_points = [] # 3D点
l_img_points = [] # 左图2D点
r_img_points = [] # 右图2D点
for l_img, r_img in img_pairs:
gray_l = cv2.cvtColor(l_img, cv2.COLOR_BGR2GRAY)
gray_r = cv2.cvtColor(r_img, cv2.COLOR_BGR2GRAY)
ret_l, corners_l = cv2.findChessboardCorners(gray_l, pattern_size, None)
ret_r, corners_r = cv2.findChessboardCorners(gray_r, pattern_size, None)
if ret_l and ret_r:
obj_points.append(objp)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners_l = cv2.cornerSubPix(gray_l, corners_l, (11,11), (-1,-1), criteria)
corners_r = cv2.cornerSubPix(gray_r, corners_r, (11,11), (-1,-1), criteria)
l_img_points.append(corners_l)
r_img_points.append(corners_r)
if len(obj_points) < 3:
raise ValueError("有效图像对不足,至少需要3对")
# 单目标定
ret_l, mtx_l, dist_l, _, _ = cv2.calibrateCamera(obj_points, l_img_points, gray_l.shape[::-1], None, None)
ret_r, mtx_r, dist_r, _, _ = cv2.calibrateCamera(obj_points, r_img_points, gray_r.shape[::-1], None, None)
# 双目标定(获取相对位姿)
ret_s, mtx_l, dist_l, mtx_r, dist_r, R, T, E, F = cv2.stereoCalibrate(
obj_points, l_img_points, r_img_points,
mtx_l, dist_l, mtx_r, dist_r,
gray_l.shape[::-1], flags=cv2.CALIB_FIX_INTRINSIC)
return mtx_l, dist_l, mtx_r, dist_r, R, T
# ========== 3. 立体校正与输出 ==========
def rectify_and_save(l_img, r_img, mtx_l, dist_l, mtx_r, dist_r, R, T, out_prefix="rectified"):
h, w = l_img.shape[:2]
R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
mtx_l, dist_l, mtx_r, dist_r, (w,h), R, T, alpha=0)
map1_l, map2_l = cv2.initUndistortRectifyMap(mtx_l, dist_l, R1, P1, (w,h), cv2.CV_32FC1)
map1_r, map2_r = cv2.initUndistortRectifyMap(mtx_r, dist_r, R2, P2, (w,h), cv2.CV_32FC1)
rect_l = cv2.remap(l_img, map1_l, map2_l, cv2.INTER_LINEAR)
rect_r = cv2.remap(r_img, map1_r, map2_r, cv2.INTER_LINEAR)
# 水平拼接并画辅助线验证行对齐
vis = np.hstack([rect_l, rect_r])
for y in range(0, h, 30):
cv2.line(vis, (0, y), (2*w, y), (0, 255, 0), 1)
cv2.imwrite(f"{out_prefix}_left.jpg", rect_l)
cv2.imwrite(f"{out_prefix}_right.jpg", rect_r)
cv2.imwrite(f"{out_prefix}_compare.jpg", vis)
return Q # 重投影矩阵,用于后续深度计算
# ========== 4. 主流程 ==========
if __name__ == "__main__":
# 生成8对合成图像用于标定
pairs = [make_synth_pair() for _ in range(8)]
mtx_l, dist_l, mtx_r, dist_r, R, T = calibrate_stereo(pairs)
# 用其中一对做校正演示
l_img, r_img = pairs[0]
Q = rectify_and_save(l_img, r_img, mtx_l, dist_l, mtx_r, dist_r, R, T)
print("左目内参:\n", mtx_l)
print("右目内参:\n", mtx_r)
print("旋转矩阵 R:\n", R)
print("平移向量 T:\n", T.ravel())
print("校正对比图已保存为 rectified_compare.jpg")
运行与验证
运行方式:直接执行 python stereo_calib.py(将上述代码保存为该文件)。若使用真实相机,将 make_synth_pair() 替换为读取真实左右图像对的代码,并确保图像中棋盘格角点完整可见。
验证方法:
- 程序正常结束且打印出左右目内参矩阵、旋转矩阵
R和平移向量T; - 打开
rectified_compare.jpg,观察绿色水平辅助线:校正后左右图像中的棋盘格角点应落在同一条水平线上(行对齐); - 若使用真实图像,可进一步用鼠标点击左图某个角点,检查右图同一行是否出现对应角点。
通过标准:校正对比图中棋盘格横线无明显倾斜,左右图对应角点行坐标差不超过2个像素。
常见问题
- 角点检测失败:确保棋盘格为奇数×偶数(如9×6),且图像中棋盘完整无遮挡;
- 标定结果跳动大:增加图像对数量至15~20对,并覆盖不同角度和距离;
- 校正后图像畸变严重:检查
alpha=0参数,若希望保留全部像素可改为alpha=1。