如何对图像进行卷积操作[通俗易懂]

如何对图像进行卷积操作[通俗易懂]1、首先先了解下什么是卷积呢?2、卷积操作:卷积核与原图对应位置相乘再求和;然后将所求和放在被卷积操作的图中心位置。上图表示一个8×8的原图,每个方格代表一个像素点;其中一个包含X的方格是一个5×5的卷积核,核半径等于5/2=2;进行卷积操作后,生成图像为上图中包含Y的方格,可以看出是一个4×4的生成图;通过比较观察可以发现,生成图比原图尺寸要小,为了保证生成…

大家好,又见面了,我是你们的朋友全栈君。

1、首先先了解下什么是卷积呢?
在这里插入图片描述
2、卷积操作:卷积核与原图对应位置相乘再求和;然后将所求和放在被卷积操作的图中心位置。
在这里插入图片描述
上图表示一个 8×8 的原图,每个方格代表一个像素点;其中一个包含 X 的方格是一个 5×5 的卷积核,核半径等于 5/2 = 2;

进行卷积操作后,生成图像为上图中包含 Y 的方格,可以看出是一个 4×4 的生成图;

通过比较观察可以发现,生成图比原图尺寸要小,为了保证生成图与原图保持尺寸大小一样,需要对原图进行边界补充,方法有如下四种:
(1)补零填充;
(2)镜像填充;
(3)块填充;
(4)边界复制填充。

由上图可知,生成图边界与原图边界差2个像素点,这是因为,卷积核半径为2,所以,为了保证图像处理前后尺寸一致,可将原图填充为 12×12 大小。

3、可参考下面程序了解卷积操作:

  • 方法一
//边缘锐化
Mat Kernel_test_3_3 = (Mat_<double>(3,3) << 
    0,-1,0,
    -1,5,-1,
    0,-1,0);
//均值平滑模板(平滑)
Mat Kernel_junzhi=(Mat_<double>(3,3) <<
    1.0/9,1.0/9,1.0/9,
    1.0/9,1.0/9,1.0/9,
    1.0/9,1.0/9,1.0/9);
Mat Kernel_junzhi_new=(Mat_<double>(5,5))<<
    0.04,0.04,0.04,0.04,0.04,
    0.04,0.04,0.04,0.04,0.04,
    0.04,0.04,0.04,0.04,0.04,
    0.04,0.04,0.04,0.04,0.04,
    0.04,0.04,0.04,0.04,0.04);

//拉普拉斯4邻域模板(锐化)
Mat Kernel_laPuLaSi=(Mat_<double>(3,3)<<
    0,1,0,
    1,-4,1,
    0,1,0);
//sobel算子模板(边缘检测)
Mat Kernel_sobel=(Mat_<double>(3,3)<<
    -1,0,1,
    -2,0,2,
    -1,0,1);


void Convlution(Mat  InputImage,Mat  OutputImage,Mat kernel)
{
    //计算卷积核的半径
    int sub_x = kernel.cols/2;
    int sub_y = kernel.rows/2;
    //遍历图片,除边界以外每个像素
    for (int image_y=0;image_y<InputImage.rows-2*sub_y;image_y++)
    {
        for(int image_x=0;image_x<InputImage.cols-2*sub_x;image_x++)
        {
            int pix_value = 0;//用来累加每个位置的乘积
            for (int kernel_y = 0;kernel_y<kernel.rows;kernel_y++)//对每一个点根据卷积模板进行卷积
            {
                for(int kernel_x = 0;kernel_x<kernel.cols;kernel_x++)
                {
                    double  weihgt = kernel.at<double>(kernel_y,kernel_x);
                    int value =  (int)InputImage.at<uchar>(image_y+kernel_y,image_x+kernel_x); 
                    pix_value +=weihgt*value;
                }
            }
            OutputImage.at<uchar>(image_y+sub_y,image_x+sub_x) = (uchar)pix_value;
            //OutputImage.at<uchar>(image_y+sub_y,image_x+sub_x) = saturate_cast<uchar>((int)pix_value);
            if ((int)pix_value!=(int)saturate_cast<uchar>((int)pix_value))
            {
                //cout<<"没有防溢出"<<(int)pix_value<<endl;
                //cout<<"防溢出"<<(int)saturate_cast<uchar>((int)pix_value)<<endl;
                //cout<<"没有防溢出写入了什么?"<<(int)OutputImage.at<uchar>(image_y+sub_y,image_x+sub_x)<<endl;
                //cout<<endl;
            }
        }
    }
}


