文章目录

  • 边缘检测介绍
  • Canny算法的四个基本步骤
    • 高斯滤波器平滑处理图像
      • 原理
      • 高斯滤波器
      • Matlab中的高斯模版生成函数
    • 计算图像每一个像素点的梯度值以及梯度方向
      • Sobel算子
    • 对梯度值进行非极大值抑制
      • 论文中的方法
      • 插值法
    • 双阈值检测以及连接边缘
  • Matlab代码实现
    • 主函数
    • 高斯滤波器生成函数
    • 梯度计算函数
    • 非极大值抑制函数
      • 论文算法
      • 插值算法
    • 双阈值检测以及连接边缘函数
  • 结果分析

边缘检测介绍

  • 提取图像的边缘信息是底层数字图像处理的基本任务之一
  • 图象的边缘是指图象局部区域亮度变化显著的部分
  • Canny 是一种常用的边缘检测算法. 1986 年由 John F.Canny 提出

Canny算法的四个基本步骤

高斯滤波器平滑处理图像

原理

  • 使用高斯滤波器的主要目的是为了给图像降噪, 因为之后的导数计算对噪声非常敏感, 如果不对噪声进行提前处理, 进行导数运算后, 这些噪声会被放大, 使得检测出来的虚假边缘增多.
  • 虽然使用滤波器平滑处理图像能够有效的抑制噪声, 但是在此过程中会使得图像边缘模糊, 增加了边缘定位的不确定性.
  • 实际工程经验表明, 使用高斯滤波器是在抗噪声干扰和边缘检测精确定位之间的一个较好的折中方案

高斯滤波器

  • 高斯滤波器实际上就是一个n*n的矩阵, 将这个矩阵和图像的矩阵进行卷积, 即可达到对图像平滑处理的目的.
  • 这个矩阵由二维高斯函数生成.
  • 比如想得到一个3*3的高斯矩阵, 以矩阵的中心位置为坐标原点, 各个位置的坐标如下图所示, 将各个位置的坐标带入高斯函数中得到的值就是高斯核中对应位置的元素的值. (水平向右为x轴正方向, 竖直向上为y轴正方向)
(-1, 1) (0, 1) (1, 1)
(-1, 0) (0, 0) (1, 0)
(-1, -1) (0, -1) (1, -1)
  • 假设模版是k*k的大小, 模版中各个元素的计算值如下, i表示行, j表示列. 最后的计算结果一般为小数, 每个元素都需要除以所有元素的和 (保证所有元素值的和为1)
  • 如果要把矩阵化为整数, 则需要在矩阵前面加一个系数 (1/元素和)

Matlab中的高斯模版生成函数

  • Matlab中可以通过函数fspecial('gaussian', HSIZE, SIGMA) 来生成一个高斯模版, HSIZE是模版大小默认是3*3
  • 使用imfilter(img, h, 'replicate') 对图像进行平滑处理
  • 下面代码中的高斯模版生成函数为自己根据原理实现, 平滑处理使用的是conv2卷积函数

计算图像每一个像素点的梯度值以及梯度方向

Sobel算子

  • 边缘是图像局部区域亮度变化显著的部分, 如何衡量亮度变化? 梯度大小和梯度方向
  • Sobel算子认为邻域的像素对当前像素产生的影响不是等价的,所以距离不同的像素具有不同的权值,对算子结果产生的影响也不同。一般来说,距离越远,产生的影响越小 (水平竖直方向的权重大于45度方向)
  • Sobel算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度梯度值
  • 横向 Gx
-1 0 1
-2 0 2
-1 0 1
  • 纵向 Gy
-1 -2 -1
0 0 0
1 2 1
  • 图像中每一个像素的梯度值大小用下面公式计算
  • 梯度方向

对梯度值进行非极大值抑制

  • 图像梯度值矩阵中的元素值越大, 说明图像在该点的梯度值越大, 并不能说明该点就是边缘(这可能仅仅只是图像亮度增强过程中的某一个点)
  • 非极大值抑制就是说寻找在梯度方向上的像素点的局部梯度最大值, 将非极大值点对应的灰度设为0.

