目标
在本章中
- 你将学习非局部均值去噪算法,以去除图像中的噪点。
- 你将看到不同的函数,如 cv.fastNlMeansDenoising()、cv.fastNlMeansDenoisingColored()
理论
在前面的章节中,我们已经看到了许多图像平滑技术,如高斯模糊(Gaussian Blurring)、中值模糊(Median Blurring)等,它们在一定程度上可以很好地去除少量噪声。在这些技术中,我们选取像素周围的一个小邻域,然后进行一些操作,如高斯加权平均、数值中值等来替换中心元素。简而言之,像素的噪点去除是在其邻域内进行的。
噪声有一个特性。噪音通常被认为是一个均值为零的随机变量。考虑一个噪声像素,
其中 p0 是像素的真实值,n 是该像素的噪声。您可以从不同的图像中提取大量相同的像素(例如 N),然后计算它们的平均值。理想情况下,您应该得到 p=p0,因为噪声的平均值为零。
您可以通过一个简单的设置来验证这一点。将一个静态摄像头对准某个位置几秒钟。这样就可以获得大量的帧,或者同一场景的大量图像。然后编写一段代码,找出视频中所有帧的平均值(这对你来说应该太简单了)。比较最终结果和第一帧图像。您可以看到噪点的减少。遗憾的是,这种简单的方法对摄像机和场景运动的影响不大。此外,通常情况下只有一张噪点图像可用。
因此,我们需要一组相似的图像来平均噪点。在图像中考虑一个小窗口(比如 5x5 窗口)。在图像的其他地方也有可能出现相同的斑块。有时就在它周围的一个小范围内。把这些相似的斑块放在一起,找出它们的平均值如何?对于该特定窗口来说,这样就可以了。请看下面的示例图像:
图片中的蓝色斑块看起来很相似。绿色斑块看起来相似。因此,我们取一个像素点,在其周围开一个小窗口,搜索图像中的相似窗口,求出所有窗口的平均值,然后用得到的结果替换像素点。这种方法就是非局部均值去噪。与我们之前看到的模糊技术相比,它需要更多时间,但效果非常好。更多详细信息和在线演示,请参见附加资源中的第一个链接。
对于彩色图像,先将图像转换为 CIELAB 色彩空间,然后分别对 L 和 AB 部分进行去噪。
OpenCV 中的图像去噪
OpenCV 提供了该技术的四种变体。
- cv.fastNlMeansDenoising()
- cv.fastNlMeansDenoisingColored()
- cv.fastNlMeansDenoisingMulti()
- cv.fastNlMeansDenoisingColoredMulti()- 与上述方法相同,但用于彩色图像。
常用参数有
- h:决定滤波强度的参数。h 值越大,去除噪点的效果越好,但也会去除图像的细节。(10也可以)
- hForColorComponents:与 h 相同,但只适用于彩色图像。(通常与 h 相同)
- templateWindowSize:应为奇数(建议为 7)
- searchWindowSize : 应该是奇数。
有关这些参数的详细信息,请访问附加资源中的第一个链接。
我们将在此演示 2 和 3。其余部分留给您。
1. cv.fastNlMeansDenoisingColored()
如上所述,它用于去除彩色图像中的噪声。(噪声预计是高斯噪声)。请看下面的示例:
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('die.png')
dst = cv.fastNlMeansDenoisingColored(img,None,10,10,7,21)
plt.subplot(121),plt.imshow(img)
plt.subplot(122),plt.imshow(dst)
plt.show()
以下是放大后的结果。我的输入图像具有 σ=25 的高斯噪声。请看结果:
2. cv.fastNlMeansDenoisingMulti()
现在我们将对视频应用相同的方法。第一个参数是imgToDenoiseIndex。第二个参数 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('vtest.avi')
# 创建前 5 帧的列表
img = [cap.read()[1] for i in range(5)] # 将所有图像转换为灰度图像。
# 将所有画面转换为灰度
gray = [cv.cvtColor(i, cv.COLOR_BGR2GRAY) for i in img] # 将所有图像转换为 float64 格式。
# 将所有图像转换为 float64
gray = [np.float64(i) for i in gray] # 创建方差为 25 的噪声。
# 创建方差为 25 的噪声
noise = np.random.randn(*gray[1].shape)*10
# 将此噪声添加到图像中
noisy = [i+noise for i in gray] # 转换回 uint8 文件格式。
# 转换回 uint8
noisy = [np.uint8(np.clip(i,0,255)) for i in noisy] # 对第三帧图像进行去噪处理。
# 考虑到所有 5 幅图像,对第 3 幅图像进行去噪处理
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()
下图是我们得到的结果的放大版:
这需要大量的计算时间。结果中,第一幅图像为原始图像,第二幅为噪声图像,第三幅为去噪图像。