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相机,还包括左右相机或投影仪之间的相对位姿。
为什么重要?你想想看,如果内参不准,一个平面点云会被算成曲面。我在一个项目中遇到过这种情况——标定板明明是平的,重建出来却像锅盖。重新标定后立马正常。
标定的数学核心是张正友标定法。流程大致是:
- 打印一张棋盘格标定板
- 从不同角度拍10~20张图
- 提取角点坐标
- 用最小二乘法求解单应矩阵
- 分解出内参和外参
- 用非线性优化微调所有参数
嗯,这里要注意:标定板一定要平整。我曾经用亚克力板自己打印,结果板子有点弯,标定出来的畸变参数全是错的。后来换了陶瓷标定板,一次过。
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 本章知识体系
下面这张图帮你理清本章的核心逻辑: