OpenCV数字图像处理基于C++:图像平滑

OpenCV数字图像处理基于C++:图像平滑

1、图像平滑算法

图像平滑是一种区域增强的算法,平滑算法有邻域平均法、空间低通滤波、多图像平均、中值滤波等。在图像产生、传输和复制过程中,常常会因为多方面原因而被噪声干扰或出现数据丢失,降低了图像的质量(某一像素,如果它与周围像素点相比有明显的不同,则该点被噪声所感染)。这就需要对图像进行一定的增强处理以减小这些缺陷带来的影响。

图像平滑从信号处理的角度看就是去除其中的高频信息,保留低频信息。因此我们可以对图像实施低通滤波。低通滤波可以去除图像中的噪音,模糊图像(噪音是图像中变化比较大的区域,也就是高频信息)。而高通滤波能够提取图像的边缘(边缘也是高频信息集中的区域)。

根据滤波器的不同又可以分为均值滤波,高斯加权滤波,中值滤波, 双边滤波。

图像滤波的目的有两个:
一是抽出对象的特征作为图像识别的特征模式;
二是为适应图像处理的要求,消除图像数字化时所混入的噪声。
对滤波处理的要求也有两条:
一是不能损坏图像的轮廓及边缘等重要信息;
二是使图像清晰视觉效果好。
平滑滤波是低频增强的空间域滤波技术。它的目的有两类:
一类是模糊;另一类是消除噪音。
空间域的平滑滤波一般采用简单平均法进行,就是求邻近像元点的平均亮度值。邻域的大小与平滑的效果直接相关,邻域越大平滑的效果越好,但邻域过大,平滑会使边缘信息损失的越大,从而使输出的图像变得模糊,因此需合理选择邻域的大小。
关于滤波器,一种形象的比喻法是:我们可以把滤波器想象成一个包含加权系数的窗口,当使用这个滤波器平滑处理图像时,就把这个窗口放到图像之上,透过这个窗口来看我们得到的图像。

2、线性滤波

2.1 均值滤波

一个方块区域(一般为3*3)内,中心点的像素为全部点像素值的平均值。均值滤波就是对于整张图片进行以上操作。均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。特别是校验噪声。(均值滤波是方波滤波归一化后的特殊情况)

image-20221006162519469

2.2 实现均值滤波

非官方编写的均值滤波

//均值滤波
void AverFiltering(const Mat& src, Mat& dst) {
	if (!src.data) return;
	//at访问像素点
	for (int i = 1; i < src.rows; ++i)
		for (int j = 1; j < src.cols; ++j) {
			if ((i - 1 >= 0) && (j - 1) >= 0 && (i + 1) < src.rows && (j + 1) < src.cols) {//边缘不进行处理
				dst.at<Vec3b>(i, j)[0] = (src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i - 1, j - 1)[0] + src.at<Vec3b>(i - 1, j)[0] + src.at<Vec3b>(i, j - 1)[0] +
					src.at<Vec3b>(i - 1, j + 1)[0] + src.at<Vec3b>(i + 1, j - 1)[0] + src.at<Vec3b>(i + 1, j + 1)[0] + src.at<Vec3b>(i, j + 1)[0] +
					src.at<Vec3b>(i + 1, j)[0]) / 9;
				dst.at<Vec3b>(i, j)[1] = (src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i - 1, j - 1)[1] + src.at<Vec3b>(i - 1, j)[1] + src.at<Vec3b>(i, j - 1)[1] +
					src.at<Vec3b>(i - 1, j + 1)[1] + src.at<Vec3b>(i + 1, j - 1)[1] + src.at<Vec3b>(i + 1, j + 1)[1] + src.at<Vec3b>(i, j + 1)[1] +
					src.at<Vec3b>(i + 1, j)[1]) / 9;
				dst.at<Vec3b>(i, j)[2] = (src.at<Vec3b>(i, j)[2] + src.at<Vec3b>(i - 1, j - 1)[2] + src.at<Vec3b>(i - 1, j)[2] + src.at<Vec3b>(i, j - 1)[2] +
					src.at<Vec3b>(i - 1, j + 1)[2] + src.at<Vec3b>(i + 1, j - 1)[2] + src.at<Vec3b>(i + 1, j + 1)[2] + src.at<Vec3b>(i, j + 1)[2] +
					src.at<Vec3b>(i + 1, j)[2]) / 9;
			}
			else {//边缘赋值
				dst.at<Vec3b>(i, j)[0] = src.at<Vec3b>(i, j)[0];
				dst.at<Vec3b>(i, j)[1] = src.at<Vec3b>(i, j)[1];
				dst.at<Vec3b>(i, j)[2] = src.at<Vec3b>(i, j)[2];
			}
		}
}
//均值滤波
void AverFiltering(const Mat& src, Mat& dst) {
	if (!src.data) return;
	//at访问像素点
	for (int i = 1; i < src.rows; ++i)
		for (int j = 1; j < src.cols; ++j) {
			if ((i - 1 >= 0) && (j - 1) >= 0 && (i + 1) < src.rows && (j + 1) < src.cols) {//边缘不进行处理
				dst.at<Vec3b>(i, j)[0] = (src.at<Vec3b>(i, j)[0] + src.at<Vec3b>(i - 1, j - 1)[0] + src.at<Vec3b>(i - 1, j)[0] + src.at<Vec3b>(i, j - 1)[0] +
					src.at<Vec3b>(i - 1, j + 1)[0] + src.at<Vec3b>(i + 1, j - 1)[0] + src.at<Vec3b>(i + 1, j + 1)[0] + src.at<Vec3b>(i, j + 1)[0] +
					src.at<Vec3b>(i + 1, j)[0]) / 9;
				dst.at<Vec3b>(i, j)[1] = (src.at<Vec3b>(i, j)[1] + src.at<Vec3b>(i - 1, j - 1)[1] + src.at<Vec3b>(i - 1, j)[1] + src.at<Vec3b>(i, j - 1)[1] +
					src.at<Vec3b>(i - 1, j + 1)[1] + src.at<Vec3b>(i + 1, j - 1)[1] + src.at<Vec3b>(i + 1, j + 1)[1] + src.at<Vec3b>(i, j + 1)[1] +
					src.at<Vec3b>(i + 1, j)[1]) / 9;
				dst.at<Vec3b>(i, j)[2] = (src.at<Vec3b>(i, j)[2] + src.at<Vec3b>(i - 1, j - 1)[2] + src.at<Vec3b>(i - 1, j)[2] + src.at<Vec3b>(i, j - 1)[2] +
					src.at<Vec3b>(i - 1, j + 1)[2] + src.at<Vec3b>(i + 1, j - 1)[2] + src.at<Vec3b>(i + 1, j + 1)[2] + src.at<Vec3b>(i, j + 1)[2] +
					src.at<Vec3b>(i + 1, j)[2]) / 9;
			}
			else {//边缘赋值
				dst.at<Vec3b>(i, j)[0] = src.at<Vec3b>(i, j)[0];
				dst.at<Vec3b>(i, j)[1] = src.at<Vec3b>(i, j)[1];
				dst.at<Vec3b>(i, j)[2] = src.at<Vec3b>(i, j)[2];
			}
		}
}
//图像椒盐化
void salt(Mat& image, int num) {
	if (!image.data) return;//防止传入空图
	int i, j;
	srand(time(NULL));
	for (int x = 0; x < num; ++x) {
		i = rand() % image.rows;
		j = rand() % image.cols;
		if (image.channels() == 1)
		{
			image.at<uchar>(i, j) = 255;
		}
		else if (image.channels() == 3)
		{
			image.at<Vec3b>(i, j)[0] = 255;
			image.at<Vec3b>(i, j)[1] = 255;
			image.at<Vec3b>(i, j)[2] = 255;
		}
		
	}
}
void main() {
	Mat image = imread("E:\\Lena.jpg");

	Mat Salt_Image;
	image.copyTo(Salt_Image);
	salt(Salt_Image, 3000);	//添加噪声

	Mat image1(image.size(), image.type());	//创建一个跟原图大小相等的图片
	Mat image2;
	AverFiltering(Salt_Image, image1);
	blur(Salt_Image, image2, Size(3, 3));//openCV库自带的均值滤波函数
	imshow("原图", image);
	imshow("自定义均值滤波", image1);
	imshow("openCV自带的均值滤波", image2);
	waitKey();
}

image-20221006161456214

//定义全局变量
Mat g_srcImage;         //定义输入图像
Mat g_dstImage;         //定义目标图像

const int g_nTrackbarMaxValue = 9;      //定义轨迹条最大值
int g_nTrackbarValue;                   //定义轨迹条初始值
int g_nKernelValue;                     //定义kernel尺寸

void on_kernelTrackbar(int, void*);     //定义回调函数

int main()
{
	g_srcImage = imread("E:\\Lena.jpg");

	//判断图像是否加载成功
	if (g_srcImage.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	namedWindow("原图像", WINDOW_AUTOSIZE);     //定义窗口显示属性
	imshow("原图像", g_srcImage);

	g_nTrackbarValue = 1;
	namedWindow("均值滤波", WINDOW_AUTOSIZE);   //定义滤波后图像显示窗口属性

	//定义轨迹条名称和最大值
	char kernelName[20];
	sprintf_s(kernelName, "kernel尺寸 %d", g_nTrackbarMaxValue);

	//创建轨迹条
	createTrackbar(kernelName, "均值滤波", &g_nTrackbarValue, g_nTrackbarMaxValue, on_kernelTrackbar);
	on_kernelTrackbar(g_nTrackbarValue, 0);

	waitKey(0);

	return 0;
}

void on_kernelTrackbar(int, void*)
{
	//根据输入值重新计算kernel尺寸
	g_nKernelValue = g_nTrackbarValue * 2 + 1;

	//均值滤波函数
	blur(g_srcImage, g_dstImage, Size(g_nKernelValue, g_nKernelValue));

	imshow("均值滤波", g_dstImage);
}

image-20221006162118711

image-20221006162139196

createTrackbar(
    const string& trackbarname, 
    const string& winname,
    int* value, 
    int count,
    TrackbarCallback onChange = 0,
    void* userdata = 0
);
其各个参数含义如下: 
const string& trackname: 滑动条名字 
const string& winname: 想要把该滑动条依附到的窗口名字,在程序中可能该窗口名称由namedWindow()声明。 
int* value: 创建滑动条时,滑动条的初始值 
int count: 滑动条的最大值,即所有滑动条的数据变动都要在0-count之间,滑动条最小值为0 
TrackbarCallback onChange = 0: 这是指的回调函数,每次滑动条数据变化时都对该函数进行回调 
void* userdata = 0: 这个是用户传给回调函数的数据,用来处理滑动条数值变动。如果在创建滑动条时,输入value实参是全局变量,则本参数userdata可使用默认值0.

2.3 高斯滤波

数值图像处理中,高斯滤波主要可以使用两种方法实现。一种是离散化窗口滑窗卷积,另一种方法是通过傅里叶变化。最常见的就是滑窗实现,只有当离散化的窗口非常大,用滑窗计算量非常搭的情况下,可能会考虑基于傅里叶变化的实现方法。所以本文将主要介绍滑窗实现的卷积。
离散化窗口划船卷积时主要利用的是高斯核,高斯核的大小为奇数,因为高斯卷积会在其覆盖区域的中心输出结果。常用的高斯模板有如下几种形式:

在这里插入图片描述

高斯模板是通过高斯函数计算出来的(正态分布),公式如下:

在这里插入图片描述

二维高斯函数可以表示如下:

img

img为均值(峰值对应的位置),img为标准差(变量x和变量y各有一个均值,也各有一个标准差)。

高斯滤波一般使用的二维零均值的高斯分布函数,通过高斯分布函数求出模板系数,例如一个3*3的模板:以模板的中心位置为坐标原点进行取样,其中模板各个坐标位置如下图,x轴水平向右,y轴垂直向下,(x,y)表示:

在这里插入图片描述

将各个位置的坐标代入二维零均值高斯分布函数,计算出来的模板有两种形式:
整数模板和小数模板,可以使用二维数组来存放计算出的模板系数。

(1)小数模板

image-20221006170400392

(2)整数模板

image-20221006170448942

结论:整数模板与图像进行卷积后还要除以总的模板系数和

输入:目标核,核的大小,sigma值
1.以x,y方向联合实现获取高斯模板
①取样获得模板
②将坐标代入高斯公式
③将对应的值写入高斯核中
④归一化
输入:原图像,目标图像,核
1.判断原图像是否为空,空则直接返回
2.判断核的是否为奇数
3.原图像边界填充,目标图像清空
4.用高斯核高斯滤波

2.4 实现高斯滤波

/* 高斯滤波 (待处理单通道图片, 高斯分布数组, 高斯数组大小(核大小) ) */
void gaussian(Mat* _src, double** _array, int _size)
{
    Mat temp = (*_src).clone();
    // [1] 扫描
    for (int i = 0; i < (*_src).rows; i++) {
        for (int j = 0; j < (*_src).cols; j++) {
            // [2] 忽略边缘
            if (i > (_size / 2) - 1 && j > (_size / 2) - 1 &&
                i < (*_src).rows - (_size / 2) && j < (*_src).cols - (_size / 2)) {
                // [3] 找到图像输入点f(i,j),以输入点为中心与核中心对齐
                //     核心为中心参考点 卷积算子=>高斯矩阵180度转向计算
                //     x y 代表卷积核的权值坐标   i j 代表图像输入点坐标
                //     卷积算子     (f*g)(i,j) = f(i-k,j-l)g(k,l)          f代表图像输入 g代表核
                //     带入核参考点 (f*g)(i,j) = f(i-(k-ai), j-(l-aj))g(k,l)   ai,aj 核参考点
                //     加权求和  注意:核的坐标以左上0,0起点
                double sum = 0.0;
                for (int k = 0; k < _size; k++) {
                    for (int l = 0; l < _size; l++) {
                        sum += (*_src).ptr<uchar>(i - k + (_size / 2))[j - l + (_size / 2)] * _array[k][l];
                    }
                }
                // 放入中间结果,计算所得的值与没有计算的值不能混用
                temp.ptr<uchar>(i)[j] = sum;
            }
        }
    }

    // 放入原图
    (*_src) = temp.clone();
}


/* 获取高斯分布数组               (核大小, sigma值) */
double** getGaussianArray(int arr_size, double sigma)
{
    int i, j;
    // [1] 初始化权值数组
    double** array = new double* [arr_size];
    for (i = 0; i < arr_size; i++) {
        array[i] = new double[arr_size];
    }
    // [2] 高斯分布计算
    int center_i, center_j;
    center_i = center_j = arr_size / 2;
    double pi = 3.141592653589793;
    double sum = 0.0f;
    // [2-1] 高斯函数
    for (i = 0; i < arr_size; i++) {
        for (j = 0; j < arr_size; j++) {
            array[i][j] =
                //后面进行归一化,这部分可以不用
                //0.5f *pi*(sigma*sigma) * 
                exp(-(1.0f) * (((i - center_i) * (i - center_i) + (j - center_j) * (j - center_j)) /
                    (2.0f * sigma * sigma)));
            sum += array[i][j];
        }
    }
    // [2-2] 归一化求权值
    for (i = 0; i < arr_size; i++) {
        for (j = 0; j < arr_size; j++) {
            array[i][j] /= sum;
            printf(" [%.15f] ", array[i][j]);
        }
        printf("\n");
    }
    return array;
}


void myGaussianFilter(Mat* src, Mat* dst, int n, double sigma)
{
    // [1] 初始化
    *dst = (*src).clone();
    // [2] 彩色图片通道分离
    vector<Mat> channels;
    split(*src, channels);
    // [3] 滤波
    // [3-1] 确定高斯正态矩阵
    double** array = getGaussianArray(n, sigma);
    // [3-2] 高斯滤波处理
    for (int i = 0; i < 3; i++) {
        gaussian(&channels[i], array, n);
    }
    // [4] 合并返回
    merge(channels, *dst);
    return;
}


int main(void)
{
    // [1] src读入图片
    Mat src = imread("E:\\Lena.jpg");
    // [2] dst目标图片
    Mat dst;
    // [3] 高斯滤波  sigma越大越平越模糊
    myGaussianFilter(&src, &dst, 5, 1.5f);
    // [4] 窗体显示
    imshow("src", src);
    imshow("dst", dst);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

image-20221006182906482

int main(int argc, char** argv)
{
	Mat src, dst;
	src = imread("E:\\Lena.jpg");
	if (!src.data)
	{
		printf("could not load image3...\n");
		return -1;
	}
	//定义窗口名称
	char input_title[] = "输入图片";
	char output_title[] = "均值滤波";
	//新建窗口
	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);

	imshow(input_title, src);//原图显示
	//均值滤波操作
	blur(src, dst, Size(15, 15), Point(-1, -1));//均值滤波,Size里面都要奇数,正数。内核内数值分别表示宽,高。Point(-1,-1)表示锚点,一般取-1,表示锚点在核中心。
	imshow(output_title, dst);

	Mat gblur;
	//高斯滤波操作
	GaussianBlur(src, gblur, Size(15, 15), 11, 11);//高斯滤波
	imshow("高斯滤波", gblur);
	waitKey(0);
	return 0;
}

image-20221006183716591

void GaussianBlur(
    InputArray src, 
    OutputArray dst, 
    Size ksize, 
    double sigmaX, 
    double sigmaY=0, 
    int borderType=BORDER_DEFAULT
);
参数详解如下:
src,输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
ksize,高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数(并不能理解)。或者,它们可以是零的,它们都是由sigma计算而来。
sigmaX,表示高斯核函数在X方向的的标准偏差。
sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。

3、非线性滤波

3.1 中值滤波

中值滤波会取当前像素点及其周围临近像素点(一共有奇数个像素点)的像素值,将这些像素值排序,然后将位于中间位置的像素值作为当前像素点的像素值。

image-20221006185540854

对如下矩阵:

image-20221006184538765

将其邻域设置为3×3大小,对其3×3邻域内像素点的像素值进行排序(升序降序均可),按升序排序后得到序列值为:[66,78,90,91,93,94,95,97,101]。在该序列中,处于中心位置(也叫中心点或中值点)的值是“93”,因此用该值替换原来的像素值78,作为当前点的新像素值。中值滤波效果如下:
image-20221006184649153

3.2 实现中值滤波

//求九个数的中值
uchar Median(uchar n1, uchar n2, uchar n3, uchar n4, uchar n5,
	uchar n6, uchar n7, uchar n8, uchar n9) {
	uchar arr[9];
	arr[0] = n1;
	arr[1] = n2;
	arr[2] = n3;
	arr[3] = n4;
	arr[4] = n5;
	arr[5] = n6;
	arr[6] = n7;
	arr[7] = n8;
	arr[8] = n9;
	for (int gap = 9 / 2; gap > 0; gap /= 2)//希尔排序
		for (int i = gap; i < 9; ++i)
			for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j -= gap)
				swap(arr[j], arr[j + gap]);
	return arr[4];//返回中值
}

