[计算机视觉] 边缘检测Canny算法原理总结 以及 matlab代码实现

文章目录

  • 边缘检测介绍
  • Canny算法的四个基本步骤
    • 高斯滤波器平滑处理图像
      • 原理
      • 高斯滤波器
      • Matlab中的高斯模版生成函数
    • 计算图像每一个像素点的梯度值以及梯度方向
      • Sobel算子
    • 对梯度值进行非极大值抑制
      • 论文中的方法
      • 插值法
    • 双阈值检测以及连接边缘
  • Matlab代码实现
    • 主函数
    • 高斯滤波器生成函数
    • 梯度计算函数
    • 非极大值抑制函数
      • 论文算法
      • 插值算法
    • 双阈值检测以及连接边缘函数
  • 结果分析

边缘检测介绍

  • 提取图像的边缘信息是底层数字图像处理的基本任务之一
  • 图象的边缘是指图象局部区域亮度变化显著的部分
  • Canny 是一种常用的边缘检测算法. 1986 年由 John F.Canny 提出

Canny算法的四个基本步骤

高斯滤波器平滑处理图像

原理

  • 使用高斯滤波器的主要目的是为了给图像降噪, 因为之后的导数计算对噪声非常敏感, 如果不对噪声进行提前处理, 进行导数运算后, 这些噪声会被放大, 使得检测出来的虚假边缘增多.
  • 虽然使用滤波器平滑处理图像能够有效的抑制噪声, 但是在此过程中会使得图像边缘模糊, 增加了边缘定位的不确定性.
  • 实际工程经验表明, 使用高斯滤波器是在抗噪声干扰和边缘检测精确定位之间的一个较好的折中方案

高斯滤波器

  • 高斯滤波器实际上就是一个n*n的矩阵, 将这个矩阵和图像的矩阵进行卷积, 即可达到对图像平滑处理的目的.
  • 这个矩阵由二维高斯函数生成.
    在这里插入图片描述
  • 比如想得到一个3*3的高斯矩阵, 以矩阵的中心位置为坐标原点, 各个位置的坐标如下图所示, 将各个位置的坐标带入高斯函数中得到的值就是高斯核中对应位置的元素的值. (水平向右为x轴正方向, 竖直向上为y轴正方向)
(-1, 1) (0, 1) (1, 1)
(-1, 0) (0, 0) (1, 0)
(-1, -1) (0, -1) (1, -1)
  • 假设模版是k*k的大小, 模版中各个元素的计算值如下, i表示行, j表示列. 最后的计算结果一般为小数, 每个元素都需要除以所有元素的和 (保证所有元素值的和为1)
    在这里插入图片描述
  • 如果要把矩阵化为整数, 则需要在矩阵前面加一个系数 (1/元素和)

Matlab中的高斯模版生成函数

  • Matlab中可以通过函数fspecial('gaussian', HSIZE, SIGMA) 来生成一个高斯模版, HSIZE是模版大小默认是3*3
  • 使用imfilter(img, h, 'replicate') 对图像进行平滑处理
  • 下面代码中的高斯模版生成函数为自己根据原理实现, 平滑处理使用的是conv2卷积函数

计算图像每一个像素点的梯度值以及梯度方向

Sobel算子

  • 边缘是图像局部区域亮度变化显著的部分, 如何衡量亮度变化? 梯度大小和梯度方向
  • Sobel算子认为邻域的像素对当前像素产生的影响不是等价的,所以距离不同的像素具有不同的权值,对算子结果产生的影响也不同。一般来说,距离越远,产生的影响越小 (水平竖直方向的权重大于45度方向)
  • Sobel算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度梯度值
  • 横向 Gx
-1 0 1
-2 0 2
-1 0 1
  • 纵向 Gy
-1 -2 -1
0 0 0
1 2 1
  • 图像中每一个像素的梯度值大小用下面公式计算
    在这里插入图片描述
  • 梯度方向
    在这里插入图片描述

对梯度值进行非极大值抑制

  • 图像梯度值矩阵中的元素值越大, 说明图像在该点的梯度值越大, 并不能说明该点就是边缘(这可能仅仅只是图像亮度增强过程中的某一个点)
  • 非极大值抑制就是说寻找在梯度方向上的像素点的局部梯度最大值, 将非极大值点对应的灰度设为0.

