C++实现Canny边缘检测(原理+底层代码)

news2024/11/27 20:24:28

文章目录

  • 一、算法原理
  • 二、环境配置
  • 三、算法详解
    • 3.1、数据结构 Mat
    • 3.2、高斯滤波器的C++实现
    • 3.3、用一阶偏导有限差分计算梯度幅值和方向
  • 三、项目实战:C++实现Canny边缘检测

一、算法原理

canny边缘检测算法步骤:
1、使用高斯滤波器对图像进行平滑处理
2、利用一阶偏导算子找到灰度图像沿着水平方向Gx和垂直方向Gy的偏导数,并计算梯度的幅值和方向
求幅值公式:

3、对梯度幅值进行NMS非极大值抑制,获取局部梯度的最大值。在3X3窗口中,将给定像素P与沿着梯度线方向的两个像素进行比较,若P的梯度幅值小于该两个像素的梯度幅值,则令P=0;否则保留原幅值。
备注:将梯度方向分为4种来比较梯度幅值的强度:水平方向、垂直方向、正方向、-45°方向。==
4、用双阈值算法检测和边缘连接。分三种情况:
(1)若像素值大于高阈值,则该像素一定是边缘像素(强边缘点),置为255;
(2)若小于低阈值,则一定不是,置为0;
(3)若像素值大于低阈值但小于高阈值,则观察该像素的(3X3)8邻域像素中是否有大于高阈值的像素点,若有则该像素是边缘像素,并将该点置为255,用以连接强边缘点;否则不是,则该点置为0。Canny边缘检测算法原理

二、环境配置

详细请看博主这篇文章:【深度学习环境配置】Anaconda +Pycharm + CUDA +cuDNN + Pytorch + Opencv(资源已上传)

三、算法详解

3.1、数据结构 Mat

Opencv C++ 基本数据结构 Mat

/*
函数说明:Mat(int rows, int cols, int type)
函数输入:		rows		代表行数
				cols		代表列数
				type		可以设置为:CV_8UC(n)、CV_8SC(n)、CV_16SC(n)、CV_16UC(n)、CV_32FC(n)、CV_32SC(n)、CV_64FC(n)
							其中:8U、8S、16S、16U、32S、32F、64F中的数字,代表Mat中每一个数值的位数
							U代表uchar类型、S代表int类型、F代表浮点型(32F为float、64F为double)其他类似。
备注:Mat代表矩阵,该类声明在头文件opencv2/core/core.hpp中
*/

3.2、高斯滤波器的C++实现

【图像处理】高斯模糊、高斯函数、高斯核、高斯卷积操作
将二维图像先按水平方向进行一维高斯卷积(行卷积),再按垂直方向进行一维高斯卷积(列卷积)。同理:将二维高斯卷积核拆分为一维高斯卷积核。

  • (1)对图像使用一维高斯卷积核,在一个方向上进行滤波(例如水平方向);
  • (2)将图像进行转置,并使用相同一维高斯卷积核,在相同方向上进行滤波;(由于转置,其实是对垂直方向进行滤波)
  • (3)再次转置,将图像还原为原来位置,最终得到二维高斯滤波后的图像。

3.3、用一阶偏导有限差分计算梯度幅值和方向

在这里插入图片描述

三、项目实战:C++实现Canny边缘检测

在这里插入图片描述
Canny边缘检测算法(C++实现)

#include <opencv2/opencv.hpp>
#include <math.h>
#include <corecrt_math_defines.h>
#define _USE_MATH_DEFINES

using namespace cv;		// 使用命名空间cv。例如:cv::Mat可以省略写为 Mat


/*
函数说明:将两个图像拼接,以便在同一个窗口显示
函数输入:	dst 		输出的拼接后的图像
            src1 		拼接的第一幅图
            src2 		拼接的第二幅图
*/
void mergeImg(Mat& dst, Mat& src1, Mat& src2)
{
    int rows = src1.rows;
    int cols = src1.cols + 5 + src2.cols;
    CV_Assert(src1.type() == src2.type());
    dst.create(rows, cols, src1.type());
    src1.copyTo(dst(Rect(0, 0, src1.cols, src1.rows)));
    src2.copyTo(dst(Rect(src1.cols + 5, 0, src2.cols, src2.rows)));
}


