本章将学习以下内容:
- 学习非局部均值去噪算法以去除图像中的噪声
- 了解相关函数如
cv2.fastNlMeansDenoising()
、cv2.fastNlMeansDenoisingColored()
等。
理论基础
前几章中,我们学习了多种图像平滑技术(如高斯模糊、中值模糊等),这些技术在一定程度上能够去除少量噪声。 其核心原理是:针对每个像素,取其周围邻域内的像素值,通过高斯加权平均、中值计算等操作替换中心像素值。 简而言之,传统去噪方法的操作仅局限于像素周围的局部邻域。
噪声通常被认为是均值为零的随机变量。考虑一个含噪像素 $p = p_0 + n$,其中 $p_0$是像素的真实值, $n$ 是该像素的噪声。 若从不同图像中获取大量相同像素(例如 $N$ 个)并计算它们的平均值,理想情况下,由于噪声均值为零,最终应得到 $p = p_0$。
可通过一个简单操作自行验证:将相机固定拍摄数秒,获得多帧同一场景图像;编写代码计算所有帧的平均值(这对你而言应很简单),对比最终结果与首帧,可观察到噪声减弱。 但此方法存在明显缺陷:对相机和场景的运动变化不鲁棒,且通常只能获取单张含噪图像。
核心思路是:需多张相似图像以平均掉噪声。 假设图像中存在一个5×5的小窗口,同一图像中其他位置大概率存在相似块(可能位于邻近区域)。 若将这些相似块合并后取平均,即可有效抑制该区域的噪声。参见下图示例:
图像中蓝色块与绿色块具有相似性。具体方法为:选取一个像素,提取其周围小窗口,在图像中搜索相似窗口,将这些窗口取平均后替换原像素。 此方法即非局部均值去噪。相较于之前的模糊方法,该算法耗时更长但效果更优,更多细节及在线演示可参考补充资源中的首个链接。
针对彩色图像,需先转换至CIELAB色彩空间,再分别对L分量与AB分量进行独立去噪。
OpenCV 中的图像去噪
OpenCV提供了四种实现方式:
cv2.fastNlMeansDenoising()
:处理单通道灰度图像cv2.fastNlMeansDenoisingColored()
:处理彩色图像cv2.fastNlMeansDenoisingMulti()
:处理短时间捕获的图像序列(灰度图像)cv2.fastNlMeansDenoisingColoredMulti()
:同上,但适用于彩色图像
通用参数:
h
:决定滤波强度的参数。值越大去噪效果越强,但会损失更多细节(建议值10)。hForColorComponents
:仅用于彩色图像的类似参数(通常与h相同)。templateWindowSize
:需为奇数(推荐7)。searchWindowSize
:需为奇数(推荐21)。
更多参数细节请参考补充资源中的首个链接。
示例演示:(以下展示第2、3种方法,其余自行尝试):
cv2.fastNlMeansDenoisingColored()
用于去除彩色图像噪声(假设噪声为高斯分布)。示例见下图:
%matplotlib inline
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('/data/cvdata/die.png')
dst = cv2.fastNlMeansDenoisingColored(img,None,10,10,7,21)
plt.subplot(121),plt.imshow(img)
plt.subplot(122),plt.imshow(dst)
plt.show()
help(cv2.fastNlMeansDenoisingColored)
Help on built-in function fastNlMeansDenoisingColored: fastNlMeansDenoisingColored(...) fastNlMeansDenoisingColored(src[, dst[, h[, hColor[, templateWindowSize[, searchWindowSize]]]]]) -> dst . @brief Modification of fastNlMeansDenoising function for colored images . . @param src Input 8-bit 3-channel image. . @param dst Output image with the same size and type as src . . @param templateWindowSize Size in pixels of the template patch that is used to compute weights. . Should be odd. Recommended value 7 pixels . @param searchWindowSize Size in pixels of the window that is used to compute weighted average for . given pixel. Should be odd. Affect performance linearly: greater searchWindowsSize - greater . denoising time. Recommended value 21 pixels . @param h Parameter regulating filter strength for luminance component. Bigger h value perfectly . removes noise but also removes image details, smaller h value preserves details but also preserves . some noise . @param hColor The same as h but for color components. For most images value equals 10 . will be enough to remove colored noise and do not distort colors . . The function converts image to CIELAB colorspace and then separately denoise L and AB components . with given h parameters using fastNlMeansDenoising function.
下图为放大后的结果。输入图像包含标准差为 $\sigma = 25$ 的高斯噪声,请观察结果:
cv2.fastNlMeansDenoisingMulti()
现在将同样的方法应用于视频处理。第一个参数是含噪帧的列表。 第二个参数 $imgToDenoiseIndex$ 指定需要去噪的帧,这里传入该帧在输入列表中的索引。 第三个参数 $temporalWindowSize$ 表示用于去噪的邻近帧数量,该值应为奇数。 此时总共使用 $temporalWindowSize$ 帧图像,其中中心帧就是要去噪的目标帧。 例如输入5帧组成的列表,设 $imgToDenoiseIndex = 2$ 且 $temporalWindowSize = 3$,则将使用第1帧、第2帧和第3帧来对第2帧进行去噪处理。
下面来看具体示例。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
cap = cv.VideoCapture('/data/cvdata/vtest.avi')
# create a list of first 5 frames
img = [cap.read()[1] for i in range(5)]
# convert all to grayscale
gray = [cv.cvtColor(i, cv.COLOR_BGR2GRAY) for i in img]
# convert all to float64
gray = [np.float64(i) for i in gray]
# create a noise of variance 25
noise = np.random.randn(*gray[1].shape)*10
# Add this noise to images
noisy = [i+noise for i in gray]
# Convert back to uint8
noisy = [np.uint8(np.clip(i,0,255)) for i in noisy]
# Denoise 3rd frame considering all the 5 frames
dst = cv.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35)
plt.subplot(131),plt.imshow(gray[2],'gray')
plt.subplot(132),plt.imshow(noisy[2],'gray')
plt.subplot(133),plt.imshow(dst,'gray')
plt.show()