C# How to: Image Convolution

Article Purpose

This article is intended to serve as an introduction to the concepts related to creating and processing filters being applied on . The filters discussed are: Blur, Gaussian Blur, Soften, Motion Blur, High Pass, Edge Detect, Sharpen and Emboss.

Sample Source code

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

Using the Sample Application

A Sample Application has been included with this article’s sample source code. The Sample Application has been developed to target the platform. Using the Sample Application users are able to select a source/input from the local file system and from a drop down select a filter to apply. Filtered can be saved to the local file system when a user clicks the ‘Save’ button.

The following screenshot shows the Image Convolution Filter sample application in action.

ImageConvolutionFilter_Screenshot

Image Convolution

Before delving into discussions on technical implementation details it is important to have a good understanding of the concepts behind .

In relation to can be considered as algorithms being implemented resulting in translating input/source . Algorithms being applied generally take the form of accepting two input values and producing a third value considered to be a modified version of one of the input values.

can be implemented to produce filters such as: Blurring, Smoothing, Edge Detection, Sharpening and Embossing. The resulting filtered still bares a relation to the input source .

Convolution Matrix

In this article we will be implementing through means of a or representing the algorithms required to produce resulting filtered . A should be considered as a two dimensional array or grid. It is required that the number or rows and columns be of an equal size, which is furthermore required to not be a factor of two. Examples of valid dimensions could be 3×3 or 5×5. Dimensions such as 2×2 or 4×4 would not be valid. Generally the sum total of all the values expressed in a equates to one, although it is not a strict requirement.

The following table represents an example /:

2 0 0
0 -1 0
0 0 -1

An important aspect to keep in mind: When implementing a the value of a pixel will be determined by the values of the pixel’s neighbouring pixels. The values contained in a represent factor values intended to be multiplied with pixel values. In a the centre pixel represents the pixel currently being modified. Neighbouring matrix values express the factor to be applied to the corresponding neighbouring pixels in regards to the pixel currently being modified.

The ConvolutionFilterBase class

The sample code defines the class ConvolutionFilterBase. This class is intended to represent the minimum requirements of a . When defining a we will be inheriting from the ConvolutionFilterBase class. Because this class and its members have been defined as , implementing classes are required to implement all defined members.

The following code snippet details the ConvolutionFilterBase definition:

public abstract class ConvolutionFilterBase 
{ 
    public abstract string FilterName 
    {
        get; 
    }

public abstract double Factor { get; }
public abstract double Bias { get; }
public abstract double[,] FilterMatrix { get; } }

As to be expected the member property FilterMatrix is intended to represent a two dimensional array containing a . In some instances when the sum total of values do not equate to 1  a filter might implement a Factor value other than the default of 1. Additionally some filters may also require a Bias value to be added the final result value when calculating the matrix.

Calculating a Convolution Filter

Calculating filters and creating the resulting can be achieved by invoking the ConvolutionFilter method. This method is defined as an targeting the class. The definition of the ConvolutionFilter as follows:

 public static Bitmap ConvolutionFilter<T>(this Bitmap sourceBitmap, T filter)  
                                 where T : ConvolutionFilterBase 
{ 
    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 = filter.FilterMatrix.GetLength(1); int filterHeight = filter.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]) * filter.FilterMatrix[filterY + filterOffset, filterX + filterOffset];
green += (double)(pixelBuffer[calcOffset + 1]) * filter.FilterMatrix[filterY + filterOffset, filterX + filterOffset];
red += (double)(pixelBuffer[calcOffset + 2]) * filter.FilterMatrix[filterY + filterOffset, filterX + filterOffset]; } }
blue = filter.Factor * blue + filter.Bias; green = filter.Factor * green + filter.Bias; red = filter.Factor * red + filter.Bias;
if (blue > 255) { blue = 255; } else if (blue < 0) { blue = 0; }
if (green > 255) { green = 255; } else if (green < 0) { green = 0; }
if (red > 255) { red = 255; } else if (red < 0) { red = 0; }
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; }

The following section provides a detailed discussion of the ConvolutionFilter .

ConvolutionFilter<T> – Method Signature

public static Bitmap ConvolutionFilter<T>
                     (this Bitmap sourceBitmap,
                      T filter)  
                      where T : ConvolutionFilterBase 

