本文共 6594 字,大约阅读时间需要 21 分钟。
Canny边缘检测算法是图像处理领域中的经典算法之一,由Joseph Faure和Luc Boyer于1986年提出。经过数十年的发展,Canny算法依然是图像边缘检测领域的核心技术之一。与传统的Sobel、Prewitt等算法相比,Canny算法在边缘检测准确性和鲁棒性上具有显著优势。
传统的Sobel、Prewitt等算子在边缘检测中存在以下不足:
Canny算法针对上述问题进行了深入改进,提出了以下核心技术:
Canny算法的目标是实现以下三点:
Canny算法的具体实现步骤如下:
高斯平滑
高斯滤波是一种有效的降噪技术,通过卷积核对图像进行平滑处理,可以减少噪声对边缘检测的干扰。计算梯度
使用Sobel算子等计算水平和垂直方向的梯度。Sobel算子是一种多 tapped 核,能够有效计算图像的梯度信息。通过对原始图像进行水平和垂直方向的卷积,可以得到水平和垂直方向的差分矩阵。梯度幅度和方向
梯度的幅度可以通过勾股定理计算,即√(dx² + dy²)。方向则通过反正切函数计算,θ = arctan(dy/dx)。非极大值抑制
在3x3邻域内,Canny算法将梯度值进行方向分组。具体来说,边缘的方向分为四类:在每个方向上,Canny算法对3x3邻域内的梯度值进行比较,保留最大的梯度值,抑制非极大值,从而细化边缘。
双阈值滞后处理
通过双阈值策略,确定边缘点。通常采用两个阈值,较大的阈值用于确定明确的边缘点,而较小的阈值用于连接相邻的边缘点,形成连通的边缘曲线。这种双阈值策略能够有效抑制伪边缘,同时保持边缘的连贯性。#include#include #include #include using namespace std;// 获取Sobel平滑算子的权重int factorial(int n) { int fac = 1; for (int i = 1; i <= n; ++i) { fac *= i; } return fac;}Mat getSobelSmooth(int wsize) { int n = wsize - 1; Mat SobelSmooth = Mat::zeros(Size(wsize, 1), CV_32FC1); for (int k = 0; k <= n; ++k) { float *ptr = SobelSmooth.ptr (0); ptr[k] = factorial(n) / (factorial(k) * factorial(n - k)); } return SobelSmooth;}Mat getSobelDiff(int wsize) { Mat SobelDiff = Mat::zeros(Size(wsize, 1), CV_32FC1); Mat SobelSmooth = getSobelSmooth(wsize - 1); for (int k = 0; k < wsize; ++k) { if (k == 0) { SobelDiff.at (0, k) = 1; } else if (k == wsize - 1) { SobelDiff.at (0, k) = -1; } else { SobelDiff.at (0, k) = SobelSmooth.at (0, k) - SobelSmooth.at (0, k - 1); } } return SobelDiff;}void conv2D(Mat &src, Mat &dst, Mat kernel, int ddepth, Point anchor = Point(-1, -1), int delta = 0, int borderType = BORDER_DEFAULT) { Mat kernelFlip; flip(kernel, kernelFlip, -1); filter2D(src, dst, ddepth, kernelFlip, anchor, delta, borderType);}void sepConv2D_Y_X(Mat &src, Mat &dst, Mat kernel_Y, Mat kernel_X, int ddepth, Point anchor = Point(-1, -1), int delta = 0, int borderType = BORDER_DEFAULT) { Mat dst_kernel_Y; conv2D(src, dst_kernel_Y, kernel_Y, ddepth, anchor, delta, borderType); conv2D(dst_kernel_Y, dst, kernel_X, ddepth, anchor, delta, borderType);}void sepConv2D_X_Y(Mat &src, Mat &dst, Mat kernel_X, Mat kernel_Y, int ddepth, Point anchor = Point(-1, -1), int delta = 0, int borderType = BORDER_DEFAULT) { Mat dst_kernel_X; conv2D(src, dst_kernel_X, kernel_X, ddepth, anchor, delta, borderType); conv2D(dst_kernel_X, dst, kernel_Y, ddepth, anchor, delta, borderType);}void Sobel(Mat &src, Mat &dst_X, Mat &dst_Y, Mat &dst, int wsize, int ddepth, Point anchor = Point(-1, -1), int delta = 0, int borderType = BORDER_DEFAULT) { Mat SobelSmooth = getSobelSmooth(wsize); Mat SobelDiff = getSobelDiff(wsize); sepConv2D_Y_X(src, dst_X, SobelSmooth, SobelDiff, ddepth, anchor, delta, borderType); sepConv2D_X_Y(src, dst_Y, SobelSmooth, SobelDiff.t(), ddepth, anchor, delta, borderType); dst = abs(dst_X) + abs(dst_Y); convertScaleAbs(dst, dst);}bool checkInRange(int r, int c, int rows, int cols) { return r >= 0 && r < rows && c >= 0 && c < cols;}void trace(Mat &edgeMag_noMaxsup, Mat &edge, float TL, int r, int c, int rows, int cols) { if (edge.at (r, c) == 0) { edge.at (r, c) = 255; for (int i = -1; i <= 1; ++i) { for (int j = -1; j <= 1; ++j) { if (checkInRange(r + i, c + j, rows, cols) && edgeMag_noMaxsup.at (r + i, c + j) >= TL) { trace(edgeMag_noMaxsup, edge, TL, r + i, c + j, rows, cols); } } } }}void Edge_Canny(Mat &src, Mat &edge, float TL, float TH, int wsize = 3, bool L2gradient = false) { int rows = src.rows; int cols = src.cols; GaussianBlur(src, src, Size(5, 5), 0.8); Mat dx, dy, sobel_dst; Sobel(src, dx, dy, sobel_dst, wsize, CV_32FC1); Mat edgeMag; if (L2gradient) { magnitude(dx, dy, edgeMag); } else { edgeMag = abs(dx) + abs(dy); } Mat edgeMag_noMaxsup = Mat::zeros(rows, cols, CV_32FC1); for (int r = 1; r < rows - 1; ++r) { for (int c = 1; c < cols - 1; ++c) { float x = dx.at (r, c); float y = dy.at (r, c); float angle = atan2f(y, x) / PI * 180; float mag = edgeMag.at (r, c); if (abs(angle) < 22.5 || abs(angle) > 157.5) { float left = edgeMag.at (r, c - 1); float right = edgeMag.at (r, c + 1); if (mag >= left && mag >= right) { edgeMag_noMaxsup.at (r, c) = mag; } } else if ((angle >= 67.5 && angle <= 112.5) || (angle >= -112.5 && angle <= -67.5)) { float top = edgeMag.at (r - 1, c); float down = edgeMag.at (r + 1, c); if (mag >= top && mag >= down) { edgeMag_noMaxsup.at (r, c) = mag; } } else if ((angle >= 112.5 && angle <= 157.5) || (angle >= -67.5 && angle <= -22.5)) { float right_top = edgeMag.at (r - 1, c + 1); float left_down = edgeMag.at (r + 1, c - 1); if (mag >= right_top && mag >= left_down) { edgeMag_noMaxsup.at (r, c) = mag; } } else if ((angle >= 22.5 && angle <= 67.5) || (angle >= -157.5 && angle <= -112.5)) { float left_top = edgeMag.at (r - 1, c - 1); float right_down = edgeMag.at (r + 1, c + 1); if (mag >= left_top && mag >= right_down) { edgeMag_noMaxsup.at (r, c) = mag; } } } } edge = Mat::zeros(rows, cols, CV_8UC1); for (int r = 1; r < rows - 1; ++r) { for (int c = 1; c < cols - 1; ++c) { float mag = edgeMag_noMaxsup.at (r, c); if (mag >= TH) { trace(edgeMag_noMaxsup, edge, TL, r, c, rows, cols); } else if (mag < TL) { edge.at (r, c) = 0; } } }}int main() { Mat src = imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\lena.jpg"); if (src.empty()) { return -1; } if (src.channels() > 1) { cvtColor(src, src, CV_RGB2GRAY); } Mat edge; Edge_Canny(src, edge, 20, 60); namedWindow("src", CV_WINDOW_NORMAL); imshow("src", src); namedWindow("My_canny", CV_WINDOW_NORMAL); imshow("My_canny", edge); namedWindow("Opencv_canny", CV_WINDOW_NORMAL); imshow("Opencv_canny", src); waitKey(0); return 0;}
Canny算法与OpenCV的Canny函数有以下主要区别:
通过对比,可以看出Canny算法与OpenCV的实现方式有所不同,但核心目标保持一致:实现高精度、高鲁棒性的边缘检测。
转载地址:http://qerfk.baihongyu.com/