形态学变换在图像处理中的应用

1. 引言

        形态学变换(Morphological Transformations) 是数字图像处理中的一组重要操作,主要用于图像预处理、特征提取和对象识别等领域。它是一种基于形状的简单变换,处理对象通常是二值化图像。

        形态学变换有两个输入(原图像、核(结构化元素)),一个输出(形态学变换后的图像)。其基本操作有腐蚀和膨胀,这两种操作是相反的,即较亮的像素会被腐蚀和膨胀。下面我们来说一下核、腐蚀与膨胀的概念。 本教案将详细介绍形态学变换的核心概念和常用操作。

2. 核(Kernel)

2.1 概念

        核(也称为结构元素)是形态学操作的基本工具,通常是一个小的奇数矩阵或形状(如矩形、椭圆形、十字形等如下图所示。通过不同的结构可以对不同特征的图像进行形态学操作(从左上角向右下角运算的处理。 )),用于探测图像中的特定结构。

2.2 核的性质

  • 大小:通常为3×3、5×5等奇数尺寸(因为要重新赋值给最中间的像素值‘锚点’,一定要是奇数)

  • 形状:矩形、椭圆形、十字形等

  • 锚点:核的中心点,默认为几何中心

2.3 代码示例 

import cv2
import numpy as np

# 创建矩形核
kernel_rect = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
# 创建椭圆形核
kernel_ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
# 创建十字形核
kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (5,5))

3. 腐蚀(Erosion)

3.1 基本概念

        腐蚀操作会"侵蚀"物体的边界,使得物体尺寸变小,可以消除小的噪声点。

        在图像处理的形态学变换中,腐蚀(Erosion)的操作逻辑可以直观理解为“用邻域内像素的最小值替代中心像素值”,但这种描述需要结合二值图像和灰度图像的具体场景来区分。

3.2 操作原理

3.2.1. 二值图像(黑白图像)

  • 定义:图像中像素值仅为 0(黑,背景)或 1(白,前景)。(就是一个全黑的图上有白色物体)

  • 腐蚀操作
    用结构元素(核)扫描图像,只有当核覆盖的所有像素都为 1 时,中心像素才保持为 1,否则变为 0

    • 效果:前景(白色)区域被“缩小”,边缘被侵蚀,孤立噪声点被消除。

    • 类比:确实可以理解为“用邻域内的最小值(0)替代中心像素值”,因为只要有一个 0,中心就会变成 0

示例

原始二值矩阵(1为前景):

        用核扫描图像的每一个像素,只有当核覆盖的所有像素都为1时,中心像素才保持为1,否则变为0。

                        

腐蚀后(若核为3×3)中心像素变为 0(因为邻域中存在 0)。

3.2.2. 灰度图像

  • 定义:像素值为连续的灰度值(如 0~255)。

  • 腐蚀操作
    用核覆盖邻域,取邻域内像素的最小值作为中心像素的新值

    • 效果:暗区域(低像素值)扩大,亮区域(高像素值)缩小。

    • 数学表达

示例

原始灰度矩阵(假设核为3×3):

                

腐蚀后中心像素值 = min(80, 150, 90, 70, 100, 130, ...) = 70

3.2.3. 对比膨胀(Dilation)

  • 膨胀是腐蚀的逆操作:

    • 二值图像:邻域中有一个 1 则中心变为 1

    • 灰度图像:取邻域最大值即“用邻域内高像素值替代中心低像素值”

3.3 应用场景

  • 消除小噪声

  • 分离相连物体

  • 边缘检测预处理

3.4 代码实现

import cv2
import numpy as np

# 灰度图像示例
gray_image = np.array([[100, 120, 110], [80, 150, 90], [70, 100, 130]], dtype=np.uint8)
kernel = np.ones((3,3), np.uint8)
eroded_gray = cv2.erode(gray_image, kernel)
print("灰度腐蚀结果:\n", eroded_gray)  # 中心值变为70

# 二值图像示例
binary_image = np.array([[1, 1, 1], [1, 1, 1], [1, 0, 1]], dtype=np.uint8)
eroded_binary = cv2.erode(binary_image, kernel)
print("二值腐蚀结果:\n", eroded_binary)  # 中心值变为0