int main()
{
    Mat srcImage = imread("1.jpg",0);//创建一个图形矩阵
    namedWindow("srcImage", WINDOW_AUTOSIZE);
    imshow("原图", srcImage);

    //filter2D卷积
    Mat dstImage_oprncv(srcImage.rows,srcImage.cols,CV_8UC1,Scalar(0));
    filter2D(srcImage,dstImage_oprncv,srcImage.depth(),Kernel_test_3_3);
    imshow("filter2D卷积图",dstImage_oprncv);
    imwrite("0.jpg",dstImage_oprncv);

    //自定义卷积1
    Mat dstImage_mycov(srcImage.rows,srcImage.cols,CV_8UC1,Scalar(0));
    Convlution(srcImage,dstImage_mycov,Kernel_test_3_3);
    imshow("卷积图1",dstImage_mycov);
    imwrite("1.jpg",dstImage_mycov);

    //均值平滑
    Mat dstImage_junzhi(srcImage.rows,srcImage.cols,CV_8UC1,Scalar(0));
    Convlution(srcImage,dstImage_junzhi,Kernel_junzhi);
    imshow("平滑图",dstImage_junzhi);
    imwrite("2.jpg",dstImage_junzhi);

    //拉普拉斯锐化
    Mat dstImage_lapulasi(srcImage.rows,srcImage.cols,CV_8UC1,Scalar(0));
    Convlution(srcImage,dstImage_lapulasi,Kernel_laPuLaSi);
    imshow("锐化图",dstImage_lapulasi);
    imwrite("3.jpg",dstImage_lapulasi);

    //sobel边缘检测
    Mat dstImage_sobel(srcImage.rows,srcImage.cols,CV_8UC1,Scalar(0));
    Convlution(srcImage,dstImage_sobel,Kernel_sobel);
    imshow("边缘检测图",dstImage_sobel);
    imwrite("4.jpg",dstImage_sobel);

    waitKey(0);
    return 0;

}
  • 方法二
/convolution卷积
//inputeImage图片矩阵 mytemplate模板矩阵
Mat convolution(Mat inputImage, Mat myTemplate) 
{
	int inputImageWidth = inputImage.size().width;//图片矩阵的宽度
	int inputImageHeigh = inputImage.size().height;//图片矩阵的高度
	int myTemplateWidth = myTemplate.size().width;//模板矩阵的宽度
	int myTemplateHeigh = myTemplate.size().height;//模板矩阵的高度
	Mat result(inputImageHeigh, inputImageWidth,CV_8UC1);//返回结果
	float temp;//卷积所用临时变量

	//两层for循环遍历除边界以外每一个像素
	for (int i = 1; i<inputImageHeigh - 1; i++)
	{
		for (int j = 1; j<inputImageWidth - 1; j++)
		{
			//对每一个点进行卷积
			temp = 0;//累加每一个位置的乘积
			for (int m = -1; m<myTemplateHeigh - 1; m++)
				for (int n = -1; n<myTemplateWidth - 1; n++)
					temp += myTemplate.at<float>((m + 1), (n + 1)) * inputImage.at<uchar>((i + m), (j + n));
			temp = (temp >= 0) ? temp : 0;//如果结果小于0置0
			temp = (temp <= 255) ? temp : 255;//如果结果大于255置255
			result.at<uchar>(i, j) = temp;//为结果矩阵对应位置赋值
		}
	}
	//边界不进行修改
	for (int i = 0; i < inputImageWidth; i++)//复制第一行和最后一行
	{
		result.at<uchar>(0, i) = inputImage.at<uchar>(0, i);
		result.at<uchar>((inputImageHeigh - 1), i) = inputImage.at<uchar>((inputImageHeigh - 1), i);
	}
	for (int i = 0; i < inputImageHeigh; i++)//复制第一列和最后一列
	{
		result.at<uchar>(i, 0) = inputImage.at<uchar>(i, 0);
		result.at<uchar>(i, (inputImageWidth - 1)) = inputImage.at<uchar>(i, (inputImageWidth - 1));
	}
	return result;
}