论文中的方法

  • 在John Canny提出的Canny算子的论文中,非极大值抑制只是在0、45、90、135四个梯度方向上进行的,每个像素点梯度方向按照相近程度用这四个方向来代替
  • 对于某一个像素点, 根据其梯度方向, 近似到上述4个方向的其中一个, 然后将该像素点的灰度值与该方向上相邻的两个像素点的梯度值进行比较, 如果是极大值则保留, 否则设为0.

插值法

  • 如果不采用近似的方法, 实际数字图像中的像素点是离散的二维矩阵,所以处在某个像素梯度方向两侧的点是不一定存在的,或者说是一个亚像素(sub pixel)点,而这个不存在的点, 以及这个点的梯度值就必须通过对其两侧的点进行插值来得到 (以下图片来源)
  • 对这两种方法, 代码中均有实现, 并进行了效果比较

双阈值检测以及连接边缘

  • 一般情况下设定一个阈值来确定边缘, 高于这个阈值的像素点为边缘像素点, 低于这个阈值的设为0. 但是这样佐会造成一些明明是边缘的点, 却因为梯度值大小(过渡不明显) 而被舍弃
  • 使用启发式的方法确定一个上阀值和下阀值,位于下阀值之上的都有可能作为边缘,这样就可能提高准确度
  • 设置两个阀值(threshold),分别为maxVal和minVal。
    • 大于maxVal的都被检测为边缘
    • 小于minVal的都被检测为非边缘
    • 对于中间的像素点,如果与确定为边缘的像素点邻接,则判定为边缘;否则为非边缘 (即判断领域8个像素点中有没有梯度值大于maxVal的)


图片来源

Matlab代码实现

主函数

%% =============== Part 0: 读取图像 ================
apple = imread('2.jpg');
apple_gray = double(rgb2gray(apple));
figure(1);
subplot(3,3,1);
imshow(uint8(apple_gray));
title('原图')%% =============== Part 1: 高斯平滑处理 ================
% 生成高斯模版
H = gaussian_filter(5, 0.8);
apple_filter = imfilter(apple_gray, H, 'replicate');
subplot(3,3,2);
imshow(uint8(apple_filter));
title('高斯平滑')%% =============== Part 2: 索贝尔算子计算梯度值及方向 ================
[grad, grad_direction] = compute_grad(apple_filter);
subplot(3,3,3);
imshow(uint8(grad));
title('sobel')%% =============== Part 3: 非极大值抑制 ================
canny1 = non_maximum_restrain(grad, grad_direction);
subplot(3,3,4);
imshow(uint8(canny1));
title('非极大值抑制 论文法')
canny2 = non_maximum_restrain_improvement(grad, grad_direction);
subplot(3,3,7);
imshow(uint8(canny2));
title('非极大值抑制 插值法')%% =============== Part 4: 双阈值检测 ================
canny11 = dual_threshold_detection(canny1, 50, 100);
subplot(3,3,5);
imshow(uint8(canny11));
title('双阈值检测 论文法')
canny22 = dual_threshold_detection(canny2, 50, 100);
subplot(3,3,8);
imshow(uint8(canny22));
title('双阈值检测 插值法')

高斯滤波器生成函数

function gaussian_matrix = gaussian_filter(size, sigma)gaussian_matrix = zeros(size, size);coef = 1 / (2 * 3.14159265 * sigma * sigma);total_sum = 0;for i = 1:sizefor j = 1:sizex2 = (j - (size + 1)  / 2)^2;y2 = (i - (size + 1)  / 2)^2;gaussian_matrix(i, j) = coef * exp(-(x2 + y2) / (2 * sigma * sigma));total_sum = total_sum + gaussian_matrix(i, j);endend% 使得矩阵中所有元素和为1gaussian_matrix = gaussian_matrix / total_sum;
end

梯度计算函数

