无人机相机标定实战:从像素到真实坐标的工程指南
2026/9/13 2:59:25 网站建设 项目流程

从无人机飞起来那一刻起,有一个问题会一直绕着你走:它看到的像素,到底对应真实世界的哪个点?尤其是当你要做自主降落、目标抓取、障碍物绕飞或者建图定位时,只要像素和真实坐标之间的关系差上几个像素,飞机在几米外算出来的位置就可能偏出十几厘米。这个“像素坐标 → 真实坐标”的数学映射,就是相机模型,而确定映射参数的过程,就是标定。

这篇文章我打算把相机模型和标定这件事,从无人机实际工程应用的角度彻底讲透。不只讲公式,也不只贴代码,而是把为什么这么做、踩过哪些坑、怎么判断标定结果能不能用,全部摊开来说。内容适合正在做无人机视觉感知、SLAM、目标跟踪,或者刚接触相机标定的同学。相信我,看完你不仅能自己动手标出一套可靠的相机参数,还能搞清楚float和double之间那点误差,为什么会让你无人机在降落时“亲”歪地垫。

1. 无人机眼中的世界:从像素到真实坐标

1.1 针孔模型与四个坐标系

相机成像的本质是针孔成像。光线从物体表面反射,穿过镜头光心,打在传感器上形成一个倒像。现代相机传感器会直接输出正向图像,但几何关系还是那套:空间中的一点,通过光心和镜头,投影到像素平面。这个投影关系,可以用四个坐标系串联起来:

  • 世界坐标系:描述无人机或目标物在真实空间中的位置,单位通常是米。
  • 相机坐标系:原点在相机光心,Z轴沿光轴向前,单位是米。
  • 图像坐标系:在成像平面上,原点在光轴与平面交点(主点),单位是毫米或像素对应的物理尺寸。
  • 像素坐标系:图像左上角为原点,单位是像素。

从世界坐标里的点 (P_w),到像素坐标里的点 (p),整个过程拆成三步:

  1. 世界坐标 → 相机坐标:通过旋转矩阵 R 和平移向量 t,即外参。外参描述的是相机在世界坐标系里的姿态和位置。
  2. 相机坐标 → 图像坐标:通过针孔投影,把三维点压在二维平面上。
  3. 图像坐标 → 像素坐标:通过像素尺寸和主点位置,换算成像素索引。

把这三步写成一个矩阵形式,就是我们常说的:

[ s \begin{bmatrix} u \ v \ 1 \end{bmatrix} = K \begin{bmatrix} R & t \end{bmatrix} \begin{bmatrix} X_w \ Y_w \ Z_w \ 1 \end{bmatrix} ]

其中 K 是内参矩阵:

[ K = \begin{bmatrix} f_x & 0 & c_x \ 0 & f_y & c_y \ 0 & 0 & 1 \end{bmatrix} ]

这里 (f_x, f_y) 分别是 x、y 方向上的等效焦距(像素单位),(c_x, c_y) 是主点坐标。换成人话:内参告诉你“像素坐标和相机坐标系里的方向之间是怎么对应的”,外参告诉你“相机在这个世界上站在哪里、看向哪里”。对无人机而言,内参通常认为固定(只要镜头不松、焦距不变),外参则每次起飞都变,因为飞机会动。

1.2 畸变:镜头不完美带来的误差

实际镜头不是完美针孔,它会产生畸变。畸变主要分两类:

  • 径向畸变:光线经过透镜边缘时折射更厉害,导致直线变弯。靠近边缘更明显,表现为“桶形畸变”或“枕形畸变”。
  • 切向畸变:镜头和传感器不严格平行,装歪了导致的偏差。

数学上,我们通常用五个畸变系数((k_1, k_2, p_1, p_2, k_3))去描述。OpenCV里常见的模型是:

[ x_{distorted} = x(1 + k_1 r^2 + k_2 r^4 + k_3 r^6) + 2 p_1 x y + p_2 (r^2 + 2x^2) ] [ y_{distorted} = y(1 + k_1 r^2 + k_2 r^4 + k_3 r^6) + p_1 (r^2 + 2y^2) + 2 p_2 x y ]