The ConvolutionFilter method defines a generic type T constrained by the requirement to be of type ConvolutionFilterBase. The filter parameter being of generic type T has to be of type ConvolutionFilterBase or a type which inherits from the ConvolutionFilterBase class.

Notice how the sourceBitmap parameter type definition is preceded by the indicating the method can be implemented as an . Keep in mind are required to be declared as static.

The sourceBitmap parameter represents the source/input upon which the filter is to be applied. Note that the ConvolutionFilter method is implemented as immutable. The input parameter values are not modified, instead a new instance will be created and returned.

ConvolutionFilter<T> – Creating the Data Buffer

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);

In order to access the underlying ARGB values from a object we first need to lock the into memory by invoking the method. Locking a into memory prevents the from moving a object to a new location in memory.

When invoking the method the source code instantiates a object from the return value. The property represents the number of in a single pixel row. In this scenario the property should be equal to the ’s width in pixels multiplied by four seeing as every pixel consists of four : Alpha, Red, Green and Blue.

The ConvolutionFilter method defines two buffers, of which the size is set to equal the size of the ’s underlying data. The property of type represents the memory address of the first value of a ’s underlying buffer. Using the method we specify the starting point memory address from where to start copying the ’s buffer.

Important to remember is the next operation being performed: invoking the method. If a has been locked into memory ensure releasing the lock by invoking the method.

ConvolutionFilter<T> – Iterating Rows and Columns

double blue = 0.0; 
double green = 0.0; 
double red = 0.0; 

int filterWidth = filter.FilterMatrix.GetLength(1); int filterHeight = filter.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;

The ConvolutionFilter method employs two for loops in order to iterate each pixel represented in the ARGB data buffer. Defining two for loops to iterate a one dimensional array simplifies the concept of accessing the array in terms of rows and columns.

Note that the inner loop is limited to the width of the source/input parameter, in other words the number of horizontal pixels. Remember that the data buffer represents four , Alpha, Red, Green and Blue, for each pixel. The inner loop therefore iterates entire pixels.

As discussed earlier the filter has to be declared as a two dimensional array with the same odd number of rows and columns. If the current pixel being processed relates to the element at the centre of the matrix, the width of the matrix less one divided by two equates to the neighbouring pixel index values.

The index of the current pixel can be calculated by multiplying the current row index (offsetY) and the number of ARGB byte values per row of pixels (sourceData.Stride), to which is added the current column/pixel index (offsetX) multiplied by four.

ConvolutionFilter<T> – Iterating the Matrix

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]) * filter.FilterMatrix[filterY + filterOffset, filterX + filterOffset];
green += (double)(pixelBuffer[calcOffset + 1]) * filter.FilterMatrix[filterY + filterOffset, filterX + filterOffset];
red += (double)(pixelBuffer[calcOffset + 2]) * filter.FilterMatrix[filterY + filterOffset, filterX + filterOffset]; } }

The ConvolutionFilter method iterates the two dimensional by implementing two for loops, iterating rows and for each row iterating columns. Both loops have been declared to have a starting point equal to the negative value of half the length (filterOffset). Initiating the loops with negative values simplifies implementing the concept of neighbouring pixels.

The first statement performed within the inner loop calculates the index of the neighbouring pixel in relation to the current pixel. Next the value is applied as a factor to the corresponding neighbouring pixel’s individual colour components. The results are added to the totals variables blue, green and red.

In regards to each iteration iterating in terms of an entire pixel, to access individual colour components the source code adds the required colour component offset. Note: ARGB colour components are in fact expressed in reversed order: Blue, Green, Red and Alpha. In other words, a pixel’s first (offset 0) represents Blue, the second (offset 1) represents Green, the third (offset 2) represents Red and the last (offset 3) representing the Alpha component.

ConvolutionFilter<T> – Applying the Factor and Bias

blue = filter.Factor * blue + filter.Bias; 
green = filter.Factor * green + filter.Bias; 
red = filter.Factor * red + filter.Bias; 

if (blue > 255) { blue = 255; } else if (blue < 0) { blue = 0; }
if (green > 255) { green = 255; } else if (green < 0) { green = 0; }
if (red > 255) { red = 255; } else if (red < 0) { red = 0; }
resultBuffer[byteOffset] = (byte)(blue); resultBuffer[byteOffset + 1] = (byte)(green); resultBuffer[byteOffset + 2] = (byte)(red); resultBuffer[byteOffset + 3] = 255;

