200字
相机标定入门:内参、外参、畸变与棋盘格标定
title: 相机标定入门:内参、外参、畸变与棋盘格标定
slug: camera-calibration-intro

1. 从一个问题开始:为什么相机需要标定?

相机不是完美的光电器件。镜头有瑕疵,安装有误差。这些东西会让你看到的像素坐标和几何理论值之间出现偏差。相机标定的目的,就是算出这些偏差,让你能用数学描述"三维世界的某个点,最终落到图像上的哪个像素"。

换句话说,标定就是求下面这个映射的参数:

三维世界点 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) 的转换涉及两个因素:

  1. 缩放:物理坐标(通常是毫米)需要乘以像素密度(pixel/mm),得到像素数。
  2. 平移:光轴与成像平面的交点(主点)通常不在图像正中心,需要偏移。

综合之后:

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,即张正友标定法)的核心思路是:

  1. 拿一块已知格子大小的棋盘格,从不同角度、不同距离拍多张照片。
  2. 在每张图片中检测棋盘格的角点(内角点,即黑白格交界的那个点)。
  3. 因为棋盘格的物理尺寸是已知的(比如每格 5cm),我们可以建立一个"棋盘格坐标系",把棋盘格上每个角点在这个坐标系下的 3D 坐标写出来。为简单起见,通常令棋盘格平面为 Z=0 平面。
  4. 这样一来,每张棋盘格图片就给了你一组 3D 点 → 2D 像素 的对应关系。
  5. 用这些对应关系,通过优化方法同时求出内参 K 和畸变 D。

张正友标定法的详细推导不在这里展开。工程上你只需要理解它的输入输出:

  • 输入:多张棋盘格图像的角点检测结果 + 棋盘格物理尺寸
  • 输出:K(内参矩阵)+ D(畸变系数)+ 每张图的 R, t(外参,但这对外参一般不用)

棋盘格标定:相机观测标定板并检测内角点

4.2 棋盘格怎么拍?

这是实操中最容易被忽视但又最关键的一步。拍不好,标定就废。原则:

  1. 覆盖全视野:棋盘格要出现在画面的中心、四边、四角。尤其是鱼眼相机,边缘区域畸变最大,必须充分覆盖。
  2. 角度要有变化:不要只平着拍。棋盘格要倾斜、旋转、远近变化。
  3. 格子要够大:棋盘格在画面中的占比要足够高。经验值是至少占画面面积的 10%-20% 以上。太小的话角点检测精度不够,标定结果会很不稳定。
  4. 不要反光、不要模糊:反光会让角点检测定位不准,模糊同理。
  5. 多拍几张: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 的常见因素

  1. 棋盘格太小:画面中棋盘格占比不够 → 角点定位误差大 → RMS 高。
  2. 覆盖不充分:只拍了画面中央,边缘没有棋盘格 → 畸变系数约束不够 → RMS 看上去还行但畸变参数偏差大。
  3. 图像模糊或反光:角点检测抖动。
  4. 棋盘格不规整:打印的棋盘格有缩放或变形。

重投影误差可视化:检测角点与重投影点之间的偏差向量


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. 小结

  1. 内参 K 描述相机自身怎么成像(焦距 + 主点)。换镜头内参会变,挪相机内参不变。
  2. 外参 R, t 描述相机在世界中的位置和朝向。挪相机外参会变,换镜头外参基本不变。
  3. 畸变 D 描述镜头的非理想性。径向畸变最常见,切向畸变通常很小。
  4. 棋盘格标定的核心是建立已知 3D 坐标和 2D 像素之间的对应关系,然后优化求解 K 和 D。
  5. RMS 重投影误差是判断标定质量的首要指标。RMS > 2px 就应该排查数据质量问题。
  6. 采集比算法更重要:棋盘格太小、覆盖不足、反光模糊,再好的标定算法也救不回来。
相机标定入门:内参、外参、畸变与棋盘格标定
作者
若离
发表于
2026-05-11
License
CC BY-NC-SA 4.0

评论