//图像椒盐化
void salt(Mat& image, int num) {
	if (!image.data) return;//防止传入空图
	int i, j;
	srand(time(NULL));
	for (int x = 0; x < num; ++x) {
		i = rand() % image.rows;
		j = rand() % image.cols;
		image.at<Vec3b>(i, j)[0] = 255;
		image.at<Vec3b>(i, j)[1] = 255;
		image.at<Vec3b>(i, j)[2] = 255;
	}
}

//中值滤波函数
void MedianFlitering(const Mat& src, Mat& dst) {
	if (!src.data)return;
	Mat _dst(src.size(), src.type());
	for (int i = 0; i < src.rows; ++i)
		for (int j = 0; j < src.cols; ++j) {
			if ((i - 1) > 0 && (i + 1) < src.rows && (j - 1) > 0 && (j + 1) < src.cols) {
				_dst.at<Vec3b>(i, j)[0] = Median(src.at<Vec3b>(i, j)[0], src.at<Vec3b>(i + 1, j + 1)[0],
					src.at<Vec3b>(i + 1, j)[0], src.at<Vec3b>(i, j + 1)[0], src.at<Vec3b>(i + 1, j - 1)[0],
					src.at<Vec3b>(i - 1, j + 1)[0], src.at<Vec3b>(i - 1, j)[0], src.at<Vec3b>(i, j - 1)[0],
					src.at<Vec3b>(i - 1, j - 1)[0]);
				_dst.at<Vec3b>(i, j)[1] = Median(src.at<Vec3b>(i, j)[1], src.at<Vec3b>(i + 1, j + 1)[1],
					src.at<Vec3b>(i + 1, j)[1], src.at<Vec3b>(i, j + 1)[1], src.at<Vec3b>(i + 1, j - 1)[1],
					src.at<Vec3b>(i - 1, j + 1)[1], src.at<Vec3b>(i - 1, j)[1], src.at<Vec3b>(i, j - 1)[1],
					src.at<Vec3b>(i - 1, j - 1)[1]);
				_dst.at<Vec3b>(i, j)[2] = Median(src.at<Vec3b>(i, j)[2], src.at<Vec3b>(i + 1, j + 1)[2],
					src.at<Vec3b>(i + 1, j)[2], src.at<Vec3b>(i, j + 1)[2], src.at<Vec3b>(i + 1, j - 1)[2],
					src.at<Vec3b>(i - 1, j + 1)[2], src.at<Vec3b>(i - 1, j)[2], src.at<Vec3b>(i, j - 1)[2],
					src.at<Vec3b>(i - 1, j - 1)[2]);
			}
			else
				_dst.at<Vec3b>(i, j) = src.at<Vec3b>(i, j);
		}
	_dst.copyTo(dst);//拷贝
}


