

图像复原与重建是数字图像处理的核心内容之一,旨在将退化的图像恢复到原始状态。与图像增强不同,图像复原是基于数学模型的客观恢复过程,而增强是主观的视觉改善过程。本文将从退化模型、噪声分析、各类复原算法到图像重建,全方位讲解图像复原技术,并通过 Python 代码实现所有关键算法的可视化对比。


噪声类型 | 概率密度函数(PDF) | 特点 |
|---|---|---|
高斯噪声 | p(z)=2πσ1e−(z−μ)2/(2σ2) | 自然场景中最常见,噪声值服从正态分布 |
椒盐噪声 | 其他 | 由图像传感器、传输错误导致,表现为黑白点 |
泊松噪声 | p(z)=z!λze−λ | 低光照条件下的光子噪声,服从泊松分布 |
瑞利噪声 | p(z)={b2(z−a)e−(z−a)2/b0z≥az<a | 用于描述成像系统的噪声 |
伽马噪声 | p(z)={Γ(b)abzb−1e−az0z≥0z<0 | 适用于激光成像等场景 |
由电力线、机械振动等周期性干扰引起,在频域表现为离散的冲激点,可通过频域滤波消除。
常用方法:
当图像仅受噪声退化时,可通过空间域滤波实现复原。
根据局部区域的统计特性(均值、方差)自适应调整滤波参数,效果优于固定滤波器。
抑制特定频率范围的噪声,分为理想、巴特沃斯、高斯带阻滤波器。
允许特定频率范围通过,可用于提取周期性噪声。
抑制 / 通过特定频率点(而非频率范围),是消除周期性噪声最常用的滤波器。
结合多个陷波滤波器,自适应消除周期性噪声。

从退化图像中选取平坦区域,估计 PSF。
使用与退化图像相同的成像系统,对已知目标成像,直接测量 PSF。
根据物理过程(如运动模糊、大气湍流)建立数学模型,推导 PSF。




基于投影的重建是 CT、MRI 等医学成像的核心技术,通过多角度投影数据重建断层图像。
CT 通过 X 射线源和探测器阵列绕人体旋转,采集不同角度的投影数据,再通过数学方法重建断层图像。



步骤:
针对扇形束扫描(实际 CT 常用)的重建方法,需要进行扇形束到平行束的转换。
import cv2
import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage
from scipy.fftpack import fft2, ifft2, fftshift, ifftshift
import warnings
warnings.filterwarnings('ignore')
# ====================== 全局设置 ======================
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
plt.rcParams['figure.figsize'] = (16, 12)
# ====================== 1. 噪声生成函数 ======================
def add_gaussian_noise(image, mean=0, var=0.001):
"""添加高斯噪声"""
image = np.float32(image) / 255.0
sigma = var ** 0.5
noise = np.random.normal(mean, sigma, image.shape)
noisy_image = image + noise
noisy_image = np.clip(noisy_image, 0, 1)
return np.uint8(noisy_image * 255)
def add_salt_pepper_noise(image, prob=0.05):
"""添加椒盐噪声"""
noisy_image = np.copy(image)
# 盐噪声(白色)
salt = np.random.rand(*image.shape) < prob/2
noisy_image[salt] = 255
# 椒噪声(黑色)
pepper = np.random.rand(*image.shape) < prob/2
noisy_image[pepper] = 0
return noisy_image
def add_poisson_noise(image):
"""添加泊松噪声"""
vals = len(np.unique(image))
vals = 2 ** np.ceil(np.log2(vals))
noisy_image = np.random.poisson(image * vals) / float(vals)
return np.uint8(np.clip(noisy_image, 0, 255))
# ====================== 2. 空间域滤波函数 ======================
def arithmetic_mean_filter(image, kernel_size=3):
"""算术均值滤波"""
return cv2.blur(image, (kernel_size, kernel_size))
def geometric_mean_filter(image, kernel_size=3):
"""几何均值滤波"""
image = np.float32(image) + 1e-8 # 避免0值
kernel = np.ones((kernel_size, kernel_size))
log_image = np.log(image)
filtered = ndimage.convolve(log_image, kernel) / (kernel_size * kernel_size)
filtered = np.exp(filtered)
return np.uint8(np.clip(filtered, 0, 255))
def median_filter(image, kernel_size=3):
"""中值滤波"""
return cv2.medianBlur(image, kernel_size)
def adaptive_median_filter(image, kernel_size=3, max_kernel_size=7):
"""自适应中值滤波"""
h, w = image.shape
pad_size = max_kernel_size // 2
padded = np.pad(image, pad_size, mode='reflect')
result = np.copy(image)
for i in range(h):
for j in range(w):
current_size = kernel_size
while current_size <= max_kernel_size:
# 提取邻域
y1, y2 = i, i + current_size
x1, x2 = j, j + current_size
neighborhood = padded[y1:y2, x1:x2]
# 计算统计量
z_min = np.min(neighborhood)
z_max = np.max(neighborhood)
z_med = np.median(neighborhood)
z_xy = image[i, j]
# 阶段A
A1 = z_med - z_min
A2 = z_med - z_max
if A1 > 0 and A2 < 0:
# 阶段B
B1 = z_xy - z_min
B2 = z_xy - z_max
if B1 > 0 and B2 < 0:
result[i, j] = z_xy
else:
result[i, j] = z_med
break
else:
current_size += 2
if current_size > max_kernel_size:
result[i, j] = z_med
break
return result
# ====================== 3. 频域滤波(周期性噪声抑制) ======================
def create_notch_filter(shape, center, radius=5, notch_type='reject'):
"""创建陷波滤波器"""
rows, cols = shape
crow, ccol = center
mask = np.ones((rows, cols), np.float32)
# 创建圆形掩码
x, y = np.ogrid[:rows, :cols]
mask_area = (x - crow)**2 + (y - ccol)**2 <= radius**2
if notch_type == 'reject':
mask[mask_area] = 0
else: # pass
mask[mask_area] = 1
mask[~mask_area] = 0
# 对称陷波(频域共轭对称)
mask[rows-crow, cols-ccol] = mask[crow, ccol]
return mask
def notch_filtering(image, noise_freqs, radius=5):
"""陷波滤波消除周期性噪声"""
# 转换为浮点型
img_float = np.float32(image) / 255.0
# 傅里叶变换
f = fft2(img_float)
f_shift = fftshift(f)
# 创建陷波滤波器
notch_filter = np.ones_like(f_shift)
for freq in noise_freqs:
notch_filter *= create_notch_filter(img_float.shape, freq, radius)
# 应用滤波器
f_shift_filtered = f_shift * notch_filter
f_ishift = ifftshift(f_shift_filtered)
img_filtered = ifft2(f_ishift)
img_filtered = np.abs(img_filtered)
# 归一化
img_filtered = np.uint8(np.clip(img_filtered * 255, 0, 255))
return img_filtered, f_shift, f_shift_filtered
# ====================== 4. 图像复原(逆滤波、维纳滤波) ======================
def motion_blur_kernel(shape, angle=45, len=15):
"""生成运动模糊核(PSF)"""
kernel = np.zeros(shape)
angle_rad = np.deg2rad(angle)
# 计算模糊核的中心和方向
center = (shape[0]//2, shape[1]//2)
x_len = len * np.cos(angle_rad)
y_len = len * np.sin(angle_rad)
# 生成线条
x = np.linspace(center[0] - x_len/2, center[0] + x_len/2, len)
y = np.linspace(center[1] - y_len/2, center[1] + y_len/2, len)
x = np.round(x).astype(int)
y = np.round(y).astype(int)
# 确保坐标在范围内
mask = (x >= 0) & (x < shape[0]) & (y >= 0) & (y < shape[1])
x = x[mask]
y = y[mask]
kernel[x, y] = 1
kernel = kernel / np.sum(kernel) # 归一化
return kernel
def inverse_filtering(blurred_image, psf, epsilon=1e-6):
"""逆滤波"""
# 转换为浮点型
img_float = np.float32(blurred_image) / 255.0
# 傅里叶变换
f = fft2(img_float)
psf_padded = np.zeros_like(img_float)
psf_padded[:psf.shape[0], :psf.shape[1]] = psf
psf_padded = np.roll(psf_padded, (-psf.shape[0]//2, -psf.shape[1]//2), (0, 1))
# 退化函数的傅里叶变换
H = fft2(psf_padded)
# 逆滤波(添加epsilon避免除以0)
F_hat = f / (H + epsilon)
# 逆傅里叶变换
img_restored = ifft2(F_hat)
img_restored = np.abs(img_restored)
# 归一化
img_restored = np.uint8(np.clip(img_restored * 255, 0, 255))
return img_restored
def wiener_filtering(blurred_image, psf, K=0.01):
"""维纳滤波(简化版,K为噪声功率与信号功率比)"""
# 转换为浮点型
img_float = np.float32(blurred_image) / 255.0
# 傅里叶变换
f = fft2(img_float)
psf_padded = np.zeros_like(img_float)
psf_padded[:psf.shape[0], :psf.shape[1]] = psf
psf_padded = np.roll(psf_padded, (-psf.shape[0]//2, -psf.shape[1]//2), (0, 1))
# 退化函数的傅里叶变换
H = fft2(psf_padded)
H_conj = np.conj(H)
# 维纳滤波
F_hat = (H_conj / (np.abs(H)**2 + K)) * f
# 逆傅里叶变换
img_restored = ifft2(F_hat)
img_restored = np.abs(img_restored)
# 归一化
img_restored = np.uint8(np.clip(img_restored * 255, 0, 255))
return img_restored
# ====================== 5. 主函数:演示所有关键算法 ======================
def main():
# 1. 读取图像(转为灰度图)
img = cv2.imread('lena.jpg', 0) # 替换为你的图像路径
if img is None:
print("无法读取图像,请检查路径!")
print("使用内置测试图像...")
# 创建测试图像(512x512 Lena替代)
img = np.uint8(ndimage.imread('https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png', mode='L'))
# ====================== 实验1:噪声生成与空间域滤波 ======================
print("=== 实验1:噪声生成与空间域滤波 ===")
# 添加不同噪声
img_gaussian = add_gaussian_noise(img, var=0.01)
img_salt_pepper = add_salt_pepper_noise(img, prob=0.05)
img_poisson = add_poisson_noise(img)
# 空间域滤波
# 高斯噪声滤波
img_gaussian_mean = arithmetic_mean_filter(img_gaussian, 3)
img_gaussian_geo = geometric_mean_filter(img_gaussian, 3)
# 椒盐噪声滤波
img_salt_pepper_median = median_filter(img_salt_pepper, 3)
img_salt_pepper_adaptive = adaptive_median_filter(img_salt_pepper, 3, 7)
# 可视化对比
plt.figure(figsize=(20, 15))
# 原始图像
plt.subplot(3, 4, 1)
plt.imshow(img, cmap='gray')
plt.title('原始图像', fontsize=12)
plt.axis('off')
# 高斯噪声及滤波
plt.subplot(3, 4, 2)
plt.imshow(img_gaussian, cmap='gray')
plt.title('添加高斯噪声', fontsize=12)
plt.axis('off')
plt.subplot(3, 4, 3)
plt.imshow(img_gaussian_mean, cmap='gray')
plt.title('算术均值滤波 (3x3)', fontsize=12)
plt.axis('off')
plt.subplot(3, 4, 4)
plt.imshow(img_gaussian_geo, cmap='gray')
plt.title('几何均值滤波 (3x3)', fontsize=12)
plt.axis('off')
# 椒盐噪声及滤波
plt.subplot(3, 4, 5)
plt.imshow(img_salt_pepper, cmap='gray')
plt.title('添加椒盐噪声', fontsize=12)
plt.axis('off')
plt.subplot(3, 4, 6)
plt.imshow(img_salt_pepper_median, cmap='gray')
plt.title('中值滤波 (3x3)', fontsize=12)
plt.axis('off')
plt.subplot(3, 4, 7)
plt.imshow(img_salt_pepper_adaptive, cmap='gray')
plt.title('自适应中值滤波', fontsize=12)
plt.axis('off')
# 泊松噪声
plt.subplot(3, 4, 8)
plt.imshow(img_poisson, cmap='gray')
plt.title('添加泊松噪声', fontsize=12)
plt.axis('off')
plt.tight_layout()
plt.show()
# ====================== 实验2:周期性噪声与陷波滤波 ======================
print("=== 实验2:周期性噪声与陷波滤波 ===")
# 生成周期性噪声
rows, cols = img.shape
x, y = np.meshgrid(np.arange(cols), np.arange(rows))
# 添加正弦噪声(模拟周期性噪声)
noise = 30 * np.sin(2 * np.pi * 10 * x / cols) + 30 * np.sin(2 * np.pi * 10 * y / rows)
img_periodic_noise = img + noise
img_periodic_noise = np.uint8(np.clip(img_periodic_noise, 0, 255))
# 陷波滤波
# 噪声频率位置(频域中心附近)
noise_freqs = [(rows//2, cols//2 + cols//20), (rows//2 + rows//20, cols//2)]
img_notch_filtered, f_shift, f_shift_filtered = notch_filtering(img_periodic_noise, noise_freqs)
# 可视化
plt.figure(figsize=(18, 6))
plt.subplot(1, 3, 1)
plt.imshow(img_periodic_noise, cmap='gray')
plt.title('含周期性噪声的图像', fontsize=12)
plt.axis('off')
plt.subplot(1, 3, 2)
plt.imshow(20 * np.log1p(np.abs(f_shift)), cmap='gray')
plt.title('噪声图像的频域', fontsize=12)
plt.axis('off')
plt.subplot(1, 3, 3)
plt.imshow(img_notch_filtered, cmap='gray')
plt.title('陷波滤波复原图像', fontsize=12)
plt.axis('off')
plt.tight_layout()
plt.show()
# ====================== 实验3:运动模糊与图像复原 ======================
print("=== 实验3:运动模糊与图像复原 ===")
# 生成运动模糊核
psf = motion_blur_kernel((15, 15), angle=45, len=15)
# 添加运动模糊
img_blurred = ndimage.convolve(img, psf, mode='reflect')
# 添加少量高斯噪声
img_blurred_noisy = add_gaussian_noise(img_blurred, var=0.001)
# 图像复原
img_inverse = inverse_filtering(img_blurred_noisy, psf)
img_wiener = wiener_filtering(img_blurred_noisy, psf, K=0.01)
# 可视化
plt.figure(figsize=(20, 8))
plt.subplot(1, 5, 1)
plt.imshow(img, cmap='gray')
plt.title('原始图像', fontsize=12)
plt.axis('off')
plt.subplot(1, 5, 2)
plt.imshow(img_blurred, cmap='gray')
plt.title('运动模糊图像', fontsize=12)
plt.axis('off')
plt.subplot(1, 5, 3)
plt.imshow(img_blurred_noisy, cmap='gray')
plt.title('模糊+噪声图像', fontsize=12)
plt.axis('off')
plt.subplot(1, 5, 4)
plt.imshow(img_inverse, cmap='gray')
plt.title('逆滤波复原', fontsize=12)
plt.axis('off')
plt.subplot(1, 5, 5)
plt.imshow(img_wiener, cmap='gray')
plt.title('维纳滤波复原', fontsize=12)
plt.axis('off')
plt.tight_layout()
plt.show()
# ====================== 运行主函数 ======================
if __name__ == "__main__":
# 安装依赖(如果需要)
# !pip install opencv-python numpy matplotlib scipy
main()


Python 3.7+
依赖库:
pip install opencv-python numpy matplotlib scipyimage_restoration.pypython image_restoration.pyadd_gaussian_noise中的var参数、add_salt_pepper_noise中的prob参数kernel_size参数wiener_filtering中的K参数(噪声功率比)📌 本文所有代码均可直接运行,建议你修改参数、更换测试图像,亲身体验不同算法的效果差异,加深对图像复原技术的理解