文章目录
- [一、图像去噪(Image Denoising)](#一、图像去噪(Image Denoising))
- 二、函数详解
- 三、项目实战
-
- [实战1:cv.fastNlMeansDenoising() ------ 单张灰度图像去噪](#实战1:cv.fastNlMeansDenoising() —— 单张灰度图像去噪)
- [实战2:cv.fastNlMeansDenoisingColored() ------ 单张彩色图像去噪](#实战2:cv.fastNlMeansDenoisingColored() —— 单张彩色图像去噪)
- [实战3:cv.fastNlMeansDenoisingMulti() ------ 多帧灰度图像去噪(增强单帧效果)](#实战3:cv.fastNlMeansDenoisingMulti() —— 多帧灰度图像去噪(增强单帧效果))
- [实战4:cv.fastNlMeansDenoisingColoredMulti() ------ 多帧彩色图像去噪(增强单帧效果)](#实战4:cv.fastNlMeansDenoisingColoredMulti() —— 多帧彩色图像去噪(增强单帧效果))
- 实战5:手搓代码(不加速版本)
一、图像去噪(Image Denoising)
(1)基本原理 ------ 基于非局部均值(Non-Local Means, NLM)滤波算法
cv2.fastNlMeansDenoising() 是一种基于非局部均值去噪(Non-Local Means, NLM)的方法,旨在去除图像中的噪声。其核心思想是在图像的局部区域内,通过计算相似性并加权平均周围像素的值来减少噪声,同时保持图像的细节和边缘。
理解区别
传统的均值滤波:是一种局部滤波方法,通过计算当前像素及其邻域像素(如3x3、5x5等)的加权平均值来平滑图像。所有邻域像素的贡献通常被认为是相同的(等权重),且由于只依赖局部区域,导致了图像在去噪时可能会出现模糊,尤其是在图像边缘部分。非局部均值滤波(NLM):是一种全局搜索滤波方法,计算每个像素与其他区域(除了当前像素所在区域之外的所有其他区域)的相似度,并使用这些相似区域的加权平均值来替换原有像素值。
(2)算法步骤
- (1)确定目标像素和邻域:选择需要去噪的目标像素
I(x, y),并为每个目标像素定义一个搜索窗口。这个窗口决定了算法在图像中搜索与当前像素相似的区域。搜索窗口通常包括目标像素周围的一定数量的像素。- (2)计算像素块的相似性:计算目标像素的周围区域(通常是一个小块区域)与搜索窗口内所有其他区域的相似度 。相似度度量通常基于欧几里得距离 (或其他距离度量,如曼哈顿距离)来计算。假设目标像素周围有一个
m×m的区域,算法将把每个像素周围的m×m区域视为一个小块 ,并计算当前像素的邻域与其他所有小块的差异。通常,使用以下的相似性度量公式:
sim ( I 1 , I 2 ) = exp ( − ∥ I 1 − I 2 ∥ 2 h 2 ) \text{sim}(I_1, I_2) = \exp\left(\frac{-\| I_1 - I_2 \|^2}{h^2}\right) sim(I1,I2)=exp(h2−∥I1−I2∥2)
其中:
- I 1 I_1 I1 和 I 2 I_2 I2 分别是两个区域的像素值。
- ∥ I 1 − I 2 ∥ 2 \| I_1 - I_2 \|^2 ∥I1−I2∥2 是两个区域之间的欧几里得距离的平方。
- h h h 是平滑参数,用于控制相似度的衰减。通常需要通过实验来选择合适的 h h h 值。
- (3)加权函数(Weight Function): 将相似性度量转化为权重,决定了周围像素对目标像素去噪结果的影响程度。在 NLM 中,最常见的加权函数是基于相似度的指数函数 ,即随着两块区域差异的增大,权重逐渐减小。权重 w ( x ′ , y ′ ) w(x', y') w(x′,y′) 可以定义为:
w ( x ′ , y ′ ) = exp ( − ∥ I ( x , y ) − I ( x ′ , y ′ ) ∥ 2 h 2 ) w(x', y') = \exp\left(\frac{-\| I(x, y) - I(x', y') \|^2}{h^2}\right) w(x′,y′)=exp(h2−∥I(x,y)−I(x′,y′)∥2)
其中:
- I ( x , y ) I(x, y) I(x,y) 和 I ( x ′ , y ′ ) I(x', y') I(x′,y′) 是当前像素和候选像素的邻域块。
- h h h 是一个控制相似度阈值的参数,值越小,只有相似度非常高的像素才会对去噪产生较大影响。
- (4)加权平均:根据计算出的相似性,算法为每个像素计算加权平均值。与目标像素相似度较高的像素会对去噪结果贡献更多,而相似度较低的像素则贡献较少。这是通过对每个像素周围的小块区域计算加权平均值来实现的。去噪公式如下:
I ′ ( x , y ) = ∑ ( x ′ , y ′ ) ∈ Ω w ( x ′ , y ′ ) I ( x ′ , y ) ∑ ( x ′ , y ′ ) ∈ Ω w ( x ′ , y ′ ) I'(x, y) = \frac{\sum_{(x', y') \in \Omega} w(x', y') I(x', y)}{\sum_{(x', y') \in \Omega} w(x', y')} I′(x,y)=∑(x′,y′)∈Ωw(x′,y′)∑(x′,y′)∈Ωw(x′,y′)I(x′,y)
其中:
- I ′ ( x , y ) I'(x, y) I′(x,y) 是去噪后的像素值。
- w ( x ′ , y ′ ) w(x', y') w(x′,y′) 是像素 ( x ′ , y ′ ) (x', y') (x′,y′) 的权重,通常与相似度成正比。
- Ω \Omega Ω 是搜索窗口范围内的像素集合。
- (5)递归:对图像中的每个像素都执行相同的去噪过程,逐个计算每个像素的加权平均值,直到整个图像的所有像素都被去噪处理。
(3)优缺点
- 优点 :
- 保留图像的细节和边缘:非局部均值滤波(NLM)通过计算每个像素与其他像素区域的相似性来进行去噪,因此能够保留图像中的细节和边缘。与传统的滤波方法(如均值滤波)相比,NLM避免了过度平滑,能够更好地保持图像结构,避免模糊。
- 只适用于去除高斯噪声,对非高斯噪声效果较差(如椒盐噪声):与高斯滤波器相比,NLM利用全局信息去噪,能够避免高斯滤波可能带来的模糊效应,特别是在处理具有细节和边缘的图像时。
- 局限性 :
- 计算量较大:由于NLM需要计算图像中每个像素与所有其他区域的相似性,尤其在处理高分辨率图像时,计算量会急剧增加,导致处理速度较慢。无法满足实时性
备注:局限性太高,且去噪效果没有预期那么好。不建议使用,学习一下即可。
二、函数详解
单帧去噪:cv.fastNlMeansDenoising() + cv.fastNlMeansDenoisingColored()
仅基于当前图像本身的像素值进行降噪,它通过搜索空间上的相似性(即查找当前帧内相似的像素区域)来计算非局部均值滤波。利用时间维度信息来增强去噪效:cv.fastNlMeansDenoisingMulti() + cv.fastNlMeansDenoisingColoredMulti()
指的是在多个连续帧(如视频帧或图像序列)之间寻找相似性,以优化当前帧的去噪效果。这并不是对所有帧同时进行去噪,而是利用前后多帧的信息来增强对某一帧的去噪处理,从而提高降噪效果,同时尽可能保留图像的细节。
三、项目实战
实战1:cv.fastNlMeansDenoising() ------ 单张灰度图像去噪

python
import cv2
img = cv2.imread(r'F:\py\die.png', cv2.IMREAD_GRAYSCALE)
dst = cv2.fastNlMeansDenoising(img, None, 30, 7, 21)
cv2.imshow('Original Image', img)
cv2.imshow('Denoised Image', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
"""#############################################################################################
# 函数功能:非局部均值去噪(适用于单通道灰度图像)
# 函数说明:dst = cv2.fastNlMeansDenoising(src, dst=None, h=3, templateWindowSize=7, searchWindowSize=21)
# 参数说明:
# src:输入图像,必须是单通道灰度图像(uint8)。
# dst:输出去噪后的图像,可选项,默认为 None。
# h:滤波强度,值越大去噪效果越强,但可能会导致细节损失,推荐范围 3~10。
# templateWindowSize:模板窗口大小(奇数),通常为 7。
# searchWindowSize:搜索窗口大小(奇数),通常为 21。
# 返回值:
# 返回去噪后的图像,与输入图像具有相同尺寸和类型。
# 功能描述:
# - 该方法基于非局部均值(Non-Local Means, NLM)算法,能有效去除噪声,同时保留图像细节。
# - 适用于单通道灰度图像,彩色图像需使用 cv2.fastNlMeansDenoisingColored() 。
# - 常用于去除高斯噪声、均匀噪声等,适用于低噪声图像的平滑处理。
#############################################################################################"""
实战2:cv.fastNlMeansDenoisingColored() ------ 单张彩色图像去噪

python
import cv2
img = cv2.imread(r"F:\py\die.png")
dst = cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)
cv2.imshow('Original Image', img)
cv2.imshow('Denoised Image', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
"""#############################################################################################
# 函数功能:非局部均值去噪(适用于彩色图像)
# 函数说明:dst = cv2.fastNlMeansDenoisingColored(src, dst=None, h=10, hForColorComponents=10,
# templateWindowSize=7, searchWindowSize=21)
# 参数说明:
# src:输入图像,必须是 3 通道 BGR 彩色图像(uint8)。
# dst:输出去噪后的图像,可选项,默认为 None。
# h:亮度通道的滤波强度,值越大去噪效果越强,但可能导致细节损失,推荐范围 3~10。
# hForColorComponents:颜色通道的滤波强度,一般与 h 相同,控制颜色去噪强度。
# templateWindowSize:模板窗口大小(奇数),通常为 7。
# searchWindowSize:搜索窗口大小(奇数),通常为 21。
# 返回值:
# 返回去噪后的图像,与输入图像尺寸和类型相同。
# 功能描述:
# - 该方法基于非局部均值(Non-Local Means, NLM)算法,能有效去除噪声,同时保留图像细节。
# - 专为彩色图像设计,可同时对亮度和颜色通道进行去噪。
# - 适用于高斯噪声、均匀噪声等,特别适用于低噪声图像的平滑处理。
#############################################################################################"""
实战3:cv.fastNlMeansDenoisingMulti() ------ 多帧灰度图像去噪(增强单帧效果)

python
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
# 读取视频的帧图像
cap = cv.VideoCapture(r"D:\opencv c++\opencv\sources\samples\data\vtest.avi")
img = [cap.read()[1] for i in range(5)] # create a list of first 5 frames
gray = [cv.cvtColor(i, cv.COLOR_BGR2GRAY) for i in img] # convert all to grayscale
gray = [np.float64(i) for i in gray] # convert all to float64
noise = np.random.randn(*gray[1].shape)*20 # create a noise of variance 25
noisy = [i+noise for i in gray] # Add this noise to images
noisy = [np.uint8(np.clip(i, 0, 255)) for i in noisy] # Convert back to uint8
dst = cv.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35) # Denoise 3rd frame considering all the 5 frames
plt.subplot(131), plt.imshow(gray[2], 'gray'), plt.title('Original'), plt.axis('off')
plt.subplot(132), plt.imshow(noisy[2], 'gray'), plt.title('Noisy'), plt.axis('off')
plt.subplot(133), plt.imshow(dst, 'gray'), plt.title('Denoised'), plt.axis('off')
plt.show()
# import tifffile
# import numpy as np
# import cv2 as cv
# from matplotlib import pyplot as plt
#
# # 读取tif图像
# img = tifffile.imread(r"F:\py\image.tif")
# img = [i for i in img]
#
# noise = np.random.randn(*img[1].shape)*20 # create a noise of variance 25
# noisy = [i+noise for i in img] # Add this noise to images
# noisy = [np.uint8(np.clip(i, 0, 255)) for i in noisy] # Convert back to uint8
#
# dst = cv.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35) # Denoise 3rd frame considering all the 5 frames
#
# plt.subplot(131), plt.imshow(img[2], 'gray'), plt.title('Original'), plt.axis('off')
# plt.subplot(132), plt.imshow(noisy[2], 'gray'), plt.title('Noisy'), plt.axis('off')
# plt.subplot(133), plt.imshow(dst, 'gray'), plt.title('Denoised'), plt.axis('off')
# plt.show()
"""#############################################################################################
# 函数功能:多帧非局部均值去噪(适用于多张灰度图像)
# 函数说明:
# dst = cv2.fastNlMeansDenoisingMulti(srcImgs, imgToDenoiseIndex, temporalWindowSize, dst=None,
# h=10, templateWindowSize=7, searchWindowSize=21)
# 参数说明:
# srcImgs:输入图像序列(列表形式),每个图像通常为单通道灰度图像(uint8)。
# imgToDenoiseIndex:需要去噪的目标图像在输入序列中的索引(0 ≤ index < len(srcImgs))。
# temporalWindowSize:时间窗口大小(奇数),决定用于去噪的前后帧数量,一般取 3~5。
# dst:输出去噪后的图像,可选项,默认为 None。
# h:滤波强度,值越大去噪效果越强,但可能导致细节损失,推荐范围 3~10。
# templateWindowSize:模板窗口大小(奇数),通常为 7。
# searchWindowSize:搜索窗口大小(奇数),通常为 21。
# 返回值:
# 返回去噪后的单通道灰度图像,与输入图像尺寸和类型相同。
# 功能描述:
# - 该方法基于非局部均值(Non-Local Means, NLM)算法,利用时间维度的信息来增强去噪效果。
# - 适用于视频降噪、医学影像处理等场景,可有效减少随机噪声,同时保留细节。
# - 仅适用于灰度图像,若处理彩色图像,请使用 `cv2.fastNlMeansDenoisingColoredMulti()`。
#############################################################################################"""
实战4:cv.fastNlMeansDenoisingColoredMulti() ------ 多帧彩色图像去噪(增强单帧效果)

python
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
cap = cv.VideoCapture(r"D:\opencv c++\opencv\sources\samples\data\vtest.avi")
img = [cap.read()[1] for i in range(5)] # create a list of first 5 frames
noise = np.random.randn(*img[1].shape)*20 # create a noise of variance 25
noisy = [i+noise for i in img] # Add this noise to images
noisy = [np.uint8(np.clip(i, 0, 255)) for i in noisy] # Convert back to uint8
dst = cv.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35) # Denoise 3rd frame considering all the 5 frames
plt.subplot(131), plt.imshow(cv.cvtColor(img[2], cv.COLOR_BGR2RGB)), plt.title('Original'), plt.axis('off')
plt.subplot(132), plt.imshow(cv.cvtColor(noisy[2], cv.COLOR_BGR2RGB)), plt.title('Noisy'), plt.axis('off')
plt.subplot(133), plt.imshow(cv.cvtColor(dst, cv.COLOR_BGR2RGB)), plt.title('Denoised'), plt.axis('off')
plt.show()
"""#############################################################################################
# 函数功能:多帧彩色图像的非局部均值去噪(适用于多个彩色图像)
# 函数说明:
# dst = cv2.fastNlMeansDenoisingColoredMulti(srcImgs, imgToDenoiseIndex, temporalWindowSize, dst=None,
# h=10, hForColorComponents=10,
# templateWindowSize=7, searchWindowSize=21)
# 参数说明:
# srcImgs:输入图像序列(列表形式),每个图像应为彩色图像(BGR格式,uint8)。
# imgToDenoiseIndex:需要去噪的目标图像在输入序列中的索引(0 ≤ index < len(srcImgs))。
# temporalWindowSize:时间窗口大小(奇数),决定用于去噪的前后帧数量,一般取 3~5。
# dst:输出去噪后的图像,可选项,默认为 None。
# h:滤波强度(亮度通道),值越大去噪效果越强,但可能导致细节损失,推荐范围 3~10。
# hForColorComponents:滤波强度(颜色通道),用于处理彩色噪声,推荐范围 3~10。
# templateWindowSize:模板窗口大小(奇数),通常为 7。
# searchWindowSize:搜索窗口大小(奇数),通常为 21。
# 返回值:
# 返回去噪后的彩色图像,与输入图像尺寸和类型相同。
# 功能描述:
# - 该方法基于非局部均值(Non-Local Means, NLM)算法,利用时间维度信息来增强去噪效果。
# - 适用于视频降噪、多帧影像处理等场景,可有效减少随机噪声,同时保留图像细节和颜色信息。
#############################################################################################"""
实战5:手搓代码(不加速版本)

python
import numpy as np
import cv2
def non_local_means_denoising(img, h=10, templateWindowSize=7, searchWindowSize=21):
"""
非局部均值去噪算法(简化版)
参数:
img: 输入的灰度图像 (2D numpy array)。
h: 控制去噪强度的参数,较大的 h 值会导致更强的平滑效果。
templateWindowSize: 用于计算相似性的模板大小(通常为奇数)。
searchWindowSize: 搜索窗口的大小,决定了搜索相似像素的范围。
返回:
去噪后的图像。
"""
height, width = img.shape # 图像的高度和宽度
denoised_img = np.zeros_like(img, dtype=np.float32) # 输出去噪后的图像
# 在图像上遍历每个像素
for i in range(height):
for j in range(width):
# 定义目标像素的邻域(template window)
x_start = max(i - templateWindowSize // 2, 0)
x_end = min(i + templateWindowSize // 2 + 1, height)
y_start = max(j - templateWindowSize // 2, 0)
y_end = min(j + templateWindowSize // 2 + 1, width)
# 获取目标像素的邻域区域(模板)
template = img[x_start:x_end, y_start:y_end]
# 定义搜索区域(search window)
x_start_search = max(i - searchWindowSize // 2, 0)
x_end_search = min(i + searchWindowSize // 2 + 1, height)
y_start_search = max(j - searchWindowSize // 2, 0)
y_end_search = min(j + searchWindowSize // 2 + 1, width)
# 计算与目标像素模板相似的区域的权重
weights = np.zeros_like(img, dtype=np.float32)
weighted_sum = 0.0
normalization_factor = 0.0
# 遍历搜索窗口中的每个像素
for x in range(x_start_search, x_end_search):
for y in range(y_start_search, y_end_search):
# 跳过当前目标像素
if x == i and y == j:
continue
# 定义候选区域(模板),确保模板大小一致
candidate_x_start = max(x - templateWindowSize // 2, 0)
candidate_x_end = min(x + templateWindowSize // 2 + 1, height)
candidate_y_start = max(y - templateWindowSize // 2, 0)
candidate_y_end = min(y + templateWindowSize // 2 + 1, width)
candidate_template = img[candidate_x_start:candidate_x_end, candidate_y_start:candidate_y_end]
# 确保模板和候选区域大小一致
if template.shape == candidate_template.shape:
# 计算相似度(欧几里得距离),加入调试日志
diff = template - candidate_template
squared_diff = diff ** 2
similarity = np.exp(-np.sum(squared_diff) / (h ** 2))
# 打印日志,查看模板和候选区域之间的差异和相似度
# print(f"Pixel ({i},{j}) - Diff: {squared_diff}, Similarity: {similarity}")
# 防止溢出和无效相似度
if similarity > 1.0:
similarity = 1.0
elif similarity < 0:
similarity = 0.0
# 更新权重和加权平均值
weights[x, y] = similarity
weighted_sum += similarity * img[x, y]
normalization_factor += similarity
# 防止除零错误
if normalization_factor > 0:
denoised_img[i, j] = weighted_sum / normalization_factor
else:
denoised_img[i, j] = img[i, j] # 如果没有有效的相似区域,保持原像素值
# 打印日志,查看归一化因子
print(f"Pixel ({i},{j})/({height}, {width}) - Weighted Sum: {weighted_sum}, Normalization Factor: {normalization_factor}")
# 返回去噪后的图像
return np.uint8(denoised_img)
# (1)读取灰度图像
image = cv2.imread(r'F:\py\die.png', cv2.IMREAD_GRAYSCALE)
# (2)调用非局部均值去噪算法
denoised_image = non_local_means_denoising(image, h=10, templateWindowSize=7, searchWindowSize=21)
# (3)显示原图和去噪后的图像
cv2.imshow("Original Image", image)
cv2.imshow("Denoised Image", denoised_image)
cv2.waitKey(0)
cv2.destroyAllWindows()