Image Processing Lab in C# within AForge.NET framework




Introduction

Image Processing Lab is a simple tool for image processing, which includes different filters and tools to analyze images available in the AForge.NET framework. It's easy to develop your own filters and to integrate them with the code or use the tools in your own application. The following filters are implemented in the AForge.NET framework and demonstrated in the application:
  • Color filters (grayscale, sepia, invert, rotate, channel extraction, channel replacing, channel filtering, color filtering, Euclidean color filtering);
  • HSL filters (linear correction, brightness, contrast, saturation, hue modifier, HSL filtering);
  • YCbCr filters (linear correction, YCbCr filtering, channel extraction/replacement);
  • Binarization filters (threshold, threshold with carry, ordered dithering, Bayer dithering, Floyd-Steinberg, Burkes, Jarvis-Judice-Ninke, Sierra, Stevenson-Arce, Stucki dithering methods);
  • Automatic binarization (simple image statistics);
  • Mathematical morphology filters (erosion, dilatation, opening, closing, hit & miss, thinning, thickening);
  • Convolution filters (mean, blur, sharpen, edges, Gaussian);
  • 2 Source filters (merge, intersect, add, subtract, difference, move towards, morph);
  • Edge detectors (homogeneity, difference, sobel, canny);
  • Blob counter, Connected components labeling;
  • Pixellate, Simple skeletonization, Jitter, Shrink, Oil painting;
  • Levels linear filter, gamma correction;
  • Median filter, Adaptive smoothing, Conservative smoothing;
  • Resize and Rotate;
  • Texture generators based on Perlin noise;
  • Texture filters (texturer, textured filtering, textured merging);
  • Fourier transformation (lowpass and hipass filters).
You can create (save and load) your own convolution filters or filters based on standard mathematical morphology operators. Colorized grid makes it very convenient to work with custom convolution filters.
A preview window allows you to view the results of changing filter parameters on the fly. You can scroll an image using the mouse in the preview area. All filters are applied only to the portion of the image currently viewed to speed up preview.
A PhotoShop-like histogram allows you to get information about mean, standard deviation, median, minimum and maximum values.
The program allows you to copy to or paste from clipboard, save and print images.

Using the Code

Most filters are designed to work with 24bpp RGB images or with grayscale images. In the case of grayscale images, we use PixelFormat.Format8bppIndexed with color palette of 256 entries. To guarantee that your image is in one of the formats, you can use the following code:
// load an image
System.Drawing.Bitmap image = (Bitmap) Bitmap.FromFile( fileName );
// format image
AForge.Imaging.Image.FormatImage( ref image );
It is easy to apply any filter to your image:
// load an image
System.Drawing.Bitmap image = (Bitmap) Bitmap.FromFile( fileName );
// create filter
AForge.Imaging.Filters.Median filter = new AForge.Imaging.Filters.Median( );
// apply filter
System.Drawing.Bitmap newImage = filter.Apply( image );
Suppose, you want to apply a series of filters to an image. The straight way to do it is to apply filters one after another, but it's not very likely in the case of 3 or more filters. All filters implement the IFilter interface, so it allows us to create a collection of filters and apply it at once to an image (besides, the collection will also save us from disposing routines on intermediate images):
// create filters sequence AForge.Imaging.Filters.FiltersSequence
filter = new AForge.Imaging.Filters.FiltersSequence( );
// add filters to the sequence
filter.Add( new AForge.Imaging.Filters.Sepia( ) );
filter.Add( new AForge.Imaging.Filters.RotateBilinear( 45) );
filter.Add( new AForge.Imaging.Filters.ResizeBilinear( 320, 240 ) );
filter.Add( new AForge.Imaging.Filters.Pixellate( 8 ) );
filter.Add( new AForge.Imaging.Filters.Jitter( 2 ) );
filter.Add( new AForge.Imaging.Filters.Blur( ) );
// apply the sequence to an image
System.Drawing.Bitmap newImage = filter.Apply( image );
It's easy to get such image statistics as mean, standard deviation, median, minimum and maximum values. It can be useful for image brightness/contrast regulation.
// get image statistics
AForge.Imaging.ImageStatistics statistics =
    new AForge.Imaging.ImageStatistics( image );
