3. 相机模型与内参
做3D视觉的人,天天跟相机打交道。但说实话,很多人用了很久的相机,对相机到底怎么成像的,还是一知半解。我刚开始做Halcon标定时也踩过不少坑,后来才明白——搞懂相机模型和内参,是深度图标定的地基。地基不稳,后面全白搭。
这一章,咱们就把针孔模型、畸变模型、内参矩阵这几个核心概念彻底讲透。我会结合项目经验,告诉你每个参数到底在说什么,以及它们怎么影响你的点云精度。
3.1 针孔模型:相机成像的“理想世界”
先问个问题:相机是怎么把三维世界“拍”成二维图像的?
最经典的模型就是针孔模型。说白了,它假设光线从物体出发,穿过一个小孔,投射到后面的成像平面上。这个小孔就是光心,成像平面就是传感器。
这个模型有三个关键坐标系:
- 世界坐标系:物体在真实世界的位置,单位是mm或m
- 相机坐标系:以光心为原点,Z轴指向物体方向
- 图像坐标系:成像平面上的像素位置,单位是pixel
从世界坐标到像素坐标,中间经历两次变换:
- 刚体变换:世界坐标 → 相机坐标(旋转+平移,即外参)
- 透视投影:相机坐标 → 图像坐标(用内参矩阵)
透视投影的公式很简单:
x = f * (X / Z)
y = f * (Y / Z)
其中f是焦距,X、Y、Z是相机坐标系下的坐标。你看,Z在分母上——这就是“近大远小”的数学本质。
3.2 畸变模型:现实世界的“不完美”
理想很丰满,现实很骨感。实际镜头不是完美的小孔,光线穿过透镜会发生折射,导致图像变形。这就是畸变。
畸变主要分两类:
| 畸变类型 | 表现 | 数学模型 | 典型参数 |
|---|---|---|---|
| 径向畸变 | 直线变弯,越靠近边缘越明显 | 多项式:k1, k2, k3 | 桶形畸变(负值)、枕形畸变(正值) |
| 切向畸变 | 镜头与传感器不平行,图像倾斜 | p1, p2 | 镜头装配误差导致 |
径向畸变的校正公式:
x_corrected = x * (1 + k1*r² + k2*r⁴ + k3*r⁶)
y_corrected = y * (1 + k1*r² + k2*r⁴ + k3*r⁶)
其中r是像素到光心的距离。你看,r越大(越靠边缘),畸变越严重。
3.3 内参矩阵:相机的“身份证”
内参矩阵,说白了就是相机的“出厂配置”。它描述了相机内部的光学特性,一旦标定好,只要焦距和分辨率不变,内参就不变。
内参矩阵的数学形式:
K = [fx, 0, cx
0, fy, cy
0, 0, 1]
每个参数的物理意义:
| 参数 | 物理意义 | 单位 | 典型值(以1920×1080为例) |
|---|---|---|---|
| fx | x方向焦距(像素单位) | pixel | ~1200-1400 |
| fy | y方向焦距(像素单位) | pixel | ~1200-1400 |
| cx | 主点x坐标(光心在图像上的位置) | pixel | ~960 |
| cy | 主点y坐标 | pixel | ~540 |
这里有个关键点:fx和fy通常不相等。为什么?因为传感器像素不一定是正方形。我见过很多新手以为fx=fy,结果标定出来的点云在x和y方向尺度不一致。
3.4 从内参到深度图标定:为什么重要?
你可能会问:内参跟深度图标定有什么关系?
关系大了。深度图标定的核心,就是建立深度值(Z)与像素坐标(u,v)之间的映射关系。而这个映射,必须以内参矩阵为基础。
具体来说:
- 内参矩阵告诉你每个像素对应的光线方向
- 深度图告诉你该方向上的距离
- 两者结合,才能算出三维坐标
公式如下:
X = (u - cx) * Z / fx
Y = (v - cy) * Z / fy
Z = 深度值
你看,如果fx、fy、cx、cy标不准,算出来的X、Y坐标全错。这就是为什么我反复强调:内参标定是深度图标定的第一步,也是最关键的一步。
3.5 知识结构总览
为了让你更直观地理解这一章的知识体系,我画了一张结构图:
3.6 Halcon中的内参标定实战
理论讲完了,咱们看看Halcon里怎么操作。我个人习惯用标定板法,精度最高。
核心算子就这几个:
* 创建标定数据模型
create_calib_data ('calibration_object', 1, 1, CalibDataID)
* 设置相机内参初始值
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', [0.008, 0, 0.000008, 0, 0, 640, 480, 1280, 960])
* 添加标定板描述
set_calib_data_calib_object (CalibDataID, 0, 'caltab_30mm.descr')
* 标定
calibrate_cameras (CalibDataID, Errors)
* 获取标定结果
get_calib_data (CalibDataID, 'camera', 0, 'params', CameraParam)
标定完成后,CameraParam里就包含了内参矩阵和畸变系数。你可以用tuple_select提取各个参数:
* CameraParam格式:[fx, fy, cx, cy, k1, k2, p1, p2, k3]
fx := CameraParam[0]
fy := CameraParam[1]
cx := CameraParam[2]
cy := CameraParam[3]
k1 := CameraParam[4]
k2 := CameraParam[5]
p1 := CameraParam[6]
p2 := CameraParam[7]
k3 := CameraParam[8]
3.7 内参标定的常见问题
| 问题 | 表现 | 原因 | 解决方案 |
|---|---|---|---|
| 重投影误差大 | 标定误差 > 0.5像素 | 标定板图像太少或角度不够 | 拍15-20张不同角度的标定板图像 |
| fx/fy差异大 | 差异 > 3% | 传感器像素非正方形 | 确认相机规格,正常现象 |
| 主点偏移大 | cx, cy偏离图像中心 > 5% | 镜头装配误差或传感器移位 | 重新标定,检查镜头安装 |
| 畸变参数不稳定 | 多次标定结果差异大 | 标定板图像质量差或光照不均 | 改善光照,使用高对比度标定板 |
嗯,这一章的内容就到这里。内参是深度图标定的基石,搞懂了针孔模型、畸变模型和内参矩阵,后面的路就好走了。记住:内参标定不是一次性的工作,每次更换镜头、调整焦距、甚至环境温度变化,都可能需要重新标定。