论文名称:Parameter-Free Fast Pixelwise Non-Local Means Denoising
之前分享了一篇是块级别的NLM denoising (NLM_B),这篇基于像素级的NLM denoising(NLM_P),主要有两种NLM_Pa和NLM_P。差别在于计算块间距离时,NLM_Pa使用的是Gaussian Euclidean norm,而NLM_P使用的是Euclidean norm。当然,和NLM_B不同的是,计算权重时,没有减2*sigma^2了,应该是原论文也没减,是《Non-Local Means Denoising》里减的IPOL Journal · Non-Local Means Denoising。
接下来介绍了基本算法的计算伪代码,并分析了计算复杂度,为后面优化做铺垫。
介绍了几种优化方法,针对NLM_P,由于其特殊性,可以使用积分图进行优化,复杂度可以减低,还介绍了基于傅里叶变换的优化,但图像较大时,计算复杂度就很高了,最后就是文章的重点方法SIL,计算复杂度可以降。OpenCV里的NLM系列函数(fastNlMeansDenoisingColored)应该就是参考这个方法。
由于参数和噪声强度有关,文章分析了下怎么设置最优的参数。 通过构造了两个数据集,以PSNR为标准,得到最优的参数。
最后分析对比了NLM_P和NLM_B的效果,从PSNR数据上看,NLM基本上是优于NLM-P/NLM-Pa,但主观效果是相当的 (感觉有点耍流氓)。
论文公开了C代码(IPOL Journal · Parameter-Free Fast Pixelwise Non-Local Means Denoising),只有SIL那种方法,没有仔细研究了,如果实际会使用该算法的话,再来细看,如果只是看效果的话,可以调用OpenCV里的函数即可。
另外,对于基础算法流程,用Python代码简单写了单通道的(计算权重那一步应该可以提前计算为表,之后查表就行),可以作为参考,根据这个可以扩展为彩色图像的,Python代码会很慢,代码参考了这篇博文非局部均值滤波-A non-local algorithm for image denoising_Nick Blog-CSDN博客1. 简介Non-Local Means顾名思义,这是一种非局部平均算法。何为局部平均滤波算法呢?那是在一个目标像素周围区域平滑取均值的方法,所以非局部均值滤波就意味着它使用图像中的所有像素,这些像素根据某种相似度进行加权平均。滤波后图像清晰度高,而且不丢失细节。2. 原理该算法使用自然图像中普遍存在的冗余信息来去噪声。与双线性滤波、中值滤波等利用图像局部信息来滤波不同,它利用了整幅图像进行去噪。即以图像块为单位在图像中寻找相似区域,再对这些区域进行加权平均平均,较好地滤除图像中的高斯噪声。2.1https://niecongchong.blog.csdn.net/article/details/111304042
def GaussianKernel(d, sigma):
# Generate array
ax = np.arange(-d // 2 + 1., d // 2 + 1.)
# Generate 2D matrices by duplicating ax along two axes
xx, yy = np.meshgrid(ax, ax)
# kernel will be the gaussian over the 2D arrays
kernel = np.exp(-(xx**2 + yy**2) / (2. * sigma**2))
# Normalise the kernel
final = kernel / kernel.sum()
return final
def onLocalMeansGray_NLMP(image, h=10, templateWindowSize=7, searchWindow=21):
height, width = image.shape[0], image.shape[1]
patchWin = int(templateWindowSize / 2)
searchWind = int(searchWindow / 2)
# Padding the image
padLength = patchWin + searchWind
img = cv2.copyMakeBorder(image, padLength, padLength, padLength, padLength, cv2.BORDER_CONSTANT, value=255)
# output image
outImage = np.zeros((height, width), dtype='float')
# generate gaussian kernel matrix of 7*7
kernel = GaussianKernel(templateWindowSize, 1)
# Run the non-local means for each pixel
for j in range(height):
for i in range(width):
padj = j + padLength
padi = i + padLength
centerPatch = img[padj - patchWin: padj + patchWin + 1, padi - patchWin: padi + patchWin + 1]
sumPixel = 0
sumWeight = 0
# Apply Gaussian weighted square distance between patches of 7*7 in a window of 21*21
for r in range(padj - searchWind, padj + searchWind):
for c in range(padi - searchWind, padi + searchWind):
otherPatch = img[r - patchWin: r + patchWin + 1, c - patchWin: c + patchWin + 1]
diff = centerPatch - otherPatch
distance_2 = np.multiply(diff, diff)
pixelWeight = np.sum(np.multiply(kernel, distance_2))
pixelWeight = np.exp(pixelWeight / (h**2))
sumWeight = sumWeight + pixelWeight
sumPixel = sumPixel + pixelWeight * img[r, c]
outImage[j, i] = sumPixel / sumWeight
outImage = np.clip(outImage, 0, 255)
outImage = outImage.astype(np.uint8)
return outImage