Code covered by the BSD License  

Highlights from
Canny Edge Detector

image thumbnail
from Canny Edge Detector by Halim Can
Performs Canny Edge Detector with some additional features.

CannyEdgeDetector(varargin)
function varargout = CannyEdgeDetector(varargin)
% CannyEdgeDetector Detects edges in the intensity image.
%     imo = CannyEdgeDetector(imi) takes the intensity image imi as input
%     and returns a binary image imo with the same size as imi, where 1's
%     are the edge pixels detected by Canny's method (default). The default
%     value for the thresholds are specified as
%     0.3*max(GradientMagnitudeImage), 0.1*max(GradientMagnitudeImage) and
%     1 for high and low thresholds and sigma for Gaussian kernel,
%     respectively. There are also some additional features listed as
%     below:
%     
%     imo = CannyEdgeDetector(imi,method) takes an additional string 
%     'method' as input which specifies the function to detect the edges 
%     whether using Canny's method, or Laplacian of Gaussian. The possible 
%     valid entries for method input are 'canny' and 'log'. The default 
%     threshold value for log method is automatically chosen by 'edge' 
%     function, which is present in "Image Processing Toolbox".
%     
%     imo = CannyEdgeDetector(imi,sigma) returns the same output as 
%     described at the top, only with the specified sigma value for
%     Gaussian kernel in stead of the default value. The default value for
%     method is 'canny'.
%     
%     imo = CannyEdgeDetector(imi,sigma,method) returns the edge detected 
%     binary image imo, with the specified sigma value and the specified 
%     method, in stead of the defaults.
%     
%     imo = CannyEdgeDetector(...,Th) returns the same output as described
%     above, with an additional input to specify the threshold for 'log'
%     method, or the high threshold for 'canny'. In the second case, the
%     low threshold is 1/3 of Th by default.
%         
%     imo = CannyEdgeDetector(...,Th,Tl) takes an additional input Tl,
%     which specifies the low threshold in case of Canny's method. When the
%     method input is 'log', this usage is invalid and the function will
%     return an error message.
%     
%     imo = CannyEdgeDetector(...,Th,Tl,feature) In the case of Canny's
%     method, there are two distinct ways to apply hysteresis thresholding
%     defined. The first one is a recursive search (namely, depth first
%     search), and the second one is to use some morphological operations.
%     Obviously, the recursive method takes longer to operate. The string
%     input 'feature' specifies which one to use. Valid options for this
%     input are 'dfs' and 'morph'. It is defined as 'dfs' by default. In
%     the case of log method, this usage is invalid and will return an
%     error message.
% 
%     CannyEdgeDetector(...) usage (without the output arguments) plots the
%     binary image using imshow, in stead of making any assignments.
%     
%     Author: Halim Can ALBASAN
%     
%     Written for the term project of the course "Robot Vision" (EE701)
%     given in Middle East Technical University, Ankara, Turkey; by the 
%     lecturer A. Aydin Alatan.
%     
%     Date: 15/06/2008 


[imi,sigma,method,Th,Tl,feature] = parse_inputs (varargin{:});

switch method
    case 'canny'
        
%% Detection of Gaussian kernel size ( 3 <= N <= 15 )

G = gaussmf (-10:10,[sigma 0]);
ind = find (G<=.1);
if length(ind)<=6
    N = 15;                             % Upper boundary
    warning('MATLAB:LargeSigma','Sigma too large!')
    
elseif length(ind)>=18
    N = 3;                              % Lower boundary
    warning('MATLAB:SmallSigma','Sigma too small!')
else
    G(ind) = [];
    N = length (G);
end

%% Gaussian kernel and its first derivatives ( horizontal (Gh) and vertical
%% (Gv) )

G = fspecial ('gaussian', [N N], sigma);
[Gh,Gv] = gradient(G);

%% Applying the operators to the image and obtaining the gradient
%% magnitudes and directions

imx = conv2(double(imi),Gh);
imx = imx ((N-1)/2+1:size(imx,1)-(N-1)/2,(N-1)/2+1:size(imx,2)-(N-1)/2);
imy = conv2(double(imi),Gv);
imy = imy ((N-1)/2+1:size(imy,1)-(N-1)/2,(N-1)/2+1:size(imy,2)-(N-1)/2);

gra_mag = sqrt(imx.^2+imy.^2);
gra_dir = atan2(imy,imx);

%% Gradient direction discretization

[r c] = size (gra_dir);

for i=1:r
    for j=1:c
        if ( gra_dir(i,j)<pi/8 && gra_dir(i,j)>=0 ) ...
                || ( gra_dir(i,j)<=2*pi && gra_dir(i,j)>=15*pi/8 ) ...
                || ( gra_dir(i,j)<9*pi/8 && gra_dir(i,j)>=7*pi/8 )
            gra_dir(i,j) = 0;
        elseif ( gra_dir(i,j)>=pi/8 && gra_dir(i,j)<3*pi/8 ) ...
                || ( gra_dir(i,j)>=9*pi/8 && gra_dir(i,j)<11*pi/8 )
            gra_dir(i,j) = 45;
        elseif ( gra_dir(i,j)>=3*pi/8 && gra_dir(i,j)<5*pi/8 ) ...
                || ( gra_dir(i,j)>=11*pi/8 && gra_dir(i,j)<13*pi/8)
            gra_dir(i,j) = 90;
        else
            gra_dir(i,j) = 135;
        end
    end
