图像处理之canny边缘检测(非极大值抑制和高低阈值)

Canny 边缘检测方法

Canny算子是John F.Canny 大佬在1986年在其发表的论文 《Canny J. A computational approach to edge detection [J]. IEEE Transactions on Pattern Analysis and Machine Intelligence, 1986 (6): 679-698.》提出来的。

检测目标:

  • 低错误率。所有边缘都应该被找到,并且应该没有伪响应。也就是检测到的边缘必须尽可能时真实的边缘。
  • 边缘点应被很好地定位。已定位边缘必须尽可能接近真实边缘。也就是由检测器标记为边缘的点和真实边缘的中心之间的距离应该最小。
  • 单一的边缘点响应。这意味着在仅存一个单一边缘点的位置,检测器不应指出多个边缘像素。

Canny算法步骤

①高斯模糊 - GaussianBlur
②灰度转换 - cvtColor
③计算梯度 – Sobel/Scharr
④非最大信号抑制
⑤高低阈值输出二值图像——高低阈值比值为2:1或3:1最佳

1.灰度转换

点击图像处理之图像灰度化查看

2.高斯模糊

点击图像处理之高斯滤波查看

3.计算梯度

点击图像处理之梯度及边缘检测算子查看

4.非极大抑制

非极大值抑制是进行边缘检测的一个重要步骤,通俗意义上是指寻找像素点局部最大值。沿着梯度方向,比较它前面和后面的梯度值,如果它不是局部最大值,则去除。
在这里插入图片描述
在John Canny提出的Canny算子的论文中,非最大值抑制就只是在 0 ∘ 、 9 0 ∘ 、 4 5 ∘ 、 13 5 ∘ 0^\circ、90^\circ、45^\circ、135^\circ 09045135四个梯度方向上进行的,每个像素点梯度方向按照相近程度用这四个方向来代替。这四种情况也代表着四种不同的梯度,即
G y > G x G_y>G_x Gy>Gx,且两者同号。
G y > G x G_y>G_x Gy>Gx,且两者异号。
G y < G x G_y<G_x Gy<Gx,且两者同号。
G y < G x G_y<G_x Gy<Gx,且两者异号。
如上图所示,根据X方向和Y方向梯度的大小可以判断A点是靠近X轴还是Y轴,通过A1和A2的像素值则可计算A点的亚像素值,B点同理,不再赘述。上面两图为靠近Y轴的梯度大,下面两图为靠近X轴的像素大。
由于A、B两点的位置是通过梯度来确定的,那么A、B两点的梯度值也可以根据Q点的梯度计算,因此假设Q点在四个方向上的梯度分别为 G 1 G_1 G1 G 2 G_2 G2 G 3 G_3 G3 G 4 G_4 G4
G y > G x G_y>G_x Gy>Gx时, w = G x G y , G 1 = ( i − 1 , j ) , G 2 = ( i + 1 , j ) w=\frac{G_x}{G_y},G_1=(i-1,j),G_2=(i+1,j) w=GyGx,G1=(i1,j),G2=(i+1,j)
两者同号时: G 3 = ( i − 1 , j − 1 ) , G 4 = ( i + 1 , j + 1 ) G_3=(i-1,j-1),G_4=(i+1,j+1) G3=(i1,j1),G4=(i+1,j+1)
两者异号时: G 3 = ( i − 1 , j + 1 ) , G 4 = ( i + 1 , j − 1 ) G_3=(i-1,j+1),G_4=(i+1,j-1) G3=(i1,j+1),G4=(i+1,j1)
G y < G x G_y<G_x Gy<Gx时, w = G y G x , G 1 = ( i , j − 1 ) , G 2 = ( i , j + 1 ) w=\frac{G_y}{G_x},G_1=(i,j-1),G_2=(i,j+1) w=GxGy,G1=(i,j1),G2=(i,j+1)
两者同号时: G 3 = ( i + 1 , j − 1 ) , G 4 = ( i + 1 , j − 1 ) G_3=(i+1,j-1),G_4=(i+1,j-1) G3=(i+1,j1),G4=(i+1,j1)
两者异号时: G 3 = ( i − 1 , j − 1 ) , G 4 = ( i + 1 , j + 1 ) G_3=(i-1,j-1),G_4=(i+1,j+1) G3=(i1,j1),G4=(i+1,j+1)
如此便可以计算出两个相邻亚像素点的梯度值
g A = w ∗ G 1 + ( 1 − w ) ∗ G 3 g B = w ∗ G 2 + ( 1 − w ) ∗ G 4 g_A=w*G_1+(1-w)*G_3\\ g_B=w*G_2+(1-w)*G_4 gA=wG1+(1w)G3gB=wG2+(1w)G4
比较三者的像素值,如果Q点像素值大于其余两者,则保留Q点作为边缘上的点,否则认为Q点为冗余点。
python代码:

ef NMS(gradients, direction):""" Non-maxima suppressionArgs:gradients: the gradients of each pixeldirection: the direction of the gradients of each pixelReturns:the output image"""W, H = gradients.shapenms = np.copy(gradients[1:-1, 1:-1])for i in range(1, W - 1):for j in range(1, H - 1):theta = direction[i, j]weight = np.tan(theta)if theta > np.pi / 4:d1 = [0, 1]d2 = [1, 1]weight = 1 / weightelif theta >= 0:d1 = [1, 0]d2 = [1, 1]elif theta >= - np.pi / 4:d1 = [1, 0]d2 = [1, -1]weight *= -1else:d1 = [0, -1]d2 = [1, -1]weight = -1 / weightg1 = gradients[i + d1[0], j + d1[1]]g2 = gradients[i + d2[0], j + d2[1]]g3 = gradients[i - d1[0], j - d1[1]]g4 = gradients[i - d2[0], j - d2[1]]grade_count1 = g1 * weight + g2 * (1 - weight)grade_count2 = g3 * weight + g4 * (1 - weight)if grade_count1 > gradients[i, j] or grade_count2 > gradients[i, j]:nms[i - 1, j - 1] = 0return nms

5.双阈值跟踪边界

设置两个阈值,minVal和maxVal。梯度大于maxVal的任何边缘是真边缘,而minVal以下的边缘是非边缘。位于这两个阈值之间的边缘会基于其连通性而分类为边缘或非边缘,如果它们连接到“可靠边缘”像素,则它们被视为边缘的一部分;否则,不是边缘。
代码如下:

def double_threshold(nms, threshold1, threshold2):""" Double ThresholdUse two thresholds to compute the edge.Args:nms: the input imagethreshold1: the low thresholdthreshold2: the high thresholdReturns:The binary image."""visited = np.zeros_like(nms)output_image = nms.copy()W, H = output_image.shapedef dfs(i, j):if i >= W or i < 0 or j >= H or j < 0 or visited[i, j] == 1:returnvisited[i, j] = 1if output_image[i, j] > threshold1:output_image[i, j] = 255dfs(i-1, j-1)dfs(i-1, j)dfs(i-1, j+1)dfs(i, j-1)dfs(i, j+1)dfs(i+1, j-1)dfs(i+1, j)dfs(i+1, j+1)else:output_image[i, j] = 0for w in range(W):for h in range(H):if visited[w, h] == 1:continueif output_image[w, h] >= threshold2:dfs(w, h)elif output_image[w, h] <= threshold1:output_image[w, h] = 0visited[w, h] = 1for w in range(W):for h in range(H):if visited[w, h] == 0:output_image[w, h] = 0return output_image

整体代码如下:

# -*- coding: utf-8 -*-
import numpy as np
import cv2
import imgShow as iSdef smooth(image, sigma = 1.4, length = 5):""" Smooth the imageCompute a gaussian filter with sigma = sigma and kernal_length = length.Each element in the kernal can be computed as below:G[i, j] = (1/(2*pi*sigma**2))*exp(-((i-k-1)**2 + (j-k-1)**2)/2*sigma**2)Then, use the gaussian filter to smooth the input image.Args:image: array of grey imagesigma: the sigma of gaussian filter, default to be 1.4length: the kernal length, default to be 5Returns:the smoothed image"""# Compute gaussian filterk = length // 2gaussian = np.zeros([length, length])for i in range(length):for j in range(length):gaussian[i, j] = np.exp(-((i-k) ** 2 + (j-k) ** 2) / (2 * sigma ** 2))gaussian /= 2 * np.pi * sigma ** 2# Batch Normalizationgaussian = gaussian / np.sum(gaussian)# Use Gaussian FilterW, H = image.shapenew_image = np.zeros([W - k * 2, H - k * 2])for i in range(W - 2 * k):for j in range(H - 2 * k):new_image[i, j] = np.sum(image[i:i+length, j:j+length] * gaussian)new_image = np.uint8(new_image)return new_imagedef get_gradient_and_direction(image):""" Compute gradients and its directionUse Sobel filter to compute gradients and direction.-1 0 1        -1 -2 -1Gx = -2 0 2   Gy =  0  0  0-1 0 1         1  2  1Args:image: array of grey imageReturns:gradients: the gradients of each pixeldirection: the direction of the gradients of each pixel"""Gx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])Gy = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])W, H = image.shapegradients = np.zeros([W - 2, H - 2])direction = np.zeros([W - 2, H - 2])for i in range(W - 2):for j in range(H - 2):dx = np.sum(image[i:i+3, j:j+3] * Gx)dy = np.sum(image[i:i+3, j:j+3] * Gy)gradients[i, j] = np.sqrt(dx ** 2 + dy ** 2)if dx == 0:direction[i, j] = np.pi / 2else:direction[i, j] = np.arctan(dy / dx)gradients = np.uint8(gradients)return gradients, directiondef NMS(gradients, direction):""" Non-maxima suppressionArgs:gradients: the gradients of each pixeldirection: the direction of the gradients of each pixelReturns:the output image"""W, H = gradients.shapenms = np.copy(gradients[1:-1, 1:-1])for i in range(1, W - 1):for j in range(1, H - 1):theta = direction[i, j]weight = np.tan(theta)if theta > np.pi / 4:d1 = [0, 1]d2 = [1, 1]weight = 1 / weightelif theta >= 0:d1 = [1, 0]d2 = [1, 1]elif theta >= - np.pi / 4:d1 = [1, 0]d2 = [1, -1]weight *= -1else:d1 = [0, -1]d2 = [1, -1]weight = -1 / weightg1 = gradients[i + d1[0], j + d1[1]]g2 = gradients[i + d2[0], j + d2[1]]g3 = gradients[i - d1[0], j - d1[1]]g4 = gradients[i - d2[0], j - d2[1]]grade_count1 = g1 * weight + g2 * (1 - weight)grade_count2 = g3 * weight + g4 * (1 - weight)if grade_count1 > gradients[i, j] or grade_count2 > gradients[i, j]:nms[i - 1, j - 1] = 0return nmsdef double_threshold(nms, threshold1, threshold2):""" Double ThresholdUse two thresholds to compute the edge.Args:nms: the input imagethreshold1: the low thresholdthreshold2: the high thresholdReturns:The binary image."""visited = np.zeros_like(nms)output_image = nms.copy()W, H = output_image.shapedef dfs(i, j):if i >= W or i < 0 or j >= H or j < 0 or visited[i, j] == 1:returnvisited[i, j] = 1if output_image[i, j] > threshold1:output_image[i, j] = 255dfs(i-1, j-1)dfs(i-1, j)dfs(i-1, j+1)dfs(i, j-1)dfs(i, j+1)dfs(i+1, j-1)dfs(i+1, j)dfs(i+1, j+1)else:output_image[i, j] = 0for w in range(W):for h in range(H):if visited[w, h] == 1:continueif output_image[w, h] >= threshold2:dfs(w, h)elif output_image[w, h] <= threshold1:output_image[w, h] = 0visited[w, h] = 1for w in range(W):for h in range(H):if visited[w, h] == 0:output_image[w, h] = 0return output_imageif __name__ == "__main__":# code to read imageimg=cv2.imread('./originImg/Lena.tif')img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)smoothed_image = smooth(img)gradients, direction = get_gradient_and_direction(smoothed_image)nms = NMS(gradients, direction)output_image = double_threshold(nms, 40, 100)imageList = []origin_img = [img, 'origin_img']imageList.append(origin_img)# smoothed= [smoothed_image, ' smoothed_image']# imageList.append(smoothed)gradient = [gradients, 'gradients']imageList.append(gradient)nms = [nms, 'nms']imageList.append(nms)output_images = [output_image, 'output_image']imageList.append(output_images)iS.showMultipleimages(imageList, 25, 25, './ProcessedImg/canny.jpg')

