1. 双目相机标定基础概念
双目视觉就像人类的双眼,通过两个摄像头从不同角度观察同一场景,获取深度信息。但在这之前,我们需要先解决一个关键问题:如何让两个摄像头"对齐"?这就是双目相机标定的核心目标。
我刚开始接触这个领域时,最大的困惑是:为什么简单的摄像头组合不能直接用于立体视觉?后来发现,每个镜头都存在独特的畸变特性,就像每个人都有不同的指纹。OpenCV提供的标定工具,本质上就是在为相机建立"身份证",记录下这些光学特性。
棋盘格在这个过程中扮演着重要角色。它就像一把标准的尺子,帮助我们量化镜头的畸变程度。标准的OpenCV棋盘格有9x6个内部角点(注意不是格子数),每个格子建议打印成24mm大小。实测发现,使用A4纸打印时,确保棋盘平整非常关键——我有次用了稍有褶皱的纸板,标定误差直接增加了30%。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 硬件准备与环境搭建
2.1 摄像头选型与设置
市面上几十元的USB双目摄像头就能满足基础需求,但要注意分辨率的匹配。我手头的这个摄像头实际物理分辨率是1280x480(两个640x480摄像头并列),但系统可能错误识别为2560x480。通过这段代码可以验证真实分辨率:
python复制import cv2
cap = cv2.VideoCapture(1)
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
print(f"实际分辨率: {width}x{height}")
2.2 Python环境配置
推荐使用conda创建独立环境:
bash复制conda create -n stereo python=3.8
conda activate stereo
pip install opencv-contrib-python numpy matplotlib
特别注意要安装opencv-contrib-python而非基础版,因为一些立体视觉算法在contrib模块中。我曾在项目中浪费半天时间排查为什么cv2.stereoRectify不存在,最后发现是装错了包。
3. 图像采集实战技巧
3.1 棋盘格拍摄要点
采集图像时要注意:
- 棋盘格需要覆盖整个视野区域,特别是边缘
- 至少15组不同角度(倾斜/旋转/远近)
- 避免强光反射,我有次因棋盘格反光导致角点检测失败
这段自动保存脚本非常实用:
python复制import cv2, os
cap = cv2.VideoCapture(1)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
save_count = 0
while True:
ret, frame = cap.read()
cv2.imshow('Preview', frame)
key = cv2.waitKey(1)
if key == 32: # 空格键触发保存
left_img = frame[:, :640]
right_img = frame[:, 640:]
cv2.imwrite(f'left_{save_count}.jpg', left_img)
cv2.imwrite(f'right_{save_count}.jpg', right_img)
save_count += 1
elif key == 27: # ESC退出
break
3.2 角点检测优化
原始角点检测可能不够精确,建议使用亚像素优化:
python复制criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners = cv2.cornerSubPix(gray_img, corners, (11,11), (-1,-1), criteria)
4. 标定参数计算详解
4.1 单目标定过程
每个摄像头需要单独标定获取内参:
python复制ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
obj_points, img_points,
image_size, None, None)
关键参数说明:
- mtx:3x3相机矩阵 [[fx,0,cx], [0,fy,cy], [0,0,1]]
- dist:畸变系数 [k1,k2,p1,p2,k3]
4.2 双目标定核心算法
立体标定的核心是计算两个相机间的几何关系:
python复制flags = cv2.CALIB_FIX_INTRINSIC
R, T = cv2.stereoCalibrate(
obj_points, img_points_left, img_points_right,
mtx_left, dist_left, mtx_right, dist_right,
image_size, flags=flags)[5:7]
这里有个坑:CALIB_FIX_INTRINSIC表示保持单目标定结果不变,只优化R和T。如果去掉这个flag,算法会重新计算所有参数,但可能导致结果不稳定。
5. 图像校正与效果验证
5.1 校正映射生成
python复制R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
mtx_left, dist_left, mtx_right, dist_right,
image_size, R, T, alpha=0)
map_left = cv2.initUndistortRectifyMap(
mtx_left, dist_left, R1, P1, image_size, cv2.CV_16SC2)
alpha参数控制裁剪程度:
- 1:保留所有原始像素(可能有黑边)
- 0:裁剪所有无效区域
- -1:自动平衡
5.2 对齐效果验证
校正后的图像应该满足:
- 水平极线对齐(同一行像素对应同一物理高度)
- 垂直方向无偏移
测试代码:
python复制rectified_left = cv2.remap(left_img, map_left[0], map_left[1], cv2.INTER_LINEAR)
rectified_right = cv2.remap(right_img, map_right[0], map_right[1], cv2.INTER_LINEAR)
combined = np.hstack((rectified_left, rectified_right))
for y in range(0, combined.shape[0], 50):
cv2.line(combined, (0,y), (combined.shape[1],y), (0,255,0), 1)
6. 常见问题排查指南
6.1 标定误差过大
典型表现:
- 重投影误差 > 0.5像素
- 校正后图像严重扭曲
解决方案:
- 检查棋盘格是否平整
- 增加采样图片数量(建议20+)
- 验证角点检测是否准确
6.2 立体对齐失败
可能原因:
- 左右相机分辨率不一致
- 标定时相机位置移动
- 光照条件变化过大
有个实用技巧:在校正后的图像上画水平线,观察相同特征是否在同一直线上。我开发时发现y方向有3个像素的偏移,最终发现是摄像头物理安装存在轻微倾斜。
7. 进阶优化方向
7.1 自动标定流程
可以改进为全自动标定:
python复制def auto_calibrate(video_source, num_required=20):
cap = cv2.VideoCapture(video_source)
saved = 0
while saved < num_required:
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
ret, corners = find_chessboard(gray)
if ret and is_good_pose(frame, corners):
save_images(frame)
saved += 1
7.2 实时校正实现
对于实时应用,建议预计算映射关系:
python复制# 预计算
map_left = ... # 标定时计算
# 实时处理
while True:
frame = get_frame()
rectified = cv2.remap(frame, map_left[0], map_left[1], cv2.INTER_LINEAR)
process(rectified)
在实际机器人项目中,我发现预处理映射能使处理速度提升3倍以上。不过要注意,分辨率改变时需要重新标定。
