第三章:相机模型(Camera)——针孔相机模型原理、内参矩阵与外参矩阵、畸变模型、Apollo相机模型配置与仿真

各位同学,大家好。我是你们的老朋友,一个在自动驾驶传感器融合领域摸爬滚打多年的工程师。今天我们来聊聊相机模型,特别是针孔相机模型。说实话,相机是自动驾驶里最像人眼的传感器,但它看到的“世界”和我们看到的,其实差别很大。为什么?因为相机有自己的“脾气”——畸变、透视、还有它自己的坐标系。

我个人习惯把相机模型拆成三块来理解:成像原理(针孔模型)、坐标变换(内参和外参)、畸变矫正。这三块搞定了,你就能在Apollo里把相机玩得转。

3.1 针孔相机模型原理

说白了,针孔相机模型就是一个小孔成像。光线从物体上反射,穿过一个小孔,投射到后面的感光元件上。嗯,这里要注意:成像是倒立的。不过算法里我们通常把它正过来处理,省得看着别扭。

数学上,我们用相似三角形来描述。假设相机坐标系下有一个点 P(X, Y, Z),它投影到成像平面上的像素坐标 p(u, v)。关系很简单:

u = f * X / Z + cx
v = f * Y / Z + cy

这里的 f 是焦距,cx, cy 是光心偏移。为什么会有偏移?因为感光芯片的安装不可能绝对居中。我在项目中遇到过,有些工业相机光心偏了十几个像素,如果不标定,融合出来的点云位置直接歪掉。

核心公式:针孔模型本质就是“除以Z”的操作。Z越大,物体在图像上越小。这就是透视效应。

3.2 内参矩阵与外参矩阵

相机模型里,内参和外参是两个核心矩阵。我习惯把它们看作“翻译官”:

  • 内参矩阵 K:把相机坐标系下的3D点,翻译到像素坐标系下的2D点。它包含焦距、光心、以及像素的尺度信息。
  • 外参矩阵 [R|t]:把世界坐标系下的点,翻译到相机坐标系下。说白了,就是告诉你相机在世界里的位置和朝向。

内参矩阵长这样:

K = [[fx,  0, cx],
     [ 0, fy, cy],
     [ 0,  0,  1]]

外参矩阵就是旋转矩阵R和平移向量t的组合。你想想看,如果外参标定错了,那相机看到的“前方”可能实际上是“左前方”,融合出来的障碍物位置自然就错了。

避坑指南:我曾经在标定外参时,把旋转矩阵的符号搞反了。结果车明明直行,相机检测到的车道线却往右偏。排查了整整两天才发现是外参的Y轴方向定义反了。所以,一定要确认坐标系定义——Apollo用的是“右-前-上”坐标系,和很多开源库不一样。

3.3 畸变模型(径向与切向)

理想很丰满,现实很骨感。针孔模型假设镜头是完美的,但实际镜头有畸变。畸变主要分两种:

  • 径向畸变:光线经过透镜边缘时弯曲得更厉害,导致图像边缘的直线变弯。说白了就是“桶形畸变”或“枕形畸变”。
  • 切向畸变:镜头和感光芯片不平行,导致图像倾斜。这个在手机摄像头里常见,工业相机相对少一些。

畸变模型用多项式来描述:

x_distorted = x * (1 + k1*r^2 + k2*r^4 + k3*r^6) + 2*p1*x*y + p2*(r^2 + 2*x^2)
y_distorted = y * (1 + k1*r^2 + k2*r^4 + k3*r^6) + p1*(r^2 + 2*y^2) + 2*p2*x*y

其中 k1, k2, k3 是径向畸变系数,p1, p2 是切向畸变系数。r是点到光心的距离。

注意:畸变参数不是越多越好。k3通常只在鱼眼相机里用,普通相机用k1和k2就够了。我见过有人把k3设成非零值,结果矫正后的图像边缘出现波浪形扭曲——这就是过拟合了。

3.4 Apollo相机模型配置与仿真

在Apollo里,相机模型配置主要在 modules/common/configs/proto/vehicle_config.proto 和相机对应的 calibration.yaml 文件里。我个人习惯先检查这几个字段:

camera:
  name: "front_6mm"
  sensor_id: 1
  device: "/dev/camera_front_6mm"
  image_width: 1920
  image_height: 1080
  fx: 2648.0
  fy: 2648.0
  cx: 960.0
  cy: 540.0
  distortion:
    k1: -0.283
    k2: 0.074
    p1: 0.0001
    p2: -0.0002
    k3: 0.0

嗯,这里要注意:fx和fy的单位是像素,不是毫米。如果你从标定板得到的是毫米单位的焦距,记得除以像素尺寸。

至于仿真,Apollo的仿真平台里,相机模型是通过 modules/simulation/common/camera_simulator.cc 实现的。它会读取上述配置,然后对虚拟场景中的3D点进行投影,并加上畸变。我建议你在仿真时,故意把畸变参数设大一点,看看算法能不能扛得住——这叫“压力测试”。

仿真小技巧:在Apollo的Dreamview里,你可以实时切换不同相机的视角。如果发现投影后的车道线弯曲,大概率是畸变参数没配对。我曾经用这个办法,五分钟就定位到标定文件里k2符号写反了。

3.5 本章知识体系

为了让你更直观地理解相机模型在Apollo里的位置,我画了一张图:

Apollo相机模型知识体系 世界坐标系 (Xw, Yw, Zw) 外参矩阵 [R | t] 相机坐标系 (Xc, Yc, Zc) 针孔投影 除以Z + 内参 畸变模型 径向+切向 像素坐标系 (u, v) 外参变换 3D→3D 投影 畸变 输出 Apollo配置 calibration.yaml 读取配置 仿真验证 camera_simulator.cc 仿真测试

这张图把整个流程串起来了:世界坐标 → 外参变换 → 相机坐标 → 针孔投影 → 畸变 → 像素坐标。Apollo的配置和仿真模块分别负责“参数输入”和“效果验证”。

好了,关于相机模型,今天就聊这么多。记住:内参管投影,外参管位置,畸变管矫正。这三样搞明白了,你在Apollo里调相机参数就不会抓瞎。

最后一个小建议:如果你在实车上发现检测不准,先别急着调算法。检查一下相机标定文件——我敢打赌,80%的问题出在畸变参数或外参上。我曾经在项目交付前夜,发现一辆车的相机标定文件里fx和fy写反了,导致障碍物距离全部偏大。嗯,从那以后,我每次都会先跑一遍标定验证脚本。

可可精品资料库