前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >如何对图像进行卷积操作[通俗易懂]

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

作者头像
全栈程序员站长
发布于 2022-08-26 05:43:56
发布于 2022-08-26 05:43:56
2.9K02
代码可运行
举报
运行总次数:2
代码可运行

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

1、首先先了解下什么是卷积呢?

2、卷积操作:卷积核与原图对应位置相乘再求和;然后将所求和放在被卷积操作的图中心位置。

上图表示一个 8×8 的原图,每个方格代表一个像素点;其中一个包含 X 的方格是一个 5×5 的卷积核,核半径等于 5/2 = 2;

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

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

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

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

  • 方法一
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//边缘锐化
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;

}
  • 方法二
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/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:\\7.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;
}

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
降龙算法1:图像的基本数据格式(8K字)
开始更新降龙算法系列了。因为停更近半年了,所以先啰嗦几句话交代一下这个系列的前因后果:
周旋
2023/01/30
5530
OpenCV图像锐化---USM锐化和Laplace锐化
图像锐化 (image sharpening) 是补偿图像的轮廓,增强图像的边缘及灰度跳变的部分,使图像变得清晰,分为空间域处理和频域处理两类。图像锐化是为了突出图像上地物的边缘、轮廓,或某些线性目标要素的特征。这种滤波方法提高了地物边缘与周围像元之间的反差,因此也被称为边缘增强。
Vaccae
2022/04/06
10.5K0
OpenCV图像锐化---USM锐化和Laplace锐化
理解图像卷积操作的意义
卷积一词最开始出现在信号与线性系统中,信号与线性系统中讨论的就是信号经过一个线性系统以后发生的变化。由于现实情况中常常是一个信号前一时刻的输出影响着这一时刻的输出,所在一般利用系统的单位响应与系统的输入求卷积,以求得系统的输出信号(当然要求这个系统是线性时不变的)。 卷积的定义: 卷积是两个变量在某范围内相乘后求和的结果。如果卷积的变量是序列x(n)和h(n),则卷积的结果:
全栈程序员站长
2022/09/01
9880
C++实现卷积操作
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/140281.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/01
6890
聊一聊OpenCV的saturate_cast防溢出
本文介绍了OpenCV中的saturate_cast函数的作用和原理,该函数用于将一个整型值转换为CV_8UC1格式的Mat,并处理溢出情况。在具体应用中,该函数可以用于对图像进行灰度变换、滤波等操作。在处理边界溢出的情况时,该函数使用了一种巧妙的算法,将边界上的像素值进行特殊处理,避免了图像失真等问题。
chaibubble
2018/01/02
2.1K0
聊一聊OpenCV的saturate_cast防溢出
Canny边缘检测算法的原理与实现[通俗易懂]
Canny的原理就不细说了,冈萨雷斯的《数字图像处理》(中文第三版)P463~465讲解的比较清楚,主要就四个步骤:
全栈程序员站长
2022/08/23
1.1K0
Canny边缘检测算法的原理与实现[通俗易懂]
OpenCV 角点检测(二) Harrise
Harrise算子是在Moravec算子的基础上改进得到的,Moravec角点检测算子见链接:http://blog.csdn.net/chaipp0607/article/details/54649235
chaibubble
2022/05/07
2400
OpenCV 角点检测(二) Harrise
Opencv分水岭算法——watershed自动图像分割用法[通俗易懂]
分水岭算法是一种图像区域分割法,在分割的过程中,它会把跟临近像素间的相似性作为重要的参考依据,从而将在空间位置上相近并且灰度值相近的像素点互相连接起来构成一个封闭的轮廓,封闭性是分水岭算法的一个重要特征。
全栈程序员站长
2022/09/07
4.7K0
【手撕算法】基于队列实现的区域增长分割算法
上一篇介绍了深度搜索DFS和广度搜索BFS两个算法,本文就是基于BFS算法实现的区域增长算法。
周旋
2022/08/07
7180
【手撕算法】基于队列实现的区域增长分割算法
OpenCV mat类实现水平投影和垂直投影
图像经过灰度化和otsu阈值分割,分别绘制水平和垂直投影 #include<iostream> #include <cv.h> #include <highgui.h> #include <cvaux.h> #include <tchar.h> using namespace std ; using namespace cv ; int main() { //原图 Mat srcImage=imread("test.png"); imshow("原图",srcImage
chaibubble
2022/05/07
7480
OpenCV mat类实现水平投影和垂直投影
理解图像卷积操作的意义
chaibubble
2018/01/02
3.9K0
理解图像卷积操作的意义
【手撕算法】opencv实现走迷宫算法
本文利用opencv实现了深度优先搜索DFS和广度优先搜索BFS两个算法来走迷宫,迷宫也是用opencv+鼠标画的。
threeQing
2021/09/29
7870
【手撕算法】opencv实现走迷宫算法
【OpenCV入门教程之十二】OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑
效果图看完,我们来唠唠嗑。 首先,需要说明的是,浅墨这篇文章最后的示例代码是采用两周前刚刚发布的2.4.9来书写的。里面的lib都已经改成了2.4.9版本的。如果大家需要运行的话,要么配置好2.4.9.要么把浅墨在工程中包含的末尾数字为249的各种lib改成之前的248或者你对应的OpenCV版本。 不然会提示: LINK : fatal error LNK1181: 无法打开输入文件“opencv_calib3d248.lib”之类的错误。 OpenCV 2.4.9的配置和之前的2.4.8差不多,如果还是不太清楚,具体可以参考浅墨修改过的对应2.4.9版的配置文章: 【OpenCV入门教程之一】 安装OpenCV:OpenCV 2.4.8或2.4.9 +VS 开发环境配置 第二,给大家分享一个OpenCV中写代码时节约时间的小常识。其实OpenCV中,不用namedWindow,直接imshow就可以显示出窗口。大家看下文的示例代码就可以发现,浅墨在写代码的时候并没有用namedWindow,遇到想显示出来的Mat变量直接imshow。我们一般是为了规范,才先用namedWindow创建窗口,再imshow出它来,因为我们还有需要用到指定窗口名称的地方,比如用到trackbar的时候。而一般情况想显示一个Mat变量的图片的话,直接imshow就可以啦。 OK,开始正文吧~ 一、关于边缘检测 在具体介绍之前,先来一起看看边缘检测的一般步骤吧。 1)滤波:边缘检测的算法主要是基于图像强度的一阶和二阶导数,但导数通常对噪声很敏感,因此必须采用滤波器来改善与噪声有关的边缘检测器的性能。常见的滤波方法主要有高斯滤波,即采用离散化的高斯函数产生一组归一化的高斯核(具体见“高斯滤波原理及其编程离散化实现方法”一文),然后基于高斯核函数对图像灰度矩阵的每一点进行加权求和(具体程序实现见下文)。 2)增强:增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将图像灰度点邻域强度值有显著变化的点凸显出来。在具体编程实现时,可通过计算梯度幅值来确定。 3)检测:经过增强的图像,往往邻域中有很多点的梯度值比较大,而在特定的应用中,这些点并不是我们要找的边缘点,所以应该采用某种方法来对这些点进行取舍。实际工程中,常用的方法是通过阈值化方法来检测。 另外,需要注意,下文中讲到的Laplace算子,sobel算子和Scharr算子都是带方向的,所以,示例中我们分别写了X方向,Y方向和最终合成的的效果图。 OK,正餐开始,召唤canny算子。:) 二、canny算子篇 2.1 canny算子相关理论与概念讲解
全栈程序员站长
2022/09/02
9450
【OpenCV入门教程之十二】OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑
图像的卷积(滤波)运算(一)——图像梯度
首先要明确的一点是图像的卷积/滤波运算,是针对原图像每一个像素进行处理,得到一个新的图像的过程。那么进行怎么样的运算呢?要知道图像能够被人所识别,是因为图像中每个像素并不完全是离散而独立的,每个像素都跟周围的像素相关。因此,对每一个像素,选定其周围一定范围内的像素值进行运算,得到新的图像的像素值也一定是相关的。而这个范围,就是卷积/滤波的窗口。
charlee44
2019/08/13
1.8K0
OpenCV图像处理专栏十七 | 清华大学《基于单幅图像的快速去雾》C++复现(有一定工程意义)
这是OpenCV图像处理算法朴素实现专栏的第17篇文章。今天为大家带来一篇之前看到的用于单幅图像去雾的算法,作者来自清华大学,论文原文见附录。
BBuf
2020/03/19
1.4K0
OpenCV图像处理专栏十七 | 清华大学《基于单幅图像的快速去雾》C++复现(有一定工程意义)
角点检测方法_什么叫五点取样法
这里有个细节:将计算的所有方向上的变化值平方和的最小值作为像素点的灰度变化特征值。为何是最小值呢? 分别对平均区域、边缘区域、角落区域进行计算,观察结果:
全栈程序员站长
2022/11/01
5570
暗通道去雾的好文合集
https://www.cnblogs.com/Imageshop/p/3281703.html https://blog.csdn.net/qq_34902877/article/details/103432959 https://blog.csdn.net/qq_29462849/article/details/84848631 https://www.cnblogs.com/herenzhiming/articles/5280759.html 下面给出第3篇文章中代码的详细注释版本。 首先是头文件 #
zy010101
2020/03/25
3530
OpenCV图像处理笔记(二):图片操作进阶
一、图像模糊 1、模糊原理 Smooth/Blur 是图像处理中最简单和常用的操作之一 使用该操作的原因之一就为了给图像预处理时候减低噪声 使用Smooth/Blur操作其背后是数学的卷积计算 通常这
MiChong
2020/09/24
1.2K0
OpenCV图像处理笔记(二):图片操作进阶
opencv角点检测学习总结[通俗易懂]
如果一个点在两个正交方向上都有明显的导数,则我们认为此点更倾向于是独一无二的,所以许多可跟踪的特征点都是角点。
全栈程序员站长
2022/09/27
9780
Canny算法解析,opencv源码实现及实例[通俗易懂]
Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法。
全栈程序员站长
2022/09/02
2.3K0
推荐阅读
相关推荐
降龙算法1:图像的基本数据格式(8K字)
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文