4. OpenCV 基础(三):图像模糊、边缘检测与形态学操作
图像处理里,模糊、边缘检测和形态学操作,可以说是最常用的三板斧。我刚开始接触 OpenCV 时,觉得这些不就是些滤镜嘛,后来做项目才发现,每一步都藏着不少坑。今天咱们就把这三块内容彻底捋清楚。
4.1 图像模糊:降噪的第一步
模糊,说白了就是用邻域像素的某种统计值,去替代中心像素。为什么要这么做?因为真实世界的图像总带着噪声,模糊一下,噪声就被「平均」掉了。我个人习惯在边缘检测之前,一定会先做模糊处理。
4.1.1 均值模糊
最简单的模糊方式。把核覆盖区域的所有像素值加起来,除以像素个数。OpenCV 里用 cv2.blur() 实现。
import cv2
import numpy as np
img = cv2.imread('example.jpg')
# 核大小 5x5
blurred = cv2.blur(img, (5, 5))
cv2.imshow('Mean Blur', blurred)
cv2.waitKey(0)
4.1.2 高斯模糊
均值模糊有个问题——它把邻域里每个像素的权重看成一样的。但实际上,离中心越近的像素,相关性应该越强。高斯模糊就是基于这个想法,用高斯分布给像素分配权重。
# 高斯模糊,核大小 5x5,sigmaX 自动计算
gaussian = cv2.GaussianBlur(img, (5, 5), 0)
cv2.imshow('Gaussian Blur', gaussian)
这里 sigmaX=0 表示让 OpenCV 根据核大小自动计算标准差。我个人习惯用这个参数,省事。
4.1.3 中值模糊
中值模糊不是算平均,而是取邻域内所有像素的中位数。它对椒盐噪声(就是那种黑白点噪声)效果特别好。我在做工业检测项目时,遇到过相机传感器坏点导致的噪声,中值模糊一上,效果立竿见影。
# 中值模糊,核大小必须为奇数
median = cv2.medianBlur(img, 5)
cv2.imshow('Median Blur', median)
4.2 边缘检测:Canny 算法
边缘检测里,Canny 算法是当之无愧的王者。它不像 Sobel 那样只算梯度,而是有一套完整的流程:去噪、算梯度、非极大值抑制、双阈值检测。我当年第一次用 Canny 时,觉得这东西太神奇了,怎么就能把轮廓提取得这么干净?
4.2.1 Canny 的基本用法
# 先模糊,再 Canny
blurred = cv2.GaussianBlur(img, (5, 5), 0)
edges = cv2.Canny(blurred, 50, 150)
cv2.imshow('Canny Edges', edges)
两个阈值:50 是低阈值,150 是高阈值。梯度值高于高阈值的,一定是边缘;低于低阈值的,一定不是;介于两者之间的,只有跟确定边缘相连的才算。
| 参数 | 作用 | 我的建议值 |
|---|---|---|
| 低阈值 | 控制弱边缘的保留 | 30-60 |
| 高阈值 | 控制强边缘的保留 | 100-200 |
| 核大小 | 高斯模糊的核 | 5x5 |
核心原则:高阈值一般是低阈值的 2-3 倍。我曾经调参时把两个阈值设成一样,结果边缘断断续续,根本没法用。
4.2.2 为什么一定要先模糊?
Canny 本身不包含去噪步骤。如果直接对原图做 Canny,噪声会被当成边缘检测出来,结果就是满屏的杂乱线条。我见过不少新手踩这个坑——「为什么我的 Canny 结果全是噪点?」嗯,先模糊一下就好了。
4.3 形态学操作:腐蚀与膨胀
形态学操作,说白了就是「让白色区域变大或变小」。它基于图像的形状(形态)来做处理,主要用在二值图像上。我经常在分割之后,用形态学操作来清理分割结果。
4.3.1 腐蚀
腐蚀让白色区域「瘦一圈」。它的原理是:核覆盖的区域里,只要有一个像素是黑色,中心像素就变成黑色。效果就是消除白色的小噪点,断开细小的连接。
# 定义核
kernel = np.ones((5,5), np.uint8)
eroded = cv2.erode(binary_img, kernel, iterations=1)
cv2.imshow('Eroded', eroded)
4.3.2 膨胀
膨胀跟腐蚀相反,让白色区域「胖一圈」。核覆盖的区域里,只要有一个像素是白色,中心像素就变成白色。效果是填充白色区域内的小黑洞,连接断开的白色区域。
dilated = cv2.dilate(binary_img, kernel, iterations=1)
cv2.imshow('Dilated', dilated)
4.3.3 开运算与闭运算
开运算 = 先腐蚀后膨胀。用来消除小白点,同时保持大区域形状不变。闭运算 = 先膨胀后腐蚀。用来填充小黑洞,同时保持大区域形状不变。
# 开运算
opening = cv2.morphologyEx(binary_img, cv2.MORPH_OPEN, kernel)
# 闭运算
closing = cv2.morphologyEx(binary_img, cv2.MORPH_CLOSE, kernel)
4.4 综合应用:预处理流水线
在实际项目中,这些操作很少单独使用。我一般会搭一条预处理流水线:
- 读图 → 转灰度(如果后续不需要颜色信息)
- 高斯模糊 → 降噪,核大小 5x5
- Canny 边缘检测 → 提取轮廓
- 形态学闭运算 → 连接断开的边缘
- 轮廓查找 → 得到目标区域
def preprocess_pipeline(img):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5,5), 0)
edges = cv2.Canny(blurred, 50, 150)
kernel = np.ones((3,3), np.uint8)
closed = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
return closed
好了,这一章的内容就是这些。模糊、边缘检测、形态学操作,这三样东西在 OpenCV 里几乎天天用。多动手试试不同的参数组合,你很快就能找到感觉。