OpenCV基础教学(五):形态学变换(腐蚀与膨胀)
本文将深入讲解形态学变换的基本原理,重点介绍腐蚀和膨胀两种核心操作,并通过Python+OpenCV代码实战演示它们在图像处理中的应用。
一、形态学变换概述
什么是形态学变换?
形态学变换是一种基于形状的图像处理技术,主要应用于二值图像。它通过移动一个称为"核"或"结构化元素"的小窗口,对图像进行局部操作,从而提取图像中有用的形状信息。
形态学变换的组成部分
- 输入图像:必须是二值图像(0和255)
- 核(结构化元素):一个小的二进制矩阵,定义了操作的邻域形状
- 操作算法:定义了如何将核应用于图像的规则
基本操作:腐蚀与膨胀
- 腐蚀(Erosion):使物体边界收缩,去除小物体
- 膨胀(Dilation):使物体边界扩张,填补空洞
二、腐蚀操作(Erosion)详解

腐蚀操作的数学原理
腐蚀操作的数学定义是集合论中的平移 和交集操作。对于图像A和核B,腐蚀定义为:
A ⊖ B = {z | (B)_z ⊆ A}
其中(B)_z表示核B平移到位置z。
简单理解 :只有当核完全覆盖在物体内部时,中心点才保留为物体点。

核(结构化元素)的类型
OpenCV提供了三种基本的核类型:
python
import cv2
import numpy as np
# 1. 矩形核(MORPH_RECT)
kernel_rect = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
print("矩形核(5×5):")
print(kernel_rect)
# 2. 椭圆形核(MORPH_ELLIPSE)
kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
print("\n椭圆形核(5×5):")
print(kernel_ellipse)
# 3. 十字形核(MORPH_CROSS)
kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
print("\n十字形核(5×5):")
print(kernel_cross)
核的形状对比:
- 矩形核:最简单的形状,适用于大多数场景
- 椭圆形核:更平滑,适合处理圆形或椭圆形物体
- 十字形核:各向异性,适合特定方向的操作
腐蚀操作的实际效果
腐蚀操作的主要作用:
- 去除小物体:孤立的小点或细线
- 分离物体:断开相连的物体
- 平滑边界:使边界更加整齐
- 缩小物体:整体尺寸减小
三、膨胀操作(Dilation)详解

膨胀操作的数学原理
膨胀操作的数学定义也是基于集合论:
A ⊕ B = {z | (B)_z ∩ A ≠ ∅}
其中(B)_z表示核B平移到位置z。
简单理解 :只要核与物体有重叠,中心点就变为物体点。