总结

  • 腐蚀的本质:用邻域内最小像素值替代中心像素值。

  • 二值图像:表现为“前景缩小”,逻辑是“与运算”。

  • 灰度图像:直接表现为“暗区扩张”,数学上是最小值滤波。

4. 膨胀(Dilation)

4.1 基本概念

        膨胀操作会"扩张"物体的边界,使得物体尺寸变大,可以填补物体中的空洞。

        在形态学变换中,膨胀的操作逻辑可直观理解为“用邻域内像素的最大值替代中心像素值”,具体表现需区分二值图像和灰度图像。

4.2 操作原理

4.2.1 二值图像(黑白图像)

定义:像素值仅为 0(黑,背景)或 1(白,前景)。

膨胀操作
用结构元素(核)扫描图像,只要核覆盖的邻域内至少有一个像素为 1,中心像素即变为 1,否则保持为 0。

效果

  • 前景(白色)区域向外扩展,边缘被扩张。

  • 断裂部分可能被连接,小孔洞被填充。

类比
可理解为“用邻域内的最大值(1)替代中心像素值”,因为只要有一个 1,中心就会变成 1。

示例
原始二值矩阵(核为3×3):

                

膨胀后中心像素变为 1(邻域中存在 1)。

4.2.2 灰度图像

定义:像素值为连续灰度值(如 0~255)。

膨胀操作
用核覆盖邻域,取邻域内像素的最大值作为中心像素的新值。

效果

  • 亮区域(高像素值)向外扩张,暗区域(低像素值)被压缩。

数学表达

示例
原始灰度矩阵(核为3×3):

膨胀后中心像素值 = max(70, 100, 130, 60, 110, 50, ...) = 130

4.2.3 对比腐蚀(Erosion)
  • 腐蚀:取邻域最小值,导致物体缩小。

  • 膨胀:取邻域最大值,导致物体扩大。

4.3 应用场景

  1. 填补空洞或裂缝(如断裂的文字笔画修复)。

  2. 连接相邻物体(如分离的细胞图像合并)。

  3. 边缘增强(先腐蚀后膨胀可突出边界)。

4.4 代码实现

import cv2  
import numpy as np  

# 灰度图像示例  
gray_image = np.array([[80, 120, 90], [70, 100, 130], [60, 110, 50]], dtype=np.uint8)  
kernel = np.ones((3,3), np.uint8)  
dilated_gray = cv2.dilate(gray_image, kernel)  
print("灰度膨胀结果:\n", dilated_gray)  # 中心值变为130  

# 二值图像示例  
binary_image = np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]], dtype=np.uint8)  
dilated_binary = cv2.dilate(binary_image, kernel)  
print("二值膨胀结果:\n", dilated_binary)  # 中心值变为1  

总结
  • 本质:用邻域内最大像素值替代中心像素值。

  • 二值图像:表现为“前景扩大”,逻辑是“或运算”。

  • 灰度图像:直接表现为“亮区扩张”,数学上是最大值滤波。

5. 开运算(Opening)

5.1 基本概念

开运算是先腐蚀后膨胀的过程,可以消除小的物体或噪声,而不影响原来的图像 。

5.2 数学表达式

开运算 = 膨胀(腐蚀(图像))

5.3 应用场景

  • 去除小噪声点(如二值图像中的白点噪声)。

  • 平滑物体边界(如分割后的不规则物体)。

  • 断开细连接(如OCR中粘连字符的分离)。

5.4 代码实现

5.4.1. 基于 OpenCV 的实现(推荐)

import cv2
import numpy as np

def opening_with_opencv(image, kernel_size=(3, 3)):
    """
    使用 OpenCV 实现开运算
    :param image: 输入图像(二值或灰度)
    :param kernel_size: 结构元素大小,如 (3,3)
    :return: 开运算结果
    """
    kernel = np.ones(kernel_size, np.uint8)  # 矩形结构元素
    eroded = cv2.erode(image, kernel)        # 1. 先腐蚀
    opened = cv2.dilate(eroded, kernel)      # 2. 再膨胀
    return opened