/*
函数说明:一维高斯卷积,对每行进行高斯卷积
函数输入:	img 		输入原图像
            dst 		一维高斯卷积后的输出图像
*/
void gaussianConvolution(Mat& img, Mat& dst)
{
    int nr = img.rows;
    int nc = img.cols;
    int templates[3] = { 1, 2, 1 };

    // 按行遍历除每行边缘点的所有点
    for (int j = 0; j < nr; j++)
    {
        uchar* data = img.ptr<uchar>(j); 			//提取该行地址
        for (int i = 1; i < nc - 1; i++)
        {
            int sum = 0;
            for (int n = 0; n < 3; n++)
            {
                sum += data[i - 1 + n] * templates[n]; 	//相乘累加
            }
            sum /= 4;
            dst.ptr<uchar>(j)[i] = sum;
        }
    }
}


/*
函数说明:高斯滤波器,利用3*3的高斯模版进行高斯卷积
函数输入:	img 		输入原图像
            dst  		高斯滤波后的输出图像
*/
void gaussianFilter(Mat& img, Mat& dst)
{
    // 对水平方向进行滤波
    Mat dst1 = img.clone();
    gaussianConvolution(img, dst1);
    // 图像矩阵转置
    Mat dst2;
    transpose(dst1, dst2);

    // 对垂直方向进行滤波
    Mat dst3 = dst2.clone();
    gaussianConvolution(dst2, dst3);
    // 再次转置
    transpose(dst3, dst);
}


/*
函数说明:用一阶偏导有限差分计算梯度幅值和方向
函数输入:	img 		输入原图像
            gradXY 		输出的梯度幅值
            theta 		输出的梯度方向
*/
void getGrandient(Mat& img, Mat& gradXY, Mat& theta)
{
    gradXY = Mat::zeros(img.size(), CV_8U);
    theta = Mat::zeros(img.size(), CV_8U);

    for (int j = 1; j < img.rows - 1; j++)
    {
        for (int i = 1; i < img.cols - 1; i++)
        {
            double gradY = double(img.ptr<uchar>(j - 1)[i - 1] + 2 * img.ptr<uchar>(j - 1)[i] + img.ptr<uchar>(j - 1)[i + 1] - img.ptr<uchar>(j + 1)[i - 1] - 2 * img.ptr<uchar>(j + 1)[i] - img.ptr<uchar>(j + 1)[i + 1]);
            double gradX = double(img.ptr<uchar>(j - 1)[i + 1] + 2 * img.ptr<uchar>(j)[i + 1] + img.ptr<uchar>(j + 1)[i + 1] - img.ptr<uchar>(j - 1)[i - 1] - 2 * img.ptr<uchar>(j)[i - 1] - img.ptr<uchar>(j + 1)[i - 1]);

            gradXY.ptr<uchar>(j)[i] = sqrt(gradX * gradX + gradY * gradY); 		//计算梯度
            theta.ptr<uchar>(j)[i] = atan(gradY / gradX); 					//计算梯度方向
        }
    }
}


/*
函数说明:NMS非极大值抑制
函数输入:	gradXY 		输入的梯度幅值
            theta 		输入的梯度方向
            dst 		输出的经局部非极大值抑制后的图像
*/
void nonLocalMaxValue(Mat& gradXY, Mat& theta, Mat& dst)
{
    dst = gradXY.clone();
    for (int j = 1; j < gradXY.rows - 1; j++)
    {
        for (int i = 1; i < gradXY.cols - 1; i++)
        {
            double t = double(theta.ptr<uchar>(j)[i]);
            double g = double(dst.ptr<uchar>(j)[i]);
            if (g == 0.0)
            {
                continue;
            }
            double g0, g1;
            if ((t >= -(3 * M_PI / 8)) && (t < -(M_PI / 8)))
            {
                g0 = double(dst.ptr<uchar>(j - 1)[i - 1]);
                g1 = double(dst.ptr<uchar>(j + 1)[i + 1]);
            }
            else if ((t >= -(M_PI / 8)) && (t < M_PI / 8))
            {
                g0 = double(dst.ptr<uchar>(j)[i - 1]);
                g1 = double(dst.ptr<uchar>(j)[i + 1]);
            }
            else if ((t >= M_PI / 8) && (t < 3 * M_PI / 8))
            {
                g0 = double(dst.ptr<uchar>(j - 1)[i + 1]);
                g1 = double(dst.ptr<uchar>(j + 1)[i - 1]);
            }
            else
            {
                g0 = double(dst.ptr<uchar>(j - 1)[i]);
                g1 = double(dst.ptr<uchar>(j + 1)[i]);
            }

            if (g <= g0 || g <= g1)
            {
                dst.ptr<uchar>(j)[i] = 0.0;
            }
        }
    }
}


