3. 相机标定与深度图获取

各位同学,欢迎来到第三章。这一章我们聊聊3D视觉的“地基”——相机标定与深度图获取。说白了,就是搞清楚你的3D相机到底是怎么看到深度的,以及怎么让它看得准。

我刚开始接触3D视觉时,觉得标定这事儿挺枯燥的。后来在产线上吃过亏——一台结构光相机测出来的平面度总是偏0.2mm,折腾了两天才发现是标定板放歪了。从那以后,我对标定流程再也不敢马虎。

3.1 三种主流3D相机:你该选哪种?

先看个总览表,心里有个底。

类型 原理 典型距离 精度 典型场景
结构光 投射编码条纹,三角法解算 0.1~5 m 0.01~0.1 mm 手机人脸解锁、PCB焊点检测
双目立体 双相机视差匹配 0.3~20 m 0.1~1 mm AGV避障、物流体积测量
ToF 发射光脉冲,测量飞行时间 0.1~10 m 1~10 mm Kinect、手势识别、安全围栏

结构光:精度最高,但怕强光。我在做手机中框检测时用的就是它,0.02mm的深度分辨率,连划痕都能看清。不过室外阳光直射下基本废掉。

双目立体:成本最低,两个普通相机就行。但纹理弱的表面(比如白墙)会匹配失败。我记得有个项目测白色泡沫板,双目直接“瞎了”,后来不得不加投影纹理。

ToF:速度快,一帧就能拿到深度图。但分辨率低,边缘有飞点。适合做人体跟踪,不适合精密测量。

我的建议:精密测量选结构光,大范围粗测选双目,实时交互选ToF。别指望一种相机包打天下。

3.2 相机标定原理:到底在标什么?

标定,说白了就是求两个东西:内参和外参。

  • 内参:焦距、主点、畸变系数。这是相机自己的“脾气”。
  • 外参:相机在世界坐标系下的位置和姿态。对于3D相机,还包括左右相机或投影仪之间的相对位姿。

为什么重要?你想想看,如果内参不准,一个平面点云会被算成曲面。我在一个项目中遇到过这种情况——标定板明明是平的,重建出来却像锅盖。重新标定后立马正常。

标定的数学核心是张正友标定法。流程大致是:

  1. 打印一张棋盘格标定板
  2. 从不同角度拍10~20张图
  3. 提取角点坐标
  4. 用最小二乘法求解单应矩阵
  5. 分解出内参和外参
  6. 用非线性优化微调所有参数

嗯,这里要注意:标定板一定要平整。我曾经用亚克力板自己打印,结果板子有点弯,标定出来的畸变参数全是错的。后来换了陶瓷标定板,一次过。

3.3 Halcon标定流程:一步步来

Halcon的标定非常成熟,我一般用calibrate_cameras算子。下面是我常用的流程:

3.3.1 准备标定板

Halcon支持两种标定板:棋盘格和圆点阵。我个人习惯用圆点阵,因为圆心提取精度更高。

小技巧:用gen_caltab生成标定板描述文件(.descr),打印时注意实际尺寸要和描述文件一致。我一般用7×7的圆点阵,间距12.5mm。

3.3.2 采集图像

采集时注意:

  • 标定板要占画面1/4以上
  • 角度要覆盖俯仰、偏航、旋转
  • 距离要覆盖工作范围
  • 至少15张,我通常拍20张

3.3.3 执行标定

核心代码片段如下:

* 创建标定数据模型
create_calib_data ('calibration_object', 'calibration_plate', 1, 1, CalibDataID)
set_calib_data_calib_object (CalibDataID, 0, 'caltab.descr')

* 设置相机初始内参
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', [0.008, 0, 0.00001, 1280, 1024, 640, 512])

* 逐张图像提取角点并加入标定
for i := 1 to 20 by 1
    read_image (Image, '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)

避坑指南:我曾经在标定双目相机时,左右图像没有同步采集,导致外参算出来有0.5°的旋转误差。一定要用硬件触发同步,或者至少保证标定板在两张图中位置完全一致。

3.4 深度图生成:从标定到深度

标定完成后,就可以生成深度图了。不同相机生成方式不同:

  • 结构光:通过解调条纹相位,得到每个像素的深度值
  • 双目:通过立体匹配算法(如SGM)计算视差图,再转深度
  • ToF:直接输出深度图,但需要做飞点滤波

在Halcon中,双目深度图生成的标准流程:

* 读取标定好的相机参数
read_cam_par ('left_cam.dat', CamParamLeft)
read_cam_par ('right_cam.dat', CamParamRight)
read_pose ('relative_pose.dat', RelPose)

* 立体校正
gen_binocular_rectification_map (Map1, Map2, CamParamLeft, CamParamRight, RelPose, 1, 'half', 'bilinear', RectCamParamLeft, RectCamParamRight)

* 对左右图像进行校正
map_image (ImageLeft, Map1, RectImageLeft)
map_image (ImageRight, Map2, RectImageRight)

* 立体匹配生成视差图
disparity (RectImageLeft, RectImageRight, Disparity, 'sgm', 'default', 'default', 64, 5, 10, 20, 1, 'default', 'default', 'default')

* 视差转深度图
disparity_to_depth (Disparity, Depth, CamParamLeft, CamParamRight, RelPose, 1)

生成的深度图是一个单通道图像,每个像素值代表该点的深度(单位:米)。

关键点:深度图的质量90%取决于标定精度。标定做不好,后面的点云处理全是白费功夫。我每次标定完都会用标定板验证一下——重建出来的板子平面度应该在0.02mm以内才算合格。

3.5 本章知识体系

下面这张图帮你理清本章的核心逻辑:

3D相机标定与深度图获取知识体系 3D相机标定与深度图获取知识体系 3D相机类型 相机标定原理 Halcon标定流程 结构光 双目立体 ToF 内参 外参 畸变系数 准备标定板 采集图像 提取角点 执行标定 深度图生成 条纹解调 立体匹配 飞点滤波 视差转深度 核心:标定精度决定深度图质量,深度图质量决定后续点云处理效果