// get the red histogram
AForge.Math.Histogram histogram = statistics.Red;
// get the values
double mean = histogram.Mean;     // mean red value
double stddev = histogram.StdDev; // standard deviation of red values
int    median = histogram.Median; // median red value
int    min = histogram.Min;       // min red value
int    max = histogram.Max;       // max value
// get 90% range around the median
AForge.IntRange range = histogram.GetRange( 0.9 );
Image statistics can be easily combined with filters. Suppose that the minimum value of red is 50 on the image and the maximum value is 200. So, we can normalize the contrast of the red channel:
// create levels filter
AForge.Imaging.Filters.LevelsLinear filter =
    new AForge.Imaging.Filters.LevelsLinear( );
filter.InRed = new IntRange( histogram.Min, histogram.Max );
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );
Or we can normalize the contrast of each channel, getting only the 90% ranges from each channel:
// create levels filter
AForge.Imaging.Filters.LevelsLinear filter =
    new AForge.Imaging.Filters.LevelsLinear( );
filter.InRed = statistics.Red.GetRange( 0.9 );
filter.InGreen = statistics.Green.GetRange( 0.9 );
filter.InBlue = statistics.Blue.GetRange( 0.9 );
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );

HSL Filters

Using HSL color space is more obvious for some sorts of filters. For example, it's not very clean, how to adjust saturation levels of an image using RGB color space. But it can be done easily, using HSL color space:
// create filter
AForge.Imaging.Filters.SaturationCorrection filter =
    new AForge.Imaging.Filters.SaturationCorrection( 0.1 );
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );
Initial image
Initial image
Saturation adjusted
Saturation adjusted
Using HSL color space, we can modify the hue value of pixels. Setting all hue values to the same value will lead to an image in gradations of one color:
// create filter
AForge.Imaging.Filters.HueModifier filter =
    new AForge.Imaging.Filters.HueModifier( 142 );
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );
It's possible to get much more interesting results using HSL filtering. For example, we can preserve only the specified range of hue values and desaturate all others out of the range. So, it will lead to a black and white image with only some regions colored.
// create filter
AForge.Imaging.Filters.HSLFiltering filter =
    new AForge.Imaging.Filters.HSLFiltering( );
filter.Hue = new IntRange( 340, 20 );
filter.UpdateHue = false;
filter.UpdateLuminance = false;
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );
Hue modified
Hue modified
HSL filtering
HSL filtering

Mathematical Morphology Filters

There are many tasks that can be accomplished using mathematical morphology filters. For example, we can reduce noise on binary images using erosion, or we can separate some objects with the filter. Using dilatation, we can grow some parts of our interest on the image. One of the most interesting morphological operators is known as Hit & Miss. All other morphological operators can be expressed from the Hit & Miss operator. For example, we can use it to search for particular structures on the image:
// searching for vertical lines
short[,] vse = new short[3, 3] {
    { 0, 1, 0 },
    { 0, 1, 0 },
    { 0, 1, 0 }
};
AForge.Imaging.Filters.HitAndMiss vFilter =
    new AForge.Imaging.Filters.HitAndMiss( vse );
System.Drawing.Bitmap vImage = vFilter.Apply( image );
// searching for horizontal lines
short[,] hse = new short[3, 3] {
    { 0, 0, 0 },
    { 1, 1, 1 },
    { 0, 0, 0 }
};
AForge.Imaging.Filters.HitAndMiss hFilter =
    new AForge.Imaging.Filters.HitAndMiss( hse );
System.Drawing.Bitmap hImage = hFilter.Apply( image );

Original image

Searching for vertical lines

Searching for horizontal lines
Using a thickening operator, we can grow some parts of the image in the places we need. For example, the next sample will lead to thickening horizontal lines in the bottom direction:
// create filter
AForge.Imaging.Filters.FilterIterator filter =
    new AForge.Imaging.Filters.FilterIterator(
    new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { 1, 1, 1 }, { -1, 0, -1 }, { -1, -1, -1 } },
    HitAndMiss.Modes.Thinning ), 5 );
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );

Original image

Thickened image
Using a thinning operator, you can remove some unnecessary parts of the image. For example, you can develop a skeletonization filter with appropriate structuring elements:
// create filter sequence
AForge.Imaging.Filters.FiltersSequence filterSequence =
    new AForge.Imaging.Filters.FiltersSequence( );
// add 8 thinning filters with different structuring elements
filterSequence.Add( new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { 0, 0, 0 }, { -1, 1, -1 }, { 1, 1, 1 } },
    HitAndMiss.Modes.Thinning ) );
filterSequence.Add( new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { -1, 0, 0 }, { 1, 1, 0 }, { -1, 1, -1 } },
    HitAndMiss.Modes.Thinning ) );
filterSequence.Add( new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { 1, -1, 0 }, { 1, 1, 0 }, { 1, -1, 0 } },
    HitAndMiss.Modes.Thinning ) );