核的大小与形状影响
核的大小直接影响膨胀的程度:
- 小核:精细的膨胀,保持细节
- 大核:强烈的膨胀,可能连接分离的物体
python
# 不同大小的核对比
kernel_small = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
kernel_medium = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
kernel_large = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
膨胀操作的实际效果
膨胀操作的主要作用:
- 填补空洞:填充物体内部的孔洞
- 连接物体:将相邻但分离的物体连接起来
- 扩大物体:整体尺寸增大
- 平滑边界:填充凹陷部分
四、代码实战:腐蚀与膨胀
基础实现代码
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取并预处理图像
image_np = cv2.imread('./test.png')
image_gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)
# 2. 二值化图像
ret, image_thresh = cv2.threshold(image_gray, 127, 255, cv2.THRESH_BINARY)
# 3. 腐蚀操作
kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (9, 9))
image_erode = cv2.erode(image_thresh, kernel_cross)
# 4. 膨胀操作
kernel_rect = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
image_dilate = cv2.dilate(image_thresh, kernel_rect)
不同核类型的对比实验
python
# 对比不同核类型的腐蚀效果
def compare_kernels_erosion(image, size=5):
"""对比不同核类型的腐蚀效果"""
kernels = [
('MORPH_RECT', cv2.MORPH_RECT),
('MORPH_ELLIPSE', cv2.MORPH_ELLIPSE),
('MORPH_CROSS', cv2.MORPH_CROSS)
]
plt.figure(figsize=(15, 5))
plt.subplot(1, 4, 1)
plt.imshow(image, cmap='gray')
plt.title('Original Binary')
plt.axis('off')
for idx, (name, kernel_type) in enumerate(kernels, 2):
kernel = cv2.getStructuringElement(kernel_type, (size, size))
result = cv2.erode(image, kernel)
plt.subplot(1, 4, idx)
plt.imshow(result, cmap='gray')
plt.title(f'Erosion: {name}')
plt.axis('off')
plt.tight_layout()
plt.show()
# 对比不同核类型的膨胀效果
def compare_kernels_dilation(image, size=5):
"""对比不同核类型的膨胀效果"""
kernels = [
('MORPH_RECT', cv2.MORPH_RECT),
('MORPH_ELLIPSE', cv2.MORPH_ELLIPSE),
('MORPH_CROSS', cv2.MORPH_CROSS)
]
plt.figure(figsize=(15, 5))
plt.subplot(1, 4, 1)
plt.imshow(image, cmap='gray')
plt.title('Original Binary')
plt.axis('off')
for idx, (name, kernel_type) in enumerate(kernels, 2):
kernel = cv2.getStructuringElement(kernel_type, (size, size))
result = cv2.dilate(image, kernel)
plt.subplot(1, 4, idx)
plt.imshow(result, cmap='gray')
plt.title(f'Dilation: {name}')
plt.axis('off')
plt.tight_layout()
plt.show()
# 运行对比实验
compare_kernels_erosion(image_thresh, 9)
compare_kernels_dilation(image_thresh, 7)
核大小对效果的影响
python
# 分析核大小对腐蚀效果的影响
def erosion_size_analysis(image, sizes=[3, 5, 9, 15]):
"""分析核大小对腐蚀效果的影响"""
plt.figure(figsize=(15, 10))
plt.subplot(2, 3, 1)
plt.imshow(image, cmap='gray')
plt.title('Original Binary')
plt.axis('off')
for idx, size in enumerate(sizes, 2):
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (size, size))
result = cv2.erode(image, kernel)
plt.subplot(2, 3, idx)
plt.imshow(result, cmap='gray')
plt.title(f'Erosion: {size}×{size}')
plt.axis('off')
plt.tight_layout()
plt.show()
# 分析核大小对膨胀效果的影响
def dilation_size_analysis(image, sizes=[3, 5, 9, 15]):
"""分析核大小对膨胀效果的影响"""
plt.figure(figsize=(15, 10))
plt.subplot(2, 3, 1)
plt.imshow(image, cmap='gray')
plt.title('Original Binary')
plt.axis('off')
for idx, size in enumerate(sizes, 2):
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (size, size))
result = cv2.dilate(image, kernel)
plt.subplot(2, 3, idx)
plt.imshow(result, cmap='gray')
plt.title(f'Dilation: {size}×{size}')
plt.axis('off')
plt.tight_layout()
plt.show()
# 运行分析
erosion_size_analysis(image_thresh)
dilation_size_analysis(image_thresh)
腐蚀与膨胀的联合应用
python
# 腐蚀后膨胀(开运算)
def opening_operation(image, kernel_size=5):
"""开运算:先腐蚀后膨胀"""
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size))
# 先腐蚀
eroded = cv2.erode(image, kernel)
# 再膨胀
opened = cv2.dilate(eroded, kernel)
return opened
# 膨胀后腐蚀(闭运算)
def closing_operation(image, kernel_size=5):
"""闭运算:先膨胀后腐蚀"""
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size))
# 先膨胀
dilated = cv2.dilate(image, kernel)
# 再腐蚀
closed = cv2.erode(dilated, kernel)
return closed
# 应用示例
image_opened = opening_operation(image_thresh, 5)
image_closed = closing_operation(image_thresh, 5)
# 可视化对比
plt.figure(figsize=(15, 5))
plt.subplot(1, 4, 1)
plt.imshow(image_thresh, cmap='gray')
plt.title('Original Binary')
plt.axis('off')
plt.subplot(1, 4, 2)
plt.imshow(image_erode, cmap='gray')
plt.title('Erosion Only')
plt.axis('off')
plt.subplot(1, 4, 3)
plt.imshow(image_opened, cmap='gray')
plt.title('Opening (Erode→Dilate)')
plt.axis('off')
plt.subplot(1, 4, 4)
plt.imshow(image_closed, cmap='gray')
plt.title('Closing (Dilate→Erode)')
plt.axis('off')
plt.tight_layout()
plt.show()
五、高级形态学操作
开运算(Opening)
定义 :先腐蚀后膨胀
公式:Opening(A) = Dilation(Erosion(A))
作用:
- 去除小物体(噪声)
- 平滑物体边界
- 断开细的连接
- 保留原始大小大致不变
代码实现:
python
# 使用OpenCV内置函数
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
opened = cv2.morphologyEx(image_thresh, cv2.MORPH_OPEN, kernel)
闭运算(Closing)
定义 :先膨胀后腐蚀
公式:Closing(A) = Erosion(Dilation(A))
作用:
- 填补物体内部孔洞
- 连接邻近物体
- 平滑边界
- 保留原始大小大致不变
代码实现:
python
# 使用OpenCV内置函数
closed = cv2.morphologyEx(image_thresh, cv2.MORPH_CLOSE, kernel)
形态学梯度
定义 :膨胀结果减去腐蚀结果
公式:Gradient(A) = Dilation(A) - Erosion(A)
作用:提取物体边界
代码实现:
python
# 形态学梯度
gradient = cv2.morphologyEx(image_thresh, cv2.MORPH_GRADIENT, kernel)
# 可视化所有高级操作
plt.figure(figsize=(15, 10))
operations = [
('Original', image_thresh),
('Erosion', cv2.erode(image_thresh, kernel)),
('Dilation', cv2.dilate(image_thresh, kernel)),
('Opening', cv2.morphologyEx(image_thresh, cv2.MORPH_OPEN, kernel)),
('Closing', cv2.morphologyEx(image_thresh, cv2.MORPH_CLOSE, kernel)),
('Gradient', cv2.morphologyEx(image_thresh, cv2.MORPH_GRADIENT, kernel))
]
for idx, (name, img) in enumerate(operations, 1):
plt.subplot(2, 3, idx)
plt.imshow(img, cmap='gray')
plt.title(name)
plt.axis('off')
plt.tight_layout()
plt.show()
六、总结与应用建议
如何选择合适的形态学操作?
| 问题 | 推荐操作 | 参数建议 |
|---|---|---|
| 去除小噪声点 | 开运算 | 小核(3×3或5×5) |
| 填补内部孔洞 | 闭运算 | 小到中型核 |
| 分离相连物体 | 腐蚀 | 根据连接处大小选择核 |
| 连接分离物体 | 膨胀 | 根据间隙大小选择核 |
| 提取物体边界 | 形态学梯度 | 小核(3×3) |
| 平滑物体边界 | 先开运算后闭运算 | 小到中型核 |
实际应用场景分析
- 医学图像处理
python
def medical_image_processing(image_path):
"""医学图像处理:提取细胞边界"""
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 去除小噪声
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
cleaned = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# 提取边界
gradient = cv2.morphologyEx(cleaned, cv2.MORPH_GRADIENT, kernel)
return binary, cleaned, gradient
- 车牌字符分割
python
def license_plate_segmentation(plate_image):
"""车牌字符分割"""
# 转换为灰度并二值化
gray = cv2.cvtColor(plate_image, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 膨胀连接字符内部的断点
kernel_horizontal = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 1))
dilated = cv2.dilate(binary, kernel_horizontal)
# 腐蚀分离字符
kernel_vertical = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 5))
eroded = cv2.erode(dilated, kernel_vertical)
return binary, dilated, eroded
- 工业缺陷检测
python
def defect_detection(product_image):
"""工业产品缺陷检测"""
gray = cv2.cvtColor(product_image, cv2.COLOR_BGR2GRAY)
# 使用自适应阈值处理光照不均
binary = cv2.adaptiveThreshold(gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2)
# 去除小噪声
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
# 膨胀连接缺陷区域
dilated = cv2.dilate(opened, kernel, iterations=2)
return binary, opened, dilated
📌 下期预告
下一期我们将学习图像的颜色识别
扩展思考
- 如何设计自适应核来适应不同形状的物体?
- 形态学操作在彩色图像上如何应用?
- 如何结合深度学习和形态学操作?
欢迎在评论区分享你的实践经验和遇到的问题!
如果觉得本文有帮助,欢迎点赞、收藏、关注!
你的支持是我持续创作的最大动力!