Canny边缘检测是Canny在1986年提出来的,目前仍是图像边缘检测算法中最经典、先进的算法之一。canny方法基于如下三个基本目标:

1. 低错误率:所有边缘都应被找到,并且不应有虚假响应。

2. 最优定位:已定位的边缘必须尽可能接近真实边缘 。也就是说,由检测子标记为边缘的一点和真实边缘的中心之间的距离应最小。

3. 单个边缘点响应:对于每个真实的边缘点,检测子应只返回一个点。也就是说,真实边缘周围的局部最大数应该是最小的。这意味着检测子不应识别只存在单个边缘点的多个边缘像素。

canny的工作本质是,从数学上表达了前三个准则,并试图找到这些公式的最优解。

上篇博客介绍了边缘模型以及基本的边缘检测算法,接下来将了解Canny算法的步骤:

1. 使用高斯函数滤波器平滑输入图像

令f(x,y)表示输入图像,G(x,y)表示高斯函数:

我们用G和f卷积后得到平滑后的图像fs(x,y):

Mat gauImg;
GaussianBlur(src, gauImg, Size(13, 13), 2);

2. 计算梯度幅度和角度图像

计算梯度:

分别计算梯度幅度M的方向α:

代码实现:

Mat grad_x, grad_y, grad_xy;
Sobel(gauImg, grad_x, CV_32F, 1, 0, 3);
Sobel(gauImg, grad_y, CV_32F, 0, 1, 3);
Mat directImg, gradXY;
divide(grad_y, grad_x, directImg);
Mat gradX, gradY;
convertScaleAbs(grad_x, gradX);
convertScaleAbs(grad_y, gradY);
gradXY = gradX + gradY;

3. 非极大值抑制

利用非极大值抑制,细化梯度图像在局部极大值附近包含的一些宽脊。这种方法的实质是规定法线(梯度向量)的多个离散方向。例如,在一个3×3区域内,对于一个过该区域中心点的边缘,我们可以定义4个方向:水平垂直+45度-45度,分别为d1,d2,d3,d4。对于任意点(x,y)为中心的3×3区域,我们将非极大值抑制方案表述如下:

1. 寻找最接近α(x,y)的方向dk。

2. 令K表示梯度幅值在(x,y)处的值。若K小于dk方向上的点(x,y)的一个或两个邻点处的幅度值,令g(x,y)=0(抑制);否则,令g(x,y)=K。

对x和y的所有值重复这一过程,产生一幅与梯度幅值图像大小相同的非极大值抑制图像g(x,y)。例如下图的边缘点,方向均是垂直方向,感兴趣的点为p2和p8,如果p5大于等于这两点,则保留该点,不然令该点为0(抑制)。

 代码实现:将梯度法线分为4个方向,根据4个方向找相邻感兴趣的两个点的梯度幅值

void NMS(Mat& grad_x, Mat& grad_y,Mat& gradXY, Mat& directImg, Mat& dst) {
dst = gradXY.clone();
for (int i = 1; i < grad_x.rows - 1; i++) {
for (int j = 1; j < grad_x.cols - 1; j++) {
directImg.at<float>(i, j) = atan(directImg.at<float>(i, j));
int P1 = (int)gradXY.at<uchar>(i - 1, j - 1);
int P2 = (int)gradXY.at<uchar>(i - 1, j);
int P3 = (int)gradXY.at<uchar>(i - 1, j + 1);
int P4 = (int)gradXY.at<uchar>(i, j - 1);
int P5 = (int)gradXY.at<uchar>(i, j);
int P6 = (int)gradXY.at<uchar>(i, j + 1);
int P7 = (int)gradXY.at<uchar>(i + 1, j - 1);
int P8 = (int)gradXY.at<uchar>(i + 1, j);
int P9 = (int)gradXY.at<uchar>(i + 1, j + 1);
// 边缘法线水平 —
if (directImg.at<float>(i, j) <= CV_PI / 8 && directImg.at<float>(i, j) >= -CV_PI / 8)
if (P5 < P4 || P5 < P6)
dst.at<uchar>(i, j) = 0;
// 边缘法线45° \
if (directImg.at<float>(i, j) > CV_PI / 8 && directImg.at<float>(i, j) < CV_PI / 8 * 3)
if (P5 < P1 || P5 < P9)
dst.at<uchar>(i, j) = 0;
// 边缘法线垂直 |
if ((directImg.at<float>(i, j) >= CV_PI / 8 * 3 && directImg.at<float>(i, j) <= CV_PI / 2) ||
(directImg.at<float>(i, j) <= -CV_PI / 8 * 3 && directImg.at<float>(i, j) >= -CV_PI / 2))
if (P5 < P2 || P5 < P8)
dst.at<uchar>(i, j) = 0;
// 边缘法线-45° /
if (directImg.at<float>(i, j) < -CV_PI / 8 && directImg.at<float>(i, j) > -CV_PI / 8 * 3)
if (P5 < P3 || P5 < P7)
dst.at<uchar>(i, j) = 0;
}
}
}

