Article Purpose
This article explores various image processing concepts, which feature in combination when implementing Image Boundary Extraction. Concepts covered within this article include: Morphological Image Erosion and Image Dilation, Image Addition and Subtraction, Boundary Sharpening, Boundary Tracing and Boundary Extraction.
Parrot: Boundary Extraction, 3×3, Red, Green, Blue
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
This article’s accompanying sample source code includes the definition of a sample application. The sample application serves as an implementation of the concepts discussed in this article. In using the sample application concepts can be easily tested and replicated.
The sample application has been defined as a Windows Forms application. The user interface enables the user to configure several options which influence the output produced from image filtering processes. The following section describes the options available to a user when executing the sample application:
- Loading and Saving files – Users can specify source/input images through clicking the Load Image button. If desired, resulting filtered images can be saved to the local file system when clicking the Save Image button.
- Filter Type – The types of filters implemented represent variations on Image Boundary Extraction. The supported filter types are: Conventional Boundary extraction, Boundary Sharpening and Boundary Tracing.
- Filter Size – Filter intensity/strength will mostly be reliant on the filter size implemented. A Filter size represents the number of neighbouring pixels examined when applying filters.
- Colours Applied – The sample source code and sample application provides functionality allowing a filter to only effect user specified colour components. Colour components are represented in the form of an RGB colour scheme. The inclusion or exclusion of the colour components Red, Green and Blue will be determined through user configuration.
- Structuring Element – As mentioned, the Filter Size option determines the size of neighbourhood pixels examined. The Structuring Element’s setup determine the neighbouring pixels within the pixel neighbourhood size bounds that should be used as input when calculating filter results.
The following image is a screenshot of the Image Boundary Extraction sample application in action:
Parrot: Boundary Extraction, 3×3, Green
Morphological Boundary Extraction
Image Boundary Extraction can be considered a method of Image Edge Detection. In contrast to more commonly implemented gradient based edge detection methods, Image Boundary Extraction originates from Morphological Image Filters.
When drawing a comparison, Image Boundary Extraction and Morphological Edge Detection express strong similarities. Morphological Edge Detection results from the difference in image erosion and image dilation. Considered from a different point of view, creating one image expressing thicker edges and another image expressing thinner edges provides the means to calculate the difference in edges.
Image Boundary Extraction implements the same concept as Morphological Edge Detection. The base concept can be regarded as calculating the difference between two images which rendered the same image, but expressing a difference in image edges. Image Boundary Extraction relies on calculating the difference between either image erosion and the source image or image dilation and the source image. The difference between image erosion and image dilation in most cases result in more of difference than the difference between image erosion and the source image or image dilation and the source image. The result of Image Boundary Extraction representing less of a difference than Morphological Edge Detection can be observed in Image Boundary Extraction being expressed in finer/smaller width lines.
Difference of Gaussians is another method of edge detection which functions along the same basis. Edges are determined by calculating the difference between two images, each having been filtered from the same source image, using a Gaussian blur of differing intensity levels.
Parrot: Boundary Extraction, 3×3, Red, Green, Blue
Boundary Sharpening
The concept of Boundary Sharpening refers to enhancing or sharpening the boundaries or edges expressed in a source/input image. Boundaries can be easily determined or extracted as discussed earlier when exploring Boundary Extraction.
The steps involved in performing Boundary Sharpening can be described as follows:
- Extract Boundaries – Determine boundaries by performing image dilation and calculating the difference between the dilated image and the source image.
- Match Source Edges and Extracted Boundaries – The boundaries extracted in the previous step represent the difference between image dilation and the original source image. Ensure that extracted boundaries match the source image through performing image dilation on a copy of the source/input image.
- Emphasise Extracted boundaries in source image – Perform image addition using the extracted boundaries image and dilated copy of the source image.
Parrot: Boundary Extraction, 3×3, Red, Green, Blue
Boundary Tracing
Boundary Tracing refers to applying image filters which result in image edges/boundaries appearing darker or more pronounced. This type of filter also relies on Boundary Extraction.
Boundary Tracing can be implemented in two steps, described as follows:
- Extract Boundaries – Determine boundaries by performing image dilation and calculating the difference between the dilated image and the source image.
- Emphasise Extracted boundaries in source image – Subtract the extracted boundaries from the original source image.
Parrot: Boundary Extraction, 3×3, Red, Green, Blue
Implementing Morphological Erosion and Dilation
The accompanying sample source code defines the MorphologyOperation method, defined as an extension method targeting the Bitmap class. In terms of parameters this method expects a two dimensional array representing a structuring element. The other required parameter represents an enumeration value indicating which Morphological Operation to perform, either erosion or dilation.
The following code snippet provides the definition in full:
private static Bitmap MorphologyOperation(this Bitmap sourceBitmap, bool[,] se, MorphologyOperationType morphType, bool applyBlue = true, bool applyGreen = true, bool applyRed = true) { 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 = (se.GetLength(0) - 1) / 2; int calcOffset = 0, byteOffset = 0; byte blueErode = 0, greenErode = 0, redErode = 0; byte blueDilate = 0, greenDilate = 0, redDilate = 0;
for (int offsetY = 0; offsetY < sourceBitmap.Height - filterOffset; offsetY++) { for (int offsetX = 0; offsetX < sourceBitmap.Width - filterOffset; offsetX++) { byteOffset = offsetY * sourceData.Stride + offsetX * 4;
blueErode = 255; greenErode = 255; redErode = 255; blueDilate = 0; greenDilate = 0; redDilate = 0;
for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) { for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) { if (se[filterY + filterOffset, filterX + filterOffset] == true) { calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride);
calcOffset = (calcOffset < 0 ? 0 : (calcOffset >= pixelBuffer.Length + 2 ? pixelBuffer.Length - 3 : calcOffset));
blueDilate = (pixelBuffer[calcOffset] > blueDilate ? pixelBuffer[calcOffset] : blueDilate);
greenDilate = (pixelBuffer[calcOffset + 1] > greenDilate ? pixelBuffer[calcOffset + 1] : greenDilate);
redDilate = (pixelBuffer[calcOffset + 2] > redDilate ? pixelBuffer[calcOffset + 2] : redDilate);
blueErode = (pixelBuffer[calcOffset] < blueErode ? pixelBuffer[calcOffset] : blueErode);
greenErode = (pixelBuffer[calcOffset + 1] < greenErode ? pixelBuffer[calcOffset + 1] : greenErode);
redErode = (pixelBuffer[calcOffset + 2] < redErode ? pixelBuffer[calcOffset + 2] : redErode); } } }
blueErode = (applyBlue ? blueErode : pixelBuffer[byteOffset]); blueDilate = (applyBlue ? blueDilate : pixelBuffer[byteOffset]);
greenErode = (applyGreen ? greenErode : pixelBuffer[byteOffset + 1]); greenDilate = (applyGreen ? greenDilate : pixelBuffer[byteOffset + 1]);
redErode = (applyRed ? redErode : pixelBuffer[byteOffset + 2]); redDilate = (applyRed ? redDilate : pixelBuffer[byteOffset + 2]);
if (morphType == MorphologyOperationType.Erosion) { resultBuffer[byteOffset] = blueErode; resultBuffer[byteOffset + 1] = greenErode; resultBuffer[byteOffset + 2] = redErode; } else if (morphType == MorphologyOperationType.Dilation) { resultBuffer[byteOffset] = blueDilate; resultBuffer[byteOffset + 1] = greenDilate; resultBuffer[byteOffset + 2] = redDilate; }
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; }
Parrot: Boundary Extraction, 3×3, Red, Green
Implementing Image Addition
The sample source code encapsulates the process of combining two separate images through means of addition. The AddImage method serves as a single declaration of image addition functionality. This method has been defined as an extension method targeting the Bitmap class. Boundary Sharpen filtering implements image addition.
The following code snippet provides the definition of the AddImage extension method:
private static Bitmap AddImage(this Bitmapsource Bitmap, Bitmap addBitmap) { BitmapData sourceData = sourceBitmap.LockBits(new Rectangle (0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, resultBuffer, 0, resultBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
BitmapData addData = addBitmap.LockBits(new Rectangle(0, 0, addBitmap.Width, addBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] addBuffer = new byte[addData.Stride * addData.Height];
Marshal.Copy(addData.Scan0, addBuffer, 0, addBuffer.Length);
addBitmap.UnlockBits(addData);
for (int k = 0; k + 4 < resultBuffer.Length && k + 4 < addBuffer.Length; k += 4) { resultBuffer[k] = AddColors(resultBuffer[k], addBuffer[k]); resultBuffer[k + 1] = AddColors(resultBuffer[k + 1], addBuffer[k + 1]); resultBuffer[k + 2] = AddColors(resultBuffer[k + 2], addBuffer[k + 2]); resultBuffer[k + 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; }
private static byte AddColors(byte color1, byte color2) { int result = color1 + color2;
return (byte)(result < 0 ? 0 : (result > 255 ? 255 : result)); }
Parrot: Boundary Extraction, 3×3, Red, Green, Blue
Implementing Image Subtraction
In a similar fashion regarding the AddImage method the sample code defines the SubractImage method. By definition this method serves as an extension method targeting the Bitmap class. Image subtraction has been implemented in Boundary Extraction and Boundary Tracing.
The definition of the SubtractImage method listed as follows:
private static Bitmap SubtractImage(this Bitmap sourceBitmap, Bitmap subtractBitmap) { BitmapData sourceData = sourceBitmap.LockBits(new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, resultBuffer, 0, resultBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
BitmapData subtractData = subtractBitmap.LockBits(new Rectangle(0, 0, subtractBitmap.Width, subtractBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] subtractBuffer = new byte[subtractData.Stride * subtractData.Height];
Marshal.Copy(subtractData.Scan0, subtractBuffer, 0, subtractBuffer.Length);
subtractBitmap.UnlockBits(subtractData);
for (int k = 0; k + 4 < resultBuffer.Length && k + 4 < subtractBuffer.Length; k += 4) { resultBuffer[k] = SubtractColors(resultBuffer[k], subtractBuffer[k]);
resultBuffer[k + 1] = SubtractColors(resultBuffer[k + 1], subtractBuffer[k + 1]);
resultBuffer[k + 2] = SubtractColors(resultBuffer[k + 2], subtractBuffer[k + 2]);
resultBuffer[k + 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; }
private static byte SubtractColors(byte color1, byte color2) { int result = (int)color1 - (int)color2;
return (byte)(result < 0 ? 0 : result); }
Parrot: Boundary Extraction, 3×3, Green
Implementing Image Boundary Extraction
In the sample source code processing Image Boundary Extraction can be achieved when invoking the BoundaryExtraction method. Defined as an extension method, the BoundaryExtraction method targets the Bitmap class.
As discussed earlier, this method performs Boundary Extraction through subtracting the source image from a dilated copy of the source image.
The following code snippet details the definition of the BoundaryExtraction method:
private static Bitmap BoundaryExtraction(this Bitmap sourceBitmap, bool[,] se, bool applyBlue = true, bool applyGreen = true, bool applyRed = true) { Bitmap resultBitmap = sourceBitmap.MorphologyOperation(se, MorphologyOperationType.Dilation, applyBlue, applyGreen, applyRed);
resultBitmap = resultBitmap.SubtractImage(sourceBitmap);
return resultBitmap; }
Parrot: Boundary Extraction, 3×3, Red, Blue
Implementing Image Boundary Sharpening
Boundary Sharpening in the sample source code has been implemented through the definition of the BoundarySharpen method. The BoundarySharpen extension method targets the Bitmap class. The following code snippet provides the definition:
private static Bitmap BoundarySharpen(this Bitmap sourceBitmap, bool[,] se, bool applyBlue = true, bool applyGreen = true, bool applyRed = true) { Bitmap resultBitmap = sourceBitmap.BoundaryExtraction(se, applyBlue, applyGreen, applyRed);
resultBitmap = sourceBitmap.MorphologyOperation(se, MorphologyOperationType.Dilation, applyBlue, applyGreen, applyRed).AddImage(resultBitmap);
return resultBitmap; }
Parrot: Boundary Extraction, 3×3, Green
Implementing Image Boundary Tracing
Boundary Tracing has been defined through the BoundaryTrace extension method, which targets the Bitmap class. Similar to the BoundarySharpen method this method performs Boundary Extraction, the result of which serves to be subtracted from the original source image. Subtracting image boundaries/edges result in those boundaries/edges being darkened, or traced. The definition of the BoundaryTracing extension method detailed as follows:
private static Bitmap BoundaryTrace(this Bitmap sourceBitmap, bool[,] se, bool applyBlue = true, bool applyGreen = true, bool applyRed = true) { Bitmap resultBitmap = sourceBitmap.BoundaryExtraction(se, applyBlue, applyGreen, applyRed);
resultBitmap = sourceBitmap.SubtractImage(resultBitmap);
return resultBitmap; }
Parrot: Boundary Extraction, 3×3, Green, Blue
Implementing a Wrapper Method
The BoundaryExtractionFilter method is the only method defined as publicly accessible. Following convention, this method’s definition signals the method as an extension method targeting the Bitmap class. This method has the intention of acting as a wrapper method, a single method capable of performing Boundary Extraction, Boundary Sharpening and Boundary Tracing, depending on method parameters.
The definition of the BoundaryExtractionFilter method detailed by the following code snippet:
public static Bitmap BoundaryExtractionFilter(this Bitmap sourceBitmap, bool[,] se, BoundaryExtractionFilterType filterType, bool applyBlue = true, bool applyGreen = true, bool applyRed = true) { Bitmap resultBitmap = null;
if (filterType == BoundaryExtractionFilterType.BoundaryExtraction) { resultBitmap = sourceBitmap.BoundaryExtraction(se, applyBlue, applyGreen, applyRed); } else if (filterType == BoundaryExtractionFilterType.BoundarySharpen) { resultBitmap = sourceBitmap.BoundarySharpen(se, applyBlue, applyGreen, applyRed); } else if (filterType == BoundaryExtractionFilterType.BoundaryTrace) { resultBitmap = sourceBitmap.BoundaryTrace(se, applyBlue, applyGreen, applyRed); }
return resultBitmap; }
Parrot: Boundary Extraction, 3×3, Red, Green, Blue
Sample Images
This article features a number of sample images. All featured images have been licensed allowing for reproduction. The following images feature as sample images:
- Scarlet Macaw at Diergaarde Blijdorp, Rotterdam, Netherlands.
- Attributed to: Jar0d. This file is licensed under the Creative Commons Attribution-Share Alike 2.0 Generic license.
- Download from Wikipedia
- A Scarlet Macaw flying away from the photographer.
- Attributed to: Robert01. This file is licensed under the Creative Commons Attribution-Share Alike 3.0 Germany license.
- Download from Wikipedia
- Blue-and-yellow Macaw in flight.
- Attributed to: Luc Viatour – www.lucnix.be. This file is licensed under the Creative Commons Attribution 2.0 Generic license.
- Download from Wikipedia
- Two Green-winged Macaws (also known as the Red-and-green Macaw) resting at Seaport Village, San Diego, USA.
- Attributed to: Dave Fayram. This file is licensed under the Creative Commons Attribution 2.0 Generic license.
- Download from Wikimedia.org
- A Scarlet Macaw riding a small tricycle at an exhibition in Spain.
- Attributed to: The Torch. This file is licensed under the Creative Commons Attribution 2.0 Generic license.
- Download from Wikipedia.
- Long-tailed Broadbill (Psarisomus dalhousiae), Kaeng Krachan National Park, Phetchaburi, Thailand.
- Attributed to User-JJ Harrison. This file is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license.
- Download from Wikipedia.
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 filtering by directly manipulating Pixel ARGB values
- C# How to: Image filtering implemented using a ColorMatrix
- C# How to: Blending Bitmap images using colour filters
- C# How to: Bitmap Colour Substitution implementing thresholds
- C# How to: Generating Icons from Images
- C# How to: Swapping Bitmap ARGB Colour Channels
- C# How to: Bitmap Pixel manipulation using LINQ Queries
- C# How to: Linq to Bitmaps – Partial Colour Inversion
- C# How to: Bitmap Colour Balance
- C# How to: Bi-tonal Bitmaps
- C# How to: Bitmap Colour Tint
- C# How to: Bitmap Colour Shading
- C# How to: Image Solarise
- C# How to: Image Contrast
- C# How to: Bitwise Bitmap Blending
- C# How to: Image Arithmetic
- C# How to: Image Convolution
- C# How to: Image Edge Detection
- C# How to: Difference Of Gaussians
- C# How to: Image Median Filter
- C# How to: Image Unsharp Mask
- C# How to: Image Colour Average
- C# How to: Image Erosion and Dilation
- C# How to: Morphological Edge Detection
- C# How to: Boolean Edge Detection
- C# How to: Gradient Based Edge Detection
- C# How to: Sharpen Edge Detection
- C# How to: Image Cartoon Effect
- C# How to: Calculating Gaussian Kernels
- C# How to: Image Blur
- C# How to: Image Transform Rotate
- C# How to: Image Transform Shear
- C# How to: Compass Edge Detection
- C# How to: Oil Painting and Cartoon Filter
- C# How to: Stained Glass Image Filter
- C# How to: Image ASCII Art
- C# How to: Weighted Difference of Gaussians
Filed under: Augmented Reality, Blogging, C#, Code Samples, Edge Detection, Extension Methods, Graphic Filters, Graphics, How to, Image Arithmetic, Image Filters, Image Processing, Image Transform, Learn Everyday, Microsoft, Morphology Filters, New Version, Opensource, Tip, Update Tagged: Augmented Reality, Binary Image, Bitmap ARGB, Bitmap Filters, Bitmap.LockBits, BitmapData, Boundary Sharpen, Boundary Tracing, C#, Code Sample, Computer vision, Converting Images, Edge Detect, Edge Detection, Edge enhance, Edge Tracing, Feature extraction, Graphic Filters, Image, Image Boundary Extraction, Image Difference, Image Dilation, Image Erosion, Image Intensity, Image Morphology, Image processing, Image Sharpen, Image Transform, Machine vision, Morphologial Filters, Morphological Edge Detection, Morphological Transform, Pixel Filters, Structured Element
