别再瞎调了!用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)常见问题排查如果返回retFalse检查棋盘格是否完整可见确认输入的(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 世界坐标系的建立标定需要建立棋盘格的三维世界坐标。假设棋盘格在Z0平面每个格子大小为2mmobjp 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点对应后调用calibrateCameraret, 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不同理想情况下fxfy但实际中可能因以下原因不同像素不是完美的正方形镜头存在非对称畸变图像传感器安装倾斜两者差异通常在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)这个转换是许多高级应用如线激光平面标定的基础。例如在激光三角测量中我们需要将激光线上的像素点转换为三维空间中的射线方向。