function [grad, grad_direction] = compute_grad(img_filter)% 索贝尔算子sobel = [-1.0 0.0 1.0;-2.0 0.0 2.0;-1.0 0.0 1.0];% 计算图像的sobel水平梯度 gradx=conv2(img_filter, sobel, 'same');% gradx = imfilter(apple_filter, sobel, 'replicate');% 计算图像的sobel垂直梯度 grady=conv2(img_filter, sobel', 'same');% grady = imfilter(apple_filter, sobel', 'replicate');% 得到图像的sobel梯度以及方向, 使用绝对值代替平方开方grad=sqrt(gradx.^2+grady.^2);grad_direction = atan(grady./gradx);
end

非极大值抑制函数

论文算法

function canny = non_maximum_restrain(grad, grad_direction)[m, n] = size(grad_direction);sector = zeros(m, n);canny = zeros(m, n);% 构造% 2 1 0% 3 x 3% 0 1 2for i=1:mfor j=1:nangle = grad_direction(i, j);if (angle < 3*pi/4) && (angle >= pi/4)sector(i, j) = 0;    elseif (angle < pi/4) && (angle >= -pi/4)sector(i, j) = 3;    elseif (angle < -pi/4) && (angle >= -3*pi/4)sector(i, j) = 2;    elsesector(i, j) = 1;    end    endend% 判断没一点像素的梯度方向,并和该方向上的数值进行比较for i=2:m-1for j=2:n-1if (sector(i, j) == 0) % 45度if ((grad(i, j) > grad(i - 1, j + 1) && grad(i, j) > grad(i + 1, j - 1)) || (grad(i, j) > grad(i, j + 1) && grad(i, j) > grad(i, j - 1)) || (grad(i, j) > grad(i - 1, j) && grad(i, j) > grad(i + 1, j)))canny(i,j) = grad(i, j);elsecanny(i, j) = 0;endendif (sector(i, j) == 1) % 90度if (grad(i, j) > grad(i - 1, j) && grad(i, j) > grad(i + 1, j))canny(i,j) = grad(i, j);elsecanny(i, j) = 0;endendif (sector(i, j) == 2) % 135度if ((grad(i, j) > grad(i - 1, j - 1) && grad(i, j) > grad(i + 1, j + 1))|| (grad(i, j) > grad(i, j + 1) && grad(i, j) > grad(i, j - 1)) || (grad(i, j) > grad(i - 1, j) && grad(i, j) > grad(i + 1, j)))canny(i,j) = grad(i, j);elsecanny(i, j) = 0;endendif (sector(i, j) == 3) % 180度if (grad(i, j) > grad(i, j + 1) && grad(i, j) > grad(i, j - 1))canny(i,j) = grad(i, j);elsecanny(i, j) = 0;endendendend
end

插值算法