int main() {
	Mat image = imread("E:\\Lena.jpg");
	imshow("原图", image);
	Mat Salt_Image;
	image.copyTo(Salt_Image);
	salt(Salt_Image, 3000);
	imshow("椒盐图", Salt_Image);
	Mat image3, image4;
	MedianFlitering(Salt_Image, image3);
	medianBlur(Salt_Image, image4, 3);
	imshow("自定义中值滤波处理后", image3);
	imshow("openCV自带的中值滤波", image4);
	waitKey();
	return 0;
}

image-20221006185847747

int main()
{
	// 载入原图
	Mat image = imread("E:\\Lena.jpg");
	//创建窗口
	namedWindow("中值滤波【原图】");
	namedWindow("中值滤波【效果图】");
	//显示原图
	imshow("中值滤波【原图】", image);
	
	//salt(image, 3000);	//添加噪声
	//显示椒盐图
	//imshow("中值滤波【椒盐图】", image);
    
	//进行中值滤波操作
	Mat out;
	medianBlur(image, out, 3);//输入,输出,7通道
	//显示效果图
	imshow("中值滤波【效果图】", out);
	waitKey(0);
}

image-20221006185240796

可以看出中值滤波对噪声的消除效果比线性滤波好,但是随着滤波核的增大,图像也会变模糊。

