<图像处理> 可分离滤波器核

可分离滤波器核

空间滤波器核是一个二维矩阵,若它能够表示为两个一维矩阵的乘积时,则表示该滤波器核是可分离的。
例如,一个3x3的核,
w = [ 1 1 1 1 1 1 1 1 1 ] w=\begin{bmatrix} 1 & 1 & 1\\ 1 & 1& 1\\ 1 & 1& 1\\ \end{bmatrix} w= 111111111
它可以表示为两个一维矩阵的乘积
c = [ 1 1 1 ] c=\begin{bmatrix} 1 & 1 & 1\\ \end{bmatrix} c=[111]
r = [ 1 1 1 ] r=\begin{bmatrix} 1 & 1 & 1\\ \end{bmatrix} r=[111]

w = c r T w=cr^T w=crT

性质

可分离核的重要性是卷积结合律性质导致的计算优势,如果有一个核 w w w,它可以分为两个简单的核并满足 w = w 1 ∗ w 2 w=w_1*w_2 w=w1w2,则其满足
w ∗ f = ( w 1 ∗ w 2 ) ∗ f = ( w 2 ∗ w 1 ) ∗ f = w 2 ∗ ( w 1 ∗ f ) = ( w 1 ∗ f ) ∗ w 2 w*f=(w_1*w_2)*f=(w_2*w_1)*f=w_2*(w_1*f)=(w_1*f)*w_2 wf=(w1w2)f=(w2w1)f=w2(w1f)=(w1f)w2
对于一个大小为 M ∗ N M*N MN的图像与大小为 m ∗ n m*n mn的核实现卷积,需要 M N m n MNmn MNmn次加法和乘法,如果核是可分离的,则需要 M N ( m + n ) MN(m+n) MN(m+n)次,可加速计算。

必要条件

要确定一个核是否可分离,只需要确定其秩是否为1。 因此确定某个矩阵的秩为1后,能够计算其两个分离的一维核,步骤如下

  1. 在核中找到任意一个非零元素,并将其表示为E;
  2. 找他该元素所在的行和列,表示为 c , r c,r c,r
  3. 可以得出两个一维核为 c c c r / E r/E r/E

示例

以x方向上的Sobel滤波核进行性能测试,比较 M N m n MNmn MNmn以及 M N ( m + n ) MN(m+n) MN(m+n)的处理时间,并与自带opencv 的cv::filter2D与cv::Sobel算子进行比较滤波效果。

int main()
{
	//x方向的Sobel核
	Mat kernel = (Mat_(3, 3) <<
		-1, 0, 1,
		-2, 0, 2,
		-1, 0, 1);

	const char* imageName = ".....";
	Mat src = imread(imageName, IMREAD_GRAYSCALE);
	Mat srcBorder;
	copyMakeBorder(src, srcBorder, kernel.cols / 2, kernel.cols / 2, kernel.rows / 2, kernel.rows / 2, cv::BORDER_CONSTANT);//填充边缘

	clock_t start, end;
	//1.MNmn
	Mat dst(src.rows, src.cols, CV_8UC1);
	start = clock();
	int sum = 0;
	for (int i = 1;i <= dst.rows;i++)
	{
		for (int j = 1;j <= dst.cols;j++)
		{
			sum = 0;
			for (int m = 0;m < kernel.rows;m++)
			{
				for (int n = 0;n < kernel.cols;n++)
				{
					sum += (int)(srcBorder.ptr(i + m - 1)[j + n - 1] * kernel.ptr(m)[n]);
				}
			}
			dst.ptr(i - 1)[j - 1] = (uchar)(sum < 0 ? 0 : (sum > 255 ? 255 : sum));
		}
	}
	end = clock();
	std::cout << "1.常规计算(MNmn):" << end - start << std::endl;

	//2.可分离滤波计算
	Mat _src2(src.rows + kernel.rows / 2 + 1, src.cols + kernel.cols / 2 + 1, CV_32SC1);
	_src2 = Scalar::all(0);
	Mat dst2(src.rows, src.cols, CV_8UC1);
	start = clock();
	//分离卷积核
	char kernelRow[3] = { 1,0,-1 };
	char kernelCol[3] = { -1,-2,-1 };

	for (int i = 1;i <= dst2.rows;i++)
	{
		for (int j = 1;j <= dst2.cols;j++)
		{
			sum = 0;
			for (int m = 0;m < 3;m++)
			{
				sum += (int)(srcBorder.ptr(i)[j + m - 1] * kernelRow[m]);
			}
			_src2.ptr(i)[j] = sum;
		}
	}

	for (int i = 1;i <= dst2.rows;i++)
	{
		for (int j = 1;j <= dst2.cols;j++)
		{
			sum = 0;
			for (int n = 0;n < 3;n++)
			{
				sum += (int)(_src2.ptr(i + n - 1)[j] * kernelCol[n]);
			}
			dst2.ptr(i - 1)[j - 1] = (uchar)(sum < 0 ? 0 : (sum > 255 ? 255 : sum)); //防止溢出。opencv中使用内联函数saturate_cast()
		}
	}
	end = clock();
	std::cout << "2.可分离核计算MN(m+n):" << end - start << std::endl;

	//3.opencv-filter2D计算
	Mat dst3;
	start = clock();
	cv::filter2D(src, dst3, -1, kernel, Point(-1, -1), 0.0, BORDER_CONSTANT);
	end = clock();
	std::cout << "3.opencv-filter2D计算:" << end - start << std::endl;

	//4.opencv-sobel计算
	Mat dst4;
	start = clock();
	cv::Sobel(src, dst4, -1, 1, 0, 3, 1.0, 0.0, BORDER_CONSTANT);
	end = clock();
	std::cout << "4.opencv-sobel计算:" << end - start << std::endl;

	// 效果比较
	Mat findzero1 = dst2 != dst4; //方法一和方法二比较效果
	Mat findzero2 = dst2 != dst4; //方法二和方法三比较效果
	Mat findzero3 = dst2 != dst4; //方法二和方法四比较效果
	vector veczero1;
	vector veczero2;
	vector veczero3;
	cv::findNonZero(findzero1, veczero1);
	cv::findNonZero(findzero2, veczero2);
	cv::findNonZero(findzero3, veczero3);
	int num1 = veczero1.size();
	int num2 = veczero2.size();
	int num3 = veczero3.size();
	std::cout << "方法一和方法二逐像素比较,像素不同个数:" << num1 << std::endl;
	std::cout << "方法二和方法三逐像素比较,像素不同个数:" << num2 << std::endl;
	std::cout << "方法二和方法四逐像素比较,像素不同个数:" << num3 << std::endl;

	system("pause");
	return 0;
}

计算结果显示,可分离核计算比常规计算快一倍左右,与OpenCV的sobel算子处理时间相当。

<图像处理> 可分离滤波器核_第1张图片

你可能感兴趣的:(图像处理,图像处理)