After iterating the matrix and calculating the matrix values of the current pixel’s Red, Green and Blue colour components we apply the Factor and add the Bias defined by the filter parameter.

Colour components may only contain a value ranging from 0 to 255 inclusive. Before we assign the newly calculated colour component value we ensure that the value falls within the required range. Values which exceed 255 are set to 255 and values less than 0 are set to 0. Note that assignment is implemented in terms of the result buffer, the original source buffer remains unchanged.

ConvolutionFilter<T> – Returning the Result

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 final steps performed by the ConvolutionFilter method involves creating a new object instance and copying the calculated result buffer. In a similar fashion to reading underlying pixel data we copy the result buffer to the object.

Creating Filters

The main requirement when creating a filter is to inherit from the ConvolutionBaseFilter class. The following sections of this article will discuss various filter types and variations where applicable.

To illustrate the different effects resulting from applying filters all of the filters discussed make use of the same source . The original file is licensed under the Creative Commons Attribution 2.0 Generic license and can be downloaded from:

 http://commons.wikimedia.org/wiki/File:Ara_macao_-on_a_small_bicycle-8.jpg

Ara_macao_-on_a_small_bicycle-8

Blur Filters

is typically used to reduce and detail. The filter’s matrix size affects the level of . A larger results in higher level of , whereas a smaller results in a lesser level of .

Blur3x3Filter

The Blur3x3Filter results in a slight to medium level of . The consists of 9 elements in a 3×3 configuration.

Blur3x3Filter

public class Blur3x3Filter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "Blur3x3Filter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 0.0, 0.2, 0.0, }, { 0.2, 0.2, 0.2, }, { 0.0, 0.2, 0.2, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Blur5x5Filter

The Blur5x5Filter results in a medium level of . The consists of 25 elements in a 5×5 configuration. Notice the factor of 1.0 / 13.0.

Blur5x5Filter

public class Blur5x5Filter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "Blur5x5Filter"; } 
    }

private double factor = 1.0 / 13.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 0, 0, 1, 0, 0, }, { 0, 1, 1, 1, 0, }, { 1, 1, 1, 1, 1, }, { 0, 1, 1, 1, 0, }, { 0, 0, 1, 0, 0, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Gaussian3x3BlurFilter

The Gaussian3x3BlurFilter implements a through a matrix of 9 elements in a 3×3 configuration. The sum total of all elements equal 16, therefore the Factor is defined as 1.0 / 16.0. Applying this filter results in a slight to medium level of .

Gaussian3x3BlurFilter

public class Gaussian3x3BlurFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "Gaussian3x3BlurFilter"; } 
    }

private double factor = 1.0 / 16.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 1, 2, 1, }, { 2, 4, 2, }, { 1, 2, 1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Gaussian5x5BlurFilter

The Gaussian5x5BlurFilter implements a through a matrix of 25 elements in a 5×5 configuration. The sum total of all elements equal 159, therefore the Factor is defined as 1.0 / 159.0. Applying this filter results in a medium level of .

Gaussian5x5BlurFilter

public class Gaussian5x5BlurFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "Gaussian5x5BlurFilter"; } 
    }

private double factor = 1.0 / 159.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = 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 override double[,] FilterMatrix { get { return filterMatrix; } } }

MotionBlurFilter

By implementing the MotionBlurFilter resulting indicate the appearance of a high level of associated with motion/movement. This filter is a combination of left to right and right to left . The matrix consists of 81 elements in a 9×9 configuration.

MotionBlurFilter

public class MotionBlurFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "MotionBlurFilter"; } 
    }

private double factor = 1.0 / 18.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 1, 0, 0, 0, 0, 0, 0, 0, 1, }, { 0, 1, 0, 0, 0, 0, 0, 1, 0, }, { 0, 0, 1, 0, 0, 0, 1, 0, 0, }, { 0, 0, 0, 1, 0, 1, 0, 0, 0, }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, }, { 0, 0, 0, 1, 0, 1, 0, 0, 0, }, { 0, 0, 1, 0, 0, 0, 1, 0, 0, }, { 0, 1, 0, 0, 0, 0, 0, 1, 0, }, { 1, 0, 0, 0, 0, 0, 0, 0, 1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

MotionBlurLeftToRightFilter

The MotionBlurLeftToRightFilter creates the effect of as a result of left to right movement. The matrix consists of 81 elements in a 9×9 configuration.

MotionBlurLeftToRightFilter

public class MotionBlurLeftToRightFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "MotionBlurLeftToRightFilter"; } 
    }

