图像处理之Canny边缘检测(C++)
文章目录
- 图像处理之Canny边缘检测(C++)
- 前言
- 一、Canny边缘检测步骤
- 二、代码实现
- 1.手动实现
- 2.OpenCV实现
- 3.结果展示
- 总结
前言
Canny边缘检测是一种经典的边缘检测算法,它可以在图像中找到明显的边缘区域。
一、Canny边缘检测步骤
Canny边缘检测的算法步骤如下:
- 预处理:将图像转换为灰度图像,并进行高斯滤波以减小图像中的噪声。
- 计算梯度幅值和方向:使用Sobel算子计算图像中每个像素点的梯度幅值和方向。
- 非极大值抑制:在图像中找到梯度幅值最大的像素,然后沿着梯度方向上的相邻像素进行比较,保留梯度幅值较大的像素,将其他像素置为0。
- 双阈值检测(阈值滞后处理):根据设置的高阈值和低阈值,将非极大值抑制后的图像进行二值化。梯度幅值大于高阈值的像素被认为是强边缘,梯度幅值介于低阈值和高阈值之间的像素被认为是弱边缘,梯度幅值低于低阈值的像素被认为不是边缘。
- 边缘连接:将强边缘与相邻的弱边缘连接起来,形成完整的边缘。如果一个弱边缘与一个强边缘相邻,那么这个弱边缘被认为是边缘的一部分。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结果图
OpenCV实现Canny结果图
总结
本文主要用C++代码实现了Canny边缘检测算法(预处理、高斯滤波、非极大值抑制、滞后阈值、边缘跟踪)的实现,并与OpenCV结果形成对比,发现OpenCV实现的结果图,边缘更加完整细化,奈何本人水平有限,希望大家阅读本文的同时可以给出宝贵的意见。
本文的代码本人均已经在本地运行正确,有问题欢迎交流。