title: 相机标定入门:内参、外参、畸变与棋盘格标定
slug: camera-calibration-intro1. 从一个问题开始:为什么相机需要标定?
相机不是完美的光电器件。镜头有瑕疵,安装有误差。这些东西会让你看到的像素坐标和几何理论值之间出现偏差。相机标定的目的,就是算出这些偏差,让你能用数学描述"三维世界的某个点,最终落到图像上的哪个像素"。
换句话说,标定就是求下面这个映射的参数:
三维世界点 P_world → 相机坐标系下的点 P_camera → 图像上的像素 (u, v)
这个映射涉及三组参数:
- 内参:描述相机内部的成像特性(镜头焦距、感光元件位置等)
- 外参:描述相机在世界中的位置和朝向
- 畸变参数:描述镜头制造偏差造成的图像变形
三者缺一不可。接下来我们逐一拆解。
2. 针孔相机模型
针孔模型是最基本的相机成像模型。它的核心假设是:来自三维场景的光线,全部穿过一个无限小的"针孔",投射到后方的成像平面上。
虽然真实相机有镜头而不是针孔,但针孔模型在大多数场景下是一个足够好的一阶近似。我们后面的所有推导都基于它。
2.1 世界坐标系 → 相机坐标系(外参登场)
假设你有一个三维点 P,它在世界坐标系(就是你用来描述场景的那个参考系)下的坐标是:
P_w = [X_w, Y_w, Z_w]^T
相机自己也有一个坐标系。相机所在的位置就是相机坐标系的原点。不同坐标系之间可以通过一个刚体变换(旋转 + 平移)来关联:
P_c = R · P_w + t
其中:
- R(3×3 旋转矩阵):描述相机坐标系相对于世界坐标系的朝向。比如相机朝前看还是朝下看。
- t(3×1 平移向量):描述相机坐标系原点相对于世界坐标系原点的位置。比如相机装在车头前方 0.5 米、离地 0.3 米。
把 R 和 t 拼成一个 4×4 的齐次变换矩阵,就可以写成:
[ R t ]
[ 0 1 ]
R 和 t 合在一起,就是相机的外参(extrinsic parameters)。
工程直觉:外参描述的是"相机装在哪里、朝哪里"。你挪一下相机,外参就变了。换镜头不换外参。

2.2 相机坐标系 → 图像坐标系(内参登场)
现在 P 在相机坐标系下。接下来,光线穿过相机光心(针孔),投影到后方的成像平面上。
根据相似三角形关系,相机坐标系下的点 (X_c, Y_c, Z_c) 投影到成像平面上的物理坐标 (x, y):
x = f_x · X_c / Z_c
y = f_y · Y_c / Z_c
其中 f_x 和 f_y 是焦距。理想情况下 f_x = f_y,但真实镜头的两个方向焦距可能略有不同。
2.3 图像物理坐标 → 像素坐标
成像平面上的点还要变成最终图像上的像素。像素坐标系以图像左上角为原点,u 轴向右,v 轴向下。
物理坐标 (x, y) 到像素坐标 (u, v) 的转换涉及两个因素:
- 缩放:物理坐标(通常是毫米)需要乘以像素密度(pixel/mm),得到像素数。
- 平移:光轴与成像平面的交点(主点)通常不在图像正中心,需要偏移。
综合之后:
u = f_x · X_c / Z_c + c_x
v = f_y · Y_c / Z_c + c_y
其中:
- f_x, f_y:以像素为单位的焦距(focal length in pixels)。f_x = 物理焦距 × 每毫米像素数。它们描述了镜头的"放大能力"。
- c_x, c_y:主点(principal point)的像素坐标,描述了光轴打在图像平面的位置。理想情况下是图像中心,但实际上会有偏移。
写成矩阵形式:
[u] [f_x 0 c_x] [X_c / Z_c]
[v] = [ 0 f_y c_y] · [Y_c / Z_c]
[1] [ 0 0 1 ] [ 1 ]
即:
p = K · P_norm
其中 K 是相机内参矩阵(camera intrinsic matrix),P_norm 是归一化相机坐标(除以 Z_c 以后)。
工程直觉:K 描述了"相机镜头和感光元件是怎么成像的"。换镜头会改变 f_x, f_y;挪动相机不会改变 K。

