OpenCV3学习笔记——边缘检测之Canny算法解析

边缘检测之Canny算法解析

Aim at the stars in the sky, you may never shoot, but you shoot farther than you aim at the tree tops.
——瞄准天上的星星,或许你永远也射不到,但却比你瞄准树梢射得高远。

Canny算子

边缘检测出现在了图像变换的许多地方,前段时间我在学习这一部分的知识时,看着这些算法的源码,品味它们的思想,真的是一边学一边感叹到——太牛逼了!所以打算写一篇博文来记录这段时间的学习成果

预备知识:

1. 边缘检测的一般步骤:

  1. 滤波:因为边缘检测主要是基于图像强度的一阶导和二阶导(具体导数的意义咱们一会探讨一下),然而导数对噪声非常敏感,因此滤波环节就显得尤为重要了

  2. 增强:这一步可以将图像灰度点邻域强度有着明显变化的点凸显出来(灰度值的跃升一般情况下表示边缘的存在)我们可以通过计算梯度的幅值来确定
    来看看这个比较易于理解的图片:
    在这里插入图片描述
    此处f(t)代表灰度值,被我们圈出来的地方变化率最大,因而它可能是边缘,那么我们如何确定它?
    答案是:求导
    在这里插入图片描述
    使用一阶微分求导,我们可以更加清晰的看到边缘跃升的存在

  3. 检测:很多情况下,我们可能找到了一些假的边缘点(因为在增强操作之后,有的地方的灰度值可能很大,但他们也许并非我们需要的边缘),那么这一步中我们需要有一些方法对它们进行一些取舍

2. 关于图像的梯度的理解

图像,我们可以把它看成二元函数嘛,梯度,在图像处理里面也就是对图像求导
我们先看看偏导数的定义式(以对x的偏导为例)
在这里插入图片描述
因为图像是一个离散的二维函数,h不能无限小,我们的图像是按照像素来离散的,最小的h就是1像素,那么,对图像x方向的偏导数(梯度)就变成了这样:
f(x + 1, y0) - f(x, y0)
诶,这不就相当于2个相邻像素之间的差值嘛?
是的,而且它还有另一个名字:这叫做向前差商,只不过这里h=1罢了
而在某点的梯度可以用向前差商、向后差商或者中心差商获得
我们这里采用中心差商来计算图像某一点的梯度:(在下一篇Sobel算子的解析时我们还会看到这个式子)
在这里插入图片描述
是的,因此边缘部分的像素值和其邻域部分的像素值的差(梯度)是比较大的,我们就利用这一点来找到边缘
梯度的幅值是这样表示的:
在这里插入图片描述
梯度的方向是这样表示的:
在这里插入图片描述
梯度的方向是函数f(x,y)变化最快的方向,当图像中存在边缘时,一定有较大的梯度值,相反,当图像中有比较平滑的部分时,灰度值变化较小,则相应的梯度也较小,我们在增强和检测这两步需要用到这方面的知识

好,热身完啦,我们开始吧!!

一,Canny算子

1.1 Canny边缘检测算法的操作步骤:

  1. 使用高斯滤波器,以平滑图像,滤除噪声
  2. 计算图像中每个像素点的梯度强度和方向
  3. 应用非极大值抑制,以消除边缘检测带来的杂散响应
  4. 应用双阈值(就是我们调用Canny()时填入的两个阈值)检测来确定真实的和潜在的边缘
  5. 通过抑制孤立的弱边缘最终完成边缘检测

关于第一步呢,在之前的博文中我们有详细地探讨过GaussianBlur的实现原理,这里就不再赘述

第二步:计算图像中每个像素点的梯度强度和方向
这一步我们是使用的Sobel滤波器的步骤来操作的
先来看看x和y方向的Sobel算子:
在这里插入图片描述
然后我们就是用这两个算子对图像进行卷积,(Sx用于检测y方向的边缘,Sy用于检测x方向的边缘,因为梯度与边缘方向垂直)

然后求出Gx和Gy之后用之前讲过的公式计算梯度的幅值和方向
在这里插入图片描述
第三步:非极大值抑制
图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,也就是找真正的极大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点

在这里插入图片描述
我们看到,红色线的方向代表了梯度方向,因此,极大值就分布在了这条线上,但是是不是P点呢?我们这时就需要把P点的灰度强度和P1和P2比较,如果P的灰度强度大于P1和P2,那么P就是真正的边缘,反之,P就不是

第四步: 双阈值检测(滞后性阈值)
在非极大值抑制之后,剩余的像素可以更准确地表示图像中的实际边缘。然而,仍然存在由于噪声和颜色变化引起的一些边缘像素。为了解决这些杂散响应,必须用弱梯度值过滤边缘像素,并保留具有高梯度值的边缘像素,可以通过选择高低阈值来实现。

  1. 若某一像素位置的幅值高于高阈值,那么它将被视为边缘而被保留下来
  2. 若某一像素位置的幅值低于低阈值,那么该像素就被排除
  3. 若某一像素位置的幅值介于高阈值和低阈值之间,那么只有当该像素连接一个高于高阈值像素的时候被保留

说明:对于高低阈值的选择,推荐的高低阈值比在2:1到3:1之间

第五步:抑制孤立低阈值点

二,Canny()函数的使用

先来看看Canny()函数的原型

void Canny( InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false);
  1. 第一个参数:输入图像
  2. 第二个参数:输出图像,需要和原图像有一样的尺寸和类型
  3. 第三个参数:double类型的 threshold1,第一个滞后性阈值
  4. 第四个参数:double 类型的threshold2,第二个滞后性阈值
  5. 第五个参数:表示应用Sobel算子的孔径大小,有默认值3
  6. 第六个参数:一般有默认值false

下面我们就来看看具体是如何使用Canny()函数的吧!

情景:现在给了你一张轿车的图片,需要你把它的边缘找出来

#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;

int main()
{
    Mat scrimage, gray_scrimage,  gray_scrimage1,dstimage;
    scrimage = imread("timg.jpg", 1);
    imshow("【原始图】", scrimage);    
    cvtColor(scrimage, gray_scrimage, COLOR_RGB2GRAY);     //颜色空间转换,将原图转化为灰度图
    GaussianBlur(gray_scrimage, gray_scrimage1, Size(3, 3), 0, 0);  //高斯滤波处理
    Canny(gray_scrimage1, dstimage, 10, 30, 3);
    imshow("【边缘图】", dstimage);
    waitKey(0);
}

我们来看看效果图,不得不说,用上面选择的高低阈值,实在是太细致了,边缘图把我看的起了一身鸡皮疙瘩
在这里插入图片描述

在这里插入图片描述
当我们重新调整了高低阈值:

Canny(gray_scrimage1, dstimage, 80, 240, 3);

按照新的高低阈值,得到的边缘图像就会像这样:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值