第一章 成像原理与坐标系:从光线到像素的旅程
做机器视觉这些年,我越来越觉得一个道理:搞懂成像原理,比会调参重要一百倍。你想想看,如果连相机怎么把三维世界变成二维图像都不清楚,那后面做标定、做匹配、做三维重建,基本就是摸着石头过河。
这一章,咱们就把最核心的东西掰开揉碎讲清楚。说白了就三件事:针孔相机模型、四大坐标系、畸变模型。这三件事搞明白了,双目视觉的根基就算打牢了。
1.1 针孔相机模型:最朴素的成像逻辑
先问个问题:相机到底是怎么把三维世界「拍」到二维照片上的?
最朴素的理解,就是针孔相机模型。说白了,就是一个小黑盒,前面戳个针眼大小的孔,光线穿过小孔,在后面的感光面上倒着成像。嗯,就是这么简单。
我记得刚入行时,带我的老师傅跟我说:「别小看这个针孔模型,所有相机标定的数学基础都在这。」当时我不信,后来踩了坑才明白——所有复杂的相机模型,都是在针孔模型基础上打补丁。
针孔模型的核心就三个要素:
- 光心(Oc):小孔的位置,所有光线都从这里穿过
- 光轴:穿过光心且垂直于成像平面的直线
- 焦距(f):光心到成像平面的距离
这里有个关键公式,大家一定要刻在脑子里:
X_image = f * (X_world / Z_world)
Y_image = f * (Y_world / Z_world)
什么意思?就是世界中的点,除以它在Z轴上的深度,再乘以焦距,就得到了图像上的位置。这个「除以Z」的操作,就是透视投影的本质——近大远小。
核心理解:针孔模型建立的是「相似三角形」关系。世界坐标系中的一个点,通过光心投影到成像平面上,形成倒立的像。我们通常把成像平面「虚拟」到光心前方,这样图像就是正立的,方便计算。
我的经验:在实际项目中,我很少直接用物理针孔相机(那玩意儿进光量太少)。但所有工业相机、手机摄像头,在数学建模时都默认是针孔模型。你想想看,这基础多重要。
1.2 四大坐标系:从世界到像素的「四步走」
好了,现在我们知道光线怎么走了。但问题来了——一个三维点,怎么变成屏幕上那个像素的坐标?
这中间要经过四次坐标变换。我个人习惯把这四个坐标系串成一条链:
世界坐标系 → 相机坐标系 → 图像坐标系 → 像素坐标系
咱们一个一个说。
1.2.1 世界坐标系 (Xw, Yw, Zw)
这是客观世界的坐标系。你想放在哪就放在哪。比如我做一个流水线检测项目,就把世界坐标系原点定在传送带起始端。这个坐标系的作用,就是描述物体在真实世界中的位置。
1.2.2 相机坐标系 (Xc, Yc, Zc)
以相机光心为原点,Zc轴沿着光轴方向。从世界坐标系到相机坐标系,需要做一次刚体变换——说白了就是旋转加平移。
[Xc] [R11 R12 R13] [Xw] [Tx]
[Yc] = [R21 R22 R23] [Yw] + [Ty]
[Zc] [R31 R32 R33] [Zw] [Tz]
这里的R是旋转矩阵,T是平移向量。这6个参数(3个旋转角+3个平移量),就是相机的外参。
注意:外参是每个相机独有的。双目视觉中,两个相机的外参不同,所以需要标定出它们之间的相对位姿。我曾经在一个项目中,因为外参标定差了0.1度,导致三维重建误差直接飙到5毫米——嗯,那晚我加班到凌晨三点。
1.2.3 图像坐标系 (x, y)
这一步是从三维到二维的投影。用针孔模型:
x = f * (Xc / Zc)
y = f * (Yc / Zc)
注意,这里的单位是物理长度(毫米)。图像坐标系的原点在光轴与成像平面的交点上。
1.2.4 像素坐标系 (u, v)
最后一步,把物理坐标变成像素坐标。这一步要考虑两个事:
- 缩放:每个像素在x和y方向上的物理尺寸(dx, dy)
- 平移:图像坐标系原点在像素坐标系中的位置(u0, v0)
u = x / dx + u0
v = y / dy + v0
把上面所有步骤合并,就得到了大名鼎鼎的相机内参矩阵K:
K = [fx 0 u0]
[0 fy v0]
[0 0 1]
其中 fx = f/dx, fy = f/dy。
一句话总结:世界坐标 → 外参变换 → 相机坐标 → 透视投影 → 图像坐标 → 内参变换 → 像素坐标。这四步走完,一个三维点就变成了屏幕上的一个像素。
1.3 相机畸变模型:理想很丰满,现实很骨感
你可能会问:按针孔模型算出来的坐标,准吗?
答案是:不准。因为真实镜头不是完美的针孔。
镜头有曲率,有装配误差,光线穿过时会发生偏移。这个偏移,就是畸变。我做过一个项目,用理论模型算出来的像素位置和实际差了十几个像素——你说这能忍吗?
畸变主要分两种:
1.3.1 径向畸变
这是最常见的畸变。原因是镜头表面的曲率不完美。离光轴越远,畸变越明显。
- 桶形畸变:图像向外凸出,像木桶。常见于广角镜头。
- 枕形畸变:图像向内凹陷。常见于长焦镜头。
数学上,用泰勒级数来修正:
x_corrected = x * (1 + k1*r^2 + k2*r^4 + k3*r^6)
y_corrected = y * (1 + k1*r^2 + k2*r^4 + k3*r^6)
其中 r^2 = x^2 + y^2,k1、k2、k3 是径向畸变系数。
避坑指南:我曾经只用k1和k2做矫正,结果在图像边缘还是有明显畸变。后来加了k3项,效果好了很多。对于高精度测量,建议至少用3个径向畸变参数。
1.3.2 切向畸变
这个畸变的原因是镜头和成像平面不平行。说白了就是装配时镜头装歪了。
x_corrected = x + [2*p1*x*y + p2*(r^2 + 2*x^2)]
y_corrected = y + [p1*(r^2 + 2*y^2) + 2*p2*x*y]
p1、p2 是切向畸变系数。
重要提醒:畸变参数是相机内参的一部分。在双目视觉中,左右相机的畸变参数通常不同。标定时一定要分别标定,不能共用一套参数。我见过有人图省事只标一个相机,结果立体匹配时误差大得离谱。
1.4 完整的内参矩阵与畸变参数
好了,我们把所有参数汇总一下。一个完整的相机模型,需要标定以下参数:
| 参数类别 | 参数符号 | 含义 | 数量 |
|---|---|---|---|
| 内参 | fx, fy | 焦距(像素单位) | 2 |
| u0, v0 | 主点坐标 | 2 | |
| k1, k2, k3 | 径向畸变系数 | 3 | |
| p1, p2 | 切向畸变系数 | 2 | |
| 外参 | R | 旋转矩阵(3x3) | 3(欧拉角) |
| T | 平移向量 | 3 | |
| 总计 | 15个参数 | ||
这15个参数,就是相机标定要解的全部未知数。你想想看,一个相机就要解15个参数,双目视觉就是30个参数——所以标定这件事,真不能马虎。
我的建议:初学者不要自己手写标定算法。OpenCV的cv2.calibrateCamera()已经封装得很好。但你要理解它背后在干什么——就是在解这15个参数的最优解。理解了这个,你才能判断标定结果好不好。
1.5 从理论到实践:一个简单的标定流程
光说不练假把式。我给大家梳理一下实际项目中怎么做:
- 准备标定板:棋盘格或圆点阵,打印出来贴平板上
- 采集图像:从不同角度拍20-30张照片。注意:标定板要占画面1/3以上
- 提取角点:用cv2.findChessboardCorners()找到棋盘格角点
- 执行标定:cv2.calibrateCamera(),得到内参和畸变参数
- 评估结果:看重投影误差,一般要小于0.5像素
- 保存参数:把参数存成yaml或json文件,后续直接加载
一个小技巧:标定时,让标定板在画面中「动起来」——上下左右、倾斜旋转,各种姿势都来一遍。这样解出来的参数才稳定。我见过有人只拍了5张正对着的照片,结果标定出来的畸变参数全是0——嗯,那还不如不标。
好了,这一章的内容就到这里。成像原理和坐标系是双目视觉的「地基」,地基不牢,后面盖什么楼都白搭。下一章咱们聊双目立体视觉的几何基础——对极几何和视差原理,那才是真正开始「玩」双目了。
公众号:蓝海资料掘金营,微信deep3321