Canny算法原理和应用

news2024/11/17 8:32:41

Canny算法的原理

  • 使用高斯滤波器滤波

  • 使用 Sobel 滤波器滤波获得在 x 和 y 方向上的输出,在此基础上求出梯度的强度和梯度的角度

edge为边缘强度,tan为梯度方向

上图表示的是中心点的梯度向量、方位角以及边缘方向(任一点的边缘与梯度向量正交)
  • 对梯度角度进行量化处理

划重点:是沿着梯度方向对幅值进行非极大值抑制,而非边缘方向,这里初学者容易弄混。

例如:3*3区域内,边缘可以划分为垂直、水平、45°、135°4个方向,同样,梯度反向也为四个方向(与边缘方向正交)。因此为了进行非极大值,将所有可能的方向量化为4个方向,如下图:

量化后的情况可以总结为:

  • 根据梯度角度对边缘强度进行非极大值抑制(Non-maximum suppression),使图像边缘变得更细

非极大值抑制算法:0°时取(x,y)、(x+1,y)、(x-1,y) 中的最大值,其它角度类似

  • 使用滞后阈值对图像进行二值化处理,优化图像显示效果

  1. 选取系数TH和TL,比率为2:1或3:1。(一般取TH=0.3或0.2,TL=0.1);

b. 将小于低阈值的点抛弃,赋0;将大于高阈值的点立即标记(这些点为确定边缘点),赋1或255;

c. 将小于高阈值,大于低阈值的点使用8连通区域确定(即:只有与TH像素连接时才会被接受,成为边缘点,赋 1或255)

python算法实现

import cv2
import numpy as np
import matplotlib.pyplot as plt

