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






4.69/5 (8 votes)
This would be the alternative only to the one algorithm for edge detection described inside the main article, "Difference Edge Detection".
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
"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.
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 limitUpper_Brightness_Limit type of NumericUpDown
for selecting value of upper brightness limitInvert_Edge_Color type of CheckBox
for selecting type of detected edge colorBlack_White type of RadioButton
for selecting type of resulting image colorGray_Scale type of RadioButton
for selecting type of resulting image colorImage_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.
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