private double factor = 1.0 / 9.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 1, 0, 0, 0, 0, 0, 0, 0, 0, }, { 0, 1, 0, 0, 0, 0, 0, 0, 0, }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, }, { 0, 0, 0, 1, 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, 1, 0, 0, 0, }, { 0, 0, 0, 0, 0, 0, 1, 0, 0, }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

MotionBlurRightToLeftFilter

The MotionBlurRightToLeftFilter creates the effect of as a result of right to left movement. The consists of 81 elements in a 9×9 configuration.

MotionBlurRightToLeftFilter

public class MotionBlurRightToLeftFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "MotionBlurRightToLeftFilter"; } 
    }

private double factor = 1.0 / 9.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 0, 0, 0, 0, 0, 0, 0, 0, 1, }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, }, { 0, 0, 0, 0, 0, 0, 1, 0, 0, }, { 0, 0, 0, 0, 0, 1, 0, 0, 0, }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, }, { 0, 0, 0, 1, 0, 0, 0, 0, 0, }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, }, { 0, 1, 0, 0, 0, 0, 0, 0, 0, }, { 1, 0, 0, 0, 0, 0, 0, 0, 0, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Soften Filter

The SoftenFilter can be used to smooth or soften an . The consists of 9 elements in a 3×3 configuration.

SoftenFilter

public class SoftenFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "SoftenFilter"; } 
    }

private double factor = 1.0 / 8.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 1, 1, 1, }, { 1, 1, 1, }, { 1, 1, 1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Sharpen Filters

Sharpening an does not add additional detail to an image but rather adds emphasis to existing image details. is sometimes referred to as image crispness.

SharpenFilter

This filter is intended as a general usage . In a variety of scenarios this filter should provide a reasonable level of depending on source quality.

SharpenFilter

public class SharpenFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "SharpenFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { -1, -1, -1, }, { -1, 9, -1, }, { -1, -1, -1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Sharpen3x3Filter

The Sharpen3x3Filter results in a medium level of , less intense when compared to the SharpenFilter discussed previously.

Sharpen3x3Filter

public class Sharpen3x3Filter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "Sharpen3x3Filter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 0, -1, 0, }, { -1, 5, -1, }, { 0, -1, 0, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Sharpen3x3FactorFilter

The Sharpen3x3FactorFilter provides a level of similar to the Sharpen3x3Filter explored previously. Both filters define a 9 element 3×3 . The filters differ in regards to Factor values. The Sharpen3x3Filter matrix values equate to a sum total of 1, the Sharpen3x3FactorFilter in contrast equate to a sum total of 3. The Sharpen3x3FactorFilter defines a Factor of 1 / 3, resulting in sum total being negated to 1.

Sharpen3x3FactorFilter

public class Sharpen3x3FactorFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "Sharpen3x3FactorFilter"; } 
    }

private double factor = 1.0 / 3.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 0, -2, 0, }, { -2, 11, -2, }, { 0, -2, 0, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Sharpen5x5Filter

The Sharpen5x5Filter matrix defines 25 elements in a 5×5 configuration. The level of resulting from implementing this filter to a greater extent is depended on the source . In some scenarios result images may appear slightly softened.

Sharpen5x5Filter

public class Sharpen5x5Filter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "Sharpen5x5Filter"; } 
    }

private double factor = 1.0 / 8.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = 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, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

IntenseSharpenFilter

The IntenseSharpenFilter produces result with overly emphasized edge lines.

IntenseSharpenFilter

public class IntenseSharpenFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "IntenseSharpenFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 1, 1, 1, }, { 1, -7, 1, }, { 1, 1, 1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Edge Detection Filters

is the first step towards feature detection and feature extraction in . Edges are generally perceived in in areas exhibiting sudden differences in brightness.

EdgeDetectionFilter

The EdgeDetectionFilter is intended to be used as a general purpose filter, considered appropriate in the majority of scenarios applied.