Mat matAddAndSqrt(Mat mat1,Mat mat2) //两个矩阵平方求和再开方
{
	int temp1 = 0;
	int temp2 = 0;
	for (int i = 0; i < mat1.size().height; i++)
		for (int j = 0; j < mat1.size().width; j++)
		{
			temp1 = mat1.at<uchar>(i, j);
			temp2 = mat2.at<uchar>(i, j);
			temp1 = sqrt(temp1*temp1 + temp2*temp2);
			temp1 = (temp1 <= 255) ? temp1 : 255;
			mat1.at<uchar>(i, j) = temp1;
		}
	return mat1;
}


int main()
{
	Mat image = imread("D:\.jpg", 0);
	Mat newImage1(image.size().height, image.size().width, CV_8UC1, Scalar(0));
	Mat newImage2(image.size().height, image.size().width, CV_8UC1, Scalar(0));
	Mat newImage3(image.size().height, image.size().width, CV_8UC1, Scalar(0));
	Mat newImage4(image.size().height, image.size().width, CV_8UC1, Scalar(0));

	//均值平滑模板
	Mat mat1 = (Mat_<float>(3, 3) << 1.0/9, 1.0/9, 1.0/9, 1.0/9, 1.0/9, 1.0/9, 1.0/9, 1.0/9, 1.0/9);
	newImage1 = convolution(image, mat1);
	//拉普拉斯4邻域锐化模板
	Mat mat2 = (Mat_<float>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
	newImage2 = convolution(image, mat2);
	newImage2 = image + newImage2;//锐化图像=原图像+加重的边缘
	//sobel算子边缘检测模板
	Mat mat3 = (Mat_<float>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);//横向边缘检测
	newImage3 = convolution(image, mat3);
	Mat mat4 = (Mat_<float>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);//纵向边缘检测
	newImage4 = convolution(image, mat4);
	//newImage3 = abs(newImage3) + abs(newImage4);//为了提高效率,使用绝对值相加为近似值
	newImage3 = matAddAndSqrt(newImage3, newImage4);//综合横向与纵向

	namedWindow("原图");//添加" , CV_WINDOW_NORMAL "可自由拖动鼠标调整窗口大小
	imshow("原图", image);
	namedWindow("均值平滑");
	imshow("均值平滑", newImage1);
	namedWindow("拉普拉斯4邻域锐化");
	imshow("拉普拉斯4邻域锐化", newImage2);
	namedWindow("sobel算子边缘检测");
	imshow("sobel算子边缘检测", newImage3);

	waitKey(0);
    return 0;
}

附:上述为个人理解,可能存在考虑不全面的情况,如果哪里有问题,还望大家留言指出~

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/144019.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Unity3D在一建筑GL材料可以改变颜色和显示样本

    Unity3D在一建筑GL材料可以改变颜色和显示样本

  • laravel 在nginx服务器上除了首页其余都是404的问题

    laravel 在nginx服务器上除了首页其余都是404的问题

  • 差分数组技巧

    差分数组技巧一、差分数组适用题型,和技巧前缀和数组:适用于原始数组不会被修改的情况下,频繁查询某个区间的累加和差分数组:主要适⽤场景是频繁对原始数组的某个区间的元素进⾏增减(比如:给你和数组arr,然后再下标0-4之间各元素加一,2-5之间各个元素减2,求最终的原数组)差分数组技巧1.构建差分数组(diff),diff[0]=nums[0],之后diff[i]=nums[i]-nums[i-1]int[]diff=newint[nums.length];//构造差分数组diff[0]=n

  • 常见的黑盒测试方法有哪些_黑盒测试方法10种

    常见的黑盒测试方法有哪些_黑盒测试方法10种常见的黑盒测试方法

  • 史上最全的Android面试题集锦

    史上最全的Android面试题集锦Android基本知识点1、常规知识点1、Android类加载器在Android开发中,不管是插件化还是组件化,都是基于Android系统的类加载器ClassLoader来设计的。只不过Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并、优化,然后…

  • Linux-nmap命令使用

    Linux-nmap命令使用用namp对局域网扫描一遍,然后查看arp缓存表就可以知道局域内ip-mac的对应了namp比较强大也可以直接扫描mac地址和端口进行ping扫描,打印出对扫描做出响应的主机:  nmap-sP192.168.1.0/24仅列出指定网络上的每台主机,不发送任何报文到目标主机:  nmap-sL192.168.1.0/24  探测目标主机开放的端口,可以指定一个以…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号