Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Image Processing for Dummies with C# and GDI+ Part 3 - Edge Detection Filters

4.69/5 (8 votes)
11 May 2015Public Domain4 min read 32.9K   2.9K  
This would be the alternative only to the one algorithm for edge detection described inside the main article, Difference Edge Detection.

Image 1

Program overview

Simple window form application for finding contours of objects at image. For running release version of program it is necessary to have Microsoft .Net framework ver. 4.0 or higher installed.

Program controls :

 - Click on the original image (left image panel) will open a dialog to load a new image
 - Click on the resulting image (right image panel) will open a dialog to save a result image
 - Changing the limit values for brightness of points, automatically starts
    new processing of the original image
 - Changing the type of display of the processed image, automatically starts
    new processing of the original image

Introduction

Program detects contours (edges) of objects at image saved as JPЕG or BMP standard 24 bits RGB format. Detection of edge points is performed by algorithm which represents a variation of "Difference Edge Detection" algorithm shown at main article.

"Difference Edge Detection" is based on the assumption that the contour (edge) of the object at the image is noticeable with the naked eye if there is a significant difference in between the color of pair of opposing pixels positioned directly around the observed point that belongs to the edge of object. This refers to the pair of points which can form the straight line that passes through the observed point.

         0 0 0   0 1 0   1 0 0   0 0 1      2 - observed point
         1 2 1   0 2 0   0 2 0   0 2 0      1 - a pair of opposing points
         0 0 0   0 1 0   0 0 1   1 0 0 
 
In the essence, we must find the maximum value for color difference,  between four pairs of opposing points and if this value is different from the pre-defined, we assign a different value for the color to the observed point.
At the resulting picture this point becomes edge point.
 
A variation of the previous algorithm that is implemented in program consists in the fact that instead of color, we observe difference in the value of Brightness of pair of opposing points in the image. If the maximum value for the brightness difference is less than the lower limit of the brightness, point is changing its base color to Black, and if it is greater than the value of the upper limit of brightness, point changes its base color to White.
 

Points of Interest

Although the difference in the pseudo code of two algorithms is minimum, there is significant diference in between implementation of algorithm. Instead of using unsafe code and direct access to the locked bitmap data in the memory by using pointers, we use Marshal.Copy method from System.Runtime.Interopservices that alows fast and safe copy of data from a managed array to an unmanaged memory pointer, or from an unmanaged memory pointer to a managed array.

After making copy of bitmap data from an unmanaged memory pointer to a managed one dimenzional byte array, we use that array with its values for RGB components of color of pixels, for edge detection process.

When image processing is done, new bitmap data were copied back from array to unmanaged memory pointer, and resulting image is shown to user.

Most important, image processing speed is the same and there is no need to think about safety issue of working with unsafe code. The results are different and quality depends on many parameters, resolution of original bitmap image(higher better slower / lower worse faster), amount of colors and their shades(less better / more worse), etc.

Using the code

The best thing is to download full solution of program written in IDE #Develop ver. 4.4, language C# ver 4.0. Microsoft .Net framework 4.0, where is the complete program code, with all necessary graphic user interface controls ready for building executable version of program. Here is presented only the main method for contour detection. Inside this method is used one global variable and several controls defined out of this method:

  • Original_image type of Bitmap  for storing original image from file,
  • Lower_Brightness_Limit type of NumericUpDown  for selecting value of lower brightness limit
  • Upper_Brightness_Limit type of NumericUpDown  for selecting value of upper brightness limit
  • Invert_Edge_Color type of CheckBox  for selecting type of detected edge color
  • Black_White type of RadioButton  for selecting type of resulting image color
  • Gray_Scale type of RadioButton  for selecting type of resulting image color
  • Image_2 type of PictureBox   for showing result image to user

If You would like to use this method as it is presented here, the above variable and controls are necessary to be defined inside Your program.