2.4 完整投影链路
把外参和内参串起来:
P_world → [R|t] → P_camera → /Z_c → P_norm → K → (u, v)
展开写:
s · [u, v, 1]^T = K · [R | t] · [X_w, Y_w, Z_w, 1]^T
其中 s 是尺度因子(其实就是 Z_c)。
这就是相机投影的完整方程。标定的目标,就是求出 K、R、t 以及畸变参数。
3. 畸变:为什么直线会变弯?
针孔模型假设光线直线传播,但真实镜头不是理想针孔。镜头的加工和装配误差会导致图像变形。
3.1 径向畸变(Radial Distortion)
最常见的畸变类型。原因是镜头形状不是理想球面,离光轴越远的光线偏折越厉害。
想象你要把一个大球面压缩到一个平面感光元件上——要么中间挤(桶形畸变),要么边缘拉(枕形畸变)。
数学上,径向畸变用关于半径 r 的多项式来建模:
x_undistorted = x · (1 + k1·r² + k2·r⁴ + k3·r⁶)
y_undistorted = y · (1 + k1·r² + k2·r⁴ + k3·r⁶)
其中 r² = x² + y²,x, y 是归一化坐标(已经除以 Z_c 了)。
- k1 主导控制整体畸变量
- k2, k3 修正边缘区域
- k < 0 通常是桶形畸变(画面四角向内缩)
- k > 0 通常是枕形畸变(画面四角向外拉)
工程直觉:用手机拍一张方格纸,放在画面边缘。如果格线弯了,就是有径向畸变。
3.2 切向畸变(Tangential Distortion)
切向畸变比径向畸变小很多。它的成因是镜头和感光元件没有严格平行,或者镜头组装存在倾斜。
x_undistorted = x + [2·p1·x·y + p2·(r² + 2·x²)]
y_undistorted = y + [p1·(r² + 2·y²) + 2·p2·x·y]
工程直觉:如果画面看起来像梯形而非矩形,很可能是切向畸变比较大。但在大多数现代相机上,切向畸变非常小(p1、p2 接近 0)。

