一架无人机在室内走廊里低速飞行,飞控上报的坐标是(1.5, 2.3, 1.0),单位是米。这个数字到底怎么来的?GPS在室内早就失效了,光流在纯色墙面前也会漂移,此时最可靠的感知手段就是视觉。但相机输出的只是一帧 1920×1080 的像素矩阵,从这些离散的 RGB 数值到“我在空间哪个位置”的连续坐标,中间隔着的不是一步,而是一整套数学模型——这就是标题里说的相机模型与标定。这篇文章我想把这条链路从头到尾拆一遍,从针孔模型到底层畸变,从标定板选型到 Kalibr 实操,再补上我在无人机视觉项目里踩过的几个真实的坑。内容主要面向正在做无人机视觉感知、SLAM 入门、以及想把相机真正用起来做空间定位的开发者。
1. 室内无GPS时,无人机靠什么给自己定位
1.1 从像素到坐标:相机就是无人机的一双“眼睛”
先把问题摆到台面上:无人机要“理解空间中的真实位置”,它理解的是什么?是一组相对于某个参考系的坐标,比如机体系下的(x, y, z)或者世界系下的经纬度加高度。GPS 直接给经纬度,所以室外飞的时候飞控很轻松;但在室内、桥洞下、树冠遮挡密集的区域,GPS 信号要么没有,要么跳变严重。这时候就得换一套测距手段,而视觉是成本最低、信息量最大的方案。
相机本质上是一个“投影仪的反向设备”:三维世界里的一个点P(X, Y, Z),经过镜头的光学中心,投射到图像传感器上,变成一个像素点p(u, v)。这个从(X, Y, Z)到(u, v)的映射关系,就是相机模型要回答的问题。知道了这个映射,无人机看到图像上某个特征点,就能反向推算它在空间中的方向;再配合多个角度的观测或者 IMU 的加速度积分,就能估算出自身位置。
这里有个最容易被新手忽略的点:像素坐标本身没有任何物理意义,必须结合相机内参才能把像素“还原”成一条方向射线。而这条射线到底准不准,完全取决于内参标定得准不准。很多人以为标定就是跑个脚本出一堆数字,实际上数字只是开始,后面所有定位、建图、避障算法的精度,都被这几个数字锚定着。
1.2 视觉定位的两条经典路线:VIO与全局重定位
无人机视觉定位大体上分两派:一派是 VIO(Visual-Inertial Odometry),代表作是 VINS-Fusion、OKVIS、ROVIO;另一派是全局重定位,典型手段是 map-based localization,比如先建好一张地图,然后无人机对着当前图像找自己在地图中的位置。
VIO 的核心逻辑是:把相机当成“速度计”,通过连续帧之间特征点的移动估算出相机的相对运动,再用 IMU 的角速度与加速度数据把尺度拉回来。这里有个关键前提——特征点在不同帧之间的像素位移必须和真实运动保持一致。如果内参标定错了,比如焦距偏了 2%,那么算法算出来的平移量也会按比例偏掉;畸变参数没标准,图像边缘的特征点位置就会系统性偏移,导致估计轨迹出现明显的弯曲。
全局重定位则更直接:当前图像里的特征描述子,和预存地图里的特征描述子做匹配,匹配对了,就能直接给出相机的位姿。但这一步对特征点位置的准确性极其敏感,一个像素级别的偏差,在 5 米外的物体上就对应几厘米的空间误差;如果畸变校正不到位,边缘区域的误差会被放大到十几甚至几十厘米,直接导致定位跳变。
所以无论走哪条路线,内参和外参的精度都决定了整个系统的上限。这也是为什么这篇文章愿意花大篇幅去讲模型和标定,而不是直接丢一段 Kalibr 命令让你去跑——不理解原理的人,出了问题根本不知道怎么排查。
2. 针孔模型与畸变:把三维世界“压缩”进二维图像的那道数学门
2.1 内参矩阵里每个数字到底在说什么
先建立最朴素的针孔相机模型。想象一个暗箱,前面戳一个小孔,外面的光线通过小孔后在箱内壁上成一个倒像。为了数学上方便处理,我们把成像平面翻到小孔前方,形成虚拟像平面,这样图像就是正立的。设相机坐标系原点在光心O,Z轴指向相机前方,一个空间点P(X, Y, Z)在虚拟像平面上的投影点坐标是:
x = f * X / Z y = f * Y / Zf是焦距,单位是毫米。但图像像素坐标的原点在左上角,u轴向右,v轴向下,而且像素本身是长方形排列的,所以还要引入像素坐标系到物理坐标系的转换:
u = fx * X / Z + cx v = fy * Y / Z + cy这里的fx、fy是焦距在像素单位下的表达,实际等于物理焦距除以单个像素的物理尺寸;cx、cy是主点坐标,理论上应该在图像正中心,但实际生产装配总有偏差。把这四个参数写成矩阵形式,就是内参矩阵K:
K = [fx 0 cx] [ 0 fy cy] [ 0 0 1]有人问,为什么fx和fy不相等?因为像素不是正方的,尤其是卷帘快门的 CMOS 传感器,横向和纵向的像元尺寸可能有细微差异。如果你用的是普通工业相机,标定出来的fx和fy比值通常在 1 附近,但如果是鱼眼镜头或者经过前置镜头组,差异会明显变大。
主点cx、cy是很多人标定完从不看的参数,但实际上它直接决定了图像的“有效中心”。如果cx偏差很大,说明镜头光轴和传感器中心没有对准,这在无人机挂载相机时特别常见——安装座加工误差、镜头拧歪、甚至撞击后的微小位移都会导致主点漂移。VIO 算法里主点错误会导致轨迹在旋转时出现系统性偏移,很难在后期通过调参补偿。
2.2 畸变为什么躲不掉,以及张氏标定法怎么解决它
真实镜头不是数学上的针孔,而是一组透镜。透镜的折射会引入两种主要畸变:
- 径向畸变:画面边缘的直线向内凹或向外凸。原因是透镜边缘的曲率比中心大或更复杂,导致入射角与投影位置不完全线性。
- 切向畸变:镜头组装配时,各镜片的光轴不完全重合,导致图像在某些区域产生“拉扯”。在低价镜头上尤其明显。
径向畸变的数学模型通常用泰勒级数展开:
x_corrected = x * (1 + k1*r^2 + k2*r^4 + k3*r^6) y_corrected = y * (1 + k1*r^2 + k2*r^4 + k3*r^6)这里r是像素点到主点的距离。切向畸变用p1、p2两个参数建模:
x_corrected = x + 2*p1*x*y + p2*(r^2 + 2*x^2) y_corrected = y + p1*(r^2 + 2*y^2) + 2*p2*x*y听到这里很多人头大了,但只要记住一件事:畸变系数的作用是在对图像去畸变时把边缘像素拉回正确位置。张正友标定法之所以成为行业标准,是因为它只需要相机对着一个平面棋盘格拍十几张不同角度的照片,就能同时估计出内参和畸变系数,过程可以完全自动化,不需要昂贵的三维标定设备。其核心是利用棋盘格角点的共面性,先通过单应矩阵建立每张图像的角度约束,再通过多张图像联合优化所有参数。
我自己在标定后会做的事情是:用 OpenCV 把一张棋盘格图像做去畸变,然后叠加到原图上,肉眼观察边缘直线是否恢复笔直。这一步比任何数值指标都直观,也是快速发现标定异常的第一道关卡。
3. 标定全流程实战:从标定板选型到 Kalibr 一次跑通
3.1 标定板、镜头和采集姿势的选择细节
标定板是第一步,很多人随便在某宝买一张几十块的棋盘格就开干,后面出了问题才回头找原因。棋盘格要关注几个参数:
- 格子边长精度:标定算法假设每个格子的物理尺寸是已知的,如果边长标称 30mm 实际是 29.5mm,内参里的焦距就会跟着偏 1.7%;
- 表面平整度:纸张覆膜后贴在不平的泡沫板上,棋盘格整体会有局部扭曲,标定出来的畸变系数会参考这种错误扭曲,导致边缘去畸变后反而更糟;
- 覆膜反光:哑光覆膜最好,亮面在斜射光照下会产生高光,角点检测会失败或者出现半像素偏移。
如果你做的是无人机视觉项目,建议直接用 25mm 到 40mm 格子边长的陶瓷基板或者铝基板,重量虽然比纸板重一点,但平整度、耐磨性和抗反光能力完全不在一个级别。室内环境用 A3 纸打印覆哑光膜也能凑合,但照片数量最好多采 20 张以上。
镜头选择也有讲究。标定用的镜头应该是你实际飞行时用的镜头,不能在标定后更换。无人机视觉模块如果需要调焦,最好选锁焦镜头,标定完用胶点死;如果用的是可变焦镜头,每换一次焦距就要重新标定,这也是 ZED 2i 这类双目相机容易被吐槽的原因——它出厂标定不错,但你拆装过镜头或者大力碰撞后,内参就可能漂移,必须重新标定。
3.2 数据采集的五个黄金准则
数据采集效率直接决定标定质量。我总结出五个准则,基本都是踩坑踩出来的:
保持标定板在画面中有足够占比:至少占画面的三分之一,否则角点像素太少,亚像素提取精度不够,标定结果误差大。离得太远的结果是内参偏小,畸变系数会被平均掉。
姿态多样化:棋盘格相对于相机的姿态至少要有三个不同方向的倾斜,包括:正面平视、绕水平轴上下倾、绕竖直轴左右倾。如果所有照片都是正对棋盘格拍,内参矩阵中的
fx、fy会退化,因为这种状态下尺度信息被约束在单一方向上。覆盖画面所有区域:棋盘格必须出现在画面的九个区域——四个角、四条边中间、正中间。很多人只让棋盘格在画面中心附近晃动,边缘区域的畸变参数完全没被观测,标定出来的
k1、k2基本不可信。运动幅度要小且稳定:采集时无人机或者相机要保持低动态,不要快速旋转或大幅移动,否则图像运动模糊,角点检测亚像素精度下降。Kalibr 的
imu-cam标定对运动激励有要求,但纯相机标定不需要大幅运动,手稳拿着慢慢换角度就行。亮度均匀:最好在散射光环境下拍摄,避免阳光直射产生高反光和阴影。曝光时间可以适当缩短,确保棋盘格黑白交界处有清晰的梯度,不要出现过曝或欠曝。
3.3 Kalibr、OpenCV、MATLAB 三套工具的选型对比
标定工具我实际都用过,各有适用场景。用一张表说清楚:
| 工具 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| OpenCV(Python/C++) | 免费开源、脚本可控、上手快 | 只标内参和畸变,不标相机-IMU外参;优化模型相对简单 | 快速验证镜头;纯视觉定位项目前期 |
| MATLAB Camera Calibrator | 界面可视化、误差分析图表齐全、支持多种畸变模型 | 收费;导出到 ROS 节点配置稍麻烦 | 实验室教学、单目/双目内参标定 |
| Kalibr | 支持多相机、相机-IMU 外参标定、APRILGRID 标定板、与 ROS/ROS2 生态无缝衔接 | 命令行操作门槛略高;依赖环境配置较繁琐;对输入数据质量非常挑剔 | 无人机 VIO/SLAM 项目、多传感器融合 |
如果你走 ROS2 + PX4 这条路,我推荐直接上 Kalibr,因为它的产出格式可以直接被 VINS-Fusion、ORB-SLAM3 等视觉惯性算法读取。Kalibr 内部使用的标定板是 Aprilgrid,它比棋盘格好在:支持部分遮挡时可继续检测,且能提供完整 ID 以区分姿势,数据丢失的容错性好很多。
Kalibr 标定单目内参的命令大致长这样:
rosrun kalibr kalibr_calibrate_cameras \ --target april_6x6_50x50cm.yaml \ --bag dataset_cam.bag \ --models pinhole-radtan \ --topics /cam0/image_raw \ --bag-from-to 5 60 \ --show-extraction--models pinhole-radtan表示使用针孔模型加径向切向畸变模型,这是最通用的组合。如果是鱼眼镜头,可以换成pinhole-equidistant或者omni-radtan。
3.4 标定结果怎么判断好坏:重投影误差与直观验证
拿到标定结果后,第一眼看重投影误差。所谓重投影误差,就是把棋盘格角点的三维坐标通过估计出的内外参重新投影到图像上,与检测到的像素坐标的均方根误差。Kalibr 输出里会有一个reprojection error字段,单位是像素。经验上,单目针孔模型的均值在 0.1~0.3 像素之间算合格;超过 0.5 像素说明标定过程有问题,需要检查数据。
但重投影误差只是内部一致性指标,它不能直接告诉你“实际测距准不准”。很多标定结果在重投影上表现优秀,但部署到 VIO 后轨迹仍然漂移,原因往往是内参和外参之间的一致性不够。这里我建议做一次直观验证:把标定好的内参写进去畸变函数,对着一张平面上的标尺拍照,测量图像中两端最远像素点对应的物理距离,看是否与实际一致。焦距偏差 1%,一米尺子的测量误差就是 1 厘米,这个测试简单但非常有效。
另外还有一个很实用的技巧:标定两次,间隔一天,然后对比两次结果中的fx、fy、cx、cy。如果相差超过 1%,说明镜头结构不稳定,或者标定数据有较大的条件偏差,需要重新标定。这比单次标定的误差指标更能反映系统的稳定性。
4. 外参标定:让相机和飞控“对准同一个世界”
4.1 单目/双目内参之后的相机-IMU外参标定
很多做无人机视觉的人标完内参就以为大功告成,结果一跑 VINS-Fusion 轨迹全是斜的,问题几乎都出在外参上。外参描述的是相机坐标系到 IMU/机体系之间的旋转和平移关系。如果外参的旋转角度差了三度,那么视觉估计的位姿和 IMU 积分出来的位姿之间就会存在一个持续偏差,融合出来的轨迹要么漂移要么振荡。
标定外参最常用的工具还是 Kalibr,它的kalibr_calibrate_imu_camera命令可以联合标定相机-IMU 外参、时延、以及相机内参。前提是:
- 有一组稳定的图像序列和 IMU 数据,时间戳已经对齐;
- IMU 和相机安装在同一个刚性结构上,没有相对松动;
- 数据采集时让设备做充分的运动激励,包括平移、旋转、加速,覆盖六个自由度。
我个人的做法是:先把设备固定在一个小车上,或者手持在桌面空间划“8”字,同时旋转手腕让相机朝向不断变化。这样 IMU 和相机的运动都能被充分激发,外参的可观性才会被满足。如果运动幅度太小,外参中的平移量会非常不可靠。
4.2 标定结果如何接入 ROS2 与 PX4
Kalibr 标定结束后会输出一个camchain.yaml,里面包含相机内参、畸变系数、以及 IMU 的外参变换。接下来要做的就是把它们写进 ROS2 的相机驱动配置文件,以及 PX4 的传感器参数。
在 ROS2 侧,典型的配置是:
camera_name: cam0 image_width: 1280 image_height: 720 camera_matrix: rows: 3 cols: 3 data: [526.3, 0.0, 635.1, 0.0, 525.8, 365.2, 0.0, 0.0, 1.0] distortion_model: radtan distortion_coefficients: rows: 1 cols: 4 data: [0.108, -0.241, 0.002, -0.001]在 PX4 侧,如果你用的是 PX4 自带的 VIO 模块,需要把相机位姿写入到vehicle_visual_odometry发布前的一个坐标变换关系里。简单说,PX4 期望收到的视觉里程计位姿已经是以机体系为参考的,如果你的相机坐标系和机体系不重合,就要做一次T_body_cam的变换。外参标定的数字如果错了,位置估计就会一路漂移,甚至出现“概念翻转”——飞控认为无人机在向右飞,实际上是向左。
4.3 外参误差在 SLAM 里是怎么被放大的
说一个我印象深刻的例子。有一次在模拟器里用 VINS-Fusion 跑仿真数据集,轨迹精度一直在 10 厘米左右浮动,但真机测试时误差到了 30 厘米。排查了很久,最后发现是无人机上相机的安装座有一点几毫米的胶垫形变,导致外参里的平移量和标定时不一致。几毫米的误差在地面上肉眼根本看不出,但 SLAM 算法在优化过程中会把这种偏差折算成尺度误差和旋转漂移,距离越远放大越明显。
还有一个常见的坑是:相机从标定支架上拆下来再装回去,外参已经发生了变化。如果做不到重新标定,至少要做一次安装重复性测试——拆装三次,每次跑一小段里程计,对比轨迹差异是否在误差范围内。如果三次轨迹都一致,说明安装一致性尚可;如果差异明显,就要考虑加装定位销或者刚性更好的安装座。
5. 真实踩坑记录:三次标定失败背后的排查链路
5.1 标定板轻微反光,重投影误差从0.15涨到0.8
那次用 A3 纸打印棋盘格,覆了一层亮膜,在实验室荧光灯正下方拍。采集的时候肉眼看着棋盘格没什么问题,但 Kalibr 跑完--show-extraction,发现大约 15% 的角点提取位置偏移了半像素还多,重投影误差居然到了 0.8 像素。查看提取图像才注意到,棋盘格黑格表面有很轻微的高光反射,角点边缘的灰度梯度被高光污染,亚像素检测的位置发生了偏移。
解决办法是:换哑光覆膜,或者把光源从正上方改成侧向散射。另一个方案是降低相机曝光时间,避免白色区域过曝。自那以后我的规则是,任何标定板照片,都不能出现纯白区域饱和度达到 255 的现象。
5.2 采集时手抖导致图像运动模糊,Kalibr直接崩
Kalibr 对图像质量非常挑剔,尤其是提取 Aprilgrid 角点的环节,稍微模糊一点的图像就会导致检测不出完整网格,或者生成错误的坐标。有一回我采集了 90 秒的数据,结果 Kalibr 运行到中途直接报KeyError,查看日志发现是连续十几帧图像都无法识别出足够的完整网格单元。
排查过程是这样的:先看提取结果图,发现模糊帧基本出现在我转身的瞬间——手持设备旋转过快,曝光时间又偏长,产生了运动模糊。解决方案是:降低曝光时间到 2ms 左右,同时尽量用机械云台固定设备,让相机在运动过程中保持姿态稳定。人造光源下如果曝光时间太短会有频闪,可以把采集场地安排到稳定的 LED 光源环境,或者使用外部补光灯。
5.3 双目基线距离写错,深度图出现“纸片化”
双目相机的深度估计依赖基线长度B和焦距f。如果你用的 ZED 2i 的官方标称基线是 120mm,但实际安装架子的公差让基线变成了 119mm,那么深度值的整体偏差大约是 0.8%。这听起来不大,但在 5 米处就会产生 4 厘米的误差,而且距离越远误差成比例增大。更麻烦的是,如果左右目的外参旋转矩阵有 0.5 度的误差,那么深度图在边缘区域会出现明显的“纸片化”——物体仿佛被压缩成一张张薄片,垂直边缘出现锯齿。
这个问题的排查链路是:输出深度图,看边缘的连续性;再对比真实距离,看 3 米处和 8 米处的偏差趋势。如果远距离偏差明显大于近距离,优先怀疑基线或者外参旋转;如果远近偏差比较均匀,优先怀疑焦距和主点。我自己最后是重新做了一次全尺寸标定,把左右目的相对变换也交给 Kalibr 去估计,而不是直接采用出厂参数,问题才彻底解决。
6. 标定之后的路:从内参到空间理解的完整闭环
6.1 标定结果在VINS-Fusion里的真实表现
当内参、畸变、外参都标定到位之后,VINS-Fusion 这类 VIO 系统的表现才能正常。我在室内走廊里跑过一段真实测试:无人机搭载单目加 IMU,以 0.8m/s 的速度平飞,距离地面约 1.2 米,走廊长度约 18 米。标定良好的情况下,端到端轨迹总漂移控制在 2% 以内,也就是大约 35 厘米;而在外参未重新标定时,同样的路径漂移会超过 15%,轨迹末端直接偏移出走廊边界。
这里要强调一点:VIO 的尺度恢复依赖 IMU 与相机的外参平移量,如果T_imu_cam的平移误差过大,尺度就会收敛到错误值,导致速度估计偏大或偏小。很多人在真机测试时发现视觉里程计的速度和实际速度差一大截,第一反应是调滤波参数,实际上应该回去检查外参标定。
6.2 如何快速验证标定精度是否满足飞行需求
最后分享一个快速验证流程,不需要复杂的实验设施:
- 把相机固定好,对准一个已知大小的矩形标记物(比如 A4 纸),从 0.5 米到 5 米每隔 0.5 米拍一张照片;
- 根据内参和畸变参数做去畸变后,用 PnP 求解标记物四个角点对应的距离,和实际激光测距值对比;
- 误差在 3% 以内,说明内参和畸变满足一般视觉定位需求;超过 5%,建议重新标定;
- 然后开启 VIO,让无人机悬停,观察速度和位置估计是否稳定;如果悬停 30 秒位置漂移超过 0.3 米,重点排查外参和 IMU 噪声参数。
按照这个流程,每次装完无人机都能在半小时内确定标定是否需要返工。从我个人的经验来看,绝大多数视觉定位问题,追根溯源都发生在标定这个环节。把相机模型和标定搞通,后面做路径规划、避障、目标跟踪,才会有一个真正可靠的空间参考基准。