详细解读Canny检测算法与实现

简介: 详细解读Canny检测算法与实现

1、原理

图象边缘就是图像颜色快速变化的位置,对于灰度图像来说,也就是灰度值有明显变化的位置。图像边缘信息主要集中在高频段,图像锐化或检测边缘实质就是高通滤波。数值微分可以求变化率,在图像上离散值求梯度,图像处理中有多种边缘检测(梯度)算子,常用的包括普通一阶差分,Robert算子(交叉差分),Sobel算子,二阶拉普拉斯算子等等,是基于寻找梯度强度。

Canny 边缘检测算法是John F. Canny 于1986年开发出来的一个多级边缘检测算法,也被很多人认为是边缘检测的 最优算法, 最优边缘检测的三个主要评价标准是:

低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。

高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。

最小响应: 图像中的边缘只能标识一次。

Canny算子求边缘点具体算法步骤如下:

1. 用高斯滤波器平滑图像.

2. 用一阶偏导有限差分计算梯度幅值和方向.

3. 对梯度幅值进行非极大值抑制.

4. 用双阈值算法检测和连接边缘.

2、实现步骤

2.1、消除噪声

使用高斯平滑滤波器卷积降噪。下面显示了一个 size = 5 的高斯内核示例:

2.2、计算梯度幅值和方向

按照Sobel滤波器的步骤,计算水平和垂直方向的差分Gx和Gy:

在vs中可以看到sobel像素值和形状:

梯度幅值和方向为:

梯度方向近似到四个可能角度之一(一般 0, 45, 90, 135)。

2.3、非极大值抑制

非极大值抑制是指寻找像素点局部最大值。sobel算子检测出来的边缘太粗了,我们需要抑制那些梯度不够大的像素点,只保留最大的梯度,从而达到瘦边的目的。沿着梯度方向,比较它前面和后面的梯度值,梯度不够大的像素点很可能是某一条边缘的过渡点,排除非边缘像素,最后保留了一些细线。

在John Canny提出的Canny算子的论文中,非最大值抑制就只是在0、90、45、135四个梯度方向上进行的,每个像素点梯度方向按照相近程度用这四个方向来代替。梯度向量的每个四分之一圆被45°线分成两种情况,一种情况是倾向于水平,另一种倾向于竖直,一共 8 个方向。这种情况下,非最大值抑制所比较的相邻两个像素就是:

1) 0:左边 和 右边

2) 45:右上 和 左下

3) 90:上边 和 下边

4)135:左上 和 右下

这样做的好处是简单,但是这种简化的方法无法达到最好的效果,因为自然图像中的边缘梯度方向不一定是沿着这四个方向的,即梯度方向的线并没有落在8邻域坐标点上。因此,就有很大的必要进行插值,找出在一个像素点上最能吻合其所在梯度方向的两侧的像素值。

如果|gx|>|gy|,这说明该点的梯度方向更靠近X轴方向,所以g2和g4则在C的左右,我们可以用下面来说明这两种情况(方向相同和方向不同):

可以使用插值计算出真实梯度值:

其中,插值计算方式为:dTemp1 = weightg1 + (1-weight)g2; dTemp2 = weightg3 + (1-weight)g4;

Matlab使用非常有技巧的方式来计算方向,如下不仅做了dx、dy的大小判断还做了方向的判定。

witch direction

case 1

idx = find((iy<=0 & ix>-iy) | (iy>=0 & ix<-iy));

case 2

