图像处理之Canny边缘检测(C++)

server/2024/10/21 2:40:22/

图像处理之Canny边缘检测(C++)


文章目录

  • 图像处理之Canny边缘检测(C++)
  • 前言
  • 一、Canny边缘检测步骤
  • 二、代码实现
    • 1.手动实现
    • 2.OpenCV实现
    • 3.结果展示
  • 总结


前言

Canny边缘检测是一种经典的边缘检测算法,它可以在图像中找到明显的边缘区域。

一、Canny边缘检测步骤

Canny边缘检测的算法步骤如下:

  1. 预处理:将图像转换为灰度图像,并进行高斯滤波以减小图像中的噪声。
  2. 计算梯度幅值和方向:使用Sobel算子计算图像中每个像素点的梯度幅值和方向。
  3. 非极大值抑制:在图像中找到梯度幅值最大的像素,然后沿着梯度方向上的相邻像素进行比较,保留梯度幅值较大的像素,将其他像素置为0。
  4. 双阈值检测(阈值滞后处理):根据设置的高阈值和低阈值,将非极大值抑制后的图像进行二值化。梯度幅值大于高阈值的像素被认为是强边缘,梯度幅值介于低阈值和高阈值之间的像素被认为是弱边缘,梯度幅值低于低阈值的像素被认为不是边缘。
  5. 边缘连接:将强边缘与相邻的弱边缘连接起来,形成完整的边缘。如果一个弱边缘与一个强边缘相邻,那么这个弱边缘被认为是边缘的一部分。Canny边缘检测可以提取出图像中的边缘信息,并且能够较好地抑制掉噪声。

二、代码实现

1.手动实现

