网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
9.1、椒盐噪声
是一种随机在图像中出现的稀疏分布的黑白像素点, 对椒盐噪声一种有效的去噪手段就是图像中值滤波(因为均值滤波就是取中值,而椒盐噪声又是黑白像素点,因此中值滤波去椒盐噪声的效果是比较好的)。它的本质其实就是在图像中部分像素是0或255的杂像素点。
代码实践:
//产生椒盐噪声
void QuickDemo::add\_saltPepperNoise(Mat &image)
{
imshow("原图", image);
RNG rng(12345);//分配随机数种子
int h = image.rows;
int w = image.cols;
int nums = 10000;
for (int i = 0; i < nums; i++)
{
int x = rng.uniform(0, w);//0到w之间参数一个随机数
int y = rng.uniform(0, h);
if (i % 2 == 1) {
image.at<Vec3b>(y, x) = Vec3b(255, 255, 255);//椒盐噪声的核心就是产生黑白像素点
}
else
{
image.at<Vec3b>(y, x) = Vec3b(0, 0, 0);
}
}
imshow("salt pepper", image);
}
9.2、高斯噪声/符合高斯分布
一般会在数码相机的图像采集(acquisition)阶段发生的一些错误,这个时候它的物理/电/光等各种信号都可能导致产生高斯分布噪声。
代码实践:
//产生高斯噪声
void QuickDemo::add\_gaussianNoise(Mat &image)
{
imshow("原图", image);
Mat nosi = Mat::zeros(image.size(), image.type());
randn(nosi, (15, 15, 15), (30, 30, 30));//用正态分布的随机数填充数组。 产生均值15, 方差30的高斯图像
Mat dst;
add(image, nosi, dst);//叠加
imshow("gaussian Noise", dst);
}
9.3、均匀分布噪声
这种噪声都是由于某种规律性的错误导致的,这样消除在硬件上成本就比较高一点一般会考虑在软件上进行消除的。
10、图像去噪声
图像去噪对图像二值化与二值分析是很有作用的,opencv当中常见的去噪方法有。
均值去噪声:也就是我们的卷积模糊的作用,
高斯模糊去噪;非均值卷积模糊的作用
非局部均值去噪声:opencv中提供了专门的API;fastNlMeansDenoisingColored
双边滤波去噪;
形态学去噪:主要是针对二值图像的灰度图像效果相对明显一点。
均值和中值去噪的区别。
实践
卷积我们最好是选择33的,最多选择55,再选择大了的话就会影响原本图像了。
代码实际 :
//去除噪声
void QuickDemo::pictureNoise\_wipe(Mat &image)
{
Mat ret1, ret2, ret3, ret4;
blur(image, ret1, Size(3,3));//最好选择3\*3的卷积,最多选择5\*5 再大就会影响原图像数据了
imshow("卷积模糊去噪", ret1);
GaussianBlur(image, ret2, Size(3, 3), 0);
imshow("高斯模糊去噪", ret2);
medianBlur(image, ret3, 3);
imshow("中值卷积去噪", ret3);
/\*
非局部均值去噪声:他是有两个版本的一个是针对灰度图,一个是有颜色的图像
第3、4个参数分别是表示我们的颜色分量和亮度分量,取一个阈值分量,这个分量值越大去噪的程度越大那么图像原本的细节也会被去除掉。通常不用超过10或15,默认3
第5,6个参数,搜索窗口大小,和模板窗口的大小 ; 注意模板窗口一个不能大过搜索窗口,因为模板窗口是在搜索窗口里面进行搜索的
非局部均值去噪;实际上是一个大的窗口上有一个小的搜索窗口和模糊窗口,通过这两个窗口进行搜索或移动来达到非局部均值去噪
\*/
fastNlMeansDenoisingColored(image, ret4, 15,15,10, 30);
imshow("非局部均值去噪声", ret4);
}
11、边缘保留滤波算法
边缘保留滤波算法(EPF):之前介绍的滤波去噪都是模糊卷积(均值卷积或高斯卷积),他们都是模糊之后图像的边缘信息不复存在,受到了破坏。而边缘保留滤波算法就是在通过卷积处理实现图像模糊的同时对图像边缘不会造成破坏,滤波之后的输出完整的保存了图像整体边缘(轮廓)信息。常见的边缘保留滤波算法有以下几种:
高斯双边模糊、PS的磨皮算法一般用这个精度高只是耗时需要一定条件。
MeanShitf均值迁移模糊、实际当中是基于金字塔的
局部均方差模糊、基于积分图思想的快速计算的边缘保留滤波算法,在深度学习没有大热起来之前美图pp他是最常用的。
opencv当中专门提供的一个边缘保留滤波的API函数;对我们去噪是很有作用的
11.1、高斯双边模糊
高斯模糊是考虑图像空间位置对权重的影响(离输出中心点越近的那么他所占的权重也就越大,考虑的输出位置对我们卷积的影响),但是它没有考虑图像像素分布对图像卷积输出的影响(其实正确的方式应该只有周围像素值相差不大的才进行卷积计算的,说明他们有相同的像素分布,如果他们像素值相差比较大那么他们就应该从计算当中剥离出来不参与本个像素点值的卷积计算),双边模糊考虑了像素值分布的影响,对像素值空间分布差异较大的进行保留从而完整的保留了图像的边缘信息。
不是整个图形都进行模糊,这样把图像原有的信息丢掉了。高斯双边模糊就是处理这个问题的,他模糊的同时保留了较大区别的边缘(如亮暗的边缘)而模糊掉了一些细节处理。
双边是指的空间的和色彩的。
实践
CV_EXPORTS_W void bilateralFilter( InputArray src, OutputArray dst, int d,
double sigmaColor, double sigmaSpace,
int borderType = BORDER_DEFAULT );
InputArray src, OutputArray dst, 原图像和目标图像
int d,窗口大小,之前说了可以填0 由后面的sigma来反推计算
double sigmaColor, 这个值要大一点,用于色彩的卷积核
double sigmaSpace, 空间的卷积核
int borderType = BORDER_DEFAULT 边缘的处理方式
11.1.1、使用高斯双边模糊还可以实现防盗水印的去除的
图19;图片来自于贾志刚老师的知识星球内容
//计算双边高斯卷积
void QuickDemo::Bilateralgaussian\_blur\_demo(Mat &image)
{
/\*namedWindow("原图", WINDOW\_FREERATIO);
imshow("原图", image);\*/
Mat dst;
//100表示色彩的卷积核 要大一点
//10表示空间距离的卷积核
//d表示我们输出的维度是否放大缩小,一般我们都是与输入保持一致的
// sigmaColor, 表示色彩空间色彩相差多大范围之内的他才参与计算,这个可以填大一点
// sigmaSpace,表示高斯模糊里面的 sigmaX,sigmaY,空间的的sigma,这个值要选择小一点 5,10,15
bilateralFilter(image, dst, 0, 100, 10);
Mat combine1, combine;
hconcat(image, dst, combine1);//水平拼接函数
vconcat(image, dst, combine);//垂直拼接函数
namedWindow("高斯模糊1", WINDOW_FREERATIO);
imshow("高斯模糊1", combine);
namedWindow("高斯模糊", WINDOW_FREERATIO);
imshow("高斯模糊", combine1);
}
图像水平竖直拼接函数实际
11.2、均值迁移模糊(mean-shift blur)
概念:是边缘保留滤波算法中的一种,经常用来对图像分水岭分割之前对图像去噪,并且均值迁移模糊不仅可以对图像进行模糊还可以实现对图像的目标跟踪,图像模式识别 视频分析对比等多环节都可以进行应用是很经典的图像处理算法。
基本原理
前面卷积就是要在图片上开窗点乘,这是空间域卷积上要做的事情。而我们均值迁移模糊就是在图像进行开窗的时候同样,不仅考虑像素值空间范围分布(窗口位置中心点与窗口位置的空间关系),而且还要考虑空间关系中像素值的分布, 只有符合像素值分布的像素点才参与计算,他们参与计算之后得到像素均值与空间位置xy也有均值(对于彩色图像而言我们就是要找到均值迁移当中的五个向量RGB三通道和空间的xy分别在这些方向上移动了多少,再对比原来的中心位置的差那么就可以得到新的均值位置),使用新的均值位置作为窗口中心位置继续基于给定像素值空间分布计算均值与均值位置,如此不断迁移中心位置直到不再变化位置(dx=dy=0)(就是向密度高的方向不断迁移直到来到密度最高的中心位置,因为这个时候得到的均值与所有像素去得到的均值也是一致的了,当一致之后就不会再有data数据移动了,如果有一个像素点没有参数计算那么他的均值不可能是0,就是因为他周围的像素点分布已经与中心点已经很相似了,符合我们要求,因此这个算法是基于均值的因此会不断迁移),但是在实际情况中数据可能不会那么理想化,因此我们会人为设置一个停止条件比如迁移几次,这样就可以把最后的RGB均值赋值给中心位置。
实际
代码
//均值迁移滤波
void QuickDemo::MeanShift(Mat &image)
{
/\*
sp 空间大小就是我们的空间域
sr颜色大小就是我们色彩空间分布值
在什么情况停止:5是迁移5次,或者两次迁移从差值小于1的时候停止
void pyrMeanShiftFiltering( InputArray src, OutputArray dst,
double sp, double sr, int maxLevel = 1,
TermCriteria termcrit=TermCriteria(TermCriteria::MAX\_ITER+TermCriteria::EPS,5,1) );
\*/
Mat ret1, combine1;
pyrMeanShiftFiltering(image, ret1, 15, 50, 1, TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 5, 1));
hconcat(image, ret1, combine1);//水平拼接函数
imshow("均值迁移滤波", combine1);
}
11.3、图像积分图算法
概念:为了在多尺度透视投影中提高渲染速度,是一种快速计算图像区域和与平方和的算法。他可以很快速度的计算一幅图像的任意区域的像素和或者平方和,也就是我们卷积窗口下对应区域的像素和和平方和他都可以很快获取到。
**其核心思想:**对每个图像建立自己的积分图查找表(如果是计算和那么就要积分和图查找表、如果是计算平方和那么就是建立自己的 积分图平方和查找表),在图像积分处理计算阶段根据预先建立的积分图查找表,直接查找从而实现对均值卷积线性时间计算(我们进行卷积的时候就可以实现对图像线性时间的查找计算),做到了卷积执行的时间与半径窗口大小的无关联。图像积分图在图像特征提取HAAR/SURF、二值图像分析、图像相似相关性NCC计算、图像卷积快速计算等方面均有应用,是图像处理中的经典算法之一。
图像积分图建立与查找 :
在积分图像(Integral Image - ii)上任意位置(x, y)处的ii(x, y)表示该点左上角所有像素之和, 其中(x,y)是图像像素点坐标。
基于图像平方和表来做一个图像均值模糊,也就是基于图像积分图
代码
//图像积分图算法
int getblockSum(Mat &sum, int x1, int y1, int x2, int y2, int i) {//获取指定区域的和
int tl = sum.at<Vec3i>(y1, x1)[i];
int tr = sum.at<Vec3i>(y2, x1)[i];
int bl = sum.at<Vec3i>(y1, x2)[i];
int br = sum.at<Vec3i>(y2, x2)[i];
int s = (br - bl - tr + tl);
return s;
}
void blurintegral\_demo(Mat &image, Mat &sum) {//图像积分图算法
int w = image.cols;
int h = image.rows;
Mat result = Mat::zeros(image.size(), image.type());
int x2 = 0, y2 = 0;
int x1 = 0, y1 = 0;
int ksize = 5;//图像直径
int radius = ksize / 2;
int ch = image.channels();
int cx = 0, cy = 0;
//遍历像素点
for (int row = 0; row < h + radius; row++) {
y2 = (row + 1)>h ? h : (row + 1);
y1 = (row - ksize) < 0 ? 0 : (row - ksize);
for (int col = 0; col < w + radius; col++) {
//然后要找到区域四个点的位置传过去得到区域积分和
x2 = (col + 1)>w ? w : (col + 1);
x1 = (col - ksize) < 0 ? 0 : (col - ksize);
cx = (col - radius) < 0 ? 0 : col - radius;
cy = (row - radius) < 0 ? 0 : row - radius;
int num = (x2 - x1)\*(y2 - y1);//有多少个像素参与计算
for (int i = 0; i < ch; i++) {
// 积分图查找和表,计算卷积
int s = getblockSum(sum, x1, y1, x2, y2, i);
//和表除以个数就是得到中心像素点的值 均值滤波的一个输出 也就是这块区域积分和表均值
result.at<Vec3b>(cy, cx)[i] = saturate\_cast<uchar>(s / num);
}
}
}
Mat combine1;
hconcat(image, result, combine1);//水平拼接函数
imshow("output", combine1);
//imwrite("D:/result.png", result);
}
void QuickDemo::integral\_demo()
{
/\*
sum 和表opencv中有API可以输出和表的
sqsum 平方和表
tilted 瓦块和表(就是倾斜45度的和表) 重载了该参数可以没有
sdepth 和表数据深度常见:CV\_32S //保证数据不会溢出
sqdepth 平方和表数据深度常见:CV\_32F //保证数据不会溢出
void integral(InputArray src, OutputArray sum,
OutputArray sqsum, OutputArray tilted,
int sdepth = -1, int sqdepth = -1);
\*/
//需要区域的四个点坐标 num有多少个像素点参与计算,cx/cy中心点的坐标 就可以通过getblockSum和表
Mat src = imread("D:/images/yuan\_test.png");
if (src.empty()) {
printf("could not load image...\n");
return ;
}
// 计算积分图
Mat sum, sqrsum;
integral(src, sum, sqrsum, CV_32S, CV_32F);
blurintegral\_demo(src, sum);
}
11.4、快速的图像边缘滤波算法
高斯双边模糊与mean shift均值模糊两种边缘保留滤波算法,都因为计算量比较大,无法实时实现图像边缘保留滤波,限制了它们的使用场景,OpenCV中还实现了一种快速的边缘保留滤波算法。高斯双边与mean shift均值在计算时候使用五维向量是其计算量大速度慢的根本原因,该算法通过等价变换到低纬维度空间,实现了数据降维与快速计算。
实践
代码
void QuickDemo::edgePreserving\_demo()
{
Mat src = imread("D:/images/example.png");
Mat dst;
/\*
flag 1 就表示我们选择的边缘保留算法其中的一种 还有其他的边缘保留滤波降维算法,这只是i其中的一种。
sigma\_s 空间域的窗口值,跟我们开窗模糊都有关系。
sigma\_r 表示颜色空间域的特征值。
其中sigma\_s的取值范围为0~200, sigma\_r的取值范围为0~1
注意sigma\_s 和sigma\_r 在取某些值的时候会影响最后的结果输出。
sigma\_s 和sigma\_r都很小的时候(sigma\_s 很大,sigma\_r很小)那个边缘保留滤波基本很少的影响。
只有当两者都取到合适的值的时候,其中一个不变,另外一个变大的话那么图像的模糊效果就会越明显。
CV\_EXPORTS\_W void edgePreservingFilter(InputArray src, OutputArray dst, int flags = 1,
float sigma\_s = 60, float sigma\_r = 0.4f);
\*/
edgePreservingFilter(src, dst, 1, 60, 0.44);
Mat combine1;
hconcat(src, dst, combine1);//水平拼接函数
imshow("output", combine1);
return;
}
12、OpenCV中的自定义滤波器
图像卷积最主要功能有图像模糊、锐化、梯度边缘等,前面已经分享图像卷积模糊的相关知识点,OpenCV除了支持上述的卷积模糊(均值与边缘保留)还支持自定义卷积核,实现自定义的滤波操作。自定义卷积核常见的主要是均值、锐化、梯度等算子。下面的三个自定义卷积核分别可以实现卷积的均值模糊、锐化、梯度功能。
代码
void QuickDemo::filter2D\_demo()
{
Mat src = imread("D:/images/test.png");
Mat kernel1 = Mat::ones(5, 5, CV_32F) / (float)(25); //均值算子
//锐化算子
Mat kernel2 = (Mat\_<char>(3, 3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
//梯度算子
Mat kernel3 = (Mat\_<int>(2, 2) << 1, 0, 0, -1);
/\*
ddepth 输出图像的深度,默认-1表示输入输出是一致的
InputArray kernel;// 卷积核或者卷积窗口大小
Point anchor;就是卷积的铆点,就是最后卷积结果赋值的地方。
CV\_EXPORTS\_W void filter2D(InputArray src, OutputArray dst, int ddepth,
InputArray kernel, Point anchor = Point(-1, -1),
double delta = 0, int borderType = BORDER\_DEFAULT);
\*/
Mat dst1, dst2, dst3;
filter2D(src, dst1, -1, kernel1);
filter2D(src, dst2, -1, kernel2);
filter2D(src, dst3, CV_32F, kernel3);
convertScaleAbs(dst3, dst3);
imshow("blur=5x5", dst1);
imshow("shape=3x3", dst2);
imshow("gradient=2x2", dst3);
return;
}
13、图像梯度 – Sobel算子
卷积的作用除了实现图像模糊或者去噪,还可以寻找一张图像上所有梯度信息,这些梯度信息是图像的最原始特征数据,进一步处理之后就可以生成一些比较高级抽象的特征信息用来表示一张图像实现基于图像特征的匹配,图像分类等应用。
Sobel算子是一种很经典的图像梯度提取算子,其本质是基于图像空间域卷积,背后的思想是图像一阶导数算子的理论支持。OpenCV实现了基于Sobel算子提取图像梯度的API,表示与解释如下:
API介绍
void cv::Sobel(
InputArray src, // 输入图像
OutputArray dst, // 输出结果
int ddepth, // 图像深度CV\_32F
int dx,// 1,X方向 一阶导数
int dy, // 1,Y方向 一阶导数
int ksize = 3, // 窗口大小
double scale = 1, // 放缩比率,1 表示不变
double delta = 0, // 对输出结果图像加上常量值
int borderType = BORDER_DEFAULT )
ddepth表示图像的深度,-1表示于输入图像一致的深度,但是这个时候会有应该麻烦,-1出来的sobel梯度图像效果很差,原因是sobel算子的范围已经超过了0-255,所以当我们用字节类型去存储的时候就会被截取从而导致x有些信息丢失不全了从而以sobel算子计算出来d的梯度信息也就不准了,
int dx,// 1,X方向 一阶导数
int dy, // 1,Y方向 一阶导数
其中s可以dx=1,dy=0那么就表示是x方向的一阶导数,反之同意是y方向的一阶导数。
int borderType = BORDER_DEFAULT ;就是在卷积的时候怎么c去处理图像边缘的类型,给定一个方法。
实践
代码
void QuickDemo::Sobel\_demo()
{
Mat src = imread("D:/images/hist\_02.jpg");
Mat grad_x, grad_y;
Mat dst;
/\*
CV\_EXPORTS\_W void Sobel(
InputArray src,
OutputArray dst,
int ddepth, // 图像深度CV\_32F
int dx,X方向 一阶导数
int dy, Y方向 一阶导数
int ksize = 3,// 窗口大小
double scale = 1,// 放缩比率,1 表示不变
double delta = 0,// 对输出结果图像加上常
int borderType = BORDER\_DEFAULT);就是在卷积的时候怎么c去处理图像边缘的类型,给定一个方法。
注意:
其中s可以dx=1,dy=0那么就表示是x方向的一阶导数,反之同意是y方向的一阶导数。
ddepth表示图像的深度,-1表示于输入图像一致的深度,但是这个时候会有应该麻烦,-1出来的sobel梯度图像效果很差,
原因是sobel算子的范围已经超过了0-255,所以当我们用字节类型去存储的时候就会被截取从而导致x有些信息丢失不全了
从而以sobel算子计算出来d的梯度信息也就不准了,
\*/
Sobel(src, grad_x, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT);//计算得到图像的X方向的梯度,并且是CV\_32F的
Sobel(src, grad_y, CV_32F, 0, 1, 3, 1, 0, BORDER_DEFAULT);//计算得到图像的Y方向的梯度
//就会得到一个8位的放缩之后的0-255的图像,就是字节大小的图像了,就是吧float类型的图像转换位255的图像说明我们的信息就没有损失
//因为Sobel的出来的结果有可能是正值也有可能是负值,因此要经过这个转换到0-255之间的
convertScaleAbs(grad_x, grad_x);
convertScaleAbs(grad_y, grad_y);
//在将X,Y方向梯度的梯度值转换为8位之后再进行叠加
add(grad_x, grad_y, dst, Mat(), CV_16S);//因为叠加之后的结果又会超出0-255,因此需要使用CV\_16S接收
convertScaleAbs(dst, dst);//最后再次进行转换
Mat combine1;
hconcat(src, dst, combine1);//水平拼接函数
imshow("output", combine1);
}
14、图像梯度 – 更多梯度算子robert算子与prewitt算子
图像的一阶导数算子除了sobel算子之外,常见的还有robert算子与prewitt算子,它们也都是非常好的可以检测图像的梯度边缘信息,通过OpenCV中自定义滤波器,使用自定义创建的robert与prewitt算子就可以实现图像的rober与prewitt梯度边缘检测。
实践
代码
void QuickDemo::robertAndprewitt\_demo()
{
Mat src = imread("D:/images/hist\_02.jpg");
//定义两种梯度的算子
Mat robert_x = (Mat\_<int>(2, 2) << 1, 0, 0, -1);
Mat robert_y = (Mat\_<int>(2, 2) << 0, -1, 1, 0);
Mat prewitt_x = (Mat\_<char>(3, 3) << -1, 0, 1,
-1, 0, 1,
-1, 0, 1);
Mat prewitt_y = (Mat\_<char>(3, 3) << -1, -1, -1,
0, 0, 0,
1, 1, 1);
/\*
通过自定义的卷积核然后再通过filter2D来实现我们的robert算子与prewitt算子的一阶导数计算和效果演示。
ddepth,这里不能用 - 1,因为算子不一样那么取值范围也不一样,那么久不能于输入图像一致了,而是要与输出的结果相匹配。
\*/
Mat dst;
Mat robert_grad_x, robert_grad_y, prewitt_grad_x, prewitt_grad_y;
filter2D(src, robert_grad_x, CV_16S, robert_x);
filter2D(src, robert_grad_y, CV_16S, robert_y);
convertScaleAbs(robert_grad_x, robert_grad_x);//转换位8位的0-255之间的
convertScaleAbs(robert_grad_y, robert_grad_y);
add(robert_grad_x, robert_grad_y, dst, Mat(), CV_16S);//因为叠加之后的结果又会超出0-255,因此需要使用CV\_16S接收
convertScaleAbs(dst, dst);//最后再次进行转换
Mat combine1;
hconcat(src, dst, combine1);//水平拼接函数
imshow("robert", combine1);
filter2D(src, prewitt_grad_x, CV_32F, prewitt_x);
filter2D(src, prewitt_grad_y, CV_32F, prewitt_y);
convertScaleAbs(prewitt_grad_x, prewitt_grad_x);
convertScaleAbs(prewitt_grad_y, prewitt_grad_y);
add(prewitt_grad_x, prewitt_grad_y, dst, Mat(), CV_16S);//因为叠加之后的结果又会超出0-255,因此需要使用CV\_16S接收
convertScaleAbs(dst, dst);//最后再次进行转换
hconcat(src, dst, combine1);//水平拼接函数
imshow("prewitt", combine1);
}
15、图像梯度 – 拉普拉斯算子(二阶导数算子)
图像的一阶导数算子可以得到图像梯度局部梯度相应值,二阶导数可以通过快速的图像像素值强度的变化来检测图像边缘,其检测图像边缘的原理跟图像的一阶导数有点类似,只是在二阶导数是求X、Y方向的二阶偏导数,对图像来说:
一阶导数对图像求导,能突出图像中的对象边缘,且能对x、y方向分别提取边缘,也就是具有方向性;二阶导数对图像的导数求导,对图像中灰度值的剧烈变化敏感,能突出图像的纹理结构,而且不具有方向性。
拉普拉斯算子是一种特别容易受到噪声干扰的边缘发现算子,所以经常对要处理的图像首先进行一个高斯模糊,然后再进行拉普拉斯算子的边缘提取,因此高斯模糊和拉普拉斯算子边缘提取一般在一起,而且在一些场景中会把这两步合并成为一步,就是我们经常听说的LOG算子。
API
OpenCV中Laplacian滤波函数就是二阶导数发现边缘的函数: void cv::Laplacian(
InputArray src,
OutputArray dst,
int ddepth, // 深度默认是-1表示输入与输出图像相同,但是这里一般不使用而是使用CV\_32F。
int ksize = 1,// 必须是奇数, 等于1是四邻域算子,大于1改用八邻域算子 double scale = 1,
double delta = 0, // 对输出图像加上常量值
int borderType = BORDER_DEFAULT )
实践
代码
void QuickDemo::Laplacian\_demo()
{
Mat image = imread("D:/images/yuan\_test.png");
Mat blured, dst;
GaussianBlur(image, blured, Size(3, 3), 0);//3\*3的高斯模糊
//cv::Laplacian(
// InputArray src,
// OutputArray dst,
// int ddepth, // 深度默认是-1表示输入与输出图像相同,但是这里一般不使用而是使用CV\_32F。
// int ksize = 1,// 必须是奇数, 等于1是四邻域算子,大于1改用八邻域算子梯度选择越明显
//double scale = 1,
// double delta = 0, // 对输出图像加上常量值
// int borderType = BORDER\_DEFAULT)
Laplacian(blured, dst, CV_32F, 3, 1.0, 127.0, BORDER_DEFAULT);//拉普拉斯算子的边缘梯度提取
convertScaleAbs(dst, dst);//将结果转换位8位的图像
Mat combine1;
hconcat(image, dst, combine1);//水平拼接函数
imshow("Laplacian\_demo四邻域", combine1);
}
16、图像锐化
图像卷积的主要有三功能分别是图像的模糊/去噪、图像梯度/边缘发现、图像锐化/增强。图像锐化的本质是图像拉普拉斯滤波加原图权重像素叠加的输出,目的是锐化图像,增强图形细节 :
锐化算子
-1 -1 -1
-1 C -1
-1 -1 -1
当C值大于8时候表示图像锐化、越接近8表示锐化效果越好
当C值等于8时候图像的高通滤波 (高对比度的区域)
当C值越大,图像锐化效果在减弱、中心像素的作用在提升(就是中心像素的权重越大,而差异权重加上去作用越小了)
一般常见取9。
还有四邻域那种就是
0 -1 0
-1 5 -1
0 -1 0
实践
代码
void QuickDemo::ruihua\_demo()
{
Mat image = imread("D:/images/yuan\_test.png");
//定义锐化算子 就是强化细节
Mat sharpen_op = (Mat\_<char>(3, 3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
Mat result;
filter2D(image, result, CV_32F, sharpen_op);//自定义卷积核,传入锐化算子
convertScaleAbs(result, result);//结果转换8字节
Mat combine1;
hconcat(image, result, combine1);//水平拼接函数
//在图像上加字符
//第一个参数为要加字符的目标函数
//第二个参数为要加的字符
//第三个参数为字体
//第四个参数为子的粗细
//第五个参数为字符的颜色
cv::putText(combine1, "original image", cv::Point(10,30), cv::FONT_HERSHEY_TRIPLEX, 0.8, cv::Scalar(0, 0, 255), 2);
cv::putText(combine1, "suihua image", cv::Point(image.rows+10, 30), cv::FONT_HERSHEY_TRIPLEX, 0.8, cv::Scalar(0, 0, 255), 2);
imshow("ruihua\_demo四邻域", combine1);
}
17、USM 锐化增强算法
图像卷积处理实现锐化有一种常用的算法叫做Unsharpen Mask方法,这种锐化的方法就是对原图像先做一个高斯模糊,然后用原来的图像减去一个系数乘以高斯模糊之后的图像,然后再把值Scale到0~255的RGB像素值范围之内。
基于USM锐化的方法可以去除一些细小的干扰细节和噪声,比一般直接使用卷积锐化算子得到的图像锐化结果更加真实可信。
USM锐化公式表示如下: (源图像– w*高斯模糊)/(1-w);其中w表示权重(0.1~0.9),默认为0.6
OpenCV中的代码实现步骤
- 高斯模糊
- 权重叠加
- 输出结果
实践
代码
void QuickDemo::USM\_demo()
{
Mat image = imread("D:/images/yuan\_test.png");
Mat blur_img, usm;
GaussianBlur(image, blur_img, Size(0, 0), 25);//高斯模糊
addWeighted(image, 1.5, blur_img, -0.5, 0, usm);//权重增加
Mat combine1;
hconcat(image, usm, combine1);//水平拼接函数
imshow("USM锐化", combine1);
}
18、Canny边缘检测器
Canny边缘检测器是一种经典的图像边缘检测与提取算法,应用广泛,
主要是因为Canny边缘检测具备以下特点: 1. 有效的噪声抑制 2. 更强的完整边缘提取能力
Canny算法是如何做到精准的边缘提取的,主要是靠下面五个步
- 高斯模糊 – 抑制噪声
- 梯度提取得到边缘候选
- 3角度计算与非最大信号抑制
- 高低阈值链接、获取完整边缘
- 输出边缘
API介绍
void cv::Canny
( InputArray image,
OutputArray edges,
double threshold1, //threshold1 是Canny边缘检测算法第四步中高低阈值链接中低阈值
double threshold2, // threshold2 是Canny边缘检测算法第四步中高低阈值链接中高阈值、高低阈值之比在2:1~3:1之间
int apertureSize = 3,
bool L2gradient = false )// 最后一个参数是计算gradient的方法L1或者L2
实践
代码
void QuickDemo::Canny\_demo()
{
Mat src = imread("D:/images/lena.png");
imshow("src image", src);
Mat edges,dst;
/\*
void cv::Canny
( InputArray image,
![img](https://img-blog.csdnimg.cn/img_convert/7f87604c4204a5e315af1b8dbf57a402.png)
![img](https://img-blog.csdnimg.cn/img_convert/4851e8c9171422bf86855207fddf5ec6.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
实践
代码
void QuickDemo::Canny\_demo()
{
Mat src = imread("D:/images/lena.png");
imshow("src image", src);
Mat edges,dst;
/\*
void cv::Canny
( InputArray image,
[外链图片转存中...(img-j0ge8XKW-1715660994201)]
[外链图片转存中...(img-aEiPggxK-1715660994201)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**