其中 (x, y) 是去畸变后的归一化坐标,(r^2 = x^2 + y^2)。你可以把畸变想象成哈哈镜:中间正常,越靠边歪得越厉害。如果你不做畸变校正,直接用原始像素做目标定位,视觉引导的误差会随着目标靠近画面边缘急剧增大。对无人机这种悬停精度要求厘米级的场景,边缘误差可能直接让抓取失败。

1.3 为什么无人机对相机标定要求特别苛刻

地面机器人相机标定一次可以管很久,因为机体不大会变形。无人机不一样:

  • 螺旋桨震动会导致镜头松动、焦点漂移,内参变化。
  • 云台挂载的相机,姿态变化比车载相机更大,外参标定如果不准,激光雷达和相机的融合就会产生错位。
  • 光照变化剧烈,低光照时图像噪声大,标定角点提取精度下降。
  • 飞行高度变化,实际对地距离从 0.5 米到 30 米都有,对相机模型的近距精度和远距泛化能力都有要求。

所以无人机场景下,标定不只是一次性的“交差”,而是需要一套可复用、可校验、甚至能在线补偿的流程。这也是很多飞控和视觉算法工程师把相机标定当成基本功的原因。

2. 动手标定前,先把这些事情准备好

2.1 相机选型与固定方式:别让镜头“晃动”毁了精度

很多人觉得标定随便拿个相机就能做,先用手机拍几张棋盘格,结果标出来误差大得离谱,还找不出原因。我建议,用于无人机的相机,优先选定焦镜头,不要用变焦。变焦镜头每次改变焦距,内参都会变,你标定结果只对当前焦距有效。一旦推拉镜头,之前标的全部作废。

镜头的固定也很讲究。机载相机(摄像头模块)通常用螺丝锁紧在减震板上,但很多飞手为了省事,直接扎带绑上,或者用双面胶粘一层泡棉。震动一段飞行后,镜头角度会细微偏转。这个偏转在图像上可能只有 1~2 个像素,但对于相距 10 米的目标,位置就偏了十几厘米。所以,正式项目里,镜头螺纹要锁紧,必要时打螺纹胶,相机底座用刚性连接,不要垫太多软胶。

如果做双目或者多目,两相机之间的机械结构也要固定。双目基线一旦松动,视差计算的深度值就会系统性偏移,那比单目标定误差更麻烦。

2.2 标定板选择:棋盘格、ChArUco 还是 AprilTag?

常见标定板有三种:

  • 棋盘格:传统张正友法的标准选择,角点检测简单稳定,成本低(打印一张就行)。
  • ChArUco:在棋盘格内部嵌入 ArUco 码,部分遮挡时也能检测,适合标定板不能完全入镜的场景。
  • AprilTag:更适合定位和 SLAM,不太适合直接做内参标定,但其角点精度高,也能用于外参标定。

对无人机标定,我个人更喜欢 ChArUco 或高精度棋盘格。原因很简单:无人机相机视场角大,标定板边缘容易出画,普通棋盘格如果边缘被切掉,角点识别就乱了;ChArUco 靠编码保证每个角点的唯一性,哪怕只拍到部分也照样能恢复。

打印标定板时,黑白格子要尽量高反差,最好用激光打印机在哑光相纸上打印。哑光很重要,如果是亮面纸,反光会让某些格子亮度溢出,角点提取位置偏移。打印后记得测量格子边长,用游标卡尺量十个小格的宽度取平均值,这个值对应代码里的 square_size,填错了标出来的焦距、平移量全是错的。

标定板一定要贴平。贴在硬纸板上都会鼓包,最好贴在铝板或亚克力板上。不平整的标定板,相当于你在拿一个歪曲的“真实尺度”去映射像素,标出来的内参必然带偏。

2.3 工具链选型:OpenCV、MATLAB、Kalibr 各自适合什么

