第四章:图像处理与特征提取
各位同学好,欢迎来到第四章。这一章的内容,我个人认为是整个视觉引导运动规划的基石。你想想看,机器人要看到世界,首先要能“看清”图像,然后要能“认出”关键点。说白了,就是让机器从像素矩阵里,提炼出有意义的几何信息。
我刚开始做视觉SLAM的时候,就吃过亏。当时觉得特征提取嘛,调个OpenCV函数就完事了。结果在真实产线上,光照一变,特征点全丢了,机器人直接撞上了工件。嗯,从那以后,我再也不敢轻视图像预处理这一步了。
4.1 图像滤波:去噪与保边
图像滤波,说白了就是“磨皮”。但你不能把边缘也磨没了,否则机器人就认不出物体轮廓了。
常用的滤波方法,我整理了一个表格,方便你对比:
| 滤波方法 | 原理 | 优点 | 缺点 | 我常用的场景 |
|---|---|---|---|---|
| 均值滤波 | 邻域像素取平均 | 速度快,简单 | 边缘模糊严重 | 快速预览,不要求精度 |
| 高斯滤波 | 加权平均,中心权重高 | 平滑效果好,自然 | 仍会模糊边缘 | 去高斯噪声,预处理标准步骤 |
| 中值滤波 | 取邻域中值 | 去椒盐噪声极好,保边 | 计算稍慢 | 工业相机有坏点或噪点 |
| 双边滤波 | 空间+颜色双权重 | 保边去噪,效果惊艳 | 计算量大,参数敏感 | 需要保留边缘的精细处理 |
4.2 边缘检测:Canny算子实战
边缘检测,是特征提取的前奏。我个人最常用的是Canny算子,没有之一。为什么?因为它稳定、成熟、效果好。
Canny的流程其实就四步:
- 高斯平滑:先降噪,避免把噪声当成边缘
- 计算梯度幅值和方向:用Sobel算子算一下
- 非极大值抑制:只保留梯度方向上的局部最大值,让边缘变细
- 双阈值检测:高阈值确定强边缘,低阈值连接弱边缘
这里有个坑,我踩过。双阈值怎么设?高阈值太高,边缘断断续续;太低,全是假边缘。我的经验是:高阈值设为最大梯度的70%,低阈值设为高阈值的1/3。你可以试试这个比例。
// C++ 示例:Canny边缘检测
cv::Mat src = cv::imread("part.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat edges;
cv::Canny(src, edges, 50, 150); // 低阈值50,高阈值150
// 嗯,这个参数组合在大多数工业场景下都适用
4.3 特征点提取:ORB vs SIFT
特征点,就是图像里那些“与众不同”的点——角点、斑点、纹理丰富的地方。机器人靠这些点来匹配前后帧,或者匹配模板。
ORB和SIFT,是两大主流。我分别说说我的使用感受。
4.3.1 SIFT:精度之王,但慢
SIFT(尺度不变特征变换)是经典中的经典。它对旋转、尺度、光照变化都很鲁棒。但问题是,它太慢了。在嵌入式平台上,一帧图像提取SIFT特征可能要几百毫秒,根本跑不了实时。
我记得有一次做无人机视觉定位,用SIFT在树莓派上跑,帧率只有2fps,飞机稍微一晃就丢特征。后来我换成了ORB,帧率到了30fps,虽然匹配精度差一点,但够用了。
4.3.2 ORB:速度与精度的平衡
ORB(Oriented FAST and Rotated BRIEF)是OpenCV的宠儿。它结合了FAST角点检测和BRIEF描述子,还加了旋转不变性。速度比SIFT快一个数量级,精度在大多数场景下也够用。
我个人在移动机器人上,90%的情况都用ORB。只有在高精度视觉测量(比如零件装配)时,才会考虑SIFT。
// C++ 示例:ORB特征提取
cv::Ptr<cv::ORB> orb = cv::ORB::create(500); // 提取500个特征点
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
orb->detectAndCompute(image, cv::Mat(), keypoints, descriptors);
// 嗯,500个点对于640x480的图像,通常够用了
- SIFT:专利保护(虽然过期了),计算量大,精度高,适合离线或高算力平台
- ORB:开源免费,速度快,适合嵌入式实时系统
- 我的建议:先试ORB,不行再换SIFT。别一上来就上SIFT,容易把系统拖垮。
4.4 光流法简介:让图像动起来
光流法,说白了就是追踪像素点的运动。你想想看,如果上一帧的某个角点,在下一帧移动到了另一个位置,那我们就知道物体在运动了。
光流法分两种:
- 稀疏光流:只追踪少数特征点(比如Lucas-Kanade方法)。速度快,适合特征点跟踪。
- 稠密光流:计算每个像素的运动。信息量大,但计算量也大。
我在项目中常用稀疏光流来做视觉里程计。比如,先用ORB提取特征点,然后用LK光流追踪这些点,最后通过匹配点对估计相机的运动。这一套流程,在室内环境下非常稳定。
// C++ 示例:LK光流追踪
std::vector<cv::Point2f> prev_pts, next_pts;
std::vector<uchar> status;
std::vector<float> err;
cv::calcOpticalFlowPyrLK(prev_gray, curr_gray, prev_pts, next_pts, status, err);
// status为1表示追踪成功,为0表示丢失
4.5 本章知识体系
为了让你更直观地理解本章的知识结构,我画了一张流程图。它展示了从原始图像到特征点的完整处理链路。
这张图很清楚地展示了:原始图像先经过滤波去噪,然后做边缘检测,最后根据需求选择特征点提取或光流追踪。嗯,每一步都有它的意义,缺一不可。
好了,第四章的内容就到这里。图像处理这部分,说难不难,说简单也不简单。关键是多动手,多调参。我建议你找几张自己的图片,试试不同的滤波和特征提取参数,看看效果差异。实践出真知嘛。