Simple digital image processing

When it comes to digital image manipulation there are many applications that provide various possibilities to process images. But most often they do not offer exactly what I’m looking for. So finally I decided to implement my own tools to manipulate digital images.

An image can basically be regarded as a 2 dimensional array of pixels. These pixels are usually coded in RGB. That means in each pixel there is an integer value for the colour red, one for the colour green and one for blue. But for the mathematical treatment I consider a binary image that just has some grey scaled pixels.

To smoothen such an image we can in a simplest approach just take the surrounding pixels pij of one pixel pxy and set this center pixel pxy to the mean of all the surrounding pixels plus the center pixel itself.

For these 9 pixels that would mean



ImageProc


ImageProc

This must be done for all pixels (except for the border pixels. These can be left :-))


ImageProc

If the smoothing function is written as a matrix..


ImageProc

The operation to process the pixels with this filter can be regarded as a 2 dimensional convolution:


ImageProc


In a short C# sequence that would look like:


int x, y, i, j;
double R, G, B;
Bitmap bmpDest = new Bitmap(pictureBox2.Image);
for (x = 1; x < bmpSource.Width - 1; x++)
{
     for (y = 1; y < bmpSource.Height - 1; y++)
     {
         R = 0;
         G = 0;
         B = 0;
         for (i = 0; i < order; i++)
         {
              for (j = 0; j < order; j++)
              {
                   Color pix = bmpSource.GetPixel(x - 1 + i, y - 1 + j);
                   R += (pix.R * s[i, j]);
                   G += (pix.G * s[i, j]);
                   B += (pix.B * s[i, j]);
              }
         }
         if (R < 0)
              R = 0;
         else
         {
              if (R > 255)
                   R = 255;
         }
         if (G < 0)
              G = 0;
         else
         {
              if (G > 255)
                   G = 255;
         }
         if (B < 0)
              B = 0;
         else
         {
              if (B > 255)
                   B = 255;
         }
         Color newPix = Color.FromArgb((int)(255 - R), (int)(255 - G), (int)(255 - B));
         bmpDest.SetPixel(x, y, newPix);
     }
}


With the filter matrix.


double[,] s = {    { 1.0/9.0, 1.0/9.0, 1.0/9.0 },

           { 1.0/9.0, 1.0/9.0, 1.0/9.0 },
           { 1.0/9.0, 1.0/9.0, 1.0/9.0 } };


Of course this filter must be applied to all 3 colours R, G and B and the computed pixels must be limited to the range between 0 to 255.


If this smoothing operation is applied to the moon on the left side below

ImageProcImageProc



We get the output on the right side. There is not too much change to be seen, but if we zoom out to lower part, the right side is a bit blurred. That’s the result of the smoothing


ImageProcImageProc



The smoothing becomes a little better if a Gauss low pass filter is applied. This low pass filter is defined as

ImageProc

It uses a kind of weight function. The closer the pixel the more it is weighted. With this the smoothed moon becomes:

ImageProc

The image is a little less blurred.


The opposite of smoothing is sharpening. Therefore the second derivation of the pixel colour sequence is built and subtracted from the original sequence. The first derivation with respect to x is

ImageProc

And the second derivation:

ImageProc


For the second derivation with respect to y it’s the similar approach and becomes:

ImageProc


With these the derivation for both directions is:

ImageProc


And as Δx = Δy = 1 pixel

ImageProc


Or in matrix form:

ImageProc


This matrix is the so called Laplace filter (the deviation see Discrete Laplace operator - Wikipedia).


If there is an edge on a image, that could be the change from a small value to a big value in a colour like:

ImageProc


The first derivation of this is

ImageProc


And the second derivation would be somewhat like:

ImageProc


To sharpen the edge we have to subtract the second derivation from the original shape of the edge. That would become like:

ImageProc


The change becomes steeper and sharper.


Now we have to create a matrix form that just copies the original picture. That’s quite simple:

ImageProc


and the final filter to sharpen edges the second derivation subtracted from this matrix:

ImageProc


With this filter the moon becomes:

ImageProc


The kraters become a bit sharper.


Some say the Laplace filter is quite sensitive for noise and recommend a filter like

ImageProc


And with the original image included:

ImageProc


That creates the following sharpened image:

ImageProc


A bit too sharp in my opinion :-)

That can be improved slightly by reducing the values in the filter like:

ImageProc


This is like reducing the amplification of the filter and shows a little smoother output:

ImageProc


Basically it is possible to play around with the values in all the filters. That changes their output quite a bit :-)


Another interesting possibility of the Laplace filter, if it is used as is, is the possibility to highlight edges. If I apply the filter

ImageProc


Applied to a colour photography like the Monterosa hut


ImageProc



And inverting the colours. I get


ImageProc



Only the edges remain. But they are a bit too weak. To make them stronger we can just multiply each value of the Laplace filter by an amplification like


ImageProc



Then the image becomes:

ImageProc



But now there is still too much grey on the image. This can be removed by applying a threshold to the pixel colours and say if a pixel value is bigger than this threshold, we set it to 255 and otherwise we set it to 0.

With this the processing




private Bitmap ProcessImage(Bitmap bmpSource, double[,] s, int order, int threshold)
{
     int x, y, i, j;
     double R, G, B;
     Bitmap bmpDest = new Bitmap(pictureBox2.Image);
     for (x = 1; x < bmpSource.Width - 1; x++)
     {
         for (y = 1; y < bmpSource.Height - 1; y++)
         {
              R = 0;
              G = 0;
              B = 0;
              for (i = 0; i < order; i++)
              {
                   for (j = 0; j < order; j++)
                   {
                        Color pix = bmpSource.GetPixel(x - 1 + i, y - 1 + j);
                        R += (pix.R * s[i, j]);
                        G += (pix.G * s[i, j]);
                        B += (pix.B * s[i, j]);
                   }
              }
              if (R < 0)
                   R = 0;
              else
              {
                   if (R > 255)
                        R = 255;
              }
              if (G < 0)
                   G = 0;
              else
              {
                   if (G > 255)
                        G = 255;
              }
              if (B < 0)
                   B = 0;
              else
              {
                   if (B > 255)
                        B = 255;
              }
              if(Math.Sqrt(R*R + G*G + B*B) > threshold)
              {
                   R = 255;
                   G = 255;
                   B = 255;
              }
              else
              {
                   R = 0;
                   G = 0;
                   B = 0;
              }
              Color newPix = Color.FromArgb((int)(255 - R), (int)(255 - G), (int)(255 - B));
              bmpDest.SetPixel(x, y, newPix);
         }
     }
     return bmpDest;
}



With threshold = 120 and


double[,] s = { { -3, -3, -3 },
           { -3, 24, -3 },
           { -3, -3, -3 } };




Creates the output:

ImageProc

A perfect pencil drawing :-)


With this kind of filtering it becomes easy to detect lines or curves or forms and subjects in the drawing.



C# Demo Projects digital image filter
  • Img_Filter.zip
  • Img_Pencil.zip