/*
* @param cv::Mat src        源图像
* @param cv::Mat dst        结果图像
* @param double high_thresh 高阈值
* @param double low_thresh  低阈值
* @brief Canny边缘检测获得边缘图像,原理实现
*/
void CannyEdgeDetect(cv::Mat& src, cv::Mat& dst, double low_thresh, double high_thresh)
{//1.高斯滤波cv::Mat gaussImg;cv::GaussianBlur(src, gaussImg, cv::Size(5, 5), 0);//2.Sobel算子计算梯度cv::Mat sobel_x(src.size(), CV_32FC1);  //水平方向的梯度图像cv::Mat sobel_y(src.size(), CV_32FC1);  //竖直方向的梯度图像cv::Sobel(gaussImg, sobel_x, CV_32FC1, 1, 0);cv::Sobel(gaussImg, sobel_y, CV_32FC1, 0, 1);//计算幅值图像和相位图像cv::Mat sobel_magnitude(src.size(), CV_32FC1), sobel_dir(src.size(), CV_8UC1);double temp_mag = 0, temp_dir = 0;for (int i = 0; i < sobel_x.rows; i++)for (int j = 0; j < sobel_x.cols; j++){//temp_mag = cv::sqrt(sobel_x.at<float>(i, j) * sobel_x.at<float>(i, j) + sobel_y.at<float>(i, j) * sobel_y.at<float>(i, j));temp_mag = cv::abs(sobel_x.at<float>(i, j)) + cv::abs(sobel_y.at<float>(i, j));temp_dir = cv::fastAtan2(sobel_y.at<float>(i, j), sobel_x.at<float>(i, j));sobel_magnitude.at<float>(i, j) = temp_mag;if ((temp_dir > 0 && temp_dir <= 22.5) || (temp_dir > 157.5 && temp_dir <= 202.5) || (temp_dir > 337.5 && temp_dir <= 360))temp_dir = 0;else if ((temp_dir > 22.5 && temp_dir <= 67.5) || (temp_dir > 202.5 && temp_dir <= 247.5))temp_dir = 45;else if ((temp_dir > 67.5 && temp_dir <= 112.5) || (temp_dir > 247.5 && temp_dir <= 292.5))temp_dir = 90;else if ((temp_dir > 112.5 && temp_dir <= 157.5) || (temp_dir > 292.5 && temp_dir <= 337.5))temp_dir = 135;elsetemp_dir = 0;sobel_dir.at<uchar>(i, j) = temp_dir;}//3.非极大值抑制(NMS)double left, right;for (int i = 1; i < sobel_x.rows - 1; i++)for (int j = 1; j < sobel_x.cols - 1; j++){switch (sobel_dir.at<uchar>(i, j)){case 0:left = sobel_magnitude.at<float>(i, j - 1);right = sobel_magnitude.at<float>(i, j + 1);break;case 45:left = sobel_magnitude.at<float>(i + 1, j - 1);right = sobel_magnitude.at<float>(i - 1, j + 1);break;case 90:left = sobel_magnitude.at<float>(i - 1, j);right = sobel_magnitude.at<float>(i + 1, j);break;case 135:left = sobel_magnitude.at<float>(i - 1, j - 1);right = sobel_magnitude.at<float>(i + 1, j + 1);break;default:break;}if (sobel_magnitude.at<float>(i, j) < left || sobel_magnitude.at<float>(i, j) < right){sobel_magnitude.at<float>(i, j) = 0;}else{sobel_magnitude.at<float>(i, j) = std::max(left,right);}}//4.阈值滞后处理cv::Mat threshImg = cv::Mat::zeros(sobel_magnitude.size(), CV_8UC1);    //存储结果for (int i = 0; i < threshImg.rows; i++)for (int j = 0; j < threshImg.cols; j++){if (sobel_magnitude.at<float>(i, j) > low_thresh){threshImg.at<uchar>(i, j) = 128;            //128--弱边缘if (sobel_magnitude.at<float>(i, j) > high_thresh){threshImg.at<uchar>(i, j) = 255;        //255--强边缘}}}//5.孤立弱边缘抑制,基于迟滞现象的边缘跟踪cv::Mat cannyImg(threshImg.size(), CV_8UC1, cv::Scalar::all(0));for (int i = 1; i < threshImg.rows - 1; i++)for (int j = 1; j < threshImg.cols - 1; j++){if (threshImg.at<uchar>(i, j) == 128){if (threshImg.at<uchar>(i - 1, j - 1) == 255 ||threshImg.at<uchar>(i - 1, j) == 255 ||threshImg.at<uchar>(i - 1, j + 1) == 255 ||threshImg.at<uchar>(i, j - 1) == 255 ||threshImg.at<uchar>(i, j + 1) == 255 ||threshImg.at<uchar>(i + 1, j - 1) == 255 ||threshImg.at<uchar>(i + 1, j) == 255 ||threshImg.at<uchar>(i + 1, j + 1) == 255){cannyImg.at<uchar>(i, j) = 255;}else{cannyImg.at<uchar>(i, j) = 0;}}else if (threshImg.at<uchar>(i, j) == 255){cannyImg.at<uchar>(i, j) = 255;}}dst = cannyImg.clone();
}
int main()
{/// 以灰度图的形式加载源图像cv::Mat img = cv::imread("F://work_study//algorithm_demo//baby.jpg", cv::IMREAD_GRAYSCALE);if (img.empty()){cout << "Can't read the image" << endl;return -1;}//一般高阈值设置为低阈值的2~3倍cv::Mat dst;CannyEdgeDetect(img, dst, 50, 100);cv::imshow("dst", dst);waitKey(0);return 0;
}

2.OpenCV实现

int main()
{/// 以灰度图的形式加载源图像cv::Mat img = cv::imread("F://work_study//algorithm_demo//baby.jpg", cv::IMREAD_GRAYSCALE);if (img.empty() || templ.empty()){cout << "Can't read the image" << endl;return -1;}cv::Mat dst;//Canny边缘检测cv::Canny(img,dst, 50, 100);cv::imshow("dst", dst);waitKey(0);return 0;
}

3.结果展示

自己实现的Canny结果图
自己实现的Canny结果图

