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 核心标定流程

好,图片拍好了,描述文件也有了。现在开始真正的标定。流程分三步:

  1. 读取图片:循环读入所有标定图片。
  2. 提取角点:用find_caltab和find_marks_and_pose找到标定板上的圆点。
  3. 执行标定:用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个像素,别急着用。检查一下:是不是有图片模糊了?标定板是不是没拍全?或者初始参数给得太离谱?

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 标定流程总览

我把整个流程画了张图,方便你对照着操作:

单目相机标定流程 ① 准备标定板 ② 拍摄15-20张标定图片 ③ 提取标定板角点 ④ 执行相机标定 ⑤ 保存内参文件 关键参数 焦距 f、畸变 κ 像元尺寸 Sx/Sy 主点坐标 Cx/Cy 质量指标 重投影误差 < 0.5 px 主点接近图像中心

这张图把整个标定流程串起来了。你照着走,基本不会出大问题。

3.8 常见问题与解决

最后,我把自己踩过的坑整理一下:

  • 标定失败,找不到标定板:检查图片对比度,或者调大find_caltab的对比度阈值参数。
  • 重投影误差太大:先剔除误差最大的几张图片,重新标定。我曾经用20张图片标定,剔除3张后误差从0.8降到了0.3。
  • 畸变参数异常大:可能是初始焦距给错了。查一下相机手册,把焦距填准。
  • 标定结果不稳定:每次标定结果都不一样?说明图片数量不够,或者角度变化太小。加到25张试试。
💡 我的习惯
标定完成后,我会用distortion_correction矫正一张图片看看效果。如果矫正后的图像边缘直线变直了,说明标定没问题。如果反而更弯了……嗯,重新来吧。

好了,内参文件拿到手,咱们的相机就算“认识”这个世界了。下一件事,就是让相机和机器人“认识”彼此——那就是手眼标定的内容了。