相机模型与成像原理:从三维世界到像素坐标
做机器视觉这么多年,我始终觉得一个道理没变过——不懂相机模型,标定就是瞎蒙。你想想看,如果连图像上的一个点是怎么从真实世界投影过来的都不清楚,那你怎么去矫正畸变?怎么去算三维坐标?
这一章,我们就来把相机成像这件事彻底讲透。我会从最经典的针孔模型开始,然后聊镜头畸变这个"捣蛋鬼",最后把内参和外参矩阵掰开揉碎。嗯,这些都是标定脚本里绕不开的基础。
针孔相机模型:最朴素的成像逻辑
针孔相机模型,说白了就是初中物理课上那个小孔成像实验。光线穿过一个小孔,在后面的感光平面上形成一个倒立的实像。虽然现在的镜头复杂得多,但数学上我们依然用这个模型来近似。
核心公式其实就一个相似三角形关系:
Z / f = X / x = Y / y
其中:
Z —— 物体到相机光心的距离(深度)
f —— 焦距(光心到成像平面的距离)
X, Y —— 物体在三维空间中的坐标
x, y —— 物体在成像平面上的坐标
这个公式看着简单,但它是整个视觉几何的基石。我在做第一个3D测量项目时,就是靠这个公式反推物体尺寸的。当时没做标定,直接用标称焦距算,结果差了5毫米——嗯,后来才明白,焦距不是标称值,得标定出来。
关键点:针孔模型假设光线是直线传播的,没有畸变。但现实中的镜头都有畸变,所以我们需要先做畸变矫正,再用针孔模型去算。
镜头畸变:理想模型的"捣蛋鬼"
为什么实际镜头会有畸变?因为镜头是透镜组,不是小孔。光线穿过透镜时会发生折射,尤其是边缘区域。这就导致了两种主要的畸变:
径向畸变
径向畸变是沿着镜头半径方向发生的变形。离光心越远,变形越明显。它又分两种:
- 桶形畸变:图像向外凸出,像桶一样。广角镜头常见。
- 枕形畸变:图像向内凹陷,像枕头。长焦镜头常见。
数学上用泰勒级数来建模:
x_corrected = x * (1 + k1 * r² + k2 * r⁴ + k3 * r⁶)
y_corrected = y * (1 + k1 * r² + k2 * r⁴ + k3 * r⁶)
其中:
r —— 像素点到光心的距离
k1, k2, k3 —— 径向畸变系数
一般标定只算k1和k2就够了,k3用在鱼眼镜头那种大畸变场景。我遇到过一台老相机,k1大得离谱,标定完一看畸变图,边缘都快扭成麻花了。
切向畸变
切向畸变是因为镜头和成像平面不平行造成的。说白了就是镜头装歪了,或者传感器贴歪了。数学上:
x_corrected = x + [2 * p1 * x * y + p2 * (r² + 2 * x²)]
y_corrected = y + [p1 * (r² + 2 * y²) + 2 * p2 * x * y]
其中:
p1, p2 —— 切向畸变系数
我的经验:在HALCON中,畸变系数通常用5个参数表示:(k1, k2, p1, p2, k3)。但大多数工业镜头只需要前4个。如果k3标出来接近0,就别用了,省得引入噪声。
内参矩阵:相机自己的"身份证"
内参矩阵描述的是相机内部的光学特性。它把相机坐标系中的点映射到像素坐标系。矩阵形式如下:
[ fx 0 cx ]
K = [ 0 fy cy ]
[ 0 0 1 ]
其中:
fx, fy —— 焦距在x和y方向上的像素单位(通常fx ≈ fy)
cx, cy —— 主点坐标(光心在图像上的像素位置)
这个矩阵的作用,就是把三维相机坐标 (Xc, Yc, Zc) 投影到像素坐标 (u, v):
[ u ] [ fx 0 cx ] [ Xc ]
[ v ] = [ 0 fy cy ] * [ Yc ]
[ 1 ] [ 0 0 1 ] [ Zc ]
注意这里有个隐含的除法——实际像素坐标是 (u/Zc, v/Zc)。这就是为什么远处的物体看起来小。
常见坑:很多初学者以为cx, cy就是图像中心。其实不是!由于镜头安装和传感器切割的误差,主点往往偏离中心几个像素甚至几十个像素。我见过一台相机,主点偏了15个像素,用中心代替的话,测量误差直接翻倍。
外参矩阵:相机在空间中的"位置和姿态"
外参矩阵描述的是相机坐标系和世界坐标系之间的变换关系。它由两部分组成:
- 旋转矩阵 R:3×3矩阵,描述相机的朝向
- 平移向量 t:3×1向量,描述相机在世界坐标系中的位置
合起来就是:
[ Xc ] [ R11 R12 R13 tx ] [ Xw ]
[ Yc ] = [ R21 R22 R23 ty ] * [ Yw ]
[ Zc ] [ R31 R32 R33 tz ] [ Zw ]
[ 1 ] [ 0 0 0 1 ] [ 1 ]
这个矩阵把世界坐标 (Xw, Yw, Zw) 转换到相机坐标 (Xc, Yc, Zc)。然后经过内参矩阵,最终得到像素坐标。
整个成像链路的完整流程就是:
- 世界坐标 → 外参矩阵 → 相机坐标
- 相机坐标 → 内参矩阵 → 图像坐标
- 图像坐标 → 畸变模型 → 实际像素坐标
一句话总结:外参告诉你相机在哪、朝哪看;内参告诉你相机怎么把光线变成像素;畸变模型告诉你实际像素和理想像素的偏差。三者缺一不可。
HALCON中的参数表示
在HALCON里,标定结果通常用以下方式存储:
| 参数 | HALCON变量名 | 说明 |
|---|---|---|
| 内参矩阵 | CamParam | 包含 fx, fy, cx, cy, 畸变系数等 |
| 外参矩阵 | Pose | 包含旋转和平移,通常用7个值表示 |
| 畸变系数 | 内嵌在CamParam中 | k1, k2, p1, p2, k3 |
举个例子,读取标定文件后的典型代码:
* 读取相机内参
read_cam_par('calib.cal', CamParam)
* 读取某一幅图像的外参
read_pose('pose_01.dat', Pose)
* 查看内参
get_cam_par_data(CamParam, 'focus', Focus)
get_cam_par_data(CamParam, 'sx', PixelSizeX)
get_cam_par_data(CamParam, 'sy', PixelSizeY)
get_cam_par_data(CamParam, 'cx', CenterX)
get_cam_par_data(CamParam, 'cy', CenterY)
get_cam_par_data(CamParam, 'k1', K1)
get_cam_par_data(CamParam, 'k2', K2)
get_cam_par_data(CamParam, 'p1', P1)
get_cam_par_data(CamParam, 'p2', P2)
个人习惯:我每次标定完都会把内参打印出来看一眼。如果fx和fy相差超过1%,或者cx, cy偏离图像中心超过图像宽高的5%,我就会检查一下标定板图像质量,或者重新拍一组。别偷懒,这一步能省掉后面很多调试时间。