标定工具没有绝对最优,只有适合场景。

  • OpenCV:免费、可脚本化、适合集成到自己的标定流程里。支持鱼眼、普通针孔、双目,功能全面。
  • MATLAB Camera Calibrator:交互式体验好,能实时显示重投影误差和畸变可视化,适合快速验证结果。
  • Kalibr:多传感器标定神器,主要用于相机-IMU 外参标定,支持多目相机。它的优点是能联合优化,精度高,但使用门槛也高,得熟悉 ROS 和 bag 包。
  • ROS camera_calibration:命令行工具,在线标定,适合做单目和双目快速标定,依赖棋盘格。

我的建议是:内参标定用 OpenCV + 自己的 Python 脚本,因为你能完全掌握数据筛选和处理逻辑;多传感器外参标定用 Kalibr 或 opengv 系列的优化工具。反正别把标定当成“运行一个命令就结束”的事,得留出时间反复迭代。

3. 核心实操:基于张正友法的单目相机内参标定全流程

3.1 图像采集规范:张正友法的“张正友”式拍摄技巧

张正友标定法本质上是拍 10~30 张不同角度的棋盘格平面照片,然后通过单应性矩阵来优化内参和外参。它的核心假设是:标定板是一个平面,角点的空间相对关系已知。所以拍摄的照片要尽可能覆盖整个画面,并且姿态多样。

具体拍摄建议:

  1. 数量:至少 20 张,推荐 30 张。少于 15 张,优化容易病态。
  2. 角度:标定板与相机光轴夹角从 0 度到 45 度之间变化,最好有俯仰、偏航、滚转三个方向的旋转变化。
  3. 位置:让标定板出现在画面的中心、四个角、四条边附近,尽量让角点覆盖整个图像区域。
  4. 距离:从近到远都拍,但要保证角点清晰可辨,别让棋盘格边缘被压缩到几十个像素以下。
  5. 光照:均匀自然光,避免强光直射和反光斑。

还有一个很多人忽视的点:拍摄标定板时,相机保持静止,移动标定板。而不是人举着相机绕来绕去。因为运动模糊会直接影响角点提取,尤其是手持无人机相机模块拍摄时,快门速度跟不上就糊了。固定相机、动标定板,能大幅提高图像质量。

3.2 使用 OpenCV 完成标定的最小流程

我用 Python 写一个最常用的标定流程,你可以直接拿来改着用。我用的是 OpenCV 的 cv2.findChessboardCorners 和 cv2.calibrateCamera。

import cv2 import numpy as np import glob # 棋盘格内部角点数:比如棋盘格是 10x7 的格子,内部角点是 9x6 pattern = (9, 6) square_size = 0.024 # 单位:米,必须实测 # 准备世界坐标系中的棋盘格角点坐标,比如每个角点z=0 objp = np.zeros((pattern[0] * pattern[1], 3), np.float32) objp[:, :2] = np.mgrid[0:pattern[0], 0:pattern[1]].T.reshape(-1, 2) objp *= square_size obj_points = [] # 世界坐标 img_points = [] # 像素坐标 images = glob.glob('calib_images/*.jpg') for fname in images: img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 亚像素精度检测角点(可以考虑用cv2.CALIB_CB_EXHAUSTIVE进一步细化) ret, corners = cv2.findChessboardCorners(gray, pattern, None) if ret: # 细化角点到亚像素 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) obj_points.append(objp) img_points.append(corners2) cv2.drawChessboardCorners(img, pattern, corners2, ret) cv2.imshow('checker', img) cv2.waitKey(100) cv2.destroyAllWindows() # 标定 ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( obj_points, img_points, gray.shape[::-1], None, None) print("重投影误差:", ret) print("内参矩阵:\n", mtx) print("畸变系数:\n", dist)

