OpenCV - 图像保留纹理去噪 fastNlMeansDenoising

本文最后更新于:2022年5月21日 凌晨

图像去噪是图像处理中的重要需求,本文介绍 OpenCV 库中集成的去噪函数 fastNlMeansDenoising。

简介

  • 去噪是十分重要的预处理步骤之一,但是在去噪的同时保留正常的图像纹理则需要更精细的去噪算法
  • 之前介绍过的 Photoshop 中的表面模糊 算法可以算是去噪中比较有效的方法之一,但是没有快速算法
  • OpenCV 集成了 Non-Local Means Denoising 算法的同时对其进行了加速
  • 可以有效处理高斯白噪声
  • 官方文档:https://docs.opencv.org/2.4.13.7/modules/photo/doc/denoising.html?highlight=fastnlmeansdenoising

Non-Local Means Denoising

$$ N L u(p)=\frac{1}{C(p)} \int f(d(B(p), B(q)) u(q) d q $$
  • 其中$p$​ 为当前正在处理的像素,$q$​ 为周围邻域一个像素,$d(B§, B(q))$​ 为二者邻域patch 数据的距离度量(欧氏距离),$u(q)$​为$q$​的权重,$C§$​ 为权重标准化系数
  • 即为了估计当前像素$p$去噪后的真实像素值,需要周围邻域像素的信息辅助,邻域单个像素辅助也不可靠,需要邻域像素周围的patch与$p$ 周围的patch 数据相比较,将结果作为估计像素的权重加权求和计算得到当前像素点的去噪像素值
  • 考虑一幅彩色图像$ u=\left(u_{1}, u_{2}, u_{3}\right) $,对于像素 $p$,为其去噪的核心公式为:
$$ \hat{u}_{i}(p)=\frac{1}{C(p)} \sum_{q \in B(p, r)} u_{i}(q) w(p, q), \quad C(p)=\sum_{q \in B(p, r)} w(p, q) $$
  • 其中 $i=1,2,3$​,$B(p,r)$​ 为 $p$​ 周围 $r$​ 为半径的邻域,$w(p, q)$​ 为权重,$C§$​ 为权重标准化系数
  • 权重的计算与像素间距离度量相关,定义$p, q$ 间欧式距离度量 $d{2}=d{2}(B(p, f), B(q, f)) $:
$$ d^{2}(B(p, f), B(q, f))=\frac{1}{3(2 f+1)^{2}} \sum_{i=1}^{3} \sum_{j \in B(0, f)}\left(u_{i}(p+j)-u_{i}(q+j)\right)^{2} $$
  • 有了 $d^2$​,定义 $w(p, q)$​计算方法:
$$ w(p, q)=e^{-\frac{\max \left(d^{2}-2 \sigma^{2}, 0.0\right)}{h^{2}}} $$
  • 其中 $σ$​ 表示噪声标准差,$h$​ 表示过滤参数,越大表示去噪能力越强,同时图像的细节丢失的也会越多

OpenCV 函数介绍

核心函数

1
cv2.fastNlMeansDenoising(src, h=3, templateWindowSize=7, searchWindowSize=21 )

参数介绍

参数 含义
src 噪声图像(对于此函数接受2D图像)
templateWindowSize 用于计算权重的模板块的像素大小,需要奇数,建议为7
searchWindowSize 用于计算给定像素加权平均数的窗口的像素大小,需要奇数,建议为21
h 参数调节过滤器强度。较大的 h 值可以完全去除图像中的噪声,但同时也去除了图像中的细节,较小的 h 值可以保留细节,但同时也保留了一些噪声, 默认为3

配套函数

  • fastNlMeansDenoising 仅用于灰度图像去噪
  • 彩色图像去噪需要用到 fastNlMeansDenoisingColored函数,该函数会将图像转换到 CIELAB 空间并分别对 L 和 AB 分量去噪
  • fastNlMeansDenoisingMulti 函数用于连续相关灰度图像的快速去噪(例如视频中的连续灰度帧)
  • fastNlMeansDenoisingColoredMulti 函数用于连续相关彩色图像的快速去噪(例如视频中的连续彩色帧)

实现示例

  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
from mtutils import to_gray_image
from mtutils import cv_rgb_imread
from mtutils import PIS


if __name__ == '__main__':
noise_image_path = 'assets/noise.jpg'
noise_img = cv_rgb_imread(noise_image_path)

# h -> 6
denoised_img = cv2.fastNlMeansDenoisingColored(noise_img, None, 6, 10, 7, 21)
PIS([noise_img, 'with noise'], [denoised_img, 'denoised'])

pass
  • 效果示例

原始论文

参考资料