第四章:图像处理与特征提取

各位同学好,欢迎来到第四章。这一章的内容,我个人认为是整个视觉引导运动规划的基石。你想想看,机器人要看到世界,首先要能“看清”图像,然后要能“认出”关键点。说白了,就是让机器从像素矩阵里,提炼出有意义的几何信息。

我刚开始做视觉SLAM的时候,就吃过亏。当时觉得特征提取嘛,调个OpenCV函数就完事了。结果在真实产线上,光照一变,特征点全丢了,机器人直接撞上了工件。嗯,从那以后,我再也不敢轻视图像预处理这一步了。

4.1 图像滤波:去噪与保边

图像滤波,说白了就是“磨皮”。但你不能把边缘也磨没了,否则机器人就认不出物体轮廓了。

常用的滤波方法,我整理了一个表格,方便你对比:

滤波方法 原理 优点 缺点 我常用的场景
均值滤波 邻域像素取平均 速度快,简单 边缘模糊严重 快速预览,不要求精度
高斯滤波 加权平均,中心权重高 平滑效果好,自然 仍会模糊边缘 去高斯噪声,预处理标准步骤
中值滤波 取邻域中值 去椒盐噪声极好,保边 计算稍慢 工业相机有坏点或噪点
双边滤波 空间+颜色双权重 保边去噪,效果惊艳 计算量大,参数敏感 需要保留边缘的精细处理
我的小技巧: 在嵌入式平台上,如果算力有限,我建议优先用中值滤波代替双边滤波。我曾经在STM32H7上跑双边滤波,一帧640x480的图像要200ms,换成中值滤波后降到30ms,效果也够用。

4.2 边缘检测:Canny算子实战

边缘检测,是特征提取的前奏。我个人最常用的是Canny算子,没有之一。为什么?因为它稳定、成熟、效果好。

Canny的流程其实就四步:

  1. 高斯平滑:先降噪,避免把噪声当成边缘
  2. 计算梯度幅值和方向:用Sobel算子算一下
  3. 非极大值抑制:只保留梯度方向上的局部最大值,让边缘变细
  4. 双阈值检测:高阈值确定强边缘,低阈值连接弱边缘

这里有个坑,我踩过。双阈值怎么设?高阈值太高,边缘断断续续;太低,全是假边缘。我的经验是:高阈值设为最大梯度的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
// 嗯,这个参数组合在大多数工业场景下都适用
注意: 如果图像光照不均匀,直接做Canny效果会很差。我建议先做直方图均衡化,或者用自适应阈值。我曾经在一条产线上,因为没做光照补偿,Canny检测出来的边缘全是阴影,差点把工件当成背景。

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 本章知识体系

为了让你更直观地理解本章的知识结构,我画了一张流程图。它展示了从原始图像到特征点的完整处理链路。

图像处理与特征提取知识体系 原始图像 图像滤波(去噪、保边) 边缘检测(Canny) 特征点提取(ORB/SIFT) 光流法(稀疏/稠密) 图:从原始图像到特征信息的处理流程

这张图很清楚地展示了:原始图像先经过滤波去噪,然后做边缘检测,最后根据需求选择特征点提取或光流追踪。嗯,每一步都有它的意义,缺一不可。


好了,第四章的内容就到这里。图像处理这部分,说难不难,说简单也不简单。关键是多动手,多调参。我建议你找几张自己的图片,试试不同的滤波和特征提取参数,看看效果差异。实践出真知嘛。