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.
void Start_Contour_Detection()
{
Bitmap bmp = (Bitmap)Original_image.Clone();
int Image_width = bmp.Width;
int Image_height = bmp.Height;
Rectangle Rectangle = new Rectangle(0, 0, Image_width, Image_height);
BitmapData bmp_Data = bmp.LockBits(Rectangle, ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr bmp_First_byte_adress = bmp_Data.Scan0;
int Number_of_pixels = Image_width * Image_height;
int Number_of_bytes = Number_of_pixels * 3;
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;
Number_of_bytes += Image_height * Number_of_alignment_bytes;
byte[] bmp_RGB_values = new byte[Number_of_bytes];
Marshal.Copy(bmp_First_byte_adress, bmp_RGB_values, 0, Number_of_bytes);
byte [,,] RGB = new byte[Image_width,Image_height,3];
float [,] Brightness = new float[Image_width,Image_height];
int bmp_k = 0;
for (int i=0;i < Image_height;i++)
{
for(int j=0;j < Image_width;j++)
{
RGB[j,i,0] = bmp_RGB_values[bmp_k+2];
RGB[j,i,1] = bmp_RGB_values[bmp_k+1];
RGB[j,i,2] = bmp_RGB_values[bmp_k+0];
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;
}
float lower_limit = (float) Lower_Brightness_Limit.Value;
float upper_limit = (float) Upper_Brightness_Limit.Value;
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])
);
}
}
}
bmp_k = 0;
for (int i=0;i < Image_height;i++)
{
for(int j=0;j < Image_width;j++)
{
bmp_RGB_values[bmp_k+2] = RGB[j,i,0];
bmp_RGB_values[bmp_k+1] = RGB[j,i,1];
bmp_RGB_values[bmp_k+0] = RGB[j,i,2];
bmp_k+=3;
}
bmp_k+=Number_of_alignment_bytes;
}
Marshal.Copy(bmp_RGB_values, 0, bmp_First_byte_adress, Number_of_bytes);
bmp.UnlockBits(bmp_Data);
Image_2.Image = bmp;
}
History
Last revised 10.05.2015. 19:00, author