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
|
and |
|
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 | -1 | 0 | |||
L= | -1 | 4 | -1 | ||
0 | -1 | 0 |
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
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.
4H
filter (no threshold)
|
4V
filter (no threshold)
|
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.
Sobel
filter (clipped)
|
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:
L
filter
t=0.9
|
The result improves with the Sobel filter. Here there are two examples:
Sobel
filter
t=0.95
|
Sobel
filter
t=0.98
|
Finally, let us see the effect of the Canny edge detector as implemented in octave with the default parameters:
Canny edge detector |
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