EdgeDetectionFilter

public class EdgeDetectionFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "EdgeDetectionFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { -1, -1, -1, }, { -1, 8, -1, }, { -1, -1, -1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

EdgeDetection45DegreeFilter

The EdgeDetection45DegreeFilter has the ability to detect edges at 45 degree angles more effectively than other filters.

EdgeDetection45DegreeFilter

public class EdgeDetection45DegreeFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "EdgeDetection45DegreeFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { -1, 0, 0, 0, 0, }, { 0, -2, 0, 0, 0, }, { 0, 0, 6, 0, 0, }, { 0, 0, 0, -2, 0, }, { 0, 0, 0, 0, -1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

HorizontalEdgeDetectionFilter

The HorizontalEdgeDetectionFilter has the ability to detect horizontal edges more effectively than other filters.

HorizontalEdgeDetectionFilter

public class HorizontalEdgeDetectionFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "HorizontalEdgeDetectionFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, }, { -1, -1, 2, 0, 0, }, { 0, 0, 0, 0, 0, }, { 0, 0, 0, 0, 0, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

VerticalEdgeDetectionFilter

The VerticalEdgeDetectionFilter has the ability to detect vertical edges more effectively than other filters.

VerticalEdgeDetectionFilter

public class VerticalEdgeDetectionFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "VerticalEdgeDetectionFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 0, 0, -1, 0, 0, }, { 0, 0, -1, 0, 0, }, { 0, 0, 4, 0, 0, }, { 0, 0, -1, 0, 0, }, { 0, 0, -1, 0, 0, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

EdgeDetectionTopLeftBottomRightFilter

This filter closely resembles an indicating object depth whilst still providing a reasonable level of detail.

EdgeDetectionTopLeftBottomRightFilter

public class EdgeDetectionTopLeftBottomRightFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "EdgeDetectionTopLeftBottomRightFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 0.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { -5, 0, 0, }, { 0, 0, 0, }, { 0, 0, 5, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Emboss Filters

filters produce result with an emphasis on depth, based on lines/edges expressed in an input/source . Result give the impression of being three dimensional to a varying extent, depended on details defined by input .

EmbossFilter

The EmbossFilter is intended as a general application filter. Take note of the Bias value of 128. Without a bias value, result would be very dark or mostly black.

EmbossFilter

public class EmbossFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "EmbossFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 128.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { 2, 0, 0, }, { 0, -1, 0, }, { 0, 0, -1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

Emboss45DegreeFilter

The Emboss45DegreeFilter has the ability to produce result with good emphasis on 45 degree edges/lines.

Emboss45DegreeFilter

public class Emboss45DegreeFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "Emboss45DegreeFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 128.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { -1, -1, 0, }, { -1, 0, 1, }, { 0, 1, 1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

EmbossTopLeftBottomRightFilter

The EmbossTopLeftBottomRightFilter provides a more subtle level of result .

EmbossTopLeftBottomRightFilter

public class EmbossTopLeftBottomRightFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "EmbossTopLeftBottomRightFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 128.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { -1, 0, 0, }, { 0, 0, 0, }, { 0, 0, 1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

IntenseEmbossFilter

When implementing the IntenseEmbossFilter result provide a good three dimensional/depth level. A drawback of this filter can sometimes be noticed in a reduction detail.

IntenseEmbossFilter

public class IntenseEmbossFilter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "IntenseEmbossFilter"; } 
    }

private double factor = 1.0; public override double Factor { get { return factor; } }
private double bias = 128.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { -1, -1, -1, -1, 0, }, { -1, -1, -1, 0, 1, }, { -1, -1, 0, 1, 1, }, { -1, 0, 1, 1, 1, }, { 0, 1, 1, 1, 1, }, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

High Pass

produce result where only high frequency components are retained.

HighPass3x3Filter

public class HighPass3x3Filter : ConvolutionFilterBase 
{
    public override string FilterName 
    {
        get { return "HighPass3x3Filter"; } 
    }

private double factor = 1.0 / 16.0; public override double Factor { get { return factor; } }
private double bias = 128.0; public override double Bias { get { return bias; } }
private double[,] filterMatrix = new double[,] { { -1, -2, -1, }, { -2, 12, -2, }, { -1, -2, -1,, };
public override double[,] FilterMatrix { get { return filterMatrix; } } }

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:

31 Responses to “C# How to: Image Convolution”



