Canny算子边缘检测原理及实现
梯度方向利用不足:这些算子通常只计算了边缘的梯度幅度,未充分利用边缘的梯度方向信息。 简单的二值处理:检测后的二值图像仅依赖单一的阈值进行处理,难以有效抑制伪边缘。 基于梯度方向的非极大值抑制:通过分析3x3邻域内的梯度方向,抑制非极大值,保留局部最强的边缘点。 双阈值滞后处理:采用双阈值策略,既能有效抑制伪边缘,又能准确提取真实边缘。 低错误率:确保所有真实边缘都被检测到,同时避免检测伪边缘。 高精度定位:检测到的边缘点尽可能接近真实边缘。 单一边缘点响应:在单个边缘点位置,检测器应只输出一个响应。 高斯平滑:对输入图像进行高斯滤波,降低噪声对边缘检测的影响。 计算梯度:使用Sobel算子等计算图像的水平和垂直差分,进而得到每点的梯度向量(方向和幅度)。 非极大值抑制:根据梯度方向,将3x3邻域内的梯度值进行比较,保留最大的梯度值,抑制非极大值。 双阈值滞后处理:通过双阈值策略,确定边缘点,并对相邻的边缘点进行连接,形成连通的边缘曲线。 双阈值处理:OpenCV的Canny函数默认使用L2距离作为梯度计算方式,并提供了多种参数选项。 非极大值抑制:OpenCV的Canny函数支持多种边缘抑制策略。
发布日期:2025-06-18 15:58:18
浏览次数:3
分类:精选文章
本文共 6594 字,大约阅读时间需要 21 分钟。
Canny边缘检测算法是图像处理领域中的经典算法之一,由Joseph Faure和Luc Boyer于1986年提出。经过数十年的发展,Canny算法依然是图像边缘检测领域的核心技术之一。与传统的Sobel、Prewitt等算法相比,Canny算法在边缘检测准确性和鲁棒性上具有显著优势。
Canny算法的优势
传统的Sobel、Prewitt等算子在边缘检测中存在以下不足:
Canny算法针对上述问题进行了深入改进,提出了以下核心技术:
Canny算法的原理
Canny算法的目标是实现以下三点:
Canny算法的具体实现步骤如下:
详细步骤解析
高斯平滑
高斯滤波是一种有效的降噪技术,通过卷积核对图像进行平滑处理,可以减少噪声对边缘检测的干扰。计算梯度
使用Sobel算子等计算水平和垂直方向的梯度。Sobel算子是一种多 tapped 核,能够有效计算图像的梯度信息。通过对原始图像进行水平和垂直方向的卷积,可以得到水平和垂直方向的差分矩阵。梯度幅度和方向
梯度的幅度可以通过勾股定理计算,即√(dx² + dy²)。方向则通过反正切函数计算,θ = arctan(dy/dx)。非极大值抑制
在3x3邻域内,Canny算法将梯度值进行方向分组。具体来说,边缘的方向分为四类:- 水平边缘(梯度方向垂直):22.5° ≤ |θ| ≤ 67.5° 或 -157.5° ≤ θ ≤ -112.5°。
- 垂直边缘(梯度方向水平):67.5° ≤ |θ| ≤ 112.5° 或 -112.5° ≤ θ ≤ -67.5°。
- 45°边缘(梯度方向正交):112.5° ≤ |θ| ≤ 157.5° 或 -67.5° ≤ θ ≤ -22.5°。
- 135°边缘(梯度方向正交):-22.5° ≤ θ ≤ 22.5° 或 157.5° ≤ θ ≤ 180°。
在每个方向上,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的实现方式有所不同,但核心目标保持一致:实现高精度、高鲁棒性的边缘检测。
发表评论
最新留言
哈哈,博客排版真的漂亮呢~
[***.90.31.176]2026年06月14日 20时04分51秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
PHP 插入排序 -- 折半查找
2023-02-28
PHP 支持8种基本的数据类型
2023-02-28
php 放大镜,放大镜放大图片效果
2023-02-28
PHP 数据库连接池实现
2023-02-28
php 数组 区别,PHP中数组的区别
2023-02-28
PHP 数组怎么添加一个元素
2023-02-28
PHP 文件操作
2023-02-28
php 文字弹幕效果代码,HTML5文字弹幕效果
2023-02-28
php 时间日期函数,获取今天开始时间,结束时间
2023-02-28
php 标准规范
2023-02-28
PHP 浮点型精度运算相关问题
2023-02-28
php 浮点型计算精度问题
2023-02-28
php 特定时间段统计,jpgraph某个时间段的数据统计
2023-02-28
php 生成csv mac下乱码
2023-02-28
php 生成证书 签名及验签
2023-02-28
PHP 的标准输入与输出
2023-02-28
php 笔记 (早前的,很乱)
2023-02-28
PHP 第一天
2023-02-28
Redis使用量暴增,快速定位有哪些大key在作怪
2023-02-28
PHP 统计数据功能 有感
2023-02-28