end

%% Nonmaxima suppression

gra_mag_s = gra_mag;
gra_mag_s(1,:) = zeros (1,c);
gra_mag_s(r,:) = zeros (1,c);
gra_mag_s(:,1) = zeros (r,1);
gra_mag_s(:,c) = zeros (r,1);             % Suppress boundaries first

for i=2:r-1
    for j=2:c-1
        if gra_dir(i,j)==0
            if ( gra_mag(i,j)<=gra_mag(i,j+1) ) ...
                    || ( gra_mag(i,j)<=gra_mag(i,j-1) )
                gra_mag_s(i,j) = 0;
            end
        elseif gra_dir(i,j)==45
            if ( gra_mag(i,j)<=gra_mag(i-1,j+1) ) ...
                    || ( gra_mag(i,j)<=gra_mag(i+1,j-1) )
                gra_mag_s(i,j) = 0;
            end
        elseif gra_dir(i,j)==90
            if ( gra_mag(i,j)<=gra_mag(i+1,j) ) ...
                    || ( gra_mag(i,j)<=gra_mag(i-1,j) )
                gra_mag_s(i,j) = 0;
            end
        else
            if ( gra_mag(i,j)<=gra_mag(i+1,j+1) ) ...
                    || ( gra_mag(i,j)<=gra_mag(i-1,j-1) )
                gra_mag_s(i,j) = 0;
            end
        end
    end
end

%% Hysteresis thresholding and forming the output image

if isempty(Th)
    Tl = .10*max(max(gra_mag_s));
    Th = .30*max(max(gra_mag_s));
end

h_thr_im = gra_mag_s;
h_thr_im(gra_mag_s<Th) = 0;

l_thr_im = gra_mag_s;
l_thr_im(gra_mag_s<Tl) = 0;

        switch feature
            case 'morph'
            
h_thr_im = logical(h_thr_im);
l_thr_im = logical(l_thr_im);

[ii jj] = find(h_thr_im);

imo = bwselect(l_thr_im , jj , ii , 8);
imo = bwmorph(imo , 'thin' , 1);

            case 'dfs'
                
for i=2:r-1
    for j=2:c-1
        if h_thr_im (i,j)>0
            h_thr_im = edge_follow(h_thr_im,l_thr_im,i,j);
        end
    end
end

imo = logical(h_thr_im);

        end
        
    case 'log'
        
imo = edge(imi,method,Th,sigma);

end

if nargout<1
    imshow(imo)
else
    varargout{1} = imo;
end


function thr_h = edge_follow(thr_h,thr_l,i,j)
    
    x = [-1  0  1 -1 1 -1 0 1];               % Relative coordinates of 8 neighbors
    y = [-1 -1 -1  0 0  1 1 1];
    
    
    for k=1:8
        if thr_h(i+x(k),j+y(k))==0 && thr_l(i+x(k),j+y(k))~=0
            thr_h(i+x(k),j+y(k)) = -thr_l(i+x(k),j+y(k));
            thr_h = edge_follow(thr_h,thr_l,i+x(k),j+y(k));
        end
    end

end

function [imi,sigma,method,Th,Tl,feature] = parse_inputs(varargin)
   
    imi = varargin{1};
    sigma = 1;                  % defaults
    method = 'canny';
    Th = [];                    % the defaults for the thresholds will be 
    Tl = [];                    % specified after the gradient magnitude is 
    feature = 'dfs';            % calculated.
    
    methods = {'canny','log'};
    features = {'dfs','morph'};
    sigma_default = false;
    
    if nargin<1
        error('Not enough input arguments.')
    end
    
    if nargin>=2
        
        if ischar(varargin{2})
            if length(strmatch(varargin{2},methods))~=1
                error('Wrong input for edge detection method')
            else
                method=varargin{2};
                sigma_default = true;
            end
            
        else
            sigma=varargin{2};
        end
    end
        
    if nargin>=3
        
        if sigma_default
            Th = varargin{3};
            Tl = Th/3;
        else
            if length(strmatch(varargin{3},methods))~=1
                error('Wrong input for edge detection method')
            else
                method=varargin{3};
            end
        end
    end
    
    if nargin>=4
        
        if sigma_default
            if strcmp(method,'canny')
                Tl = varargin{4};
            else
                error('Wrong sequence or number of inputs for "log" method')
            end
        else
            Th = varargin{4};
        end
    end
    
    if nargin>=5
        if strcmp(method,'log')
            error('Too many input arguments!')
        else
            if sigma_default
                if ischar(varargin{5})
                    if length(strmatch(varargin{5},features))~=1
                        error('Wrong input for hysteresis thresholding method')
                    else
                        feature = varargin{5};
                    end
                else
                    error('Wrong usage of input argument "feature"!')
                end
            else
                Tl = varargin{5};
            end
        end
    end
    
    if nargin>=6
        if sigma_default
            error('Too many input arguments!')
        else
            if ischar(varargin{6})
                if length(strmatch(varargin{6},features))~=1
                    error('Wrong input for hysteresis thresholding method')
                else
                    feature = varargin{5};
                end
            else
                error('Wrong usage of input argument "feature"!')
            end
        end
    end
    
    if nargin>6
        error('Too many input arguments')
    end
end

end

Contact us at files@mathworks.com