别再死记硬背!用Python+OpenCV手把手带你标定相机内参外参(附完整代码)

张开发
2026/4/18 18:31:18 15 分钟阅读

分享文章

别再死记硬背!用Python+OpenCV手把手带你标定相机内参外参(附完整代码)
PythonOpenCV实战从零完成相机标定的完整流程与代码解析相机标定是计算机视觉中一项基础但至关重要的技术。无论是构建增强现实应用、开发自动驾驶系统还是进行三维重建准确的相机参数都是确保结果可靠的前提。本文将带你用Python和OpenCV一步步实现相机标定避开理论推导的复杂数学专注于实际应用中的关键技巧。1. 准备工作与环境配置在开始标定前我们需要准备合适的硬件和软件环境。标定过程需要一个稳定的相机手机或工业相机均可和一个棋盘格标定板。棋盘格应当打印在平整的表面上黑白方格对比鲜明。首先安装必要的Python库pip install opencv-python numpy matplotlibOpenCV提供了完整的相机标定工具链从角点检测到参数计算一应俱全。我们主要会用到以下模块cv2.findChessboardCorners检测棋盘格角点cv2.cornerSubPix提高角点检测精度cv2.calibrateCamera计算相机内参和外参cv2.projectPoints验证标定结果的准确性标定板选择建议使用7x9或更大尺寸的棋盘格角点数越多越好打印在哑光材质上避免反光确保棋盘格平整无褶皱方格边长最好在2-5cm之间根据相机视场调整2. 数据采集获取高质量的标定图像标定的准确性很大程度上取决于输入图像的质量。我们需要从不同角度和距离拍摄15-20张棋盘格照片覆盖整个相机视场。采集时的关键技巧角度多样性让棋盘格出现在图像的不同位置包括边缘和角落距离变化包含远、中、近不同距离的拍摄倾斜角度让棋盘格有不同程度的倾斜但不要过度光照条件保持均匀照明避免强烈阴影或反光import cv2 import glob # 读取所有标定图像 images glob.glob(calibration_images/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 查找棋盘格角点 ret, corners cv2.findChessboardCorners(gray, (7,9), None) if ret: # 提高角点检测精度 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners2 cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria) # 可视化角点 cv2.drawChessboardCorners(img, (7,9), corners2, ret) cv2.imshow(Corners, img) cv2.waitKey(500)提示在采集图像时可以实时显示角点检测结果确保每张图像都能被正确识别。如果某张图像检测失败应该重新拍摄。3. 构建标定数据从图像到参数有了高质量的图像后我们需要准备标定所需的数据结构。这包括对象点棋盘格在3D空间中的坐标假设Z0图像点检测到的棋盘格角点在2D图像中的坐标图像尺寸图像的宽度和高度import numpy as np # 准备对象点(0,0,0), (1,0,0), (2,0,0), ..., (6,8,0) objp np.zeros((7*9, 3), np.float32) objp[:,:2] np.mgrid[0:7,0:9].T.reshape(-1,2) # 存储所有图像的对象点和图像点 objpoints [] # 3D点 imgpoints [] # 2D点 for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, (7,9), None) if ret: objpoints.append(objp) corners2 cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria) imgpoints.append(corners2)参数说明objp定义了棋盘格在3D世界坐标系中的位置Z坐标始终为0每个方格的实际大小不影响结果因为我们只关心比例关系图像点通过亚像素级优化提高了定位精度4. 执行相机标定获取内参和外参有了充分的数据准备现在可以调用OpenCV的calibrateCamera函数进行标定ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) print(相机矩阵:\n, mtx) print(\n畸变系数:, dist.ravel())输出参数解析参数描述典型值mtx3x3相机内参矩阵[[fx, 0, cx], [0, fy, cy], [0, 0, 1]]dist畸变系数(k1,k2,p1,p2[,k3])[k1, k2, p1, p2, k3]rvecs每张图像的旋转向量列表形式tvecs每张图像的平移向量列表形式关键参数调整技巧使用cv2.CALIB_FIX_K3标志可以固定k3畸变系数对于广角镜头可能需要考虑更高阶的畸变模型标定质量与图像数量和多样性直接相关5. 评估标定结果重投影误差分析标定完成后我们需要验证结果的准确性。重投影误差是衡量标定质量的重要指标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(\n平均重投影误差: {:.2f}像素.format(mean_error/len(objpoints)))误差解读0.5像素优秀0.5-1像素良好1-2像素可接受2像素可能需要重新标定如果误差过大可以尝试增加标定图像数量特别是不同角度提高角点检测精度使用更高质量的标定板检查标定板是否平整调整calibrateCamera的标志参数6. 实际应用图像去畸变与位姿估计获得准确的相机参数后我们可以进行两项重要应用图像去畸变和相机位姿估计。图像去畸变# 读取测试图像 img cv2.imread(test_image.jpg) h, w img.shape[:2] # 优化相机矩阵 newcameramtx, roi cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h)) # 去畸变 dst cv2.undistort(img, mtx, dist, None, newcameramtx) # 裁剪图像 x, y, w, h roi dst dst[y:yh, x:xw] cv2.imwrite(calibrated_result.jpg, dst)相机位姿估计# 对于新图像可以估计相机相对于标定板的位姿 ret, corners cv2.findChessboardCorners(gray, (7,9), None) if ret: # 计算位姿 _, rvec, tvec cv2.solvePnP(objp, corners, mtx, dist) # 可视化 img cv2.drawFrameAxes(img, mtx, dist, rvec, tvec, 0.1) cv2.imshow(Pose Estimation, img) cv2.waitKey(0)7. 高级技巧与常见问题解决在实际项目中我们可能会遇到各种特殊情况。以下是一些经验分享处理低质量图像使用高斯模糊预处理减少噪声影响调整findChessboardCorners的阈值参数手动验证角点检测结果# 预处理示例 gray cv2.GaussianBlur(gray, (5,5), 0) ret, corners cv2.findChessboardCorners(gray, (7,9), None, cv2.CALIB_CB_ADAPTIVE_THRESH cv2.CALIB_CB_NORMALIZE_IMAGE)多相机系统标定需要同时标定多个相机之间的相对位置使用同一标定板在不同相机中的视图计算相机间的基础矩阵或单应性矩阵标定参数保存与加载import pickle # 保存标定结果 data {camera_matrix: mtx, dist_coeff: dist} with open(calibration.pkl, wb) as f: pickle.dump(data, f) # 加载标定结果 with open(calibration.pkl, rb) as f: data pickle.load(f) mtx data[camera_matrix] dist data[dist_coeff]标定板不可见时的处理考虑使用AprilTag或ArUco标记替代传统棋盘格这些标记在部分遮挡情况下仍能稳定检测OpenCV提供了相应的检测函数# ArUco标记检测示例 aruco_dict cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250) parameters cv2.aruco.DetectorParameters_create() corners, ids, _ cv2.aruco.detectMarkers(gray, aruco_dict, parametersparameters)在完成标定后我发现最常犯的错误是没有充分覆盖相机的整个视场。特别是在边缘区域缺乏足够的样本会导致这些区域的畸变校正效果不佳。另一个教训是标定板的质量——使用激光打印的高精度棋盘格比普通喷墨打印的结果要稳定得多。

更多文章