资讯动态

张正友相机标定:棋盘格与OpenCV内参解算全流程

发布时间:2026/10/19 15:50:45 来源:尧图企业网站定制
简介张正友相机标定Opencv实现完整工程包面向计算机视觉初学者和需要快速集成相机标定功能的开发者。工程基于VS2015开发含有详细注释的C源码、一键运行的批处理脚本以及可直接运行的exe程序实现相机内参、外参、旋转与平移矩阵输出并带有标定效果评价模块能利用标定结果对原始棋盘图进行畸变矫正。资源另附棋盘格打印PDF以及手机、摄像头拍摄的共30张测试图片视角覆盖多距离多角度便于实际验证算法稳定性。包内共107个文件以jpg图片、cpp源码、exe程序、txt配置为主另有mat脚本用于与Matlab标定结果对比打包大小约95.67MB目录结构清晰并包含VS2015工程配置与编译中间文件无需额外配置即可运行。目前已有6817人学习下载标定结果与Matlab非常接近适合入门学习、项目开发或论文实验参考。1. 张正友相机标定先搞清楚这套资源能帮你解决什么问题做视觉相关开发的工程师绕不开张正友标定。它不用笨重的三维标定块一张打印出来的棋盘格加十几张不同角度的照片就能把相机内参和畸变系数解出来。这套资源把棋盘格打印 PDF 和一组测试图片直接打包好了意味着你不需要满网找标定板文件也不用为采集图片发愁从打印棋盘格到跑出 fx、fy、cx、cy 和 k1、k2、p1、p2 是一条闭环链路。适合刚接到视觉测量、机器人抓取、AR 注册任务的工程师也适合学校里需要快速跑通畸变校正验证的实验场景。拿到手先跑一遍测试图再替换自己的图基本不会卡壳。2. 张正友标定的原理与 OpenCV 映射为什么棋盘格加平面照片就能解出内参2.1 张正友方法的核心单应矩阵与正交约束相机标定的本质是求「三维世界坐标 → 像素坐标」的映射关系。完整的小孔成像公式是s * [u, v, 1]^T K * [r1 r2 r3 t] * [X, Y, Z, 1]^T因为棋盘格是平面我们让 Z 0公式就简化成s * [u, v, 1]^T K * [r1 r2 t] * [X, Y, 1]^T中间那个 3x3 矩阵就是单应矩阵 H。每一张棋盘格照片能提供一个 H而 H 同时受内参 K 和这个视角的外参旋转 平移影响。张正友方法的数学巧妙点在于旋转向量 r1 和 r2 本身满足正交约束于是每个 H 都能对内参 K 提供两个约束方程。拍 3 张以上不同角度的棋盘格理论上就能把 fx、fy、cx、cy 解出来。实际工程里我们不会只拍 3 张因为角点提取有噪声畸变参数也没进模型。张正友的完整流程是先利用单应矩阵求出一个闭式解作为初值再用非线性最小二乘把径向畸变和切向畸变一起迭代优化进去最后得到一组全局最优的参数。这就是为什么 OpenCV 的 calibrateCamera 会返回重投影误差 RMS它衡量的是优化收敛的好坏。这里需要解释几个关键量K 是 3x3 内参矩阵fx、fy 是焦距相关的尺度因子单位是像素cx、cy 是光心在像素坐标系中的位置理应在图像中心附近但如果镜头装配有偏差会偏离中心几个像素甚至更多。dist 是畸变系数向量OpenCV 默认输出 5 个参数 [k1, k2, p1, p2, k3]其中 k1、k2、k3 是径向畸变系数p1、p2 是切向畸变系数。径向畸变在图像边缘表现最明显表现为直线变弯切向畸变来自镜头与 sensor 不完全平行表现为成像平面倾斜。2.2 OpenCV 标定流程的四个阶段与资源包内容对应OpenCV 把张正友方法封装成了标准流程完整落地分四个阶段角点提取findChessboardCorners 在灰度图中定位棋盘格内角点再用 cornerSubPix 做亚像素细化让角点坐标精度从整数像素提升到亚像素级。世界坐标生成根据棋盘格内角点行列数和实际格边长生成每个角点对应的三维世界坐标也就是 objpoints。参数求解calibrateCamera 读取多组对应点迭代计算内参、畸变系数以及每张图的外参。验证校正用 undistort 或 initUndistortRectifyMap remap 对图片去畸变直观验证直线是否被拉直。这套资源包的构成正好对应这四个阶段棋盘格 PDF 负责阶段 1 的标定板测试图片负责阶段 1 的输入标定脚本把阶段 2 到阶段 4 串起来。我拿到手后直接把 PDF 打印出来先用测试图片跑通整条链路再换自己的相机采集数据没有遇到任何缺文件或者格式不匹配的问题。选型上棋盘格是 OpenCV 原生支持最完整的标定板方案。圆点标定板圆心检测精度高但要做椭圆拟合校正OpenCV 内置支持不如棋盘格顺畅ArUco 板适合做姿态估计和单目标定但标定精度对图像清晰度更敏感。常规视觉测量场景棋盘格就是最稳的选择这也是张正友标定在工业界长期占主导的原因。标定板类型优势主要问题棋盘格OpenCV 支持成熟、角点提取稳定打印不平整会影响精度圆点阵列圆心定位精度高倾斜时要做椭圆校正ArUco / 二维码姿态估计便捷对标定图清晰度要求高2.3 内参与畸变系数的物理含义读懂 calibrateCamera 的输出很多新手跑完 calibrateCamera看着内参矩阵和畸变系数一头雾水不知道怎么判断结果对不对。先建立直觉很关键。fx、fy 的单位是像素它等于镜头焦距毫米除以单个像素的物理尺寸。假如一个相机 sensor 的像素尺寸是 4 微米焦距 8 毫米那 fx 大约就是 2000 像素。不同分辨率下 fx 会等比例变化所以比较两个相机的内参时要在同分辨率下比。cx、cy 正常应该接近图像中心。比如 1280x720 的图像cx 应该在 640 附近cy 在 360 附近。如果偏差超过几十个像素要么是镜头装配偏心要么是标定图片的角度分布有问题比如棋盘格全部集中在画面左下角把光心带偏了。畸变系数里k1、k2 支配大部分畸变k3 只在极广角镜头或鱼眼镜头里显著。p1、p2 通常非常小但如果标定图里棋盘格总是放在画面中心切向畸变就解不准p1、p2 会变得不稳定。判断畸变系数靠不靠谱除了看数值大小还要看它们在不同图片子集下反复标定的波动幅度。这就是为什么我们说要多拍图片、多跑几遍而不是一次就跑完就交付。3. 从棋盘格 PDF 到内参输出OpenCV 标定的完整落地步骤3.1 打印棋盘格与采集标定图像先把输入做好标定质量的第一个变量是标定板本身不是代码。棋盘格 PDF 打印时务必关掉打印对话框里的「适应页面」「缩放」选项按 100% 实际尺寸输出。打印完用毫米尺量几个格子的总长度再除以格数得到真实的格子边长。打印纸受潮变形或者打印机误差会让实际边长偏离设计值这一步不要凭设计值硬写进代码。资源包里自带的测试图片是现成的先用来跑通流程。如果换成自己的相机采集记住三个原则。第一图片数量控制在 10 到 20 张少于 10 张时约束不够畸变系数容易过拟合或者解不出来。第二角度要分散棋盘格要出现在画面中心、四个角落和第二象限并且带明显的倾斜不要只是平行正对相机移动位置。第三每张图里棋盘格占比不要太低至少占画面的四分之一否则角点提取精度会明显下降。3.2 角点提取与亚像素细化标定精度的第一道关口提取角点是整个标定流程中最基础也最影响结果的一步。直接用 findChessboardCorners 返回的整数坐标进 calibrateCameraRMS 很难降到 0.5 以下。必须用 cornerSubPix 做亚像素细化。import cv2 import numpy as np pattern_size (9, 6) # 棋盘格内角点9列 × 6行 img cv2.imread(test_01.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) flags cv2.CALIB_CB_ADAPTIVE_THRESH | cv2.CALIB_CB_NORMALIZE_IMAGE found, corners cv2.findChessboardCorners(gray, pattern_size, flagsflags) if found: criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria)findChessboardCorners 返回的 corners 是 shape 为 (N, 1, 2) 的浮点数组N 等于 9×6 54。这里 pattern_size 的 (9, 6) 指的是棋盘格的内角点排布对应打印纸上 10 列 × 7 行的黑白格子。CALIB_CB_ADAPTIVE_THRESH 让算法在光照不均时先做自适应阈值二值化CALIB_CB_NORMALIZE_IMAGE 在检测前做直方图均衡这两项对手机拍摄、室内灯光不均的图帮助很大。cornerSubPix 的 (5, 5) 是亚像素搜索窗口的半宽窗口越大搜索范围越广但超过 11 像素时容易跨到相邻角点保持 (5, 5) 或 (7, 7) 是稳妥的选择。criteria 里 30 是最大迭代次数0.001 是亚像素位置收敛的精度阈值。如果棋盘格在画面中占比太小或者图案边缘被截断findChessboardCorners 会直接返回 False这个坑在第 4 章会展开。3.3 calibrateCamera 求解内参看懂返回的四个量角点提取通过后下一步是生成世界坐标并调用 calibrateCamera。这是整套标定的核心求解步骤。square_size 25.0 # 打印后实测的格子边长单位 mm 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) objp[:, :2] * square_size objpoints, imgpoints [], [] # flags、criteria 沿用上一节的定义 for fname in img_files: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) found, corners cv2.findChessboardCorners(gray, pattern_size, flagsflags) if found: corners cv2.cornerSubPix(gray, corners, (5, 5), (-1, -1), criteria) objpoints.append(objp.copy()) imgpoints.append(corners) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) print(RMS , ret) print(内参矩阵 K , mtx) print(畸变系数 , dist.ravel())objp 的生成方式是这套代码里最需要小心的部分。np.mgrid[0:9, 0:6] 生成网格.T 转置后再 reshape得到 (X, Y) 坐标对对应像素坐标 (u, v) 的排列顺序。如果不转置角点的顺序就会错位标定结果会完全乱掉而且这种错误很难从数值上直接看出来。gray.shape[::-1] 传入的是 (width, height)因为 OpenCV 的 calibrateCamera 要求图像尺寸参数是 (宽, 高)而 numpy 数组形状是 (高, 宽)这里必须反转。calibrateCamera 返回的 ret 就是重投影误差 RMS单位是像素。一般小于 0.5 算合格0.3 以内是正常水平如果 RMS 超过 0.8基本可以判定标定输入有问题要么是角点精度不够要么是边长设置错误。mtx 是 3x3 内参矩阵dist 是包含 5 个元素的畸变向量 [k1, k2, p1, p2, k3]。rvecs 和 tvecs 是每张图对应的外参分别表示旋转向量和平移向量后续做投影误差分析和验证时会用到。3.4 undistort 去畸变用最直观的方式验证标定结果标定完成后最直观的验证方式是把一张图片去畸变看棋盘格的边缘直线是否被拉直。这里有一个大多数人第一次都会踩的坑直接把原始 mtx 传给 undistort结果边缘畸变更夸张了。h, w gray.shape[:2] newcameramtx, roi cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h)) dst cv2.undistort(img, mtx, dist, None, newcameramtx) # 批量处理时改用重映射方式 mapx, mapy cv2.initUndistortRectifyMap( mtx, dist, None, newcameramtx, (w, h), cv2.CV_32FC1) dst2 cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)getOptimalNewCameraMatrix 的 alpha 参数控制像素取舍。alpha1 保留全部原始像素适合用来验证畸变是否被拉直但边缘会出现黑边alpha0 会裁掉畸变严重的边缘区域得到一张干净但视野略小的图。工程上通常先用 alpha1 确认标定效果再按实际需要调到 0 到 0.5 之间。直接传 mtx 而不传 newcameramtx等于用一套包含畸变的内参去做反向映射边缘像素的投影关系已经超出了原模型的适用范围这就是边缘异常拉伸的原因。4. 标定翻车现场四个常见问题的排查与解决4.1 findChessboardCorners 一直返回 False现象图片里肉眼看得到棋盘格但函数就是不返回角点。原因最常见的是棋盘格图案被截断或者边缘反光导致二值化后棋盘格与背景无法分离。其次是分辨率太低54 个内角点在低分辨率下已经不可分辨。还有一种情况是棋盘格在画面里倾斜角度过大超出了算法的鲁棒范围。解决先给 findChessboardCorners 的 flags 加上 CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE。再看棋盘格是否完整出现在画面范围内占比尽量大于四分之一。如果是边缘反光换一个拍摄角度或者补一盏散射光。资源包里自带的测试图是验证过的处理自己的图一直失败时优先怀疑采集阶段的问题而不是去改代码。4.2 重投影误差降不下来卡在 0.5 以上现象RMS 始终在 0.5 到 1.5 之间波动换更多图片也不改善。原因图片数量不足或者视角太单一。如果所有图片都是棋盘格正对相机、放在画面中心旋转向量之间的正交约束就会退化内参解出来不稳定。另一个高频原因是世界坐标边长与实际边长不一致比如打印时被缩放或者量错边长。解决先确认打印边长是否准确再补拍三分之一数量的斜视角图片让棋盘格出现在画面四个角落和不同深度。我的一般做法是先对当前图片集跑一次标定把 rvecs 的角度分布打印出来如果所有旋转角度都接近零那就别急着优化参数先去补拍角度。4.3 棋盘格打印尺寸与设置边长对不上现象标定结果 fx 和 fy 明显偏大或偏小用标定结果去测量实际物体尺寸时误差超过 5%。原因PDF 打印时勾了「适应页面」或非 100% 缩放打印出的格子边长和设计值不一致。甚至不同打印机默认有边界偏移也会导致实际尺寸偏差。解决打印后用毫米尺量至少 3 个格子的总长度除以格子数得到实际边长把实测值写进 square_size。这一步很反直觉但标定算法对边长是等比例换算的边长错了fx、fy 以及后续所有的测距结果都会成比例出错。整套流程我看过太多人在这里翻车打印爽快量尺寸嫌麻烦结果标定结果完全不可用。4.4 去畸变后图像边缘严重拉伸或出现黑边现象undistort 输出的图片中心正常边缘像被掰弯或者出现大面积黑色无像素区域。原因直接把原始 mtx 传给了 undistort没有用 getOptimalNewCameraMatrix 生成校正后的内参矩阵。边缘区域本来畸变就大直接用带畸变的内参去做映射误差会被放大。解决按 3.4 的流程先求 newcameramtx 和 roi再传给 undistort 或 remap。如果边缘拉伸仍然明显降低 alpha 值让 OpenCV 裁掉变形最严重的区域。注意裁掉边缘后后续使用像素坐标做测量时要同步更新内参里的 cx、cy否则投影计算会整体偏移。5. 精度验证与参数固化用 projectPoints 做逐角点误差分析5.1 用 projectPoints 对每张标定图逐角点计算误差calibrateCamera 返回的 RMS 是一个总体指标看不出误差集中在哪里。我在工程交付前一般会用 projectPoints 把所有角点重新投影回去做逐点误差分析这一步能暴露很多隐藏问题。# 沿用前面的 objpoints, imgpoints, rvecs, tvecs, mtx, dist all_errs [] for i in range(len(objpoints)): pts2d, _ cv2.projectPoints( objpoints[i], rvecs[i], tvecs[i], mtx, dist) errs np.linalg.norm( imgpoints[i].reshape(-1, 2) - pts2d.reshape(-1, 2), axis1) all_errs.append(errs) all_errs np.concatenate(all_errs) print(均值误差:, all_errs.mean(), pixel) print(90分位误差:, np.percentile(all_errs, 90), pixel) print(最大误差:, all_errs.max(), pixel)projectPoints 做的事情是用标定得到的内参、畸变系数和第 i 张图的外参把世界坐标系下棋盘格的角点重新投影到图像上得到一组理论像素坐标。再和实际提取的亚像素角点坐标逐点求距离就是每个角点的重投影误差。如果误差最大值集中在画面边缘说明畸变模型在边缘区域拟合不足需要补充边缘视角的图片。如果某一张图的误差整体偏大那张图大概率存在运动模糊或者标定板没有完全贴合平面。我会把误差超过 1.5 像素的角点画在图像上观察空间分布这是判断标定质量最直观的方式。只看 RMS 会掩盖边缘拟合不足的问题逐角点分析能把这个短板暴露出来。5.2 把内参与畸变系数固化到配置文件标定跑完只是第一步后续每帧图像去畸变、检测、测距都要复用这组参数不能每次启动都重新标定。把结果落盘是工程落地的基本习惯。np.savez(calib_result.npz, mtxmtx, distdist, newcameramtxnewcameramtx, sizenp.array([w, h])) data np.load(calib_result.npz) mtx data[mtx] dist data[dist]用 np.savez 保存成 npz 文件启动项目时加载即可。image size 也要存下来加载后创建 remap 映射时还要用到。如果你在 C 工程里用 OpenCV也可以用 cv2.FileStorage 写 YAML方便其他模块读取同一份标定文件。从那以后我每到一个新相机都会强制走一遍「打印标定板 → 采集 15 张左右多角度图 → 跑标定 → projectPoints 逐点误差分析 → 实测尺寸验证」这五步。重投影误差和实测误差对不上时回头查边长设置和图片质量基本都能定位到问题。标定这个环节看着像玄学实际上每一步都有据可查关键是把验证机制建立起来。希望这份笔记里的流程和踩坑记录能帮你少走一趟弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