灰度图像卷积的暴力算法实现二

灰度图像的暴力算法实现一中,确定待卷积图像某个像素点卷积后的像素是在待卷积图像上移动卷积模板过程中直接计算出来的,下面介绍另一种计算方法。
注:本文只谈论灰度图像在边界外推像素值为零,且卷积核的锚点位于卷积核中心位置的特定情况下的卷积。

一、算法设计过程

1:设待卷积图像src的高为s_row、宽为s_col,卷积核的高为k_row 、宽为k_col(k_row和k_col均为奇数);在卷积之前通常都需要对待卷积图像进行边界外推,在我们规定的条件下,k_row和k_col均为奇数,所以src边界外推时,向上和向下各外推k_row/2个像素单位,值均为零;向左和向右各外推k_col/2个像素单位,值均为零。得到的外推图像src_extr的大小为(s_row+k_row-1)(s_col+k_col-1)。
2:卷积核在外推图像上以步长为1移动,每移动一次就可以计算出锚点对应的像素的卷积结果,所以需要移动s_row
s_col次,移动次数和输入图像src的大小一样。当计算某一像素点的卷积结果时,可
将卷积核转换成一行(列)的矩阵(向量)M,卷积核所覆盖的区域也相应转换成一列(行)的矩阵(向量)N,则该像素点的最后结果就是M·N,即这两个向量的点积。(我将卷积模板转换成行,其覆盖区域转换成列)
3:每次移动都可得到一个新的列,移动结束后就得到一个行为kernel.total(),列为src.total()的矩阵,将卷积模板转换后的行矩阵与该矩阵做乘积就得到了图像的卷积结果。

二、算法代码

void myFilter2D2(Mat &src, Mat &dst, Mat &kernel, double delta)
{
	int k_row = kernel.rows;
	int k_col = kernel.cols;
	int k_size = kernel.total();
	if (k_row % 2 != 1 || k_col % 2 != 1)
		return;
	int s_row = src.rows, s_col = src.cols;
	int s_size= src.total();
	if (k_row > s_row || k_col > s_col)
		return;
	dst.release();
	dst = Mat::zeros(src.rows, src.cols, CV_8U);

	/*因为k_row和k_col均为奇数,所以src边界外推时,
	向上和向下各外推k_row/2个像素单位,值均为零;
	向左和向右各外推k_col/2个像素单位,值均为零*/
	Mat src_extr;//输入图像边界外推后的图像
	copyMakeBorder(src, src_extr, k_row / 2, k_row / 2, k_col / 2, k_col / 2, 0);

	Mat new_src;
	new_src = Mat::zeros(k_size, s_size, CV_32F);
	Mat new_kernel;
	new_kernel = Mat::zeros(1, k_size, kernel.type());
	
	//将卷积模板转换成单行矩阵
	for (int i = 0;i < k_row;i++)
	{
		float *ptr = kernel.ptr<float>(i);
		for (int j = 0;j < k_col;j++)
		{
			new_kernel.at<float>(0,i*k_row + j)= ptr[j];
		}
	}
	
	int i, j, curKernelResult;//循环变量实例放在循环外
	for (i = 0;i < k_size;i++)
	{//小循环
		curKernelResult = 0;
		for (j = 0;j < s_size;j++)
		{//大循环
			new_src.at<float>(i, j) = src_extr.at<uchar>(j / s_col + i / k_col, j%s_col + i%k_col);
		}
	}
	
	//进行矩阵的乘积计算
	Mat result=new_kernel*new_src;	
	for (int i = 0;i < s_row;i++)
	{
		uchar *ptr = dst.ptr<uchar>(i);
		for (int j = 0;j < s_col;j++)
		{
			ptr[j] = result.at<float>(0, i*s_row + j)+delta;
		}
	}
}

算法的效率是较为低下的,主要是思路的不同。算法中主要的注意点就是使用at()函数时如何计算下标。

new_src.at<float>(i, j) = src_extr.at<uchar>(j / s_col + i / k_col, j%s_col + i%k_col);

在上一行代码中j可理解为卷积模板的第几次移动,i可表示一列中的第几个。j/s_col就是卷积模板中第一个像素在src_extr中的行数,i/k_col就是像素点在卷积模板中对应的行数,两者相加就可以得到new_src中像素点(i,j)在外推图像src_extr中对应的行数。同理j%s_col + i%k_col就表示该像素点在外推图像src_extr中对应的列数。

三、测试效果
代码

int main()
{
	//待卷积矩阵
	uchar points[25] = { 1,2,3,4,5,
		6,7,8,9,10,
		11,12,13,14,15,
		16,17,18,19,20,
		21,22,23,24,25 };
	Mat img(5, 5, CV_8UC1, points);

	//卷积模板
	Mat kernel = (Mat_<float>(3, 3) << 1, 2, 1, 2, 0, 2, 1, 2, 1);
	//Mat kernel_normal = kernel / 12;//卷积模板归一化

	//result_normal;
	Mat result, myresult;
	
	filter2D(img, result, CV_8U, kernel, Point(-1, -1), 2, BORDER_CONSTANT);//OpenCV库函数
	myFilter2D2(img, myresult, kernel, 2);

	cout << "库函数矩阵卷积结果:" << endl << result << endl;
	cout << "自定义函数矩阵卷积结果:" << endl << myresult << endl;

	cout << endl;
	
	//图像卷积
	Mat lena = imread("../lena.png");
	if (lena.empty())
	{
		cout << "请确认图像文件名称是否正确" << endl;
		return -1;
	}
	Mat mylena_filter,lena_filter;
	Mat gray;
	cvtColor(lena, gray, COLOR_BGR2GRAY);

	double startTime1 = clock();//1计时开始
	myFilter2D2(gray, mylena_filter, kernel_normal, 2);//自定义函数
	double endTime1 = clock();//1计时结束
	double startTime2 = clock();//2计时开始
	filter2D(gray, lena_filter, -1, kernel_normal, Point(-1, -1), 2, BORDER_CONSTANT);
	double endTime2 = clock();//2计时结束

	
	cout<<"自定义函数运行时间:"<<(double)(endTime1 - startTime1) / CLOCKS_PER_SEC << "s" << std::endl;
	cout << "库函数运行时间:" << (double)(endTime2 - startTime2) / CLOCKS_PER_SEC << "s" << std::endl;
	
	imshow("原图:",lena);
	imshow("灰度图像:", gray);
	imshow("库函数卷积后的灰度图像:", lena_filter);
	imshow("自定义函数卷积后的灰度图像:", mylena_filter);
	
	waitKey(0);
	return 0;
}

Mat img结果
在这里插入图片描述
我在卷积后的灰度图中从图像左上角截取了一部分点的像素值进行对比。
库函数卷积后部分图像的像素点
在这里插入图片描述
自定义函数卷积后部分图像的像素点
在这里插入图片描述

可以看到在对5*5Mat矩阵的卷积结果上两者是没有差别的,但在图像卷积上有一点的差距,同位置的像素点的数值有的出现了不同值,但相差不大。
运行时间差距比较大,算法效率低下,但思路是正确的,可行的。可以参考学习。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zmq1998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值