/*
函数说明:弱边缘点补充连接强边缘点
函数输入:img 弱边缘点补充连接强边缘点的输入和输出图像
 */
void doubleThresholdLink(Mat& img)
{
    // 循环找到强边缘点,把其领域内的弱边缘点变为强边缘点
    for (int j = 1; j < img.rows - 2; j++)
    {
        for (int i = 1; i < img.cols - 2; i++)
        {
            // 如果该点是强边缘点
            if (img.ptr<uchar>(j)[i] == 255)
            {
                // 遍历该强边缘点领域
                for (int m = -1; m < 1; m++)
                {
                    for (int n = -1; n < 1; n++)
                    {
                        // 该点为弱边缘点(不是强边缘点,也不是被抑制的0点)
                        if (img.ptr<uchar>(j + m)[i + n] != 0 && img.ptr<uchar>(j + m)[i + n] != 255)
                        {
                            img.ptr<uchar>(j + m)[i + n] = 255; //该弱边缘点补充为强边缘点
                        }
                    }
                }
            }
        }
    }

    for (int j = 0; j < img.rows - 1; j++)
    {
        for (int i = 0; i < img.cols - 1; i++)
        {
            // 如果该点依旧是弱边缘点,及此点是孤立边缘点
            if (img.ptr<uchar>(j)[i] != 255 && img.ptr<uchar>(j)[i] != 255)
            {
                img.ptr<uchar>(j)[i] = 0; //该孤立弱边缘点抑制
            }
        }
    }
}


/*
函数说明:用双阈值算法检测和连接边缘
函数输入:	low 		输入的低阈值
            high 		输入的高阈值
            img 		输入的原图像
            dst 		输出的用双阈值算法检测和连接边缘后的图像
 */
void doubleThreshold(double low, double high, Mat& img, Mat& dst)
{
    dst = img.clone();

    // 区分出弱边缘点和强边缘点
    for (int j = 0; j < img.rows - 1; j++)
    {
        for (int i = 0; i < img.cols - 1; i++)
        {
            double x = double(dst.ptr<uchar>(j)[i]);
            // 像素点为强边缘点,置255
            if (x > high)
            {
                dst.ptr<uchar>(j)[i] = 255;
            }
            // 像素点置0,被抑制掉
            else if (x < low)
            {
                dst.ptr<uchar>(j)[i] = 0;
            }
        }
    }

    // 弱边缘点补充连接强边缘点
    doubleThresholdLink(dst);
}


int main()
{
    // (1)读取图片
    Mat img = imread("test.jpg");
    if (img.empty())
    {
        printf("读取图像失败!");
        system("pause");
        return 0;
    }
    // 转换为灰度图
    Mat img_gray;
    if (img.channels() == 3)
        cvtColor(img, img_gray, COLOR_RGB2GRAY);
    else
        img_gray = img.clone();

    // (2)高斯滤波
    Mat gauss_img;
    gaussianFilter(img_gray, gauss_img);

    // (3)用一阶偏导有限差分计算梯度幅值和方向
    Mat gradXY, theta;
    getGrandient(gauss_img, gradXY, theta);

    // (4)NMS非极大值抑制
    Mat local_img;
    nonLocalMaxValue(gradXY, theta, local_img);

    // (5)用双阈值算法检测和连接边缘
    Mat dst;
    doubleThreshold(40, 80, local_img, dst);

    // (6)图像显示
    Mat outImg;
    //namedWindow("原始图", 0);
    //imshow("原始图", img);
    //namedWindow("灰度图", 0);
    //imshow("灰度图", img_gray);

    mergeImg(outImg, img_gray, dst); 		//图像拼接(维度需相同)
    namedWindow("img_gray");				//图窗名称
    imshow("img_gray", outImg);				//图像显示
    imwrite("canny.jpg", outImg);			//图像保存
    waitKey(); 								//等待键值输入
    return 0;
}

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

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