filterSequence.Add( new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { -1, 1, -1 }, { 1, 1, 0 }, { -1, 0, 0 } },
    HitAndMiss.Modes.Thinning ) );
filterSequence.Add( new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { 1, 1, 1 }, { -1, 1, -1 }, { 0, 0, 0 } },
    HitAndMiss.Modes.Thinning ) );
filterSequence.Add( new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { -1, 1, -1 }, { 0, 1, 1 }, { 0, 0, -1 } },
    HitAndMiss.Modes.Thinning ) );
filterSequence.Add(new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { 0, -1, 1 }, { 0, 1, 1 }, { 0, -1, 1 } },
    HitAndMiss.Modes.Thinning ) );
filterSequence.Add( new AForge.Imaging.Filters.HitAndMiss(
    new short [,] { { 0, 0, -1 }, { 0, 1, 1 }, { -1, 1, -1 } },
    HitAndMiss.Modes.Thinning ) );
// create filter iterator for 10 iterations
AForge.Imaging.Filters.FilterIterator filter =
    new AForge.Imaging.Filters.FilterIterator( filterSequence, 10 );
// apply the filter
System.Drawing.Bitmap newImage = filter.Apply( image );

Original image

Thinned image

Fourier Transformation

It is easy to perform a Fourier transformation, which is useful for image analysis and filtering with the library:
// create complex image from bitmap
AForge.Imaging.ComplexImage cimage =
    AForge.Imaging.ComplexImage.FromBitmap( bitmap );
// perform forward Fourier transformation
cimage.ForwardFourierTransform( );
// get frequency view
System.Drawing.Bitmap img = cimage.ToBitmap( );
Lowpass and hipass filtering can be performed using the FrequencyFilter method of the ComplexImage class:
// lowpass filtering
cimage.FrequencyFilter( new Range( 0, 100 ) );
// perform backward Fourier transformation
cimage.BackwardFourierTransform( );
// get filtered image
System.Drawing.Bitmap img = cimage.ToBitmap( );

Blob Counter

Blob counter is a very useful feature and can be applied in many different applications. What does it do? It can count objects on a binary image and extract them. The idea comes from "Connected components labeling," a filter that colors each separate object with a different color. Let's look into a small sample:
// create filter
AForge.Imaging.Filters.ConnectedComponentsLabeling filter =
    new AForge.Imaging.Filters.ConnectedComponentsLabeling( );
// apply filter
System.Drawing.Bitmap newImage = filter.Apply( image );
// objects count
System.Diagnostics.Debug.WriteLine( "Objects count: " +
    filter.ObjectCount );
Here are two images: initial image and colored image. So, it looks like the filter is really able to count objects.
Initial image Colored image
Here is another example of objects counting and retrieving their position and size:
// process an image
AForge.Imaging.BlobCounter blobCounter = new BlobCounter( image );
Rectangle[] rects = blobCounter.GetObjectRectangles( );
// objects count
System.Diagnostics.Debug.WriteLine( "Objects count: " + rects.Length );
// objects dimension
foreach ( Rectangle rc in rects )
{
    System.Diagnostics.Debug.WriteLine(
        string.Format("Position: ({0}, {1}), Size: {2} x {3}",
        rc.Left, rc.Top, rc.Width, rc.Height ) );
}
It's possible to extract each object with the GetObjects method of BlobCounter:
// process an image
AForge.Imaging.BlobCounter blobCounter = new BlobCounter( image );
Blob[] blobs = blobCounter.GetObjects( image );
// process blobs
foreach ( Blob blob in blobs )
{
    // ...
    // blob.Location - location of the blob
    // blob.Image - blob`s image
}

YCbCr Filtering

YCbCr filters are provided with similar functionality to RGB and HSL filters. The YCbCr linear correction filter performs as its analogues from other color spaces, but operates with the Y, Cb and Cr components respectively, providing us with additional convenient ways of color correction. The next small sample demonstrates the use of the YCbCr linear filter and the use of in-place filtering: the feature, which allows you to filter a source image instead of creating a new result image, is as follows:
// create filter
YCbCrLinear filter = new YCbCrLinear( );
filter.InCb = new DoubleRange( -0.276, 0.163 );
filter.InCr = new DoubleRange( -0.202, 0.500 );
// apply filter
filter.ApplyInPlace( image );
Initial image Image after color correction with YCbCr linear filter

Perlin Noise Filters

Perlin noise has many applications and one of the most interesting of them is the creation of different effects, like marble, wood, clouds, etc. Application of such effects to images can be done in two steps. The first step is to generate effect textures and the second step is to apply the textures to the particular image. Texture generators are placed into the Textures namespace of the library, which contains generators for such effects as clouds, wood, marble, labyrinth and textile. All these texture generators implement the ITextureGenerator interface. For applying textures to images, there are three filters. The fist one, Texturer, is for texturing images. The second, TexturedFilter, allows application of any other filter to an image using the texture as a mask. The third, TexturedMerge, allows merging of two images using the texture as a mask.
// 1 - Marble effect
// create texture
ITextureGenerator generator = new MarbleTexture( );
float[,] texture = generator.Generate( image.Width, image.Height );
// create filter
IFilter filter1 = new Texturer( texture );
// apply filter
Bitmap newImage1 = filter1.Apply( image );
// 2 - Wood effect
// create filter
IFilter filter2 = new Texturer( new WoodTexture( ) );
// apply filter
Bitmap newImage2 = filter2.Apply( image );
// 3 - Textile effect
// create filter
IFilter filter3 = new Texturer( new TextileTexture( ) );
// apply filter
Bitmap newImage3 = filter3.Apply( image );
// 4 - Rusty effect
IFilter filter4 = new TexturedFilter( new CloudsTexture( ),
    new Sepia( ) , new GrayscaleBT709( ) );
// apply filter
Bitmap newImage4 = filter4.Apply( image );
Marble effect Wood effect
Textile effect Rusty effect

AForge.NET Framework

The Image Processing Lab application is based on the AForge.NET framework, which provides all the filters and image processing routines available in the application. To get more information about the framework, you may read the dedicated article on The Code Project or visit the project's home page, where you can get all the latest information about it, participate in a discussion group or submit issues or requests for enhancements.

Conclusion

I suppose the code may be interesting for someone who would like to start studying image processing or for filters/effects developers. As for me, I'll use the tool for my further research in computer vision. Besides, the library helped me very much in successfully finishing my bachelor work.

History

  • [08.03.2007] - Version 2.4.0
    • Application converted to .NET 2.0;
    • Integrated with AForge.NET framework.
  • [13.06.2006] - Version 2.3.0
    • In place filter interface introduced, which allows filter application on the source image;
    • Perlin noise texture generators (marble, wood, textile, labyrinth, clouds);
    • Texture filters (texturer, textured filtering, textured merging);
    • RGB to YCbCr and YCbCr to TGB converters;
    • YCbCr filters;
    • Image statistics for YCbCr;
    • Other minor changes (fix of Canny edge detector, Pixellate filter updated, morph filter).
  • [20.09.2005] - Version 2.2.0
    • Canny edge detector;
    • Oil painting, Conservative smoothing;
    • Simple image statistics threshold.
  • [20.08.2005] - Version 2.1.0
    • Blob counter, Connected components labeling;
    • Sobel edge detector;
    • Adaptive smoothing, Gaussian blur, Image cropping.
  • [12.07.2005] - Version 2.0.0
    • Homogeneity and Difference edge detectors;
    • Fourier transformation (lowpass and hipass filters);
    • AForge namespace;
    • Copy and Paste to clipboard;
    • Image saving and printing.
  • [20.06.2005] - Version 1.4.0
    • More morphological methods (hit & miss, thinning, thickening);
    • HSL filters;
    • Gamma correction, filter iterator, etc.
  • [29.03.2005] - Version 1.3.0
    • Resize and rotate;
    • Jitter, Shrink, more dithering methods;
    • MaskedFilter.
  • [20.03.2005] - Version 1.2.0
    • More filters;
    • Preview window;
    • Grid colorization for morphology and convolution custom filters;
    • Support for two source filters;
    • Toolbar.
  • [03.03.2005] - Version 1.0.0

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

Andrew Kirillov
Software Developer (Senior) Cisco Systems
United Kingdom United Kingdom
Started software development at about 15 years old and it seems like now it lasts most part of my life. Fortunately did not spend too much time with Z80 and BK0010 and switched to 8086 and further. Similar with programming languages – luckily managed to get away from BASIC and Pascal to things like Assembler, C, C++ and then C#. Apart from daily programming for food, do it also for hobby, where mostly enjoy areas like Computer Vision, Robotics and AI. This led to some open source stuff like AForge.NET and not so open Computer Vision Sandbox.

Going out of computers I am just a man loving his family, enjoying traveling, a bit of books, a bit of movies and a mixture of everything else. Always wanted to learn playing guitar, but it seems like 6 strings are much harder than few dozens of keyboard’s keys. Will keep progressing ...

Comments