图像去噪#DCNN去噪Python代码 双边滤波、双边滤波、巴特沃斯高通滤波、同态滤波、各项异...
图像去噪#DCNN去噪Python代码 双边滤波、双边滤波、巴特沃斯高通滤波、同态滤波、各项异性扩散滤波、均值滤波、维纳滤波、小波三级软阈值去噪、自适应中值滤波、高斯滤波 MATLAB程序代码
前阵子翻到小时候拍的胶卷扫描照,噪点密得像撒了一把细盐,本来想靠手机修图APP磨个皮就完,结果越磨人脸都糊成马赛克了,才想起不如自己写点简单的去噪脚本试试——这就整理了最近摸鱼试的十几种常用去噪方法,从傻瓜式的一键滤波到自己搭的DCNN都有,代码也直接贴出来,嫌麻烦的直接抄就能用。
先从入门级的线性滤波玩起
最基础的就是均值滤波和高斯滤波,属于无脑好用的类型:
- 均值滤波就是把窗口里所有像素直接拉平均,不管邻域里的像素差多少,适合均匀噪点但容易糊边缘
- 高斯滤波给邻域像素加了高斯权重,离中心越近的像素越管用,保留细节比均值强太多
import cv2
import numpy as np
noisy_img = cv2.imread("old_photo.jpg")
# 均值滤波,5x5窗口就是周围25个像素平均
mean_filtered = cv2.blur(noisy_img, (5,5))
# 高斯滤波,sigmaX控制高斯分布的宽度,越大越模糊
gaussian_filtered = cv2.GaussianBlur(noisy_img, (5,5), sigmaX=1.5)
cv2.imwrite("mean_result.jpg", mean_filtered)
cv2.imwrite("gaussian_result.jpg", gaussian_filtered)
我当时跑自己的猫片时发现,高斯滤波对付均匀的高斯白噪真的一绝,拍夜景的彩噪压得干干净净,还没把猫的胡须磨糊,但要是碰到那种突然的黑白椒盐噪,这俩直接拉胯。
对付椒盐噪就靠中值滤波
椒盐噪就是那种突然冒出来的黑白点,像素值要么特别亮要么特别暗,普通线性滤波根本搞不定,这时候就得靠中值滤波——把窗口里的像素排序,取中间值替换中心像素,噪点直接被筛掉。
图像去噪#DCNN去噪Python代码 双边滤波、双边滤波、巴特沃斯高通滤波、同态滤波、各项异性扩散滤波、均值滤波、维纳滤波、小波三级软阈值去噪、自适应中值滤波、高斯滤波 MATLAB程序代码
普通中值滤波会把细线条磨平,所以我更爱用自适应中值,会自动调整窗口大小:小窗口对付小噪点,大窗口对付密集噪点,还能保住发丝、边缘这类细节。
def adaptive_median_filter(img, s_max=7):
h, w, c = img.shape
res = np.zeros_like(img)
for channel in range(c):
for i in range(h):
for j in range(w):
s = 1
while s <= s_max:
# 自动调整窗口范围,别超出图片边界
top = max(0, i - s)
bottom = min(h-1, i + s)
left = max(0, j - s)
right = min(w-1, j + s)
window = img[top:bottom+1, left:right+1, channel].flatten()
window_sorted = np.sort(window)
med = window_sorted[len(window_sorted)//2]
min_val, max_val = window_sorted[0], window_sorted[-1]
# 判断当前像素是不是噪点,不是就保留原图
if min_val < med < max_val:
if min_val < img[i,j,channel] < max_val:
res[i,j,channel] = img[i,j,channel]
else:
res[i,j,channel] = med
break
else:
s +=1
# 到最大窗口还没搞定就直接用中值
if s > s_max:
res[i,j,channel] = med
return res
# 跑起来试试
adaptive_median_result = adaptive_median_filter(noisy_img)
cv2.imwrite("adaptive_median.jpg", adaptive_median_result)
亲测这个代码对付密集椒盐噪特别好用,连猫的胡须都没被磨平,比我之前用的手机修图APP强多了。
保边去噪选双边滤波
之前用这个给奶奶的老照片去噪,她脸上的皱纹一点没被磨掉,比高斯滤波靠谱太多。原理就是不仅看像素的空间距离,还看像素值的差异:边缘的像素值差大,权重就低,所以不会把边缘糊掉。
# opencv自带的函数,参数d是窗口直径,sigmaColor和sigmaSpace控制滤波强度
bilateral_filtered = cv2.bilateralFilter(noisy_img, d=9, sigmaColor=75, sigmaSpace=75)
cv2.imwrite("bilateral.jpg", bilateral_filtered)
唯一的缺点就是慢,毕竟要算每个像素的空间和颜色权重,处理大尺寸图片得等一会儿,但日常修修小图完全ok。
对付光照不均的同态滤波
老照片经常一边亮一边暗,还带着扫描的网点噪点,这时候同态滤波就派上用场了。它会把图像拆成光照分量和反射分量,先把光照拉平,再去掉噪点,一举两得。
from skimage import exposure, img_as_float
def homomorphic_filter(img, cutoff=30, gamma_h=1.5, gamma_l=0.5):
img_float = img_as_float(img)
# 取对数分离光照和反射分量
img_log = np.log1p(img_float)
f = np.fft.fft2(img_log)
fshift = np.fft.fftshift(f)
# 构建巴特沃斯高通滤波器,压低频光照分量
rows, cols = img.shape[:2]
crow, ccol = rows//2, cols//2
mask = 1 - np.exp(-((np.arange(rows)[:,None]-crow)**2 + (np.arange(cols)-ccol)**2)/(2*cutoff**2))
fshift_filtered = fshift * mask
img_back = np.real(np.fft.ifft2(np.fft.ifftshift(fshift_filtered)))
# 取指数还原图像
img_exp = np.expm1(img_back)
return exposure.rescale_intensity(img_exp, out_range=(0,1))*255
# 处理灰度图效果最好
homomorphic_result = homomorphic_filter(cv2.cvtColor(noisy_img, cv2.COLOR_BGR2GRAY))
cv2.imwrite("homomorphic.jpg", homomorphic_result)
我一般把cutoff设成30左右,太大容易把细节滤掉,太小又拉不平光照,调起来也很省心。
频域滤波选手:巴特沃斯高通滤波+维纳滤波
巴特沃斯高通滤波
专门用来去掉低频的平滑噪点,比如扫描的网点纹、背景的模糊噪点,也可以用来提取图像边缘:
from scipy import ndimage
def butterworth_highpass(img, cutoff=10, order=2):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
f = np.fft.fft2(img_gray)
fshift = np.fft.fftshift(f)
rows, cols = img_gray.shape
crow, ccol = rows//2, cols//2
# 巴特沃斯高通掩码
mask = 1 / (1 + (cutoff / np.sqrt((np.arange(rows)[:,None]-crow)**2 + (np.arange(cols)-ccol)**2)))**(2*order)
img_back = np.real(np.fft.ifft2(np.fft.ifftshift(fshift*mask)))
return np.uint8(255*(img_back - img_back.min())/(img_back.max()-img_back.min()))
维纳滤波
比高斯滤波更智能的统计滤波,会估计原始图像和噪声的功率谱,适合已知噪声方差的场景,比如热成像图、相机噪点:
from skimage.restoration import wiener
img_gray = cv2.cvtColor(noisy_img, cv2.COLOR_BGR2GRAY)
# 先估计一下噪声方差
noise_var = np.var(img_gray - cv2.GaussianBlur(img_gray, (5,5), 1.5))
wiener_result = wiener(img_gray, balance=noise_var/0.1)
cv2.imwrite("wiener.jpg", wiener_result*255)
更硬核的保边:各项异性扩散滤波
也就是Perona-Malik滤波,比双边滤波的保边效果还强,会根据像素梯度调整扩散速度:边缘梯度大的地方扩散慢,不会磨糊边缘。
def perona_malik_filter(img, iterations=50, k=50, option=1):
img = img_as_float(img)
if len(img.shape) ==3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
h, w = img.shape
I = img.copy()
for _ in range(iterations):
# 计算四个方向的像素梯度
N = np.zeros_like(I); S = np.zeros_like(I)
E = np.zeros_like(I); W = np.zeros_like(I)
N[1:,:] = I[:-1,:] - I[1:,:]
S[:-1,:] = I[1:,:] - I[:-1,:]
E[:,1:] = I[:,:-1] - I[:,1:]
W[:,:-1] = I[:,1:] - I[:,:-1]
# 计算权重,两种可选方案
if option ==1:
c = np.exp(-(np.abs([N,S,E,W]))/k)
else:
c = 1/(1 + (np.abs([N,S,E,W])/k)**2)
# 更新图像
I = I + 0.25*(c[0]*N + c[1]*S + c[2]*E + c[3]*W)
return np.uint8(I*255)
pm_result = perona_malik_filter(noisy_img, iterations=30, k=30)
cv2.imwrite("pm_filter.jpg", pm_result)
这个处理星空图特别好用,星星的边缘一点没糊,噪点全没了,就是迭代次数多的话会慢一点,不过胜在效果自然。
小波三级软阈值去噪
属于频域去噪的天花板之一,把图像分解成小波系数,把小的噪点系数阈值掉,再重构图像,对高斯白噪的效果特别好。
import pywt
def wavelet_soft_threshold_filter(img, wavelet='db4', level=3, threshold=0.2):
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 小波三级分解
coeffs = pywt.wavedec2(img_gray, wavelet, level=level)
# 软阈值处理细节系数
for i in range(1, level+1):
cA, (cH, cV, cD) = coeffs[i-1], coeffs[i]
coeffs[i] = (cA, (pywt.threshold(cH, threshold, 'soft'),
pywt.threshold(cV, threshold, 'soft'),
pywt.threshold(cD, threshold, 'soft')))
# 重构图像
img_recon = pywt.waverec2(coeffs, wavelet)
return np.uint8(np.clip(img_recon,0,1)*255)
wavelet_result = wavelet_soft_threshold_filter(noisy_img, threshold=0.15)
cv2.imwrite("wavelet.jpg", wavelet_result)
阈值别设太大,不然会把细节磨没,我一般用0.1到0.2之间的数,小波基用db4就够用,不用搞太复杂的。
最后上深度学习:DCNN去噪
传统方法再强也不如深度学习的效果好,这里贴一个极简的U-Net风格去噪网络,适合新手快速上手:
import torch
import torch.nn as nn
import torch.nn.functional as F
class DenoiseCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, 3, padding=1)
self.conv2 = nn.Conv2d(64, 64, 3, padding=1)
self.conv3 = nn.Conv2d(64, 3, 3, padding=1)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = self.conv3(x)
return x
# 推理的时候直接加载训练好的模型就行
def dcnn_denoise(img, model_path="denoise_model.pth"):
model = DenoiseCNN()
model.load_state_dict(torch.load(model_path, map_location="cpu"))
model.eval()
img_tensor = torch.from_numpy(img.transpose(2,0,1)).float()/255.0
img_tensor = img_tensor.unsqueeze(0)
with torch.no_grad():
out = model(img_tensor)
out_img = np.clip(out.squeeze(0).numpy().transpose(1,2,0),0,1)*255
return np.uint8(out_img)
这个需要用带噪和干净的数据集训练,我之前用DIV2K训了一个小模型,处理手机拍的噪点图,效果比所有传统方法都好,就是对新手不太友好,不过套现成的预训练模型就能直接用。
补一个MATLAB版的常用代码
很多做图像处理的同学还是习惯用MATLAB,贴个自适应中值滤波的MATLAB版:
function img_out = adaptive_median_filter(img, s_max)
if nargin < 2
s_max = 7;
end
[h, w, c] = size(img);
img_out = zeros(h, w, c, 'like', img);
for ch = 1:c
for i = 1:h
for j = 1:w
s = 1;
while s <= s_max
top = max(1, i - s);
bottom = min(h, i + s);
left = max(1, j - s);
right = min(w, j + s);
window = img(top:bottom, left:right, ch);
window_sorted = sort(window(:));
med = window_sorted(floor(length(window_sorted)/2)+1);
min_val = window_sorted(1);
max_val = window_sorted(end);
if min_val < med && med < max_val
if min_val < img(i,j,ch) && img(i,j,ch) < max_val
img_out(i,j,ch) = img(i,j,ch);
else
img_out(i,j,ch) = med;
end
break;
else
s = s + 1;
end
end
if s > s_max
img_out(i,j,ch) = med;
end
end
end
end
end
% 调用示例
% noisy_img = imread('old_photo.jpg');
% img_filtered = adaptive_median_filter(noisy_img);
% imwrite(img_filtered, 'adaptive_median_matlab.jpg');
其实这些方法没有绝对的好坏,得看你的噪点类型是什么:比如椒盐噪就用中值滤波,高斯白噪就用小波或者高斯滤波,光照不均的老照片就用同态滤波,要是追求效果又有训练数据就上DCNN。我一般都是先试传统方法,不行再上深度学习,毕竟调参比训模型快多了😎
要是你有更好的去噪方法,或者调参踩过的坑,欢迎在评论区唠唠。

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)