C#
void Start_Contour_Detection()
{
    // Copy original image to new bitmap
    Bitmap bmp = (Bitmap)Original_image.Clone();

    int Image_width = bmp.Width;
    int Image_height = bmp.Height;

    // Rectangle size
    Rectangle Rectangle = new Rectangle(0, 0, Image_width, Image_height);

    // Lock bitmap inside memory for faster processing and load bitmap data
    BitmapData bmp_Data = bmp.LockBits(Rectangle, ImageLockMode.ReadWrite, bmp.PixelFormat);


    // Load adress of the first byte of the bitmap,
    // it is pointer to the adress
    IntPtr bmp_First_byte_adress = bmp_Data.Scan0;


    //
    // Declaration of the matrix that should contain all bytes of the bitmap data
    //
    // Bitmap must contain 24 bits (three bytes) per pixel
    // One byte for each component of RGB color of pixels
    int Number_of_pixels = Image_width * Image_height;
    int Number_of_bytes  = Number_of_pixels * 3;
    //
    // Because of the way the bitmap data are stored in the memory,
    // so called 'bytes fine alignment inside the row',
    // number of bytes in a row is rounded up to the nearest number divisible by four
    // So that one row of the bitmap is always containing the same or larger number of bytes,
    // that is necessary for memorising data for the actual number of points in row
    int Exact_number_of_bytes_in_row  = bmp_Data.Stride;
    int Necessary_number_of_bytes = Image_width*3;
    //
    int Number_of_alignment_bytes =  Exact_number_of_bytes_in_row - Necessary_number_of_bytes;
    //
    // Total count of bytes necesary for all image pixels
    Number_of_bytes += Image_height * Number_of_alignment_bytes;
    //
    // One dimensional matrix for memorizing bitmap
    // values of RGB components of color of pixels
    byte[] bmp_RGB_values = new byte[Number_of_bytes];


    // Copy values of RGB components of pixel color from bitmap to matrix
    Marshal.Copy(bmp_First_byte_adress, bmp_RGB_values, 0, Number_of_bytes);



    // Two dimensional matrix for memorizing bitmap
    // values of RGB components of color of pixels
    byte [,,] RGB = new byte[Image_width,Image_height,3];

    // Matrix for memorizing values of brightness of pixels
    float [,] Brightness = new float[Image_width,Image_height];

    // Byte counter inside one dimenzional matrix
    int bmp_k = 0;


    // Copy bitmap values of RGB components of color of pixels,
    // from one dimenzional to two dimenzional matrix
    // and fill matrix Brightness with values of brightness of pixels
    //
    // NOTICE :
    // When loading bitmap data
    // BitmapData bmp_Data = bmp.LockBits(Rectangle, ImageLockMode.ReadWrite, bmp.PixelFormat); ,
    // values of RGB components of color of pixels are returned in opposite direction
    // RGB -> BGR
    //
    for (int i=0;i < Image_height;i++)
    {
        for(int j=0;j < Image_width;j++)
        {
            // Value of R component of pixel color
            RGB[j,i,0] = bmp_RGB_values[bmp_k+2];

            // Value of G component of pixel color
            RGB[j,i,1] = bmp_RGB_values[bmp_k+1];

            // Value of B component of pixel color
            RGB[j,i,2] = bmp_RGB_values[bmp_k+0];

            // Value of pixel brightness
            Brightness[j,i] = Color.FromArgb
                (
                    bmp_RGB_values[bmp_k+2],
                    bmp_RGB_values[bmp_k+1],
                    bmp_RGB_values[bmp_k+0]
                ).GetBrightness();

            bmp_k+=3;
        }

        bmp_k+= Number_of_alignment_bytes;
    }


    // Load lower and upper limit of the brightness
    float lower_limit  = (float) Lower_Brightness_Limit.Value;
    float upper_limit  = (float) Upper_Brightness_Limit.Value;

    // Maximum found value for the difference in brightness
    // between the opposing pixels
    float mfd = 0;

    for(int i=1;i < Image_height-1;i++)
    {
        for(int j=1;j < Image_width-1;j++)
        {

            //
            mfd = Math.Abs(Brightness[j-1,i-1]-Brightness[j+1,i+1]);

            //
            if(mfd < Math.Abs(Brightness[j-1,i+1]-Brightness[j+1,i-1]))
                mfd=Math.Abs(Brightness[j-1,i+1]-Brightness[j+1,i-1]);

            //
            if(mfd < Math.Abs(Brightness[j,i+1]-Brightness[j,i-1]))
                mfd=Math.Abs(Brightness[j,i+1]-Brightness[j,i-1]);

            //
            if(mfd < Math.Abs(Brightness[j-1,i]-Brightness[j+1,i]))
                mfd=Math.Abs(Brightness[j-1,i]-Brightness[j+1,i]);

            //
            if(Invert_Edge_Color.Checked)
            {
                if(mfd < lower_limit)
                {
                    RGB[j,i,0] = (byte) 255;
                    RGB[j,i,1] = (byte) 255;
                    RGB[j,i,2] = (byte) 255;
                }
                else if(mfd > upper_limit)
                {
                    RGB[j,i,0] = (byte) 0;
                    RGB[j,i,1] = (byte) 0;
                    RGB[j,i,2] = (byte) 0;
                }
            }
            else
            {
                if(mfd < lower_limit)
                {
                    RGB[j,i,0] = (byte) 0;
                    RGB[j,i,1] = (byte) 0;
                    RGB[j,i,2] = (byte) 0;
                }
                else if(mfd > upper_limit)
                {
                    RGB[j,i,0] = (byte) 255;
                    RGB[j,i,1] = (byte) 255;
                    RGB[j,i,2] = (byte) 255;
                }
            }

        }
    }


    if(Black_White.Checked)
    {
        for(int i=1;i < Image_height-1;i++)
        {
            for(int j=1;j < Image_width-1;j++)
            {
                if(Invert_Edge_Color.Checked)
                {
                    if(RGB[j,i,0] < 255 || RGB[j,i,1] < 255 || RGB[j,i,2] < 255)
                        RGB[j,i,0] = RGB[j,i,1] = RGB[j,i,2] = (byte) 0;
                }
                else
                {
                    if(RGB[j,i,0] > 0 || RGB[j,i,1] > 0 || RGB[j,i,2] > 0)
                        RGB[j,i,0] = RGB[j,i,1] = RGB[j,i,2] = (byte) 255;
                }
            }
        }
    }


    if(Gray_Scale.Checked)
    {
        for(int i=1;i < Image_height-1;i++)
        {
            for(int j=1;j < Image_width-1;j++)
            {
                RGB[j,i,0] = RGB[j,i,1] = RGB[j,i,2] =
                    (byte)
                    (
                        (0.299*RGB[j,i,0]) +
                        (0.587*RGB[j,i,1]) +
                        (0.114*RGB[j,i,2])
                    );
            }
        }
    }


    // Byte counter inside one dimenzional matrix
    bmp_k = 0;

    // Copy new bitmap values of RGB components of color of pixels,
    // from two dimenzional to one dimenzional matrix
    for (int i=0;i < Image_height;i++)
    {
        for(int j=0;j < Image_width;j++)
        {
            // Value of R component of pixel color
            bmp_RGB_values[bmp_k+2] = RGB[j,i,0];

            // Value of G component of pixel color
            bmp_RGB_values[bmp_k+1] = RGB[j,i,1];

            // Value of B component of pixel color
            bmp_RGB_values[bmp_k+0] = RGB[j,i,2];

            bmp_k+=3;
        }

        bmp_k+=Number_of_alignment_bytes;
    }

    // Copy new values of RGB components of pixel color from matrix back to bitmap
    Marshal.Copy(bmp_RGB_values, 0, bmp_First_byte_adress, Number_of_bytes);

    // Unlock bitmap inside memory
    bmp.UnlockBits(bmp_Data);

    // Show the processed image
    Image_2.Image = bmp;
}

History

Last revised 10.05.2015. 19:00, author

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication