3、HALCON标定实战:单目相机标定与内参文件生成
好了,咱们进入真正的实战环节。前面聊了那么多坐标系的理论,现在终于要动手了。这一章,我带你走一遍单目相机标定的完整流程——从准备标定板,到跑代码,最后生成那个关键的相机内参文件。
说实话,标定这件事,看着简单,但坑不少。我刚开始做视觉的时候,就因为在标定板上偷了懒,结果后面折腾了整整两天。嗯,咱们一步步来,把该注意的地方都说到。
3.1 标定前的准备工作
别急着开HALCON,先把这几样东西准备好:
- 标定板:HALCON官方推荐用棋盘格或圆点阵。我个人习惯用棋盘格,角点检测更稳定。
- 标定板描述文件:一个.descr文件,告诉HALCON你的标定板长什么样。
- 至少15-20张图片:从不同角度、不同距离拍摄标定板。
⚠️ 避坑指南
我曾经因为标定板不平整,导致标定出来的内参偏差很大。标定板一定要贴在平整的硬板上,别用软纸打印就往上贴。
我曾经因为标定板不平整,导致标定出来的内参偏差很大。标定板一定要贴在平整的硬板上,别用软纸打印就往上贴。
3.2 生成标定板描述文件
HALCON里有个专门工具——gen_caltab。你可以在代码里直接调用,也可以用HDevelop的助手生成。我一般用代码,因为参数可控。
下面是我常用的生成脚本:
* 生成7x7圆点阵标定板,间距1.25mm
gen_caltab (
7, 7, * 行列上的圆点数量
0.0075, * 圆点直径(单位:米)
0.0125, * 圆点间距(单位:米)
'caltab.descr', * 输出描述文件名
'caltab.ps' * 输出可打印文件
)
💡 小提示
圆点直径和间距的单位是米。如果你用毫米,记得换算。我习惯用米,因为后面和机器人坐标系对接时,单位统一不容易出错。
圆点直径和间距的单位是米。如果你用毫米,记得换算。我习惯用米,因为后面和机器人坐标系对接时,单位统一不容易出错。
运行后,你会得到两个文件:
caltab.descr:描述文件,标定时要用。caltab.ps:PostScript文件,打印出来就是标定板。
3.3 拍摄标定图片
这一步看着简单,但很多人栽在这里。你想想看,如果图片质量不行,后面算法再牛也白搭。
拍摄时注意这几点:
| 要求 | 说明 |
|---|---|
| 光照均匀 | 避免反光、阴影覆盖标定板 |
| 角度多样 | 倾斜角度在30°-45°之间,别太平 |
| 覆盖视场 | 标定板出现在图像的四个角和中心 |
| 距离变化 | 近、中、远各拍几张 |
| 图片清晰 | 不要模糊,对焦要准 |
我记得有一次,客户发来的标定图片全是正对着标定板拍的,角度几乎没有变化。结果标定出来的畸变参数完全不对。说白了,标定板必须“动起来”,让算法能从不同视角看到它。
3.4 核心标定流程
好,图片拍好了,描述文件也有了。现在开始真正的标定。流程分三步:
- 读取图片:循环读入所有标定图片。
- 提取角点:用
find_caltab和find_marks_and_pose找到标定板上的圆点。 - 执行标定:用
camera_calibration计算内参和外参。
下面是一个完整的标定代码示例:
* 创建标定数据模型
create_calib_data ('calibration_object', 'camera_calibration', [], [], CalibDataID)
* 设置相机类型和初始参数
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', [0.008, 0, 0.000012, 0.000012, 640, 480, 320, 240])
* 设置标定板描述文件
set_calib_data_calib_object (CalibDataID, 0, 'caltab.descr')
* 循环处理每张图片
for i := 1 to 20 by 1
read_image (Image, 'calib_images/calib_' + i$'02d')
* 查找标定板
find_caltab (Image, CaltabRegion, 'caltab.descr', 3, 112, 5)
* 提取圆点并获取位姿
find_marks_and_pose (Image, CaltabRegion, 'caltab.descr', 8, 10, 18, 0.9, 15, 100, AllMarks, Pose)
* 将位姿加入标定数据
set_calib_data (CalibDataID, 'calib_obj_pose', [0, i], 'pose', Pose)
endfor
* 执行标定
calibrate_cameras (CalibDataID, Errors)
* 获取标定结果
get_calib_data (CalibDataID, 'camera', 0, 'params', CameraParam)
* 保存内参文件
write_cam_par (CameraParam, 'camera_params.dat')
🔑 关键点
set_calib_data_cam_param中的初始参数很重要。焦距、像元尺寸、图像分辨率这些,最好从相机手册里查一下,别随便填。初始值越准,标定收敛越快。
3.5 标定结果解读
标定完成后,CameraParam里存的就是相机内参。对于area_scan_division模型,参数顺序如下:
| 参数索引 | 含义 | 示例值 |
|---|---|---|
| 0 | 焦距 f (米) | 0.0082 |
| 1 | 畸变系数 κ | -1200.5 |
| 2 | 像元宽度 Sx (米) | 0.000012 |
| 3 | 像元高度 Sy (米) | 0.000012 |
| 4 | 主点列坐标 Cx (像素) | 319.8 |
| 5 | 主点行坐标 Cy (像素) | 239.5 |
| 6 | 图像宽度 (像素) | 640 |
| 7 | 图像高度 (像素) | 480 |
你可能会问:怎么判断标定结果好不好?
看两个指标:
- 重投影误差:
calibrate_cameras返回的Errors数组。一般小于0.5像素就算不错。我见过最好的能到0.1像素以下。 - 主点位置:理论上应该在图像中心附近。如果偏差超过几十个像素,说明标定可能有问题。
⚠️ 注意
如果重投影误差大于1个像素,别急着用。检查一下:是不是有图片模糊了?标定板是不是没拍全?或者初始参数给得太离谱?
如果重投影误差大于1个像素,别急着用。检查一下:是不是有图片模糊了?标定板是不是没拍全?或者初始参数给得太离谱?
3.6 内参文件长什么样?
保存后的camera_params.dat是个文本文件,打开看看:
calibration file for camera 0
area_scan_division
0.00821234
-1250.67
0.000012
0.000012
319.845
239.512
640
480
嗯,就是这么简单。但别小看这几行数字,后面做手眼标定、坐标转换,全得靠它。
3.7 标定流程总览
我把整个流程画了张图,方便你对照着操作:
这张图把整个标定流程串起来了。你照着走,基本不会出大问题。
3.8 常见问题与解决
最后,我把自己踩过的坑整理一下:
- 标定失败,找不到标定板:检查图片对比度,或者调大
find_caltab的对比度阈值参数。 - 重投影误差太大:先剔除误差最大的几张图片,重新标定。我曾经用20张图片标定,剔除3张后误差从0.8降到了0.3。
- 畸变参数异常大:可能是初始焦距给错了。查一下相机手册,把焦距填准。
- 标定结果不稳定:每次标定结果都不一样?说明图片数量不够,或者角度变化太小。加到25张试试。
💡 我的习惯
标定完成后,我会用
标定完成后,我会用
distortion_correction矫正一张图片看看效果。如果矫正后的图像边缘直线变直了,说明标定没问题。如果反而更弯了……嗯,重新来吧。
好了,内参文件拿到手,咱们的相机就算“认识”这个世界了。下一件事,就是让相机和机器人“认识”彼此——那就是手眼标定的内容了。