idx = find((ix>0 & -iy>=ix) | (ix[span style="color: rgba(128, 0, 128, 1)">0 & -iy<=ix));

case 3

idx = find((ix<=0 & ix>iy) | (ix>=0 & ix[span style="color: rgba(0, 0, 0, 1)">iy));

case 4

idx = find((iy[span style="color: rgba(128, 0, 128, 1)">0 & ix0 & ix>=iy));

end

2.4、双阈值检测和区域连通

最后一步,Canny 使用了滞后阈值,滞后阈值需要两个阈值(高阈值和低阈值)。如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;如果边缘像素的梯度值小于低阈值,则会被抑制。阈值的选择取决于给定输入图像的内容。Canny 推荐的 高:低 阈值比在 2:1 到3:1之间。

3、代码实现

3.1 计算梯度

/

Sobel 梯度计算

/

Mat gradients(Mat &img, Mat &sobel)

{

int W = img.cols;

int H = img.rows;

Mat dx = Mat_span style="color: rgba(0, 0, 255, 1)">int</span);

int border = (int)sobel.rows / 2;

for (int r = border; r < H - border; r++)

{

for (int c = border; c < W - border; c++)

{

float tmp = 0;

for (int i = -border; i <= border; i++) {

for (int j = -border; j <= border; j++) {

tmp += (int)img.data【(r + i)W + c + j】 sobel.atspan style="color: rgba(0, 0, 255, 1)">int</span;

}

}

dx.atspan style="color: rgba(0, 0, 255, 1)">int</span = tmp;

}

}

return dx;

}

3.2计算非极大值抑制(详细推导过程见参考文献文章)

/

fucntion: non-maximum suppression

input:

pMag: pointer to Magnitude,

pGradX: gradient of x-direction

pGradY: gradient of y-direction

sz: size of pMag (width = size.cx, height = size.cy)

limit: limitation

output:

pNSRst: result of non-maximum suppression

/

void NonMaxSuppress(int pMag, int pGradX, int pGradY, Size sz, int pNSRst)

{

long x, y;

int nPos;

// the component of the gradient

int gx, gy;

// the temp varialbe

int g1, g2, g3, g4;

double weight;

double dTemp, dTemp1, dTemp2;

//设置图像边缘为不可能的分界点

for (x = 0; x < sz.width; x++)

{

pNSRst【x】 = 0;

pNSRst【(sz.height - 1)sz.width + x】 = 0;

}

for (y = 0; y < sz.height; y++)

{

pNSRst【ysz.width】 = 0;

pNSRst【ysz.width + sz.width - 1】 = 0;

}

for (y = 1; y < sz.height - 1; y++)

{

for (x = 1; x < sz.width - 1; x++)

{

nPos = y sz.width + x;

// if pMag【nPos】==0, then nPos is not the edge point

if (pMag【nPos】 == 0)

{

pNSRst【nPos】 = 0;

}

else

{

// the gradient of current point

dTemp = pMag【nPos】;

// x,y 方向导数

gx = pGradX【nPos】;

gy = pGradY【nPos】;

//如果方向导数y分量比x分量大,说明导数方向趋向于y分量

if (abs(gy) > abs(gx))

{

// calculate the factor of interplation

weight = fabs(gx) / fabs(gy);

g2 = pMag【nPos - sz.width】; // 上一行

g4 = pMag【nPos + sz.width】; // 下一行

//如果x,y两个方向导数的符号相同

//C 为当前像素,与g1-g4 的位置关系为:

//g1 g2

// C

// g4 g3

if (gxgy > 0)

{

g1 = pMag【nPos - sz.width - 1】;

g3 = pMag【nPos + sz.width + 1】;

}

//如果x,y两个方向的方向导数方向相反

//C是当前像素,与g1-g4的关系为:

// g2 g1

// C

// g3 g4

else

{

//代码效果参考:http://www.zidongmutanji.com/bxxx/105082.html

g1 = pMag【nPos - sz.width + 1】;

g3 = pMag【nPos + sz.width - 1】;

}

}

else

{

//插值比例

weight = fabs(gy) / fabs(gx);

g2 = pMag【nPos + 1】; //后一列

g4 = pMag【nPos - 1】; // 前一列

//如果x,y两个方向的方向导数符号相同

//当前像素C与 g1-g4的关系为

// g3

// g4 C g2

// g1

if (gx gy > 0)

{

g1 = pMag【nPos + sz.width + 1】;

g3 = pMag【nPos - sz.width - 1】;

}

//如果x,y两个方向导数的方向相反

// C与g1-g4的关系为

// g1

// g4 C g2

// g3

else

{

g1 = pMag【nPos - sz.width + 1】;

g3 = pMag【nPos + sz.width - 1】;

}

}

dTemp1 = weight g1 + (1 - weight)g2;

dTemp2 = weight g3 + (1 - weight)g4;

if(dTemp )

//当前像素的梯度是局部的最大值

//该点可能是边界点

if (dTemp >= dTemp1 && dTemp >= dTemp2)

{

pNSRst【nPos】 = dTemp;

}

else

{

//不可能是边界点

pNSRst【nPos】 = 0;

}

}

}

}

}

3.3双阈值检测和边缘连接

void duble_threshold(Mat &pMag, Mat &pThreadImg, float threshold)

{

double maxv;

int img_ptr = pMag.ptrspan style="color: rgba(0, 0, 255, 1)">int</span;

uchar dst_ptr = pThreadImg.ptr(0);

minMaxLoc(pMag, 0, &maxv, 0, 0);

cout [ "max" [ maxv [ endl;

int TL = 0.333 threshold maxv; // 1/3 of TH

int TH = threshold maxv;

int w = pMag.cols;

int h = pMag.rows;

for (int r = 1; r < pMag.rows; r++)

{

for (int c = 1; c < pMag.cols; c++)

{

int tmp = img_ptr【rw + c】;

if (tmp [span style="color: rgba(0, 0, 0, 1)"> TL) {

dst_ptr【rw + c】 = 0;

}

else if (tmp >= TH) {

dst_ptr【rw + c】 = 255;

}

else {

bool connect = false;

for(int i=-1; i<=1 && connect == false; i++)

for (int j = -1; j <= 1 && connect == false; j++)

{

if (img_ptr【r + i, c + j】 >= TH)

{

dst_ptr【rw + c】 = 255;

connect = true;

break;

}

else dst_ptr【r*w + c】 = 0;

}

}

}

}

}

4、测试结论

测试1:左侧是原图,右侧是进行了sobel梯度计算和非极大值抑制后的图。

可见右图,在企鹅轮廓内部还有孤立的点,放大后如下图。

使用双阈值限定后如下图,内部点消失了。

测试2:选择合适的阈值,图像中心的白色噪点可以消除。

测试3:

如下图,图2的双阈值计算梯度后最大梯度360,图3使用0.5倍高阈值,轮廓不连贯,可见阈值过高。改为0.2倍高阈值,结果如图4,改善了轮廓缺失问题。

5、参考文献

1、《数字图像处理与机器视觉》,第二版。 张铮、徐超、任淑霞、韩海玲等编著。

2、Canny 边缘检测

3、Sobel算子的数学基础

4、Canny边缘检测

5、Canny算子中的非极大值抑制(Non-Maximum Suppression)分析

6、一种改进非极大值抑制的Canny边缘检测算法

个人博客,转载请注明。

相关文章
|
12天前
|
算法 JavaScript 前端开发
在JavaScript中实现基本的碰撞检测算法,我们通常会用到矩形碰撞检测,也就是AABB(Axis-Aligned Bounding Box)碰撞检测
【6月更文挑战第16天】JavaScript中的基本碰撞检测涉及AABB(轴对齐边界框)方法,常用于2D游戏。`Rectangle`类定义了矩形的属性,并包含一个`collidesWith`方法,通过比较边界来检测碰撞。若两矩形无重叠部分,四个条件(关于边界相对位置)均需满足。此基础算法适用于简单场景,复杂情况可能需采用更高级的检测技术或物理引擎库。
48 6
|
21天前
|
算法 计算机视觉
图像处理之角点检测算法(Harris Corner Detection)
图像处理之角点检测算法(Harris Corner Detection)
18 3
|
8天前
|
机器学习/深度学习 算法 语音技术
基于语音信号MFCC特征提取和GRNN神经网络的人员身份检测算法matlab仿真
**语音识别算法概览** MATLAB2022a中实现,结合MFCC与GRNN技术进行说话人身份检测。MFCC利用人耳感知特性提取语音频谱特征,GRNN作为非线性映射工具,擅长序列学习,确保高效识别。预加重、分帧、加窗、FFT、滤波器组、IDCT构成MFCC步骤,GRNN以其快速学习与鲁棒性处理不稳定数据。适用于多种领域。
|
9天前
|
机器学习/深度学习 算法 计算机视觉
基于ADAS的车道线检测算法matlab仿真
**摘要:** 基于ADAS的车道线检测算法利用Hough变换和边缘检测在视频中识别车道线,判断车道弯曲情况,提供行驶方向信息,并高亮显示。在MATLAB2022a中实现,系统包括图像预处理(灰度化、滤波、边缘检测)、车道线特征提取(霍夫变换、曲线拟合)和车道线跟踪,确保在实时场景中的准确性和稳定性。预处理通过灰度转换减少光照影响,滤波去除噪声,Canny算法检测边缘。霍夫变换用于直线检测,曲线拟合适应弯道,跟踪则增强连续帧的车道线检测。
|
16天前
|
机器学习/深度学习 监控 算法
基于yolov2深度学习网络的昆虫检测算法matlab仿真,并输出昆虫数量和大小判决
YOLOv2算法应用于昆虫检测,提供实时高效的方法识别和定位图像中的昆虫,提升检测精度。核心是统一检测网络,预测边界框和类别概率。通过预测框尺寸估算昆虫大小,适用于农业监控、生态研究等领域。在matlab2022A上运行,经过关键升级,如采用更优网络结构和损失函数,保证速度与精度。持续优化可增强对不同昆虫的检测能力。![image.png](https://ucc.alicdn.com/pic/developer-ecology/3tnl7rfrqv6tw_e760ff6682a3420cb4e24d1e48b10a2e.png)
|
21天前
|
算法 计算机视觉
图像处理之霍夫变换圆检测算法
图像处理之霍夫变换圆检测算法
14 0
|
22天前
|
算法 计算机视觉
图像处理之霍夫变换(直线检测算法)
图像处理之霍夫变换(直线检测算法)
21 0
|
22天前
|
机器学习/深度学习 算法
五种基于RGB色彩空间统计的皮肤检测算法
五种基于RGB色彩空间统计的皮肤检测算法
13 0
|
3天前
|
机器学习/深度学习 自然语言处理 算法
m基于深度学习的OFDM+QPSK链路信道估计和均衡算法误码率matlab仿真,对比LS,MMSE及LMMSE传统算法
**摘要:** 升级版MATLAB仿真对比了深度学习与LS、MMSE、LMMSE的OFDM信道估计算法,新增自动样本生成、复杂度分析及抗频偏性能评估。深度学习在无线通信中,尤其在OFDM的信道估计问题上展现潜力,解决了传统方法的局限。程序涉及信道估计器设计,深度学习模型通过学习导频信息估计信道响应,适应频域变化。核心代码展示了信号处理流程,包括编码、调制、信道模拟、降噪、信道估计和解调。
23 8
|
5天前
|
算法
基于GA遗传优化的混合发电系统优化配置算法matlab仿真
**摘要:** 该研究利用遗传算法(GA)对混合发电系统进行优化配置,旨在最小化风能、太阳能及电池储能的成本并提升系统性能。MATLAB 2022a用于实现这一算法。仿真结果展示了一系列图表,包括总成本随代数变化、最佳适应度随代数变化,以及不同数据的分布情况,如负荷、风速、太阳辐射、弃电、缺电和电池状态等。此外,代码示例展示了如何运用GA求解,并绘制了发电单元的功率输出和年变化。该系统原理基于GA的自然选择和遗传原理,通过染色体编码、初始种群生成、适应度函数、选择、交叉和变异操作来寻找最优容量配置,以平衡成本、效率和可靠性。