图像处理基础——滤波、边缘检测和形态学操作
上篇聊了点云处理——法线估计、FPFH特征、配准。从三维点云到二维图像,感知系统处理的不仅是立体世界,平面图像的处理同样是机器人视觉的根基。
面试中被问到图像处理,很多人上来就讲深度学习、讲YOLO。但面试官真正想听的,往往是你对传统图像处理的理解。你想想,就算是深度学习方案,输入图像也得先做预处理——去噪、增强、二值化,这些全是传统算法的活。滤波怎么选型?边缘检测用Canny还是Sobel?形态学操作到底解决什么问题?这些基本功不扎实,后面讲再多网络结构也站不住。
今天把图像处理最核心的三块拆开讲:滤波、边缘检测、形态学操作。这三块在机器人视觉管线里几乎是标配,不管你后端接的是传统算法还是神经网络,前端处理都绕不开它们。
滤波:去噪还是保边,这是个问题
图像滤波说白了就是用一个小矩阵(卷积核)在图像上滑动,对每个像素周围的区域做加权计算,得到新的像素值。
最常见的两种滤波:
均值滤波——卷积核里所有权重相等,效果就是把每个像素变成周围像素的平均值。简单粗暴,去噪快,但边缘也糊掉了。
import cv2
img = cv2.imread("noisy.jpg", 0)
blurred = cv2.blur(img, (5, 5))
高斯滤波——权重按高斯分布排列,离中心越远权重越小。比均值滤波平滑自然,是实际项目中最常用的去噪手段。
gaussian = cv2.GaussianBlur(img, (5, 5), 1.0)
但这两种都有个问题:去噪的同时会模糊边缘。
讲真,机器人场景里边缘信息特别重要——车道线检测、二维码识别、货架定位,都依赖清晰的边缘。所以你需要一种既能去噪又能保边的滤波器。
双边滤波就是干这个的。它在高斯滤波的基础上,多了一个灰度差的权重——只有灰度相近的像素才会互相影响,灰度差大的(通常就是边缘)不会被平滑掉。代价是计算量比高斯滤波大不少,因为每个像素都要算灰度权重。
bilateral = cv2.bilateralFilter(img, 9, 75, 75)
面试追问场景:面试官问你"双边滤波的参数怎么调",你要能说清楚:第二个参数是邻域直径,后两个分别是颜色空间和坐标空间的sigma。直径越大计算越慢,sigma越大平滑范围越广。实际调参时先固定直径在9到15之间,然后调sigma,观察边缘保持的效果。
之前做仓储机器人的时候,相机拍的二维码经常因为光照不均出现噪点,直接二值化识别率很低。加了双边滤波之后,噪点去掉了,二维码边缘还很锐利,识别率从70%多直接飙到95%以上。
边缘检测:从Sobel到Canny
边缘检测是图像处理里最经典的操作。图像中灰度变化剧烈的地方就是边缘——数学上就是梯度大的地方。
Sobel算子是最简单的边缘检测器。它用两个3x3的卷积核分别计算水平方向和垂直方向的梯度:
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
magnitude = cv2.magnitude(sobel_x, sobel_y)
Sobel的问题是:它只算了一阶导数,对噪声敏感,而且边缘比较粗。
Canny边缘检测是工业界用得最多的。它不是一个简单的算子,而是一整套流程:
- 高斯滤波去噪
- 计算梯度幅值和方向(通常用Sobel)
- 非极大值抑制——沿着梯度方向,只保留局部最大的点,把粗边缘细化成单像素宽
- 双阈值检测——高阈值以上的点确定为边缘,低阈值以下的点丢弃,中间的点如果跟高阈值的边缘相连就保留
edges = cv2.Canny(img, 50, 150)
两个阈值的选择很讲究。经验法则是高阈值是低阈值的2到3倍。具体数值要看图像本身的梯度分布——可以先算出整张图的梯度直方图,取梯度值的某个百分位作为高阈值。
面试中经常被问到的一个坑:Canny为什么用非极大值抑制?答案是Sobel算出来的边缘是"胖"的——一个真实的边缘可能被多个像素响应。非极大值抑制把胖边缘压成单像素宽,后续处理(比如直线检测、轮廓提取)才准确。
形态学操作:处理二值图像的利器
形态学操作是针对二值图像(或者灰度图像)的一组操作,核心思想是用一个结构元素在图像上"滑动",根据结构元素和图像的交集来改变像素值。
最基本的两个操作:
腐蚀——结构元素覆盖的区域里,只要有一个像素是0,中心像素就变成0。效果是缩小白色区域,去掉小的噪点。
膨胀——结构元素覆盖的区域里,只要有一个像素是1,中心像素就变成1。效果是扩大白色区域,填补小的空洞。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
eroded = cv2.erode(binary_img, kernel)
dilated = cv2.dilate(binary_img, kernel)
腐蚀和膨胀组合起来,可以得到更强大的操作:
开运算——先腐蚀再膨胀。去掉小的白色噪点,断开狭窄的连接。适合去除背景里的小白点。
闭运算——先膨胀再腐蚀。填补白色区域内的小黑洞,连接邻近的区域。适合填补目标内部的空洞。
opened = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, kernel)
closed = cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel)
形态学梯度——膨胀减去腐蚀,得到的是目标的边界。
顶帽变换——原图减去开运算结果,提取出比周围亮的细节。
底帽变换——闭运算结果减去原图,提取出比周围暗的细节。
实际项目中,形态学操作经常是图像处理的最后一步。比如做车道线检测,经过滤波、边缘检测、霍夫变换之后,得到的车道线可能有一些断裂或者噪点。用闭运算填补断裂,用开运算去掉孤立噪点,最终结果就干净了。
结构元素的选择也有讲究。圆形结构元素对各个方向的处理比较均匀,矩形结构元素在水平和垂直方向效果更好。十字形结构元素适合处理线状结构。
面试中怎么聊
面试官问图像处理基础,按这个顺序回答:先讲滤波(均值、高斯、双边滤波各自的适用场景),再讲边缘检测(Sobel的局限性、Canny的完整流程),最后讲形态学操作(腐蚀膨胀开闭运算的实际用途)。
如果面试官追问"高斯滤波的sigma怎么选",你可以说:sigma决定了高斯核的"胖瘦"。sigma大,核的权重分散,平滑效果强但细节丢失多。sigma小,核的权重集中,保留细节但去噪能力弱。经验值是取核大小的六分之一左右,比如5x5的核sigma取1.0到1.5。
如果面试官追问"为什么不用Sobel直接做边缘检测",你可以说:Sobel只算了一阶梯度,对噪声太敏感,而且边缘是粗的。Canny通过非极大值抑制细化了边缘,通过双阈值检测区分了强弱边缘,效果比Sobel好很多。实际项目中几乎没人直接用Sobel做边缘检测,都是上Canny。
分享一个我做视觉检测项目的经验。当时给一个产线做产品缺陷检测,相机拍到的图像光照不均匀,背景有渐变。直接做边缘检测,整个背景渐变都被当成边缘了。我们的处理流程是:先做顶帽变换去除光照不均(顶帽变换能提取出比周围亮的细节,把渐变背景去掉),然后做高斯滤波去噪,最后用Canny提取缺陷边缘。形态学操作用了闭运算来连接断裂的缺陷轮廓。整套流程跑下来,缺陷检出率从60%多提升到92%。
图像处理这些基础操作看着简单,但调参和组合非常依赖经验。面试时候能讲出具体的项目案例,比背公式有用得多。
下一篇讲信号处理基础——傅里叶变换、滤波和采样定理。从图像处理到信号处理,底层数学工具是相通的。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。
「机器人软件开发面试·从入门到精通」连载系列 上一篇:第172篇 点云数据处理——法线估计、FPFH特征和配准 下一篇预告:第174篇 信号处理基础——傅里叶变换、滤波和采样定理
有任何问题欢迎评论区留言,我会尽量回复。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)