计算机视觉处理(OpenCV基础教学(五):形态学变换(腐蚀与膨胀))

OpenCV基础教学(五):形态学变换(腐蚀与膨胀)

本文将深入讲解形态学变换的基本原理,重点介绍腐蚀和膨胀两种核心操作,并通过Python+OpenCV代码实战演示它们在图像处理中的应用。


一、形态学变换概述

什么是形态学变换?

形态学变换是一种基于形状的图像处理技术,主要应用于二值图像。它通过移动一个称为"核"或"结构化元素"的小窗口,对图像进行局部操作,从而提取图像中有用的形状信息。

形态学变换的组成部分
  1. 输入图像:必须是二值图像(0和255)
  2. 核(结构化元素):一个小的二进制矩阵,定义了操作的邻域形状
  3. 操作算法:定义了如何将核应用于图像的规则
基本操作:腐蚀与膨胀
  • 腐蚀(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)

核的形状对比

  • 矩形核:最简单的形状,适用于大多数场景
  • 椭圆形核:更平滑,适合处理圆形或椭圆形物体
  • 十字形核:各向异性,适合特定方向的操作
腐蚀操作的实际效果

腐蚀操作的主要作用:

  1. 去除小物体:孤立的小点或细线
  2. 分离物体:断开相连的物体
  3. 平滑边界:使边界更加整齐
  4. 缩小物体:整体尺寸减小

三、膨胀操作(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))
膨胀操作的实际效果

膨胀操作的主要作用:

  1. 填补空洞:填充物体内部的孔洞
  2. 连接物体:将相邻但分离的物体连接起来
  3. 扩大物体:整体尺寸增大
  4. 平滑边界:填充凹陷部分

四、代码实战:腐蚀与膨胀

基础实现代码
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))

作用

  1. 去除小物体(噪声)
  2. 平滑物体边界
  3. 断开细的连接
  4. 保留原始大小大致不变

代码实现

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))

作用

  1. 填补物体内部孔洞
  2. 连接邻近物体
  3. 平滑边界
  4. 保留原始大小大致不变

代码实现

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)
平滑物体边界 先开运算后闭运算 小到中型核
实际应用场景分析
  1. 医学图像处理
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
  1. 车牌字符分割
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
  1. 工业缺陷检测
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

📌 下期预告

下一期我们将学习图像的颜色识别


扩展思考

  1. 如何设计自适应核来适应不同形状的物体?
  2. 形态学操作在彩色图像上如何应用?
  3. 如何结合深度学习和形态学操作?

欢迎在评论区分享你的实践经验和遇到的问题!


如果觉得本文有帮助,欢迎点赞、收藏、关注!
你的支持是我持续创作的最大动力!

相关推荐
NAGNIP18 小时前
轻松搞懂全连接神经网络结构!
人工智能·算法·面试
moshuying19 小时前
别让AI焦虑,偷走你本该有的底气
前端·人工智能
董董灿是个攻城狮20 小时前
零基础带你用 AI 搞定命令行
人工智能
喝拿铁写前端1 天前
Dify 构建 FE 工作流:前端团队可复用 AI 工作流实战
前端·人工智能
阿里云大数据AI技术1 天前
阿里云 EMR Serverless Spark + DataWorks 技术实践:引领企业 Data+AI 一体化转型
人工智能
billhan20161 天前
MCP 深入理解:协议原理与自定义开发
人工智能
Jahzo1 天前
openclaw桌面端体验--ClawX
人工智能·github
billhan20161 天前
Agent 开发全流程:从概念到生产
人工智能
threerocks1 天前
过了个年,AI 圈变天了?但没人告诉你为什么
人工智能