3.3 双边滤波

双边滤波是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空间与信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部处理的特点。之所以能够达到保边去噪的滤波效果是因为滤波器由两个函数构成:一个函数是由几何空间距离决定滤波器系数,另一个是由像素差值决定滤波器系数。
双边滤波器中,输出像素的值依赖于邻域像素的值的加权组合,其公式如下:

双边滤波器中,输出像素的值依赖于邻域像素的值的加权组合,

在这里插入图片描述

权重系数w(i,j,k,l)取决于定义域核

在这里插入图片描述

和值域核

在这里插入图片描述

的乘积

在这里插入图片描述

通俗来讲就是双边滤波模板主要有两个模板生成,第一个是高斯模板,第二个是以灰度级的差值作为函数系数生成的模板,然后这两个模板点乘就得到了最终的双边滤波模板,第一个模板是全局模板,所以只需要生成一次,第二个模板需要对每个像素都计算一次。双边滤波器比高斯滤波器多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素,这样就能对边缘附近的像素值予以保存,但是由于保存过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤除。

 
//获取色彩模板(值域模板)
///
void getColorMask(std::vector<double>& colorMask, double colorSigma) {

	for (int i = 0; i < 256; ++i) {
		double colordiff = exp(-(i * i) / (2 * colorSigma * colorSigma));
		colorMask.push_back(colordiff);
	}

}



