若该文为原创文章,未经允许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105237807
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究
目录
前言
笔者对于算法的一点观念
Demo
边缘检测
概述
边缘检测的一般步骤
第一步:滤波(去噪)
第二步:增强(强化边缘特性)
第三步:检测
算子边缘检测
Canny边缘检测算子
概述
原理
第一步:消除噪声
第二步:计算梯度幅值和方向
第三步:非极大值抑制.
第四步:滞后阈值
Canny检测函数原型
Demo源码
工程模板:对应版本号v1.32.0
OpenCV开发专栏
OpenCV开发笔记(三十七):红胖子8分钟带你深入了解边缘检测和Canny算子边缘检测(图文并茂+浅显易懂+程序源码)
前言
红胖子来也!!!
本篇章开始讲解一些边缘检测方面的内容,更贴近图像处理和图像识别。
有些图像优化,其实是可以分层进行处理的(类似于抠图),比如一张签证,上面就分为背景、表格框架、头像、印章,整体处理是一种思路,将其识别封层后再进一步处理,那么更加精准。
笔者对于算法的一点观念
算法是与具体的个人很有关系的,算法本质都是基本操作(类似软件API),但是怎么组合基本操作,怎么根据场景做各种基本操作外的一些额外优化操作,那就是人的思维方式并且没有固定的套路(相对来说软件是依据业务直接可以开发),这部分是需要花费大量的时间去研究算法尝试效果,研究一个算法可能几月几年,达到目标效果的代码也许就几千几百行,也许有几万行吧。
Demo
边缘检测
概述
图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。有许多方法用于边缘检测,它们的绝大部分可以划分为两类:基于查找一类和基于零穿越的一类。基于查找的方法通过寻找图像一阶导数中的最大和最小值来检测边界,通常是将边界定位在梯度最大的方向。基于零穿越的方法通过寻找图像二阶导数零穿越来寻找边界,通常是Laplacian过零点或者非线性差分表示的过零点。
边缘检测的一般步骤
第一步:滤波(去噪)
根据图像的特点做滤波,具体的滤波需要结合实际情况,使用滤波算法。
滤波算法前面讲解到的有:方框滤波、均值滤波、高斯滤波、中值滤波、双边滤波,针对不同状态下的去噪滤波,如高频、低频、一定频率内等等。
特别注意,芝麻点的斑点这些不算噪声,此处噪声通俗的讲为高频、低频、一定范围内的噪声,是指变化的梯度,就是变化的速度,斑点内部是一样的,那么梯度为0,既不是低频、高频,就是正常的图像,所以滤不掉的,强制加大是可以滤掉,但是直接导致需要保留的部分也一起被滤掉了。
第二步:增强(强化边缘特性)
强化边缘特性就是增强边缘,让边缘更加突出,使用增强算法将梯度值(变化度)大的部分凸显出来,边缘一般是背景与前景交界的地方,那么其梯度值(变化度)一般情况下都大(特殊的情况是:背景有些地方融合或者颜色相似的情况)。
由上可知,用梯度值相关算法去实现即可,梯度相关算法前面讲解到的有:膨胀、腐蚀、开运算、闭运算、形态学梯度、顶帽(礼帽)、黑帽。
第三步:检测
增强后的图像,会发现其实还是有很多点存在的梯度值比较大啊,而在应用中,这些点并不是边缘点,所以此时会再进行一次操作进行过滤。常用的方法就是阈值化。
阈值化相关的算法,前面讲解到的有:阈值化、自适应阈值、经典OTSU算法阈值化、双阈值化、半阈值化,延伸的算法还有漫水填充。
算子边缘检测
算子边缘检测就是基于算子的边缘检测,是已经封装好滤波、增强、检测三个步骤的方法,可以直接使用,也可以自己先进行一些滤波、增强、去噪后再继续使用边缘检测算子,那么其效果会更好。
Canny边缘检测算子
概述
Canny边缘检测算子是John F.Canny与1986年开发出来的一个多级边缘检测算法。
Canny的目标是找到一个最优的边缘检测算法:
低错误率:标识出尽可能多的实际边缘,同时尽可能减少噪声产生的误报;
高定位性:标识出的边缘要与图像中的实际边缘尽可能接近;
最小响应:图像中的边缘只能标识一次,并且可能存在的图像噪声不应表示为边缘。
Canny 算法包含许多可以调整的参数,它们将影响到算法的计算的时间与实效。
高斯滤波器的大小:第一步所有的平滑滤波器将会直接影响 Canny 算法的结果。较小的滤波器产生的模糊效果也较少,这样就可以检测较小、变化明显的细线。较大的滤波器产生的模糊效果也较多,将较大的一块图像区域涂成一个 特定点的颜色值。这样带来的结果就是对于检测较大、平滑的边缘更加有用,例如彩虹的边缘。
双阈值:使用两个阈值比使用一个阈值更加灵活,但是它还是有阈值存在的共性问题。设置的阈值过高,可能会漏掉重要信息;阈值过低,将会把枝节信息看得很重要。很难给出一个适用于所有图像的通用阈值。
原理
Canny边缘检测分为四步:
第一步:消除噪声
使用高斯滤波,使用size=5的高斯滤波进行滤波:
第二步:计算梯度幅值和方向
按照Sobel滤波器的步骤来操作。
先运用一堆卷积阵列分别作用于x和y方向:
使用公式计算梯度幅值和方向:
而梯度幅值一般取4个可能角度之一:0°、45°、90°、135°。
第三步:非极大值抑制.
进一步排除非边缘像素,仅仅保留一些细线条,都作为候选边缘。
第四步:滞后阈值
滞后阈值需要两个阈值:高阈值和地狱之;
若某一像素位置的幅值超过高阈值,该像素被保留为边缘像素;
若某一像素位置的幅值小于高阈值,该像素被排除;
若某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素是被保留;
Canny检测函数原型
void Canny( InputArray image,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize = 3,
bool L2gradient = false );
参数一:InputArray类型的image,一般是cv::Mat,类型为必须为单通道;
参数二:OutputArray类型的edges;边缘输出边缘图;单通道8位图像,与输入的图像大小相同;
参数三:double类型的threshold1,滞后过程的第一阈值;
参数四:double类型的threshold2,滞后过程的第二阈值;
参数五:int类型的额apertureSize,Sobel运算符的孔径大小,默认为3;
参数六:bool类型的L2gradient,计算梯度幅值的操作,默认为false;
void Canny( InputArray dx,
InputArray dy,
OutputArray edges,
double threshold1,
double threshold2,
bool L2gradient = false );
参数一:InputArray类型的src,一般是cv::Mat,类型为必须为CV_16SC1 或者CV_16SC3;
参数二:OutputArray类型的dst;与src大小和类型一样;
参数三:边缘输出边缘图;单通道8位图像,与图像大小相同;
参数四:double类型的threshold1,滞后过程的第一阈值;(注意:threshold1与threshold2的比值最好在2:1~3:1之间)
参数五:double类型的threshold2,滞后过程的第二阈值;(注意:threshold1与threshold2的比值最好在2:1~3:1之间)
参数六:bool类型的L2gradient,计算梯度幅值的操作,默认为false。
Demo源码
void OpenCVManager::testCanny()
{
QString fileName1 = "E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/11.jpg";
cv::Mat srcMat = cv::imread(fileName1.toStdString());
int width = 400;
int height = 300;
cv::resize(srcMat, srcMat, cv::Size(width, height));
cv::String windowName = _windowTitle.toStdString();
cvui::init(windowName);
cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 2),
srcMat.type());
cv::cvtColor(srcMat, srcMat, CV_BGR2GRAY);
int threshold1 = 200;
int threshold2 = 100;
int apertureSize = 1;
while(true)
{
windowMat = cv::Scalar(0, 0, 0);
cv::Mat mat;
cv::Mat dstMat;
cv::Mat grayMat;
// 转换为灰度图像
// 原图先copy到左边
cv::Mat leftMat = windowMat(cv::Range(0, srcMat.rows),
cv::Range(0, srcMat.cols));
cv::cvtColor(srcMat, grayMat, CV_GRAY2BGR);
cv::addWeighted(leftMat, 0.0f, grayMat, 1.0f, 0.0f, leftMat);
{
cvui::printf(windowMat,
srcMat.rows * 1 + 100,
srcMat.cols * 0 + 20,
"threshold1");
cvui::trackbar(windowMat,
srcMat.rows * 1 + 100,
srcMat.cols * 0 + 50,
200,
&threshold1,
0,
255);
cvui::printf(windowMat,
srcMat.rows * 1 + 100,
srcMat.cols * 0 + 100, "threshold2");
cvui::trackbar(windowMat,
srcMat.rows * 1 + 100,
srcMat.cols * 0 + 130,
200,
&threshold2,
0,
255);
// 不能调整Sobel孔径,否则宕机
// cvui::printf(windowMat,
// srcMat.rows * 1 + 100,
// srcMat.cols * 0 + 180, "apertureSize * 2 + 1");
// cvui::trackbar(windowMat,
// srcMat.rows * 1 + 100,
// srcMat.cols * 0 + 210,
// 200,
// &apertureSize,
// 0,
// 10);
// 使用边缘检测
cv::Canny(srcMat, dstMat, threshold1, threshold2, apertureSize * 2 + 1);
// copy
mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::cvtColor(dstMat, grayMat, CV_GRAY2BGR);
cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);
}
// 更新
cvui::update();
// 显示
cv::imshow(windowName, windowMat);
// esc键退出
if(cv::waitKey(25) == 27)
{
break;
}
}
}
工程模板:对应版本号v1.32.0
对应版本号v1.32.0
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105237807