OpenCV实现Canny结果图OpenCV实现Canny结果图


总结

本文主要用C++代码实现了Canny边缘检测算法(预处理、高斯滤波、非极大值抑制、滞后阈值、边缘跟踪)的实现,并与OpenCV结果形成对比,发现OpenCV实现的结果图,边缘更加完整细化,奈何本人水平有限,希望大家阅读本文的同时可以给出宝贵的意见。
本文的代码本人均已经在本地运行正确,有问题欢迎交流。


http://www.ppmy.cn/server/12327.html

相关文章

安全小课堂丨什么是暴力破解?如何防止暴力破解

什么是暴力破解&#xff1f; 暴力破解也可称为穷举法、枚举法&#xff0c;是一种比较流行的密码破译方法&#xff0c;也就是将密码进行一一推算直到找出正确的密码为止。比如一个6位并且全部由数字组成的密码&#xff0c;可能有100万种组合&#xff0c;也就是说最多需要尝试10…

4月23日,每日信息差

第一、目前全国共确定工伤保险异地就医直接结算试点城市131个&#xff0c;开通上线工伤医疗、工伤康复、辅助器具配置协议机构共398家。工伤职工按规定完成备案&#xff0c;持社保卡或电子社保卡可以到试点城市的协议机构直接结算相关费用 第二、极兔快递仅用 4 年成为中国国内…

Altair:Python数据可视化库的魅力之旅

目录 一、引言 二、Altair概述 三、Altair的核心特性 1.声明式语法 2.丰富的图表类型 3.交互式与响应式 4.无缝集成 四、案例与代码实践 案例一&#xff1a;使用Altair绘制折线图 案例二&#xff1a;使用Altair绘制热力图 五、新手入门指南 1.安装与导入 2.数据准…

Rust:遍历 BinaryHeap

Rust 的 BinaryHeap 结构体实现了迭代器接口&#xff0c;因此你可以遍历它。不过&#xff0c;由于 BinaryHeap 是一个优先队列&#xff0c;它默认是按照元素的优先级顺序&#xff08;对于 MinBinaryHeap 是最小到最大&#xff0c;对于 MaxBinaryHeap 是最大到最小&#xff09;来…

维基百科、百度百科和搜狗百科词条的创建流程

随着网络的发展&#xff0c;百度百科、搜狗百科、维基百科等百科网站已经成为大众获取知识的重要途径。因为百科具有得天独厚的平台优势&#xff0c;百科上的信息可信度高&#xff0c;权威性强。所以百科平台也成为商家的必争之地。这里小马识途聊聊如何创建百度百科、搜狗百科…

烟雾识别图像处理:原理、应用与未来发展---豌豆云

本文详细介绍了烟雾识别图像处理的基本原理、应用领域以及未来的发展趋势。 通过深入剖析烟雾识别图像处理的关键技术和方法&#xff0c;帮助读者了解该领域的最新进展&#xff0c;为实际应用提供有价值的参考。 随着计算机视觉和人工智能技术的快速发展&#xff0c;烟雾识别…

关于电脑卡死如何开机、F8、安全模式

问题来源和原因 亲戚家台式电脑无法开机了 原因&#xff1a;游戏、等等等东西太多占用内存&#xff0c;导致系统无法开机 比如常见的开机蓝屏&#xff0c;不能正常开机&#xff0c;这个时候我们可以采取通过安全模式启动的办法来启动电脑&#xff0c;从而进一步找到问题并排查故…

【STM32+HAL+Proteus】系列学习教程4---GPIO输入模式(独立按键)

实现目标 1、掌握GPIO 输入模式控制 2、学会STM32CubeMX配置GPIO的输入模式 3、具体目标&#xff1a;1、按键K1按下&#xff0c;LED1点亮&#xff1b;2、按键K2按下&#xff0c;LED1熄灭&#xff1b;2、按键K3按下&#xff0c;LED2状态取反&#xff1b; 一、STM32 GPIO 输入…