//获取高斯模板(空间模板)
///
void getGausssianMask(cv::Mat& Mask, cv::Size wsize, double spaceSigma) {
	Mask.create(wsize, CV_64F);
	int h = wsize.height;
	int w = wsize.width;
	int center_h = (h - 1) / 2;
	int center_w = (w - 1) / 2;
	double sum = 0.0;
	double x, y;

	for (int i = 0; i < h; ++i) {
		y = pow(i - center_h, 2);
		double* Maskdate = Mask.ptr<double>(i);
		for (int j = 0; j < w; ++j) {
			x = pow(j - center_w, 2);
			double g = exp(-(x + y) / (2 * spaceSigma * spaceSigma));
			Maskdate[j] = g;
			sum += g;
		}
	}
}



//双边滤波
///
void bilateralfiter(cv::Mat& src, cv::Mat& dst, cv::Size wsize, double spaceSigma, double colorSigma) {
	cv::Mat spaceMask;
	std::vector<double> colorMask;
	cv::Mat Mask0 = cv::Mat::zeros(wsize, CV_64F);
	cv::Mat Mask1 = cv::Mat::zeros(wsize, CV_64F);
	cv::Mat Mask2 = cv::Mat::zeros(wsize, CV_64F);

	getGausssianMask(spaceMask, wsize, spaceSigma);//空间模板
	getColorMask(colorMask, colorSigma);//值域模板
	int hh = (wsize.height - 1) / 2;
	int ww = (wsize.width - 1) / 2;
	dst.create(src.size(), src.type());
	//边界填充
	cv::Mat Newsrc;
	cv::copyMakeBorder(src, Newsrc, hh, hh, ww, ww, cv::BORDER_REPLICATE);//边界复制;

	for (int i = hh; i < src.rows + hh; ++i) {
		for (int j = ww; j < src.cols + ww; ++j) {
			double sum[3] = { 0 };
			int graydiff[3] = { 0 };
			double space_color_sum[3] = { 0.0 };

			for (int r = -hh; r <= hh; ++r) {
				for (int c = -ww; c <= ww; ++c) {
					if (src.channels() == 1) {
						int centerPix = Newsrc.at<uchar>(i, j);
						int pix = Newsrc.at<uchar>(i + r, j + c);
						graydiff[0] = abs(pix - centerPix);
						double colorWeight = colorMask[graydiff[0]];
						Mask0.at<double>(r + hh, c + ww) = colorWeight * spaceMask.at<double>(r + hh, c + ww);//滤波模板
						space_color_sum[0] = space_color_sum[0] + Mask0.at<double>(r + hh, c + ww);

					}
					else if (src.channels() == 3) {
						cv::Vec3b centerPix = Newsrc.at<cv::Vec3b>(i, j);
						cv::Vec3b bgr = Newsrc.at<cv::Vec3b>(i + r, j + c);
						graydiff[0] = abs(bgr[0] - centerPix[0]); graydiff[1] = abs(bgr[1] - centerPix[1]); graydiff[2] = abs(bgr[2] - centerPix[2]);
						double colorWeight0 = colorMask[graydiff[0]];
						double colorWeight1 = colorMask[graydiff[1]];
						double colorWeight2 = colorMask[graydiff[2]];
						Mask0.at<double>(r + hh, c + ww) = colorWeight0 * spaceMask.at<double>(r + hh, c + ww);//滤波模板
						Mask1.at<double>(r + hh, c + ww) = colorWeight1 * spaceMask.at<double>(r + hh, c + ww);
						Mask2.at<double>(r + hh, c + ww) = colorWeight2 * spaceMask.at<double>(r + hh, c + ww);
						space_color_sum[0] = space_color_sum[0] + Mask0.at<double>(r + hh, c + ww);
						space_color_sum[1] = space_color_sum[1] + Mask1.at<double>(r + hh, c + ww);
						space_color_sum[2] = space_color_sum[2] + Mask2.at<double>(r + hh, c + ww);

					}
				}
			}

			//滤波模板归一化
			if (src.channels() == 1)
				Mask0 = Mask0 / space_color_sum[0];
			else {
				Mask0 = Mask0 / space_color_sum[0];
				Mask1 = Mask1 / space_color_sum[1];
				Mask2 = Mask2 / space_color_sum[2];
			}


			for (int r = -hh; r <= hh; ++r) {
				for (int c = -ww; c <= ww; ++c) {

					if (src.channels() == 1) {
						sum[0] = sum[0] + Newsrc.at<uchar>(i + r, j + c) * Mask0.at<double>(r + hh, c + ww); //滤波
					}
					else if (src.channels() == 3) {
						cv::Vec3b bgr = Newsrc.at<cv::Vec3b>(i + r, j + c); //滤波
						sum[0] = sum[0] + bgr[0] * Mask0.at<double>(r + hh, c + ww);//B
						sum[1] = sum[1] + bgr[1] * Mask1.at<double>(r + hh, c + ww);//G
						sum[2] = sum[2] + bgr[2] * Mask2.at<double>(r + hh, c + ww);//R
					}
				}
			}

			for (int k = 0; k < src.channels(); ++k) {
				if (sum[k] < 0)
					sum[k] = 0;
				else if (sum[k] > 255)
					sum[k] = 255;
			}
			if (src.channels() == 1)
			{
				dst.at<uchar>(i - hh, j - ww) = static_cast<uchar>(sum[0]);
			}
			else if (src.channels() == 3)
			{
				cv::Vec3b bgr = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
				dst.at<cv::Vec3b>(i - hh, j - ww) = bgr;
			}

		}
	}

}

