Posts Tagged 'Computer vision'



C# How to: Sharpen Edge Detection

Article Purpose

It is the objective of this article to explore and provide a discussion based in the concept of through means of . Illustrated are various methods of sharpening and in addition a implemented in reduction.

Sample Source Code

This article is accompanied by a sample source code Visual Studio project which is available for download here.

Using the Sample Application

The sample source code accompanying this article includes a based Sample Application. The concepts illustrated throughout this article can easily be tested and replicated by making use of the Sample Application.

The Sample Application exposes seven main areas of functionality:

  • Loading input/source images.
  • Saving image result.
  • Sharpen Filters
  • Median Filter Size
  • Threshold value
  • Grayscale Source
  • Mono Output

When using the Sample application users are able to select input/source from the local file system by clicking the Load Image button. If desired, users may save result to the local file system by clicking the Save Image button.

The sample source code and sample application implement various methods of . Each method of results in varying degrees of . Some methods are more effective than other methods. The method being implemented serves as a primary factor influencing results. The effectiveness of the selected method is reliant on the input/source provided. The sample application implements the following methods:

  • Sharpen5To4
  • Sharpen7To1
  • Sharpen9To1
  • Sharpen12To1
  • Sharpen24To1
  • Sharpen48To1
  • Sharpen10To8
  • Sharpen11To8
  • Sharpen821

is regarded as a common problem relating to . Often will be incorrectly detected as forming part of an edge within an . The sample source code implements a in order to counter act . The size/intensity of the applied can be specified via the labelled Median Filter Size.

The Threshold value configured through the sample application’s user interface has a two-fold implementation. In a scenario where output images are created in a black and white format the Threshold value will be implemented to determine whether a pixel should be either black or white. When output are created as full colour the Threshold value will be added to each pixel, acting as a bias value.

In some scenarios can be achieved more effectively when specifying format source/input . The purpose of the labelled Grayscale Source is to format source/input in a format before implementing .

The labelled Mono Output, when selected, has the effect of producing result in a black and white format.

The image below is a screenshot of the Sharpen Edge Detection sample application in action:

Sharpen Edge Detection Sample Application

Edge Detection through Image Sharpening

The sample source code performs on source/input by means of . The steps performed can be broken down to the following items:

  1. If specified, apply a filter to the input/source image. A filter results in smoothing an . can be reduced when implementing a . smoothing/ often results reducing details/. The is well suited to smoothing away whilst implementing edge preservation. When performing the functions as an ideal method of reducing whilst not negatively impacting tasks.
  2. If specified, convert the source/input to by iterating each pixel that forms part of the . Each pixel’s colour components are calculated multiplying by factor values: Red x 0.3  Green x 0.59  Blue x 0.11.
  3. Using the specified   iterate each pixel forming part of the source/input , performing on each pixel colour channel.
  4. If the output has been specified as Mono, the middle pixel calculated in should be multiplied with the specified factor value. Each colour component should be compared to the specified threshold value and be assigned as either black or white.
  5. If the output has not been specified as Mono, the middle pixel calculated in should be multiplied with the factor value to which the threshold/bias value should be added. The value of each colour component will be set to the result of subtracting the calculated convolution/filter/bias value from the pixel’s original colour component value. In other words perform using applying a factor and bias which should then be subtracted from the original source/input .

Implementing Sharpen Edge Detection

The sample source code achieves through image sharpening by implementing three methods: MedianFilter and two overloaded methods titled SharpenEdgeDetect.

The MedianFilter method is defined as an targeting the class. The definition as follows:

 public static Bitmap MedianFilter(this Bitmap sourceBitmap, 
                                   int matrixSize) 
{ 
     BitmapData sourceData = 
                sourceBitmap.LockBits(new Rectangle(0, 0, 
                sourceBitmap.Width, sourceBitmap.Height), 
                ImageLockMode.ReadOnly, 
                PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
int filterOffset = (matrixSize - 1) / 2; int calcOffset = 0;
int byteOffset = 0;
List<int> neighbourPixels = new List<int>(); byte[] middlePixel;
for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { byteOffset = offsetY * sourceData.Stride + offsetX * 4;
neighbourPixels.Clear();
for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) {
calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
neighbourPixels.Add(BitConverter.ToInt32( pixelBuffer, calcOffset)); } }
neighbourPixels.Sort(); middlePixel = BitConverter.GetBytes( neighbourPixels[filterOffset]);
resultBuffer[byteOffset] = middlePixel[0]; resultBuffer[byteOffset + 1] = middlePixel[1]; resultBuffer[byteOffset + 2] = middlePixel[2]; resultBuffer[byteOffset + 3] = middlePixel[3]; } }
Bitmap resultBitmap = new Bitmap (sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }

The public implementation of the SharpenEdgeDetect has the purpose of translating user specified options into the relevant method calls to the private implementation of the SharpenEdgeDetect . The public implementation of the SharpenEdgeDetect method as follows:

public static Bitmap SharpenEdgeDetect(this Bitmap sourceBitmap, 
                                            SharpenType sharpen, 
                                                   int bias = 0, 
                                         bool grayscale = false, 
                                              bool mono = false, 
                                       int medianFilterSize = 0) 
{ 
    Bitmap resultBitmap = null; 

if (medianFilterSize == 0) { resultBitmap = sourceBitmap; } else { resultBitmap = sourceBitmap.MedianFilter(medianFilterSize); }
switch (sharpen) { case SharpenType.Sharpen7To1: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen7To1, 1.0, bias, grayscale, mono); } break; case SharpenType.Sharpen9To1: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen9To1, 1.0, bias, grayscale, mono); } break; case SharpenType.Sharpen12To1: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen12To1, 1.0, bias, grayscale, mono); } break; case SharpenType.Sharpen24To1: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen24To1, 1.0, bias, grayscale, mono); } break; case SharpenType.Sharpen48To1: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen48To1, 1.0, bias, grayscale, mono); } break; case SharpenType.Sharpen5To4: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen5To4, 1.0, bias, grayscale, mono); } break; case SharpenType.Sharpen10To8: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen10To8, 1.0, bias, grayscale, mono); } break; case SharpenType.Sharpen11To8: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen11To8, 3.0 / 1.0, bias, grayscale, mono); } break; case SharpenType.Sharpen821: { resultBitmap = resultBitmap.SharpenEdgeDetect( Matrix.Sharpen821, 8.0 / 1.0, bias, grayscale, mono); } break; }
return resultBitmap; }

The Matrix class provides the definition of static pre-defined values. The definition as follows:

public static class Matrix   
{
    public static double[,] Sharpen7To1 
    {
        get   
        { 
            return new double[,]   
            {  { 1,  1,  1, },  
               { 1, -7,  1, },   
               { 1,  1,  1, }, }; 
        }  
    }  

public static double[,] Sharpen9To1 { get { return new double[,] { { -1, -1, -1, }, { -1, 9, -1, }, { -1, -1, -1, }, }; } }
public static double[,] Sharpen12To1 { get { return new double[,] { { -1, -1, -1, }, { -1, 12, -1, }, { -1, -1, -1, }, }; } }
public static double[,] Sharpen24To1 { get { return new double[,] { { -1, -1, -1, -1, -1, }, { -1, -1, -1, -1, -1, }, { -1, -1, 24, -1, -1, }, { -1, -1, -1, -1, -1, }, { -1, -1, -1, -1, -1, }, }; } }
public static double[,] Sharpen48To1 { get { return new double[,] { { -1, -1, -1, -1, -1, -1, -1, }, { -1, -1, -1, -1, -1, -1, -1, }, { -1, -1, -1, -1, -1, -1, -1, }, { -1, -1, -1, 48, -1, -1, -1, }, { -1, -1, -1, -1, -1, -1, -1, }, { -1, -1, -1, -1, -1, -1, -1, }, { -1, -1, -1, -1, -1, -1, -1, }, }; } }
public static double[,] Sharpen5To4 { get { return new double[,] { { 0, -1, 0, }, { -1, 5, -1, }, { 0, -1, 0, }, }; } }
public static double[,] Sharpen10To8 { get { return new double[,] { { 0, -2, 0, }, { -2, 10, -2, }, { 0, -2, 0, }, }; } }
public static double[,] Sharpen11To8 { get { return new double[,] { { 0, -2, 0, }, { -2, 11, -2, }, { 0, -2, 0, }, }; } }
public static double[,] Sharpen821 { get { return new double[,] { { -1, -1, -1, -1, -1, }, { -1, 2, 2, 2, -1, }, { -1, 2, 8, 2, 1, }, { -1, 2, 2, 2, -1, }, { -1, -1, -1, -1, -1, }, }; } } }

The private implementation of the SharpenEdgeDetect performs through and then performs subtraction. The definition as follows:

private static Bitmap SharpenEdgeDetect(this Bitmap sourceBitmap, 
                                          double[,] filterMatrix, 
                                               double factor = 1, 
                                                    int bias = 0, 
                                          bool grayscale = false, 
                                               bool mono = false) 
{ 
    BitmapData sourceData = sourceBitmap.LockBits(new Rectangle(0, 0, 
                             sourceBitmap.Width, sourceBitmap.Height), 
                                               ImageLockMode.ReadOnly, 
                                         PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height]; byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); sourceBitmap.UnlockBits(sourceData);
if (grayscale == true) { for (int pixel = 0; pixel < pixelBuffer.Length; pixel += 4) { pixelBuffer[pixel] = (byte)(pixelBuffer[pixel] * 0.11f);
pixelBuffer[pixel + 1] = (byte)(pixelBuffer[pixel + 1] * 0.59f);
pixelBuffer[pixel + 2] = (byte)(pixelBuffer[pixel + 2] * 0.3f); } }
double blue = 0.0; double green = 0.0; double red = 0.0;
int filterWidth = filterMatrix.GetLength(1); int filterHeight = filterMatrix.GetLength(0);
int filterOffset = (filterWidth - 1) / 2; int calcOffset = 0;
int byteOffset = 0;
for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { blue = 0; green = 0; red = 0;
byteOffset = offsetY * sourceData.Stride + offsetX * 4;
for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
blue += (double )(pixelBuffer[calcOffset]) * filterMatrix[filterY + filterOffset, filterX + filterOffset];
green += (double )(pixelBuffer[calcOffset + 1]) * filterMatrix[filterY + filterOffset, filterX + filterOffset];
red += (double )(pixelBuffer[calcOffset + 2]) * filterMatrix[filterY + filterOffset, filterX + filterOffset]; } }
if (mono == true) { blue = resultBuffer[byteOffset] - factor * blue; green = resultBuffer[byteOffset + 1] - factor * green; red = resultBuffer[byteOffset + 2] - factor * red;
blue = (blue > bias ? 255 : 0);
green = (blue > bias ? 255 : 0);
red = (blue > bias ? 255 : 0); } else { blue = resultBuffer[byteOffset] - factor * blue + bias;
green = resultBuffer[byteOffset + 1] - factor * green + bias;
red = resultBuffer[byteOffset + 2] - factor * red + bias;
blue = (blue > 255 ? 255 : (blue < 0 ? 0 : blue));
green = (green > 255 ? 255 : (green < 0 ? 0 : green));
red = (red > 255 ? 255 : (red < 0 ? 0 : red)); }
resultBuffer[byteOffset] = (byte)(blue); resultBuffer[byteOffset + 1] = (byte)(green); resultBuffer[byteOffset + 2] = (byte)(red); resultBuffer[byteOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height); BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); resultBitmap.UnlockBits(resultData);
return resultBitmap; }

Sample Images

The sample image used in this article is in the public domain because its copyright has expired. This applies to Australia, the European Union and those countries with a copyright term of life of the author plus 70 years. The original image can be downloaded from Wikipedia.

The Original Image

NovaraExpZoologischeTheilLepidopteraAtlasTaf53

Sharpen5To4, Median 0, Threshold 0

Sharpen5To4 Median 0 Threshold 0

Sharpen5To4, Median 0, Threshold 0, Mono

Sharpen5To4 Median 0 Threshold 0 Mono

Sharpen7To1, Median 0, Threshold 0

Sharpen7To1 Median 0 Threshold 0

Sharpen7To1, Median 0, Threshold 0, Mono

Sharpen7To1 Median 0 Threshold 0 Mono

Sharpen9To1, Median 0, Threshold 0

Sharpen9To1 Median 0 Threshold 0

Sharpen9To1, Median 0, Threshold 0, Mono

Sharpen9To1 Median 0 Threshold 0 Mono

Sharpen10To8, Median 0, Threshold 0

Sharpen10To8 Median 0 Threshold 0

Sharpen10To8, Median 0, Threshold 0, Mono

Sharpen10To8 Median 0 Threshold 0 Mono

Sharpen11To8, Median 0, Threshold 0

Sharpen11To8 Median 0 Threshold 0

Sharpen11To8, Median 0, Threshold 0, Grayscale, Mono

Sharpen11To8 Median 0 Threshold 0 Grayscale Mono

Sharpen12To1, Median 0, Threshold 0

Sharpen12To1 Median 0 Threshold 0

Sharpen12To1, Median 0, Threshold 0, Mono

Sharpen12To1 Median 0 Threshold 0 Mono

Sharpen24To1, Median 0, Threshold 0

Sharpen24To1 Median 0 Threshold 0

Sharpen24To1, Median 0, Threshold 0, Grayscale, Mono

Sharpen24To1 Median 0 Threshold 0 Grayscale Mono

Sharpen24To1, Median 0, Threshold 0, Mono

Sharpen24To1 Median 0 Threshold 0 Mono

Sharpen24To1, Median 0, Threshold 21, Grayscale, Mono

Sharpen24To1 Median 0 Threshold 21 Grayscale Mono

Sharpen48To1, Median 0, Threshold 0

Sharpen48To1 Median 0 Threshold 0

Sharpen48To1, Median 0, Threshold 0, Grayscale, Mono

Sharpen48To1 Median 0 Threshold 0 Grayscale Mono

Sharpen48To1, Median 0, Threshold 0, Mono

Sharpen48To1 Median 0 Threshold 0 Mono

Sharpen48To1, Median 0, Threshold 226, Mono

Sharpen48To1 Median 0 Threshold 226 Mono

Related Articles and Feedback

Feedback and questions are always encouraged. If you know of an alternative implementation or have ideas on a more efficient implementation please share in the comments section.

I’ve published a number of articles related to imaging and images of which you can find URL links here:

C# How to: Image Cartoon Effect

Article purpose

In this article we explore the tasks related to creating a Cartoon Effect from which reflect real world non-animated scenarios. When applying a Cartoon Effect it becomes possible with relative ease to create appearing to have originated from a drawing/animation.

Cartoon version of Steve Ballmer: Low Pass 3×3, Threshold 65.

Low Pass 3x3 Threshold 65

Sample source code

This article is accompanied by a sample source code Visual Studio project which is available for download .

CPU: Gaussian 7×7, Threshold 84

Gaussian 7x7 Threshold 84 CPU

Using the Sample Application

A Sample Application has been included as part of the sample source code accompanying this article. The Sample Application is a based application which enables a user to specify source/input , apply various methods of implementing the Cartoon Effect. In addition users are able to save generated images to the local system.

When using the Sample Application click the Load Image button to load files from the local file system. On the right-hand side of the Sample application’s user interface, users are provided with two configuration options: Smoothing Filter and Threshold.

Rose: Gaussian 3×3 Threshold 28.

Gaussian 3x3 Threshold 28

In this article and sample source code detail and definition can be reduced through means of image smoothing filters. Several smoothing options are available to the user, the following section serves as a discussion of each option.

None – When specifying the Smoothing Filter option None, no smoothing operations will be performed on source/input .

3×3 – filters can be very effective at removing , smoothing an background, whilst still preserving the edges expressed in the sample/input . A  / of 3×3 dimensions result in slight .

 5×5 – A operation being implemented by making use of a / defined with dimensions of 5×5. A slightly larger results in an increased level of being expressed by output . A greater level of   equates to a larger degree of reduction/removal.

Rose: Gaussian 7×7 Threshold 48.

Gaussian 7x7 Threshold 48 

7×7 – As can be expected when specifying a / conforming to 7×7 size dimension an even more intense level of can be detected when looking at result . Notice how increased levels of negatively affects the process of . Consider the following: In a scenario where too many elements are being detected as part of an edge as a result of , specifying a higher level of should reduce edges being detected. The reasoning can be explained in terms of reducing /detail, higher levels of will thus result in a greater level of detail/definition reduction. Lower definition are less likely to express the same level of detected edges when compared to higher definition .

CPU: Median 3×3, Threshold 96.

Median 3x3 Threshold 96 CPU

 3×3 – When applying a to an the resulting should express a lesser degree of . In other words, the can be considered as well suited to performing . Also note that a under certain conditions has the ability to preserve the edges contained in an . In the following section we explore the importance of in achieving a Cartoon Effect. Important concepts to take note of: The when implemented on an performs whilst preserving edges. In relation, represents a core concept/task when creating a Cartoon Effect. The ’s edge preservation property compliments the process of . When an contains a low level of the Median 3×3 Filter could be considered.

5×5 – The 5×5 dimension implementation of the result in producing which exhibit a higher degree of smoothing and a lesser expression of . If the 3×3  fails to provide adequate levels of smoothing and the 5×5 could be implemented.