这个代码本身不难,但有几个参数我要特别强调。

  • pattern 是内角点数,不是格子数。比如打印的是一个 10×7 的棋盘,内角点是 9×6。很多人在这里数错,标定结果会变成“奇怪的内参”。
  • square_size 必须精确到毫米级。我曾经因为标定板打印时缩放设置问题,实际格子边长 23.8mm,代码里填 25mm,最后测距误差直接偏了 5%。
  • 标定库默认畸变模型是 5 参数(k1,k2,p1,p2,k3),如果你的相机畸变很小,也可以只标 k1,k2,p1,p2。但对无人机广角镜头,建议保留 5 参数,甚至用 cv2.fisheye 模块建模。

3.3 标定结果分析:重投影误差和畸变可视化

标定完成后,第一件事看重投影误差(ret)。OpenCV 返回的 ret 是均方根重投影误差,单位是像素。一般普通相机标定结果在 0.05~0.3 像素之间是优秀的,0.3~0.8 像素可以接受,超过 1 像素就要检查图像质量或角点提取了。

但只盯一个均方根误差还不够。我习惯把每个角点的重投影误差画成热力图,看看误差是否集中在某个区域。如果误差集中在图像边缘,多半是畸变模型不够(鱼眼镜头需要更大畸变量)、图像边缘角点模糊、或者标定板不平整。如果误差随机分布,多半是图像有轻微运动模糊或棋盘格角点检测精度下降。

还可以把畸变可视化做一个网格图,看看去畸变前后的变化量。如果画面边缘校正后变形特别剧烈,说明相机畸变大,这种情况要警惕在边缘区域做目标定位时的精度损失。

3.4 单目内参标定的四个避坑提醒

根据我帮别人排查标定问题的经验,这几个坑出现频率很高:

  1. 拍摄时标定板反光:强光下白色格子的灰度值直接顶到 255,角点附近的梯度发生变化,提取的角点位置偏移。解决方式是调整灯光或者贴哑光膜。
  2. 只拍摄标定板在中间的姿态:这样内参对周边的校正不准,边缘区域的真实误差没有被优化到,就会导致目标偏到边缘后定位跳变。
  3. 图片数量足够,但有效图很少:findChessboardCorners 检测失败,你可能强行手动选了几张,这样优化效果很差。宁可用 15 张高质量图,也不用 30 张里有 10 张半遮挡的图。
  4. 标定板的板面没有保持平面:尤其是打印纸贴在有折痕的地方,角点空间关系就不完全符合平面假设,重投影误差会偏大。

4. 从单目到多传感器:无人机常用标定扩展

4.1 双目相机标定与立体校正

很多无人机避障方案使用双目相机。双目不仅需要每个相机自身的焦距和畸变,还需要左相机到右相机的旋转矩阵 R 和平移向量 T。OpenCV 的 stereoCalibrate 可以做这件事。注意双目标定时,左右相机图像要同步拍摄。用两路视频流分别拍照再合并不行,因为飞机运动会导致左右拍到的不是同一个瞬间,外参解算就废了。

实际的无人机双目模组,出厂会做一次立体校正,但运输和震动后,基线可能微量变化。建议每次使用前先做一个快速校验:拍一对棋盘格图像,调用 cv2.stereoRectify 和 cv2.initUndistortRectifyMap,然后用 cv2.stereoMatch 看视差图是否平滑。如果视差图上有大量锯齿,多半是外参偏了。

4.2 手眼标定、九点标定和十二点标定到底是什么意思

在无人机或无人机地面站的上下文里,还经常听到“手眼标定”“九点标定”“十二点标定”。这些不都完全是相机模型标定,更多是机器人系统里的坐标对齐。

  • 手眼标定:解决“相机固定在机械臂上(eye-in-hand)”或“相机固定在外部(eye-to-hand)”时,相机坐标系与机械臂末端坐标系之间的变换关系。无人机挂载机械臂抓取物体,就需要做手眼标定。OpenCV 有 cv2.calibrateHandEye 函数,输入机械臂末端位姿序列和相机外参序列,输出相机到机械臂末端的变换。
  • 九点标定:常见于视觉引导平面定位。在平面上取 9 个已知真实坐标的点(或者让机构依次走到 9 个位置),视觉识别出对应像素坐标,然后用仿射变换(3×2 矩阵)拟合像素到真实坐标的关系。九点标定只适用于共面场景,比如传送带平面抓取,不适合三维空间。
  • 十二点标定:在九点标定的基础上,通常适用于更高阶的畸变或非线性变换模型,比如某些自动化设备标定里,用 12 个点拟合包括旋转、比例、透视和二次项的复合矩阵。canape 标定则常见于汽车 ECU 标定,用的是 CANape 软件工具,虽然标题相关但和无人机相机标定不是一回事。

