1、基本原理
是一种空间域滤波,和高斯模糊、均值滤波类似,当前像素点的值时通过周围像素的加权平均得到。不同之处在于权值计算策略不同,也就是下面公式中w计算方式不同;
2、权重策略
以上图为例,对其去噪;对于帽子边缘的一个像素点,相比于高斯模糊或者均值模糊,用附近帽子边缘的像素点求均值更能好。那么计算机如何知道哪些像素也同样的是帽子边缘的像素?帽子边缘具有相似的结构,如果我们在每个像素周围画一个方框,方框内像素的欧式距离越小,说明结构越相似,中间的像素越是处于相同的结构位置,对应权重要更大,这个框的大小称kernel size。对多大范围内(B(p,r))的像素求加权平均,在这个算法中成为search size;
距离计算公式:
距离越小,越相似,权重越大
3、实现代码
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/photo.hpp>
using namespace std;
using namespace cv;
int distance2(Mat m1, Mat m2)
{
Mat dst;
absdiff(m1, m2, dst);
int sum = 0;
for (int j = 0; j < dst.rows; j++) {
for (int i = 0; i < dst.cols; i++) {
sum += dst.at<uchar>(j,i) * dst.at<uchar>(j, i);
}
}
return sum;
}
void nlmean(Mat src, Mat &dst, double h, double sigma, int halfKernelSize, int halfSearchSize)
{
Mat boardSrc;
dst.create(src.rows, src.cols, CV_8UC1);
int boardSize = halfKernelSize + halfSearchSize;
copyMakeBorder(src, boardSrc, boardSize, boardSize, boardSize, boardSize, BORDER_REFLECT);
double h2 = h*h;
double sigma2 = 2 * sigma*sigma;
int rows = src.rows;
int cols = src.cols;
int lookupTableSize = 256 * 256 *(2* halfKernelSize +1)*(2* halfKernelSize+1);
double *lookupTable = new double[lookupTableSize];
for (int i = 0; i < lookupTableSize; i++) {
if(i<sigma2)
lookupTable[i] =1;
else
lookupTable[i] = exp(-i/ h2/(2* halfKernelSize+1) /(2* halfKernelSize+1));
}
for (int j = boardSize; j < boardSize+ rows; j++) {
for (int i = boardSize; i < boardSize+cols; i++) {
Mat patchA=boardSrc( Range(j - halfKernelSize, j + halfKernelSize), Range (i - halfKernelSize, i + halfKernelSize));
double w = 0;
double p = 0;
double sumw = 0;
for (int sr = -halfSearchSize; sr <= halfSearchSize; sr++) {
for (int sc = -halfSearchSize; sc <= halfSearchSize; sc++) {
Mat patchB = boardSrc(Range(j+sr - halfKernelSize, j+sr + halfKernelSize), Range(i +sc- halfKernelSize, i+sc + halfKernelSize));
int d2 = distance2(patchA, patchB);
w = lookupTable[d2];
p += boardSrc.at<uchar>(j+sr, i+sc)*w;
sumw += w;
}
}
dst.at<uchar>(j - boardSize, i - boardSize) = saturate_cast<uchar>(p / sumw);
}
}
delete[]lookupTable;
}
int main()
{
Mat src = imread("lena.png", IMREAD_GRAYSCALE);
Mat dst;
Mat cvdst;
fastNlMeansDenoising(src, cvdst, 3, 7, 10);
namedWindow("src");
imshow("src", src);
imshow("cvdst", cvdst);
nlmean(src, dst, 3, 5, 3, 10);
imshow("mydst", dst);
Mat diff;
absdiff(src, dst, diff);
imshow("diff", diff);
waitKey(0);
}
4、效果:
原图
opencv自带函数实现效果
我的实现效果
4、论文链接
http://www.ipol.im/pub/art/2011/bcm_nlm/?utm_source=doi