博客
关于我
Canny算子边缘检测详细原理(OpenCV+MATLAB实现)
阅读量:798 次
发布时间:2023-04-15

本文共 5921 字,大约阅读时间需要 19 分钟。

最近老师布置了一个边缘检测的作业,我借此机会深入学习了Canny算子,并尝试了OpenCV和MATLAB的实现。Canny算子是一种流行的边缘检测算法,能够有效地抑制噪声并精确定位边缘。本文将详细介绍Canny算子的基本原理、算法步骤以及实现过程。

基本原理

Canny算子的核心目标是实现两个相互矛盾的要求:抑制噪声和精确定位边缘。为了解决这一矛盾,Canny提出了三个准则:

  • 信噪比准则:确保边缘点具有较高的信噪比,减少噪声的干扰。
  • 定位精度准则:通过非极大值抑制,确保边缘点具有较高的位置精度。
  • 单边缘响应准则:确保边缘点对光照变化的响应是单一的,避免多个边缘点重叠。
  • Canny算子属于先平滑后求导的方法,通过高斯滤波平滑图像,减少噪声对后续步骤的影响。

    算法步骤

    Canny算子的实现通常包括以下步骤:

  • 高斯平滑滤波:使用高斯滤波器对图像进行平滑处理。
  • 计算图像梯度:通过一阶差分卷积模板计算图像的梯度幅值和方向。
  • 非极大值抑制:将图像梯度的方向划分为四个主要方向,抑制非极大值,保留局部梯度最大的点。
  • 双阈值检测和连接边缘:通过设定高低阈值,结合8连通区域的连接,确定边缘点。
  • 详细过程

    1. 高斯平滑滤波

    高斯滤波是Canny算子的第一步,用于对图像进行平滑处理,减少噪声对边缘检测的影响。高斯滤波器是一个2D模板,通常选择3x3大小,标准差在0.8左右。

    2. 计算图像梯度

    梯度计算是边缘检测的核心步骤。Canny算子使用一阶差分卷积模板来计算梯度。常用的模板包括:

    • x方向:dx = [-1, -1; 1, 1]
    • y方向:dy = [-1, 0; 1, 0]

    通过对原始图像进行卷积运算,可以得到图像的x方向和y方向梯度。然后计算梯度的幅值和方向。

    3. 非极大值抑制

    为了减少噪声对边缘检测的影响,Canny算子对梯度幅值进行非极大值抑制。具体方法是将图像梯度的方向划分为四个主要方向(水平、45度、垂直、135度),并在每个点上比较其周围邻域的梯度值,保留最大的值。

    4. 双阈值检测和连接边缘

    通过设定高低阈值,结合8连通区域的连接,确定边缘点。低阈值用于过滤噪声,高阈值用于保留真实的边缘点。8连通区域的连接确保边缘点之间的连接更加灵活。

    算法实现

    MATLAB代码

    % 读取图像并转换为灰度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算子检测结果');

    OpenCV代码

    #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/

    你可能感兴趣的文章