对无人机视觉来说,如果你要做降落到一个平面上的目标点引导,九点标定比完整的内参标定更直接——你不需要知道焦距,直接建立像素到地面坐标的映射。但它的缺点是假设地面是平面且相机与地面关系固定,无人机姿态变化后就不准。所以机载视觉很少用九点标定,更多用于固定工位(流水线、测试台架)。

4.3 相机与 IMU、LiDAR 的外参标定:Kalibr 实战思路

无人机光有一个相机是不够的,通常还有 IMU(惯导)、激光雷达、RTK 等传感器。要把视觉信息和惯导、点云融合,就必须知道传感器之间的相对位姿(外参)。这一块我推荐用 Kalibr。

Kalibr 的标定流程大致是:

  1. 使用 kalibr_calibrate_imu_camera 标定相机-IMU 外参。需要采集包含丰富角加速度和线性激励的 rosbag,标定板使用 AprilGrid。
  2. 使用 kalibr_calibrate_cameras 标定多目相机内外参,内部也会优化畸变模型。
  3. 对 LiDAR-IMU 或 LiDAR-Camera 外参,通常用 licalib、direct_visual_lidar_calibration 等工具。核心思想是通过互信息或边缘对齐,优化点云到图像的重投影。

相机-IMU 外参标定要注意:数据采集时,让无人机或手持设备做“激起所有可观测自由度”的运动,也就是多方向旋转和短促平移,不要长时间匀速。因为匀速运动对 IMU 的位置观测信息不足,联合优化容易退化。

4.4 无人机上更实用的在线补偿策略

标定是一回事,飞行又是另一回事。无人机飞行时,震动、温度变化、高速气流都会让相机光学参数发生微小漂移。一个工程上常用的做法是:离线标定一遍得到初值,在线用视觉-惯性里程计(VIO)后端去不断优化相机外参和 IMU 偏差。比如 VINS-Mono、ORB-SLAM3 都支持标定相机与 IMU 的外参,可以在初始化阶段在线估计。

如果你的无人机只是做视觉定位但不融合 IMU,那么更简单:每次起飞前拍一张棋盘格照片(比如贴地面上),快速重算相机内参或校验一遍单应矩阵。很多巡检无人机就是这样,起飞前用跑道上预设的靶标做一个快速标定校验,一旦重投影误差超阈值就报警提醒检修。

5. 标定常见问题与排查技巧实录

5.1 重投影误差偏大,问题到底出在哪

我习惯按以下顺序排查:

  1. 先看角点检测是否正确。把 findChessboardCorners 绘制的角点图保存下来,肉眼检查有没有错检。角点错位 1~2 个像素是有可能的。
  2. 检查棋盘格边长填写。这一步错了,焦距单位会整体缩放,但重投影误差不一定很大。你需要拿一个已知尺寸的物体去验证,比如在 1 米距离测真实长度。
  3. 检查图像数量与覆盖。少于 15 张且视角集中,误差肯定大。
  4. 检查畸变模型。如果你的镜头是超广角(视场角大于 100°),用普通针孔 + 5 参数畸变可能不够,得用 cv2.fisheye 模型。
  5. 检查是否用了非标定的分辨率。有些相机自动调整输出分辨率,或者做了裁剪,导致标定时对应的像素坐标体系和实际使用时不一致。

5.2 标定板在图像里总是无法完全检测

