Quasar > Image Processing > Edge detection > cannyedge
%==============================================================================
% cannyedge.q
% Canny edge detector in Quasar
% Bart Goossens, Nov 2013
% Note that this is an experimental program (the hysteresis thresholding is
% not recursive yet) - feel free to change it and send me the updated version
%==============================================================================
import "imfilter.q"

{!author name="Bart Goossens"}
{!doc category="Image Processing/Edge detection"}

% Function: cannyedge
%
% Implementation of the Canny edge detector
%
% Usage:
%
% :function y = cannyedge(x : cube, sigma=1, low_threshold=1, high_threshold=0.1)
%
% Parameters:
% x - the input image
% sigma - parameter for the Gaussian filter (used for smoothing)
% low_threshold - hysteresis lower threshold
% high_threshold - hysteresis higher threshold
%
% Remarks:
%
% The steps involved:
%
% * Smooth using the Gaussian with sigma above.
%
% * Apply the horizontal and vertical Sobel operators to get the gradients
%   within the image. The edge strength is the sum of the magnitudes
%   of the gradients in each direction.
%
% * Find the normal to the edge at each point using the arctangent of the
%   ratio of the Y sobel over the X sobel. Pragmatically, we can
%   look at the signs of X and Y and the relative magnitude of X vs Y
%   to sort the points into 4 categories: horizontal, vertical,
%   diagonal and antidiagonal.
%
% * Look in the normal and reverse directions to see if the values
%   in either of those directions are greater than the point in question.
%   Use interpolation to get a mix of points instead of picking the one
%   that's the closest to the normal.
%
% * Label all points above the high threshold as edges.
% * Recursively label any point above the low threshold that is 8-connected
%   to a labeled point as an edge.
%
function y = cannyedge(x : cube, sigma=1, lowThreshold : scalar =5, highThreshold : scalar =10)

    smoothed = gaussian_filter(x, sigma)
    %sobel : ccube = complex(hor_sobel(x), ver_sobel(x))
    sobel = complex(hor_sobel(x), ver_sobel(x))
    theta = mod(pi/2+angle(sobel),pi)
    magnitude = abs(sobel)    
    magnitude = (255 / max(magnitude)) * magnitude
    
    % Calculate the orientation (closest angle from 0, 45, 90, 135)
    dst = zeros(size(x))
    #pragma force_parallel
    for m=0..size(x,0)-1
        for n=0..size(x,1)-1
            for k=0..size(x,2)-1
                t = theta[m,n,k] * 180/pi
                if t < 0
                    t += 180.0
                endif
                if t < 22.5  % 0°
                    left = magnitude[m, n - 1, k]
                    right = magnitude[m, n + 1, k]
                elseif t < 67.5  % 45°
                    left = magnitude[m + 1, n - 1, k]
                    right = magnitude[m - 1, n + 1, k]
                elseif t < 112.5  % 90°
                    left = magnitude[m + 1, n, k]
                    right = magnitude[m - 1, n, k]
                elseif t < 157.5  % 135°
                    left = magnitude[m + 1, n + 1, k]
                    right = magnitude[m - 1, n - 1, k]
                else  % 0°
                    left = magnitude[m, n - 1, k]
                    right = magnitude[m, n + 1, k]
                endif
                
                % Compare current pixel value with adjacent pixels
                grad = magnitude[m, n, k]
                if grad < left || grad < right
                    dst[m, n, k] = 0.0
                else
                    dst[m, n, k] = grad
                endif
            end
        end
    end   

    % Apply hysteresis thresholding - to do: this should be recursive...
    y = copy(dst)
    #pragma force_parallel    
    for m=0..size(x,0)-1
        for n=0..size(x,1)-1
            for k=0..size(x,2)-1
                if dst[m,n,k] < highThreshold
                    if dst[m,n,k] < lowThreshold
                        % non-edge
                        y[m,n,k] = 0.0
                    % check 8 neighboring pixels                        
                    elseif dst[m-1,n-1,k] < highThreshold && _
                           dst[m,n-1,k] < highThreshold && _
                           dst[m+1,n-1,k] < highThreshold && _
                           dst[m-1,n,k] < highThreshold && _
                           dst[m+1,n,k] < highThreshold && _
                           dst[m-1,n+1,k] < highThreshold && _
                           dst[m,n+1,k] < highThreshold && _
                           dst[m+1,n+1,k] < highThreshold 
                           y[m,n,k] = 0.0
                    endif
                endif
            end
        end
    end

end

function [] = main()
    im = imread("lena_big.tif")[:,:,1]
    tic()
    y = cannyedge(im)
    toc()
    imshow(y,[])

end