图像处理之图像边缘检测算法
目录
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模板算子如下:
其中表示水平方向的卷积模板,
表示垂直方向的卷积模板。
对于图像中任何一点的像素P(i,j),使用水平和垂直卷积模板分别对图像进行卷积操作,得到水平梯度GX和垂直梯度GY,则梯度幅度计算如下:
完整梯度幅度计算公式如下:

Sobel边缘检测结果计算如下
其中,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)高斯滤波平滑处理
由于图像中经常包含一些高斯噪声,因此,在边缘检测前,要先用高斯滤波器对其进行滤波。为了方便,这里使用如下高斯滤波器模板
(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;}elsetemp = 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;}elsesrcBytes[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;//graygrayOneChannel(srcData, grayData, width, height, stride);//gauss fiterfor (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;}}}//gradientfloat* 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 judgementTwoThreshouldJudge(grayData, width, height, highThreshold, lowThreshold);//recoveryfor (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
相关文章:
图像处理之图像边缘检测算法
目录 1 图像边缘检测算法简介 2 Sobel边缘检测 3 经典的Canny边缘检测算法 4 演示Demo 4.1 开发环境 4.2 功能介绍 4.3 下载地址 参考 1 图像边缘检测算法简介 图像边缘检测是计算机视觉和图像处理中的基本问题,主要目的是提取图像中明暗变化明显的边缘细节…...
第二十五 :搭建 pinia 环境
第一步:npm install pinia 第二步:操作src/main.ts import { createApp } from vue import App from ./App.vue /* 引入createPinia,用于创建pinia */ import { createPinia } from pinia /* 创建pinia */ const pinia createPinia(…...
学习Java数组操作:从基础到高级技巧详解
在Java编程中,数组是一种非常基础且常用的非 primitives 数据结构,它用于存储一组相同类型的值。无论是数据处理、遍历还是其他操作,数组都是一个不可或缺的工具。本文将从数组的基本概念开始,逐步介绍常用的操作方法,…...
算法题(79):两个数组的交集
审题: 本题需要我们查找两个给定数组的无重复数据交集,并以数组的形式返回 思路: 方法一:set 之前我们学习过unordered_set的使用,但是unordered_set是无序的,而这里我们的比对算法需要有序数据,…...
TFChat:腾讯大模型知识引擎+飞书机器人实现AI智能助手
效果 TFChat项目地址 https://github.com/fish2018/TFChat 腾讯大模型知识引擎用的是DeepSeek R1,项目为sanic和redis实现,利用httpx异步处理流式响应,同时使用buffer来避免频繁调用飞书接口更新卡片的网络耗时。为了进一步减少网络IO消耗&…...
Linux红帽:RHCSA认证知识讲解(四)修改远程配置文件,取消root禁用,便于使用root身份远程
Linux红帽:RHCSA认证知识讲解(四)修改远程配置文件,取消root禁用,便于使用root身份远程 前言一、远程连接的用途和原因二、通过 ssh 远程登陆系统三、默认限制及解决方案(一)非常规方法一&#…...
验证码介绍及生成与验证(HTML + JavaScript实现)
验证码介绍及生成与验证(HTML JavaScript实现) 验证码 验证码(全自动区分计算机和人类的图灵测试,CAPTCHA ,Completely Automated Public Turing test to tell Computers and Humans A…...
文心一言AI创意画
介绍 文心一言是百度推出的新一代知识增强大语言模型,属于文心大模型家族的新成员。它能够与人对话互动、回答问题、协助创作,高效便捷地帮助人们获取信息、知识和灵感。 特点 文心一言基于数万亿数据和数千亿知识进行融合学习,采用预训…...
WebRTC解析
一、WebRTC 协议概述 WebRTC(Web Real-Time Communication)是由 Google 发起并成为 W3C 标准的实时音视频通信技术,核心特点: 零插件:浏览器原生支持端到端加密(SRTP DTLS)P2P 优先架构&…...
升级Office软件后,Windows 系统右键里没有新建Word、Excel、PowerPoint文件的解决办法
我办公用的电脑,Office 2013 已经用了好多年,最近突发奇想给升级到了 Ofiice 2024。升级过程还蛮顺利的,但是安装完成后,发现点右键里没有新建Word、Excel、PowerPoint,开始菜单里 Word、Excel、PowerPoint 使用都正常…...
车载DoIP诊断框架 --- 连接 DoIP ECU/车辆的故障排除
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…...
洛谷每日1题-------Day4__陶陶摘苹果
# P1046 [NOIP 2005 普及组] 陶陶摘苹果 ## 题目描述 陶陶家的院子里有一棵苹果树,每到秋天树上就会结出 $10$ 个苹果。苹果成熟的时候,陶陶就会跑去摘苹果。陶陶有个 $30$ 厘米高的板凳,当她不能直接用手摘到苹果的时候,就会踩…...
萌新学 Python 之模块管理
模块就是一个 python 代码文件,模块可以包含函数、类,可以提高代码复用率提高效率 python 模块主要分为三种: 1.内置模块:python 自带的模块,导入后可以直接使用,比如 import 模块名 2.第三方模块&#…...
6.3 - UART串口数据发送之中断
文章目录 1 实验任务2 系统框图3 软件设计 1 实验任务 本实验使用中断方式实现UART串口数据的连续发送。 2 系统框图 参见6.1。 3 软件设计 注意事项: 系统上电、程序下载后,此时TX FIFO虽然为空,但并不会触发空中断;空中断…...
Fisher信息矩阵(Fisher Information Matrix, FIM)与自然梯度下降:机器学习中的优化利器
Fisher信息矩阵与自然梯度下降:机器学习中的优化利器 在机器学习尤其是深度学习中,优化模型参数是一个核心任务。我们通常依赖梯度下降(Gradient Descent)来调整参数,但普通的梯度下降有时会显得“笨拙”,…...
Mysql基础-多表查询(详细版)
目录 一、表的关系类型与适用场景二、连接方式与使用场景三、易错点与注意事项四、总结 一、表的关系类型与适用场景 1. 一对一关系 场景:一个表的记录对应另一个表的唯一记录 案例:用户表 用户详情表 CREATE TABLE users (id INT PRIMARY KEY,name…...
港科大提出开放全曲音乐生成基础模型YuE:可将歌词转换成完整歌曲
YuE是港科大提出的一个开源的音乐生成基础模型,专为音乐生成而设计,专门用于将歌词转换成完整的歌曲(lyrics2song)。它可以生成一首完整的歌曲,时长几分钟,包括朗朗上口的声乐曲目和伴奏曲目。YuE 能够模拟…...
Python学习第十七天之PyTorch保姆级安装
PyTorch安装与部署 一、准备工作二、pytorch介绍三、CPU版本pytorch安装1. 创建虚拟环境2. 删除虚拟环境1. 通过环境名称删除2. 通过环境路径删除 3. 配置镜像源4. 安装pytorch1. 首先激活环境变量2. 进入pytorch官网,找到安装指令 5. 验证pytorch是否安装成功 四、…...
有关与 WSL 2 的主要区别的信息,请访问 https://aka.ms/wsl2
https://learn.microsoft.com/zh-cn/windows/wsl/install-manual#step-4—download-the-linux-kernel-update-package...
什么是 Java 中的线程安全?
回答 Java 中的线程安全(Thread Safety)指的是在多线程环境下,当多个线程同时访问和操作共享资源(如对象、变量、数据结构等)时,能够保证程序的正确性,不会出现数据不一致、竞争条件࿰…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