def Canny(img):

    # Gray scale
    def BGR2GRAY(img):
        b = img[:, :, 0].copy()
        g = img[:, :, 1].copy()
        r = img[:, :, 2].copy()

        # Gray scale
        out = 0.2126 * r + 0.7152 * g + 0.0722 * b
        out = out.astype(np.uint8)

        return out


    # Gaussian filter for grayscale
    def gaussian_filter(img, K_size=3, sigma=1.4):

        if len(img.shape) == 3:
            H, W, C = img.shape
            gray = False
        else:
            img = np.expand_dims(img, axis=-1)
            H, W, C = img.shape
            gray = True

        ## Zero padding
        pad = K_size // 2
        out = np.zeros([H + pad * 2, W + pad * 2, C], dtype=np.float)
        out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)

        ## prepare Kernel
        K = np.zeros((K_size, K_size), dtype=np.float)
        for x in range(-pad, -pad + K_size):
            for y in range(-pad, -pad + K_size):
                K[y + pad, x + pad] = np.exp( - (x ** 2 + y ** 2) / (2 * sigma * sigma))
        #K /= (sigma * np.sqrt(2 * np.pi))
        K /= (2 * np.pi * sigma * sigma)
        K /= K.sum()

        tmp = out.copy()

        # filtering
        for y in range(H):
            for x in range(W):
                for c in range(C):
                    out[pad + y, pad + x, c] = np.sum(K * tmp[y : y + K_size, x : x + K_size, c])

        out = np.clip(out, 0, 255)
        out = out[pad : pad + H, pad : pad + W]
        out = out.astype(np.uint8)

        if gray:
            out = out[..., 0]

        return out


    # sobel filter
    def sobel_filter(img, K_size=3):
        if len(img.shape) == 3:
            H, W, C = img.shape
        else:
            H, W = img.shape

        # Zero padding
        pad = K_size // 2
        out = np.zeros((H + pad * 2, W + pad * 2), dtype=np.float)
        out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)
        tmp = out.copy()

        out_v = out.copy()
        out_h = out.copy()

        ## Sobel vertical
        Kv = [[1., 2., 1.],[0., 0., 0.], [-1., -2., -1.]]
        ## Sobel horizontal
        Kh = [[1., 0., -1.],[2., 0., -2.],[1., 0., -1.]]

        # filtering
        for y in range(H):
            for x in range(W):
                out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y : y + K_size, x : x + K_size]))
                out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y : y + K_size, x : x + K_size]))

        out_v = np.clip(out_v, 0, 255)
        out_h = np.clip(out_h, 0, 255)

        out_v = out_v[pad : pad + H, pad : pad + W]
        out_v = out_v.astype(np.uint8)
        out_h = out_h[pad : pad + H, pad : pad + W]
        out_h = out_h.astype(np.uint8)

        return out_v, out_h


    # get edge strength and edge angle
    def get_edge_angle(fx, fy):
        # get edge strength
        edge = np.sqrt(np.power(fx.astype(np.float32), 2) + np.power(fy.astype(np.float32), 2))
        edge = np.clip(edge, 0, 255)

        # make sure the denominator is not 0
        fx = np.maximum(fx, 1e-10)
        #fx[np.abs(fx) <= 1e-5] = 1e-5

        # get edge angle
        angle = np.arctan(fy / fx)

        return edge, angle


    # 将角度量化为0°、45°、90°、135°
    def angle_quantization(angle):
        angle = angle / np.pi * 180
        angle[angle < -22.5] = 180 + angle[angle < -22.5]
        _angle = np.zeros_like(angle, dtype=np.uint8)
        _angle[np.where((angle <= 22.5) | (angle > 157.5))] = 0
        _angle[np.where((angle > 22.5) & (angle <= 67.5))] = 45
        _angle[np.where((angle > 67.5) & (angle <= 112.5))] = 90
        _angle[np.where((angle > 112.5) & (angle <= 157.5))] = 135

        return _angle


    def non_maximum_suppression(angle, edge):
        H, W = angle.shape
        _edge = edge.copy()

        for y in range(H):
            for x in range(W):
                    if angle[y, x] == 0:
                            dx1, dy1, dx2, dy2 = -1, 0, 1, 0
                    elif angle[y, x] == 45:
                            dx1, dy1, dx2, dy2 = -1, 1, 1, -1
                    elif angle[y, x] == 90:
                            dx1, dy1, dx2, dy2 = 0, -1, 0, 1
                    elif angle[y, x] == 135:
                            dx1, dy1, dx2, dy2 = -1, -1, 1, 1
                    # 边界处理
                    if x == 0:
                            dx1 = max(dx1, 0)
                            dx2 = max(dx2, 0)
                    if x == W-1:
                            dx1 = min(dx1, 0)
                            dx2 = min(dx2, 0)
                    if y == 0:
                            dy1 = max(dy1, 0)
                            dy2 = max(dy2, 0)
                    if y == H-1:
                            dy1 = min(dy1, 0)
                            dy2 = min(dy2, 0)
                    # 如果不是最大值,则将这个位置像素值置为0
                    if max(max(edge[y, x], edge[y + dy1, x + dx1]), edge[y + dy2, x + dx2]) != edge[y, x]:
                            _edge[y, x] = 0

        return _edge


    # 滞后阈值处理二值化图像
    # > HT 的设为255,< LT 的设置0,介于它们两个中间的值,使用8邻域判断法
    def hysterisis(edge, HT=100, LT=30):
        H, W = edge.shape

        # Histeresis threshold
        edge[edge >= HT] = 255
        edge[edge <= LT] = 0

        _edge = np.zeros((H + 2, W + 2), dtype=np.float32)
        _edge[1 : H + 1, 1 : W + 1] = edge

        ## 8 - Nearest neighbor
        nn = np.array(((1., 1., 1.), (1., 0., 1.), (1., 1., 1.)), dtype=np.float32)

        for y in range(1, H+2):
                for x in range(1, W+2):
                        if _edge[y, x] < LT or _edge[y, x] > HT:
                                continue
                        if np.max(_edge[y-1:y+2, x-1:x+2] * nn) >= HT:
                                _edge[y, x] = 255
                        else:
                                _edge[y, x] = 0

        edge = _edge[1:H+1, 1:W+1]

        return edge

    # grayscale
    gray = BGR2GRAY(img)

    # gaussian filtering
    gaussian = gaussian_filter(gray, K_size=5, sigma=1.4)

    # sobel filtering
    fy, fx = sobel_filter(gaussian, K_size=3)

    # get edge strength, angle
    edge, angle = get_edge_angle(fx, fy)

    # angle quantization
    angle = angle_quantization(angle)

    # non maximum suppression
    edge = non_maximum_suppression(angle, edge)

    # hysterisis threshold
    out = hysterisis(edge, 80, 20)

    return out