  1. 1 C# How to: Decoding/Converting Base64 strings to Bitmap images | Software by Default Trackback on May 2, 2013 at 9:35 PM
  2. 2 C# How to: Image Edge Detection | Software by Default Trackback on May 11, 2013 at 1:23 PM
  3. 3 C# How to: Difference Of Gaussians | Software by Default Trackback on May 18, 2013 at 12:50 AM
  4. 4 C# How to: Image Median Filter | Software by Default Trackback on May 18, 2013 at 4:16 AM
  5. 5 C# How to: Image Unsharp Mask | Software by Default Trackback on May 18, 2013 at 12:18 PM
  6. 6 C# How to: Image Colour Average | Software by Default Trackback on May 18, 2013 at 9:48 PM
  7. 7 C# How to: Image Erosion and Dilation | Software by Default Trackback on May 19, 2013 at 10:24 AM
  8. 8 C# How to: Morphological Edge Detection | Software by Default Trackback on May 25, 2013 at 8:22 AM
  9. 9 C# How to: Boolean Edge Detection | Software by Default Trackback on June 1, 2013 at 2:09 AM
  10. 10 C# How to: Gradient Based Edge Detection | Software by Default Trackback on June 1, 2013 at 4:46 PM
  11. 11 C# How to: Image Cartoon Effect | Software by Default Trackback on June 2, 2013 at 4:13 PM
  12. 12 C# How to: Sharpen Edge Detection | Software by Default Trackback on June 7, 2013 at 5:11 AM
  13. 13 C# How to: Calculating Gaussian Kernels | Software by Default Trackback on June 8, 2013 at 10:59 AM
  14. 14 C# How to: Image Blur | Software by Default Trackback on June 9, 2013 at 10:19 PM
  15. 15 C# How to: Image Transform Rotate | Software by Default Trackback on June 16, 2013 at 10:40 AM
  16. 16 C# How to: Image Transform Shear | Software by Default Trackback on June 16, 2013 at 5:45 PM
  17. 17 C# How to: Compass Edge Detection | Software by Default Trackback on June 22, 2013 at 9:34 PM
  18. 18 C# How to: Oil Painting and Cartoon Filter | Software by Default Trackback on June 30, 2013 at 10:47 AM
  19. 19 C# How to: Stained Glass Image Filter | Software by Default Trackback on June 30, 2013 at 10:50 AM
  20. 20 C# How to: Generate a Web Service from WSDL | Software by Default Trackback on June 30, 2013 at 4:08 PM
  21. 21 C# How to: Bitmap Colour Substitution implementing thresholds | Software by Default Trackback on July 6, 2013 at 4:33 PM
  22. 22 C# How to: Swapping Bitmap ARGB Colour Channels | Software by Default Trackback on July 6, 2013 at 5:02 PM
  23. 23 C# How to: Image filtering by directly manipulating Pixel ARGB values | Software by Default Trackback on July 8, 2013 at 2:57 AM
  24. 24 C# How to: Image ASCII Art | Software by Default Trackback on July 14, 2013 at 7:22 AM
  25. 25 C# How to: Weighted Difference of Gaussians | Software by Default Trackback on July 14, 2013 at 8:11 PM
  26. 26 C# How to: Image Boundary Extraction | Software by Default Trackback on July 21, 2013 at 10:23 AM
  27. 27 C# How to: Image Abstract Colours Filter | Software by Default Trackback on July 28, 2013 at 7:41 PM
  28. 28 C# How to: Fuzzy Blur Filter | Software by Default Trackback on August 9, 2013 at 6:39 AM
  29. 29 C# How to: Image Distortion Blur | Software by Default Trackback on August 9, 2013 at 10:13 PM
  30. 30 C# How to: Standard Deviation Edge Detection | Software by Default Trackback on August 8, 2015 at 8:10 AM
  31. 31 C# How to: Min/Max Edge Detection | Software by Default Trackback on August 9, 2015 at 11:29 AM

Leave a comment




Dewald Esterhuizen

Blog Stats

  • 869,790 hits

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

Join 228 other subscribers

Archives

RSS SoftwareByDefault on MSDN

  • An error has occurred; the feed is probably down. Try again later.