张正友标定法到底在干啥?用大白话和Python代码带你理解相机畸变与内参矩阵 张正友标定法到底在干啥用大白话和Python代码带你理解相机畸变与内参矩阵想象你拿着手机拍一张棋盘格照片却发现格子的直线变成了波浪线——这就是镜头畸变在作怪。张正友标定法的神奇之处在于它只需要你对着棋盘格拍几张照片就能算出让图像改邪归正的数学密码。本文将用厨房里的比喻和可运行的Python代码带你亲手揭开这个计算机视觉基础算法的神秘面纱。1. 相机如何把三维世界压扁成二维照片当光线穿过镜头时就像透过一个不均匀的透明水球。理想情况下物体上的直线成像后应该保持笔直但实际上会出现两种典型变形桶形畸变像把图片吸进了桶底直线向外弯曲枕形畸变像把图片压在了枕头下直线向内凹陷import cv2 import numpy as np import matplotlib.pyplot as plt # 生成理想网格图像 ideal_grid np.zeros((400, 400), dtypenp.uint8) ideal_grid[::40, :] 255 ideal_grid[:, ::40] 255 # 模拟畸变效果 h, w ideal_grid.shape[:2] map_x np.zeros((h, w), np.float32) map_y np.zeros((h, w), np.float32) for i in range(h): for j in range(w): # 桶形畸变 dx (j - w/2) / (w/2) dy (i - h/2) / (h/2) r np.sqrt(dx*dx dy*dy) map_x[i,j] j (dx * r * r) * (w/4) map_y[i,j] i (dy * r * r) * (h/4) distorted cv2.remap(ideal_grid, map_x, map_y, cv2.INTER_LINEAR) plt.figure(figsize(10,5)) plt.subplot(121), plt.imshow(ideal_grid, cmapgray), plt.title(理想网格) plt.subplot(122), plt.imshow(distorted, cmapgray), plt.title(桶形畸变效果) plt.show()这段代码展示了如何用数学方法模拟镜头畸变。运行后会看到右侧图像中的直线变成了曲线这就是我们需要校正的畸变效果。2. 相机内参矩阵镜头的身份证每台相机都有独特的内部参数就像人的指纹。内参矩阵主要包含三个关键信息参数物理意义典型值f_xx轴焦距像素单位800-2000f_yy轴焦距像素单位800-2000c_x主点x坐标图像中心偏移图像宽度/2c_y主点y坐标图像中心偏移图像高度/2用矩阵表示就是[[f_x, 0, c_x], [0, f_y, c_y], [0, 0, 1 ]]提示现代智能手机通常f_x和f_y值接近但专业相机可能因传感器特性而存在差异。3. 动手实践用Python实现简易标定让我们用OpenCV实现一个简化版的张氏标定流程。准备至少10张不同角度拍摄的棋盘格图片建议使用8x6的棋盘格。import glob import cv2 import numpy as np # 设置棋盘格规格 pattern_size (7, 6) # 内部角点数量 square_size 2.5 # 棋盘格实际尺寸(cm) # 准备对象点(0,0,0), (1,0,0), (2,0,0) ..., (6,5,0) 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 # 存储对象点和图像点 objpoints [] # 真实3D点 imgpoints [] # 图像2D点 images glob.glob(calibration_photos/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 查找棋盘格角点 ret, corners cv2.findChessboardCorners(gray, pattern_size, None) if ret: objpoints.append(objp) # 提高角点检测精度 corners_refined cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)) imgpoints.append(corners_refined) # 可视化角点 cv2.drawChessboardCorners(img, pattern_size, corners_refined, ret) cv2.imshow(Corners, img) cv2.waitKey(500) cv2.destroyAllWindows() # 执行相机标定 ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) print(内参矩阵:\n, mtx) print(\n畸变系数:, dist.ravel())这个脚本会输出相机的内参矩阵和畸变系数。关键步骤解析角点检测自动识别棋盘格交叉点亚像素优化将角点定位精度提高到子像素级别参数求解通过最小二乘法计算最优内参4. 畸变校正让图像改邪归正得到标定参数后我们可以校正新拍摄的图像# 加载测试图像 test_img cv2.imread(test_photo.jpg) h, w test_img.shape[:2] # 优化相机矩阵和ROI new_mtx, roi cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h)) x, y, w, h roi # 方法1使用cv2.undistort dst cv2.undistort(test_img, mtx, dist, None, new_mtx) dst dst[y:yh, x:xw] # 方法2使用remapping mapx, mapy cv2.initUndistortRectifyMap(mtx, dist, None, new_mtx, (w,h), 5) dst cv2.remap(test_img, mapx, mapy, cv2.INTER_LINEAR) dst dst[y:yh, x:xw] # 比较结果 cv2.imshow(Original, test_img) cv2.imshow(Corrected, dst) cv2.waitKey(0) cv2.destroyAllWindows()两种校正方法的区别undistort简单直接适合单张图像处理remap先计算映射关系适合视频流实时处理5. 标定质量评估你的参数靠谱吗好的标定结果应该满足以下指标重投影误差一般应小于0.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(平均重投影误差: {:.2f}像素.format(mean_error/len(objpoints)))参数合理性检查f_x/f_y应与相机焦距mm乘以传感器像素密度像素/mm接近(c_x, c_y)应接近图像中心畸变系数k1通常绝对值最大k2/k3依次递减视觉验证校正后的棋盘格直线应明显变直图像边缘不应出现明显黑边或过度拉伸在实际项目中我发现使用20-30张覆盖整个视野的标定图像并确保棋盘格出现在图像的不同区域中心、四角、边缘能得到最稳定的标定结果。