if __name__ == '__main__':
    # Read image
    img = cv2.imread("../paojie.jpg").astype(np.float32)
    image = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.uint8)

    # Canny
    edge = Canny(img)

    out = edge.astype(np.uint8)

    # Save result
    cv2.imshow('src and canny', np.hstack((image, out)))
    cv2.waitKey(0)
    cv2.destroyAllWindows()

参考链接:

https://www.cnblogs.com/wojianxin/p/12533526.html

https://blog.csdn.net/weixin_40647819/article/details/91411424

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

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

相关文章

如何在MySQL 8中实现数据迁移?这里有一个简单易用的方案

文章目录前言一. 致敬IT领域的那些女性二. 进制方式安装MySQL2.1 下载软件包2.2 配置环境&#xff1a;2.2.1 配置yum环境2.2.2 配置安全前的系统环境2.3 开始安装2.4 初始化MySQL2.5 修改配置文件2.6 将MySQL设为服务并启动测试三. MySQL数据迁移总结前言 正好赶上IT女神节&am…

《Linux运维实战:ansible中的变量定义及以及变量的优先级》

一、配置文件优先级 Ansible配置以ini格式存储配置数据&#xff0c;在Ansible中⼏乎所有配置都可以通过Ansible的Playbook或环境变量来重新赋值。在运⾏Ansible命令时&#xff0c;命令将会按照以下顺序查找配置⽂件。 # ⾸先&#xff0c;Ansible命令会检查环境变量&#xff0c…

【node : 无法将“node”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。 最全面有效的解决方案】

执行nodejs文件错误&#xff1a; 这个错误提示通常是由于你的系统无法识别 "node" 命令&#xff0c;可能是由于你没有正确地安装或配置 Node.js 环境变量。 问题描述 ​​​​​​​​​​​​​​ 原因分析&#xff1a; 可能原因包括&#xff1a; 1.Node.js未正确安…

JVM堆与堆调优以及出现OOM如何排查

调优的位置——堆 Heap&#xff0c;一个JVM只有一个堆内存&#xff0c;堆内存的大小是可以调节的。 类加载器读取了类文件后&#xff0c;一般会把什么东西放到堆中?类&#xff0c;方法&#xff0c;常量&#xff0c;变量~&#xff0c;保存我们所有引用类型的真实对象; 堆内存中…

【Linux修炼】15.进程间通信

每一个不曾起舞的日子&#xff0c;都是对生命的辜负。 进程间通信进程间通信一.理解进程间通信1.1 什么是通信1.2 为什么要有通信1.3 如何进行进程间通信二.管道2.1 匿名管道2.2 匿名管道编码部分2.3 管道的特点2.4 如何理解命令行中的管道2.5 进程控制多个子进程三.命名管道3.…

openEuler用户软件仓(EUR)介绍

什么是 EUR EUR(openEuler User Repo)是openEuler社区针对开发者推出的个人软件包托管平台&#xff0c;目的在于为开发者提供一个易用的软件包分发平台。 链接&#xff1a;https://eur.openeuler.openatom.cn/ 为什么我们需要 EUR 在操作系统的世界&#xff0c;软件包是一等…

数据库基本功之复杂查询-多表连接

1. 简单查询的解析方法 全表扫描:指针从第一条记录开始,依次逐行处理,直到最后一条记录结束;横向选择纵向投影结果集 2. 多表连接 交叉连接(笛卡尔积) 非等值连接 等值连接 内连 外连接(内连的扩展,左外,右外,全连接) 自连接 自然连接(内连,隐含连接条件,自动匹配连接字段) …

以创作之名致敬女性开发者

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 在昨天的2023年3月8日&#xff0c;是咱们女性朋友的节日妇女节&#xff0c;本章将会…

腾讯云GPU游戏服务器/云主机租用配置价格表

用于游戏业务的服务器和普通云服务器和主机空间是不同的&#xff0c;游戏服务器对于硬件的配置、网络带宽有更大的要求&#xff0c;一般游戏服务器根据不同的配置和适用场景会有十几元一小时到几十元一小时&#xff0c;而且可以根据不同的按量计费。而普通的云服务器可能需要几…

