【OpenCV图像处理】图像去噪:cv.fastNlMeansDenoising()

文章目录

  • [一、图像去噪(Image Denoising)](#一、图像去噪(Image Denoising))
    • [(1)基本原理 ------ 基于非局部均值(Non-Local Means, NLM)滤波算法](#(1)基本原理 —— 基于非局部均值(Non-Local Means, NLM)滤波算法)
    • (2)算法步骤
    • (3)优缺点
  • 二、函数详解
  • 三、项目实战
    • [实战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需要计算图像中每个像素与所有其他区域的相似性,尤其在处理高分辨率图像时,计算量会急剧增加,导致处理速度较慢。无法满足实时性

备注:局限性太高,且去噪效果没有预期那么好。不建议使用,学习一下即可。

二、函数详解

OpenCV官网:Image Denoising

  • 单帧去噪: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()
相关推荐
CoovallyAIHub3 小时前
结构化数据迎来“ChatGPT时刻”!LimitX:一个模型统一所有表格任务
深度学习·算法·计算机视觉
Valueyou245 小时前
论文阅读——CenterNet
论文阅读·python·opencv·目标检测·计算机视觉
AndrewHZ5 小时前
【图像处理基石】什么是光流法?
图像处理·算法·计算机视觉·目标跟踪·cv·光流法·行为识别
hixiong12316 小时前
C# OpenCVSharp实现Hand Pose Estimation Mediapipe
开发语言·opencv·ai·c#·手势识别
Dm_dotnet17 小时前
OpenCVSharp:ArUco 标记检测与透视变换
opencv
PixelMind20 小时前
【IQA技术专题】 基于多模态大模型的IQA Benchmark:Q-BENCH
图像处理·深度学习·lmm·iqa
wearegogog12320 小时前
基于MATLAB的多尺度血管检测与线追踪实现
开发语言·计算机视觉·matlab
AI棒棒牛1 天前
SCI精读:基于计算机视觉改进光伏热点和积尘检测:基于现场航拍图像的YOLO模型系统比较
yolo·目标检测·计算机视觉·目标跟踪·sci
yy_xzz1 天前
OpenCV 图像处理与键盘交互
图像处理·opencv