Cartoon version of Steve Ballmer: Sharpen 3×3, Threshold 80.

Sharpen 3x3 Threshold 80

7×7 – The last implemented by the sample source code conforms to a 7×7 size dimension. This filter variation results in a high level of image . The trade off to more effective will be expressed in result appearing extremely smooth, in some scenarios perhaps overly so.

Mean 3×3 – The Mean Filter provides a different implementation towards achieving image smoothing and .

Mean 5×5 – The 5×5 dimension Mean Filter variation serves as a more intense version of the Mean 3×3 Filter. Depending on the level of and type of a Mean Filter could prove a more efficient implementation in comparison to a .

Low Pass 3×3 – In much the same fashion as and Mean Filters, a achieves smoothing and . Notice when comparing , Mean and Filtering, the differences observed in output results are only expressed as slight differences. The most effective filter to apply should be seen as as being dependent on the input/source characteristics.

CPU: Gaussian 3×3, Threshold 92.

Gaussian 3x3 Threshold 92 CPU 

Low Pass 5×5 – This filter variation being of a larger dimension serves as a more intense implementation of the 3×3 Filter.

Sharpen 3×3 – In certain scenarios input/source may already be smoothed/blurred to such an extent where the process performs below expectation. can be improved when applying a .

Threshold values specified by the user through the user interface serves the purpose of enabling the user to finely control the extent/intensity of edges being detected. Implementing a higher Threshold value will have the result of less edges being detected. In order to reduce the level of being detected as false edges the Threshold value should be increased. When too few edges are being detected the Threshold value should be decreased.

The following image is a screenshot of the Image Cartoon Effect Sample Application in action:

Image Cartoon Effect Sample Application

Explanation of the Cartoon Effect

The Cartoon Effect can be characterised as an image filter producing result which appear similar to input/source with the exception of having an animated appearance.

The Cartoon Effect consists of reducing image detail/definition whilst at the same instance performing . The resulting smoothed and the edges detected in the source/input should be combined, where detected edges are being expressed in the colour black. The final reflects an appearance similar to that of an animated/artist drawn image.

Various methods of reducing detail/definition are supported in the sample source code. Most methods consist of implementing smoothing. The following configurable methods are implemented:

Rose: Mean 5×5 Threshold 37.

Mean 5x5 Threshold 37 

All of the filter methods listed above are implemented by means of . The size dimensions listed for each filter option relates to the dimension of the / being implemented by a filter.

When applying a filter, the intensity/extent will be determined by the size dimensions of the / implemented. Smaller / dimensions result in a filter being applied to a lesser extent. Larger / dimensions will result in the filter effect being more evident, being applied to a greater extent. reduction will be achieved when implementing a filter.

The Sample Source code implements Gradient Based Edge Detection using the original source/input , therefore not being influenced by any smoothing operations. I have published an in-depth article on the topic of Gradient Based Edge Detection which can be located here: .

Rose: Median 3×3 Threshold 37.

Median 3x3 Threshold 37

The Sample source code implements Gradient Based Edge Detection by means of iterating each pixel that forms part of the sample/input . Whilst iterating pixels the sample code calculate various gradients from the current pixel’s neighbouring pixels, on a per colour component basis (Red, Green and Blue). Referring to neighbouring pixels, calculations include the value of each of the surrounding pixels in regards to the pixel currently being iterated. Neighbouring pixel calculations are better know as /window/ operations.

Note: Do not confuse and the method in which we iterate and calculate gradients. Although both methods have various aspects in common, is regarded as linear filter processing, whereas our method qualifies as a non-linear filter.

We calculate various gradients, which is to be compared against the user specified global threshold value. If a calculated gradient value exceeds the value of the user specified threshold the pixel currently being iterated will be considered as part of an edge.

The first gradients to be calculated involves the pixel directly above, below, left and right of the current pixel. A gradient will be calculated for each colour component. The gradient values being calculated can be considered as an indicator reflecting the rate of change. If the sum total of the calculated gradients exceed that of the global threshold the pixel will be considered as forming part an edge.

When the comparison of the threshold value and the total gradient value reflects in favour of the threshold the following set of gradients will be calculated. This process of calculating gradients will continue either until a gradient value exceeds the threshold or all gradients have been calculated.

If a pixel was detected as forming part of an edge, the pixel’s colour will be set to black. In the case of non-edge pixels, the original colour components from the source/input image will be used in setting the current pixel’s value.

Rose: Gaussian 3×3 Threshold 28

Gaussian 3x3 Threshold 28

Implementing Cartoon Effects

The sample source code implementation can be divided into five distinct components: Cartoon Effect Filter, smoothing helper method, implementation, implementation and the collection of pre-defined / values.

The sample source code defines the MedianFilter targeting the class. The following code snippet provides the definition:

 public static Bitmap MedianFilter(this Bitmap sourceBitmap, 
                                   int matrixSize) 
{ 
     BitmapData sourceData = 
                sourceBitmap.LockBits(new Rectangle(0, 0, 
                sourceBitmap.Width, sourceBitmap.Height), 
                ImageLockMode.ReadOnly, 
                PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
int filterOffset = (matrixSize - 1) / 2; int calcOffset = 0;
int byteOffset = 0;
List<int> neighbourPixels = new List<int>(); byte[] middlePixel;
for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { byteOffset = offsetY * sourceData.Stride + offsetX * 4;
neighbourPixels.Clear();
for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) {
calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
neighbourPixels.Add(BitConverter.ToInt32( pixelBuffer, calcOffset)); } }
neighbourPixels.Sort(); middlePixel = BitConverter.GetBytes( neighbourPixels[filterOffset]);
resultBuffer[byteOffset] = middlePixel[0]; resultBuffer[byteOffset + 1] = middlePixel[1]; resultBuffer[byteOffset + 2] = middlePixel[2]; resultBuffer[byteOffset + 3] = middlePixel[3]; } }
Bitmap resultBitmap = new Bitmap (sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }

The SmoothingFilterType , defined by the sample source code, serves as a strongly typed definition of a the collection of implemented smoothing filters. The definition as follows:

 public enum SmoothingFilterType  
 {
     None, 
     Gaussian3x3, 
     Gaussian5x5, 
     Gaussian7x7, 
     Median3x3, 
     Median5x5, 
     Median7x7, 
     Median9x9, 
     Mean3x3, 
     Mean5x5, 
     LowPass3x3, 
     LowPass5x5, 
     Sharpen3x3, 
 } 

The Matrix class contains the definition of all the two dimensional / values implemented when performing . The definition as follows:

public static class Matrix 
{ 
    public static double[,] Gaussian3x3 
    { 
        get 
        {
            return new double[,]   
             { { 1, 2, 1, },  
               { 2, 4, 2, },  
               { 1, 2, 1, }, }; 
        } 
    }
 
    public static double[,] Gaussian5x5 
    {
        get 
        { 
            return new double[,]   
             { { 2, 04, 05, 04, 2  },  
               { 4, 09, 12, 09, 4  },  
               { 5, 12, 15, 12, 5  }, 
               { 4, 09, 12, 09, 4  }, 
               { 2, 04, 05, 04, 2  }, }; 
        } 
    } 
 
    public static double[,] Gaussian7x7 
    {
        get 
        { 
            return new double[,]   
             { { 1,  1,  2,  2,  2,  1,  1, },  
               { 1,  2,  2,  4,  2,  2,  1, },  
               { 2,  2,  4,  8,  4,  2,  2, },  
               { 2,  4,  8, 16,  8,  4,  2, },  
               { 2,  2,  4,  8,  4,  2,  2, },  
               { 1,  2,  2,  4,  2,  2,  1, },  
               { 1,  1,  2,  2,  2,  1,  1, }, }; 
        } 
    } 
 
    public static double[,] Mean3x3 
    { 
        get 
        { 
            return new double[,]   
             { { 1, 1, 1, },  
               { 1, 1, 1, },  
               { 1, 1, 1, }, }; 
        } 
    } 
 
    public static double[,] Mean5x5 
    { 
        get 
        { 
            return new double[,]   
             { { 1, 1, 1, 1, 1, },  
               { 1, 1, 1, 1, 1, },  
               { 1, 1, 1, 1, 1, },  
               { 1, 1, 1, 1, 1, },  
               { 1, 1, 1, 1, 1, }, }; 
        } 
    } 
 
    public static double [,] LowPass3x3 
    { 
        get 
        { 
            return new double [,]   
             { { 1, 2, 1, },  
               { 2, 4, 2, },   
               { 1, 2, 1, }, }; 
        }
    } 
 
    public static double[,] LowPass5x5 
    { 
        get 
        { 
            return new double[,]   
             { { 1, 1,  1, 1, 1, },  
               { 1, 4,  4, 4, 1, },  
               { 1, 4, 12, 4, 1, },  
               { 1, 4,  4, 4, 1, },  
               { 1, 1,  1, 1, 1, }, }; 
        }
    }
 
