第2章:相机成像模型——从物理世界到像素坐标
做机器视觉的人,天天跟相机打交道。但说实话,很多人用了几年相机,对成像原理还是一知半解。我刚开始入行时也是这样——标定过了就行,管它什么模型不模型。直到有一次项目,相机换了个角度,标定结果就飘了,排查了整整两天才发现是畸变参数没设对。
从那以后,我养成了一个习惯:每次做标定前,先把相机模型在脑子里过一遍。说白了,你只有知道相机是怎么把三维世界变成二维图像的,才能理解标定到底在干什么。
2.1 针孔相机模型——最朴素的成像原理
针孔相机模型,是所有成像模型的基础。它假设光线通过一个小孔,在后面的成像平面上形成倒立的像。嗯,虽然真实相机用的是透镜,但数学上我们可以用这个模型来近似。
核心就四个坐标系之间的转换:
- 世界坐标系(Xw, Yw, Zw)——物体在真实世界中的位置
- 相机坐标系(Xc, Yc, Zc)——以相机光心为原点
- 图像物理坐标系(x, y)——成像平面上的实际物理位置
- 图像像素坐标系(u, v)——我们最终看到的像素位置
整个流程就是:世界点 → 刚体变换到相机坐标系 → 透视投影到成像平面 → 离散化为像素坐标。
关键公式(透视投影):
x = f * (Xc / Zc)
y = f * (Yc / Zc)
其中 f 是焦距,Zc 是物体到相机的深度。注意这里有个除法——深度越远,成像越小。这就是近大远小的数学本质。
我见过不少新手问:为什么标定板要放不同角度?其实就是为了让 Zc 有变化,这样才能解算出完整的模型参数。
2.2 内参矩阵——把物理坐标变成像素坐标
从图像物理坐标 (x, y) 到像素坐标 (u, v),需要一个转换矩阵。这就是内参矩阵 K:
K = [fx 0 cx]
[ 0 fy cy]
[ 0 0 1]
参数含义:
| 参数 | 含义 | 单位 |
|---|---|---|
| fx, fy | 焦距在 x、y 方向的像素当量 | 像素 |
| cx, cy | 主点坐标(光轴与成像平面的交点) | 像素 |
fx = f / dx,其中 dx 是每个像素的物理宽度。说白了,fx 告诉你:在距离相机 1 米处,一个像素对应多少毫米?
我的经验:cx、cy 理论上应该在图像正中心,但实际生产中往往有偏移。我曾经遇到一台相机,cx 偏了 15 个像素,结果手眼标定怎么都对不准。后来重新标定内参,问题就解决了。所以别迷信出厂参数,自己标一遍最靠谱。
2.3 畸变模型——真实世界不完美
针孔模型是理想情况。真实相机有透镜,透镜就有畸变。主要分两类:
径向畸变(Radial Distortion)
说白了就是图像边缘被拉伸或压缩。离中心越远,变形越明显。数学模型:
x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6)
y_distorted = y * (1 + k1*r^2 + k2*r^4 + k3*r^6)
其中 r 是点到主点的距离,k1、k2、k3 是径向畸变系数。一般 k1、k2 就够了,鱼眼镜头才需要 k3。
切向畸变(Tangential Distortion)
这个是因为透镜和成像平面不平行造成的。模型:
x_distorted = x + [2*p1*x*y + p2*(r^2 + 2*x^2)]
y_distorted = y + [p1*(r^2 + 2*y^2) + 2*p2*x*y]
p1、p2 是切向畸变系数。
避坑指南:我曾经在一个项目中,标定结果看起来很好(重投影误差 0.05 像素),但实际测量就是不准。后来发现是畸变参数太多,过拟合了。对于普通工业相机,建议只标定 k1、k2、p1、p2 四个参数。k3 和更多高阶项反而会引入噪声。
2.4 HALCON 相机标定助手实操
理论讲完了,咱们上手。HALCON 的标定助手是我用过最顺手的工具之一。操作流程:
- 准备标定板——HALCON 支持多种标定板,最常用的是圆点阵列板。注意标定板要平整,不能有划痕。
- 采集图像——至少 10-15 张,覆盖不同角度、不同位置。我习惯让标定板出现在图像的四个角和中心,这样畸变参数才准确。
- 打开标定助手——在 HALCON 菜单栏找到 "Calibration" → "Calibration Assistant"。
- 加载标定板描述文件——.descr 文件,里面定义了标定板的物理尺寸和特征点布局。
- 读入图像——点击 "Load Images",选择刚才采集的图片。
- 提取特征点——助手会自动检测标定板上的圆点中心。检查一下有没有误检或漏检。
- 设置初始参数——输入相机型号、像元尺寸、焦距等。这些可以从相机手册里找到。
- 开始标定——点击 "Calibrate",等待计算完成。
- 检查结果——看重投影误差,一般小于 0.1 像素就算不错。如果大于 0.3 像素,说明有问题。
HALCON 标定核心代码片段:
* 创建标定数据模型
create_calib_data ('calibration_object', 'calibration_object', CalibDataID)
* 设置标定板描述文件
set_calib_data_calib_object (CalibDataID, 0, 'caltab.descr')
* 设置相机初始参数
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', [0.012, 0, 0.008, 0, 0, 0, 0, 0])
* 添加标定图像
for i := 1 to 15 by 1
read_image (Image, 'calib_' + i$'02d')
find_calib_object (Image, CalibDataID, 0, 0, i, [], [])
endfor
* 执行标定
calibrate_cameras (CalibDataID, Errors)
* 获取标定结果
get_calib_data (CalibDataID, 'camera', 0, 'params', CameraParam)
2.5 本章知识结构图
下面这张图把相机成像的完整流程和标定要素串起来了。建议你保存下来,做项目时对照着看。
2.6 标定结果验证——别急着收工
标定完成后,我建议你做三件事验证:
- 看重投影误差——HALCON 会给出每个点的误差,平均值小于 0.1 像素算合格。如果某张图误差特别大,删掉重标。
- 看畸变矫正效果——用
change_radial_distortion_cam_par生成无畸变图像,检查边缘直线是否变直了。 - 实际测量验证——放一个已知尺寸的物体,用标定结果测量,看误差是否在容忍范围内。
我曾经踩过的坑:有一次标定结果重投影误差只有 0.03 像素,我信心满满地去做手眼标定。结果机器人抓取总是偏 2mm。排查到最后发现,标定板本身有 0.1mm 的平面度误差。所以记住:标定板的精度决定了标定结果的上限。便宜标定板真的会害死人。
2.7 本章小结
相机成像模型,说白了就是回答一个问题:三维空间里的一个点,最终落在图像哪个像素上?
针孔模型给了我们数学框架,内参矩阵完成了物理到像素的转换,畸变模型修正了透镜带来的变形。HALCON 标定助手把这些都封装好了,但你要理解背后的原理,才能在出问题时知道从哪里下手。