OpenCV(4)-图像掩码操作(卷积)--平滑处理

卷积定义

矩阵的掩码操作即对图像进行卷积。对图像卷积操作的意义为:邻近像素对(包括该像素自身)对新像素的影响;影响大小取决于卷积核对应位置值得大小。

例如:图像增强可以使用

[I(i,j)=5*I(i,j)-[I(i-1,j) + I(i+1,j) + I(i, j-1) + I(i, j+1)] ]

用代码实现

 void Sharpen(const Mat& myImage, Mat& Result)
{
	CV_Assert(myImage.depth() == CV_8U);  // 仅接受uchar图像
	Result.create(myImage.size(), myImage.type());
	const int nChannels = myImage.channels();

	for (int j = 1; j < myImage.rows - 1; ++j)//从第一行开始,而不是0;到rows-2行结束
	{
		const uchar* previous = myImage.ptr<uchar>(j - 1);//上一行
		const uchar* current = myImage.ptr<uchar>(j);
		const uchar* next = myImage.ptr<uchar>(j + 1);//下一行

		uchar* output = Result.ptr<uchar>(j);

		for (int i = nChannels; i < nChannels*(myImage.cols - 1); ++i)
		{
			*output++ = saturate_cast<uchar>(5 * current[i]
				- current[i - nChannels] - current[i + nChannels] - previous[i] - next[i]);
		}
	}

	//边缘处理。边缘没有卷积,所以边缘赋值为原图像对应像素的值。

	//先处理上和下
	uchar* output_top = Result.ptr<uchar>(0);
	const uchar* origin_top = myImage.ptr<uchar>(0);
	uchar* output_bottom = Result.ptr<uchar>(myImage.rows - 1);
	const uchar* origin_bottom = Result.ptr<uchar>(myImage.rows - 1);
	for (int i = 0; i < nChannels * myImage.cols - 1; ++i){
		*output_top++ = *origin_top++;
		*output_bottom++ = *origin_bottom++;
	}

	//左和右 分别有nChannel
	for (int i = 0; i < myImage.rows; ++i){
		const uchar* origin_left = myImage.ptr<uchar>(i);
		uchar* output_left = Result.ptr<uchar>(i);
		const uchar* origin_right = origin_left + nChannels*(myImage.cols - 1);
		uchar* output_right = output_left + nChannels*(myImage.cols - 1);
		for (int j = 0; j < nChannels; ++j){
			*output_left++ = *origin_left++;
			*output_right++ = *origin_right++;
		}
	}
}

卷积在图像处理中应用很广泛,可以使用OpenCV自带函数

void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT )

image

滤波器

滤波器中,所有元素和相加后应该等于1。如果大于1,则图像会变亮,如果小于1,图像变暗。

平滑滤波器

平滑滤波器的作用是去除图像中一些不重要的细节,减小噪声。可以分为线性和非线性2大类:
1、线性平滑滤波器:均值波滤器
2、非线性平滑滤波器:最大值滤波、中值滤波、最小值滤波。

线性平滑滤波

线性平滑滤波可以减小“尖锐”,减小噪声;因为图像边缘是有“尖锐”变换引起的,所以也就模糊了边缘。

[ frac{1}{9}left[ egin{matrix} 1 & 1 & 1 \ 1 & 1 & 1 \ 1 & 1 & 1 end{matrix} ight] ag{3} ]

上面这个滤波器,各个像素的值贡献相同,是均值滤波,在OpenCV有对应函数。

void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )

有时各个像素的重要性不同,可以使用:

[ frac{1}{16}left[ egin{matrix} 1 & 2 & 1 \ 2 & 4 & 2 \ 1 & 2 & 1 end{matrix} ight] ag{3} ]

上面的滤波器表面中间和其挨着的像素贡献大一些。

非线性滤波

线性滤波是对滤波器区域内各个像素点加权求和,结果和原图像滤波区域有一个线性关系。非线性滤波则不然;基于统计排序的非线性滤波分为:最大值滤波、中值滤波、最小值滤波。顾名思义,上述三种滤波器是分别取卷积核区域内最大像素点值、中值像素点值、最小像素点值。

中值滤波:

中值滤波是对卷积核区域内像素排序,取中位数,只有奇数才有中位数,所有卷积核只能为3、5、7……这样的奇数。中值滤波在去除噪声的同时,可以比较好的保留图像细节(尖锐等),能够有效去除脉冲噪声(椒盐噪声)。

void medianBlur(InputArray src, OutputArray dst, int ksize)

高斯滤波:卷积核是按照高斯分布的

[G(x,y)=Ae^{frac{-(x-mu_x)^2}{2sigma_x^2}+frac{-(x-mu_y)^2}{2sigma_y^2}} ]

和均值像素差值大的像素,对结果贡献较小;离均值“近”的像素,对结果贡献较大。
函数为:

void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )

双边滤波:

前面提高的滤波都是知识考虑了图像的空间关系,即卷积核区域内的空域关系,没有考虑像素间的关系。因此在平滑时,会模糊整幅图像;双边滤波不仅考虑了图像的空域关系,还考虑了图像间像素值的差异关系。可以参考这里
OpenCV对应函数

void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )

实验代码

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

void Sharpen(const Mat& myImage, Mat& Result)
{
	CV_Assert(myImage.depth() == CV_8U);  // 仅接受uchar图像

	Result.create(myImage.size(), myImage.type());
	const int nChannels = myImage.channels();

	for (int j = 1; j < myImage.rows - 1; ++j)//从第一行开始,而不是0;到rows-2行结束
	{
		const uchar* previous = myImage.ptr<uchar>(j - 1);//上一行
		const uchar* current = myImage.ptr<uchar>(j);
		const uchar* next = myImage.ptr<uchar>(j + 1);//下一行

		uchar* output = Result.ptr<uchar>(j);

		for (int i = nChannels; i < nChannels*(myImage.cols - 1); ++i)
		{
			*output++ = saturate_cast<uchar>(5 * current[i]
				- current[i - nChannels] - current[i + nChannels] - previous[i] - next[i]);
		}
	}

	//边缘处理。边缘没有卷积,所以边缘赋值为原图像对应像素的值。

	//先处理上和下
	uchar* output_top = Result.ptr<uchar>(0);
	const uchar* origin_top = myImage.ptr<uchar>(0);
	uchar* output_bottom = Result.ptr<uchar>(myImage.rows - 1);
	const uchar* origin_bottom = Result.ptr<uchar>(myImage.rows - 1);
	for (int i = 0; i < nChannels * myImage.cols - 1; ++i){
		*output_top++ = *origin_top++;
		*output_bottom++ = *origin_bottom++;
	}

	//左和右 分别有nChannel
	for (int i = 0; i < myImage.rows; ++i){
		const uchar* origin_left = myImage.ptr<uchar>(i);
		uchar* output_left = Result.ptr<uchar>(i);
		const uchar* origin_right = origin_left + nChannels*(myImage.cols - 1);
		uchar* output_right = output_left + nChannels*(myImage.cols - 1);
		for (int j = 0; j < nChannels; ++j){
			*output_left++ = *origin_left++;
			*output_right++ = *origin_right++;
		}
	}
}

int main(int argc, char* argv[]){
	const char* path = "";
	Mat img = imread(path);
	if (img.empty())
	{
		cout << "error";
		return -1;
	}
	imshow("原图像", img);

	//均值滤波
	Mat blur_mat;
	blur(img, blur_mat, Size(3, 3));
	imshow("均值滤波", blur_mat);

	//线性非均值滤波
	Mat kern = (Mat_<float>(3, 3) << 1, 2, 1,
		2, 4, 2,
		1, 2, 1) / 16;
	Mat blur2_mat;
	filter2D(img, blur2_mat, img.depth(), kern);
	imshow("线性非均值滤波", blur2_mat);

	//中值滤波
	Mat memedian_mat;
	medianBlur(img, memedian_mat, 3);
	imshow("中值滤波", memedian_mat);

	//高斯滤波
	Mat Gaussian_mat;
	GaussianBlur(img, Gaussian_mat, Size(3, 3), 0, 0);
	imshow("高斯滤波", Gaussian_mat);

	//双边滤波

	Mat bilateral_mat;
	bilateralFilter(img, bilateral_mat, 25, 25 * 2, 25 / 2);
	imshow("双边滤波", bilateral_mat);
	waitKey();
	return 0;

}

原文地址:https://www.cnblogs.com/korbin/p/5612438.html