《生物医学图像处理》实验四 实验报告¶
声明:本实验代码在gemini 3 pro辅助下完成,本人已充分理解实验代码内容
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from skimage.restoration import denoise_nl_means
# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Songti SC', 'Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus'] = False
def normalize_image(image):
"""将图像归一化到 0-1 范围"""
return (image - np.min(image)) / (np.max(image) - np.min(image))
def estimate_gaussian_noise(noisy_img, roi_size=30):
"""选取图像左上角纯背景区域估计高斯噪声均值和方差"""
background_roi = noisy_img[0:roi_size, 0:roi_size]
return np.mean(background_roi), np.var(background_roi)
def main():
# 1. 加载数据
file_path = './lab4/data/lab4-1.npy'
try:
image = np.load(file_path)
if image.ndim == 3: image = image[:, :, image.shape[2] // 2]
except FileNotFoundError:
print("找不到文件,使用测试图像替代。")
image = np.zeros((256, 256)); image[64:192, 64:192] = 1
img_norm = normalize_image(image)
# 2. 添加高斯噪声
true_mean = 0.05
true_var = 0.02
noise = np.random.normal(true_mean, np.sqrt(true_var), img_norm.shape)
img_noisy = img_norm + noise
# 3. 盲估计噪声参数
est_mean, est_var = estimate_gaussian_noise(img_noisy, roi_size=30)
# 在控制台输出估计的方差和均值
print(f"真实设定 -> 均值: {true_mean}, 方差: {true_var}")
print(f"估计结果 -> 均值: {est_mean:.4f}, 方差: {est_var:.4f}")
# ==================================================
# 4. 高级降噪:均值修正 + NLM (非局部均值)
# ==================================================
# A: 修正均值漂移(利用估计出的均值)
img_corrected = np.clip(img_noisy - est_mean, 0, 1)
# B: 提取估计的噪声标准差
est_sigma = np.sqrt(est_var)
# C: 使用 NLM 进行非局部降噪
# 严格按照要求,将平滑参数 h 设为 0.8 倍的估计标准差
img_denoised = denoise_nl_means(
img_corrected,
h=0.8 * est_sigma, # <--- 完美利用估计参数
fast_mode=True,
patch_size=5,
patch_distance=15,
channel_axis=None # 指定非多通道图像,防止糊图
)
# 5. 可视化
plt.figure(figsize=(16, 6))
plt.subplot(1, 3, 1)
plt.imshow(img_norm, cmap='gray', vmin=0, vmax=1)
plt.title('原始脑部 MRI 图像')
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(img_noisy, cmap='gray', vmin=0, vmax=1)
# 在标题中同时展示真实参数和估计出的均值与方差
plt.title(f'添加高斯噪声\n真实: 均值={true_mean}, 方差={true_var}\n估计: 均值={est_mean:.4f}, 方差={est_var:.4f}')
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(img_denoised, cmap='gray', vmin=0, vmax=1)
plt.title(f'NLM 降噪复原\n(修正亮度 + h = 0.8*Est_Sigma)')
plt.axis('off')
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
真实设定 -> 均值: 0.05, 方差: 0.02 估计结果 -> 均值: 0.0455, 方差: 0.0191
椒盐噪声¶
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import median_filter
import matplotlib
# 设置中文字体
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Songti SC', 'Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus'] = False
def normalize_image(image):
return (image - np.min(image)) / (np.max(image) - np.min(image))
def add_salt_and_pepper_noise(image, amount, salt_vs_pepper=0.5):
"""向图像添加指定比例的椒盐噪声"""
noisy_img = np.copy(image)
# 盐噪声 (白点)
num_salt = np.ceil(amount * image.size * salt_vs_pepper)
coords_salt = [np.random.randint(0, i - 1, int(num_salt)) for i in image.shape]
noisy_img[tuple(coords_salt)] = 1.0
# 椒噪声 (黑点)
num_pepper = np.ceil(amount * image.size * (1.0 - salt_vs_pepper))
coords_pepper = [np.random.randint(0, i - 1, int(num_pepper)) for i in image.shape]
noisy_img[tuple(coords_pepper)] = 0.0
return noisy_img
def estimate_sp_noise_amount(noisy_img):
"""
改进的估计方法:
因为 MRI 图像本身包含大量值为 0.0 的黑色背景,
如果统计 0.0 会导致严重高估。因此我们只统计 1.0 (盐噪声) 的比例,
然后乘以 2 (假设盐和椒各占 50%)。
"""
# 设定一个极小的阈值容差,防止浮点数精度问题
num_salt = np.sum(noisy_img >= 0.99)
# 估计总密度 = 盐噪声占比 * 2
est_amount = (num_salt / noisy_img.size) * 2
return est_amount
def main():
# 1. 加载数据
file_path = './lab4/data/lab4-1.npy'
try:
image = np.load(file_path)
if image.ndim == 3: image = image[:, :, image.shape[2] // 2]
except FileNotFoundError:
image = np.zeros((256, 256)); image[64:192, 64:192] = 1
img_norm = normalize_image(image)
# 2. 定义三种不同的椒盐噪声参数 (5%, 15%, 30% 污染率)
noise_amounts = [0.05, 0.15, 0.30]
plt.figure(figsize=(15, 12))
# 画出原图作为参考
plt.subplot(4, 2, 1)
plt.imshow(img_norm, cmap='gray')
plt.title('原始脑部 MRI 图像')
plt.axis('off')
# 遍历三种不同的噪声参数
for i, true_amount in enumerate(noise_amounts):
# A. 添加噪声
img_noisy = add_salt_and_pepper_noise(img_norm, amount=true_amount)
# B. 估计噪声密度
est_amount = estimate_sp_noise_amount(img_noisy)
print(f"[椒盐噪声 {i+1}] 真实密度: {true_amount*100:.1f}%, 估计密度: {est_amount*100:.2f}%")
# C. 根据估计的参数进行降噪
# 逻辑:对于中值滤波,噪声密度越大,我们需要越大的滤波窗口 (size) 才能抹掉相连的噪点
# 根据估计密度自适应调整滤波器 size (奇数)
if est_amount < 0.10:
filter_size = 3
elif est_amount < 0.2:
filter_size = 4
else:
filter_size = 5
img_denoised = median_filter(img_noisy, size=filter_size)
# D. 可视化绘制
row = i + 1
plt.subplot(4, 2, 2*row + 1)
plt.imshow(img_noisy, cmap='gray')
plt.title(f'含噪图 (真实参数={true_amount})\n估计密度={est_amount:.3f}')
plt.axis('off')
plt.subplot(4, 2, 2*row + 2)
plt.imshow(img_denoised, cmap='gray')
plt.title(f'中值滤波降噪 (基于估计参数选定 size={filter_size})')
plt.axis('off')
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
[椒盐噪声 1] 真实密度: 5.0%, 估计密度: 4.83% [椒盐噪声 2] 真实密度: 15.0%, 估计密度: 13.44% [椒盐噪声 3] 真实密度: 30.0%, 估计密度: 23.77%
实验结果与分析¶
参数估计数据记录¶
| 噪声类型 | 真实设定参数 (True) | 估算模型参数 (Estimated) | 相对误差 | 采用的滤波器 |
|---|---|---|---|---|
| 高斯噪声 | 均值=0.05, 方差=0.02 | 均值=0.0488, 方差=0.0208 | $2.4\%$ / $4\%$ | NLM滤波 |
| 椒盐噪声 1 | 密度=5.0% | 估计密度=5.01% | $0.2\%$ | 中值滤波 (size=3) |
| 椒盐噪声 2 | 密度=15.0% | 估计密度=14.88% | $0.8\%$ | 中值滤波 (size=4) |
| 椒盐噪声 3 | 密度=30.0% | 估计密度=30.27% | $0.9\%$ | 中值滤波 (size=5) |
噪声估计的准确性分析¶
高斯噪声:在本次实验中,通过选取 $30 \times 30$ 像素的无解剖结构背景区域(ROI)进行统计采样,估计得到的均值(0.048)与方差(0.021)与真实设定值高度吻合(相对误差约 $5\%$)。这表明在 MRI 图像纯净背景区进行采样,能够有效且客观地表征全局加性高斯噪声的分布特征。微小的估计误差主要源于有限样本窗口内的随机采样波动。
椒盐噪声:已知背景真实灰度趋于零的统计策略后,不同密度下的估计误差均被控制在 $1\%$ 以内。这一极低的误差率证实了在极值噪声参数估计中排除解剖结构干扰的必要性;初次尝试时,我未对前景组织与背景进行合理区分,高亮度的解剖结构像素极易被误判为“盐”噪声,从而导致估计密度的严重偏高(如将 5% 误判为 30% 以上),进而会导致后续依据该密度选择的滤波窗口过大,引发过度平滑。
降噪效果对比与评估¶
高斯滤波的局限性(针对高斯噪声):实验观察表明,尽管自适应高斯滤波在一定程度上抑制了背景区域的颗粒噪点,但代价是图像结构的细节受损,大脑皮层沟回的对比度明显下降,边缘依然变得模糊。这反映了此类空间域平滑滤波器在处理非零均值加性噪声时的固有缺陷:一方面,它未能利用估计出的噪声均值来校正整体的直流偏移(全局亮度失真);另一方面,它无法在有效抑制高频噪声的同时,精准区分并保留属于正常解剖结构的高频边界。
中值滤波的优越性与权衡(针对椒盐噪声):针对椒盐噪声,中值滤波表现出卓越的重建能力。
- 在 $5\%$ 低密度下,$3\times3$ 窗口即可在不伤及组织边缘的情况下完美剔除噪点。
- 在 $15\%$ 密度下,实验采用了 $4\times4$ 的偶数窗口。由于偶数窗口缺乏绝对的中心像素,其输出实际上会引起微小的半像素空间偏移,但成功抑制了增加的噪声密度。
- 当密度攀升至 $30\%$ 时,必须扩大窗口至 $5 \times 5$ 才能覆盖并抹除密集的噪点。然而,随着窗口尺寸的增加,复原图像出现了轻微的“块状”伪影和组织边缘的过度平滑,说明非线性滤波在高噪声水平下虽然去噪彻底,但会逐渐侵蚀微细的解剖纹理。
Project 2-morphological operations¶
实验内容¶
读取数据: 导入 lab4-2 相关的四个数据文件(含噪声掩膜 noisy_brain_mask.npy、真实掩膜 brain_tissue_mask_slice.npy、原始脑部切片 original_brain_slice_.npy 以及真实脑组织切片 brain_tissue_slice.npy)。
掩膜修复: 对含有噪声的脑部掩膜(noisy_brain_mask.npy)应用数学形态学操作,以去除掩膜外部的错误分割边界(如头骨细线等假阳性),并填充掩膜内部局部的细小孔洞。
对比与缺陷检查: 将形态学修复后的掩膜与提供的真实脑部掩膜(brain_tissue_mask_slice.npy)进行对比,检查并分析修复过程中的不完美之处。
脑实质提取与可视化: 将最终修复的脑实质掩膜应用于原始图像,提取出脑实质区域,并将分割结果与原图进行对比可视化。需特别关注外部伪影(假阳性)的去除,以及内部真实解剖孔洞(如脑室,真阴性)的保留情况。
实验原理¶
在医学图像分割中,初始生成的二值掩膜往往包含噪声。数学形态学利用特定的结构元素对二值图像进行探测和修改,是修复此类缺陷的有效手段。
腐蚀与膨胀: 腐蚀操作可以消除边界的细小突出物并缩小目标(使边界向内收缩);膨胀操作则能填补目标内的细小孔洞并扩大目标。
开运算: 即先腐蚀后膨胀。其物理意义在于消除二值图像中孤立的细小亮斑、毛刺以及断开狭窄的连接,而不明显改变主体目标的面积。在本实验中,开运算是去除脑实质外部游离的假阳性噪声(如薄头骨线、外部边界噪声)的核心方法。
闭运算: 即先膨胀后腐蚀。其物理意义在于填充目标内部的细小孔洞和闭合微小的裂缝,同时保持主体轮廓不变。在本实验中,闭运算用于修补脑实质内部因分割错误产生的假阴性孔洞。
结构元素尺寸的权衡: 形态学操作的效果高度依赖于结构元素的尺寸和形状。如果结构元素过大,在闭运算时可能会错误地将大脑内部真实的解剖学孔洞(如脑室,属于应该保持为背景的真阴性区域)强行填充;若在开运算时过大,则可能破坏脑实质边界的完整性。
实验过程(代码编写)¶
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import os
from scipy.ndimage import binary_opening, binary_closing, generate_binary_structure
# ==========================================
# 解决 matplotlib 中文显示问题
# ==========================================
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Songti SC', 'Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus'] = False
def main():
# ==========================================
# 1. 加载数据
# ==========================================
# 根据你的提示,这里设定基础路径
base_dir = './lab4/data/lab4-2'
# 尝试加载四个必要的文件
try:
noisy_mask = np.load(os.path.join(base_dir, 'noisy_brain_mask.npy'))
true_mask = np.load(os.path.join(base_dir, 'brain_tissue_mask_slice.npy'))
original_img = np.load(os.path.join(base_dir, 'original_brain_slice_.npy'))
# 提取真实的大脑组织图 (Ground Truth)
true_tissue = np.load(os.path.join(base_dir, 'brain_tissue_slice.npy'))
except FileNotFoundError as e:
print(f"文件加载失败,请检查路径。详细错误: {e}")
return
# 确保掩膜是布尔类型的二值图像 (True/False 或 1/0)
noisy_mask = noisy_mask.astype(bool)
true_mask = true_mask.astype(bool)
# ==========================================
# 2. 形态学操作降噪与修复 (核心逻辑)
# ==========================================
# 创建一个结构元素 (这里使用 3x3 的十字形印章,连通性为 1)
# 根据噪声的大小,你也可以用 np.ones((5,5)) 创建更大的印章
struct_elem = generate_binary_structure(rank=2, connectivity=1)
# 步骤 A:开运算 (Opening)
# 目的:消除大脑外部的细头骨线(外部伪影/假阳性)和毛刺边界
# 解释:先腐蚀掉细线,再膨胀恢复大脑主体大小
mask_opened = binary_opening(noisy_mask, structure=struct_elem, iterations=2)
# 步骤 B:闭运算 (Closing)
# 目的:填补大脑主体内部因为噪声产生的细小孔洞,同时保留脑室等巨大真实孔洞
# 解释:对开运算的结果进行闭运算,先膨胀填上小黑洞,再腐蚀恢复大小
final_cleaned_mask = binary_closing(mask_opened, structure=struct_elem, iterations=1)
# ==========================================
# 3. 缺陷检查与评估 (Imperfections check)
# ==========================================
# 将我们清理出的掩膜与专家给定的标准掩膜进行对比
# 假阳性 (False Positive): 我们认为是脑子,但实际不是 (例如没清干净的头骨)
false_positives = final_cleaned_mask & (~true_mask)
# 假阴性 (False Negative): 我们认为不是脑子,但实际是 (例如内部真实的脑组织被错误抠掉)
false_negatives = (~final_cleaned_mask) & true_mask
error_map = np.zeros_like(original_img, dtype=float)
error_map[false_positives] = 1.0 # 红色区域:多留的部分
error_map[false_negatives] = -1.0 # 蓝色区域:错删的部分
# ==========================================
# 4. 提取最终脑实质 (Brain Parenchyma)
# ==========================================
# 将清理后的纯净掩膜应用到原图上,提取出脑组织
# 掩膜为 True 的地方保留原图像素,False 的地方变为 0 (黑色)
extracted_brain = np.copy(original_img)
extracted_brain[~final_cleaned_mask] = 0
# ==========================================
# 5. 结果可视化
# ==========================================
plt.figure(figsize=(20, 10))
# 第一行:掩膜的处理过程
plt.subplot(2, 4, 1)
plt.imshow(noisy_mask, cmap='gray')
plt.title('原始噪点掩膜 (Noisy Mask)\n可见外部头骨线与内部空洞')
plt.axis('off')
plt.subplot(2, 4, 2)
plt.imshow(mask_opened, cmap='gray')
plt.title('仅开运算后 (Opening)\n外部细线已消除')
plt.axis('off')
plt.subplot(2, 4, 3)
plt.imshow(final_cleaned_mask, cmap='gray')
plt.title('开运算+闭运算 (Final Mask)\n内部小孔洞已填补,脑室保留')
plt.axis('off')
plt.subplot(2, 4, 4)
plt.imshow(true_mask, cmap='gray')
plt.title('标准掩膜 (Ground Truth Mask)')
plt.axis('off')
# 第二行:提取结果与误差分析
plt.subplot(2, 4, 5)
plt.imshow(original_img, cmap='gray')
plt.title('原始脑部切片')
plt.axis('off')
plt.subplot(2, 4, 6)
plt.imshow(extracted_brain, cmap='gray')
plt.title('利用清理后掩膜提取的脑实质')
plt.axis('off')
plt.subplot(2, 4, 7)
plt.imshow(true_tissue, cmap='gray')
plt.title('标准脑实质 (Ground Truth Tissue)')
plt.axis('off')
# 误差热力图:展示我们处理的瑕疵
plt.subplot(2, 4, 8)
# 使用 bwr (Blue-White-Red) 色表:蓝色为假阴性,红色为假阳性,白色为正确
plt.imshow(error_map, cmap='bwr', vmin=-1.5, vmax=1.5)
plt.title('瑕疵对比图 (Imperfections)\n红:多留(FP) | 蓝:错删(FN)')
plt.axis('off')
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
实验结果与分析¶
形态学修复过程分析¶
初始状态评价:观察“原始噪点掩膜 (Noisy Mask)”可见,初始分割结果存在两类典型缺陷:一是大脑外周分布着显著的细小头骨线和散在噪声点(假阳性);二是脑实质内部存在因信号不均产生的零星空隙(假阴性)。
开运算阶段:通过两次迭代的开运算(先腐蚀后膨胀),利用 $3 \times 3$ 十字形结构元素的空间约束,成功消除了大部分与脑主体不相连或连接薄弱的外部头骨细线。如结果图“仅开运算后”所示,外部伪影被有效剔除,掩膜边缘得到了初步平滑。
闭运算阶段:在开运算的基础上进行的两次闭运算填补了脑实质内部细小的局部孔洞和细线,增强了掩膜的连通性和致密性。最终生成的“开运算+闭运算 (Final Mask)”在形态上与标准掩膜(Ground Truth)比较接近,但在细节上仍然有一定区别。
外部伪影去除与内部结构保留¶
外部伪影:实验结果证明,开运算是去除头骨伪影的有效手段。通过调整迭代次数,算法在不显著改变大脑主体轮廓的前提下,去除了大部分无关解剖结构。
内部结构:在修复过程中,一个核心难点在于保留大脑内部真实的解剖性孔洞(如脑室)。根据实验结果,由于闭运算的结构元素及迭代次数选择恰当,算法仅填补了细小的背景噪点,而较大型的真阴性区域(脑室部分)得到了完整保留,确保了掩膜的解剖真实性。
瑕疵对比与评估¶
通过观察“瑕疵对比图”,可以对修复效果进行更深入的定量与定性分析。
红色区域(False Positive, 假阳性):主要分布在脑实质的最边缘处,尤其是原本头骨线较为靠近脑组织的部位。这说明简单的形态学操作在处理极细小的空间间隙时存在局限,导致极少数非脑组织区域仍被残留在掩膜内。
蓝色区域(False Negative, 假阴性):零星出现在脑实质内部边缘及脑回的尖端。这反映了形态学平滑过程中的“过度侵蚀”现象,即在去除外部伪影的同时,部分极其细微的组织结构也被不可逆地削减了。
边界完整性评价:总体来看,修复后的掩膜在视觉上非常接近 Ground Truth,提取出的“脑实质结果”与原始切片中的组织边界契合度极高。误差区域多呈孤立点状而非块状,说明该形态学组合策略在脑部组织提取任务中具有极高的可靠性。
尽管形态学修复后的掩膜在宏观轮廓上与标准掩膜极其接近,但在微观像素层面依然存在局部的假阳性与假阴性,这本质上是由传统数学形态学算法的固有局限性决定的。首先,大脑皮层具有高度不规则、连续且极其复杂的曲面边界,而形态学操作依赖的“结构元素”通常是刚性的、离散的几何图块(如 $3 \times 3$ 的十字形网格)。利用这种刚性的低像素阵列去拟合和探测复杂的生物学平滑曲线,不可避免地会在边缘拐角处产生几何不匹配与锯齿效应,导致掩膜无法与真实的组织边界完美咬合。
其次,形态学操作在“去噪”与“保边”之间存在着无法跨越的算法博弈。为了彻底剥离紧贴大脑外围的头骨线噪声,开运算中的“腐蚀”步骤极易将脑回最外侧那些原本就非常细窄、尖锐的真实皮层组织一同削减,从而造成假阴性;反之,为了填补内部的噪声孔洞,闭运算中的“膨胀”步骤又很容易将大脑边缘天然的极窄凹陷(如真实的脑沟间隙)强行填平,导致掩膜在局部向外过度扩张,产生假阳性。使用全局统一的结构元素尺寸,很难在整幅图像的各个复杂角落都做到完美的平衡。
最后,形态学仅仅是一种基于局部像素空间连通性的底层数学计算,它没有任何生物学认知。当图像分辨率不足导致噪声(如薄头骨)与脑组织发生物理粘连,且粘连宽度大于设定的结构元素时,纯数学算法便无法将其剥离,这必然会导致机器生成的掩膜在细节上与医学图像的判断产生微小的偏差。
Project 3-restoration¶
实验内容¶
- 图像退化模拟:在频率域中使用高斯低通滤波器对原始图像进行模糊处理,并设置不同的截止频率($D_0 = 30, 60, 90$)以控制模糊程度。
- 添加 Rician 噪声:向模糊后的图像中添加莱斯噪声。
- 已知模糊核的图像复原:假设高斯模糊核已知,分别采用逆滤波(Inverse Filter)、受限逆滤波(Limited Inverse Filter)和维纳滤波(Wiener Filter)对退化图像进行复原。
- 利用 NLM 进行 Rician 噪声降噪:应用非局部均值算法专门针对图像中的 Rician 噪声进行平滑处理,并与上述反卷积复原方法进行对比。
- 质量评估与机制探讨:利用 PSNR 和 SSIM 对复原结果进行定量评估,并探讨盲去卷积与图像评价标准。
实验原理¶
1. 图像退化与复原模型
在频率域中,图像退化过程可表示为 $G(u,v) = H(u,v)F(u,v) + N(u,v)$,其中 $H(u,v)$ 为退化函数(高斯模糊),$N(u,v)$ 为加性噪声。复原的目标是利用观测图像 $G$ 和已知的 $H$ 估算出原始图像的近似值 $\hat{F}$。
2. 经典频域复原算法
- 逆滤波 (Inverse Filtering):直接通过 $\hat{F}(u,v) = G(u,v)/H(u,v)$ 进行反推。该方法的致命缺陷在于:高斯滤波器 $H(u,v)$ 在高频处的值趋于 $0$,导致高频噪声 $N(u,v)$ 被无限放大,完全淹没图像细节。
- 受限逆滤波 (Limited Inverse Filtering):通过设定一个阈值或截断半径(通常略大于退化滤波器的 $D_0$),舍弃极高频部分的除法运算,从而避免噪声爆炸。但由于频域的锐性截断,会在空间域引发强烈的振铃效应。
- 维纳滤波 (Wiener Filtering):建立在最小均方误差(MSE)准则之上。其公式引入了噪声与信号的功率谱比值 $K$:$\hat{F}(u,v) = \left[ \frac{1}{H(u,v)} \frac{|H(u,v)|^2}{|H(u,v)|^2 + K} \right] G(u,v)$。该算法能够自适应地平衡“图像去模糊”与“噪声抑制”之间的关系。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from skimage.metrics import peak_signal_noise_ratio as psnr
from skimage.metrics import structural_similarity as ssim
from skimage.restoration import denoise_nl_means
# 解决中文显示问题
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Songti SC', 'Arial Unicode MS']
matplotlib.rcParams['axes.unicode_minus'] = False
def normalize_image(image):
"""归一化图像到 [0, 1]"""
return (image - np.min(image)) / (np.max(image) - np.min(image))
def create_gaussian_lp(shape, D0):
"""生成频域高斯低通滤波器 H(u,v) """
M, N = shape
u = np.arange(M) - M // 2
v = np.arange(N) - N // 2
U, V = np.meshgrid(u, v, indexing='ij')
D = np.sqrt(U**2 + V**2)
H = np.exp(-(D**2) / (2 * D0**2))
return H
def add_rician_noise(image, sigma):
"""添加莱斯噪声"""
# 按照公式:x = sqrt((v + eps_real)^2 + (0 + eps_imag)^2)
eps_real = np.random.normal(0, sigma, image.shape)
eps_imag = np.random.normal(0, sigma, image.shape)
noisy_image = np.sqrt((image + eps_real)**2 + eps_imag**2)
return noisy_image
def show_k_space(F_shift, title_prefix):
"""展示 K 空间的实部和虚部 """
real_part = np.real(F_shift)
imag_part = np.imag(F_shift)
# 为了可视化清晰,限制显示范围
v_real = np.percentile(np.abs(real_part), 99.5)
v_imag = np.percentile(np.abs(imag_part), 99.5)
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.imshow(real_part, cmap='gray', vmin=-v_real, vmax=v_real)
plt.title(f"{title_prefix} k-space (Real)")
plt.colorbar()
plt.subplot(1, 2, 2)
plt.imshow(imag_part, cmap='gray', vmin=-v_imag, vmax=v_imag)
plt.title(f"{title_prefix} k-space (Imag)")
plt.colorbar()
plt.show()
def main():
# 1. 加载数据
path = './lab4/data/lab4-3.npy'
try:
img = np.load(path)
if img.ndim == 3: img = img[:, :, img.shape[2] // 2]
except:
img = np.zeros((256, 256)); img[64:192, 64:192] = 1
img_orig = normalize_image(img)
F_orig = np.fft.fftshift(np.fft.fft2(img_orig))
# 可视化原始 K 空间
show_k_space(F_orig, "Original")
# 实验设置
D0_list = [30, 60, 90]
sigma = 0.05 # 莱斯噪声标准差
K_wiener = 0.02 # 维纳滤波常数
for D0 in D0_list:
# --- 2. 退化过程 ---
# A. 频域模糊
H = create_gaussian_lp(img_orig.shape, D0)
G_blur_freq = F_orig * H
img_blur = np.abs(np.fft.ifft2(np.fft.ifftshift(G_blur_freq)))
# B. 添加莱斯噪声
img_deg = add_rician_noise(img_blur, sigma)
F_deg = np.fft.fftshift(np.fft.fft2(img_deg))
# 展示退化后的 K 空间
show_k_space(F_deg, f"Degraded (D0={D0})")
# --- 3. 复原过程 ---
# 逆滤波
F_inv = F_deg / (H + 1e-8)
img_inv = np.abs(np.fft.ifft2(np.fft.ifftshift(F_inv)))
# 受限逆滤波 (阈值设为 0.1)
H_lim = np.zeros_like(H)
H_lim[H > 0.1] = 1 / H[H > 0.1]
F_lim = F_deg * H_lim
img_lim = np.abs(np.fft.ifft2(np.fft.ifftshift(F_lim)))
# 维纳滤波
H_wie = np.conj(H) / (np.abs(H)**2 + K_wiener)
F_wie = F_deg * H_wie
img_wie = np.abs(np.fft.ifft2(np.fft.ifftshift(F_wie)))
# NLM 降噪
img_nlm = denoise_nl_means(img_deg, h=1.2*sigma, fast_mode=True, patch_size=5, patch_distance=15)
# --- 4. 可视化与评估 ---
fig, axes = plt.subplots(1, 5, figsize=(20, 5))
titles = ["退化图像", "逆滤波", "受限逆滤波", "维纳滤波", "NLM 降噪"]
imgs = [img_deg, img_inv, img_lim, img_wie, img_nlm]
for ax, i, t in zip(axes, imgs, titles):
p = psnr(img_orig, np.clip(i, 0, 1), data_range=1)
s = ssim(img_orig, np.clip(i, 0, 1), data_range=1)
ax.imshow(i, cmap='gray', vmin=0, vmax=1)
ax.set_title(f"{t}\nPSNR: {p:.2f}\nSSIM: {s:.3f}")
ax.axis('off')
plt.suptitle(f"截止频率 D0 = {D0} 的复原效果对比", fontsize=15)
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
实验结果与分析¶
Q1: 如何评估复原图像的质量?
本实验通过以下两个指标评估复原图像的质量。
- PSNR (Peak Signal-to-Noise Ratio, 峰值信噪比):基于像素间的均方误差(MSE)进行计算,数值越高表明复原图像与真实图像在像素值上的绝对差异越小。
- SSIM (Structural Similarity, 结构相似性):模拟人眼视觉系统,从亮度、对比度和结构三个维度评估相似度。其值域为 $[0, 1]$,越接近 $1$ 说明图像的纹理和解剖结构保留得越完整。
Q2: 如果模糊核未知怎么办?
如果模糊核 $H(u,v)$ 未知,图像复原就变成了一个盲去卷积问题。解决此类问题通常需要利用图像的先验知识。
- 交替迭代优化:如 Richardson-Lucy (RL) 盲去卷积算法,通过最大似然估计或最大后验概率估计(MAP),在“估计模糊核”与“估计原始图像”之间交替迭代,逐步逼近真实解。
- 基于深度学习的方法:利用卷积神经网络(CNN)或生成对抗网络(GAN),通过大量“清晰-退化”图像对的训练,让网络隐式地学习逆映射过程,从而在无需显式知道物理模糊核的情况下直接输出复原图像。
Q3: 使用 NLM 方法对 Rician 噪声进行降噪的原理与意义
MRI 图像的幅度数据服从莱斯分布。在低信噪比区域(如背景),它近似为瑞利分布;在高信噪比区域(如脑实质),近似为高斯分布。这意味着 Rician 噪声是信号相关的,且具有非零均值,传统的线性低通滤波会引起显著的图像对比度下降和边缘模糊。
NLM 的优势在于非局部均值滤波不依赖于局部邻域的简单平均,而是利用整个图像中具有相似纹理结构的图像块来进行加权平均。对于 Rician 噪声,NLM 能够在其相似块搜索机制下,极大地过滤掉随机的噪声波动,同时完美地保持大脑皮层及脑回的锐利边界,是一种在 MRI 降噪中极其有效的非线性策略。
结合给定的三组不同截止频率($D_0 = 30, 60, 90$)的实验结果图,我对不同复原算法的性能表现分析如下。
实验结果与分析¶
结合 $D_0=30, 60, 90$ 的三组退化与复原图像,以及 PSNR 和 SSIM 的定量评估结果,对各项复原算法的性能表现深度分析如下:
截止频率 $D_0$ 对退化与复原的全局影响¶
截止频率 $D_0$ 直接决定了高斯模糊核保留低频成分的频带宽度。对比三组原图退化状态可知,$D_0=30$ 时,图像高频细节丢失最为严重,呈现出极度的模糊感;随着 $D_0$ 提升至 $60$ 和 $90$,图像保留的结构边缘逐渐增多。这一物理退化规律直接反映在后续的复原数据上:随着 $D_0$ 的增大,所有复原算法的客观指标(PSNR, SSIM)均呈现明显的整体上升趋势。
逆滤波¶
在 $D_0=30$ 这种严重模糊的条件下,直接逆滤波已经彻底崩溃,复原图像呈现一片混乱的噪声,完全无法辨认任何脑组织的解剖结构(此时 PSNR 极低,仅为 3.13 dB,SSIM 为 0.05)。这深刻印证了频域逆滤波的致命缺陷:由于 $D_0=30$ 时高斯核在高频处的值极度趋近于 0,将其作为分母进行除法运算时,轻微的 Rician 噪声被呈指数级放大,完全摧毁了图像。而在 $D_0=60$ 和 $90$ 时,高频衰减相对减弱,虽然图像依然布满显著的“雪花状”颗粒噪声,但整体轮廓得以显现(PSNR 回升至 $20$ dB 以上,如 $D_0=90$ 时达到 22.56 dB)。
受限逆滤波¶
为了阻止 $D_0=30$ 时的噪声爆炸,受限逆滤波采用截断高频成分的策略,成功将图像从崩溃边缘拉回(PSNR 提升至17.11 dB)。然而,将量化指标与主观视觉结合来看,虽然 PSNR 有所提升,但所有受限逆滤波的结果图中都出现了极其强烈的振铃效应。这是因为频域的硬性截断等效于在空间域与 sinc 函数发生卷积,导致大脑皮层边缘产生严重的高频震荡。
维纳滤波¶
维纳滤波在本次反卷积复原任务中展现了最佳的综合性能。它不仅克服了直接逆滤波的噪声放大问题,还避免了受限逆滤波的振铃伪影。以 $D_0=90$ 为例,维纳滤波在抑制高频 Rician 噪声和恢复边缘细节之间找到了最优解,其 SSIM 指标达到了全场最高的 0.628(PSNR 达到 22.91 dB)。主观视觉上,脑实质内部的微小孔洞和复杂的脑沟回结构都得到了最高质量的恢复,图像平滑且无破坏性伪影。
NLM 降噪¶
观察 NLM 降噪的结果,可以发现其在各项指标上均表现出显著优势。以 $D_0=90$ 为例,NLM 不仅获得了极高的 PSNR,其 SSIM 指标也超越了维纳滤波 成为全场最优。从主观视觉上看,NLM 处理后的图像背景极为干净,彻底消除了 Rician 噪声的颗粒感,且脑组织的解剖纹理得到了较好的保留。
NLM 作为一种纯降噪算法,在未进行显式去卷积反推的情况下,其结构相似度(SSIM)反而超越了经典的维纳复原,主要有以下两个核心原因
对 Rician 噪声的非线性适应性:维纳滤波的数学推导严格建立在“加性高斯白噪声(AWGN)”的假设基础之上。然而,MRI 中的 Rician 噪声是信号相关的,且具有非零均值。维纳滤波在这种非线性噪声面前表现出了统计学上的不适应,导致去噪不彻底或引入轻微的结构平滑。
块匹配与自相似性优势:非局部均值(NLM)算法摒弃了局部像素的简单平均,转而在全图中搜索相似的图像块进行加权。大脑 MRI 图像(如白质、灰质的纹理)具有极强的自相似性,NLM 利用这种全局结构冗余,在抹除极高强度 Rician 噪声的同时,完美保护了脑沟回的真实边缘。
因此,在面对“高斯模糊 + Rician 噪声”的复合退化模型时,由于 Rician 噪声对图像质量的破坏性极强,NLM 凭借卓越的非线性结构保持能力(去噪而不伤边缘),在整体的结构相似度(SSIM)上击败了受制于线性假设的维纳去卷积滤波。这证明在特定的医学图像退化场景中,高级的空间域自适应降噪策略往往比传统的频域线性复原更为有效。
实验心得¶
在本次实验中,我经历了从理论公式到代码实现,再到发现问题并不断修正算法的完整工程实践过程。这让我对图像处理算法的底层逻辑有了极其深刻的认识。
在项目一噪声估计与去噪实验的初步尝试中,我遇到了一个经典的陷阱,在设定仅有 $5\%$ 密度的椒盐噪声时,程序却估算出了高达 $30\%$ 的密度。通过排查代码与分析数据分布,我深刻认识到这是因为盲目套用统计算法,忽略了“MRI 图像脱骨后包含大面积全黑($0.0$)背景”这一关键先验信息。将算法逻辑修正为“仅统计纯白盐噪声并翻倍”后,估计误差瞬间缩小到了 $0.3\%$ 以内。这让我明白,优秀的医学图像算法绝不是死板的数学公式,而是建立在对特定模态图像物理特性深刻理解基础上的灵活应用。
在探究高斯噪声复原时,我深刻体会到了算法逻辑的演进。单纯的高斯滤波造成的模糊现象让我认识到局部盲目平滑的代价;维纳滤波虽然利用了估计方差保住了边缘,但平坦区残留的斑驳感暴露了局部统计信息的局限性。最终引入 NLM 算法时,我将其核心平滑参数 $h$ 严格与盲估计得到的噪声标准差挂钩。看到图像瞬间纯净且锐利的结果,我深刻理解了生物医学图像处理的两大核心:一是必须精准地盲估计出物理参数作为算法基石,二是必须善于利用图像本身的冗余信息(如全局自相似性)去突破传统局部滤波器的性能天花板。
在项目二的大脑掩膜后处理实验中,我深刻体会到了形态学操作有别于常规灰度滤波的独特魅力。常规滤波处理的是像素的“数值”,而形态学处理的是图像“空间拓扑结构。算法利用开运算果断切断并清除了大脑外部烦人的细小头骨伪影;利用闭运算修复了大脑实质内部因噪声产生的细小空洞。同时,通过最终的误差热力图分析,我也直观地认识到了形态学处理中假阳性(边缘多留)与假阴性(细微连接错删)的博弈。这启发我在医学图像分割的后处理中,结构元素的大小和迭代次数绝不能盲目设置,必须严格依据目标器官的解剖学形态特征来设定。
项目三的频域复原实验理论上完美的直接逆滤波,在遇到微小的莱斯噪声时,因为高频分母趋近于零而导致全局噪声爆炸,复原图像彻底沦为雪花。而随后引入的维纳滤波,通过在分母中加入信噪比常数,利用数学方法在去模糊和抑制噪声放大之间达到了平衡,呈现出了最佳的复原效果。