检测结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/6820.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学好Elasticsearch系列-Mapping

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 文章目录 Mapping 的基本概念查看索引 Mapping 字段数据类型数字类型基本数据类型Keywords 类型Dates&#xff08;时间类型&#xff09;对象类型空间数据类型文档排名类型文本搜索类型 两种映射类型自动映射&…

动手学DL——深度学习预备知识随笔【深度学习】【PyTorch】

文章目录 2、预备知识2.1、数据操作2.2、线性代数&矩阵计算2.3、导数2.4、基础优化方法 2、预备知识 2.1、数据操作 batch&#xff1a;以图片数据为例&#xff0c;一次读入的图片数量。 小批量样本可以充分利用GPU进行并行计算提高计算效率。 数据访问 数组&#xff1a;np…

Android 实现阅读用户协议的文字控件效果

开发中&#xff0c;经常要用到一些阅读隐私协议的场景&#xff0c;原生的textview控件很难做到在一个控件里有两个点击事件&#xff0c;那现在就来安利一个强大的组件——SpannableStringBuilder。 先看看效果&#xff1a; 直接上代码&#xff0c;布局文件&#xff1a; <Li…

【图像处理】使用自动编码器进行图像降噪(改进版)

阿里雷扎凯沙瓦尔兹 一、说明 自动编码器是一种学习压缩和重建输入数据的神经网络。它由一个将数据压缩为低维表示的编码器和一个从压缩表示中重建原始数据的解码器组成。该模型使用无监督学习进行训练&#xff0c;旨在最小化输入和重建输出之间的差异。自动编码器可用于降维、…

【iOS】动态链接器dyld

参考&#xff1a;认识 dyld &#xff1a;动态链接器 dyld简介 dyld&#xff08;Dynamic Linker&#xff09;是 macOS 和 iOS 系统中的动态链接器&#xff0c;它是负责在运行时加载和链接动态共享库&#xff08;dylib&#xff09;或可执行文件的组件。在 macOS 系统中&#xf…

STM32MP157驱动开发——按键驱动(定时器)

“定时器 ”机制&#xff1a; 内核函数 定时器涉及函数参考内核源码&#xff1a;include\linux\timer.h 给定时器的各个参数赋值&#xff1a; setup_timer(struct timer_list * timer, void (*function)(unsigned long),unsigned long data)&#xff1a;设置定时器&#xf…

多元函数的概念

目录 多元函数的极限&#xff1a; 例题1&#xff1a; 例题2&#xff1a; 多元函数的连续性 连续函数的性质 偏导数 高阶偏导数 定理1&#xff1a; 全微分 可微的必要条件 用定义来判断是否可微 可微的充分条件 连续偏导可微的关系 多元函数的极限&#xff1a; 对于一个二元…

macOS Ventura 13.5 (22G74) 正式版发布,ISO、IPSW、PKG 下载

macOS Ventura 13.5 (22G74) 正式版发布&#xff0c;ISO、IPSW、PKG 下载 本站下载的 macOS Ventura 软件包&#xff0c;既可以拖拽到 Applications&#xff08;应用程序&#xff09;下直接安装&#xff0c;也可以制作启动 U 盘安装&#xff0c;或者在虚拟机中启动安装。另外也…

服务器数据库中了Locked勒索病毒,企业应该如何正确处理并采取后续防护措施

网络技术的发展极大地方便了人们的工作生活&#xff0c;但同样带来了一定的网络安全威胁&#xff0c;其中较为危险的威胁就是勒索病毒攻击&#xff0c;勒索病毒不仅会给我们的计算机系统带来破坏&#xff0c;还会加密我们的重要文件数据来敲诈勒索&#xff0c;只有用户支付的赎…

