图像处理之图像边缘检测算法

目录

1 图像边缘检测算法简介

2 Sobel边缘检测

3 经典的Canny边缘检测算法

4 演示Demo

4.1 开发环境

4.2 功能介绍

4.3 下载地址

参考


1 图像边缘检测算法简介

        图像边缘检测是计算机视觉和图像处理中的基本问题,主要目的是提取图像中明暗变化明显的边缘细节信息。

        图像边缘检测算法有很多,包括传统的模板算子(Sobel、Roberts、Prewitt、Laplace)、形态学边缘检测、经典的Canny边缘检测及基于深度学习的边缘检测算法等。

        本文主要介绍Sobel模板算子和经典的Canny边缘检测算法。

2 Sobel边缘检测

        Sobel模板算子是 Irwin Sobel 在1968年发表的论文 An Isotropic 3x3 Image Gradient Operator 中提出的一种一阶导数模板算子,用于计算图像灰度函数的近似梯度。

        Sobel模板算子如下:

G_x = \begin{pmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{pmatrix} G_y = \begin{pmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{pmatrix}

        其中G_x表示水平方向的卷积模板,G_y表示垂直方向的卷积模板。

        对于图像中任何一点的像素P(i,j),使用水平和垂直卷积模板分别对图像进行卷积操作,得到水平梯度GX和垂直梯度GY,则梯度幅度计算如下:

G=\sqrt{(GX)^2 + (GY)^2}

        完整梯度幅度计算公式如下:

        Sobel边缘检测结果计算如下

Edge(i,j) = \begin{cases} 255 &\text{if } G(i,j)>Threshold \\ 0 &\text{if } others \end{cases}

其中,255白色表示边缘,0黑色表示背景。

        C语言实现Sobel边缘检测算法代码如下:

/*************************************************
功    能:图像Sobel边缘检测
参    数:srcData     -   [输入/输出] 原始图像,格式为32位BGRA格式,执行后修为结果图像
          width       -   [输入] 原始图像宽度
          height      -   [输入] 原始图像高度
          stride      -   [输入] 原始图像的Stride(也就是行字节数width*4)
          threshold   -   [输入] 阈值
返    回: 0-成功,其他-失败.
*************************************************/
int sobel(unsigned char *srcData, int width, int height, int stride, int threshold)
{
        int ret = 0;
        unsigned char *dstData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride);
        memset(dstData, 255, sizeof(unsigned char) * height * stride);
        int x, y, i, k, pos;
        int hValue, vValue, value;
        unsigned char *pSrcL0;
        unsigned char *pSrcL1;
        unsigned char *pSrcL2;
        unsigned char *pDstL;
        unsigned char SqrtValue[65026];
        pSrcL0 = srcData;
        pSrcL1 = srcData + stride;
        pSrcL2 = srcData + stride * 2;
        pDstL = dstData + stride;
        for (i = 0; i < 65026; i++)
        {
                SqrtValue[i] = (unsigned char)(sqrt((float)i) < threshold ? 0 : 255);
        }
        for (y = 1; y < height - 1; y++)
        {
                for (x = 1; x < width - 1; x++)
                {
                        pos = x * 4;
                        hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
                        vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
                        k = hValue * hValue + vValue * vValue;
                        k = MIN2(k, 65025);
                        pDstL[pos] = SqrtValue[k];
                        pos++;
                        hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
                        vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
                        k = hValue * hValue + vValue * vValue;
                        k = MIN2(k, 65025);
                        pDstL[pos] = SqrtValue[k];
                        pos++;
                        hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
                        vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
                        k = hValue * hValue + vValue * vValue;
                        k = MIN2(k, 65025);
                        pDstL[pos] = SqrtValue[k];
                }
                pSrcL0 += stride;
                pSrcL1 += stride;
                pSrcL2 += stride;
                pDstL += stride;
        }
        memcpy(srcData, dstData, sizeof(unsigned char) * height * stride);
        free(dstData);
        return ret;
}

3 经典的Canny边缘检测算法

        Canny边缘检测是 John Canny 在1986年首次提出的一种改进的边缘检测方法。该方法主要通过图像信号函数的极大值来判断图像的边缘像素点,与基本的Sobel模板算子等相比,其具有低错误率、高定位性等优点,因此被广泛应用。

        (1)高斯滤波平滑处理

        由于图像中经常包含一些高斯噪声,因此,在边缘检测前,要先用高斯滤波器对其进行滤波。为了方便,这里使用如下高斯滤波器模板

\frac{1}{16}\begin{pmatrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{pmatrix}

        (2)梯度计算

        使用一阶导数算子(一般用Sobel模板算子)计算灰度图像中每个像素点在水平和垂直方向上的导数GX、GY,得出梯度向量(GX,GY),最后得到该像素点的梯度幅度G和相位角D

        (3)非极大值抑制

        对于上面计算得到的梯度值,其实是一个粗边缘信息,可以通过非极大值抑制去掉一些非边缘信息。这里将当前像素的梯度值与其在梯度方向上的邻域像素的梯度值做对比,如果当前像素的梯度值为最大值,则保留该点的梯度信息,否则将该点删除或将像素值置为9。

        (4)双阈值边缘检测和边缘连接

        由非极大值抑制得到的边缘信息中包含较多伪边缘信息,可通过设置高低双阈值的方法去除它们。

        首先,设定两个阈值,一个高阈值,一个低阈值,阈值大小根据实际情况设置,一般高阈值为低阈值的2.5倍。

        然后判断:梯度值大于高阈值的像素点一定是边缘点,将该点像素值置为255;梯度值小于低阈值的像素点一定不是边缘点,将该点像素值置为0;介于高低阈值之间的像素点为准边缘点,对于这些点,如果其像素点周围8邻域的梯度值都小于高阈值,则认为其不是边缘点,将该点像素值置为0,否则置为255。

        C语言实现Cannyl边缘检测算法代码如下:

/*************************************************
功    能:图像Canny边缘检测
参    数:srcData         -  [输入/输出] 原始图像,格式为32位BGRA格式,执行后修为结果图像
          width          -  [输入] 原始图像宽度
          height         -  [输入] 原始图像高度
          stride         -  [输入] 原始图像的Stride(也就是行字节数width*4)
          highThreshold  -  [输入] 高阈值,范围为[0,255]
          lowThreshold   -  [输入] 低阈值,范围为[0,255],默认值为0.4*highThreshold
返    回: 0-成功,其他-失败.
*************************************************/

//单通道灰度化
static int grayOneChannel(unsigned char* srcData, unsigned char* grayData, int width, int height, int stride)
{
        int ret = 0;
        int i, j, gray, offset;
        offset = stride - (width * 4);
        unsigned char* pSrc = srcData;
        unsigned char* pGray = grayData;
        for (j = 0; j < height; j++)
        {
                for (i = 0; i < width; i++)
                {
                        gray = (pSrc[2] + pSrc[1] + pSrc[0]) / 3;
                        *pGray = gray;
                        pSrc += 4;
                        pGray++;
                }
                pSrc += offset;
        }
        return ret;
};

//梯度相位角获取  
static void GetGradientDegree(unsigned char* srcBytes, int width, int height, float gradient[], unsigned char degree[], float* GradientMax)
{
        float gx, gy;
        int temp, pos;
        float div;
        float PI = 3.1415926f;
        float t = 180.0f / PI;
        for (int j = 1; j < height - 1; j++)
        {
                for (int i = 1; i < width - 1; i++)
                {
                        pos = i + j * width;
                        gx = srcBytes[pos + 1 - width] + srcBytes[pos + 1] + srcBytes[pos + 1] + srcBytes[pos + 1 + width] - srcBytes[pos - 1 - width] - (srcBytes[pos - 1] + srcBytes[pos - 1]) - srcBytes[pos - 1 + width];
                        gy = srcBytes[pos - 1 - width] + srcBytes[pos - width] + srcBytes[pos - width] + srcBytes[pos + 1 - width] - srcBytes[pos - 1 + width] - (srcBytes[pos + width] + srcBytes[pos + width]) - srcBytes[pos + 1 + width];
                        gradient[pos] = (float)sqrt((float)(gx * gx + gy * gy));
                        if (*GradientMax < gradient[pos])
                        {
                                *GradientMax = gradient[pos];
                        }
                        if (gx == 0)
                        {
                                temp = (gy == 0) ? 0 : 90;
                        }
                        else
                        {
                                div = gy / gx;
                                if (div < 0)
                                {
                                        temp = (int)(180 - atan(-div) * t);
                                }
                                else
                                {
                                        temp = (int)(atan(div) * t);
                                }
                                if (temp < 22.5f)
                                {
                                        temp = 0;
                                }
                                else if (temp < 67.5f)
                                {
                                        temp = 45;
                                }
                                else if (temp < 112.5f)
                                {
                                        temp = 90;
                                }
                                else if (temp < 157.5f)
                                {
                                        temp = 135;
                                }
                                else
                                        temp = 0;
                        }
                        degree[pos] = temp;
                }
        }
};

//非极大值抑制  
static void NonMaxMini(unsigned char* srcBytes, int width, int height, float gradient[], float GradientMax, unsigned char degree[])
{
        float leftPixel = 0, rightPixel = 0;
        int pos;
        for (int j = 1; j < height - 1; j++)
        {
                for (int i = 1; i < width - 1; i++)
                {
                        pos = i + j * width;
                        switch (degree[pos])
                        {
                        case 0:
                                leftPixel = gradient[pos - 1];
                                rightPixel = gradient[pos + 1];
                                break;
                        case 45:
                                leftPixel = gradient[pos - 1 + width];
                                rightPixel = gradient[pos + 1 - width];
                                break;
                        case 90:
                                leftPixel = gradient[pos + width];
                                rightPixel = gradient[pos - width];
                                break;
                        case 135:
                                leftPixel = gradient[pos + 1 + width];
                                rightPixel = gradient[pos - 1 - width];
                                break;
                        default:
                                break;
                        }
                        if ((gradient[pos] < leftPixel) || (gradient[pos] < rightPixel))
                        {
                                srcBytes[pos] = 0;
                        }
                        else
                        {
                                srcBytes[pos] = (int)(255.0f * gradient[pos] / GradientMax);
                        }
                }
        }
};

//双阈值边缘判断  
static void TwoThreshouldJudge(unsigned char* srcBytes, int width, int height, int highThreshold, int lowThreshould)
{
        int pos = 0;
        for (int j = 1; j < height - 1; j++)
        {
                for (int i = 1; i < width - 1; i++)
                {
                        pos = i + j * width;
                        if (srcBytes[pos] > highThreshold)
                        {
                                srcBytes[pos] = 255;
                        }
                        else if (srcBytes[pos] < lowThreshould)
                        {
                                srcBytes[pos] = 0;
                        }
                        else
                        {
                                if (srcBytes[pos - 1 - width] < highThreshold && srcBytes[pos - width] < highThreshold && srcBytes[pos + 1 - width] < highThreshold && srcBytes[pos - 1] < highThreshold
                                        && srcBytes[pos + 1] < highThreshold && srcBytes[pos - 1 + width] < highThreshold && srcBytes[pos + width] < highThreshold && srcBytes[pos + 1 + width] < highThreshold)
                                {
                                        srcBytes[pos] = 0;
                                }
                                else
                                        srcBytes[pos] = 255;
                        }
                }
        }
};

int cannyEdgedetection(unsigned char* srcData, int width, int height, int stride, int highThreshold, int lowThreshold)
{
        int ret = 0;
        int i, j, offset, pos, temp, size;
        unsigned char* pSrc = srcData;
        size = width * height;
        unsigned char* grayData = (unsigned char*)malloc(sizeof(unsigned char) * size);
        memset(grayData, 0, sizeof(unsigned char) * size);
        offset = stride - width * 4;
        //gray
        grayOneChannel(srcData, grayData, width, height, stride);
        //gauss fiter
        for (j = 0; j < height; j++)
        {
                for (i = 0; i < width; i++)
                {
                        pos = i + j * width;
                        if (i == 0 || j == 0 || i == width - 1 || j == height - 1)
                        {
                                grayData[pos] = 0;
                        }
                        else
                        {
                                temp = ((grayData[pos] << 2) + grayData[pos - width - 1] + grayData[pos + 1 - width] + grayData[pos - 1 + width] + grayData[pos + 1 + width] + grayData[pos - width] + grayData[pos - width] + grayData[pos - 1] + grayData[pos - 1] + grayData[pos + width] + grayData[pos + width] + grayData[pos + 1] + grayData[pos + 1]) >> 4;
                                grayData[pos] = temp;
                        }
                }
        }
        //gradient
        float* gradient = (float*)malloc(sizeof(float) * size);
        memset(gradient, 0, sizeof(float) * size);
        unsigned char* degree = (unsigned char*)malloc(sizeof(unsigned char) * size);
        memset(degree, 0, sizeof(unsigned char) * size);
        float GradientMax = 0;
        GetGradientDegree(grayData, width, height, gradient, degree, &GradientMax);
        //none max value 
        NonMaxMini(grayData, width, height, gradient, GradientMax, degree);
        //two threshold judgement
        TwoThreshouldJudge(grayData, width, height, highThreshold, lowThreshold);
        //recovery
        for (j = 0; j < height; j++)
        {
                for (i = 0; i < width; i++)
                {
                        pSrc[0] = pSrc[1] = pSrc[2] = grayData[i + j * width];
                        pSrc += 4;
                }
                pSrc += offset;
        }
        free(grayData);
        free(gradient);
        free(degree);
        return ret;
};

4 演示Demo

4.1 开发环境

  • Windows 10 Pro x64

  • Visual Studio 2015

4.2 功能介绍

        演示程序主界面如下图所示,具有图像读取、显示、保存、显示RGBA值、HSV调整、提取YUV分量、灰度化、二值化、直方图、亮度/对比度调整、饱和度调整、均值滤波、高斯滤波、拉普拉斯锐化、USM锐化、Sobel边缘检测、Canny边缘检测等功能。

原图

Sobel边缘检测(阈值为80 )效果图

Canny边缘检测(阈值为 8)效果图

4.3 下载地址

        开发环境:

  • Windows 10 pro x64

  • Visual Studio 2015

        下载地址:图像处理之图像边缘检测算法Demo

参考

        图像视频滤镜与人像美颜美妆算法详解. 胡耀武、谭娟、李云夕. 电子工业出版社、2020-07

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构(初阶)(八)----排序

排序 概念 排序&#xff1a;所谓排序&#xff0c;就是使⼀串记录&#xff0c;按照其中的某个或某些关键字的⼤⼩&#xff0c;递增或递减的排列起来的 操作。 比较排序 插入排序 直接插入排序 直接插⼊排序是⼀种简单的插⼊排序法&#xff0c;其基本思想是&#xff1a;把待…

计算机毕业设计SpringBoot+Vue.js基于JAVA语言的在线考试与学习交流网页平台(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

聊一聊 IM 如何优化数据库

IM 系列 im doc 实时通讯文档仓库 聊一聊 IM 是什么&#xff1f; IM 即时通讯系统概览 聊一聊 IM 要如何设计&#xff1f; 聊一聊 IM 要如何设计功能模块&#xff1f; 聊一聊 IM 要如何进行架构设计&#xff1f; 聊一聊 IM 要如何进行技术选型&#xff1f; 聊一聊 IM 要…

人工智能AI在汽车设计领域的应用探索

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

DeepSeek-R1 大模型实战:腾讯云 HAI 平台 3 分钟极速部署指南

引言&#xff1a;为什么选择 DeepSeek-R1&#xff1f; 近期&#xff0c;国产大模型 DeepSeek-R1 因其低成本、高性能的特点在全球 AI 领域引发热议。根据 Sensor Tower 数据&#xff0c;其发布仅 18 天便斩获 1600 万次下载量&#xff0c;远超 ChatGPT 同期表现。而腾讯云推出…

[SWPUCTF 2022 新生赛]1z_unserialize

题目描述&#xff1a;是很简单的反序列化噢 代码审计看注释 <?phpclass lyh{ //定义一个类为lyhpublic $url NSSCTF.com;//公共属性&#xff0c;初始值为NSSCTF.compublic $lt; //公共属性&#xff0c;没有初始值public $lly; //公共属性&…

三支一扶入职体检不合格项目全解析

“三支一扶” 计划为高校毕业生提供了到基层服务的宝贵机会&#xff0c;通过层层选拔后&#xff0c;入职体检也是其中关键的一环。了解哪些项目可能导致体检不合格&#xff0c;能让大家提前做好准备&#xff0c;避免在这一步出现意外。接下来&#xff0c;就为大家详细介绍三支一…

专题一四数之和

1.题目 题目分析&#xff1a; 给一个数组&#xff0c;在里面找到四个数字&#xff0c;满足四个数字之和等于给的特定值&#xff0c;四数之和可以拆分成三数之和&#xff0c;再继续拆分成二数之和&#xff0c;由简化繁。 2.算法原理 通过排序加双指针 1.依次固定一个数 2.在…

如何在docker中的mysql容器内执行命令与执行SQL文件

通过 docker ps -a 查询当前运行的容器&#xff0c;找到想执行命令的容器名称。 docker ps -a若想执行sql文件&#xff0c;则将sql文件放入当前文件夹下后将项目内的 SQL 文件拷贝到 mysql 容器内部的 root下。 sudo docker cp /root/enterprise.sql mysql:/root/然后进入 my…

Linux线程同步与互斥应用/生产者消费者模型

一&#xff0c;理论讲解 我们拿工厂&#xff0c;超市和消费者直接的关系来做讲解&#xff0c;首先人去超市买东西的过程就不用多说&#xff0c;但是超市本身是不能生产商品的&#xff0c;他们需要从各个不同的工厂进货商品&#xff0c;然后再给消费者买&#xff0c;以计算机的…

基于YOLO11深度学习的遥感视角农田检测与分割系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标分割、人工智能

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

RabbitMQ面试题及原理

RabbitMQ使用场景&#xff1a; 异步发送&#xff08;验证码、短信、邮件…&#xff09;MYSQL和Redis, ES之间的数据同步分布式事务削峰填谷 1. 消息可靠性&#xff08;不丢失&#xff09; 消息丢失场景&#xff1a; RabbitMQ-如何保证消息不丟失&#xff1f; 开启生产者确…

Python每日一练:学习指南进行汇总

Python&#xff0c;一种“优雅”、“明确”、“简单”的编程语言&#xff0c;凭借其低学习曲线、强大的开源生态系统、卓越的平台可移植性以及面向对象和函数式编程的支持&#xff0c;成为了众多开发者首选。 01 Python 应用领域和就业形势分析 Python&#xff0c;一种“优雅…

商米科技前端工程师(base上海)内推

1.根据原型或高保真设计&#xff0c;开发web、H5、小程序等类型的前端应用&#xff1b; 2.在指导下&#xff0c;高质量完成功能模块的开发&#xff0c;并负责各功能模块接口设计工作&#xff1b; 3.负责产品及相关支撑系统的开发及维护工作&#xff0c;不断的优化升级&#x…

八. Spring Boot2 整合连接 Redis(超详细剖析)

八. Spring Boot2 整合连接 Redis(超详细剖析) 文章目录 八. Spring Boot2 整合连接 Redis(超详细剖析)2. 注意事项和细节3. 最后&#xff1a; 在 springboot 中 , 整合 redis 可以通过 RedisTemplate 完成对 redis 的操作, 包括设置数据/获取数据 比如添加和读取数据 具体…

【漫话机器学习系列】113.逻辑回归(Logistic Regression) VS 线性回归(Linear Regression)

逻辑回归 vs 线性回归&#xff1a;详解对比 在机器学习和统计学中&#xff0c;逻辑回归&#xff08;Logistic Regression&#xff09; 和 线性回归&#xff08;Linear Regression&#xff09; 都是非常常见的模型。尽管它们的数学表达式有一定的相似性&#xff0c;但它们的应用…

构建智能 SQL 查询代理agent,把整个查询过程模块化,既能自动判断使用哪些表,又能自动生成 SQL 语句,最终返回查询结果

示例代码&#xff1a; import os import getpass from dotenv import load_dotenv from pyprojroot import here from typing import List from pprint import pprint from pydantic import BaseModel from langchain_core.tools import tool from langchain_core.runnables i…

fastapi中的patch请求

目录 示例测试使用 curl 访问&#xff1a;使用 requests 访问&#xff1a;预期返回&#xff1a; 浏览器访问 示例 下面是一个使用 app.patch("") 的 FastAPI 示例&#xff0c;该示例实现了一个简单的用户信息更新 API。我们使用 pydantic 定义数据模型&#xff0c;并…

【文献阅读】Collective Decision for Open Set Recognition

基本信息 文献名称&#xff1a;Collective Decision for Open Set Recognition 出版期刊&#xff1a;IEEE TRANSACTIONS ON KNOWLEDGE AND DATA ENGINEERING 发表日期&#xff1a;04 March 2020 作者&#xff1a;Chuanxing Geng and Songcan Chen 摘要 在开集识别&#xff0…

Hadoop之02:MR-图解

1、不是所有的MR都适合combine 1.1、map端统计出了不同班级的每个学生的年龄 如&#xff1a;(class1, 14)表示class1班的一个学生的年龄是14岁。 第一个map任务&#xff1a; class1 14 class1 15 class1 16 class2 10第二个map任务&#xff1a; class1 16 class2 10 class…