int main(int argc, char** argv)
{
	Mat src = imread("E:\\Lena.jpg");
	imshow("原图", src);
	
	Mat dst = src.clone();

	bilateralfiter(src, dst, Size(23,23), 3 * 2, 3 / 2);
	imshow("双边滤波", dst);
	waitKey();
	return 0;
}

image-20221006192720412

Mat src; Mat dst;

int main(int argc, char** argv)
{
	Mat src = imread("E:\\Lena.jpg");
	imshow("原图", src);
	
	dst = src.clone();
	

	blur(src, dst, Size(3, 3), Point(-1, -1));
	imshow("均值滤波", src);
		
	GaussianBlur(src, dst, Size(3,3), 0, 0);
	imshow("高斯滤波", src);

	medianBlur(src, dst, 3);
	imshow("中值滤波", src);

	bilateralFilter(src, dst, 3, 3 * 2, 3 / 2);
	imshow("双边滤波", src);
	waitKey();
	return 0;
}

image-20221006191254037

4. 总结

滤波器种类基本原理特点
均值滤波使用模板内所有像素的平均值代替模板中心像素灰度值易收到噪声的干扰,不能完全消除噪声,只能相对减弱噪声
中值滤波计算模板内所有像素中的中值,并用所计算出来的中值体改模板中心像素的灰度值对噪声不是那么敏感,能够较好的消除椒盐噪声,但是容易导致图像的不连续性
高斯滤波对图像邻域内像素进行平滑时,邻域内不同位置的像素被赋予不同的权值对图像进行平滑的同时,同时能够更多的保留图像的总体灰度分布特征
双边滤波通俗来讲就是双边滤波模板主要有两个模板生成,第一个是高斯模板,第二个是以灰度级的差值作为函数系数生成的模板,然后这两个模板点乘就得到了最终的双边滤波模板同时考虑空间与信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部处理的特点
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值