Article purpose
The purpose of this article is to detail Boolean Function Based Edge Detection. The Image filtering implemented in this 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 here.
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 image. Select image files from the local file 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 Edge Detection 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 image will be displayed reflecting no change. When users select the Edge Detect filter type the resulting output image 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 image by means of highlighting detected edges within the original input/source image.
The Trackbar labelled Threshold is intended to allow users the option of reducing image noise expressed as detected edges in the resulting image. The level of image noise present differs depending on the input/source image 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 image. 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 image. 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 image 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:
The Local Threshold and Boolean Function Based Edge Detection
Boolean Edge Detection is considered a a subset of Image Morphological Filtering. This method of edge detection employs both a local and global threshold. Implementation of the Boolean Edge Detection algorithm can be achieved by completing the following steps:
- Initiate a process of iterating each pixel that forms part of the source/input image. Calculate a local threshold value based on a 3×3 matrix/window. The matrix should be positioned in a fashion where the pixel currently being iterated is located in the middle of the matrix. Calculate a mean value using as input the 9 values covered by the matrix. Create a new blank matrix 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 matrix to one. If a pixel’s value does not exceed that of the mean value set the corresponding location on the blank matrix to zero.
- In the next step compare the newly created matrix to the set of 16 edge masks. If the new matrix is represented in the edge masks the middle pixel being iterated should be set to indicate an edge.
- The first two steps have to be repeated for each pixel contained in the source image, 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 image noise.
- False edges that were detected can be removed when implementing a global threshold. Firstly calculate the variance of each 3×3 matrix. 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 matrix matches one of the 16 edge masks.
The following image illustrates the 16 edge masks:
Implementing Boolean Edge Detection
The sample source code implements the BooleanEdgeDetectionFilter extension method targeting the Bitmap class. This method implements the Boolean Edge Detection theoretical steps discussed in the previous section.
In order to determine if a newly calculated matrix, as described in step 1, matches any of the 16 pre-defined edge masks the BooleanEdgeDetectionFilter extension method implements string 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 strings, each representing an edge mask. Edge mask strings express an edge mask matrix 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 extension method:
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 images. 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
Edge Detection, Threshold 50
Sharpen, Threshold 50, Blue
Sharpen, Threshold 50, Green
Sharpen, Threshold 50, Green and Blue
Sharpen, Threshold 50, Red
Sharpen, Threshold 50, Red and Blue
Sharpen, Threshold 50, Red and Green
Sharpen, Threshold 50, White – Red, Green and Blue
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: 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
Filed under: Augmented Reality, C#, Code Samples, Edge Detection, Extension Methods, Graphic Filters, Graphics, How to, Image Filters, Learn Everyday, Microsoft, Morphology Filters, New Version, Opensource, Update Tagged: Augmented Reality, Binary Image, Bitmap ARGB, Bitmap Filters, Bitmap.LockBits, BitmapData, Boolean Edge Detection, C#, Change detection, Code Sample, Computer vision, Converting Images, Convolution filters, Convolution Kernel, Convolution matrix, Edge Masks, Feature extraction, Graphic Filters, Image, Image Edge Detection, Image Morphology, Image processing, Image Transform, Line Detection, Machine vision, Matrix, Pixel Filters