function canny = non_maximum_restrain_improvement(grad, grad_direction)[m, n] = size(grad_direction);canny = zeros(m, n);for i=2:m-1for j=2:n-1angle = grad_direction(i, j);if (angle > 0 && angle <= pi/4)% 通过差值求的亚像素(实际上不存在)right_top_pixel = tan(angle) * grad(i-1, j+1);right_pixel = (1 - tan(angle)) * grad(i, j+1);pixel1 = right_top_pixel + right_pixel;left_bottom_pixel = tan(angle) * grad(i+1, j-1);left_pixel = (1 - tan(angle)) * grad(i, j-1);pixel2 = left_bottom_pixel + left_pixel;if (grad(i, j) > pixel1 && grad(i, j) > pixel2)canny(i, j) = grad(i, j);elsecanny(i, j) = 0;endelseif (angle > pi/4 && angle < pi/2)right_top_pixel = 1 / tan(angle) * grad(i-1, j+1);top_pixel = (1 - 1 / tan(angle)) * grad(i-1, j);pixel1 = right_top_pixel + top_pixel;left_bottom_pixel = 1 / tan(angle) * grad(i+1, j-1);bottom_pixel = (1 - 1 / tan(angle)) * grad(i+1, j);pixel2 = left_bottom_pixel + bottom_pixel;if (grad(i, j) > pixel1 && grad(i, j) > pixel2)canny(i, j) = grad(i, j);elsecanny(i, j) = 0;endelseif (angle > -pi/2 && angle <= -pi/4)left_top_pixel = -1/tan(angle) * grad(i-1, j-1);top_pixel = (1+1/tan(angle)) * grad(i-1, j);pixel1 = left_top_pixel + top_pixel;right_bottom_pixel = -1/tan(angle) * grad(i+1, j+1);bottom_pixel = (1+1/tan(angle)) * grad(i+1, j);pixel2 = right_bottom_pixel + bottom_pixel;if (grad(i, j) > pixel1 && grad(i, j) > pixel2)canny(i, j) = grad(i, j);elsecanny(i, j) = 0;endelseif (angle > -pi/4 && angle <= 0)left_top_pixel = -tan(angle) * grad(i-1, j-1);left_pixel = (1+tan(angle)) * grad(i, j-1);pixel1 = left_top_pixel + left_pixel;right_bottom_pixel =  -tan(angle) * grad(i+1, j+1);right_pixel = (1+tan(angle)) * grad(i, j+1);pixel2 = right_bottom_pixel + right_pixel;if (grad(i, j) > pixel1 && grad(i, j) > pixel2)canny(i, j) = grad(i, j);elsecanny(i, j) = 0;endelseif (angle == pi/2 || angle == -pi/2)top_pixel = grad(i-1, j);bottom_pixel = grad(i+1, j);if (grad(i, j) > top_pixel && grad(i, j) > bottom_pixel)canny(i, j) = grad(i, j);elsecanny(i, j) = 0;endend    endend
end

双阈值检测以及连接边缘函数

function canny2 = dual_threshold_detection(canny, low_th, high_th)[m, n] = size(canny);canny2 = zeros(m, n);for i=2:m-1for j=2:n-1if (canny(i, j) < low_th)canny2(i,j) = 0;elseif (canny(i, j) > high_th)canny2(i, j) = canny(i, j);elseneighbor_matrix = [canny(i-1, j-1), canny(i, j-1), canny(i+1, j-1);...canny(i-1, j), canny(i, j), canny(i+1, j);...canny(i-1, j+1), canny(i, j+1), canny(i+1, j+1);];max_neighbour = max(neighbor_matrix);if (max_neighbour > high_th)canny2(i, j) = canny(i, j);elsecanny2(i,j) = 0;endendendend
end

结果分析


  • 经过比较可以发现, 插值法最后筛选出来的边缘像素点比论文法少, 因此图像看起来有些地方并不是很连续. 也导致了一些地方细节的丢失
  • 但是在边缘宽度上, 论文方法产生宽度大于1的边缘的可能性比插值法更大,在需要达到高精度单点响应的情况下, 插值法可能会更好

参考

  1. Canny边缘检测算法
  2. [图像]Canny检测的Matlab实现(含代码)
  3. 图像处理基础(4):高斯滤波器详解
  4. 高斯滤波器原理及其实现
  5. 边缘检测sobel算子
  6. 利用sobel算子进行边缘检测
  7. Canny算子中的非极大值抑制(Non-Maximum Suppression)分析