论文中的方法

  • 在John Canny提出的Canny算子的论文中,非极大值抑制只是在0、45、90、135四个梯度方向上进行的,每个像素点梯度方向按照相近程度用这四个方向来代替
  • 对于某一个像素点, 根据其梯度方向, 近似到上述4个方向的其中一个, 然后将该像素点的灰度值与该方向上相邻的两个像素点的梯度值进行比较, 如果是极大值则保留, 否则设为0.

插值法

  • 如果不采用近似的方法, 实际数字图像中的像素点是离散的二维矩阵,所以处在某个像素梯度方向两侧的点是不一定存在的,或者说是一个亚像素(sub pixel)点,而这个不存在的点, 以及这个点的梯度值就必须通过对其两侧的点进行插值来得到 (以下图片来源)
    [计算机视觉] 边缘检测Canny算法原理总结 以及 matlab代码实现_第1张图片[计算机视觉] 边缘检测Canny算法原理总结 以及 matlab代码实现_第2张图片
  • 对这两种方法, 代码中均有实现, 并进行了效果比较

双阈值检测以及连接边缘

  • 一般情况下设定一个阈值来确定边缘, 高于这个阈值的像素点为边缘像素点, 低于这个阈值的设为0. 但是这样佐会造成一些明明是边缘的点, 却因为梯度值大小(过渡不明显) 而被舍弃
  • 使用启发式的方法确定一个上阀值和下阀值,位于下阀值之上的都有可能作为边缘,这样就可能提高准确度
  • 设置两个阀值(threshold),分别为maxVal和minVal。
    • 大于maxVal的都被检测为边缘
    • 小于minVal的都被检测为非边缘
    • 对于中间的像素点,如果与确定为边缘的像素点邻接,则判定为边缘;否则为非边缘 (即判断领域8个像素点中有没有梯度值大于maxVal的)

[计算机视觉] 边缘检测Canny算法原理总结 以及 matlab代码实现_第3张图片
图片来源

Matlab代码实现

主函数

%% =============== Part 0: 读取图像 ================
apple = imread('2.jpg');
apple_gray = double(rgb2gray(apple));
figure(1);
subplot(3,3,1);
imshow(uint8(apple_gray));
title('原图')

%% =============== Part 1: 高斯平滑处理 ================
% 生成高斯模版
H = gaussian_filter(5, 0.8);
apple_filter = imfilter(apple_gray, H, 'replicate');
subplot(3,3,2);
imshow(uint8(apple_filter));
title('高斯平滑')

%% =============== Part 2: 索贝尔算子计算梯度值及方向 ================
[grad, grad_direction] = compute_grad(apple_filter);
subplot(3,3,3);
imshow(uint8(grad));
title('sobel')

%% =============== Part 3: 非极大值抑制 ================
canny1 = non_maximum_restrain(grad, grad_direction);
subplot(3,3,4);
imshow(uint8(canny1));
title('非极大值抑制 论文法')
canny2 = non_maximum_restrain_improvement(grad, grad_direction);
subplot(3,3,7);
imshow(uint8(canny2));
title('非极大值抑制 插值法')

%% =============== Part 4: 双阈值检测 ================
canny11 = dual_threshold_detection(canny1, 50, 100);
subplot(3,3,5);
imshow(uint8(canny11));
title('双阈值检测 论文法')
canny22 = dual_threshold_detection(canny2, 50, 100);
subplot(3,3,8);
imshow(uint8(canny22));
title('双阈值检测 插值法')

高斯滤波器生成函数

function gaussian_matrix = gaussian_filter(size, sigma)
    gaussian_matrix = zeros(size, size);
    coef = 1 / (2 * 3.14159265 * sigma * sigma);
    total_sum = 0;
    for i = 1:size
        for j = 1:size
            x2 = (j - (size + 1)  / 2)^2;
            y2 = (i - (size + 1)  / 2)^2;
            gaussian_matrix(i, j) = coef * exp(-(x2 + y2) / (2 * sigma * sigma));
            total_sum = total_sum + gaussian_matrix(i, j);
        end
    end
    % 使得矩阵中所有元素和为1
    gaussian_matrix = gaussian_matrix / total_sum;
