查看全课大纲(第 7 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
腐蚀膨胀与开闭运算
腐蚀、膨胀与开闭运算
小象实战讲义 · 基于深度学习的计算机视觉
在图像处理中,我们常常需要处理二值化后的图像,例如从医学影像中分割出的器官轮廓,或从场景中提取的文字区域。这些二值图像往往存在边界不平滑、内部有小孔洞或外部有孤立噪声点等问题。形态学运算,作为图像预处理领域的重要工具,能够在不依赖复杂知识的情况下,有效地“修复”这些图像,使其轮廓更光滑、结构更完整,为后续的特征提取或深度学习分析提供更干净的输入。
💡 核心导读
- 形态学运算的定位:它是空间域图像处理中,基于“结构元素”对目标形状进行操作的邻域运算,与卷积运算有本质区别。
- 腐蚀与膨胀:形态学的基础运算。腐蚀使目标区域“收缩”,消除边界点;膨胀使目标区域“扩张”,填补邻近区域。
- 开运算与闭运算:腐蚀与膨胀的组合。开运算能消除小物体、平滑边界;闭运算能填充小孔洞、连接邻近物体。
- 应用场景:常用于二值图像的后处理,平滑由阈值分割或深度学习模型产生的粗糙边界,去除噪声,连接断裂部分。
图像处理方法的分类
在深入形态学之前,我们先从宏观上了解图像预处理的方法体系。根据处理域的不同,图像处理主要分为两大类:
空间域处理:直接在图像像素构成的二维空间上进行操作。
- 点运算:输出像素值仅取决于输入像素自身的值。典型例子是直方图均衡化(HE)。
- 邻域运算:输出像素值取决于输入像素及其周围邻域像素的值。典型例子包括:
- 形态学运算:即本节要讲的腐蚀、膨胀、开闭运算。它基于一个称为“结构元素”的模板,对目标的形状进行操作。
- 卷积运算:如平滑滤波(高斯、均值)、边缘检测(Sobel、Laplacian)以及构建图像金字塔等。卷积核(模板)与图像对应位置进行加权求和。(中值滤波取邻域中位数,属非线性排序统计,不是加权求和型卷积。)
- 自适应/分块均衡:如 CLAHE,需要借助局部窗口或分块统计,因此不属于点运算。
- 虽然形态学和卷积都使用“模板”,但运算逻辑不同:形态学模板仅定义形状,用于集合运算;卷积模板则定义了具体的权重值,用于加权求和。
频率域处理:将图像从空间域转换到频率域(如通过傅里叶变换、小波变换),在频率域中进行滤波等操作,再转换回空间域。这相当于将图像视为不同频率的二维波形的叠加。
本节我们聚焦于空间域处理中的形态学运算。
形态学基础:结构元素、腐蚀与膨胀
形态学运算的核心是使用一个称为结构元素的小图像(模板)来探测或改变目标图像的结构。
结构元素
设有两幅图像 A 和 S。若 A 是被处理的目标图像(通常是二值图,前景为1/白色,背景为0/黑色),而 S 是用来处理 A 的模板,则称 S 为结构元素。
结构元素 S 通常是一个比较小的图像,它有一个必须明确的原点(Anchor Point)。这与卷积核有显著区别:卷积核的原点通常固定在其几何中心,因此尺寸常为奇数(如3×3, 5×5);而形态学结构元素的原点可以是其内部的任意位置,形状也可以是任意的(如十字形、圆形、自定义形状)。
腐蚀
腐蚀运算会使图像中的目标区域(前景)收缩。其定义如下:
让结构元素 S 的原点在图像平面 Z² 上移动。当 S 的原点平移到某一点 z 时,如果 S 能够被完全包含在目标图像 A 中(即 S 的每一个点都落在 A 为前景的区域内),那么点 z 就属于腐蚀后的图像。
直观理解:用结构元素 S 作为“探针”扫描图像。只有当 S 完全落在目标内部时,其原点所在位置才被保留为目标点。这导致目标边界上所有无法完全容纳 S 的点被“腐蚀”掉,从而使目标整体缩小。
膨胀
膨胀是腐蚀的对偶运算,会使图像中的目标区域扩张。其定义如下:
让结构元素 S 的原点在图像平面 Z² 上移动。当 S 的原点平移到某一点 z 时,如果 S 与目标图像 A 的交集不为空(即 S 与 A 至少有一个像素重叠),那么点 z 就属于膨胀后的图像。
直观理解:用结构元素 S 扫描图像。只要 S 与目标有任何一个点接触,其原点所在位置就被标记为目标点。这相当于将结构元素的原点放在目标的每一个点上,然后画出整个结构元素,所有被覆盖的区域就构成了膨胀后的目标。结果是目标区域向外“生长”,填补了孔洞和断裂,并平滑了边界。
为了更直观地理解,我们通过一个简单的代码示例来演示腐蚀和膨胀的效果。这里我们使用 OpenCV 的 cv2.erode 和 cv2.dilate 函数。
import numpy as np
import cv2
# 创建一个简单的二值图像(8x8),前景为1(白色),背景为0(黑色)
# 图像中心有一个4x4的方块作为目标
image = np.zeros((8, 8), dtype=np.uint8)
image[2:6, 2:6] = 1 # 设置中心区域为前景
print("原始二值图像 (0=背景, 1=前景):")
print(image)
# 定义一个3x3的矩形结构元素,原点在中心(1,1)
kernel = np.ones((3, 3), dtype=np.uint8)
# 腐蚀操作
eroded = cv2.erode(image, kernel, iterations=1)
print("\n腐蚀后的图像:")
print(eroded)
# 膨胀操作
dilated = cv2.dilate(image, kernel, iterations=1)
print("\n膨胀后的图像:")
print(dilated)原始二值图像 (0=背景, 1=前景):
[[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 1 1 1 1 0 0]
[0 0 1 1 1 1 0 0]
[0 0 1 1 1 1 0 0]
[0 0 1 1 1 1 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]]
腐蚀后的图像:
[[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 1 1 0 0 0]
[0 0 0 1 1 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]]
膨胀后的图像:
[[0 0 0 0 0 0 0 0]
[0 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 0]
[0 1 1 1 1 1 1 0]
[0 0 0 0 0 0 0 0]]从输出可以看出:
- 腐蚀:中心的 4×4 方块最外一圈无法被 3×3 结构元素完全包含,被移除后缩小为 2×2。
- 膨胀:4×4 方块四周各向外扩展一圈,变成 6×6(第 1
6 行、第 16 列)。若原图中有小缺口,膨胀会把它们连接起来。
进阶组合:开运算与闭运算
单纯使用腐蚀或膨胀可能会过度消除目标或引入噪声。将两者按顺序组合,则能实现更精细的形状调整,这就是开运算和闭运算。
开运算:先腐蚀,再膨胀。
- 作用:消除目标外部的细小噪声点(孤立的亮点)、断开物体间狭窄的连接、平滑目标边界的同时不明显改变其面积。
- 直观理解:先通过腐蚀去掉毛刺和小连接点,再通过膨胀大致恢复原始大小,但那些被完全腐蚀掉的细小部分无法恢复。
闭运算:先膨胀,再腐蚀。
- 作用:填充目标内部的小孔洞、连接邻近的物体、平滑目标边界的同时能弥合狭窄的间断。
- 直观理解:先通过膨胀填补孔洞和断裂,再通过腐蚀恢复大致形状,但已被连接起来的部分会保留。
开运算和闭运算对于处理阈值分割后带有噪声的二值图像特别有效。下面我们用代码模拟一个带有噪声(外部孤立点、内部孔洞)的目标,并展示开闭运算的效果。
import numpy as np
import cv2
# 创建一个更大的二值图像,模拟一个带有噪声和孔洞的矩形目标
image_noisy = np.zeros((12, 12), dtype=np.uint8)
# 主目标:一个矩形
image_noisy[3:9, 3:9] = 1
# 添加一些噪声:外部孤立点
image_noisy[1, 1] = 1
image_noisy[1, 10] = 1
image_noisy[10, 2] = 1
# 添加内部孔洞
image_noisy[5, 5] = 0
image_noisy[6, 6] = 0
print("带有噪声和孔洞的原始图像:")
print(image_noisy)
# 使用3x3结构元素
kernel = np.ones((3, 3), dtype=np.uint8)
# 开运算:先腐蚀后膨胀
opening = cv2.morphologyEx(image_noisy, cv2.MORPH_OPEN, kernel)
print("\n开运算后的图像 (去除外部噪声):")
print(opening)
# 闭运算:先膨胀后腐蚀
closing = cv2.morphologyEx(image_noisy, cv2.MORPH_CLOSE, kernel)
print("\n闭运算后的图像 (填充内部孔洞):")
print(closing)
# 结合使用:先开运算去外部噪声,再闭运算填内部孔洞(或反之,取决于需求)
opening_then_closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel)
print("\n先开运算后闭运算的图像:")
print(opening_then_closing)带有噪声和孔洞的原始图像:
[[0 0 0 0 0 0 0 0 0 0 0 0]
[0 1 0 0 0 0 0 0 0 0 1 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 0 1 1 1 0 0 0]
[0 0 0 1 1 1 0 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]]
开运算后的图像 (去除外部噪声):
[[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 0 0 0]
[0 0 0 0 0 0 1 1 1 0 0 0]
[0 0 0 0 0 0 1 1 1 0 0 0]
[0 0 0 1 1 1 0 0 0 0 0 0]
[0 0 0 1 1 1 0 0 0 0 0 0]
[0 0 0 1 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]]
闭运算后的图像 (填充内部孔洞):
[[1 1 0 0 0 0 0 0 0 0 1 1]
[1 1 0 0 0 0 0 0 0 0 1 1]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 1 1 1 1 1 1 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0]
[0 0 1 0 0 0 0 0 0 0 0 0]]
先开运算后闭运算的图像:
[[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 1 1 0 0 0]
[0 0 0 0 0 0 1 1 1 0 0 0]
[0 0 0 0 0 0 1 1 1 0 0 0]
[0 0 0 1 1 1 0 0 0 0 0 0]
[0 0 0 1 1 1 0 0 0 0 0 0]
[0 0 0 1 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0]]从结果可以清晰看到:
- 开运算去除了左上、右上和左下角的外部孤立噪声点,但 6×6 的矩形目标也被从中间“切断”,变成了两块 3×3 的区域——因为 3×3 结构元素相对目标偏大,孔洞附近的像素在腐蚀阶段被一并去掉了。
- 闭运算填补了内部的两个孔洞((5,5) 和 (6,6)),但外部噪声点不仅保留,还被膨胀放大了。
- 先开后闭在本例中没有恢复出一个完整矩形,仍是两块 3×3 区域。这说明:开闭运算的顺序、结构元素尺寸必须与目标尺度匹配;结构元素过大时,去噪的代价可能是把目标本身拆散。
重要注意事项:前景与背景的约定
形态学运算通常是针对图像中的前景物体进行的。这里有一个关键的实现细节需要特别注意:在二值图像中,哪个颜色代表前景(物体),哪个代表背景?
不同的软件和库对此有不同的默认约定:
- OpenCV:形态学函数默认把非零像素(白色)视为前景,零像素(黑色)视为背景。本节代码正是遵循这一约定(用 1 表示前景)。
- 部分传统示例(如某些 Visual C++ 代码):习惯把物体画成黑色(灰度值 0)、背景画成白色(255)。
- MATLAB:二值形态学默认白色为前景、黑色为背景。
核心在于:在使用任何工具(如 cv2.erode, cv2.dilate)之前,必须确认它对前景/背景的约定;约定反了,腐蚀和膨胀的视觉效果就会与预期相反。
📝 动手练一练
- 概念辨析:形态学运算中的“结构元素”与卷积运算中的“卷积核”都是一种模板,它们在核心作用上有何本质区别?
👉 点击查看参考答案
结构元素:定义了一个用于进行集合论运算(如包含、交集)的形状模板。它本身的值通常为1(表示参与运算的点),运算时关注的是结构元素形状是否完全在目标内(腐蚀)或是否与目标有交集(膨胀)。原点位置可以是任意的。
卷积核:定义了一组用于进行加权求和运算的数值权重。运算时,核在图像上滑动,每个位置的输出是核内权重与对应图像像素值的乘积之和。原点通常固定在核的中心。
简言之,结构元素用于形状的探测与操作,而卷积核用于数值的加权融合与特征提取。
- 效果推演:假设有一幅二值图像,前景(白色)是一个单独的、孤立的像素点(即图像中只有一个像素值为1)。使用一个3×3的矩形结构元素(原点在中心)对其进行一次腐蚀操作和一次膨胀操作,结果图像分别是什么?为什么?
👉 点击查看参考答案
- 腐蚀结果:图像将全部变为背景(黑色)。因为腐蚀要求结构元素完全包含在目标内。对于一个孤立的像素点,没有任何位置能让一个3×3的结构元素完全被它包含(它自己只有一个点)。因此,腐蚀后没有任何点满足条件,目标消失。
- 膨胀结果:图像中将出现一个3×3的白色方块。因为膨胀要求结构元素与目标有交集。当结构元素原点与那个孤立像素点重合时,结构元素覆盖的3×3区域与该点有交集(就是该点本身)。因此,膨胀操作会将结构元素原点覆盖的所有位置(即那个3×3区域)都置为前景,形成一个3×3的块。
本章小结
本节我们深入学习了图像形态学处理的核心内容。形态学运算通过结构元素这一基本工具,对二值图像的目标形状进行腐蚀、膨胀及其组合操作,能够有效地平滑轮廓、去除噪声、连接断点、填充孔洞,是图像预处理和后处理中不可或缺的技术。
📋 行动清单
- 使用
OpenCV或numpy手动实现一次腐蚀和膨胀运算的模拟(不使用cv2.erode/dilate),加深对集合运算本质的理解。 - 找一张简单的二值图(或对灰度图进行阈值分割得到),分别应用腐蚀、膨胀、开运算、闭运算,观察并总结每种操作对图像边界、噪声、孔洞的具体影响。
- 思考在深度学习图像分割任务(如 U-Net 输出)的后处理中,如何利用开闭运算来优化分割掩码(mask)的质量。
—— 小象教研组