NLM 图像降噪算法以及 Python 实现

Non-Local Means Denoising, IPOL 2011

论文链接:https://www.ipol.im/pub/art/2011/bcm_nlm/article.pdf

请添加图片描述

是啥


基于相似性原理的图像去噪算法,与传统的局部均值滤波(平均,中值)不同,NLM 可以处理复杂的噪声模型,更好的保留图像细节

总得来说,NLM 算法的优缺点如下

优点:

  1. 去除噪音的同时,能够有效的保留图像的细节和纹理,降噪效果较好
  2. 不需要先验知识,可以处理多种不同类型的图像噪声
  3. 原理简单,易于实现

缺点:

  1. 复杂度高,运算速度较慢
  2. 对噪声强度和分布敏感,需要对参数进行合理设置才有好效果
  3. 处理低对比度和均匀纹理时,效果不佳

怎么做


先来看看整个算法流程

算法流程

在这里插入图片描述

**参考窗口(Reference Patch)**为像素块的大小,为奇数,如 5,9

**搜索窗口(Search Window)**为邻域窗口移动的范围,为奇数,如 11,13

算法是逐像素点处理的,结合上面的流程图,可以分为以下几个步骤

  1. 生成窗口:以像素点为中心生成参考窗口大小的像素块,并以滑动窗口的方式在搜索窗口内生成候选像素块
  2. 相似度匹配:计算参考窗口与候选窗口之间的相似度,获得 Top-N 个相似窗口
  3. 加权平均:将相似像素块进行加权平均,再使用高斯核获得最终降噪后的像素点的新值
  4. 重构:新值替代第一步原图中心像素点,对图像所有像素都重复上面三步,得到降噪后的图片

NLM 采用滑动窗口来寻找相似块,下图以 y 为中心的像素块,它会从左向右,从上往下寻找
在这里插入图片描述

局部和非局部

局部滤波(Local Filter)是使用当前像素的临近像素值来对图像进行降噪

非局部滤波(Non-local Filter)在这里的意思是寻找了相似块来进行降噪

相似块如何降噪

如下图所示,图像内部是存在相似的图像块,如右上角三个蓝色的矩形框所示,这三个矩形框的像素值我们可以用 P0,P1,P2 表示,那么

P0 = v0 + n0

P1 = v1 + n1

P2 = v2 + n2

其中 v 表示像素值,而 n 表示噪音且服从独立高斯分布,我们找到这些相似图像块并且计算像素均值,那么随机噪声就会被削弱,而本身的像素值 v 会被保留下来,从而达到去除噪声的同时保留图像细节
请添加图片描述

非局部均值滤波(Non-local Means Filter)

欧几里得距离,用来度量相似的块

**相似块权重,**卷积后接幂函数获得

可用以下两个公式来描述非局部均值滤波的计算

在这里插入图片描述

u 表示降噪后的图像,v 表示带噪音的图像,而 w(x, xi) 则表示权重值

在计算权重的公式中,B 表示两个像素块的欧几里得距离,Z 表示归一化系数,为所有权重之和

h 越小,权重衰减速度越快,更倾向于叠加那些误差小的块,肉眼来看就是降噪强度低,细节保留较多

h 越大,权重衰减速度约慢,近似于等权叠加,肉眼来看就是降噪强度高,细节保留较少

获得相似块后,不是直接加权叠加获得新块,而是只有每个块的中心像素点才参与计算,因为就算是相似快,块内还是会有差别,叠加会让图像变得混乱

Python 代码实现


详细的可以看下面代码的实现

import cv2
import numpy as np
def psnr(A, B):
    return 10*np.log(255*255.0/(((A.astype(np.float)-B)**2).mean()))/np.log(10)

def double2uint8(I, ratio=1.0):
    return np.clip(np.round(I*ratio), 0, 255).astype(np.uint8)

def make_kernel(f):
    """
    生成高斯核,越靠近中心位置的像素,权重越高
    """
    kernel = np.zeros((2*f+1, 2*f+1))
    for d in range(1, f+1):
        kernel[f-d:f+d+1, f-d:f+d+1] += (1.0/((2*d+1)**2))
    return kernel/kernel.sum()

def NLmeansfilter(I, h_=10, templateWindowSize=5,  searchWindowSize=11):
    f = templateWindowSize//2
    t = searchWindowSize//2
    height, width = I.shape[:2]
    padLength = t+f
    I2 = np.pad(I, padLength, 'symmetric')
    kernel = make_kernel(f)
    h = (h_**2)
    I_ = I2[padLength-f:padLength+f+height, padLength-f:padLength+f+width]

    average = np.zeros(I.shape)
    sweight = np.zeros(I.shape)
    wmax =  np.zeros(I.shape)
    for i in range(-t, t+1):
        for j in range(-t, t+1):
            if i==0 and j==0:
                continue
            I2_ = I2[padLength+i-f:padLength+i+f+height, padLength+j-f:padLength+j+f+width]
            # TODO: filter2D 的作用
            w = np.exp(-cv2.filter2D((I2_ - I_)**2, -1, kernel)/h)[f:f+height, f:f+width]
            sweight += w
            wmax = np.maximum(wmax, w)
            average += (w*I2_[f:f+height, f:f+width])
    # 原图像需要乘于最大权重参与计算
    average += (wmax*I)
    # sweight 为 weight 之和,用于计算 weight 的归一化
    sweight += wmax
    return average / sweight