通常为了更加精确的计算,在跨梯度方向的两个相邻像素之间使用线性插值来得到要比较的像素梯度。在讨论时分为下面4种情况:

代码实现:线性插值计算感兴趣两点的梯度幅值

void NMS(Mat& grad_x, Mat& grad_y, Mat& gradXY, Mat& dst) {
dst = gradXY.clone();
for (int i = 1; i < grad_x.rows - 1; i++) {
for (int j = 1; j < grad_x.cols - 1; j++) {
float gx = grad_x.at<float>(i, j);
float gy = grad_y.at<float>(i, j);
int g1, g2, g3, g4; float w;
if (abs(gy) > abs(gx)){
g2 = gradXY.at<uchar>(i - 1, j);
g3 = gradXY.at<uchar>(i + 1, j);
w = abs(gx / gy);
if (gx * gy > 0) {
g1= gradXY.at<uchar>(i - 1, j - 1);
g4 = gradXY.at<uchar>(i + 1, j + 1);
}
else {
g1 = gradXY.at<uchar>(i - 1, j + 1);
g4 = gradXY.at<uchar>(i + 1, j - 1);
}
}
else {
g2 = gradXY.at<uchar>(i, j - 1);
g3 = gradXY.at<uchar>(i, j + 1);
w = abs(gy / gx);
if (gx * gy > 0) {
g1 = gradXY.at<uchar>(i - 1, j - 1);
g4 = gradXY.at<uchar>(i + 1, j + 1);
}
else {
g1 = gradXY.at<uchar>(i + 1, j - 1);
g4 = gradXY.at<uchar>(i - 1, j + 1);
}
} float grad1 = g1 * w + g2 * (1 - w);
float grad2 = g4 * w + g3 * (1 - w);
if (gradXY.at<uchar>(i, j) < grad1 || gradXY.at<uchar>(i, j) < grad2)
dst.at<uchar>(i, j) = 0;
}
}
}

4. 使用双阈值处理和连通性分析来检测和连接边缘

双阈值处理是指设置两个阀值,分别为TL和TH。其中大于TH的都被检测为边缘,而低于TL的都被检测为非边缘。对于中间的像素点,如果与确定为边缘的像素点邻接,则判定为边缘;否则为非边缘。具体地说,高阈值得到的图像gH为强边缘,低阈值得到图像gL的弱边缘。强边缘均被认为是有效边缘,并被立即标记。较长的边缘容易被高阈值切断,可利用下面的步骤连接较长的边缘

(a)在gH中定位下一个未被访问的边缘像素p;

(b)将gL(x,y)中中用8连通连接到p的所有若像素标记为有效边缘像素

(c)如果gH中的所有非零像素已被访问,则跳到步骤(d),否则返回步骤(a)

(d)将gL中未标记为有效边缘像素的所有像素设置为零

在这一过程的末尾,将来自gL(x,y)的所有非零像素附加到gH(x,y),形成canny算子输出的最终图像。

代码实现:利用了膨胀重建完成上面的过程

以gH为标记图,gL为模板,进行膨胀重建,若想了解形态学重建,移步链接

//膨胀重建
void dilateRestruct(Mat& mark, Mat& mould, Size dilate_size, Mat& dst) {
Mat dilateImg_pre = mark.clone();
Mat temp = mark.clone();
Mat cmp = Mat::zeros(mould.size(), CV_8UC1);
Mat element = getStructuringElement(MORPH_RECT, dilate_size);
int n = -1;
while (n != mould.cols * mould.rows) {
morphologyEx(dilateImg_pre, temp, MORPH_DILATE, element);
bitwise_and(temp, mould, temp);
compare(temp, dilateImg_pre, cmp, 0);
n = countNonZero(cmp);
dilateImg_pre = temp.clone();
}
dst = temp;
}
//双阈值
void double_threshold(Mat& src, int T_low, int T_high, Mat& dst) {
Mat g_h, g_l, g;
threshold(src, g_h, 65, 255, THRESH_BINARY);
threshold(src, g_l, 25, 255, THRESH_BINARY);
dilateRestruct(g_h, g_l, Size(3, 3), dst);
}

尽管非极大值抑制后的边缘要比原始梯度边缘细,但仍要粗于1像素。为得到1像素粗的边缘,通常要在步骤4后执行一次边缘细化算法(细化见链接)。

完整代码见链接

参考:

1. 冈萨雷斯《数字图像处理(第四版)》Chapter 10(所有图片可在链接中下载)

2.canny算子_Canny检测算法与实现

3. Canny边缘检测算法的实现