    public static double[,] Sharpen3x3 
    { 
        get 
         {
            return new double[,]   
             { { -1, -2, -1, },  
               {  2,  4,  2, },   
               {  1,  2,  1, }, }; 
         }
    } 
} 

Rose: Low Pass 3×3 Threshold 61

Low Pass 3x3 Threshold 61

The SmoothingFilter targets the class. This method implements . The primary task performed by the SmoothingFilter involves translating filter options into the correct method calls. The definition as follows:

public static Bitmap SmoothingFilter(this Bitmap sourceBitmap, 
                            SmoothingFilterType smoothFilter = 
                            SmoothingFilterType.None) 
 {
    Bitmap inputBitmap = null; 

switch (smoothFilter) { case SmoothingFilterType.None: { inputBitmap = sourceBitmap; } break; case SmoothingFilterType.Gaussian3x3: { inputBitmap = sourceBitmap.ConvolutionFilter( Matrix.Gaussian3x3, 1.0 / 16.0, 0); } break; case SmoothingFilterType.Gaussian5x5: { inputBitmap = sourceBitmap.ConvolutionFilter( Matrix.Gaussian5x5, 1.0 / 159.0, 0); } break; case SmoothingFilterType.Gaussian7x7: { inputBitmap = sourceBitmap.ConvolutionFilter( Matrix.Gaussian7x7, 1.0 / 136.0, 0); } break; case SmoothingFilterType.Median3x3: { inputBitmap = sourceBitmap.MedianFilter(3); } break; case SmoothingFilterType.Median5x5: { inputBitmap = sourceBitmap.MedianFilter(5); } break; case SmoothingFilterType.Median7x7: { inputBitmap = sourceBitmap.MedianFilter(7); } break; case SmoothingFilterType.Median9x9: { inputBitmap = sourceBitmap.MedianFilter(9); } break; case SmoothingFilterType.Mean3x3: { inputBitmap = sourceBitmap.ConvolutionFilter( Matrix.Mean3x3, 1.0 / 9.0, 0); } break; case SmoothingFilterType.Mean5x5: { inputBitmap = sourceBitmap.ConvolutionFilter( Matrix.Mean5x5, 1.0 / 25.0, 0); } break; case SmoothingFilterType.LowPass3x3: { inputBitmap = sourceBitmap.ConvolutionFilter( Matrix.LowPass3x3, 1.0 / 16.0, 0); } break; case SmoothingFilterType.LowPass5x5: { inputBitmap = sourceBitmap.ConvolutionFilter( Matrix.LowPass5x5, 1.0 / 60.0, 0); } break; case SmoothingFilterType.Sharpen3x3: { inputBitmap = sourceBitmap.ConvolutionFilter( Matrix.Sharpen3x3, 1.0 / 8.0, 0); } break; }
return inputBitmap; }

The ConvolutionFilter which targets the class implements . The definition as follows:

private static Bitmap ConvolutionFilter(this Bitmap sourceBitmap, 
                                          double[,] filterMatrix, 
                                               double factor = 1, 
                                                    int bias = 0) 
{ 
    BitmapData sourceData = sourceBitmap.LockBits(new Rectangle(0, 0, 
                             sourceBitmap.Width, sourceBitmap.Height), 
                                               ImageLockMode.ReadOnly, 
                                         PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height]; byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); sourceBitmap.UnlockBits(sourceData);
double blue = 0.0; double green = 0.0; double red = 0.0;
int filterWidth = filterMatrix.GetLength(1); int filterHeight = filterMatrix.GetLength(0);
int filterOffset = (filterWidth - 1) / 2; int calcOffset = 0;
int byteOffset = 0;
for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { blue = 0; green = 0; red = 0;
byteOffset = offsetY * sourceData.Stride + offsetX * 4;
for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) {
calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
blue += (double)(pixelBuffer[calcOffset]) * filterMatrix[filterY + filterOffset, filterX + filterOffset];
green += (double)(pixelBuffer[calcOffset + 1]) * filterMatrix[filterY + filterOffset, filterX + filterOffset];
red += (double)(pixelBuffer[calcOffset + 2]) * filterMatrix[filterY + filterOffset, filterX + filterOffset]; } }
blue = factor * blue + bias; green = factor * green + bias; red = factor * red + bias;
blue = (blue > 255 ? 255 : (blue < 0 ? 0 : blue));
green = (green > 255 ? 255 : (green < 0 ? 0 : green));
red = (red > 255 ? 255 : (red < 0 ? 0 : red));
resultBuffer[byteOffset] = (byte)(blue); resultBuffer[byteOffset + 1] = (byte)(green); resultBuffer[byteOffset + 2] = (byte)(red); resultBuffer[byteOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode .WriteOnly, PixelFormat .Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); resultBitmap.UnlockBits(resultData);
return resultBitmap; }

Cartoon version of Steve Ballmer: Sharpen 3×3 Threshold 80

Sharpen 3x3 Threshold 80

The CartoonEffectFilter targets the class. This method defines all the tasks required in order to implement a Cartoon Filter. From an implementation point of view, consuming code is only required to invoke this method, no other additional method calls are required. The definition as follows:

public static Bitmap CartoonEffectFilter( 
                                this Bitmap sourceBitmap, 
                                byte threshold = 0, 
                                SmoothingFilterType smoothFilter  
                                = SmoothingFilterType.None) 
{ 
    sourceBitmap = sourceBitmap.SmoothingFilter(smoothFilter); 

BitmapData sourceData = sourceBitmap.LockBits(new Rectangle (0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
int byteOffset = 0; int blueGradient, greenGradient, redGradient = 0; double blue = 0, green = 0, red = 0;
bool exceedsThreshold = false;
for (int offsetY = 1; offsetY < sourceBitmap.Height - 1; offsetY++) { for (int offsetX = 1; offsetX < sourceBitmap.Width - 1; offsetX++) { byteOffset = offsetY * sourceData.Stride + offsetX * 4;
blueGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]);
blueGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
byteOffset++;
greenGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]);
greenGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
byteOffset++;
redGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]);
redGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
if (blueGradient + greenGradient + redGradient > threshold) { exceedsThreshold = true ; } else { byteOffset -= 2;
blueGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); byteOffset++;
greenGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); byteOffset++;
redGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]);
if (blueGradient + greenGradient + redGradient > threshold) { exceedsThreshold = true ; } else { byteOffset -= 2;
blueGradient = Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
byteOffset++;
greenGradient = Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
byteOffset++;
redGradient = Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
if (blueGradient + greenGradient + redGradient > threshold) { exceedsThreshold = true ; } else { byteOffset -= 2;
blueGradient = Math.Abs(pixelBuffer[byteOffset - 4 - sourceData.Stride] - pixelBuffer[byteOffset + 4 + sourceData.Stride]);
blueGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride + 4] - pixelBuffer[byteOffset + sourceData.Stride - 4]);
byteOffset++;
greenGradient = Math.Abs(pixelBuffer[byteOffset - 4 - sourceData.Stride] - pixelBuffer[byteOffset + 4 + sourceData.Stride]);
greenGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride + 4] - pixelBuffer[byteOffset + sourceData.Stride - 4]);
byteOffset++;
redGradient = Math.Abs(pixelBuffer[byteOffset - 4 - sourceData.Stride] - pixelBuffer[byteOffset + 4 + sourceData.Stride]);
redGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride + 4] - pixelBuffer[byteOffset + sourceData.Stride - 4]);
if (blueGradient + greenGradient + redGradient > threshold) { exceedsThreshold = true ; } else { exceedsThreshold = false ; } } } }
byteOffset -= 2;
if (exceedsThreshold) { blue = 0; green = 0; red = 0; } else { blue = pixelBuffer[byteOffset]; green = pixelBuffer[byteOffset + 1]; red = pixelBuffer[byteOffset + 2]; }
blue = (blue > 255 ? 255 : (blue < 0 ? 0 : blue));
green = (green > 255 ? 255 : (green < 0 ? 0 : green));
red = (red > 255 ? 255 : (red < 0 ? 0 : red));
resultBuffer[byteOffset] = (byte)blue; resultBuffer[byteOffset + 1] = (byte)green; resultBuffer[byteOffset + 2] = (byte)red; resultBuffer[byteOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }

Sample Images

The sample image used in this article which features Bill Gates has been licensed under the Creative Commons Attribution 2.0 Generic license and can be from .

The sample image featuring Steve Ballmer has been licensed under the Creative Commons Attribution 2.0 Generic license and can be from .

