2. 相机模型与图像处理:针孔相机模型、畸变校正、图像滤波与特征提取、OpenCV基础操作

好,咱们进入第二章。这一章,说白了就是视觉SLAM的“眼睛”怎么工作的。你想想看,机器人要感知世界,总得有个摄像头吧?但摄像头拍出来的图像,和我们人眼看到的,其实差得挺远。这里面涉及两个核心问题:相机是怎么成像的,以及怎么从图像里提取有用的信息

我个人习惯,讲这部分时一定先讲模型,再讲处理。因为只有理解了成像的物理过程,你才知道后面为什么要做畸变校正,为什么要做滤波。嗯,咱们开始。

2.1 针孔相机模型:SLAM的几何基础

针孔相机模型,是SLAM里最基础的几何模型。它把三维世界里的一个点,映射到二维图像平面上。说白了,就是一个小孔成像的原理。

我记得刚入行时,总觉得这个模型太简单,不就是个相似三角形吗?但后来在项目里吃过亏——如果你不理解这个模型,后面做三角化、做PnP,全都会乱套。

模型的核心公式是这样的:

// 世界坐标系下的点 P = [X, Y, Z]^T
// 经过相机内参 K 和 外参 [R|t] 投影到像素坐标 [u, v]^T

// 齐次坐标形式:
// s * [u, v, 1]^T = K * [R|t] * [X, Y, Z, 1]^T

// 其中内参矩阵 K:
// K = | fx  0  cx |
//     |  0  fy  cy |
//     |  0   0   1 |

这里 fx, fy 是焦距(以像素为单位),cx, cy 是光心偏移。这些参数,我们通常通过标定得到。

我的经验: 标定相机时,千万别用手机随便拍几张棋盘格就完事。我建议至少拍20-30张不同角度的图片,覆盖视野的各个角落。否则,标定出来的内参在图像边缘会有较大误差,直接影响后续的三角化精度。

2.2 畸变校正:别让镜头骗了你

现实中的镜头,不是完美的针孔。它会有畸变。最常见的两种:径向畸变切向畸变

  • 径向畸变:图像边缘的直线会变弯。桶形畸变(广角镜头常见)和枕形畸变(长焦镜头常见)。
  • 切向畸变:镜头和成像平面不平行导致的,像个梯形。

畸变校正的数学表达,其实就是用几个多项式系数去“拉直”图像。OpenCV里直接提供了函数:

// 假设你已经有了畸变系数 distCoeffs = [k1, k2, p1, p2, k3]
// 以及相机内参 cameraMatrix

cv::Mat map1, map2;
cv::initUndistortRectifyMap(cameraMatrix, distCoeffs, cv::Mat(), 
                            cameraMatrix, imageSize, CV_32FC1, map1, map2);

// 然后用 remap 一次性校正整张图
cv::remap(distorted_img, undistorted_img, map1, map2, cv::INTER_LINEAR);
避坑指南: 我曾经在嵌入式设备上直接对每一帧调用 undistort(),结果发现CPU占用率飙升到90%。后来改用 initUndistortRectifyMap 预计算映射表,再用 remap,性能提升了近10倍。记住:映射表只需计算一次

2.3 图像滤波:去噪与增强

图像处理里,滤波是基本功。SLAM里常用的滤波,主要有两个目的:去噪增强特征

我常用的几种滤波:

滤波类型 作用 OpenCV函数 适用场景
高斯滤波 平滑去噪,保留边缘 GaussianBlur() 特征点提取前的预处理
中值滤波 去除椒盐噪声 medianBlur() 传感器噪声较大的场景
双边滤波 保边去噪 bilateralFilter() 需要保留纹理细节时
拉普拉斯滤波 边缘增强 Laplacian() 特征检测前的锐化

你想想看,如果图像全是噪点,你提取出来的特征点位置会来回跳,SLAM系统根本没法稳定。所以,滤波这一步,看似简单,但参数调不好,后面全白费。

核心原则: 滤波核的大小和标准差,要根据图像分辨率来调。比如640x480的图像,高斯核用5x5就够了;但如果是1920x1080,我建议用7x7或9x9。别死记参数,多试几次。

2.4 特征提取:SLAM的“路标”

特征提取,是视觉SLAM里最核心的一步。我们得从图像里找到那些“与众不同”的点——角点、斑点、边缘。这些点,就是SLAM里的“路标”。

常用的特征点有:

  • ORB:速度快,适合实时SLAM。我自己的项目里90%都用它。
  • SIFT:尺度不变性最好,但计算量太大,不适合嵌入式。
  • SURF:SIFT的加速版,但专利问题,商用要小心。
  • FAST:只检测角点,不计算描述子,常用于光流法。

ORB特征提取的OpenCV代码示例:

cv::Ptr<cv::ORB> orb = cv::ORB::create(1000);  // 最多提取1000个特征点
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;

orb->detectAndCompute(image, cv::Mat(), keypoints, descriptors);

// 绘制特征点
cv::Mat img_with_keypoints;
cv::drawKeypoints(image, keypoints, img_with_keypoints, cv::Scalar::all(-1));
我的习惯: 提取特征点时,我会先做一次直方图均衡化,增强对比度。尤其在光照变化大的场景里,这一步能显著提升特征点的重复率。说白了,就是让暗的地方亮一点,亮的地方暗一点,特征点更容易被检测到。

2.5 OpenCV基础操作:你的瑞士军刀

OpenCV是SLAM开发者的必备工具。我建议你把它当成“瑞士军刀”——不用每个功能都精通,但常用的几个操作要烂熟于心。

这里列几个我每天都会用到的操作:

// 1. 读取和显示图像
cv::Mat img = cv::imread("image.jpg");
cv::imshow("Window", img);
cv::waitKey(0);

// 2. 颜色空间转换
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);

// 3. 图像缩放
cv::Mat resized;
cv::resize(img, resized, cv::Size(640, 480));

// 4. ROI提取(感兴趣区域)
cv::Rect roi(100, 100, 200, 200);
cv::Mat img_roi = img(roi);

// 5. 图像保存
cv::imwrite("output.jpg", img);

这些操作,看起来简单,但组合起来能解决90%的图像预处理问题。我个人习惯,把常用的图像处理流程封装成一个类,比如 ImagePreprocessor,里面包含灰度化、缩放、滤波、直方图均衡化等步骤。这样在SLAM系统里调用起来特别清爽。

注意: OpenCV默认的通道顺序是BGR,不是RGB。我第一次用OpenCV显示图像时,发现颜色不对,查了半天才发现是通道顺序的问题。如果你用 cv::imshow 显示,没问题;但如果你用其他库(比如Matplotlib)显示,记得先转换:cv::cvtColor(img, img, cv::COLOR_BGR2RGB)

知识体系总览

为了让你更直观地理解本章的知识结构,我画了一张图:

第二章:相机模型与图像处理知识体系 针孔相机模型 径向畸变 切向畸变 图像处理 图像滤波 特征提取 高斯滤波 中值滤波 双边滤波 ORB SIFT FAST OpenCV基础操作(读取/显示/变换/保存) 核心流程:相机模型 → 畸变校正 → 图像滤波 → 特征提取 → OpenCV实现 图2-1 本章知识体系结构图

这张图把本章的核心脉络串起来了。从相机模型出发,到畸变校正,再到图像滤波和特征提取,最后用OpenCV落地实现。每一步都环环相扣。


专注资料整理