Linux程序替换

Linux程序替换创建子进程的目的&#xff1f;程序替换如何实现程序替换&#xff1f;什么是程序替换&#xff1f;先见一见单进程版本的程序替换程序替换原理多进程版本的程序替换execl函数组简易版Shell创建子进程的目的&#xff1f; 目的:为了帮助父进程完成一些特定的任务&…

网络实时变更监控

网络变更监控 未经授权的配置变更会严重破坏业务连续性&#xff0c;这就是为什么检测和跟踪变更是网络管理员的一项关键任务。虽然可以手动跟踪变更&#xff0c;但此方法往往很耗时&#xff0c;并且经常会导致人为错误&#xff0c;例如在跟踪时遗漏了关键网络设备的配置。 要解…

JavaEE简单示例——Spring的控制反转

简单介绍&#xff1a; 在之前的入门程序中&#xff0c;我们简单的介绍了关于Spring框架中的控制反转的概念&#xff0c;这次我们就来详细的介绍和体验一下Spring中的控制反转的理论和实操。 使用方法&#xff1a; 控制反转&#xff08;IoC&#xff09;是面向对象编程中的一个…

HTML5 和 CSS3 的新特性

目标能够说出 3~5 个 HTML5 新增布局和表单标签能够说出 CSS3 的新增特性有哪些HTML5新特性概述HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。 这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&…

CentOS 部署rocketmq集群

一、rocketmq 概览 1.rocketmq简介 rocketmq是一个队列模型的消息中间件&#xff0c;具有高性能、高可靠、高实时、分布式特点。能够保证严格的消息顺序&#xff0c;提供丰富的消息拉取模式&#xff0c;高效的订阅者水平扩展能力&#xff0c;实时的消息订阅机制。 2.rocketmq结…

项目四:使用路由交换机构建园区网-任务三:配置路由交换机并进行通信测试

配置路由交换机并通信测试1、在RS-1上创建VLAN并配置Trunk接口2、测试通信结果3、配置RS-1的三层路由接口&#xff08;SVI&#xff09;1、在RS-1上创建VLAN并配置Trunk接口 进入系统视图&#xff0c;关闭信息中心&#xff0c;重命名为RS-1 system-view undo info-center enab…

day48第九章动态规划(二刷)

今日任务 198.打家劫舍213.打家劫舍II337.打家劫舍III 今天就是打家劫舍的一天&#xff0c;这个系列不算难&#xff0c;大家可以一口气拿下。 198.打家劫舍 题目链接&#xff1a; https://leetcode.cn/problems/house-robber/description/ 题目描述&#xff1a; 你是一个…

synchronized轻量级锁优化

synchronized优化轻量级锁 使用场景 如果一个对象虽然有多个线程访问&#xff0c;但多线程访问时间是错开的&#xff0c;也就是没有竞争&#xff0c;那么可以使用轻量级锁优化&#xff1b; 原理 1、每个线程的栈帧中有锁记录 包括&#xff1a;记录锁对象的地址Object refer…

一次有效沟通的组成

犹记得之前看过一篇文章当中写道 『Tech Lead 作为开发团队的技术负责人&#xff0c;对内对外都起到至关重要的作用。Tech Lead 对外是团队技术能力的展现窗口&#xff0c;需要将团队的技术能力呈现给客户或业务团队。对内他需要和各个角色紧密协作&#xff0c;给非技术角色技术…

50年前的程序员女神,用代码把人类送上月球

1969年7月20日&#xff0c;经过4天飞行的阿波罗11号终于来到了最关键的时刻&#xff1a;降落到月球表面。就在这个时候&#xff0c;飞船计算机突然开始闪烁报警。全球6亿人在观看电视直播&#xff0c;NASA飞行控制中心的气氛让人窒息。人类的首次登月计划面临着艰难的抉择&…

d3绘图笔记

D3绘图笔记 安装与引用 npm install d3 --save-dev import * as d3 from d3; 选择器 d3.select() 可直接接元素名&#xff0c;也可以接属性与类 添加svg标签 this.d3 d3.select(.myd3) let svg this.d3.append("svg") // 添加svg并设置好高宽 .attr("wid…