目录
4. 形态学重构(Morphological Reconstruction)
四、基于数学形态学的保留边缘图像去噪的Python代码完整实现
1. 噪声生成模块(generate_noisy_image)
2. 形态学去噪核心流水线(morphological_denoising_pipeline)
一、引言
在【计算机视觉】形态学的去噪-CSDN博客中,评论区中有人问道:“如何利用数学形态学进行高效的图像去噪处理,并且在去噪过程中如何保持图像的边缘和细节信息?”确实,在传统的形态学去噪中存在“过度平滑导致边缘模糊” 的问题,因此,本文将会深入讲解基于数学形态学的保留边缘图像去噪的原理和Python代码完整实现。
本文用到的图片:
二、数学形态学去噪的基础:开运算与闭运算
数学形态学的基本操作(腐蚀、膨胀)是去噪的基础,其中开运算和闭运算是最常用的去噪工具:
- 开运算(Opening):先腐蚀后膨胀,可去除图像中小于结构元素的亮噪声(如亮背景上的暗点噪声),同时基本保持较大目标的形状。
- 闭运算(Closing):先膨胀后腐蚀,可去除图像中小于结构元素的暗噪声(如暗背景上的亮点噪声),同样对大目标影响较小。
对于含椒盐噪声的图像,开运算可去除 “盐噪声”(亮点),闭运算可去除 “椒噪声”(暗点)。
三、保边缘去噪的关键策略
单纯的开 / 闭运算可能模糊边缘,需通过以下优化策略平衡去噪与保边:
1. 自适应结构元素(SE)设计
结构元素的大小和形状直接影响去噪效果和边缘保留能力:
- 大小选择:结构元素需略大于噪声尺寸(如噪声为 3x3 像素,SE 选 5x5),但不宜过大(否则会模糊边缘)。
- 形状选择:根据图像边缘方向动态调整 SE 形状(如对线状边缘用线形 SE,对圆形边缘用圆形 SE),减少对边缘的侵蚀。
- 多尺度 SE:结合不同大小的 SE(如 3x3、5x5),小 SE 保留细节,大 SE 去除大噪声,最后融合结果。
对于含细微纹理的图像,用小尺寸圆形 SE 进行开运算,既能去除小噪声,又能保留纹理细节。
2. 基于边缘检测的选择性形态学操作
先通过形态学梯度(如膨胀 - 腐蚀)检测边缘,在边缘区域 “弱化” 或 “跳过” 形态学操作,仅在平滑区域强化去噪:
- 步骤:
- 计算图像的形态学梯度(
gradient = dilate(img) - erode(img)
),标记边缘区域。 - 对非边缘区域应用开 / 闭运算去噪。
- 对边缘区域保留原始像素或仅用小 SE 轻量处理,避免边缘模糊。
- 计算图像的形态学梯度(
优势:精准区分噪声与边缘,针对性处理。
3. 形态学滤波的组合策略
通过开 / 闭运算的组合(如 “开 - 闭” 或 “闭 - 开”),同时去除亮、暗噪声,并减少边缘损失:
- 顶帽变换(Top-hat):
tophat = img - opening(img)
,可提取被亮噪声掩盖的暗细节(如纹理)。 - 底帽变换(Bottom-hat):
bottomhat = closing(img) - img
,可提取被暗噪声掩盖的亮细节。 - 组合应用:对去噪后的图像叠加顶帽 / 底帽结果,恢复被形态学操作模糊的细节。
对于低对比度图像,先用闭运算去除暗噪声,再用顶帽变换恢复丢失的亮细节。
4. 形态学重构(Morphological Reconstruction)
一种更精细的去噪方法,通过 “标记图像” 和 “模板图像” 的迭代运算,在去噪的同时严格保留边缘:
- 步骤:
- 以原始图像为 “模板”,以腐蚀后的图像为 “标记”(标记图像包含噪声但边缘被保留)。
- 迭代执行膨胀操作,直到标记图像不再变化(被模板图像约束,不会越过边缘)。
- 优势:相比传统开 / 闭运算,重构能更精确地保留边缘轮廓,尤其适合复杂纹理图像。
四、基于数学形态学的保留边缘图像去噪的Python代码完整实现
import cv2
import numpy as np
import matplotlib.pyplot as plt
from skimage.metrics import peak_signal_noise_ratio as psnr
from skimage.metrics import structural_similarity as ssim
# 设置中文显示
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
def generate_noisy_image(clean_img, salt_ratio=0.01, pepper_ratio=0.01):
"""生成含椒盐噪声的图像,模拟真实噪声干扰"""
noisy_img = clean_img.copy()
total_pixels = clean_img.size
# 添加盐噪声(亮点)
salt_pixels = int(total_pixels * salt_ratio)
coords = [np.random.randint(0, i, salt_pixels) for i in clean_img.shape]
noisy_img[coords[0], coords[1]] = 255
# 添加椒噪声(暗点)
pepper_pixels = int(total_pixels * pepper_ratio)
coords = [np.random.randint(0, i, pepper_pixels) for i in clean_img.shape]
noisy_img[coords[0], coords[1]] = 0
return noisy_img, coords[0], coords[1] # 返回噪声坐标用于可视化
def morphological_denoising_pipeline(noisy_img):
"""数学形态学去噪流水线"""
# 1. 定义自适应结构元素
se_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
se_large = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
# 2. 基础去噪
opening = cv2.morphologyEx(noisy_img, cv2.MORPH_OPEN, se_large)
basic_denoised = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, se_large)
# 3. 形态学重构
marker = cv2.erode(basic_denoised, se_small)
template = basic_denoised.copy()
while True:
prev_marker = marker
marker = cv2.dilate(marker, se_small)
marker = np.minimum(marker, template)
if np.array_equal(marker, prev_marker):
break
reconstructed = marker
# 4. 细节增强
tophat = cv2.morphologyEx(noisy_img, cv2.MORPH_TOPHAT, se_small)
bottomhat = cv2.morphologyEx(noisy_img, cv2.MORPH_BLACKHAT, se_small)
enhanced = reconstructed + tophat - bottomhat
enhanced = np.clip(enhanced, 0, 255).astype(np.uint8)
return {
"noisy": noisy_img,
"basic_denoised": basic_denoised,
"reconstructed": reconstructed,
"enhanced": enhanced,
"se_small": se_small,
"se_large": se_large,
"tophat": tophat,
"bottomhat": bottomhat
}
def calculate_metrics(original, processed):
"""计算去噪效果定量指标"""
# PSNR(峰值信噪比,越高越好)
psnr_val = psnr(original, processed, data_range=255)
# SSIM(结构相似性,越接近1越好)
ssim_val = ssim(original, processed, data_range=255)
return {"psnr": psnr_val, "ssim": ssim_val}
def visualize_noise_removal(noisy_img, enhanced_img, salt_coords, pepper_coords):
"""可视化噪声去除情况(标记未去除的噪声)"""
# 标记残留的盐噪声
salt_residual = np.zeros_like(noisy_img)
salt_residual[salt_coords[0], salt_coords[1]] = 255 * (enhanced_img[salt_coords[0], salt_coords[1]] == 255)
# 标记残留的椒噪声
pepper_residual = np.zeros_like(noisy_img)
pepper_residual[pepper_coords[0], pepper_coords[1]] = 255 * (enhanced_img[pepper_coords[0], pepper_coords[1]] == 0)
return salt_residual, pepper_residual
def visualize_local_details(original, noisy, enhanced, roi=(200, 250, 100, 150)):
"""提取局部区域(如眼睛、纹理)进行放大对比"""
x, y, w, h = roi # 感兴趣区域:(x,y)为左上角,w/h为宽高
original_roi = original[x:x + w, y:y + h]
noisy_roi = noisy[x:x + w, y:y + h]
enhanced_roi = enhanced[x:x + w, y:y + h]
return original_roi, noisy_roi, enhanced_roi
def visualize_results(original, results, salt_coords, pepper_coords):
"""可视化:多维度展示去噪效果"""
# 计算定量指标
metrics = {
"noisy": calculate_metrics(original, results["noisy"]),
"basic": calculate_metrics(original, results["basic_denoised"]),
"recon": calculate_metrics(original, results["reconstructed"]),
"enhanced": calculate_metrics(original, results["enhanced"])
}
# 噪声残留可视化
salt_residual, pepper_residual = visualize_noise_removal(
results["noisy"], results["enhanced"], salt_coords, pepper_coords
)
# 局部细节放大(以Lena图像的眼睛区域为例)
roi = (200, 250, 100, 150) # 可根据实际图像调整
orig_roi, noisy_roi, enh_roi = visualize_local_details(
original, results["noisy"], results["enhanced"], roi
)
# 边缘梯度对比
se = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
orig_grad = cv2.morphologyEx(original, cv2.MORPH_GRADIENT, se)
enh_grad = cv2.morphologyEx(results["enhanced"], cv2.MORPH_GRADIENT, se)
# 创建子图(3行4列布局)
fig, axes = plt.subplots(3, 4, figsize=(20, 15))
axes = axes.flatten()
# 1. 原始图像
axes[0].imshow(original, cmap="gray")
axes[0].set_title("原始图像")
axes[0].axis("off")
# 2. 含噪声图像(带指标)
axes[1].imshow(results["noisy"], cmap="gray")
axes[1].set_title(
f"含椒盐噪声图像\nPSNR: {metrics['noisy']['psnr']:.2f}\nSSIM: {metrics['noisy']['ssim']:.4f}"
)
axes[1].axis("off")
# 3. 基础去噪结果(带指标)
axes[2].imshow(results["basic_denoised"], cmap="gray")
axes[2].set_title(
f"基础开闭运算去噪\nPSNR: {metrics['basic']['psnr']:.2f}\nSSIM: {metrics['basic']['ssim']:.4f}"
)
axes[2].axis("off")
# 4. 形态学重构结果(带指标)
axes[3].imshow(results["reconstructed"], cmap="gray")
axes[3].set_title(
f"形态学重构\nPSNR: {metrics['recon']['psnr']:.2f}\nSSIM: {metrics['recon']['ssim']:.4f}"
)
axes[3].axis("off")
# 5. 最终增强结果(带指标)
axes[4].imshow(results["enhanced"], cmap="gray")
axes[4].set_title(
f"最终去噪+细节增强\nPSNR: {metrics['enhanced']['psnr']:.2f}\nSSIM: {metrics['enhanced']['ssim']:.4f}"
)
axes[4].axis("off")
# 6. 残留盐噪声(红色标记)
axes[5].imshow(results["enhanced"], cmap="gray")
axes[5].imshow(salt_residual, cmap="Reds", alpha=0.5) # 叠加红色残留噪声
axes[5].set_title("残留盐噪声(红色)")
axes[5].axis("off")
# 7. 残留椒噪声(蓝色标记)
axes[6].imshow(results["enhanced"], cmap="gray")
axes[6].imshow(pepper_residual, cmap="Blues", alpha=0.5) # 叠加蓝色残留噪声
axes[6].set_title("残留椒噪声(蓝色)")
axes[6].axis("off")
# 8. 结构元素可视化
axes[7].imshow(results["se_small"], cmap="binary")
axes[7].set_title("小结构元素 (3x3)")
axes[7].axis("off")
axes[8].imshow(results["se_large"], cmap="binary")
axes[8].set_title("大结构元素 (5x5)")
axes[8].axis("off")
# 9-11. 局部细节放大对比
axes[9].imshow(orig_roi, cmap="gray")
axes[9].set_title("原始局部细节")
axes[9].axis("off")
axes[10].imshow(noisy_roi, cmap="gray")
axes[10].set_title("含噪声局部细节")
axes[10].axis("off")
axes[11].imshow(enh_roi, cmap="gray")
axes[11].set_title("去噪后局部细节")
axes[11].axis("off")
plt.tight_layout()
plt.savefig('Implementation of Edge-Preserving Image Denoising Based on Mathematical Morphology.png')
plt.show()
if __name__ == "__main__":
# 读取图像并转为灰度图
img = cv2.imread("lena.png")
original_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 生成含噪声图像(同时获取噪声坐标)
noisy_img, salt_coords, pepper_coords = generate_noisy_image(
original_gray, salt_ratio=0.02, pepper_ratio=0.02
)
# 执行形态学去噪流程
denoise_results = morphological_denoising_pipeline(noisy_img)
# 可视化
visualize_results(original_gray, denoise_results, salt_coords, pepper_coords)
五、Python代码完整解释
(一)核心设计思想
该代码通过 **“分阶段处理 + 多维度验证”的策略,实现 “噪声去除” 与 “边缘保留” 的平衡。核心逻辑基于数学形态学的基本操作(腐蚀、膨胀、开 / 闭运算),并通过形态学重构和细节增强 ** 技术,解决传统形态学去噪中 “过度平滑导致边缘模糊” 的问题。整体流程遵循 “先去噪、再保边、最后恢复细节” 的思路,同时通过定量指标和可视化对比验证效果。
(二)模块功能与实现细节
1. 噪声生成模块(generate_noisy_image
)
- 作用:模拟真实图像中的椒盐噪声(图像中随机出现的亮噪声 “盐点” 和暗噪声 “椒点”),为去噪算法提供测试数据。
- 实现逻辑:
- 计算图像总像素数,根据设定的噪声比例(
salt_ratio
和pepper_ratio
)确定噪声像素数量。 - 通过随机坐标生成器,在图像中随机位置添加 255(盐噪声,亮点)和 0(椒噪声,暗点)。
- 返回含噪声图像及噪声坐标(用于后续噪声残留分析)。
- 计算图像总像素数,根据设定的噪声比例(
- 意义:标准化噪声输入,确保去噪效果可量化对比。
2. 形态学去噪核心流水线(morphological_denoising_pipeline
)
这是算法的核心模块,包含 4 个关键步骤,逐步实现 “去噪 - 保边 - 增强” 的目标:
(1)自适应结构元素(SE)设计
se_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) # 小结构元素
se_large = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # 大结构元素
- 结构元素:形态学操作的 “工具”,其形状和大小直接影响去噪效果和边缘保留能力。
- 设计逻辑:
- 选择椭圆形状(
MORPH_ELLIPSE
):相比矩形,椭圆对边缘的各向异性影响更小,能更好保留曲线边缘(如人脸轮廓)。 - 双尺寸设计:
- 小 SE(3x3):用于精细操作(形态学重构、细节提取),减少对边缘和纹理的破坏。
- 大 SE(5x5):用于基础去噪,可去除尺寸较大的噪声(大于小 SE 的噪声)。
- 选择椭圆形状(
- 意义:通过结构元素的 “尺寸适配”,平衡 “去噪强度” 和 “细节保留”。
(2)基础去噪:开运算 + 闭运算
opening = cv2.morphologyEx(noisy_img, cv2.MORPH_OPEN, se_large) # 去盐噪声
basic_denoised = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, se_large) # 去椒噪声
- 开运算(MORPH_OPEN):先腐蚀后膨胀,可去除小于结构元素的亮噪声(盐点),同时对较大目标的形状影响较小。
- 闭运算(MORPH_CLOSE):先膨胀后腐蚀,可去除小于结构元素的暗噪声(椒点),同样不破坏大目标的边缘。
- 执行顺序:先开运算去亮噪声,再闭运算去暗噪声,初步净化图像。
- 局限性:单纯的开 / 闭运算会轻微模糊边缘(尤其是小尺度边缘,如发丝、纹理),因此需要后续保边处理。
(3)边缘保护:形态学重构
marker = cv2.erode(basic_denoised, se_small) # 标记图像(腐蚀后缩小的区域)
template = basic_denoised.copy() # 模板图像(基础去噪结果)
while True:
prev_marker = marker
marker = cv2.dilate(marker, se_small) # 迭代膨胀标记
marker = np.minimum(marker, template) # 受模板约束,不超过原始范围
if np.array_equal(marker, prev_marker): # 收敛条件
break
reconstructed = marker
- 核心原理:通过 “标记图像” 和 “模板图像” 的迭代运算,在去除残留噪声的同时,严格保留边缘轮廓。
- 步骤解析:
- 标记图像(marker):对基础去噪结果进行腐蚀,得到 “内缩的目标区域”(边缘向内收缩,噪声被进一步去除)。
- 模板图像(template):以基础去噪结果为上限,限制标记图像的膨胀范围。
- 迭代膨胀:标记图像不断膨胀,但始终被模板图像约束(
np.minimum
操作),确保膨胀不会越过原始边缘(模板图像的边缘)。 - 收敛条件:当标记图像不再变化时,得到重构结果 —— 此时噪声被去除,边缘与模板图像一致(保边效果)。
- 优势:相比传统开 / 闭运算,重构能更精确地保留边缘,避免 “过度平滑”。
(4)细节增强:顶帽 + 底帽变换
tophat = cv2.morphologyEx(noisy_img, cv2.MORPH_TOPHAT, se_small) # 提取亮细节
bottomhat = cv2.morphologyEx(noisy_img, cv2.MORPH_BLACKHAT, se_small) # 提取暗细节
enhanced = reconstructed + tophat - bottomhat # 融合细节
- 顶帽变换(TOPHAT):
原图 - 开运算结果
,可提取被亮噪声掩盖的暗细节(如纹理、小尺度边缘)。 - 底帽变换(BLACKHAT):
闭运算结果 - 原图
,可提取被暗噪声掩盖的亮细节。 - 融合逻辑:将顶帽 / 底帽提取的细节叠加回重构结果,恢复形态学操作中可能丢失的细微结构(如皮肤纹理、发丝)。
- 意义:解决 “去噪导致细节丢失” 的问题,提升图像的视觉质量。
3. 定量指标计算(calculate_metrics
)
psnr_val = psnr(original, processed, data_range=255) # 峰值信噪比
ssim_val = ssim(original, processed, data_range=255) # 结构相似性
- PSNR(峰值信噪比):衡量去噪后图像与原图的像素差异,值越高(通常 > 30dB)表示去噪效果越好。
- SSIM(结构相似性):衡量图像结构(包括边缘、纹理)的保留程度,越接近 1 表示结构保留越好。
- 作用:从数值角度客观验证算法的 “去噪能力” 和 “保边效果”,避免仅依赖主观视觉判断。
4. 可视化模块(visualize_results
)
通过多维度可视化,直观展示算法的有效性,包含以下关键对比:
可视化内容 | 作用说明 |
---|---|
原始图像 vs 含噪声图像 | 展示噪声对图像的破坏程度,明确去噪任务的必要性。 |
各阶段去噪结果对比 | 展示 “基础去噪→重构→增强” 的效果提升,突出重构的保边作用。 |
定量指标(PSNR/SSIM) | 用数值证明增强结果在 “去噪” 和 “结构保留” 上的优势。 |
噪声残留标记(红 / 蓝色) | 标记未去除的盐 / 椒噪声,验证算法对噪声的去除彻底性。 |
结构元素可视化 | 展示 3x3 和 5x5 椭圆 SE 的形状,解释 “小 SE 保细节、大 SE 去噪” 的设计逻辑。 |
局部细节放大(如眼睛区域) | 聚焦高细节区域(如眼睑边缘、睫毛),直观展示边缘和纹理的保留效果。 |
边缘梯度对比 | 通过形态学梯度(膨胀 - 腐蚀)展示去噪后边缘与原始边缘的一致性。 |
(三)算法整体流程总结
- 输入准备:读取原始图像,生成含椒盐噪声的测试图像。
- 基础净化:用大 SE 的开 / 闭运算去除大部分盐 / 椒噪声。
- 边缘保护:通过形态学重构,在基础去噪结果上恢复边缘的清晰度。
- 细节增强:用顶帽 / 底帽变换提取并叠加丢失的细节,优化视觉效果。
- 效果验证:通过定量指标(PSNR/SSIM)和多维度可视化,证明去噪同时保边的效果。
六、总结
本文提出了一种基于数学形态学的图像去噪方法,重点解决传统去噪中边缘模糊的问题。通过自适应结构元素设计、形态学重构和细节增强技术,实现了噪声去除与边缘保留的平衡。算法流程包括:基础开闭运算去噪、形态学重构保护边缘、顶帽底帽变换恢复细节。实验通过PSNR/SSIM指标和多维度可视化验证了方法的有效性,在去除椒盐噪声的同时较好地保持了图像细节和边缘结构。Python代码实现展示了完整的处理流程,包括噪声生成、分阶段去噪、定量评估和结果可视化。