常见原因是棋盘格在图像边缘有强透视,角点合并或缺失。解决办法:

  • 把棋盘格放得距离更远一些,让整体都缩进画面。
  • 用 ChArUco 板替代,支持部分遮挡。
  • 提高分辨率或减少快门曝光时间,减少模糊。
  • 调大 EdgeThreshold 参数(OpenCV 的 findChessboardCorners 参数),有时候能救回来几张好图。

5.3 标定时内参变化剧烈:每次标出来的 f 都不一样

这种情况多半是相机焦距不固定。自动对焦(AF)镜头会自动调焦,变焦镜头会改变等效焦距。所以无人机视觉的相机必须关掉自动对焦,设为手动对焦,并把焦点固定到合适距离(比如对目标的典型工作距离)。如果相机支持硬件触发,也建议锁死光圈和曝光时间。

还有一个冷门原因:coms 传感器在高温下热膨胀,导致像距变化。无人机飞行时电子器件发热严重,如果标定是在室温下做,飞行时内参会随温度产生微量变化。这个在消费级相机上不那么明显,但在工业相机或长时间飞行任务中能观察到。解决方法是飞行前让相机通电预热 10 分钟,再进行标定。

5.4 同一套相机参数,换台飞机后还要重标吗

要看你换的是什么。如果相机模组原封不动装到另一台无人机上,但安装位置和角度变了,那内参不用变,外参(相机到机体坐标系)必须重标。外参变了会影响视觉引导和控制之间的对齐,导致“相机看到了,但机头转向不对”或者“云台指向跟实际位置有偏差”。所以建议设计一个可复现的快速外参标定流程:把无人机放在一个已知尺寸的标定架里,用几个反光标记点,通过测量已知点像素位置来反推 R、t。

5.5 标定实践避坑速查表

常见问题可能原因解决方法
重投影误差 > 1px图片模糊 / 角点提取不准 / 标定板弯曲重新采集高质量图,确保板面平整
焦距值异常(偏离标称值过大)边长填错 / 图像被裁剪 / 对焦变化实测边长、检查图像分辨率与自动对焦
边缘畸变校正后图像过度拉伸普通针孔模型不适合广角镜头改用 cv2.fisheye 模型或多阶畸变系数
双目深度在某些区域不准左右相机曝光不一致 / 同步性差设置相同曝光、使用硬件同步
相机-IMU 融合发散外参初始值偏差太大 / 运动激励不足提高外参精度,采集更多方向旋转运动
图像中心与安装轴线不重合光学中心偏移标定后通过主点 cx、cy 判断,安装时尽量让光轴对准机体轴线

6. 实际操作中的一点经验谈

做无人机视觉这几年,我最大的感受是:标定不是一次性的技术问题,而是贯穿整个项目生命周期的工程问题。你不可能期望“标完一次、永远正确”。震动、温度、磕碰、更换镜头,都会让相机模型改变。所以项目落地时要做的不是给出一份标定报告,而是给出一套可持续校验和修正的标定体系。

比如我在飞控软件里写了一个简单的看门狗任务:每次飞行前,如果相机画面里识别到地面铺的标定布(一种带固定二维码阵列的帆布),就自动尝试做一次轻量级的单应矩阵校正;如果重投影误差超过阈值,就提示需要重新执行完整标定。这样能大幅减少因为镜头松动导致的任务失败。

对于刚开始做相机标定的朋友,我的建议是:先别追求“最高精度”,用简单棋盘格 + OpenCV 把流程跑通,拍 30 张图,标出内参,然后用一个卷尺在地面拉一条线,用相机测距验证一下。当你看到像素坐标换算出的物理距离和卷尺对上时,你才算真正理解“相机模型”这四个字的意义。

最后再分享一个小技巧:标定结果里那组畸变系数,别小看它。你可以在每次标定后把新系数和旧系数对比一下,如果某一天发现 k1 突然从 0.08 变成 0.15,不用怀疑算法错了,大概率是镜头被压歪了。这种“用标定结果做健康监测”的方法,我救回过不少本来要炸机的现场。毕竟无人机飞在天上,任何一个传感器状态异常,都值得多一点警惕。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询