The sample image featuring an Amber flush Rose has been licensed under the Creative Commons Attribution-Share Alike 3.0 Unported, 2.5 Generic, 2.0 Generic and 1.0 Generic license and can be from .

The sample image featuring a Computer Processor has been licensed under the Creative Commons Attribution-Share Alike 2.0 Generic license and can be downloaded from .l The original author is attributed as Andrew Dunnhttp://www.andrewdunnphoto.com/

The Original Image

BillGates2012

No Smoothing, Threshold 100

No Smoothing Threshold 100 Gates

Gaussian 3×3, Threshold 73

Gaussian 3x3 Threshold 73 Gates

Gaussian 5×5, Threshold 78

Gaussian 5x5 Threshold 78 Gates

Gaussian 7×7, Threshold 84

Gaussian 7x7 Threshold 84 Gates

Low Pass 3×3, Threshold 72

LowPass 3x3 Threshold 72 Gates

Low Pass 5×5, Threshold 81

LowPass 5x5 Threshold 81 Gates

Mean 3×3, Threshold 79

Mean 3x3 Threshold 79 Gates

Mean 5×5, Threshold 80

Mean 5x5 Threshold 80 Gates

Median 3×3, Threshold 85

Median 3x3 Threshold 85 Gates

Median 5×5, Threshold 105

Median 5x5 Threshold 105 Gates

Median 7×7, Threshold 127

Median 7x7 Threshold 127 Gates

Median 9×9, Threshold 154

Median 9x9 Threshold 154 Gates

Sharpen 3×3, Threshold 114

Sharpen 3x3 Threshold 114 Gates

Related Articles and Feedback

Feedback and questions are always encouraged. If you know of an alternative implementation or have ideas on a more efficient implementation please share in the comments section.

I’ve published a number of articles related to imaging and images of which you can find URL links here:

C# How to: Gradient Based Edge Detection

Article purpose

This article provides a technical discussion exploring the topic of Gradient Based Edge Detection and related aspects. Several filtering options are illustrated and explained ranging from pure black and white to .

Gradient Based Edge Detection

Sample Source Code

This article is accompanied by a sample source code Visual Studio project which is available for download here.

Using the Sample Application

All of the concepts implemented in this article can be replicated and tested by making use of the sample application included in the associated sample source code. The sample application user interface provides several configurable options to be implemented when performing Gradient Based Edge Detection. The available configuration categories are: Filter Type, Derivative Level, Threshold and Colour Factor Filters.

Gradient Based Edge Detection

Configurable Filter Types exposed to the end user consist of:

  • None – When selecting this option no filtering will be applied. Source/input are displayed reflecting no change. 
  • Edge Detect Mono – This option  represents basic Gradient Based Edge Detection. Resulting are only expressed in terms of black and white pixels.
  • Edge Detect Gradient – Gradient Based Edge Detection revolves around calculating pixel colour gradients. This option signifies  a scenario where the pixels forming resulting express the relevant pixel’s colour gradient, when a pixel has been determined to reflect part of an edge. If  a pixel is not considered to be part of an edge, the relevant pixel’s colour value will be set to black.
  • Sharpen – In terms of , can be achieved by emphasising detected edges in source/input images. Emphasising edges involves combining a source/input and an which express only detected edges.
  • Sharpen Gradient – This option combines calculated colour gradients and the original colour value of a pixel on a per pixel basis when a pixel has been determined to be part of an edge. If a pixel does not form part of an edge, the pixel’s colour value is set to that of the original pixel colour.

Gradient Based Edge Detection

The user interface defines two : First Derivative and Second Derivative. These user interface options relate to the method being implemented, either First Order or Second Order derivative operators.

Comparing a global threshold and colour gradients on a per pixel scenario forms the basis of Gradient Based Edge Detection. The TrackBar labelled Threshold enables the user to adjust the global threshold value implemented in pixel colour gradient comparisons.

Gradient Based Edge Detection

The Colour Factor Filters impact on the level or extent to which colours are expressed in resulting . The three colour factors, Red, Green and Blue are intended to be used in combination with the filtering options: Filter Type and Threshold. Colour Factor Filter affects when implemented in combination:

  • Filter Type – Edge Detect Mono: Not applicable. Edge Detect Mono filtering discards all pixel colour data.
  • Filter Type – Edge Detect Gradient: If a pixel is detected as part of an edge, the pixel’s colour values will be set to the gradient calculated when evaluating edge criteria. Gradient values are multiplied by Colour Factor values before being assigned to a resulting pixel.
  • Filter Type – Sharpen: The pixels which form part of an edge, in terms of the resulting the corresponding pixels will be set to the same colour values. In addition pixel colour values in the resulting are multiplied by with the relevant Colour Factor. The pixels not detected as part of an edge will not be multiplied with any Colour Factor values.
  • Filter Type – Sharpen Gradient: Edge detected pixels in a source/input will have the effect of corresponding pixels in the resulting being assigned to the calculated colour gradient value, multiplied with the relevant Colour Factor value. In the scenario of a pixel not being detected as forming part of an edge, Colour Factors will not be implemented.
  • Threshold: The global threshold value specified by the user determines the level of sensitivity to which edges will be detected. The degree to which edges are detected through the threshold value impacts upon whether a pixel will be multiplied with the relevant Colour Factor value.

Gradient Based Edge Detection

The user has the option of saving filtered to the local system by clicking the Save Image button. The image below is a screenshot of the Gradient Based Edge Detection sample application in action:

Gradient Based Edge Detection Sample Application

Gradient Based Edge Detection Theory

Gradient Based Edge Detection qualifies to be classified as a neighbouring pixel algorithm. When calculating a pixel’s value in order to determine if a pixel should be expressed as part of an edge or not, the result will be determined by:

  • The values expressed by neighbouring pixels. The more intense or sudden differences that occur between neighbouring pixels will result in higher accuracy .
  • A user specified global threshold value used in comparison operations acts as a cut-off value, ultimately being the final factor to determine if a pixel should be expressed as part of an edge.

Gradient Based Edge Detection

In the sample source code we implement the following steps when calculating whether a pixel should be considered as part of an edge:

  1. Iterate each pixel that forms part of the source/input .
  2. Calculate and combine horizontal and vertical gradients for each of the colour components Red, Green and Blue. If the sum total of each colour component’s calculated gradient exceeds the global threshold value consider the pixel being iterated as part of an edge. If the sum total of colour gradients equate to less than the global threshold implement step 3.
  3. Calculate a pixel’s horizontal gradient per colour component. When comparing the gradient sum total against the global threshold consider the pixel being iterated part of an edge if the sum total of gradient values exceed that of the threshold. If the total of colour gradient value do not exceed the threshold value continue to step 4.
  4. Calculate a pixel’s vertical gradient per colour component. When comparing the gradient sum total against the global threshold consider the pixel being iterated part of an edge if the sum total of gradient values exceed that of the threshold. If the sum total of colour gradients  do not exceed the threshold value continue to step 5.
  5. Calculate and combine diagonal gradients for each of the colour components Red, Green and Blue. If the sum total of each colour component’s calculated gradient exceeds the global threshold value consider the pixel being iterated as being part of an edge. If the sum total of colour gradients equate to less than the global threshold the pixel being iterated should not be considered as part of an edge.

Gradient Based Edge Detection

If we determined that a pixel forms part of an edge, the value expressed by the corresponding pixel in the resulting will be determined by the Image Filter configuration value:

  • Edge Detect Mono – All pixels will be set to white.
  • Edge Detect Gradient – Each colour component will be assigned to the related colour gradient calculated when performing . Each Colour gradient will be multiplied with the related colour factor.
  • Sharpen – The value of a resulting pixel will be calculated as the product of the corresponding source pixel and the related colour factor value.
  • Sharpen Gradient – Results are calculated in terms of the sum total of the corresponding input pixel and the product of the related colour gradient and colour factor value.

Gradient Based Edge Detection

Implementing Gradient Based Edge Detection

The sample source code associated with this article provides the defines of the GradientBasedEdgeDetectionFilter targeting the class. This method  iterates every pixel contained in the source/input . Whilst iterating pixels the method creates a 3×3 window/ covering the neighbouring pixels surrounding the pixel currently being iterated. Colour gradients are calculated from every pixel’s neighbouring pixels.

The following Code snippet provides the definition of the GradientBasedEdgeDetectionFilter :