end

梯度计算函数

function [grad, grad_direction] = compute_grad(img_filter)
    % 索贝尔算子
    sobel = [-1.0 0.0 1.0;-2.0 0.0 2.0;-1.0 0.0 1.0];

    % 计算图像的sobel水平梯度 
    gradx=conv2(img_filter, sobel, 'same');
    % gradx = imfilter(apple_filter, sobel, 'replicate');

    % 计算图像的sobel垂直梯度 
    grady=conv2(img_filter, sobel', 'same');
    % grady = imfilter(apple_filter, sobel', 'replicate');

    % 得到图像的sobel梯度以及方向, 使用绝对值代替平方开方
    grad=sqrt(gradx.^2+grady.^2);
    grad_direction = atan(grady./gradx);
end

非极大值抑制函数

论文算法

function canny = non_maximum_restrain(grad, grad_direction)
    [m, n] = size(grad_direction);
    sector = zeros(m, n);
    canny = zeros(m, n);
    % 构造
    % 2 1 0
    % 3 x 3
    % 0 1 2
    for i=1:m
        for j=1:n
            angle = grad_direction(i, j);
            if (angle < 3*pi/4) && (angle >= pi/4)
                sector(i, j) = 0;    
            elseif (angle < pi/4) && (angle >= -pi/4)
                sector(i, j) = 3;    
            elseif (angle < -pi/4) && (angle >= -3*pi/4)
                sector(i, j) = 2;    
            else
                sector(i, j) = 1;    
            end    
        end
    end
    % 判断没一点像素的梯度方向,并和该方向上的数值进行比较
    for i=2:m-1
        for j=2:n-1
            if (sector(i, j) == 0) % 45if ((grad(i, j) > grad(i - 1, j + 1) && grad(i, j) > grad(i + 1, j - 1)) || (grad(i, j) > grad(i, j + 1) && grad(i, j) > grad(i, j - 1)) || (grad(i, j) > grad(i - 1, j) && grad(i, j) > grad(i + 1, j)))
                    canny(i,j) = grad(i, j);
                else
                    canny(i, j) = 0;
                end
            end
            if (sector(i, j) == 1) % 90if (grad(i, j) > grad(i - 1, j) && grad(i, j) > grad(i + 1, j))
                    canny(i,j) = grad(i, j);
                else
                    canny(i, j) = 0;
                end
            end
            if (sector(i, j) == 2) % 135if ((grad(i, j) > grad(i - 1, j - 1) && grad(i, j) > grad(i + 1, j + 1))|| (grad(i, j) > grad(i, j + 1) && grad(i, j) > grad(i, j - 1)) || (grad(i, j) > grad(i - 1, j) && grad(i, j) > grad(i + 1, j)))
                    canny(i,j) = grad(i, j);
                else
                    canny(i, j) = 0;
                end
            end
            if (sector(i, j) == 3) % 180if (grad(i, j) > grad(i, j + 1) && grad(i, j) > grad(i, j - 1))
                    canny(i,j) = grad(i, j);
                else
                    canny(i, j) = 0;
                end
            end
        end
    end
end

插值算法

function canny = non_maximum_restrain_improvement(grad, grad_direction)
    [m, n] = size(grad_direction);
    canny = zeros(m, n);
    for i=2:m-1
        for j=2:n-1
            angle = grad_direction(i, j);
            if (angle > 0 && angle <= pi/4)
                % 通过差值求的亚像素(实际上不存在)
               right_top_pixel = tan(angle) * grad(i-1, j+1);
               right_pixel = (1 - tan(angle)) * grad(i, j+1);
               pixel1 = right_top_pixel + right_pixel;
               left_bottom_pixel = tan(angle) * grad(i+1, j-1);
               left_pixel = (1 - tan(angle)) * grad(i, j-1);
               pixel2 = left_bottom_pixel + left_pixel;
               if (grad(i, j) > pixel1 && grad(i, j) > pixel2)
                   canny(i, j) = grad(i, j);
               else
                   canny(i, j) = 0;
               end
            elseif (angle > pi/4 && angle < pi/2)
                right_top_pixel = 1 / tan(angle) * grad(i-1, j+1);
                top_pixel = (1 - 1 / tan(angle)) * grad(i-1, j);
                pixel1 = right_top_pixel + top_pixel;
                left_bottom_pixel = 1 / tan(angle) * grad(i+1, j-1);
                bottom_pixel = (1 - 1 / tan(angle)) * grad(i+1, j);
                pixel2 = left_bottom_pixel + bottom_pixel;
                if (grad(i, j) > pixel1 && grad(i, j) > pixel2)
                   canny(i, j) = grad(i, j);
                else
                   canny(i, j) = 0;
                end
            elseif (angle > -pi/2 && angle <= -pi/4)
                left_top_pixel = -1/tan(angle) * grad(i-1, j-1);
                top_pixel = (1+1/tan(angle)) * grad(i-1, j);
                pixel1 = left_top_pixel + top_pixel;
                right_bottom_pixel = -1/tan(angle) * grad(i+1, j+1);
                bottom_pixel = (1+1/tan(angle)) * grad(i+1, j);
                pixel2 = right_bottom_pixel + bottom_pixel;
                if (grad(i, j) > pixel1 && grad(i, j) > pixel2)
                   canny(i, j) = grad(i, j);
                else
                   canny(i, j) = 0;
                end
            elseif (angle > -pi/4 && angle <= 0)
                left_top_pixel = -tan(angle) * grad(i-1, j-1);
                left_pixel = (1+tan(angle)) * grad(i, j-1);
                pixel1 = left_top_pixel + left_pixel;
                right_bottom_pixel =  -tan(angle) * grad(i+1, j+1);
                right_pixel = (1+tan(angle)) * grad(i, j+1);
                pixel2 = right_bottom_pixel + right_pixel;
                if (grad(i, j) > pixel1 && grad(i, j) > pixel2)
                   canny(i, j) = grad(i, j);
                else
                   canny(i, j) = 0;
                end
            elseif (angle == pi/2 || angle == -pi/2)
                top_pixel = grad(i-1, j);
                bottom_pixel = grad(i+1, j);
                if (grad(i, j) > top_pixel && grad(i, j) > bottom_pixel)
                   canny(i, j) = grad(i, j);
                else
                   canny(i, j) = 0;
                end
            end    
        end
    end
end

双阈值检测以及连接边缘函数

function canny2 = dual_threshold_detection(canny, low_th, high_th)
    [m, n] = size(canny);
    canny2 = zeros(m, n);
    for i=2:m-1
        for j=2:n-1
            if (canny(i, j) < low_th)
                canny2(i,j) = 0;
            elseif (canny(i, j) > high_th)
                canny2(i, j) = canny(i, j);
            else
                neighbor_matrix = [canny(i-1, j-1), canny(i, j-1), canny(i+1, j-1);...
                                   canny(i-1, j), canny(i, j), canny(i+1, j);...
                                   canny(i-1, j+1), canny(i, j+1), canny(i+1, j+1);];
                max_neighbour = max(neighbor_matrix);
                if (max_neighbour > high_th)
                    canny2(i, j) = canny(i, j);
                else
                    canny2(i,j) = 0;
                end
            end
        end
    end
end

结果分析

[计算机视觉] 边缘检测Canny算法原理总结 以及 matlab代码实现_第4张图片
[计算机视觉] 边缘检测Canny算法原理总结 以及 matlab代码实现_第5张图片

  • 经过比较可以发现, 插值法最后筛选出来的边缘像素点比论文法少, 因此图像看起来有些地方并不是很连续. 也导致了一些地方细节的丢失
  • 但是在边缘宽度上, 论文方法产生宽度大于1的边缘的可能性比插值法更大,在需要达到高精度单点响应的情况下, 插值法可能会更好

参考

  1. Canny边缘检测算法
  2. [图像]Canny检测的Matlab实现(含代码)
  3. 图像处理基础(4):高斯滤波器详解
  4. 高斯滤波器原理及其实现
  5. 边缘检测sobel算子
  6. 利用sobel算子进行边缘检测
  7. Canny算子中的非极大值抑制(Non-Maximum Suppression)分析

你可能感兴趣的:(计算机视觉,计算机视觉,边缘检测,canny算法,matlab)