提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
复习Canny边缘检测算法原理,并借助C++, Opencv简单实现
本文Canny边缘检测算法实现步骤如下:
1. 应用高斯模糊滤除图像噪声,算法借助梯度检测边缘,但是梯度检测对噪声敏感,应用高斯滤波器可以滤除图像噪声,提高边缘检测准确率;
2. 应用Sobel算法计算整幅图像的梯度幅值和角度,这里借助opencv cartToPolar()方法实现梯度幅值和角度的计算;
3. 对角度图像中每一角度值从0~360角度值归一化到[0, 45, 90, 135]这四个值;
4. 应用梯度幅值的非极大值抑制算法剔除不满足条件的边缘像素:遍历梯度幅值图像中每一像素点,根据该像素点角度值找到该像素点正、反方向邻近像素点梯度幅值并作比较,如果该像素点的梯度幅值大于邻近正、反方向邻近像素点,则在梯度幅值图像中保留,否则该像素点梯度幅值置为0;
5. 应用高、低阈值判断是否为边缘像素点:(1)如果梯度幅值图像中该点值大于高阈值,判定为边缘像素点;(2)如果梯度幅值图像中该点值小于低阈值,判定为非边缘像素点;(3)如果梯度幅值图像中该点值介于高、低阈值间,则要进行进一步判断:如果该点8邻域像素点有被判定为边缘像素点,则判定该点为边缘像素点, 否则判定为非边缘像素点。
{
// 1. Gaussian blurring
Mat gauImg;
GaussianBlur(*pSrcImg, gauImg, Size(5, 5), 0);
if (debug)
{
imshow("gaussian", gauImg);
cv::waitKey(0);
}
Mat grayImg;
if (gauImg.channels() == 3)
cvtColor(gauImg, grayImg, COLOR_BGR2GRAY);
else
grayImg = gauImg.clone();
if (debug)
{
imshow("gray", grayImg);
cv::waitKey(0);
}
// 2. calc gradient angle and magnitude
Mat sobxImg, sobyImg, angleImg_orig, angleImg, magImg;
Sobel(grayImg, sobxImg, CV_32FC1, 1, 0);
Sobel(grayImg, sobyImg, CV_32FC1, 0, 1);
if (debug)
{
imshow("sobel x", sobxImg);
imshow("sobel y", sobyImg);
cv::waitKey(0);
}
cartToPolar(sobxImg, sobyImg, magImg, angleImg_orig, true);
// normal angleImg_orig(angle value: 0~360) to angleImg( angle value: [0, 45, 90, 135])
angleImg = Mat(angleImg_orig.size(), CV_8UC1);
for (int row = 0; row < angleImg_orig.rows; row++)
{
for (int col = 0; col < angleImg_orig.cols; col++)
{
if ((angleImg_orig.at(row, col) >= 0 && angleImg_orig.at(row, col) < 22.5) ||
(angleImg_orig.at(row, col) >= 337.5 && angleImg_orig.at(row, col) < 360) ||
angleImg_orig.at(row, col) >= 157.5 && angleImg_orig.at(row, col) < 202.5)
{
angleImg.at(row, col) = 0;
}
else if ((angleImg_orig.at(row, col) >= 22.5 && angleImg_orig.at(row, col) < 67.5) ||
(angleImg_orig.at(row, col) >= 202.5 && angleImg_orig.at(row, col) < 247.5))
{
angleImg.at(row, col) = 45;
}
else if ((angleImg_orig.at(row, col) >= 67.5 && angleImg_orig.at(row, col) < 112.5) ||
(angleImg_orig.at(row, col) >= 247.5 && angleImg_orig.at(row, col) < 292.5))
{
angleImg.at(row, col) = 90;
}
else
{
angleImg.at(row, col) = 135;
}
}
}
// 3. non-maximum magnitude edge point suppression
Mat nmsImg = Mat::zeros(magImg.size(), CV_8UC1);
Mat nmsMagImg = Mat::zeros(magImg.size(), CV_32FC1);
for (int row = 1; row < nmsImg.rows - 1; row++)
{
for (int col = 1; col < nmsImg.cols - 1; col++)
{
if (angleImg.at(row, col) == 0 &&
(magImg.at(row, col) > magImg.at(row, col - 1) &&
magImg.at(row, col) > magImg.at(row, col + 1)))
{
nmsMagImg.at(row, col) = magImg.at(row, col);
nmsImg.at(row, col) = 255;
continue;
}
if (angleImg.at(row, col) == 45 &&
(magImg.at(row, col) > magImg.at(row - 1, col + 1) &&
magImg.at(row, col) > magImg.at(row + 1, col - 1)))
{
nmsMagImg.at(row, col) = magImg.at(row, col);
nmsImg.at(row, col) = 255;
continue;
}
if (angleImg.at(row, col) == 90 &&
(magImg.at(row, col) > magImg.at(row - 1, col) &&
magImg.at(row, col) > magImg.at(row + 1, col)))
{
nmsMagImg.at(row, col) = magImg.at(row, col);
nmsImg.at(row, col) = 255;
continue;
}
if (angleImg.at(row, col) == 135 &&
(magImg.at(row, col) > magImg.at(row - 1, col - 1) &&
magImg.at(row, col) > magImg.at(row + 1, col + 1)))
{
nmsMagImg.at(row, col) = magImg.at(row, col);
nmsImg.at(row, col) = 255;
continue;
}
}
}
if (debug)
{
imshow("nms", nmsImg);
imshow("nms mag", nmsMagImg);
cv::waitKey(0);
}
// 4. filter out edge points by low theshold and high threshold
// 4.1 judge strong edge point: nmxMagImg pixel value > hightThres
// judge weakge edge point: nmxMagImg pixel value < lowThres
// note: if use magImg to judge, result image will like threshold() output image - rough edge
Mat result = nmsImg.clone();
for (int row = 1; row < result.rows - 1; row++)
{
for (int col = 1; col < result.cols - 1; col++)
{
if (nmsMagImg.at(row, col) >= highThres)// note: must be nmsMagImg, not be magImg
{
result.at(row, col) = 255;
}
if (nmsMagImg.at(row, col) < lowThres)
{
result.at(row, col) = 0;
}
}
}
if (debug)
{
imshow("strong edge", result);
cv::waitKey(0);
}
// .4.2 judge medium edge points : lowThres< nmsMagImg pixel value (row, col) >= lowThres && nmsMagImg.at(row, col) < highThres)
{
if (result.at(row - 1, col - 1) == 255 || result.at(row - 1, col) == 255 ||
result.at(row - 1, col + 1) == 255 || result.at(row, col + 1) == 255 ||
result.at(row + 1, col + 1) == 255 || result.at(row + 1, col) == 255 ||
result.at(row + 1, col - 1) == 255 || result.at(row, col - 1) == 255)
{
result.at(row, col) = 255;
}
else
{
result.at(row, col) = 0;
}
}
}
}
if (debug)
{
imshow("my canny", result);
cv::waitKey(0);
}
}
1. 原图:
2. 本文实现Canny输出:lowThres=50, hightThres=100
本文只是简单实现了Canny算法,和opencv Canny函数的输出还是有差距的,特别是当高、低阈值比较大时,Opencv Canny函数输出图像保留更多图像边缘信息(个人认为是梯度幅值介于高、低阈值时只是简单依据该像素点8邻域范围内是否有强边缘像素点判定是否为强边缘像素点,导致很多边缘像素点丢失,造成边缘不连续,读者可以在这一方向改进)。