以下内容参考先前图像模糊处理章节:
https://blog.csdn.net/weixin_43534296/article/details/87701604
-
数字图像处理中卷积的概念明细:
首先我们先来明确一个重要数学概念——卷积。
在数字图像处理中存在着一个基本的处理方法——即线性滤波,线性滤波的基本原理就是通过一个滤波器小矩阵对原图像的大矩阵进行像素变换,而这个小矩阵我们就称作矩阵核。把矩阵核放到像素数组之上,求其锚点周围覆盖的像素乘积之和(包括锚点),用来替换锚点覆盖下像素点值的操作称为卷积处理,其数学表达式如下:
卷积操作的具体例子为下图:
-
常用的卷积算子:
①Robert算子:Robert算子,可以用于图像增强中的锐化,原因是作为一阶微分算子,Robert简单,计算量小,对细节反应敏感。算子对边缘检测的作用是提供边缘候选点,Robert算子相比于其他3x3算子,在不经过后处理时,可以给出相对较细的边缘。
②Sobel算子:
在边缘检测中,常用的一种模板是Sobel 算子。Sobel 算子有两个,一个是检测水平边缘的 ;另一个是检测垂直边缘的 。Sobel算子是滤波算子的形式,用于提取边缘,可以利用快速卷积函数, 简单有效,因此应用广泛。美中不足的是,Sobel算子并没有将图像的主体与背景严格地区分开来,换言之就是Sobel算子没有基于图像灰度进行处理,由于Sobel算子没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。
③拉普拉斯算子:
拉普拉斯算子是一种微分算子,因此强调的是图像中灰度的突变,并不强调灰度缓慢变化的区域。这将产生把浅灰色边线和突变点叠加到暗色背景中的图像。将原图像和拉普拉斯图像叠加在一起的简单方法,可以复原背景特性并保持拉普拉斯锐化处理的效果。
④除了这些由前人总结出来的经典算子外我们还可以自己定义算子 -
实验代码
Robert算子:// opencv_study14.cpp: 定义控制台应用程序的入口点。 #include"stdafx.h" #include<opencv2\opencv.hpp> #include<iostream> using namespace std; using namespace cv; int main() { Mat src,dst_x,dst_y; src = imread("J:/opencv_images/test.jpg"); if (src.empty()) { cout << "could not load the image" << endl; return -1; } namedWindow("orignal_images", CV_WINDOW_AUTOSIZE); imshow("orignal_images", src); //Robert x Mat kernel_x = (Mat_<int>(2, 2) << 1, 0, 0, -1); filter2D(src, dst_x, -1, kernel_x, Point(-1, -1), 0.0); //Robert y Mat kernel_y = (Mat_<int>(2, 2) << 0, 1, -1, 0); filter2D(src, dst_y, -1, kernel_y, Point(-1, -1), 0.0); imshow("robert_x", dst_x); imshow("robert_y", dst_y); waitKey(0); destroyAllWindows(); return 0; }
自定义算子:#include"stdafx.h" #include<opencv2\opencv.hpp> #include<iostream> using namespace std; using namespace cv; //自定义卷积模糊 int main() { Mat src,dst; int ksize; int index = 0; int c; src = imread("J:/opencv_images/timg.jpg"); if (src.empty()) { cout << "could not load the image" << endl; return -1; } namedWindow("orignal_images", CV_WINDOW_AUTOSIZE); imshow("orignal_images", src); while (1) { c = waitKey(500); if ((char)c == 27)break; ksize = 0 + (index % 5) * 2 + 1; Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F); index++; filter2D(src, dst, -1, kernel, Point(-1, -1), 0.0); imshow("result_image", dst); } waitKey(0); destroyAllWindows(); return 0; }
-
实验代码分析:
实验代码中用到的都是opencv一些基本的API,例如filter2D函数等,我们无需再进行赘述。我们需要注意的是卷积核的定义,卷积核的定义即创建Mat对象的方法,因而我们现在总结一下显示创建Mat对象的集中方法。
方法一:使用Mat()构造函数,这也是最常用的方法,简洁明了Mat M(2,2,CV_8UC3,Scalar(0,0,255));
上面的例子演示了如何创建一个超过两维的矩阵;指定维数,然后传递一个指向数组的指针,这个数组包含每个维度的尺寸;后续的两个参数与方法一中的相同。
方法二:在C/C++中通过构造函数进行初始化int sz[3]={2,2,2}; Mat L{3,sz,CV_8UC,Scalar::all(0))};
方法三:为已存在的IplImage指针创建信息头
IplImage* img = cvLoadImage("1.jpg",1); Mat mtx(img);
方法四:利用Create()函数
方法四是利用Mat类中的Create()成员函数进行Mat类的初始化操作M.create(4,4,CV_8UC(2));
方法五:采用matlab式的初始化方式:zeros(),ones(),eyes()
Mat E = Mat::eyes(4,4,CV_64F); Mat O = Mat::ones(4,4,CV_32F); Mat Z = Mat::zeros(4,4,CV_8UC1);
方法六:对小矩阵使用逗号分隔式初始化函数:
Mat kernel_y = (Mat_<int>(2, 2) << 0, 1, -1, 0);
方法七:为已存在的对象创建新信息头:方法七为使用成员函数clone()或者copyTo()为一个已存在的Mat对象创建一个新的信息头,示范代码如下:
Mat RowClone = C.row(1).clone();