查看全课大纲(第 7 / 93 节)
  1. 1.计算机视觉概述
  2. 2.计算机视觉应用场景
  3. 3.困难与挑战
  4. 4.开源库介绍
  5. 5.图像显示与存储
  6. 6.CLAHE
  7. 7.腐蚀膨胀与开闭运算
  8. 8.滤波与边缘填充
  9. 9.各种滤波
  10. 10.图像金字塔
  11. 11.傅里叶变换
  12. 12.短时傅里叶变换与小波变换
  13. 13.颜色特征与颜色直方图
  14. 14.几何特征_边缘
  15. 15.几何特征-角点与斑点
  16. 16.局部特征-SIFT特征描述子
  17. 17.局部特征-SURF特征描述子
  18. 18.局部特征-ORB特征描述子
  19. 19.其他特征描述子
  20. 20.传统图像分割技术
  21. 21.GraphCut
  22. 22.GrabCut
  23. 23.人脸检测的传统方法
  24. 24.行人检测的传统方法HOG+SVM
  25. 25.行人检测DPM
  26. 26.人工神经网络的架构
  27. 27.目标函数与梯度下降
  28. 28.误差反向传播
  29. 29.可手算的神经网络
  30. 30.改进目标函数与克服梯度消散
  31. 31.神经网络之传统神经网络与深度网络的差异
  32. 32.各种梯度下降算法
  33. 33.如何选择合适的梯度下降算法
  34. 34.避免过适应
  35. 35.CNN的架构
  36. 36.CNN的误差反向传播_卷积层
  37. 37.CNN的误差反向传播_池化层
  38. 38.分类所用的数据集
  39. 39.Alexnet详解
  40. 40.VGG详解
  41. 41.GoogLeNet_v1
  42. 42.BatchNormalization
  43. 43.GoogLeNet_v3v4
  44. 44.ResNet
  45. 45.ResNeXt
  46. 46.CNN设计准则
  47. 47.图像检索与推土机距离
  48. 48.基于颜色相似性
  49. 49.基于形状相似性
  50. 50.基于纹理相似性
  51. 51.基于局部特征相似性
  52. 52.DL的二值哈希编码
  53. 53.高维空间中检索KDTree
  54. 54.高维空间中检索LSH
  55. 55.目标识别任务综述
  56. 56.RCNN
  57. 57.SPP-Net
  58. 58.Fast R-CNN
  59. 59.Faster R-CNN
  60. 60.R-CNN系列模型回顾
  61. 61.R-FCN的设计思路
  62. 62.R-FCN的实现
  63. 63.YOLOv1
  64. 64.YOLOv2_9000
  65. 65.YOLOv3
  66. 66.图像分割应用场景与数据集
  67. 67.上采样的实现方式
  68. 68.FCN网络架构的由来
  69. 69.FCN构建细节
  70. 70.DeepLabv1
  71. 71.CRF
  72. 72.DeepLabv2
  73. 73.DeepLabv3
  74. 74.特定的医学影像分割任务
  75. 75.U-Net
  76. 76.3D U-Net
  77. 77.V-Net
  78. 78.DenseNet
  79. 79.病理切片分析任务及数据集
  80. 80.病理切片分析
  81. 81.图说任务及其传统处理方法
  82. 82.深度语言模型与RNN
  83. 83.LSTM及其变种
  84. 84.NIC模型
  85. 85.SAT模型
  86. 86.图说的性能指标
  87. 87.判别模型与生成模型
  88. 88.KL散度与JS散度
  89. 89.VAE
  90. 90.GAN的基本原理
  91. 91.DCGAN与CGAN
  92. 92.Wasserstein GAN
  93. 93.SRGAN

腐蚀膨胀与开闭运算

腐蚀、膨胀与开闭运算

小象实战讲义 · 基于深度学习的计算机视觉

在图像处理中,我们常常需要处理二值化后的图像,例如从医学影像中分割出的器官轮廓,或从场景中提取的文字区域。这些二值图像往往存在边界不平滑、内部有小孔洞或外部有孤立噪声点等问题。形态学运算,作为图像预处理领域的重要工具,能够在不依赖复杂知识的情况下,有效地“修复”这些图像,使其轮廓更光滑、结构更完整,为后续的特征提取或深度学习分析提供更干净的输入。

💡 核心导读

  • 形态学运算的定位:它是空间域图像处理中,基于“结构元素”对目标形状进行操作的邻域运算,与卷积运算有本质区别。
  • 腐蚀与膨胀:形态学的基础运算。腐蚀使目标区域“收缩”,消除边界点;膨胀使目标区域“扩张”,填补邻近区域。
  • 开运算与闭运算:腐蚀与膨胀的组合。开运算能消除小物体、平滑边界;闭运算能填充小孔洞、连接邻近物体。
  • 应用场景:常用于二值图像的后处理,平滑由阈值分割或深度学习模型产生的粗糙边界,去除噪声,连接断裂部分。

图像处理方法的分类