提高可视性的五大方法可增强 Horizon Cloud 下一代平台的性能和用户体验

我们在 VMware Explore US 2022 推出了 VMware Horizon Cloud 下一代平台。该平台为使用现代化虚拟桌面和应用的客户提供了一个新的混合型桌面服务&#xff08;DaaS&#xff09;架构&#xff0c;其围绕降低成本和提高可扩展性而构建。首次发布后&#xff0c;我们在 VMware Expl…

gerrit 从安装到出坑

一般公司在做代码审核的时候选择codereview gerrit来处理代码的入库的问题。 它是通过提交的时候产生Change-Id: If4e0107f3bd7c5df9e2dc72ee4beb187b07151b9 来决定是不是入库&#xff0c;一般如果不是通过这个管理&#xff0c;那么就是我们通常的操作 git add . git comm…

【MySQL】MySQL HeatWave 介绍

HeatWave是一个分布式、可扩展、无共享、内存中、混合柱状的查询处理引擎&#xff0c;专为获得极致性能而设计。可以通过向MySQL数据库系统添加一个HeatWave集群来启用它。 HeatWave 是一种大规模并行、高性能内存查询加速器&#xff0c;可将分析工作负载、混合工作负载和机器…

Linux 学习记录55(ARM篇)

Linux 学习记录55(ARM篇) 本文目录 Linux 学习记录55(ARM篇)一、使用C语言封装GPIO函数1. 封装GPIO组寄存器2. 封装GPIO模式以及相关配置3. 封装GPIO初始化结构体4. 使用自己的封装配置GPIO 一、使用C语言封装GPIO函数 1. 封装GPIO组寄存器 #define GPIOA ((GP…

刷题日记09《图论基础》

图的存储结构 对于图结构而言&#xff0c;常见的存储结构主要有两种&#xff1a;邻接表和邻接矩阵&#xff1a; 邻接表很直观&#xff0c;我把每个节点 x 的邻居都存到一个列表里&#xff0c;然后把 x 和这个列表关联起来&#xff0c;这样就可以通过一个节点 x 找到它的所有相邻…

【算法与数据结构】222、LeetCode完全二叉树的节点个数

文章目录 一、题目二、一般遍历解法三、利用完全二叉树性质四、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、一般遍历解法 思路分析&#xff1a;利用层序遍历&#xff0c;然后用num记录节点数量。其他的例如…

视频增强技术-去噪

本文介绍了关于视频增强技术的相关方法包括传统方法和基于深度学习的方法&#xff0c;并给出了他们的对比实验结果&#xff0c;最后对它们简单的做了总结&#xff0c;文中有一些图片和总结来自于网上其他博主的文章&#xff0c;已在文中标记并给出了相关的原文链接&#xff0c;…

一文掌握如何前后端分离?

随着科技的进步和发展&#xff0c;低代码开发产品拥有广阔的市场前景。前后端分离似乎早已经是发展趋势了&#xff0c;因为做好前后端分离对于前后端的工程师而言是非常有利的&#xff0c;这样也有利于提升办公协作效率。那么&#xff0c;如何前后端分离&#xff1f;分别都有哪…

【机器学习】支持向量机SVM入门

优化目标 相较于之前学习的线性回归和神经网络&#xff0c;支持向量机&#xff08;Supprot Vector Machine&#xff0c;简称SVM&#xff09;在拟合复杂的非线性方程的时候拥有更出色的能力&#xff0c;该算法也是十分经典的算法之一。接下来我们需要学习这种算法 首先我们回顾…

关于Docker的基本概念和使用

关于Docker的基本概念和使用 一、Docker 概述1、Dcker的概念2、容器的优势3、Docker与虚拟机的区别4、容器在内核中支持2种重要技术5、Docker核心概念 二、安装 Docker1、安装依赖包2、设置阿里云镜像源3、安装 Docker-CE并设置为开机自动启动3、查看 docker 版本信息4、docker…

AI智能化技术对项目管理的挑战与应对︱腾讯CSIG能源行业总经理王磊

腾讯CSIG能源行业总经理王磊先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;AI智能化技术对项目管理的挑战与应对。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#xff1a; 自从DALLE、Midjourney、…