2. 相机模型与标定:针孔相机模型、内参外参、畸变模型、HALCON标定板与标定流程
各位同学,欢迎来到第二章。上一章我们聊了位姿估计的整体框架,这一章我们得把地基打牢——相机模型与标定。
说白了,相机标定就是搞清楚“世界上的一个点,是怎么跑到图像像素位置上去的”。这个映射关系搞不清楚,后面的位姿估计全是空中楼阁。我个人习惯把标定看作给相机做“体检”,测出它的内参、外参和畸变参数。今天我们就一步步拆解这个过程。
2.1 针孔相机模型——最朴素的成像原理
先看最经典的模型。你想想看,一个针孔相机,光线通过一个小孔投射到感光平面上,形成倒立的像。这就是针孔相机模型的核心思想。
数学上,它描述了一个从3D世界到2D图像的投影过程:
λ * [u, v, 1]^T = K * [R | t] * [X, Y, Z, 1]^T
这个公式你会在后续章节反复见到。我来拆解一下:
- [X, Y, Z, 1]^T:世界坐标系下的3D点(齐次坐标)
- [R | t]:外参矩阵,把世界坐标转到相机坐标
- K:内参矩阵,把相机坐标投影到像素平面
- [u, v, 1]^T:最终图像上的像素坐标
- λ:尺度因子,因为深度信息在投影中丢失了
嗯,这里要注意:针孔模型是理想情况。实际相机有镜头、有畸变,所以我们还得往下走。
2.2 内参与外参——相机自己的“身份证”和“位置牌”
内参是相机自身的固有属性,包括:
| 参数 | 符号 | 含义 |
|---|---|---|
| 焦距 | fx, fy | 镜头光心到成像平面的距离(像素单位) |
| 主点 | cx, cy | 光轴与成像平面的交点(像素坐标) |
| 倾斜因子 | s | 像素坐标系x轴与y轴的不垂直度(通常近似为0) |
内参矩阵K长这样:
K = [[fx, s, cx],
[ 0, fy, cy],
[ 0, 0, 1]]
外参描述的是相机在世界坐标系中的位置和朝向。它由旋转矩阵R和平移向量t组成。说白了,就是“相机放在哪,朝哪看”。
我在项目中遇到过一个问题:同一个相机,换个位置重新标定,内参基本不变,但外参完全变了。这很正常——外参就是每次安装都要重新测的。
2.3 畸变模型——镜头不是完美的
实际镜头有畸变。最常见的两种:
- 径向畸变:光线通过镜头边缘时弯曲,导致图像边缘的直线变弯。分为桶形畸变和枕形畸变。
- 切向畸变:镜头与成像平面不平行,导致图像倾斜。
畸变模型通常用多项式来描述:
x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6) + 2*p1*x*y + p2*(r^2 + 2*x^2)
y_distorted = y * (1 + k1*r^2 + k2*r^4 + k3*r^6) + p1*(r^2 + 2*y^2) + 2*p2*x*y
其中k1、k2、k3是径向畸变系数,p1、p2是切向畸变系数。r是点到主点的距离。
2.4 HALCON标定板——标定的“尺子”
HALCON使用棋盘格标定板。它的特点是:
- 黑白相间的方格,角点检测精度高
- 每个方格尺寸已知(比如30mm × 30mm)
- 标定板上有标记点(通常是圆点或特殊图案),用于确定方向
HALCON提供了两种标定板描述文件:
| 文件类型 | 扩展名 | 说明 |
|---|---|---|
| 标定板描述文件 | .descr | 描述标定板的几何尺寸和标记点位置 |
| 标定板图像文件 | .ps / .pdf | 用于打印标定板 |
我个人习惯用HALCON自带的 caltab.descr,它对应一个30mm方格、7×7格子的标定板。打印出来贴在平整的板上就行。
2.5 HALCON标定流程——手把手教你走一遍
标定流程可以分为6步。我建议你按这个顺序来:
- 准备标定板:打印标定板,贴在平整的刚性板上,测量方格实际尺寸。
- 采集标定图像:从不同角度、不同距离拍摄15-20张标定板图像。注意覆盖整个视场,尤其是边缘。
- 创建标定数据模型:使用
create_calib_data创建标定对象。 - 设置标定板描述:用
set_calib_data_calib_object加载 .descr 文件。 - 提取角点并标定:用
find_calib_object提取角点,然后用calibrate_cameras计算参数。 - 评估标定结果:查看重投影误差,如果大于0.5像素,建议重新采集图像。
下面是一个完整的HALCON标定代码示例:
* 创建标定数据模型
create_calib_data ('calibration_object', 'calibration_plate', 1, 1, CalibDataID)
* 设置标定板描述文件路径
set_calib_data_calib_object (CalibDataID, 0, 'caltab_30mm.descr')
* 设置相机初始内参(先给个估计值)
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', [0.008, 0, 0.000008, 0, 0, 640, 480, 1280, 960])
* 循环处理每张标定图像
for I := 1 to 20 by 1
read_image (Image, 'calib_images/calib_' + I$'02d')
find_calib_object (Image, CalibDataID, 0, 0, I-1, [], [])
get_calib_data_observ_contours (Contours, CalibDataID, 'caltab', 0, 0, I-1)
endfor
* 执行标定
calibrate_cameras (CalibDataID, Errors)
* 获取标定结果
get_calib_data (CalibDataID, 'camera', 0, 'params', CameraParam)
get_calib_data (CalibDataID, 'calib_obj_pose', [0, 0], 'pose', Pose)
* 显示重投影误差
disp_message (WindowHandle, '平均误差: ' + Errors + ' 像素', 'window', 12, 12, 'black', 'true')
2.6 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张图:
2.7 标定中的常见问题与避坑
最后,我分享几个实际项目中踩过的坑:
- 标定图像数量不够:少于10张,标定结果不稳定。我一般拍15-20张。
- 标定板没有覆盖视场边缘:边缘畸变参数会不准。记得让标定板出现在图像四角。
- 标定板不平整:贴在弯曲表面上,角点坐标会偏差。用玻璃板或铝板垫着。
- 光照变化太大:角点检测可能失败。保持均匀照明。
get_calib_data 把每张图像的重投影误差打印出来。如果某张图的误差明显大于其他图,我就删掉它重新标定。这样能有效提高整体精度。
好了,这一章的内容就到这里。你理解了相机模型和标定流程,后面做位姿估计时就会顺手很多。记住:标定是位姿估计的基石,这块功夫下足了,后面的路就好走了。