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