public static Bitmap GradientBasedEdgeDetectionFilter( 
                                this Bitmap sourceBitmap, 
                                EdgeFilterType filterType, 
                                DerivativeLevel derivativeLevel,  
                                float redFactor = 1.0f, 
                                float greenFactor = 1.0f, 
                                float blueFactor = 1.0f, 
                                byte threshold = 0) 
{ 
    BitmapData sourceData = 
               sourceBitmap.LockBits(new Rectangle (0, 0, 
               sourceBitmap.Width, sourceBitmap.Height), 
               ImageLockMode.ReadOnly, 
               PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
int derivative = (int)derivativeLevel; int byteOffset = 0; int blueGradient, greenGradient, redGradient = 0; double blue = 0, green = 0, red = 0;
bool exceedsThreshold = false;
for(int offsetY = 1; offsetY < sourceBitmap.Height - 1; offsetY++) { for (int offsetX = 1; offsetX < sourceBitmap.Width - 1; offsetX++) { byteOffset = offsetY * sourceData.Stride + offsetX * 4;
blueGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]) / derivative;
blueGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]) / derivative;
byteOffset++;
greenGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]) / derivative;
greenGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]) / derivative;
byteOffset++;
redGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]) / derivative;
redGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]) / derivative;
if (blueGradient + greenGradient + redGradient > threshold) { exceedsThreshold = true ; } else { byteOffset -= 2;
blueGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); byteOffset++;
greenGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]); byteOffset++;
redGradient = Math.Abs(pixelBuffer[byteOffset - 4] - pixelBuffer[byteOffset + 4]);
if (blueGradient + greenGradient + redGradient > threshold) { exceedsThreshold = true ; } else { byteOffset -= 2;
blueGradient = Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
byteOffset++;
greenGradient = Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
byteOffset++;
redGradient = Math.Abs(pixelBuffer[byteOffset - sourceData.Stride] - pixelBuffer[byteOffset + sourceData.Stride]);
if (blueGradient + greenGradient + redGradient > threshold) { exceedsThreshold = true ; } else { byteOffset -= 2;
blueGradient = Math.Abs(pixelBuffer[byteOffset - 4 - sourceData.Stride] - pixelBuffer[byteOffset + 4 + sourceData.Stride]) / derivative;
blueGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride + 4] - pixelBuffer[byteOffset + sourceData.Stride - 4]) / derivative;
byteOffset++;
greenGradient = Math.Abs(pixelBuffer[byteOffset - 4 - sourceData.Stride] - pixelBuffer[byteOffset + 4 + sourceData.Stride]) / derivative;
greenGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride + 4] - pixelBuffer[byteOffset + sourceData.Stride - 4]) / derivative;
byteOffset++;
redGradient = Math.Abs(pixelBuffer[byteOffset - 4 - sourceData.Stride] - pixelBuffer[byteOffset + 4 + sourceData.Stride]) / derivative;
redGradient += Math.Abs(pixelBuffer[byteOffset - sourceData.Stride + 4] - pixelBuffer[byteOffset + sourceData.Stride - 4]) / derivative;
if (blueGradient + greenGradient + redGradient > threshold) { exceedsThreshold = true ; } else { exceedsThreshold = false ; } } } }
byteOffset -= 2;
if (exceedsThreshold) { if (filterType == EdgeFilterType.EdgeDetectMono) { blue = green = red = 255; } else if (filterType == EdgeFilterType.EdgeDetectGradient) { blue = blueGradient * blueFactor; green = greenGradient * greenFactor; red = redGradient * redFactor; } else if (filterType == EdgeFilterType.Sharpen) { blue = pixelBuffer[byteOffset] * blueFactor; green = pixelBuffer[byteOffset + 1] * greenFactor; red = pixelBuffer[byteOffset + 2] * redFactor; } else if (filterType == EdgeFilterType.SharpenGradient) { blue = pixelBuffer[byteOffset] + blueGradient * blueFactor; green = pixelBuffer[byteOffset + 1] + greenGradient * greenFactor; red = pixelBuffer[byteOffset + 2] + redGradient * redFactor; } } else { if (filterType == EdgeFilterType.EdgeDetectMono || filterType == EdgeFilterType.EdgeDetectGradient) { blue = green = red = 0; } else if (filterType == EdgeFilterType.Sharpen || filterType == EdgeFilterType.SharpenGradient) { blue = pixelBuffer[byteOffset]; green = pixelBuffer[byteOffset + 1]; red = pixelBuffer[byteOffset + 2]; } }
blue = (blue > 255 ? 255 : (blue < 0 ? 0 : blue));
green = (green > 255 ? 255 : (green < 0 ? 0 : green));
red = (red > 255 ? 255 : (red < 0 ? 0 : red));
resultBuffer[byteOffset] = (byte)blue; resultBuffer[byteOffset + 1] = (byte)green; resultBuffer[byteOffset + 2] = (byte)red; resultBuffer[byteOffset + 3] = 255; } } Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }

Gradient Based Edge Detection

Sample Images

The banner images depicting a butterfly featured throughout this article were generated using the sample application. The original image has been licenced under the Creative Commons Attribution-Share Alike 3.0 Unported, 2.5 Generic, 2.0 Generic and 1.0 Generic license. The original image is attributed to Kenneth Dwain Harrelson and can be downloaded from Wikipedia.

The sample image featuring a Scarlet Macaw has been licensed under the Creative Commons Attribution-Share Alike 3.0 Germany license. The original image can be downloaded from .

The Original Image

Ara_macao_-flying_away-8a

Edge Detect, Second Derivative, Threshold 50 

Edge Detect, Second Derivative, Threshold 50

Edge Detect Gradient, First Derivative, Blue

Edge Detect Gradient, First Derivative, Blue

Edge Detect Gradient, First Derivative, Green

Edge Detect Gradient, First Derivative, Green

Edge Detect Gradient, First Derivative, Green and Blue

Edge Detect Gradient, First Derivative, Green and Blue

Edge Detect Gradient, First Derivative, Red

Edge Detect Gradient, First Derivative, Red

Edge Detect Gradient, First Derivative, Red and Blue

Edge Detect Gradient, First Derivative, Red and Blue

Edge Detect Gradient, First Derivative, Red and Green

Edge Detect Gradient, First Derivative, Red and Green

Edge Detect Gradient, First Derivative, Red, Green and Blue

Edge Detect Gradient, First Derivative, Red, Green and Blue

Edge Detect Sharpen, Second Derivative, Threshold 40, Black

Edge Detect Sharpen, Second Derivative, Threshold 40, Black

Edge Detect Sharpen, Second Derivative, Threshold 40, Blue

Edge Detect Sharpen, Second Derivative, Threshold 40, Blue

Edge Detect Sharpen, Second Derivative, Threshold 40, Green

Edge Detect Sharpen, Second Derivative, Threshold 40, Green

Edge Detect Sharpen, Second Derivative, Threshold 40, Green and Blue

Edge Detect Sharpen, Second Derivative, Threshold 40, Green and Blue

Edge Detect Sharpen, Second Derivative, Threshold 40, Red

Edge Detect Sharpen, Second Derivative, Threshold 40, Red

Edge Detect Sharpen, Second Derivative, Threshold 40, Red and Blue

Edge Detect Sharpen, Second Derivative, Threshold 40, Red and Blue

Edge Detect Sharpen, Second Derivative, Threshold 40, Red and Green

Edge Detect Sharpen, Second Derivative, Threshold 40, Red and Green

Edge Detect Sharpen, Second Derivative, Threshold 40, White

Edge Detect Sharpen, Second Derivative, Threshold 40, White

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Blue

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Blue

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Green

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Green

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Green and Blue

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Green and Blue

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Red

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Red

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Red and Blue

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Red and Blue

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Red and Green

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, Red and Green

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, White

Edge Detect Sharpen Gradient, First Derivative, Threshold 0, White

Related Articles and Feedback

Feedback and questions are always encouraged. If you know of an alternative implementation or have ideas on a more efficient implementation please share in the comments section.

I’ve published a number of articles related to imaging and images of which you can find URL links here:

C# How to: Boolean Edge Detection

Article purpose

The purpose of this article is to detail Boolean Function Based Edge Detection. The filtering implemented in article occurs on a per pixel basis. The implementation relies on linear algebra. No GDI+ or traditional drawing methods are required.

Sample Source Code

This article is accompanied by a sample source code Visual Studio project which is available for download .

Using the Sample Application

Implemented as part of this article’s sample source code is a Sample Application. The concepts detailed in this article have all been implemented and tested using the associated Sample Application.

The first task required in using the Sample Application comes in the form of having to specify a source/input . Select files from the local system by clicking the Load Image button.

On the right-hand side of the Sample Application’s user interface the user will be presented with a set of controls which relate to various filter options. Users are able to specify implementation methods when adjusting filter options.

Filter type values are: None, Edge Detect and Sharpen.  Selecting a filter type of None results in no filtering being implemented, the original input/source will be displayed reflecting no change. When users select the Edge Detect filter type the resulting output reflects a black and white image of which only the detected edges are visible. The Sharpen filter type implements Boolean Edge Detection producing a sharpened by means of highlighting detected edges within the original input/source .