# 示例用法
if __name__ == "__main__":
    # 二值图像示例
    binary_image = np.array([
        [0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 0, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 0, 0, 0, 0]
    ], dtype=np.uint8) * 255  # 转换为0/255格式

    opened_binary = opening_with_opencv(binary_image)
    print("二值开运算结果:\n", opened_binary // 255)  # 还原为0/1

    # 灰度图像示例
    gray_image = np.array([
        [100, 200, 100],
        [200,  50, 200],
        [100, 200, 100]
    ], dtype=np.uint8)

    opened_gray = opening_with_opencv(gray_image)
    print("灰度开运算结果:\n", opened_gray)

 5.4.2 纯 NumPy 实现(无 OpenCV 依赖)

import numpy as np

def erosion(image, kernel):
    """腐蚀操作"""
    h, w = image.shape
    kh, kw = kernel.shape
    pad_h, pad_w = kh // 2, kw // 2
    padded = np.pad(image, ((pad_h, pad_h), (pad_w, pad_w)), mode='constant')
    result = np.zeros_like(image)

    for i in range(h):
        for j in range(w):
            region = padded[i:i+kh, j:j+kw]
            result[i, j] = np.min(region * kernel)  # 对二值/灰度均适用
    return result

def dilation(image, kernel):
    """膨胀操作"""
    h, w = image.shape
    kh, kw = kernel.shape
    pad_h, pad_w = kh // 2, kw // 2
    padded = np.pad(image, ((pad_h, pad_h), (pad_w, pad_w)), mode='constant')
    result = np.zeros_like(image)

    for i in range(h):
        for j in range(w):
            region = padded[i:i+kh, j:j+kw]
            result[i, j] = np.max(region * kernel)
    return result

def opening_with_numpy(image, kernel_size=(3, 3)):
    """纯NumPy实现开运算"""
    kernel = np.ones(kernel_size, dtype=np.uint8)
    eroded = erosion(image, kernel)
    opened = dilation(eroded, kernel)
    return opened

# 示例用法
if __name__ == "__main__":
    binary_image = np.array([
        [0, 1, 0],
        [1, 0, 1],
        [0, 1, 0]
    ], dtype=np.uint8)

    opened_binary = opening_with_numpy(binary_image)
    print("二值开运算结果(NumPy):\n", opened_binary)

 可以通过自定义 kernel 控制开运算的效果:

# 使用十字形结构元素
cross_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
opened_cross = cv2.morphologyEx(image, cv2.MORPH_OPEN, cross_kernel)

总结

  • OpenCV 实现:直接使用 cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel) 更高效。

  • 手动实现:理解腐蚀和膨胀的逐像素操作逻辑。

  • 开运算 = 腐蚀 + 膨胀,适合去噪且保留主体形状。

6. 闭运算(Closing)

6.1 基本概念

闭运算是先膨胀后腐蚀的过程,可以填补物体中的小洞。

6.2 数学表达式

闭运算 = 腐蚀(膨胀(图像))

6.3 应用场景

  • 填充孔洞(如二值图像中的黑色小孔)。

  • 连接断裂部分(如OCR中笔画断裂的修复)。

  • 平滑亮区域边界(如医学图像中的细胞轮廓)。

6.4 代码实现

6.4.1 基于 OpenCV 的实现(推荐)

import cv2
import numpy as np

def closing_with_opencv(image, kernel_size=(3, 3)):
    """
    使用 OpenCV 实现闭运算
    :param image: 输入图像(二值或灰度)
    :param kernel_size: 结构元素大小,如 (3,3)
    :return: 闭运算结果
    """
    kernel = np.ones(kernel_size, np.uint8)  # 矩形结构元素
    dilated = cv2.dilate(image, kernel)      # 1. 先膨胀
    closed = cv2.erode(dilated, kernel)      # 2. 再腐蚀
    return closed

