Edge detectors


In principle any simple convolution filter acting as a derivative has some capacity for edge detecting. For instance, for a differentiable two variable function f,

f(h,h) - f(-h,h) + 2f(h,0) - 2f(-h,0) + f(h,-h) - f(-h,-h)

is a second order approximation of 8hfx(0,0). This leads to the horizontal and vertical filters H and V corresponding to fx and fy given by

 
-101
8H= -202
  -101
and
 
-1-2-1
8V= 000
  121

The drawback is that the filter is biased to edges in certain direction.

More promising, and still a linear convolution filter, is the Laplacian filter

 
0-10
L= -14-1
  0-10

This is a discrete version of the Laplace operator in mathematical analysis which is rotation invariant. The inconvenience is that in the discrete setting we lose the perfect invariance. On the other hand, this filter depends on second derivatives and then it can show an undesired sensitivity to changes in the curvature not related to the jumps that characterize the edges. All the time we have in mind a B/W image F as a function (see a mathematical definition here).

A better option is the Sobel filter. It is a nonlinear filter acting on the image F as the square root of (H*F)2+(V*F)2 where * indicates the convolution. This tries to represent the norm of the gradient, which is rotation invariant.

Any of these filters after clipping to [0,1] (0=black, 1=white) gives in general a non binary image and then the negative output seems a drawing with a charcoal pencil. To get hard edges one can establish a threshold t such that a gray tone above t becomes white and the rest are replaced by black.

Apart from this simple filter approach to edge detecting, there are more involved methods. Arguably the most employed is the Canny edge detector. It tends to create thin edges and the negative of the output recalls more to a pen outline than to the aforementioned charcoal pencil drawing.


The octave code below applies these filters to the 640x480 test image

bruch_r2_bw

Let us see first the effect of the filters H, V, and L without any threshold. We boosts the result of H and V multiplying the filters by 4 because otherwise the images would be very faint. This is related to the format jpg of the original image that then to smooth the edges if they are not in the boundary of an 8x8 block. Note that V forgets the vertical edges (and H the horizontal edges). This is natural because if corresponds to a derivative in the vertical direction.

bruch_r2_bw
4H filter (no threshold)

bruch_r2_bw
4V filter (no threshold)

bruch_r2_bw
L filter (no threshold)

Now let see the effect of S in two variants. First, clipping to [0,1] as before and later scaling from [min,max] to [0,1]. This stretching improves the contrast.

edg_Sc
Sobel filter (clipped)

edg_Ss
Sobel filter (scaled)

Now we consider a threshold t. When t is higher we will get more edges, when it approach to 1 we could get a completely black image. Experimenting with L the best result I have got is:

edg_L9
L filter t=0.9

The result improves with the Sobel filter. Here there are two examples:

edg_S5
Sobel filter t=0.95

edg_S8
Sobel filter t=0.98

Finally, let us see the effect of the Canny edge detector as implemented in octave with the default parameters:

edg_C
Canny edge detector

The code

This is the octave code that produces the images:

pkg load image


name = './images/bruch_r2_bw.jpg';


% original image
ima = imread( name );
ima = im2double(ima);

H = [-1 0 1; -2 0 2; -1 0 1]/8;
V = [-1 -2 -1; 0 0 0; 1 2 1]/8;
L = [0 -1 0; -1 4 -1; 0 -1 0];

% 4H
imwrite( convfi( ima, 4*H, -1, 1 ) ,'./edg_H.jpg');

% 4V
imwrite( convfi( ima, 4*V, -1, 1 ) ,'./edg_V.jpg');

% L
imwrite( convfi( ima, L, -1, 1 ) ,'./edg_L.jpg');


% Sobel scaled / clipped
res = sqrt( convfi( ima, H, -1, 0 ).^2 + convfi( ima, V, -1, 0 ).^2 );
imwrite( convfi( res, [1], -1, 2 ) ,'./edg_Ss.jpg');
imwrite( convfi( res, [1], -1, 1 ) ,'./edg_Sc.jpg');


% L binary 0.9
imwrite( convfi( ima, L, 0.9, 1 ) ,'./edg_L9.png');


% Sobel binary 0.5, 0.8
res = sqrt( convfi( ima, H, -1, 0 ).^2 + convfi( ima, V, -1, 0 ).^2 );
imwrite( convfi( res, [1], 0.95, 1 ) ,'./edg_S5.png');
imwrite( convfi( res, [1], 0.98, 1 ) ,'./edg_S8.png');


% Canny
imwrite( 1-edge( ima, "canny") ,'./edg_C.png');

It calls to the function:

% Convolution filter to an image
% ima = image
% filt = filter (matrix)
% t = threshold for binary images if t<0 no threshold
% cf = 1->negative clamping 2-> negative streching

function res = convfi( ima, filt,t, cf )
  res = imfilter( ima, filt, 'same', 'conv', 'symmetric');
  
  
  if cf==1
    res = 1-max(0,min(1,res));
  end
  if cf==2
    res = im2double(mat2gray(-res));
  end
  
  if t>=0
    res = (res>t);
  end
  
end