相机标定、棋盘格角点检测与重投影误差通俗总结
目录
- 标定到底在做什么
- 棋盘格为什么有自己的坐标系
- 斜着拍时为什么棋盘格坐标的 Z 仍为 0
- OpenCV 的"实际检测角点"是什么
- 没有畸变参数时怎样检测角点
- "模型预测角点位置"是什么意思
- 预测时输入序号还是具体坐标
- 检测点不是真值,为什么还能标定
- 左目、右目和双目 RMS 的原理
- RMS 和极线误差的区别
- 去畸变前后对角点检测的影响
- 完整标定流程
- 最核心的理解
1. 标定到底在做什么
相机标定的目的,是找到一套数学参数,尽可能准确地描述真实相机的成像过程。
主要参数包括:
- 焦距:
fx、fy - 主点:
cx、cy - 畸变参数:
k1、k2、p1、p2、k3... - 每张标定图中棋盘格相对于相机的旋转和平移:
Ri、ti
可以把整个过程理解为:
text
已知棋盘格在现实中的规则结构
+
检测棋盘格角点在照片中的像素位置
+
建立一个虚拟相机模型
↓
不断调整相机参数
↓
让虚拟相机预测的位置尽量靠近照片中的检测位置
2. 棋盘格为什么有自己的坐标系
棋盘格是一块尺寸已知、平整、不会变形的标定板。
假设每个格子边长是 30 mm,可以人为把第一个内部角点定义为原点:
text
编号0 编号1 编号2
(0,0,0) (30,0,0) (60,0,0)
编号3 编号4 编号5
(0,30,0) (30,30,0) (60,30,0)
每个角点都有一个具体的三维坐标:
P_{board}=(X_b,Y_b,Z_b)
因为所有角点都在同一块平板上,所以:
Z_b=0
这里的 Zb=0 只表示:
角点没有离开棋盘格平面。
它不表示角点距离相机为 0。
3. 斜着拍时为什么棋盘格坐标的 Z 仍为 0
这是两个不同坐标系的问题。
3.1 棋盘格坐标系
无论棋盘格怎么移动和旋转,某个角点在棋盘格自身坐标系里的位置不变。
例如:
P_b=(60,30,0)
你把棋盘格正放、斜放、竖放,这个角点依然是 (60,30,0),因为它相对于棋盘格原点没有移动。
3.2 相机坐标系
同一个点在相机坐标系中的位置为:
P_c=RP_b+t
其中:
R:棋盘格相对于相机的旋转t:棋盘格相对于相机的平移Pc=(Xc,Yc,Zc):角点在相机坐标系中的坐标
斜着拍时,虽然所有角点的棋盘格坐标 Zb=0,但它们在相机坐标系中的深度 Zc 通常不同。
例如:
text
棋盘格左侧角点:Zc = 900 mm
棋盘格右侧角点:Zc = 1100 mm
所以:
text
棋盘格坐标系中的 Zb = 0
不等于
相机坐标系中的 Zc 相同
4. OpenCV 的"实际检测角点"是什么
标准标定时,OpenCV 直接在原始、带畸变的图像中检测角点。
text
原始带畸变图像
↓
转为灰度图
↓
检测黑白棋盘格交叉位置
↓
得到角点的二维像素坐标
例如:
p_{obs}=(500.4,300.2)
它表示:
OpenCV 在原始照片中观察到,这个棋盘格交叉点位于横坐标 500.4、纵坐标 300.2。
坐标可以是小数,因为 OpenCV 可以使用亚像素优化,例如 cornerSubPix。
"实际检测位置"更准确的叫法是:
观测位置。
它不是绝对无误的真值,仍然可能受到模糊、噪声、反光等影响。
5. 没有畸变参数时怎样检测角点
角点检测不需要提前知道焦距或畸变参数。
它主要依赖局部像素的亮暗变化。
棋盘格角点附近通常是:
text
白 白 白 │ 黑 黑 黑
白 白 白 │ 黑 黑 黑
─────────┼─────────
黑 黑 黑 │ 白 白 白
黑 黑 黑 │ 白 白 白
它的特点是:
- 水平方向亮度明显变化
- 垂直方向亮度也明显变化
- 四个象限呈现黑白交替结构
可以通俗地理解为:
text
只有一个方向亮度变化明显 → 边缘
两个方向亮度都变化明显 → 角点
镜头畸变会把角点搬到别的位置,也会让方格发生变形,但局部的黑白交叉结构通常仍然存在,因此 OpenCV 可以直接在原始图中检测。
6. "模型预测角点位置"是什么意思
模型预测不是再看一次图像,而是一个纯数学计算。
假设某个角点在棋盘格坐标系中的位置是:
P_b=(60,30,0)\\text{ mm}
OpenCV 当前有一套估计参数:
text
相机内参:fx、fy、cx、cy
畸变参数:k1、k2、p1、p2...
棋盘格姿态:R、t
它进行以下计算:
text
棋盘格三维坐标 (60,30,0)
↓ 使用 R、t
转换到相机坐标系
↓ 使用 K
进行针孔透视投影
↓ 使用 D
加入镜头畸变
↓
得到原始图中的预测像素位置
例如预测得到:
p_{proj}=(500.9,299.8)
这表示:
如果当前的焦距、主点、畸变和棋盘格姿态都是正确的,那么这个角点应该出现在原始照片的
(500.9,299.8)。
预测时会加入畸变,因为预测结果要和原始畸变图中的观测位置比较。
7. 预测时输入序号还是具体坐标
真正参与投影计算的是角点的具体三维坐标,不是单独的序号。
例如:
text
编号6
对应棋盘格三维坐标:(60,30,0) mm
对应图像观测坐标: (500.4,300.2) px
序号的作用只是保证三维点和二维点正确配对:
text
object_points[6] ↔ image_points[6]
例如:
python
object_points = [
[0, 0, 0],
[30, 0, 0],
[60, 0, 0],
[0, 30, 0],
[30,30, 0],
[60,30, 0],
]
二维检测结果:
python
image_points = [
[220.3,180.1],
[265.7,176.8],
[309.4,173.2],
[224.8,221.5],
[269.1,217.3],
[312.2,213.0],
]
真正的几何对应关系是:
text
(60,30,0) mm ↔ (312.2,213.0) px
8. 检测点不是真值,为什么还能标定
你的疑问是:
如果角点检测本身有误差,OpenCV 让预测结果靠近错误检测点,不就学错了吗?
答案是:
会受到影响,但 OpenCV 不是只依赖一个点,而是利用大量角点和大量图片寻找整体最合理的参数。
例如:
text
30 张图片
每张 54 个角点
总共 1620 个二维观测
不同角点的检测误差可能是:
text
角点1:偏左 0.2 px
角点2:偏右 0.3 px
角点3:偏上 0.1 px
角点4:偏下 0.2 px
如果这些误差主要是随机的,它们不会全部把参数推向同一个错误方向。
同时,一套相机参数必须解释:
- 所有角点
- 所有图像位置
- 所有距离
- 所有倾斜角度
某一套错误参数也许能很好解释一张图片,但很难同时解释几十张不同姿态的图片。
因此 OpenCV 做的是:
找到一套让全部观测总体误差最小的参数,而不是让某一个点完全重合。
但如果存在系统性错误,例如:
- 棋盘格不平整
- 棋盘格打印尺寸不准
- 图像普遍严重模糊
- 所有角点都因反光向同一方向偏
- 标定板只出现在图像中央
- 使用了错误的镜头模型
那么标定结果确实可能有偏差。
所以 RMS 小不代表绝对真值正确,只代表模型对当前检测数据拟合得较好。
9. 左目、右目和双目 RMS 的原理
9.1 单个角点误差
假设观测位置:
p_{obs}=(500.4,300.2)
模型预测位置:
p_{proj}=(500.9,299.8)
横向误差:
\\Delta u=500.4-500.9=-0.5
纵向误差:
\\Delta v=300.2-299.8=0.4
单个角点的二维重投影误差:
e=\\sqrt{(\\Delta u)\^2+(\\Delta v)\^2}
代入:
e=\\sqrt{(-0.5)^2+0.4^2}\\approx0.64\\text{ px}
9.2 整体 RMS
所有角点的误差平方后求平均,再开平方:
[
\mathrm{RMS}
\sqrt{
\frac{
\sum_{i=1}^{N}
\left(u_i-\\hat u_i)\^2+(v_i-\\hat v_i)\^2\\right
}{N}
}
]
先平方的原因是:
- 正负误差不会互相抵消
- 大误差会受到更强惩罚
9.3 左目 RMS
例如:
text
left_rms_px = 0.54196
表示:
所有左目标定图、所有左目角点的观测位置与预测位置之间,整体 RMS 约为 0.542 像素。
9.4 右目 RMS
例如:
text
right_rms_px = 0.50535
表示:
所有右目标定图、所有右目角点的观测位置与预测位置之间,整体 RMS 约为 0.505 像素。
9.5 双目 RMS
例如:
text
stereo_rms_px = 0.55299
在固定左右内参时,OpenCV 主要估计左右相机之间固定的外参:
R_{left\\rightarrow right},\\quad T_{left\\rightarrow right}
同时要求:
text
同一块棋盘格
+
同一对左右图
+
固定的左右内参
+
所有图片共用同一个双目 R、T
能够同时解释左右图中的角点。
所以双目 RMS 是:
左右相机所有重投影误差,在统一双目刚性关系约束下的整体 RMS。
双目 RMS 比单目 RMS 略大是正常现象,因为双目标定增加了约束,自由度更少。
10. RMS 和极线误差的区别
重投影 RMS 衡量的是:
text
已知棋盘格三维点
→ 使用 K、D、R、t 投影到图像
→ 和实际检测点比较
极线误差衡量的是双目矫正后:
e_{epi}=\|v_L^{rect}-v_R^{rect}\|
| 指标 | 含义 |
|---|---|
| 左目 RMS | 左相机模型对左目角点的拟合程度 |
| 右目 RMS | 右相机模型对右目角点的拟合程度 |
| 双目 RMS | 固定双目刚性关系后,左右联合拟合程度 |
| 极线误差 | 双目矫正后对应点是否位于同一水平线 |
因此:
\\text{stereo RMS}\\neq\\text{极线误差}
11. 去畸变前后对角点检测的影响
不能简单说完全没有影响。
11.1 角点是否还是同一个
是同一个物理角点,但它在两个图像坐标系中的位置不同。
例如:
text
原始畸变图中: (500.4,300.2)
去畸变图中: (520.1,306.5)
这不代表一个对、一个错,只是坐标系不同。
11.2 检测成功率
畸变较小时:
text
原图可以检测
去畸变图也可以检测
差别可能不大。
畸变很大时,边缘方格会严重拉伸、压缩或弯曲,去畸变后形状变规则,可能提高检测成功率。
11.3 定位精度不一定提高
去畸变需要重映射和插值:
text
原始像素
→ 映射到新位置
→ 新位置不是整数像素
→ 使用附近像素进行插值
这可能导致:
- 边缘变软
- 插值模糊
- 噪声重新分布
- 边缘区域被拉伸
所以去畸变后,几何形状更规则,但不保证角点亚像素定位一定更准确。
定位精度可能:
- 提升
- 基本不变
- 略微下降
11.4 标定时为什么使用原始图
标准流程仍然是:
text
原始畸变图
→ 直接检测角点
→ 拟合 K、D
原因是:
- 原图是传感器直接采集的数据
- 没有额外重采样
- 标定本来就是要学习原始图的畸变
- 在求出畸变参数前,也无法正确去畸变
12. 完整标定流程
12.1 数据准备
已知棋盘格:
- 内部角点行列数
- 每个格子的真实尺寸
- 棋盘格为平面刚体
生成棋盘格三维坐标:
text
(0,0,0)
(30,0,0)
(60,0,0)
...
(0,30,0)
(30,30,0)
...
12.2 在原始图中检测角点
text
原始带畸变图
→ 根据黑白像素变化检测角点
→ 亚像素优化
→ 得到 imagePoints
12.3 建立对应关系
text
棋盘格三维点 objectPoints
↕
图像二维观测 imagePoints
12.4 优化参数
OpenCV 同时估计:
text
所有图片共享:
K、D
每张图片独立:
Ri、ti
目标是让:
text
三维点经过 R、t、K、D 投影后的预测位置
尽量靠近
图像中检测到的观测位置
12.5 输出结果
输出:
- 相机内参
- 畸变参数
- 每张图片的棋盘格姿态
- 重投影 RMS
双目标定还会输出:
- 左右相机之间的旋转
R - 左右相机之间的平移
T
12.6 独立验证
不要只看 RMS,还应使用未参与标定的新图片验证:
- 去畸变后直线是否变直
- 双目矫正后极线是否水平
- 中央与四角误差是否一致
- 已知尺寸测量是否准确
- 动态情况下是否受同步和滚动快门影响
13. 最核心的理解
13.1 三种坐标不要混淆
text
棋盘格坐标:
角点在标定板上的真实位置
例如 (60,30,0) mm
相机坐标:
角点相对于相机的位置
例如 (-120,-50,900) mm
图像坐标:
角点在照片中的像素位置
例如 (500.4,300.2) px
它们之间的关系是:
P_c=RP_b+t
然后:
P_c\\xrightarrow{K,D}p_{image}
13.2 角点检测与相机参数是两件事
text
角点检测:
从照片的黑白像素变化中找到交叉点
不需要 K、D
模型预测:
使用棋盘格三维坐标、R、t、K、D
计算角点应该出现在原图哪里
13.3 畸变位置不是错误位置
原始畸变图中的角点位置,是镜头真实成像后的结果。
标定要学习的是:
什么样的镜头模型,能够把规则棋盘格投影成照片中观察到的样子。
13.4 标定结果是估计值
OpenCV 并不知道绝对真值,它利用:
text
已知的棋盘格几何结构
+
大量二维观测
+
不同角度、距离和图像区域
+
相机成像模型
求出一套整体最合理的参数。
最终一句话总结
棋盘格告诉 OpenCV 每个角点在现实中怎么排列;原始图像告诉 OpenCV 这些角点经过真实镜头后落在了哪些像素位置;标定程序则不断调整焦距、主点、畸变和棋盘格姿态,让虚拟相机的预测尽量贴近真实图像中的观测。