相关文章

recurdyn履带问题

1.问题&#xff1a;整车履带仿真出错&#xff0c;车辆越障时遇到障碍物直接弹开 思路&#xff1a; 关于这类模型需要调节履带和地面之间的接触参数、Bushing force&#xff0c;还有驱动函数。 弹飞了是因为接触刚度太大了&#xff0c;调小一些&#xff0c;在100以内继续调节…

从代码角度理解DETR

一个cnn的backbone, 提图像的feature, 比如, HWC.同时对这个feature做position_embedding.然后二者相加 (在Transformer里面就是二者相加)输入encoder,输入decoder (这里有object queries.)然后接Prediction Heads, 比如分类和回归. 下面的代码参考自: https://github.com/fac…

单片机原理及应用——持续更新

目录 一、单片机概述 1、单片机简介 2、单片机的特点 3、MSC-51系列与AT89S5x系列单片机 &#xff08;1&#xff09;MSC-51系列单片机 &#xff08;2&#xff09;AT89S5x系列单片机 二、AT89S52单片机的片内硬件结构 1、AT89S52单片机的硬件组成 2、AT89S52单片机的引…

Springboot +spring security,OAuth2 四种授权模式概念

一.简介 这篇文章来讲下Spring Security OAuth2 四种授权模式。 二.什么是OAuth2 OAuth 2.0 是一种用于授权的开放标准&#xff0c;允许用户授权第三方应用程序访问他们的资源&#xff0c;例如照片、视频或其他个人信息。OAuth 2.0 提供了一些不同的授权模式&#xff0c;包括…

我有一个朋友,分享给我的字节跳动测试开发真题

朋友入职已经两周了&#xff0c;整体工作环境还是非常满意的&#xff01;所以这次特意抽空给我写出了这份面试题&#xff0c;而我把它分享给小伙伴们&#xff0c;面试&入职的经验&#xff01; 大概是在3月中的时候他告诉我投递了简历&#xff0c;5月的时候经过了3轮面试收获…

Windows10中搭建ftp服务器以实现文件传输

开启ftp服务&#xff1a; 1、打开控制面板》程序和功能》 启用或关闭Windows功能 2、找到Internet Information Services&#xff0c;开启以下服务 勾选之后&#xff0c;ftp服务开启成功。 配置IIS&#xff0c;搭建ftp 1、WinS键搜索iis&#xff0c;回车打开》右击网站 》添加…

QUIC 协议:特性、应用场景及其对物联网/车联网的影响

什么是 QUIC 协议 QUIC&#xff08;Quick UDP Internet Connections&#xff09;是由谷歌公司开发的一种基于用户数据报协议&#xff08;UDP&#xff09;的传输层协议&#xff0c;旨在提高网络连接的速度和可靠性&#xff0c;以取代当前互联网基础设施中广泛使用的传输控制协议…

/dev/kmem /proc/kallsyms

文章目录 前言概述使用 /dev/kmem使用 /proc/kallsyms验证进阶 前言 上篇文章我们介绍了 /dev/mem&#xff0c;今天再来介绍下它的好兄弟 /dev/kmem crw-r----- 1 root kmem 1, 1 May 26 06:10 /dev/mem crw-r----- 1 root kmem 1, 2 May 26 06:10 /dev/kmem对比一下&#xf…

第十四届全国大学生数学竞赛决赛(非数类)游记+答案解析

2023/5/27 20:08&#xff1a;今天早上9:00~12:00考了数学竞赛国赛。广州是真的热啊&#xff01;西安才17度&#xff0c;还下着小雨&#xff0c;到广州之后那个艳阳直接给我人干废了&#xff0c;去酒店的路上步行了20分钟真的要死了已经。 拿到卷子的我是崩溃的&#xff0c;用正…

计算机视觉:填充(padding)技术