[计算机视觉] 边缘检测Canny算法原理总结 以及 matlab代码实现相关推荐

  1. Fisher线性判别算法原理及实现 MATLAB

    Fisher线性判别算法原理及实现 MATLAB 一.Fisher判别器原理 二.代码实现 clc; close all; clear; %% 生成数据 rng(2020); %指定一个种子 mu1 ...

  2. TOPSIS(逼近理想解)算法原理详解与代码实现

    写在前面: 个人理解:针对存在多项指标,多个方案的方案评价分析方法,也就是根据已存在的一份数据,判断数据中各个方案的优劣.中心思想是首先确定各项指标的最优理想值(正理想值)和最劣理想值(负理想解),所 ...

  3. Adaboost算法原理分析和实例+代码(简明易懂)

    Adaboost算法原理分析和实例+代码(简明易懂) [尊重原创,转载请注明出处] http://blog.csdn.net/guyuealian/article/details/70995333   ...

  4. 十三种基于直方图的图像全局二值化算法原理、实现、代码及效果(转)

    源:十三种基于直方图的图像全局二值化算法原理.实现.代码及效果.

  5. Kd-Tree算法原理和开源实现代码

    Kd-Tree算法原理和开源实现代码 本文介绍一种用于高维空间中的快速最近邻和近似最近邻查找技术--Kd-Tree(Kd树).Kd-Tree,即K-dimensional tree,是一种高维索引树形 ...

  6. 灰狼(GWO)算法(附完整Matlab代码,可直接复制)

    尊重他人劳动成果,请勿转载! 有问题可留言或私信,看到了都会回复解答! 其他算法请参考: 1.粒子群(PSO)优化算法(附完整Matlab代码,可直接复制)https://blog.csdn.net/ ...

  7. 粒子群(PSO)算法(附完整Matlab代码,可直接复制)

    在粒子群优化算法中,每个解可用一只鸟(粒子)表示,目标函数就是鸟群所需要寻找的食物源.寻找最优解的过程中,粒子包含两种行为:个体行为和群体行为. 个体行为:粒子根据自身在寻优过程中的最优解更新自己的位 ...

  8. 25-混合A星算法Hybrid_Astar路径规划MATLAB代码

    资源: Hybrid-Astar(混合A星算法)路径规划MATLAB代码-电子商务文档类资源-CSDN文库 主要内容: 以车辆的运动学模型为节点,以当前点到终点的Astar距离和RS距离两者最大的距离 ...

  9. 十三种基于直方图的图像全局二值化算法原理、实现、代码及效果

    十三种基于直方图的图像全局二值化算法实现 1. 什么是基于直方图的图像全局二值化算法 2. 灰度平均值 3. 百分比阈值(P-Tile法) 3. 基于双峰的阈值 3.1 基于平均值的阈值 3.2 基于 ...

  10. 【算法思想】Reed-Solomon 纠错编码基础概念,编码、解码算法原理、数学公式 Python代码实现

    [算法思想]Reed-Solomon 纠错编码基础概念,编码.解码算法原理.数学公式 & Python代码实现 文章目录 [算法思想]Reed-Solomon 纠错编码基础概念,编码.解码算法 ...

最新文章

  1. (转载博文)VC++API速查
  2. linux下TCP通信简单实例
  3. buildroot mysql
  4. com组件 安全提示_【加粉利器】百度基木鱼通用微信组件上线
  5. 什么是oracle的临时表??
  6. jqGrid Events
  7. 在VS中怎么用vb画矩形_(十四)矩形和操作展示逻辑操作
  8. Cinemachine教程 | Unity中如何快速制作相机切换动画?
  9. 长串英文数字强制折行解决办法css
  10. presto安装及使用 1
  11. java图书管理系统的不足_java图书管理系统的问题
  12. 陈玉琴老师的中医理学
  13. linux 远程安装teamviewer,如何在CentOS 7上安装TeamViewer
  14. bugku ctf 杂项 旋转跳跃 (熟悉的声音中貌似又隐藏着啥,key:syclovergeek)
  15. 【元胞自动机】基于元胞自动机的人口疏散仿真matlab源码
  16. 数据结构——哈夫曼树
  17. 行业权威来揭秘,商用PC为什么首选12代酷睿
  18. Ubuntu桌面快捷键切换窗口技巧
  19. 浅析Content Negotation在Nancy的实现和使用
  20. 神奇的 Swift 错误处理

热门文章

  1. JavaScript根据开始时间和结束时间计算具体礼拜几
  2. Win11玩红警2突然就卡住不动?
  3. WPF支持OneWay,TwoWay,OneTime,Default和OneWayToSource
  4. BZOJ 1023 仙人掌图
  5. 洛谷4315 月下“毛景树”(树链剖分)
  6. 记一次从 git pull 出现 Untracked FilesPervent Merge
  7. 重庆金域 :新系统成功上线!重庆金域第一份新系统的报告单2017年9月21日13:00正式发出
  8. 利用OpenCV进行颜色检测
  9. 不规则图片显示(css实现多边形)
  10. Rasa原文-生成NLU数据