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. Flutter笔记-基础组件

    图片和Icon 加载网络图片以及本地图片 Image( image: NetworkImage( "https://img-s-msn-com.akamaized.net/tenant/am ...

  2. Django框架——ORM执行SQL语句、神奇的双下划线、外键字段的创建、跨表查询、进阶操作

    ORM执行SQL语句 有时候ORM的操作效率可能偏低 我们是可以自己编写SQL的 方式一: models.User.objects.raw('select * from app01_user') 方式 ...

  3. 深度解读 OpenYurt:从边缘自治看 YurtHub 的扩展能力

    作者 | 新胜  阿里云技术专家 导读:OpenYurt 开源两周以来,以非侵入式的架构设计融合云原生和边缘计算两大领域,引起了不少行业内同学的关注.阿里云推出开源项目 OpenYurt,一方面是把阿 ...

  4. 基于 Serverless 架构的头像漫画风处理小程序

    ​简介: 当一个程序员想要个漫画风的头像时... 前言 我一直都想要有一个漫画版的头像,奈何手太笨,用了很多软件 "捏不出来",所以就在想着,是否可以基于 AI 实现这样一个功能, ...

  5. 基于Confluent+Flink的实时数据分析最佳实践

    简介:在实际业务使用中,需要经常实时做一些数据分析,包括实时PV和UV展示,实时销售数据,实时店铺UV以及实时推荐系统等,基于此类需求,Confluent+实时计算Flink版是一个高效的方案. 业务 ...

  6. 达摩院重要科技突破!空天数据库引擎Ganos解读

    简介: Ganos空天数据库引擎是李飞飞带领的达摩院数据库与存储实验室研发的新一代位置智能引擎,采用了平台即服务.多模融合.计算下推和云原生全新处理架构,为政府.企事业单位.泛互联网客户提供移动对象. ...

  7. WPF 触摸下如何给 StylusPointCollection 添加点

    本文告诉大家如何在触摸下给 WPF 的 StylusPointCollection 添加新的点 在自己默认创建的 StylusPointCollection 里面添加点是十分简单的,如以下代码,可以非 ...

  8. WPF 框架开发 ColumnDefinition 和 RowDefinition 的代码在哪

    我的 VisualStudio 在更新到 2022 就构建不通过 WPF 仓库,提示我在 Grid 的代码里面找不到 ColumnDefinitionCollection 和 RowDefinitio ...

  9. 2019-4-29-Roslyn-将这个文件放在你的项目文件夹,无论哪个控制台项目都会输出林德熙是逗比...

    title author date CreateTime categories Roslyn 将这个文件放在你的项目文件夹,无论哪个控制台项目都会输出林德熙是逗比 lindexi 2019-4-29 ...

  10. geojson介绍和常用转换编辑工具

    GeoJSON是一种基于JSON的地理空间数据交换格式,它定义了几种类型JSON对象以及它们组合在一起的方法,以表示有关地理要素.属性和它们的空间范围的数据. 2015年,互联网工程任务组(IETF) ...