别再瞎调了!用OpenCV+Python搞定相机标定,手把手教你从棋盘格到内参矩阵
从棋盘格到内参矩阵:OpenCV相机标定实战指南
当你第一次尝试用OpenCV进行相机标定时,是否遇到过这些困惑:为什么fx和fy不一样?棋盘格到底要拍多少张才够?亚像素优化真的有必要吗?本文将用一套完整的Python代码流程,带你避开新手常踩的坑,真正理解相机标定背后的原理。
1. 准备工作:棋盘格与拍摄技巧
1.1 棋盘格的选择与打印
棋盘格是相机标定的基础工具,但很多人在第一步就犯了错。关键不在于棋盘格本身,而在于内角点的数量。如果你使用11x8的棋盘格,实际内角点是10x7(行列各减1)。这个数字需要在代码中明确指定。
推荐使用calib.io生成棋盘格:
- 选择checkerboard类型
- 设置rows和columns为实际棋盘格的行列数
- checker width设置每个格子的实际物理尺寸(如2mm)
打印时务必选择100%比例,并用游标卡尺测量实际打印尺寸是否准确。我曾遇到因打印机自动缩放导致标定失败的案例,最终发现打印出的格子实际为1.95mm而非标称的2mm。
1.2 拍摄技巧与常见误区
拍摄棋盘格照片时,新手常犯三个错误:
- 拍摄角度单一:所有照片都是正面拍摄,缺乏视角变化
- 数量不足:通常需要15-20张不同角度的照片
- 模糊或过曝:确保棋盘格线条清晰可见
实际操作建议:
- 将棋盘格平放在桌面,从不同高度和角度拍摄
- 尝试倾斜棋盘格(30°-60°)
- 确保棋盘格占画面1/3以上面积
- 避免强光反射导致的过曝
# 示例:批量读取棋盘格图像
image_files = glob.glob('calibration_images/*.jpg')
print(f"找到{len(image_files)}张标定图片")
2. 角点检测与亚像素优化
2.1 基础角点检测
OpenCV提供了多种角点检测方法,findChessboardCornersSB是较新且稳定的选择:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCornersSB(gray, (w, h), None)
if ret:
cv2.drawChessboardCorners(img, (w, h), corners, ret)
cv2.imshow('Corners', img)
cv2.waitKey(500)
常见问题排查:
- 如果返回
ret=False,检查棋盘格是否完整可见 - 确认输入的
(w, h)是内角点数量而非棋盘格格子数 - 尝试调整图像对比度或使用直方图均衡化
2.2 亚像素优化的必要性
原始角点坐标是整数像素级的,亚像素优化可以将精度提升到0.1像素级别。这对于高精度应用(如线激光测量)至关重要:
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
参数说明:
(11, 11):搜索窗口大小(-1, -1):死区半径(-1表示自动计算)criteria:最大迭代30次或精度达到0.001时停止
3. 相机标定核心流程
3.1 世界坐标系的建立
标定需要建立棋盘格的三维世界坐标。假设棋盘格在Z=0平面,每个格子大小为2mm:
objp = np.zeros((w*h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2) * 2.0 # 2mm格子
3.2 执行标定计算
收集足够多的3D-2D点对应后,调用calibrateCamera:
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
输出参数解读:
mtx:内参矩阵 [[fx, 0, cx], [0, fy, cy], [0, 0, 1]]dist:畸变系数 [k1, k2, p1, p2, k3]rvecs,tvecs:每张图片的旋转和平移向量
3.3 为什么fx和fy不同?
理想情况下fx=fy,但实际中可能因以下原因不同:
- 像素不是完美的正方形
- 镜头存在非对称畸变
- 图像传感器安装倾斜
两者差异通常在1%以内。如果差异过大(如超过5%),可能需要检查标定过程是否正确。
4. 结果验证与应用
4.1 重投影误差分析
重投影误差是评估标定质量的关键指标:
mean_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2)/len(imgpoints2)
mean_error += error
print(f"平均重投影误差: {mean_error/len(objpoints):.2f}像素")
误差小于0.5像素通常认为标定质量良好。
4.2 内参矩阵的实际应用
内参矩阵用于将像素坐标转换为归一化相机坐标:
def pixel_to_normalized(u, v, mtx):
fx, fy = mtx[0, 0], mtx[1, 1]
cx, cy = mtx[0, 2], mtx[1, 2]
x = (u - cx) / fx
y = (v - cy) / fy
return (x, y)
这个转换是许多高级应用(如线激光平面标定)的基础。例如,在激光三角测量中,我们需要将激光线上的像素点转换为三维空间中的射线方向。
更多推荐


所有评论(0)