if __name__ == '__main__':
    I = cv2.imread('lena.jpg', 0)
    sigma = 20.0
    I1 = double2uint8(I + np.random.randn(*I.shape) *sigma)
    print('噪声图像PSNR',psnr(I, I1))
    R1  = cv2.medianBlur(I1, 5)
    print('中值滤波PSNR',psnr(I, R1))
    R2 = cv2.fastNlMeansDenoising(I1, None, sigma, 5, 11)
    print('opencv的NLM算法',psnr(I, R2))
    R3 = double2uint8(NLmeansfilter(I1.astype(np.float), sigma, 5, 11))
    print('NLM PSNR',psnr(I, R3))

其中,关键的代码是下面这行

w = np.exp( # 使用 exp 来约束 w 取值范围为 0-1 之间
    # (I2_ - I_) ** 2 为计算欧几里得距离
    -cv2.filter2D((I2_ - I_) ** 2, -1, kernel) / h
)[f:f + height, f:f + width]

cv2.filter2D 的计算逻辑如下,anchor 默认为 (-1, -1),可以理解为是卷积操作

在这里插入图片描述

np.exp(-x/h) ,当 h = 1 时函数曲线如下

在这里插入图片描述

参数 h 为平滑参数,数值越大,函数曲线越平缓,去噪效果越强,但同时也会导致图像模糊,h 越小,边缘细节保持的越多,同样的也会保留噪声,需要根据实际情况进行调整

参考链接


https://www.youtube.com/watch?v=K29QpWil83g

https://blog.csdn.net/qq_39594939/article/details/100081204

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是使用贝叶斯优化时序信号NLM去噪的Python代码实现: ```python import numpy as np from skimage.restoration import denoise_nl_means from bayes_opt import BayesianOptimization def nlmeans_denoise(sigma, h, patch_size, patch_distance, image): # 生成NLM去噪器 denoiser = denoise_nl_means(image, patch_size=patch_size, patch_distance=patch_distance, h=h, sigma=sigma) # 对图像进行去噪 denoised_image = denoiser(image) # 计算去噪效果 mse = np.mean((denoised_image - image) ** 2) return -mse # 定义参数的搜索范围 pbounds = {'sigma': (0, 50), 'h': (0, 2), 'patch_size': (5, 25), 'patch_distance': (5, 25)} # 创建Bayesian优化器 optimizer = BayesianOptimization( f=nlmeans_denoise, pbounds=pbounds, verbose=2, # 控制输出信息的详细程度 random_state=1, # 控制随机数生成的种子 ) # 开始优化 optimizer.maximize( init_points=10, # 初始采样点数 n_iter=30, # 迭代次数 ) # 输出最优参数和目标函数值 print(optimizer.max) ``` 在这段代码中,我们首先定义了一个名为`nlmeans_denoise`的函数,用于计算NLM去噪的效果。该函数的输入参数包括四个超参数:`sigma`、`h`、`patch_size`和`patch_distance`,以及一个待去噪的图像`image`。函数使用这些超参数生成了一个NLM去噪器,并使用该去噪器对图像进行了去噪,最后计算并返回了去噪效果(使用均方误差作为评价指标)。需要注意的是,由于Bayesian优化器默认是最大化目标函数,因此我们在这里将均方误差的相反数返回,以达到最小化该指标的效果。 接着,我们定义了一个名为`pbounds`的字典,用于指定超参数的搜索范围。在本例中,我们将`sigma`的搜索范围限定在0到50之间,将`h`的搜索范围限定在0到2之间,将`patch_size`的搜索范围限定在5到25之间,将`patch_distance`的搜索范围限定在5到25之间。 然后,我们创建了一个名为`optimizer`的Bayesian优化器,并将NLM去噪函数`nlmeans_denoise`作为目标函数传递给了它。我们还传递了上一步中定义的搜索范围,以及一些其他参数,如`verbose`(控制输出信息的详细程度)和`random_state`(控制随机数生成的种子)。 最后,我们使用`maximize`方法开始进行优化。我们指定了初始采样点数为10,迭代次数为30。在优化结束后,我们输出了最优参数和目标函数值。 需要注意的是,由于NLM去噪器的计算成本较高,因此本例中的优化过程可能需要较长时间才能完成。如果想要加快运行速度,可以尝试更小的搜索范围或减少迭代次数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值