目录
一、前言
二、边缘检测
1、啥是边缘检测
2、边缘检测算子
三、Canny算子
1、讲解
1.图像平滑
2.寻找图像强度梯度
3.消除边误检
4.双阈值求可能边
5.边界跟踪
2、API
3、代码展示
4、执行结果
一、前言
继续填坑。
如果想看其他有关于OpenCV学习方法介绍、学习教程、代码实战、常见报错及解决方案等相关内容,可以直接看我的OpenCV分类
二、边缘检测
1、啥是边缘检测
我们先来简单理解一下:
原图
对于这张图,如果我们想把人物抠出来,应该怎么办?我们用抠图软件弄一下,就会得到下面的图像:
抠图
我们抠图的过程其实就是找图像中某个实例(例如人)的边缘的过程,所以,我们的边缘检测,其实就是检测图像中的实例的边缘。
那现在就有一个问题了,我们人是怎么区分边缘的呢?
我们发现,每一个实例,它的边缘跟其周围的像素差距一般是比较大的。我们的抠图,就是根据明显的像素差距,来区分实例的边缘。
所以抠图的根据,就是图像像素的明显变化。图像的边缘,一般都是图像上像素有明显变化的位置。
那我们用一个图来表示图像的像素:
图像像素
我们发现,图像像素明显变化的位置,图像的倾斜程度都很大,也就是说斜率的绝对值很大。这和我们之前讲的几个算子的原理是一致的,Sobel算子,就是计算的这个位置:
有没有觉得这个故事很感人呢???
了解了这些之后,现在我们来说一下边缘检测比较正规的定义吧!
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。
2、边缘检测算子
上面讲到,Sobel 算子可以用于边缘检测,那除了Sobel算子,还有那些算子能用于边缘检测呢?
这个问题我想大家应该比较容易回答了,我们之前学的三个算子都能用于边缘检测:
Sobel算子、Scharr算子、Laplance算子
除了这三个,我们今天再给大家讲一个算子,Canny算子。
三、Canny算子
1、讲解
接下来,我们来说一个新的算子Canny算子。
Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法。它包括如下五个步骤:
1.应用高斯滤波来平滑图像,目的是去除噪声
2.找寻图像的强度梯度(intensity gradients)
3.应用非最大抑制(non-maximum suppression)技术来消除边误检(本来不是但检测出来是)
4.应用双阈值的方法来决定可能的(潜在的)边界
5.利用滞后技术来跟踪边界
接下来,我们来详细讲解一下每一个步骤:
1.图像平滑
图像平滑就是让图像之间的像素差距更小一些,能够更好地提取特征更加明显的边缘,所以图像平滑的目的就是去除部分噪声。
而图像平滑操作,我们思考一下,不就是让挨着的像素之间差距变小,图像模糊的作用不就是这样吗?所以图像平滑操作就是进行图像模糊操作,而我们最常用的模糊操作,是高斯模糊。
对没错就是我!
世上最帅的男人,没有之一!
科普一下下,高斯模糊不是高斯提出来的!
高斯模糊用的kernel是正态分布,正态分布也叫高斯分布。这是高斯模糊这个名字的由来。不是因为高斯提出的。甚至,高斯分布也不是高斯提出的,只是用他命名。高斯分布的提出者是棣莫佛和拉普拉斯,那个定理叫做棣莫佛-拉普拉斯定理。
但是这丝毫不会影响我们数学人对高斯的推崇。也不会影响我们数学人对任何一个在数学史上贡献自己一生的人致以最崇高的敬意。
言归正传,下面这个示例是一个5×5的kernel。
2.寻找图像强度梯度
Canny算法的基本思想是寻找一幅图像中灰度强度变化最强的位置。所谓变化最强,即指梯度方向。平滑后的图像中每个像素点的梯度可以由Sobel算子(一种卷积运算)来获得(opencv中有封装好的函数,可以求图像中每个像素点的n阶导数)。首先,利用如下的核来分别求得沿水平(x)和垂直(y)方向的梯度G_X和G_Y。
3.消除边误检
这一步的目的是将模糊的边界变得清晰(sharp)。通俗的讲,就是保留了每个像素点上梯度强度的极大值,而删掉其他的值。对于每个像素点,进行如下操作:
a) 将其梯度方向近似为以下值中的一个:
(0,45,90,135,180,225,270,315)(即上下左右和45度方向)
b) 比较该像素点,和其梯度方向正负方向的像素点的梯度强度
c) 如果该像素点梯度强度最大则保留,否则抑制(删除,即置为0)
为了更好的解释这个概念,看下图:
图中的数字代表了像素点的梯度强度,箭头方向代表了梯度方向。以第二排第三个像素点为例,由于梯度方向向上,则将这一点的强度(7)与其上下两个像素点的强度(5和4)比较,由于这一点强度最大,则保留。
4.双阈值求可能边
经过非极大抑制后图像中仍然有很多噪声点。Canny算法中应用了一种叫双阈值的技术。即设定一个阈值上界和阈值下界(opencv中通常由人为指定的),图像中的像素点如果大于阈值上界则认为必然是边界(称为强边界,strong edge),小于阈值下界则认为必然不是边界,两者之间的则认为是候选项(称为弱边界,weak edge),需进行进一步处理。
5.边界跟踪
较高的亮度梯度比较有可能是边缘,但是没有一个确切的值来限定多大的亮度梯度是边缘多大又不是,所以 Canny 使用了滞后阈值。
滞后阈值(Hysteresis thresholding)需要两个阈值,即高阈值与低阈值。假设图像中的重要边缘都是连续的曲线,这样我们就可以跟踪给定曲线中模糊的部分,并且避免将没有组成曲线 的噪声像素当成边缘。所以我们从一个较大的阈值开始,这将标识出我们比较确信的真实边缘,使用前面导出的方向信息,我们从这些真正的边缘开始在图像中跟踪 整个的边缘。在跟踪的时候,我们使用一个较小的阈值,这样就可以跟踪曲线的模糊部分直到我们回到起点。
2、API
接下来我们讲一下API
void Canny(
InputArray src,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize = 3,
bool L2gradient = false
);
函数参数含义如下:
(1)InputArray类型的src ,输入图像。
(2)OutputArray类型的edges,输出边缘图;单通道8位图像,其大小与图像相同。
(3)double类型的threshold1,滞后过程的第一个阈值。
(4)double类型的threshold2,滞后过程的第二个阈值。
(5)int类型的apertureSize,Sobel运算符的孔径大小。
(6)bool类型的L2gradient,一个标志,指示是否应使用更精确的L2来计算图像渐变幅度(L2gradient=true),或者是否应使用默认的L1(L2gradient=false)。(注:这里的L2是平方和开根号,L1是绝对值求和,如下图)
3、代码展示
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat img, src;
img = imread("E:/image/girl2.png");
if (!img.data)
{
cout << "could not load image !";
return -1;
}
imshow("【输入图像】", img);
GaussianBlur(img, img, Size(5, 5), 0, 0);
Canny(img, src, 115, 255);
imshow("【输出图像】", src);
waitKey(0);
return 0;
}
4、执行结果
原图
Canny算子
如果我们不使用模糊操作,得到的结果如下:
不使用模糊操作