本文共 5921 字,大约阅读时间需要 19 分钟。
最近老师布置了一个边缘检测的作业,我借此机会深入学习了Canny算子,并尝试了OpenCV和MATLAB的实现。Canny算子是一种流行的边缘检测算法,能够有效地抑制噪声并精确定位边缘。本文将详细介绍Canny算子的基本原理、算法步骤以及实现过程。
Canny算子的核心目标是实现两个相互矛盾的要求:抑制噪声和精确定位边缘。为了解决这一矛盾,Canny提出了三个准则:
Canny算子属于先平滑后求导的方法,通过高斯滤波平滑图像,减少噪声对后续步骤的影响。
Canny算子的实现通常包括以下步骤:
高斯滤波是Canny算子的第一步,用于对图像进行平滑处理,减少噪声对边缘检测的影响。高斯滤波器是一个2D模板,通常选择3x3大小,标准差在0.8左右。
梯度计算是边缘检测的核心步骤。Canny算子使用一阶差分卷积模板来计算梯度。常用的模板包括:
通过对原始图像进行卷积运算,可以得到图像的x方向和y方向梯度。然后计算梯度的幅值和方向。
为了减少噪声对边缘检测的影响,Canny算子对梯度幅值进行非极大值抑制。具体方法是将图像梯度的方向划分为四个主要方向(水平、45度、垂直、135度),并在每个点上比较其周围邻域的梯度值,保留最大的值。
通过设定高低阈值,结合8连通区域的连接,确定边缘点。低阈值用于过滤噪声,高阈值用于保留真实的边缘点。8连通区域的连接确保边缘点之间的连接更加灵活。
% 读取图像并转换为灰度img_in = imread('lenna.jpg');img_in = rgb2gray(img_in);% 高斯滤波template = fspecial('gaussian', 3, 0.8);img_filt = imfilter(img_in, template);% 计算梯度% Prewitt算子dx = [-1, -1; 1, 1];dy = [-1, 0; 1, 0];grad_x = conv2(img_filt, dx, 'same');grad_y = conv2(img_filt, dy, 'same');grad = sqrt(grad_x.^2 + grad_y.^2);% 非极大值抑制grad_dir = atan2(grad_y, grad_x);for i = 1:rows for j = 1:cols if ((grad_dir(i,j) > -22.5 && grad_dir(i,j) <= 22.5) || ... (grad_dir(i,j) >= 157.5 && grad_dir(i,j) <= 180) || ... (grad_dir(i,j) <= -157.5 && grad_dir(i,j) > -180)) grad_dir(i,j) = 0; elseif ((grad_dir(i,j) >= 22.5 && grad_dir(i,j) <= 67.5) || ... (grad_dir(i,j) <= -112.5 && grad_dir(i,j) > -157.5)) grad_dir(i,j) = -45; elseif ((grad_dir(i,j) >= 67.5 && grad_dir(i,j) <= 112.5) || ... (grad_dir(i,j) <= -67.5 && grad_dir(i,j) > -112.5)) grad_dir(i,j) = 90; elseif ((grad_dir(i,j) >= 112.5 && grad_dir(i,j) <= 157.5) || ... (grad_dir(i,j) <= -22.5 && grad_dir(i,j) > -67.5)) grad_dir(i,j) = 45; end endendNms = zeros(rows, cols);for i = 2:rows-1 for j = 2:cols-1 if (grad_dir(i,j) == 90 && grad(i,j) == max(grad(i,j), grad(i,j+1), grad(i,j-1))) Nms(i,j) = grad(i,j); elseif (grad_dir(i,j) == -45 && grad(i,j) == max(grad(i,j), grad(i+1,j-1), grad(i-1,j+1))) Nms(i,j) = grad(i,j); elseif (grad_dir(i,j) == 0 && grad(i,j) == max(grad(i,j), grad(i+1,j), grad(i-1,j))) Nms(i,j) = grad(i,j); elseif (grad_dir(i,j) == 45 && grad(i,j) == max(grad(i,j), grad(i+1,j+1), grad(i-1,j-1))) Nms(i,j) = grad(i,j); end endend% 双阈值检测thresh = graythresh(img_in);img_bw = im2bw(img_in, thresh);bw = edge(img_bw, 'canny');img_out = zeros(rows, cols);for i = 1:rows for j = 1:cols if Nms(i,j) < 0.1 * max(Nms(:)) img_out(i,j) = 0; elseif Nms(i,j) > 0.3 * max(Nms(:)) img_out(i,j) = 1; else if (Nms(i-1,j-1) < 0.1 || Nms(i-1,j) < 0.1 || Nms(i-1,j+1) < 0.1 || ... Nms(i,j-1) < 0.1 || Nms(i,j+1) < 0.1 || Nms(i+1,j-1) < 0.1 || ... Nms(i+1,j) < 0.1 || Nms(i+1,j+1) < 0.1) img_out(i,j) = 1; end end endendfigure, imshow(img_out);title('Canny算子检测结果');
#include#include #include #include #include using namespace std;using namespace cv;Mat Img_in, Img_gray, Img_out, scr;int main() { Img_in = imread("lenna.jpg"); int rows = Img_in.rows, cols = Img_in.cols; cvtColor(Img_in, Img_gray, CV_BGR2GRAY); imshow("【灰度图】", Img_gray); // 高斯平滑滤波 Mat img_filt; GaussianBlur(Img_gray, Img_out, Size(3, 3), 0, 0); Img_out.convertTo(Img_out, CV_32FC1); // 计算梯度 Mat gx = Mat_ (2, 2) << -1, -1, 1, 1; Mat gy = Mat_ (2, 2) << 1, -1, 1, -1; filter2D(Img_out, img_gx, Img_out.depth(), gx); filter2D(Img_out, img_gy, Img_out.depth(), gy); img_gx = img_gx.mul(img_gx); img_gy = img_gy.mul(img_gy); img_g = img_gx + img_gy; sqrt(img_g, img_g); // 非极大值抑制 Mat Nms = Mat::zeros(rows, cols, CV_32FC1); for (int i = 1; i < rows; ++i) { for (int j = 1; j < cols; ++j) { if (img_dir(i, j) <= 22.5 && img_dir(i, j) > 0 || ... img_dir(i, j) >= 157.5 && img_dir(i, j) < 180 || ... img_dir(i, j) <= -157.5 && img_dir(i, j) > -180) { img_dir(i, j) = 0; } else if (img_dir(i, j) >= 22.5 && img_dir(i, j) < 67.5 || ... img_dir(i, j) <= -112.5 && img_dir(i, j) > -157.5) { img_dir(i, j) = 45; } else if (img_dir(i, j) >= 67.5 && img_dir(i, j) < 112.5 || ... img_dir(i, j) <= -67.5 && img_dir(i, j) > -112.5) { img_dir(i, j) = 90; } else if (img_dir(i, j) >= 112.5 && img_dir(i, j) < 157.5 || ... img_dir(i, j) <= -22.5 && img_dir(i, j) > -67.5) { img_dir(i, j) = 135; } } } // 双阈值检测 double maxVal = 0; minMaxLoc(Nms, 0, &maxVal, 0, 0); double TH = 0.3 * maxVal, TL = 0.1 * maxVal; Mat img_dst = Mat::zeros(rows, cols, CV_32FC1); for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { if (Nms(i, j) < TL) { img_dst(i, j) = 0; } else if (Nms(i, j) > TH) { img_dst(i, j) = 1; } else { bool connected = false; if (i > 0 && Nms(i-1, j) < TL) connected = true; if (j > 0 && Nms(i, j-1) < TL) connected = true; if (i < rows-1 && Nms(i+1, j) < TL) connected = true; if (j < cols-1 && Nms(i, j+1) < TL) connected = true; if (i > 0 && j > 0 && Nms(i-1, j-1) < TL) connected = true; if (i > 0 && j < cols-1 && Nms(i-1, j+1) < TL) connected = true; if (i < rows-1 && j > 0 && Nms(i+1, j-1) < TL) connected = true; if (i < rows-1 && j < cols-1 && Nms(i+1, j+1) < TL) connected = true; if (!connected) { img_dst(i, j) = 0; } else { img_dst(i, j) = 1; } } } } imshow("非极大值抑制图", Nms); imshow("边缘检测图", img_dst); imwrite("边缘检测效果图.jpg", img_dst); waitKey(0); return 0;}
通过以上实现,可以清晰地看到Canny算子在边缘检测中的有效性和鲁棒性。对于不同光照条件下的图像,Canny算子能够有效地检测出清晰的边缘,并且能够抑制噪声的干扰。
转载地址:http://crrfk.baihongyu.com/