深入理解膨胀的原理、实现与应用
膨胀是数学形态学的基本操作之一,主要用于扩大图像中的前景对象,常用于填补对象中的空洞或连接相近的对象。
膨胀是数学形态学中与腐蚀相对的基本操作。它使用一个结构元素(也称为核或模板)在图像上滑动,并执行最大值操作。对于二值图像,膨胀操作的定义如下:
| 结构元素 | 效果 | 应用 |
|---|---|---|
| 3×3 方形 | 轻微膨胀 | 填充小孔 |
| 5×5 方形 | 中度膨胀 | 连接物体 |
| 3×3 十字 | 方向性膨胀 | 保留特定方向 |
对于图像A和结构元素B,A被B膨胀定义为:
A ⊕ B = {z | (B^z) ∩ A ≠ Ø}
直观地说,膨胀操作检查结构元素B在图像A中的每一个位置,如果B与A有任何重叠,那么B的中心位置就被设置为前景。这会导致前景对象(白色区域)变大。
对于灰度图像,膨胀操作定义为:
(f ⊕ b)(x,y) = max{f(x-s, y-t) + b(s,t) | (s,t) ∈ B}
import cv2
import numpy as np
import matplotlib.pyplot as plt
def dilation_builtin(image_path, kernel_size=3, iterations=1):
"""
使用OpenCV内置函数实现膨胀
:param image_path: 输入图像路径
:param kernel_size: 结构元素大小
:param iterations: 膨胀次数
:return: 原图和膨胀后的图像
"""
# 读取图像
img = cv2.imread(image_path)
# 创建结构元素
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_size, kernel_size))
# 应用膨胀
dilated = cv2.dilate(img, kernel, iterations=iterations)
return img, dilated
def manual_dilation_binary(image_path, kernel_size=3):
"""
手动实现二值图像膨胀
:param image_path: 输入图像路径
:param kernel_size: 结构元素大小
:return: 膨胀后的二值图像
"""
# 读取图像并转换为二值图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 二值化图像
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
height, width = binary.shape
pad_size = kernel_size // 2
# 对图像进行填充
padded = np.pad(binary, pad_size, mode='constant', constant_values=0)
# 创建输出图像
dilated = np.zeros_like(binary)
# 应用膨胀操作
for i in range(height):
for j in range(width):
# 提取当前窗口
window = padded[i:i+kernel_size, j:j+kernel_size]
# 检查窗口中的最大值
if np.max(window) == 255: # 如果窗口中存在白色像素,输出就是白色
dilated[i, j] = 255
else:
dilated[i, j] = 0
return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), cv2.cvtColor(dilated, cv2.COLOR_GRAY2BGR)
def manual_dilation_grayscale(image_path, kernel_size=3):
"""
手动实现灰度图像膨胀
:param image_path: 输入图像路径
:param kernel_size: 结构元素大小
:return: 膨胀后的灰度图像
"""
# 读取图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
height, width = img.shape
pad_size = kernel_size // 2
# 对图像进行填充(使用最小值填充以避免边界效应)
padded = np.pad(img, pad_size, mode='edge')
# 创建输出图像
dilated = np.zeros_like(img)
# 应用膨胀操作
for i in range(height):
for j in range(width):
# 提取当前窗口
window = padded[i:i+kernel_size, j:j+kernel_size]
# 计算窗口中的最大值
dilated[i, j] = np.max(window)
return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), cv2.cvtColor(dilated, cv2.COLOR_GRAY2BGR)
def dilation_with_different_shapes(image_path):
"""
使用不同形状的结构元素进行膨胀
:param image_path: 输入图像路径
:return: 原图和不同形状结构元素的膨胀结果
"""
# 读取图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 二值化图像
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 创建不同形状的结构元素
kernels = {
'Rectangle': cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)),
'Ellipse': cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)),
'Cross': cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
}
results = {}
for name, kernel in kernels.items():
dilated = cv2.dilate(binary, kernel, iterations=1)
results[name] = cv2.cvtColor(dilated, cv2.COLOR_GRAY2BGR)
return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), results
def gap_filling_dilation(image_path):
"""
使用膨胀填补缝隙
:param image_path: 输入图像路径
:return: 原图、含缝隙图像和填补缝隙后的图像
"""
# 读取图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 二值化图像
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 创建含缝隙的图像
# 通过先腐蚀再膨胀制造缝隙
kernel_small = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
kernel_large = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
# 先腐蚀使对象变小,再膨胀使对象变大,但留下缝隙
eroded = cv2.erode(binary, kernel_small, iterations=2)
dilated = cv2.dilate(eroded, kernel_large, iterations=1)
# 使用膨胀填补缝隙
filled = cv2.dilate(dilated, kernel_small, iterations=3)
return (cv2.cvtColor(img, cv2.COLOR_GRAY2BGR),
cv2.cvtColor(dilated, cv2.COLOR_GRAY2BGR),
cv2.cvtColor(filled, cv2.COLOR_GRAY2BGR))
def object_connection_dilation(image_path):
"""
使用膨胀连接分离的对象
:param image_path: 输入图像路径
:return: 原图、分离对象图像和连接对象后的图像
"""
# 读取图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 二值化图像
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 创建分离的对象(通过在对象之间制造间隙)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# 先腐蚀分离对象
separated = cv2.erode(binary, kernel, iterations=2)
# 使用膨胀连接对象
connected = cv2.dilate(separated, kernel, iterations=3)
return (cv2.cvtColor(img, cv2.COLOR_GRAY2BGR),
cv2.cvtColor(separated, cv2.COLOR_GRAY2BGR),
cv2.cvtColor(connected, cv2.COLOR_GRAY2BGR))
def directional_dilation(image_path):
"""
方向性膨胀 - 使用不同方向的结构元素
:param image_path: 输入图像路径
:return: 原图和不同方向膨胀的结果
"""
# 读取图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 二值化图像
_, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 创建不同方向的结构元素
kernels = {
'Horizontal': cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1)), # 水平线
'Vertical': cv2.getStructuringElement(cv2.MORPH_RECT, (1, 9)), # 垂直线
'Square': cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 方形
}
results = {}
for name, kernel in kernels.items():
dilated = cv2.dilate(binary, kernel, iterations=1)
results[name] = cv2.cvtColor(dilated, cv2.COLOR_GRAY2BGR)
return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), results
def top_hat_transform(image_path):
"""
顶帽变换 - 原图减去开运算结果
:param image_path: 输入图像路径
:return: 原图和顶帽变换结果
"""
# 读取图像
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 创建结构元素
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# 开运算(先腐蚀后膨胀)
opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 顶帽变换(原图减去开运算结果)
top_hat = cv2.subtract(img, opened)
return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), cv2.cvtColor(top_hat, cv2.COLOR_GRAY2BGR)
# 使用示例
if __name__ == "__main__":
# 注意:需要提供实际的图像路径
# img, result = dilation_builtin('image.jpg', 3, 1)
# binary_img, binary_result = manual_dilation_binary('image.jpg', 3)
# gray_img, gray_result = manual_dilation_grayscale('image.jpg', 3)
# shape_img, shape_results = dilation_with_different_shapes('image.jpg')
# gap_orig, gap_img, gap_result = gap_filling_dilation('image.jpg')
# conn_orig, conn_img, conn_result = object_connection_dilation('image.jpg')
# dir_img, dir_results = directional_dilation('image.jpg')
# tophat_img, tophat_result = top_hat_transform('image.jpg')
pass