在深入形态学之前,我们先从宏观上了解图像预处理的方法体系。根据处理域的不同,图像处理主要分为两大类:

  1. 空间域处理:直接在图像像素构成的二维空间上进行操作。

    • 点运算:输出像素值仅取决于输入像素自身的值。典型例子是直方图均衡化(HE)。
    • 邻域运算:输出像素值取决于输入像素及其周围邻域像素的值。典型例子包括:
      • 形态学运算:即本节要讲的腐蚀、膨胀、开闭运算。它基于一个称为“结构元素”的模板,对目标的形状进行操作。
      • 卷积运算:如平滑滤波(高斯、均值)、边缘检测(Sobel、Laplacian)以及构建图像金字塔等。卷积核(模板)与图像对应位置进行加权求和。(中值滤波取邻域中位数,属非线性排序统计,不是加权求和型卷积。)
      • 自适应/分块均衡:如 CLAHE,需要借助局部窗口或分块统计,因此不属于点运算。
    • 虽然形态学和卷积都使用“模板”,但运算逻辑不同:形态学模板仅定义形状,用于集合运算;卷积模板则定义了具体的权重值,用于加权求和。
  2. 频率域处理:将图像从空间域转换到频率域(如通过傅里叶变换、小波变换),在频率域中进行滤波等操作,再转换回空间域。这相当于将图像视为不同频率的二维波形的叠加。

本节我们聚焦于空间域处理中的形态学运算。

形态学基础:结构元素、腐蚀与膨胀

形态学运算的核心是使用一个称为结构元素的小图像(模板)来探测或改变目标图像的结构。

结构元素

设有两幅图像 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(第 16 行、第 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]]

从结果可以清晰看到:

  1. 开运算去除了左上、右上和左下角的外部孤立噪声点,但 6×6 的矩形目标也被从中间“切断”,变成了两块 3×3 的区域——因为 3×3 结构元素相对目标偏大,孔洞附近的像素在腐蚀阶段被一并去掉了。
  2. 闭运算填补了内部的两个孔洞((5,5) 和 (6,6)),但外部噪声点不仅保留,还被膨胀放大了。
  3. 先开后闭在本例中没有恢复出一个完整矩形,仍是两块 3×3 区域。这说明:开闭运算的顺序、结构元素尺寸必须与目标尺度匹配;结构元素过大时,去噪的代价可能是把目标本身拆散。

重要注意事项:前景与背景的约定

形态学运算通常是针对图像中的前景物体进行的。这里有一个关键的实现细节需要特别注意:在二值图像中,哪个颜色代表前景(物体),哪个代表背景?

不同的软件和库对此有不同的默认约定:

  • OpenCV:形态学函数默认把非零像素(白色)视为前景,零像素(黑色)视为背景。本节代码正是遵循这一约定(用 1 表示前景)。
  • 部分传统示例(如某些 Visual C++ 代码):习惯把物体画成黑色(灰度值 0)、背景画成白色(255)。
  • MATLAB:二值形态学默认白色为前景、黑色为背景。

核心在于:在使用任何工具(如 cv2.erode, cv2.dilate)之前,必须确认它对前景/背景的约定;约定反了,腐蚀和膨胀的视觉效果就会与预期相反。

📝 动手练一练

  1. 概念辨析:形态学运算中的“结构元素”与卷积运算中的“卷积核”都是一种模板,它们在核心作用上有何本质区别?
👉 点击查看参考答案

结构元素:定义了一个用于进行集合论运算(如包含、交集)的形状模板。它本身的值通常为1(表示参与运算的点),运算时关注的是结构元素形状是否完全在目标内(腐蚀)或是否与目标有交集(膨胀)。原点位置可以是任意的。

卷积核:定义了一组用于进行加权求和运算的数值权重。运算时,核在图像上滑动,每个位置的输出是核内权重与对应图像像素值的乘积之和。原点通常固定在核的中心。

简言之,结构元素用于形状的探测与操作,而卷积核用于数值的加权融合与特征提取。

  1. 效果推演:假设有一幅二值图像,前景(白色)是一个单独的、孤立的像素点(即图像中只有一个像素值为1)。使用一个3×3的矩形结构元素(原点在中心)对其进行一次腐蚀操作和一次膨胀操作,结果图像分别是什么?为什么?
👉 点击查看参考答案
  • 腐蚀结果:图像将全部变为背景(黑色)。因为腐蚀要求结构元素完全包含在目标内。对于一个孤立的像素点,没有任何位置能让一个3×3的结构元素完全被它包含(它自己只有一个点)。因此,腐蚀后没有任何点满足条件,目标消失。
  • 膨胀结果:图像中将出现一个3×3的白色方块。因为膨胀要求结构元素与目标有交集。当结构元素原点与那个孤立像素点重合时,结构元素覆盖的3×3区域与该点有交集(就是该点本身)。因此,膨胀操作会将结构元素原点覆盖的所有位置(即那个3×3区域)都置为前景,形成一个3×3的块。

本章小结

本节我们深入学习了图像形态学处理的核心内容。形态学运算通过结构元素这一基本工具,对二值图像的目标形状进行腐蚀、膨胀及其组合操作,能够有效地平滑轮廓、去除噪声、连接断点、填充孔洞,是图像预处理和后处理中不可或缺的技术。

📋 行动清单

  • 使用 OpenCV 或 numpy 手动实现一次腐蚀和膨胀运算的模拟(不使用 cv2.erode/dilate),加深对集合运算本质的理解。
  • 找一张简单的二值图(或对灰度图进行阈值分割得到),分别应用腐蚀、膨胀、开运算、闭运算,观察并总结每种操作对图像边界、噪声、孔洞的具体影响。
  • 思考在深度学习图像分割任务(如 U-Net 输出)的后处理中,如何利用开闭运算来优化分割掩码(mask)的质量。

—— 小象教研组