# 示例用法
if __name__ == "__main__":
    # 二值图像示例(带孔洞)
    binary_image = np.array([
        [1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1],
        [1, 0, 0, 0, 1],
        [1, 0, 0, 0, 1],
        [1, 1, 1, 1, 1]
    ], dtype=np.uint8) * 255  # 转换为0/255格式

    closed_binary = closing_with_opencv(binary_image)
    print("二值闭运算结果:\n", closed_binary // 255)  # 还原为0/1

    # 灰度图像示例(暗区域断裂)
    gray_image = np.array([
        [200, 200, 200],
        [200,  50, 200],
        [200, 200, 200]
    ], dtype=np.uint8)

    closed_gray = closing_with_opencv(gray_image)
    print("灰度闭运算结果:\n", closed_gray)

 6.4.2. 纯 NumPy 实现(无 OpenCV 依赖

import numpy as np

def dilation(image, kernel):
    """膨胀操作(与开运算示例相同)"""
    h, w = image.shape
    kh, kw = kernel.shape
    pad_h, pad_w = kh // 2, kw // 2
    padded = np.pad(image, ((pad_h, pad_h), (pad_w, pad_w)), mode='constant')
    result = np.zeros_like(image)

    for i in range(h):
        for j in range(w):
            region = padded[i:i+kh, j:j+kw]
            result[i, j] = np.max(region * kernel)
    return result

def erosion(image, kernel):
    """腐蚀操作(与开运算示例相同)"""
    h, w = image.shape
    kh, kw = kernel.shape
    pad_h, pad_w = kh // 2, kw // 2
    padded = np.pad(image, ((pad_h, pad_h), (pad_w, pad_w)), mode='constant')
    result = np.zeros_like(image)

    for i in range(h):
        for j in range(w):
            region = padded[i:i+kh, j:j+kw]
            result[i, j] = np.min(region * kernel)
    return result

def closing_with_numpy(image, kernel_size=(3, 3)):
    """纯NumPy实现闭运算"""
    kernel = np.ones(kernel_size, dtype=np.uint8)
    dilated = dilation(image, kernel)  # 先膨胀
    closed = erosion(dilated, kernel)  # 后腐蚀
    return closed

# 示例用法
if __name__ == "__main__":
    # 二值图像(孔洞填充)
    binary_image = np.array([
        [1, 1, 1],
        [1, 0, 1],
        [1, 1, 1]
    ], dtype=np.uint8)

    closed_binary = closing_with_numpy(binary_image)
    print("二值闭运算结果(NumPy):\n", closed_binary)

 可通过自定义 kernel 调整闭运算效果:

# 使用椭圆形结构元素
ellipse_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
closed_ellipse = cv2.morphologyEx(image, cv2.MORPH_CLOSE, ellipse_kernel)

总结

  • OpenCV 实现:直接调用 cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel) 更高效。

  • 手动实现:需先膨胀后腐蚀,顺序不可颠倒。

  • 闭运算 = 膨胀 + 腐蚀,适合补洞且保持主体形状。

7. 礼帽运算(Top Hat)

7.1 基本概念

礼帽运算是原图像与开运算结果的差,可以突出比周围亮的区域。

7.2 数学表达式

礼帽 = 原图 - 开运算

7.3 应用场景

  • 背景校正

  • 提取比背景亮的细小物体(如白噪声、亮斑、毛发)。

  • 增强局部对比度(如显微图像中的细胞细节)。

直观理解
  • 开运算会消除亮的小物体,因此用原图减去开运算结果,相当于“找回”被开运算删除的亮区域。

  • 类似“从背景中筛出亮杂质”。

示例场景
  • 检测PCB板上的白色焊点缺陷(背景为暗色)。

  • 增强指纹图像中的脊线细节。

7.4 代码实现

tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel_rect)

8. 黑帽运算(Black Hat)

8.1 基本概念

黑帽运算是闭运算结果与原图像的差,可以突出比周围暗的区域。

8.2 数学表达式

黑帽 = 闭运算 - 原图

8.3 应用场景

  • 提取比背景暗的细小物体(如孔洞、裂纹、暗斑)。

  • 检测阴影或凹陷区域(如金属表面的划痕)。

  • 增强局部对比度

直观理解
  • 闭运算会填充暗的小孔洞,因此用闭运算结果减去原图,相当于“找回”被闭运算填充的暗区域。

  • 类似“从背景中挖出暗缺陷”。

示例场景
  • 检测轮胎图像中的裂纹(暗色)。

  • 识别医学图像中的血管或腔隙(暗区域)。

8.4 代码实现

blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel_rect)

小贴士. 记忆技巧

  • 礼帽 = 亮礼帽:像一顶白色礼帽,提取“亮”的东西。

  • 黑帽 = 黑洞:像黑色深渊,提取“暗”的东西。

  • 开运算删亮留暗 → 礼帽找回亮的
    闭运算填暗留亮 → 黑帽找回暗的

import cv2
import numpy as np

# 生成示例图像(亮斑+暗洞)
image = np.zeros((100, 100), dtype=np.uint8)
image[20:30, 20:30] = 255  # 亮方块(模拟亮斑)
image[60:70, 60:70] = 50   # 暗方块(模拟孔洞)
kernel = np.ones((15,15), np.uint8)

# 礼帽和黑帽运算
tophat = cv2.morphologyEx(image, cv2.MORPH_TOPHAT, kernel)
blackhat = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)

# 可视化
cv2.imshow("Original", image)
cv2.imshow("Top-Hat (Bright Spots)", tophat)
cv2.imshow("Black-Hat (Dark Holes)", blackhat)
cv2.waitKey(0)

 

9. 形态学梯度(Morphological Gradient)

9.1 基本概念

形态学梯度是膨胀图像与腐蚀图像的差,可以得到物体的边界,并且对噪声有一定的抑制作用 。

9.2 数学表达式

梯度 = 膨胀 - 腐蚀

 直观理解

  • 膨胀:将物体边缘向外扩展(亮区域变大)。

  • 腐蚀:将物体边缘向内收缩(亮区域变小)。

  • 梯度:两者的差值即为边缘区域,即物体内外边界的变化部分。

类比
想象用粉笔画一个圆圈:

  • 膨胀:用粗笔重描一圈,线条变粗。

  • 腐蚀:用橡皮擦掉外圈,线条变细。

  • 梯度:两者差异部分就是原始的“线条边缘”。

9.3 数学意义

  • 对于二值图像:梯度提取的是物体的轮廓像素(即前景与背景的交界处)。

  • 对于灰度图像:梯度反映的是局部灰度变化率(类似Sobel算子,但更强调结构)。

 

9.4 典型应用

  1. 目标轮廓提取

    • 在二值图像中快速获取物体的外边界。

  2. 医学图像边缘增强

    • 突出器官或细胞的边界(如X光片中的骨骼轮廓)。

  3. 工业检测

    • 检测零件边缘缺陷(如裂纹或毛刺)。

9.4 代码实现

gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel_rect)

总结

形态学梯度的核心思想是:
通过膨胀与腐蚀的差异,直接量化局部区域的“扩张程度”,从而突出边界。它的优势在于计算简单、抗噪性好,尤其适合需要快速提取结构化边缘的场景。

10. 综合应用示例

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('example.jpg', 0)

# 创建核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

# 应用各种形态学操作
erosion = cv2.erode(img, kernel, iterations=1)
dilation = cv2.dilate(img, kernel, iterations=1)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

# 显示结果
titles = ['Original', 'Erosion', 'Dilation', 'Opening', 'Closing',  'Tophat', 'Blackhat','Gradient']
images = [img, erosion, dilation, opening, closing,  tophat, blackhat, gradient]

plt.figure(figsize=(15,10))
for i in range(8):
    plt.subplot(2,4,i+1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

11. 总结

        形态学变换是图像处理中强大的工具,通过不同的组合可以实现多种图像处理效果:

  • 腐蚀和膨胀是基础操作

  • 开运算(先腐蚀后膨胀)和闭运算(先膨胀后腐蚀)是腐蚀和膨胀的组合

  • 礼帽(亮:原图-开运算)和黑帽(暗:闭运算-原图)运算用于特定特征的提取

  • 形态学梯度(边缘信息:膨胀-腐蚀)用于边缘检测

        在实际应用中,需要根据具体问题选择合适的操作和核的形状、大小,并通过调整迭代次数来获得最佳效果。

12. 课后练习

  1. 尝试不同的核形状和大小,观察对形态学操作结果的影响

  2. 组合使用多种形态学操作,解决特定的图像处理问题

  3. 比较形态学梯度与传统边缘检测算法(如Sobel、Canny)的效果差异

  4. 设计一个流程,使用形态学操作从复杂背景中提取特定形状的物体

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值