Canny边缘检测实现(Opencv C++)的更多相关文章

  1. OpenCV图像Canny边缘检测

    Canny边缘检测 图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘函数原型:     void cvCanny(       ...

  2. OpenCV: Canny边缘检测算法原理及其VC实现详解(转载)

    原文地址:http://blog.csdn.net/likezhaobin/article/details/6892176 原文地址:http://blog.csdn.net/likezhaobin/ ...

  3. openCV(四)---Canny边缘检测

    图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘. 直接上代码,函数简介都在代码注释中 //canny边缘检测 -(void) ...

  4. 基于opencv下对视频的灰度变换,高斯滤波,canny边缘检测处理,同窗体显示并保存

    如题:使用opencv打开摄像头或视频文件,实时显示原始视频,将视频每一帧依次做灰度转换.高斯滤波.canny边缘检测处理(原始视频和这3个中间步骤处理结果分别在一个窗口显示),最后将边缘检测结果保存 ...

  5. Python+OpenCV图像处理(十三)—— Canny边缘检测

    简介: 1.Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法. 2.Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是: 好的检测- ...

  6. OpenCV——边缘检测入门、Canny边缘检测

    边缘检测的一般步骤: 最优边缘检测的三个评价标准: 低错误率:表示出尽可能多的实际边缘,同时尽可能地减少噪声产生的误报: 高定位性:标识出的边缘要与图像实际边缘尽可能接近: 最小响应:图像中的边缘只能 ...

  7. OpenCV学习代码记录——canny边缘检测

    很久之前学习过一段时间的OpenCV,当时没有做什么笔记,但是代码都还在,这里把它贴出来做个记录. 代码放在码云上,地址在这里https://gitee.com/solym/OpenCVTest/tr ...

  8. openCV实例:Canny边缘检测

    http://blog.sina.com.cn/s/blog_737adf530100z0jk.html 在第一次使用openCV程序成功对图像进行打开后,现在开始试验第二个例程试验:Canny边缘检 ...

  9. OpenCV学习笔记(11)——Canny边缘检测

    了解Canny边缘检测的概念 1.原理 Canny边缘检测是一种非常流行的边缘检测算法,是 John F.Canny在1986年提出的.它是一个有很多步构成的算法 1)噪声去除 使用5*5的高斯滤波器 ...

  10. Canny边缘检测算法(基于OpenCV的Java实现)

    目录 Canny边缘检测算法(基于OpenCV的Java实现) 绪论 Canny边缘检测算法的发展历史 Canny边缘检测算法的处理流程 用高斯滤波器平滑图像 彩色RGB图像转换为灰度图像 一维,二维 ...

随机推荐

  1. 重新点亮linux 命令树————二进制安装[十一八]

    前言 简单介绍一下二进制安装 正文 wget https://openresty.org/download/openresty-1.15.8.1.tar.gz tar -zxf openresty-V ...

  2. 获取电脑真实的IP地址,忽略虚拟机等IP地址的干扰

    /** * @author yins * @date 2018年8月12日下午9:53:58 */ import java.net.Inet4Address; import java.net.Inet ...

  3. ASP.NET CORE 框架揭秘读书笔记系列——命令行程序的创建(一)

    一.dotnet --info 查看本机开发环境 dotnet --info  会显示本机安装的SDK版本.运行时环境.运行时版本 二.利用命令行创建.NET项目 我们不仅可以利用脚手架模版创建各种类 ...

  4. 技术干货丨云企业网CEN2.技术揭秘

    ​简介:随着企业数字化转型的加速,越来越多的企业选择了将业务部署在云上,这其中有超过20%的企业有全球组网的需求,这就使得云上网络的规模越来越大,复杂度也越来越高,为了应对这些变化,阿里云推出了升级版 ...

  5. 5G 和云原生时代的技术下半场,视频化是最大最新的确定性

    ------------恢复内容开始------------ null ------------恢复内容结束------------

  6. 开源微服务编排框架:Netflix Conductor

    简介:本文主要介绍netflix conductor的基本概念和主要运行机制. ​ 作者 | 夜阳 来源 | 阿里技术公众号 本文主要介绍netflix conductor的基本概念和主要运行机制. ...

  7. 前沿分享|阿里云数据库资深技术专家 姚奕玮:AnalyticDB MySQL离在线一体化技术揭秘

    ​简介: 本篇内容为2021云栖大会-云原生数据仓库AnalyticDB技术与实践峰会分论坛中,阿里云数据库资深技术专家 姚奕玮关于"AnalyticDB MySQL离在线一体化技术揭秘&q ...

  8. 图像检索在高德地图POI数据生产中的应用

    ​简介: 高德通过自有海量的图像源,来保证现实世界的每一个新增的POI及时制作成数据.在较短时间间隔内(小于月度),同一个地方的POI 的变化量是很低的. ​ 作者 | 灵笼.怀迩 来源 | 阿里技术 ...

  9. C语言程序设计-笔记04-函数

    C语言程序设计-笔记04-函数 例5-1  计算圆柱体的体积.输入圆柱的高和半径,求圆柱体积volume=πxr^2xh.要求定义和调用函数cylinder(r,h)计算圆柱体的体积. #includ ...

  10. 【AI新趋势期刊#2】AI发明计算机算法,如何给大模型排行,照片秒变二维码,视频一键动漫风

    前言 每天都要浏览大量AI相关新闻,是不是感到信息量爆炸,有效信息少? 这么多新产品和新工具,到底哪些是真正是有价值的,哪些只是浮躁的一时热点? 想参与AI产品和工具的开发,从哪里能够获得大量的灵感和 ...