4. 图像处理基础:图像滤波、边缘检测、特征提取(SIFT、SURF、ORB)
各位同学,欢迎来到第四章。说实话,这一章的内容,是咱们整个感知算法的“地基”。你想想看,摄像头采集回来的原始图像,就像一块没切过的牛排——信息量很大,但直接吃可不行。我们需要先处理一下,才能让算法“消化”掉。
我个人习惯把图像处理分成三步走:去噪 → 找轮廓 → 抓特征。今天我们就按这个顺序,把滤波、边缘检测和特征提取这三板斧讲透。
4.1 图像滤波:给图像“磨个皮”
为什么需要滤波?因为传感器有噪声。我在项目中遇到过,一个晴朗的下午,摄像头拍出来的图像上全是椒盐噪声,像撒了一把盐。如果不处理,后面的边缘检测会把这些噪点当成“假边缘”,结果就是一团糟。
常用的滤波方法,我列个表给你看:
| 滤波方法 | 核心思想 | 适用场景 |
|---|---|---|
| 均值滤波 | 用邻域像素的平均值代替中心像素 | 简单平滑,但会模糊边缘 |
| 高斯滤波 | 按高斯权重加权平均,中心权重最大 | 去除高斯噪声,保留边缘信息 |
| 中值滤波 | 用邻域像素的中位数代替中心像素 | 去除椒盐噪声,效果极佳 |
| 双边滤波 | 同时考虑空间距离和像素值差异 | 保边去噪,但计算量大 |
嗯,这里要注意:没有万能的滤波器。我刚开始做项目时,总想找一个“最优”的滤波参数,后来发现这是徒劳的。你得根据实际场景来调。比如在高速公路上,高斯滤波就够用了;但在城市道路,行人多、光线变化大,我建议用双边滤波。
4.2 边缘检测:找到图像的“骨架”
滤波之后,我们就要找边缘了。边缘是什么?说白了,就是图像中像素值发生剧烈变化的地方。比如车道线的边界、行人的轮廓、路沿的棱角。
最经典的边缘检测算法是 Canny 边缘检测。我个人觉得,Canny 是“祖师爷”级别的算法,到现在很多自动驾驶项目还在用。它的流程是这样的:
- 高斯滤波:先降噪,避免把噪声当成边缘。
- 计算梯度:用 Sobel 算子算出每个像素的梯度强度和方向。
- 非极大值抑制:只保留梯度方向上的局部最大值,把“胖”边缘变“瘦”。
- 双阈值检测:设定高低两个阈值,高阈值确定强边缘,低阈值连接弱边缘。
我曾经在项目中踩过一个坑:双阈值设得太高,结果车道线断成了好几截;设得太低,又全是杂乱的边缘。后来我总结了一个经验:高阈值设为低阈值的 2-3 倍,一般效果都不错。
4.3 特征提取:SIFT、SURF、ORB
边缘检测找到了轮廓,但还不够。我们需要找到图像中那些“独一无二”的点——也就是特征点。这些特征点要具备尺度不变性和旋转不变性,这样不管摄像头怎么动,都能匹配上。
这里我重点讲三个算法:SIFT、SURF、ORB。它们各有千秋,我直接上对比表:
| 算法 | 特点 | 速度 | 精度 | 专利问题 |
|---|---|---|---|---|
| SIFT | 尺度不变性最强,旋转不变性也好 | 慢 | 高 | 有专利,商用需授权 |
| SURF | SIFT的加速版,用积分图优化 | 较快 | 较高 | 有专利 |
| ORB | 基于FAST角点 + BRIEF描述子 | 极快 | 中等 | 无专利,开源友好 |
你可能会问:“那我该用哪个?” 我的建议是:
- 如果你做的是高精度地图构建或视觉SLAM,SIFT 依然是首选,虽然慢,但稳。
- 如果你做的是实时性要求高的应用,比如车道保持、行人检测,ORB 是更好的选择。我在一个嵌入式项目里用过 ORB,在 30fps 的摄像头数据流上跑得飞起。
- SURF 嘛,说实话,现在用得越来越少了。它比 SIFT 快,但不如 ORB 快,处于一个“不上不下”的位置。
4.4 实战:用 OpenCV 实现特征匹配
光说不练假把式。我带你写一段代码,用 ORB 做特征匹配。这是我在一个视觉定位项目中用过的简化版:
import cv2
import numpy as np
# 读取两张图像
img1 = cv2.imread('image1.jpg', 0)
img2 = cv2.imread('image2.jpg', 0)
# 初始化 ORB 检测器
orb = cv2.ORB_create(nfeatures=500)
# 检测特征点和计算描述子
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 使用 BFMatcher 进行匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# 按距离排序,取前 50 个最佳匹配
matches = sorted(matches, key=lambda x: x.distance)[:50]
# 绘制匹配结果
img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches, None, flags=2)
cv2.imshow('ORB Matches', img_matches)
cv2.waitKey(0)
cv2.destroyAllWindows()
这段代码很简单,但有几个关键点我要提醒你:
nfeatures=500表示最多提取 500 个特征点。太少可能匹配不上,太多会拖慢速度。我一般从 500 开始调。NORM_HAMMING是 ORB 专用的距离度量,因为 ORB 的描述子是二进制的。crossCheck=True可以过滤掉“一厢情愿”的匹配,提高准确率。
运行这段代码,你会看到两张图像之间连满了线。如果大部分线都是平行的、方向一致的,说明匹配效果不错。如果线乱成一团,嗯,那就要检查一下图像质量或者调整参数了。
4.5 小结
这一章我们讲了图像处理的三个核心步骤:滤波、边缘检测、特征提取。说白了,就是从“像素”到“语义”的桥梁。你想想看,如果没有这些基础操作,后面的目标检测、车道线识别根本无从谈起。
下一章,我们会进入更高级的话题——相机标定与畸变校正。到时候我会分享一个我在标定板上踩过的坑,保证让你印象深刻。
好了,今天就到这里。去动手试试 ORB 匹配吧,代码我都给你了,别偷懒。