计算机视觉处理(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. 如何结合深度学习和形态学操作?

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


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

相关推荐
檐下翻书1732 分钟前
免费在线工艺流程图制作工具_生产/化工/食品工艺流程绘制模板
人工智能·金融·架构·流程图·论文笔记·pcb工艺
GuoDongOrange12 分钟前
从 0 到 1 构建 AI 智能体——AI Agent 的工程化路径、行业范式与未来形态
人工智能·ai agent·智能体·智能体从0到1·从0到1构建智能体
极智-99613 分钟前
GitHub 热榜项目-日榜精选(2026-01-24)| AI智能体工具、Python生态等 | remotion、VibeVoice、goose等
人工智能·python·github·ai智能体·大模型部署·语音ai
翱翔的苍鹰19 分钟前
完整的“RNN + jieba 中文情感分析”项目之一:终极版
人工智能·rnn·深度学习
徐小夕@趣谈前端25 分钟前
NO-CRM 2.0正式上线,Vue3+Echarts+NestJS实现的全栈CRM系统,用AI重新定义和实现客户管理系统
前端·javascript·人工智能·开源·编辑器·echarts
北鹤M33 分钟前
用MeteoStat计算任意时刻经纬度真实气象数据
人工智能·python
星瞳科技OpenMV36 分钟前
星瞳OpenMV官方机械臂教程|从零开始:Robot Arm机械臂快速上手
arm开发·图像处理·python·计算机视觉·ai·机器人·openmv
小王努力学编程1 小时前
LangChain——AI应用开发框架(核心组件1)
linux·服务器·前端·数据库·c++·人工智能·langchain
庄小焱1 小时前
【机器学习】——房屋销售价格预测实战
人工智能·算法·机器学习·预测模型
人工智能AI技术1 小时前
【Agent从入门到实践】29 开发第一个Agent——需求定义
人工智能·python