本文重点 在前面的课程中,我们学习了使用3*3的过滤器去卷积一个5*5的图像,那么最终会得到一个3*3的输出。那是因为 33 过滤器在 55 矩阵中,只可能有 33 种可能的位置。 这背后的数学解释是,如果我们有一个nn的图像,用ff的过滤器做卷积,那么输出的维度就是(n−f+1)(n−f…

码出高效_第一章 | 有意思的二进制表示及运算

目录 0与1的世界1.如何理解32位机器能够同时处理处理32位电路信号&#xff1f;2.如何理解负数的加减法运算3.溢出在运算中如何理解4.计算机种常用的存储单位及转换5.位移运算规则6.有趣的 && 和 & 浮点数1.定点小数&#xff08;为什么会出现浮点数表示&#xff1f;…

Linux 系统烧写

目录 MfgTool 工具简介MfgTool 工作原理简介烧写方式系统烧写原理 烧写NXP 官方系统烧写自制的系统系统烧写网络开机自启动设置 改造我们自己的烧写工具改造MfgTool烧写测试解决Linux 内核启动失败 前面我们已经移植好了uboot 和linux kernle&#xff0c;制作好了根文件系统。但…

Spring 事件相关知识ApplicationEvent

Spring 事件相关知识ApplicationEvent 事件工作流程相关类ApplicationListenerApplicationEvent 我们可以发布自己的事件ApplicationEventPublisher Spring框架中提供了多种事件类型&#xff0c;常用的几个事件类型如下&#xff1a; Spring 事件驱动模型是 Spring 框架中的一个…

uCOSii中的事件标志组

事件标志管理 (EVENT FLAGS MANAGEMENT) OSFlagAccept() 无等待查询”事件标志组的事件标志位”是否建立 OSFlagPend() 需要等待”事件标志组的事件标志位”建立 OSFlagCreate() 建立一个事件标志组 OSFlagDel() 删除一个事件标志组 OSFlagPost() 置位或清0事件标志组中的…

SpringBoot整合百度云人脸识别功能

SpringBoot整合百度云人脸识别功能 1.在百度官网创建应用 首先需要在百度智能云官网中创建应用&#xff0c;获取AppID&#xff0c;API Key&#xff0c;Secret Key 官网地址&#xff1a;https://console.bce.baidu.com/ 2.添加百度依赖 添加以下依赖即可。其中版本号可在mav…

【问卷分析】调节效应检验的操作①

文章目录 1.首先要明白自己的调节和自变量是什么类别的2.实操演练2.1 当调节变量是连续变量时2.1.1 将ml中心化2.1.2 使用分层回归探讨自变量和ml的交互对adh的影响2.1.3 结果解读 1.首先要明白自己的调节和自变量是什么类别的 2.实操演练 在本次演练中&#xff0c;我们以自变…

马斯克要用人工智能对抗人工智能

导读&#xff1a;马斯克对人工智能可能变得失控并“摧毁人类”的担忧促使他采取行动&#xff0c;发起了一个名为“TruthGPT”的项目。 本文字数&#xff1a;1400&#xff0c;阅读时长大约&#xff1a;9分钟 亿万富翁埃隆马斯克在谈到人工智能&#xff08;AI&#xff09;的危险时…

瑞合信LED字幕WiFi卡使用教程(8.0版)

请按照提示下载和安装软件&#xff0c;同时请允许所有权限&#xff0c;如下图&#xff1b; 也可以在各大主流应用商店&#xff08;华为、小米、OPPO、vivo、App Store等&#xff09;搜索“瑞合信”直接安装。 首次使用APP时会提示注册登录软件&#xff0c;你可以选择“使用微信…

深度学习进阶篇-预训练模型[4]:RoBERTa、SpanBERT、KBERT、ALBERT、ELECTRA算法原理模型结构应用场景区别等详解

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

Full-Scanner是一个多功能扫描工具,支持被动/主动信息收集,漏洞扫描工具联动,可导入POC和EXP

github项目地址&#xff1a;https://github.com/Zhao-sai-sai/Full-Scanner gitee项目地址&#xff1a;https://gitee.com/wZass/Full-Scanner 工具简介 做挖漏洞渗透测试有的时候要去用这个工具那个工具去找感觉麻烦我自己就写了一个简单的整合工具&#xff0c;有互联网大佬不…