3. 针孔相机模型:成像原理、畸变模型、去畸变实战、OpenCV实现

3.1 从物理世界到像素平面

做BEV感知,第一关就是搞懂相机是怎么「看」世界的。说白了,针孔相机模型就是最基础的几何抽象——把三维空间里的点,映射到二维图像上。

我记得刚入行时,总觉得这玩意儿太简单,不就是小孔成像嘛。直到有一次在实车测试中,发现车道线投影到BEV视角下全是弯的,排查了半天才发现是相机内参标定出了问题。嗯,从那以后我再也不敢小看这个模型了。

针孔模型的数学表达其实很直观:

// 世界坐标系 -> 相机坐标系 -> 图像坐标系 -> 像素坐标系
// 核心公式:s * [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T

其中 K 是内参矩阵,[R|t] 是外参。内参决定了焦距、光心偏移这些相机本身的属性,外参决定了相机在世界中的位置和朝向。

内参矩阵 K 长这样:

[ fx   0   cx ]
[ 0    fy  cy ]
[ 0    0    1 ]

fx, fy 是焦距(像素单位),cx, cy 是光心坐标。这些值一旦标定好,只要不换镜头、不调焦,基本不变。

3.2 畸变模型:为什么直线会变弯?

理想很丰满,现实很骨感。针孔模型假设光线直线传播,但实际镜头是透镜组,光线会折射。这就导致了畸变。

畸变主要分两类:

  • 径向畸变:光线经过透镜边缘时弯曲更厉害,产生「桶形」或「枕形」效果。用 k1, k2, k3 三个参数描述。
  • 切向畸变:透镜和成像平面不平行,产生「倾斜」效果。用 p1, p2 两个参数描述。

我做过一个项目,用广角相机做环视拼接。一开始没做去畸变,结果拼接出来的俯视图,车旁边的行人都是扭曲的——你想想看,这要是用于泊车辅助,谁敢信?

注意:畸变参数和相机内参是绑定的。换镜头、调焦距后必须重新标定。我曾经见过有人把不同相机的参数混用,结果去畸变后图像反而更扭曲了。

3.3 去畸变实战:OpenCV 实现

去畸变的流程其实就三步:

  1. 从相机标定得到内参和畸变系数
  2. 用 cv::initUndistortRectifyMap 生成映射表
  3. 用 cv::remap 重映射图像

我个人习惯把映射表提前算好存下来,因为实车上每帧都算一遍太浪费算力。你想想看,嵌入式平台本来就资源紧张,能省则省。

#include <opencv2/opencv.hpp>

// 假设已经标定得到以下参数
cv::Mat cameraMatrix = (cv::Mat_<double>(3,3) <<
     fx, 0, cx,
     0, fy, cy,
     0, 0, 1);

cv::Mat distCoeffs = (cv::Mat_<double>(1,5) <<
    k1, k2, p1, p2, k3);

// 生成映射表(只做一次)
cv::Mat map1, map2;
cv::Size imageSize(1280, 720);
cv::initUndistortRectifyMap(
    cameraMatrix, distCoeffs, cv::Mat(),
    cameraMatrix, imageSize,
    CV_32FC1, map1, map2);

// 每帧去畸变
cv::Mat distortedImg, undistortedImg;
// ... 读取图像到 distortedImg ...
cv::remap(distortedImg, undistortedImg, map1, map2, cv::INTER_LINEAR);

小技巧:如果去畸变后图像边缘出现黑边,可以调整 cv::getOptimalNewCameraMatrix 的 alpha 参数。alpha=0 会裁剪掉所有黑边但损失视野,alpha=1 保留全部视野但会有黑边。我一般设 0.5 左右,平衡一下。

3.4 知识体系总览

下面这张图是我自己梳理的针孔相机模型知识结构,方便你快速把握全局:

针孔相机模型知识体系 成像原理 • 世界→相机→图像→像素 • 内参矩阵 K (fx, fy, cx, cy) • 外参矩阵 [R|t] • 齐次坐标与投影方程 • 尺度因子 s 畸变模型 • 径向畸变 (k1, k2, k3) • 切向畸变 (p1, p2) • 桶形/枕形畸变 • 畸变系数向量 • 标定获取参数 去畸变实战 • initUndistortRectifyMap • remap 重映射 • 映射表预计算 • getOptimalNewCameraMatrix • 边缘黑边处理 核心目标:建立 3D 世界点 ↔ 2D 像素点 的精确映射 在 BEV 感知中的应用 IPM 逆透视变换 多相机拼接 深度估计 3D 目标检测

3.5 避坑指南与个人经验

做去畸变这几年,我踩过不少坑。挑几个典型的说说:

  • 映射表的数据类型:initUndistortRectifyMap 的 map 类型建议用 CV_32FC1,别用 CV_16SC2。后者虽然省内存,但精度不够,去畸变后的图像边缘会有锯齿。我在一个量产项目上吃过这个亏,后来全部改成了 float 类型。
  • 畸变参数顺序:OpenCV 的畸变系数向量是 [k1, k2, p1, p2, k3],但有些标定工具输出的是 [k1, k2, p1, p2, k3, k4, k5, k6]。搞混了的话,去畸变结果会完全不对。我建议每次标定完都先拿一张棋盘格验证一下。
  • 性能优化:如果每帧图像分辨率很大(比如 1920x1080),remap 会吃掉不少 CPU。我一般用 cv::INTER_NEAREST 代替 cv::INTER_LINEAR,速度能快 30% 左右,效果差别不大。

一句话总结:针孔相机模型是 BEV 感知的基石。内参外参标定准了,去畸变做好了,后面的 IPM 变换、多相机融合才能靠谱。这一步省事,后面全是坑。

可可精品资料库