3.3 OpenCV 中畸变参数的顺序
OpenCV 标准相机模型的畸变系数向量是:
D = [k1, k2, p1, p2, k3]
鱼眼相机模型不同——后面文章会讲。
4. 内参标定:棋盘格是怎么工作的?
4.1 基本思路
棋盘格标定(Zhang's method,即张正友标定法)的核心思路是:
- 拿一块已知格子大小的棋盘格,从不同角度、不同距离拍多张照片。
- 在每张图片中检测棋盘格的角点(内角点,即黑白格交界的那个点)。
- 因为棋盘格的物理尺寸是已知的(比如每格 5cm),我们可以建立一个"棋盘格坐标系",把棋盘格上每个角点在这个坐标系下的 3D 坐标写出来。为简单起见,通常令棋盘格平面为 Z=0 平面。
- 这样一来,每张棋盘格图片就给了你一组 3D 点 → 2D 像素 的对应关系。
- 用这些对应关系,通过优化方法同时求出内参 K 和畸变 D。
张正友标定法的详细推导不在这里展开。工程上你只需要理解它的输入输出:
- 输入:多张棋盘格图像的角点检测结果 + 棋盘格物理尺寸
- 输出:K(内参矩阵)+ D(畸变系数)+ 每张图的 R, t(外参,但这对外参一般不用)

4.2 棋盘格怎么拍?
这是实操中最容易被忽视但又最关键的一步。拍不好,标定就废。原则:
- 覆盖全视野:棋盘格要出现在画面的中心、四边、四角。尤其是鱼眼相机,边缘区域畸变最大,必须充分覆盖。
- 角度要有变化:不要只平着拍。棋盘格要倾斜、旋转、远近变化。
- 格子要够大:棋盘格在画面中的占比要足够高。经验值是至少占画面面积的 10%-20% 以上。太小的话角点检测精度不够,标定结果会很不稳定。
- 不要反光、不要模糊:反光会让角点检测定位不准,模糊同理。
- 多拍几张:10 张是底线,20-30 张比较稳,50 张以上收益递减。
4.3 关于标定板的额外知识
除了经典的黑白棋盘格,还有:
- 圆点格(Circle Grid):圆形的中心比棋盘格角点更容易精确检测,但对遮挡和模糊的鲁棒性可能略差。
- AprilTag / ArUco 阵列:带有 ID 的标志物,不用手动指定顺序,适合自动化标定。
- ChArUco 板:棋盘格+ArUco 的混合板,结合了角点精度和 ID 识别的优势。
5. 重投影误差:你怎么知道标得好不好?
5.1 什么是重投影误差
标定完成后的关键检验:用标定得到的 K、D、R、t,把棋盘格 3D 角点重新投影回图像上,和实际检测到的角点位置做比较。
平均偏差就是重投影误差(Reprojection Error),通常用 RMS(Root Mean Square) 来表示,单位是像素。
RMS = sqrt( mean( ||检测角点 - 重投影点||² ) )
5.2 判断标准
| RMS | 评价 | 说明 |
|---|---|---|
| < 0.5 px | 优秀 | 高质量标定,可用于精密测量 |
| 0.5~1.0 px | 良好 | 正常工程使用没问题 |
| 1.0~2.0 px | 一般 | 非精密场景可用 |
| 2.0~5.0 px | 勉强 | 需要检查数据质量,改进采集 |
| 10+ px | 不合格 | 基本上不能当正经内参用 |
| 50+ px | 严重错误 | 流程可能跑通了,但参数完全是废的 |
鱼眼相机的 RMS 通常略高于针孔相机。
5.3 影响 RMS 的常见因素
- 棋盘格太小:画面中棋盘格占比不够 → 角点定位误差大 → RMS 高。
- 覆盖不充分:只拍了画面中央,边缘没有棋盘格 → 畸变系数约束不够 → RMS 看上去还行但畸变参数偏差大。
- 图像模糊或反光:角点检测抖动。
- 棋盘格不规整:打印的棋盘格有缩放或变形。

6. 实操建议
6.1 一次典型的标定流程
1. 打印一块棋盘格(格子边长建议 20-50mm,根据相机视野调整)
→ 贴在平整的硬板上
2. 用待标定相机拍摄 20-30 张棋盘格照片
→ 角度、距离要变
→ 棋盘格要在画面各处都出现
3. 运行标定脚本(OpenCV calibrateCamera 或 fisheye::calibrate)
→ 输入:图像路径 + 棋盘格规格
→ 输出:K, D
4. 检查 RMS
→ 如果不满意,排查数据质量,重新采集
5. 用去畸变效果验证
→ 找一张有直线的原图,去畸变后看直线是否变直
6.2 OpenCV 代码骨架
import cv2
import numpy as np
# 准备 object points(棋盘格 3D 坐标,Z=0)
objp = np.zeros((rows * cols, 3), np.float32)
objp[:, :2] = np.mgrid[0:cols, 0:rows].T.reshape(-1, 2) * square_size
objpoints = [] # 3D 点
imgpoints = [] # 2D 像素点
for img_path in image_paths:
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (cols, rows))
if ret:
objpoints.append(objp)
imgpoints.append(corners)
# 标定
ret, K, D, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None
)
print(f"K = \n{K}")
print(f"D = {D.ravel()}")
print(f"RMS = {ret}")
如果是鱼眼相机,把 cv2.calibrateCamera 换成 cv2.fisheye.calibrate,注意输入格式略有不同。
7. 小结
- 内参 K 描述相机自身怎么成像(焦距 + 主点)。换镜头内参会变,挪相机内参不变。
- 外参 R, t 描述相机在世界中的位置和朝向。挪相机外参会变,换镜头外参基本不变。
- 畸变 D 描述镜头的非理想性。径向畸变最常见,切向畸变通常很小。
- 棋盘格标定的核心是建立已知 3D 坐标和 2D 像素之间的对应关系,然后优化求解 K 和 D。
- RMS 重投影误差是判断标定质量的首要指标。RMS > 2px 就应该排查数据质量问题。
- 采集比算法更重要:棋盘格太小、覆盖不足、反光模糊,再好的标定算法也救不回来。