Simple filters and pencil line drawing effect


Choose a photo. For instance a beautiful photo like this:
niki.jpg
We are going to apply very simple convolution filters to transform it into something that recalls a pencil line drawing effect. The point here is the simplicity, of course you can do it better with your favorite image retouching software.
The filters are applied with a simple matlab code include below but the computations can be imitated using free software. For fast performance a possibility is to write some C/C++ code with the open source FreeImage library.
First of all, we convert the photo into a B/W image (imag = rgb2gray(imag)). A finite 2D convolution filter can be considered as a matrix. The key to simulate a pencil drawing is to detect the edges of the image. Our first choice is the horizontal-vertical filter:
HV=[0, 1, 0; 1, -4, 1; 0, 1, 0]
For an "interior pixel" the value (the gray level) of a point is more or less the average of the values of the horizontal and vertical  neighbors. Then the filter gives 0 (that we represent as white in the following images). For a sharp edge this valence is lost and we should see something. The pixel values are clamped to [0,255] in matlab, then beyond certain value we always see black corresponding to 255 in the following images (if you are wondering this switching with respect to the usual convention, is done by imagf = 255-imagf). The result is not very appealing:
nikif01.jpg
What is wrong? Perhaps the point is that we have to consider also diagonal (oblique) directions for the average. Then our next filter is:
Ob=[1, 1, 1; 1, -8, 1; 1, 1, 1]
The zero average assures that interior pixels are disregarded. The result improves a little but the result seems not dense and a little random.
nikif02.jpg
The explanation is that the previous filters detect variations in a range of 3 pixels and this is really tiny in a usual computer monitor, less than a millimeter. This suggests working with double sized blocks. Consider
HV'= [O, I, O; , -4I, I; O, I, O] with O=[0, 0; 0, 0] and I=[1, 1; 1, 1]
The result with this 6x6 matrix improves considerably:
nikif02.jpg
We can define Ob' in the same way
Ob'=[I, I, I; I, -8I, I; I, I, I]
to get:
nikif04.jpg
This is not bad (taking into account the simple filters that we are considering). The too dark aspect comes from clamping, recall that any value over 255 is clamped to 255 and correspond to black.  Note also that Ob' has all of its 36 entries different form 0 and then it is easy to reach 255. An idea is to scale the filter dividing the filter matrix by something. Playing with the lower limit for the clamping  we can enhance whites with some precision to reduce noise.  The result for the filter Ob'/8 with  the pixel values clamped to [15, 255] ([0,240] after inversion, this is the line imshow( imagf, [0,240])) looks great:
nikif05.jpg


The code

This is the matlab code that generates the images:



imag = imread('niki.jpg');
%imshow( imag )
imag = rgb2gray(imag);
figure(1)
imshow( imag )


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
h2 = [0 1 0;1 -4 1;0 1 0]; %edges 4
imagf = imfilter( imag, h2, 'conv');
imagf = 255-imagf;
figure(2)
imshow( imagf )
imwrite(imagf, 'nikif01.bmp')

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
h2 = [1 1 1;1 -8 1;1 1 1]; %edges 8
imagf = imfilter( imag, h2, 'conv');
imagf = 255-imagf;
figure(3)
imshow( imagf )
imwrite(imagf, 'nikif02.bmp')

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
h2 =   [0 0 1 1 0 0;
    0 0 1 1 0 0;
    1 1 -4 -4 1 1;
    1 1 -4 -4 1 1;
    0 0 1 1 0 0;
    0 0 1 1 0 0]; % edges 4 2blocks
imagf = imfilter( imag, h2, 'conv');
imagf = 255-imagf;
figure(4)
imshow( imagf )
imwrite(imagf, 'nikif03.bmp')

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
h2 =   [1 1 1 1 1 1;
    1 1 1 1 1 1;
    1 1 -8 -8 1 1;
    1 1 -8 -8 1 1;
    1 1 1 1 1 1;
    1 1 1 1 1 1]; % edges 8 2blocks
imagf = imfilter( imag, h2, 'conv');
imagf = 255-imagf;
figure(5)
imshow( imagf )
imwrite(imagf, 'nikif04.bmp')



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
h2 =   [1 1 1 1 1 1;
    1 1 1 1 1 1;
    1 1 -8 -8 1 1;
    1 1 -8 -8 1 1;
    1 1 1 1 1 1;
    1 1 1 1 1 1]/8; % edges 8 2blocks weight 1/8
imagf = imfilter( imag, h2, 'conv');
imagf = 255-imagf;
figure(6)
imshow( imagf, [0,240])
imwrite(imagf, 'nikif05.bmp')