The Trackbar labelled Threshold is intended to allow users the option of reducing expressed as detected edges in the resulting . The level of present differs depending on the input/source specified, hence the option of implementing a threshold.

The three remaining TrackBar controls are labelled Red, Green and Blue. The Colour Factor filter options allows the user to specify the extend to which detected edges are expressed when sharpening an . When all three factor values are set to the same value edges appear white if the factor value exceeds zero. Factor values set to zero results in detected edges appearing as darker/black edge lines. Factor values can be set per colour value, which will have the effect of creating a coloured outline being visible in the result . The colour of the outlining effect can be controlled by adjusting individual colour factor values.

After having implemented image filtering the user has the option of saving the result to the local file system by clicking the Save Image button. The image shown below is screenshot of the Boolean Edge Detection sample application in action:

Boolean Edge Detection Sample Application

The Local Threshold and Boolean Function Based Edge Detection

Boolean Edge Detection is considered a a subset of . This method of employs both a local and global threshold. Implementation of the Boolean Edge Detection algorithm can be achieved by completing the following steps:

  1. Initiate a process of iterating each pixel that forms part of the source/input . Calculate a local threshold value based on a 3×3 /window. The should be positioned in a fashion where the pixel currently being iterated is located in the middle of the . Calculate a mean value using as input the 9 values covered by the . Create a new blank set to 3×3 dimensions. Compare each pixel in the source image to the calculated mean value. If a pixel’s value exceeds that of the mean value set the corresponding location on the blank to one. If a pixel’s value does not exceed that of the mean value set the corresponding location on the blank to zero.
  2. In the next step compare the newly created to the set of 16 edge masks. If the new is represented in the edge masks the middle pixel being iterated should be set to indicate an edge.
  3. The first two steps have to be repeated for each pixel contained in the source , in other words each pixel should be iterated. Edges should now be detected as you progress through image pixels, although false edges will also be present as a result of .
  4. False edges that were detected can be removed when implementing a global threshold. Firstly calculate the variance of each 3×3 . If the pixel currently being iterated was detected as part of an edge in step 2 and the variance calculated exceeds the global threshold the pixel can be considered as part of an edge. If the variance calculated equates to less than the global threshold a pixel does not form part of an edge even if the calculated matches one of the 16 edge masks.

The following image illustrates the 16 edge masks:

Edge Masks

Implementing Boolean Edge Detection

The sample source code implements the BooleanEdgeDetectionFilter targeting the class. This method implements the Boolean Edge Detection theoretical steps discussed in the previous section.

In order to determine if a newly calculated , as described in step 1, matches any of the 16 pre-defined edge masks the BooleanEdgeDetectionFilter implements comparison. The reasoning behind string based edge mask comparison boils down to efficiency, both in terms of reducing code complexity and improving performance. The method defines a generic List of type string and then proceeds to add 16 , each representing an edge mask. Edge mask strings express an edge mask in terms of a row and column format. The following code snippet lists the 16 edge masks strings being defined:

List<string> edgeMasks = new List<string>();

edgeMasks.Add("011011011"); edgeMasks.Add("000111111"); edgeMasks.Add("110110110"); edgeMasks.Add("111111000"); edgeMasks.Add("011011001"); edgeMasks.Add("100110110"); edgeMasks.Add("111011000"); edgeMasks.Add("111110000"); edgeMasks.Add("111011001"); edgeMasks.Add("100110111"); edgeMasks.Add("001011111"); edgeMasks.Add("111110100"); edgeMasks.Add("000011111"); edgeMasks.Add("000110111"); edgeMasks.Add("001011011"); edgeMasks.Add("001011011"); edgeMasks.Add("110110100");

The following code snippet list the complete implementation of the BooleanEdgeDetectionFilter :

public static Bitmap BooleanEdgeDetectionFilter( 
                                this Bitmap sourceBitmap, 
                                BooleanFilterType filterType, 
                                float redFactor = 1.0f, 
                                float greenFactor = 1.0f, 
                                float blueFactor = 1.0f, 
                                byte threshold = 0) 
{ 
    BitmapData sourceData = 
               sourceBitmap.LockBits(new Rectangle(0, 0, 
               sourceBitmap.Width, sourceBitmap.Height), 
               ImageLockMode.ReadOnly, 
               PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height]; byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
List<string> edgeMasks = new List<string>();
edgeMasks.Add("011011011"); edgeMasks.Add("000111111"); edgeMasks.Add("110110110"); edgeMasks.Add("111111000"); edgeMasks.Add("011011001"); edgeMasks.Add("100110110"); edgeMasks.Add("111011000"); edgeMasks.Add("111110000"); edgeMasks.Add("111011001"); edgeMasks.Add("100110111"); edgeMasks.Add("001011111"); edgeMasks.Add("111110100"); edgeMasks.Add("000011111"); edgeMasks.Add("000110111"); edgeMasks.Add("001011011"); edgeMasks.Add("001011011"); edgeMasks.Add("110110100");
int filterOffset = 1; int calcOffset = 0;
int byteOffset = 0; int matrixMean = 0; int matrixTotal = 0; double matrixVariance = 0;
double blueValue = 0; double greenValue = 0; double redValue = 0;
string matrixPatern = String.Empty;
for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { byteOffset = offsetY * sourceData.Stride + offsetX * 4;
matrixMean = 0; matrixTotal = 0; matrixVariance = 0;
matrixPatern = String.Empty;
//Step 1: Calculate local matrix for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
matrixMean += pixelBuffer[calcOffset]; matrixMean += pixelBuffer[calcOffset + 1]; matrixMean += pixelBuffer[calcOffset + 2]; } }
matrixMean = matrixMean / 9;
//Step 4: Calculate Variance for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
matrixTotal = pixelBuffer[calcOffset]; matrixTotal += pixelBuffer[calcOffset+1]; matrixTotal += pixelBuffer[calcOffset+2];
matrixPatern += (matrixTotal > matrixMean ? "1" : "0" );
matrixVariance += Math.Pow(matrixMean - (pixelBuffer[calcOffset] + pixelBuffer[calcOffset + 1] + pixelBuffer[calcOffset + 2]), 2); } }
matrixVariance = matrixVariance / 9;
if (filterType == BooleanFilterType.Sharpen) { blueValue = pixelBuffer[byteOffset]; greenValue = pixelBuffer[byteOffset + 1]; redValue = pixelBuffer[byteOffset + 2];
//Step 4: Exlclude noise using global // threshold if (matrixVariance > threshold) { //Step 2: Compare newly calculated // matrix and image masks if (edgeMasks.Contains(matrixPatern)) { blueValue = (blueValue * blueFactor); greenValue = (greenValue * greenFactor); redValue = (redValue * redFactor);
blueValue = (blueValue > 255 ? 255 : (blueValue < 0 ? 0 : blueValue));
greenValue = (greenValue > 255 ? 255 : (greenValue < 0 ? 0 : greenValue));
redValue = (redValue > 255 ? 255 : (redValue < 0 ? 0 : redValue)); } } } //Step 4: Exlclude noise using global // threshold //Step 2: Compare newly calculated // matrix and image masks else if (matrixVariance > threshold && edgeMasks.Contains(matrixPatern)) { blueValue = 255; greenValue = 255; redValue = 255; } else { blueValue = 0; greenValue = 0; redValue = 0; }
resultBuffer[byteOffset] = (byte)blueValue; resultBuffer[byteOffset + 1] = (byte)greenValue; resultBuffer[byteOffset + 2] = (byte)redValue; resultBuffer[byteOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }

Sample Images

This article features a photograph of The Eiffel Tower used in generating sample . The original image has been licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license and can be downloaded from Wikipedia: Original Image.

The Original Image

Tour_Eiffel_Wikimedia_Commons

Edge Detection, Threshold 50

Boolean Edge Detection Threshold 50

Sharpen, Threshold 50, Blue

Boolean Edge Detection Threshold 50 Sharpen Blue

Sharpen, Threshold 50, Green

Boolean Edge Detection Threshold 50 Sharpen Green

Sharpen, Threshold 50, Green and Blue

Boolean Edge Detection Threshold 50 Sharpen Green Blue

Sharpen, Threshold 50, Red

Boolean Edge Detection Threshold 50 Sharpen Red

Sharpen, Threshold 50, Red and Blue

Boolean Edge Detection Threshold 50 Sharpen Red Blue

Sharpen, Threshold 50, Red and Green

Boolean Edge Detection Threshold 50 Sharpen Red Green

Sharpen, Threshold 50, White – Red, Green and Blue

Boolean Edge Detection Threshold 50 Sharpen White

Related Articles and Feedback

Feedback and questions are always encouraged. If you know of an alternative implementation or have ideas on a more efficient implementation please share in the comments section.

