Bootstrap

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

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



前言

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;
			else
				temp_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实现的结果图,边缘更加完整细化,奈何本人水平有限,希望大家阅读本文的同时可以给出宝贵的意见。
本文的代码本人均已经在本地运行正确,有问题欢迎交流。

;