I’ve published a number of articles related to imaging and images of which you can find URL links here:

C# How to: Morphological Edge Detection

Article purpose

The objective of this article is to explore   implemented by means of and  . In addition we explore the concept of implementing morphological .

Sample source code

This article is accompanied by a sample source code Visual Studio project which is available for download .

Using the sample application

This article is accompanied by a Sample Application intended to implement all of the concepts illustrated throughout this article. Using the sample application users can easily test and replicate concepts.

Clicking the Load Image button allows users to select source/input from the local system. Filter option categories are: Colour(s), morphology type, edge options and filter size.

This article and sample source code can process colour as source . The user can specify which colour components to include in resulting . The three labelled Red, Green and Blue indicate whether the related colour component features in result .

The four labelled Dilate, Erode, Open and Closed enable the user to select the type of morphological filter to apply.

options include: None, Edge Detection and Image Sharpening. Selecting None results in only the selected morphological filter being applied.

Filter sizes range from 3×3 up to 17×17. The filter size specified determines the intensity of the morphological filter applied.

If desired users are able to save filter result images to the local file system by clicking the Save Image button. The image below is a screenshot of the Morphological Edge Detection sample application in action:

Morphological_Edge_Detection_Sample_Application

Morphology – Image Erosion and Dilation

and are implementations of , a subset of . In simpler terms can be defined by this :

Dilation is one of the two basic operators in the area of , the other being . It is typically applied to , but there are versions that work on . The basic effect of the operator on a binary image is to gradually enlarge the boundaries of regions of foreground (i.e. white pixels, typically). Thus areas of foreground pixels grow in size while holes within those regions become smaller.

being a related concept is defined by this :

Erosion is one of the two basic operators in the area of , the other being . It is typically applied to , but there are versions that work on . The basic effect of the operator on a binary image is to erode away the boundaries of regions of foreground (i.e. white pixels, typically). Thus areas of foreground pixels shrink in size, and holes within those areas become larger.

From the definitions listed above we gather that increases the size of edges contained in an . In contrast decreases or shrinks the size of an ’s edges.

Image Edge Detection

We gain a good definition of from ’s article on :

Edge detection is the name for a set of mathematical methods which aim at identifying points in a at which the changes sharply or, more formally, has discontinuities. The points at which image brightness changes sharply are typically organized into a set of curved line segments termed edges. The same problem of finding discontinuities in 1D signals is known as and the problem of finding signal discontinuities over time is known as . Edge detection is a fundamental tool in , and , particularly in the areas of and .

In this article we implement based on the type of being performed. In the case of the eroded is subtracted from the original resulting in an with pronounced edges. When implementing , is achieved by subtracting the original from the dilated .

Image Sharpening

is often referred to by the term , from Wikipedia we gain the following :

Edge enhancement is an filter that enhances the edge contrast of an or in an attempt to improve its acutance (apparent sharpness).

The filter works by identifying sharp edge boundaries in the image, such as the edge between a subject and a background of a contrasting color, and increasing the image contrast in the area immediately around the edge. This has the effect of creating subtle bright and dark highlights on either side of any edges in the image, called and undershoot, leading the edge to look more defined when viewed from a typical viewing distance.

In this article we implement by first creating an which we then add to the original , resulting in an with enhanced edges.

Implementing Morphological Filters

The sample source code provides the definition of the DilateAndErodeFilter targeting the class. The DilateAndErodeFilter as a single method implementation is capable of applying a specified morphological filter, and . The following code snippet details the implementation of the the DilateAndErodeFilter :

public static Bitmap DilateAndErodeFilter(this Bitmap sourceBitmap,  
                                        int matrixSize, 
                                        MorphologyType morphType, 
                                        bool applyBlue = true, 
                                        bool applyGreen = true, 
                                        bool applyRed = true,
                                        MorphologyEdgeType edgeType = 
                                        MorphologyEdgeType.None)  
{ 
    BitmapData sourceData =  
               sourceBitmap.LockBits(new Rectangle (0, 0, 
               sourceBitmap.Width, sourceBitmap.Height), 
               ImageLockMode.ReadOnly,  
               PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
int filterOffset = (matrixSize - 1) / 2; int calcOffset = 0;
int byteOffset = 0;
int blue = 0; int green = 0; int red = 0;
byte morphResetValue = 0;
if (morphType == MorphologyType.Erosion) { morphResetValue = 255; }
for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = filterOffset; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { byteOffset = offsetY * sourceData.Stride + offsetX * 4;
blue = morphResetValue; green = morphResetValue; red = morphResetValue;
if (morphType == MorphologyType.Dilation) { for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
if (pixelBuffer[calcOffset] > blue) { blue = pixelBuffer[calcOffset]; }
if (pixelBuffer[calcOffset + 1] > green) { green = pixelBuffer[calcOffset + 1]; }
if (pixelBuffer[calcOffset + 2] > red) { red = pixelBuffer[calcOffset + 2]; } } } } else if (morphType == MorphologyType.Erosion) { for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
if (pixelBuffer[calcOffset] < blue) { blue = pixelBuffer[calcOffset]; }
if (pixelBuffer[calcOffset + 1] < green) { green = pixelBuffer[calcOffset + 1]; }
if (pixelBuffer[calcOffset + 2] < red) { red = pixelBuffer[calcOffset + 2]; } } } }
if (applyBlue == false ) { blue = pixelBuffer[byteOffset]; }
if (applyGreen == false ) { green = pixelBuffer[byteOffset + 1]; }
if (applyRed == false ) { red = pixelBuffer[byteOffset + 2]; }
if (edgeType == MorphologyEdgeType.EdgeDetection || edgeType == MorphologyEdgeType.SharpenEdgeDetection) { if (morphType == MorphologyType.Dilation) { blue = blue - pixelBuffer[byteOffset]; green = green - pixelBuffer[byteOffset + 1]; red = red - pixelBuffer[byteOffset + 2]; } else if (morphType == MorphologyType.Erosion) { blue = pixelBuffer[byteOffset] - blue; green = pixelBuffer[byteOffset + 1] - green; red = pixelBuffer[byteOffset + 2] - red; }
if (edgeType == MorphologyEdgeType.SharpenEdgeDetection) { blue += pixelBuffer[byteOffset]; green += pixelBuffer[byteOffset + 1]; red += pixelBuffer[byteOffset + 2]; } }
blue = (blue > 255 ? 255 : (blue < 0 ? 0 : blue)); green = (green > 255 ? 255 : (green < 0 ? 0 : green)); red = (red > 255 ? 255 : (red < 0 ? 0 : red));
resultBuffer[byteOffset] = (byte)blue; resultBuffer[byteOffset + 1] = (byte)green; resultBuffer[byteOffset + 2] = (byte)red; resultBuffer[byteOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap; }

Sample Images

The source/input used in this article is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license and can be downloaded from Wikipedia: http://en.wikipedia.org/wiki/File:Bathroom_with_bathtube.jpg

Original Image

1280px-Bathroom_with_bathtube

Erosion 3×3, Edge Detect, Red, Green and Blue

Erosion 3x3 Edge Detect Red, Green and Blue

Erosion 3×3, Edge Detect, Blue

Erosion 3x3, Edge Detect, Blue

Erosion 3×3, Edge Detect, Green and Blue

Erosion 3x3, Edge Detect, Green and Blue

Erosion 3×3, Edge Detect, Red

Erosion 3x3, Edge Detect, Red

Erosion 3×3, Edge Detect, Red and Blue

Erosion 3x3, Edge Detect, Red and Blue

Erosion 3×3, Edge Detect, Red and Green

Erosion 3x3, Edge Detect, Red and Green

Erosion 7×7, Sharpen, Red, Green and Blue

Erosion 7x7, Sharpen, Red, Green and Blue

Erosion 7×7, Sharpen, Blue

Erosion 7x7, Sharpen, Blue

Erosion 7×7, Sharpen, Green

Erosion 7x7, Sharpen, Green

Erosion 7×7, Sharpen, Green and Blue

Erosion 7x7, Sharpen, Green and Blue

Erosion 7×7, Sharpen, Red

Erosion 7x7, Sharpen, Red

Erosion 7×7, Sharpen, Red and Blue

Erosion 7x7, Sharpen, Red and Blue

Erosion 7×7, Sharpen, Red and Green

Erosion 7x7, Sharpen, Red and Green

Related Articles and Feedback

Feedback and questions are always encouraged. If you know of an alternative implementation or have ideas on a more efficient implementation please share in the comments section.

I’ve published a number of articles related to imaging and images of which you can find URL links here:


Dewald Esterhuizen

Blog Stats

  • 768,209 hits

Enter your email address to follow and receive notifications of new posts by email.

Join 223 other followers

Archives

Twitter feed


%d bloggers like this: