C# How to: Image Unsharp Mask

Article purpose

The purpose of this article is to explore and illustrate the concept of . This article implements in the form of a 3×3 , 5×5 , 3×3 Mean filter and a 5×5 Mean filter.

Sample Source code

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

Using the Sample Application

The sample source code associated with this article includes a based sample application implementing the concepts explored throughout this article.

When using the Image Unsharp Mask sample application users can select a source/input image from the local system by clicking the Load Image button. The dropdown at the bottom of the screen allows the user to select an unsharp masking variation. On the right hand side of the screen users can specify the level/intensity of resulting .

Clicking the Save Image button allows a user to save resulting to the local file system. The image below is a screenshot of the Image Unsharp Mask sample application in action:

Image Unsharp Mask Sample Application

What is Image Unsharp Masking?

A good definition of can be found on :

Unsharp masking (USM) is an image manipulation technique, often available in software.

The "unsharp" of the name derives from the fact that the technique uses a blurred, or "unsharp", positive image to create a "mask" of the original image. The unsharped mask is then combined with the negative image, creating an image that is less blurry than the original. The resulting image, although clearer, probably loses accuracy with respect to the image’s subject. In the context of , an unsharp mask is generally a or filter that amplifies high-frequency components.

In this article we implement by first creating a blurred copy of a source/input then subtracting the blurred from the original , which is known as the mask. Increased is achieved by adding a factor of the mask to the original .

Applying a Convolution Matrix filter

The sample source code provides the definition for the ConvolutionFilter targeting the class. method is invoked when implementing . The definition of the ConvolutionFilter as follows:

 private static Bitmap ConvolutionFilter(Bitmap sourceBitmap,  
                                     double[,] filterMatrix,  
                                          double factor = 1,  
                                               int bias = 0,  
                                     bool grayscale = 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) { float rgb = 0;
for (int k = 0; k < pixelBuffer.Length; k += 4) { rgb = pixelBuffer[k] * 0.11f; rgb += pixelBuffer[k + 1] * 0.59f; rgb += pixelBuffer[k + 2] * 0.3f;
pixelBuffer[k] = (byte )rgb; pixelBuffer[k + 1] = pixelBuffer[k]; pixelBuffer[k + 2] = pixelBuffer[k]; pixelBuffer[k + 3] = 255; } }
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;
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; }

Subtracting and Adding Images

An important step required when implementing comes in the form of creating a mask by subtracting a blurred copy from the original and then adding a factor of the mask to the original . In order to achieve increased performance the sample source code combines the process of creating the mask and adding the mask to the original .

The SubtractAddFactorImage iterates every pixel that forms part of an . In a single step the blurred pixel is subtracted from the original pixel, multiplied by a user specified factor and then added to the original pixel. The definition of the SubtractAddFactorImage as follows:

private static Bitmap SubtractAddFactorImage( 
                              this Bitmap subtractFrom, 
                                  Bitmap subtractValue, 
                                   float factor = 1.0f) 
{ 
    BitmapData sourceData =  
               subtractFrom.LockBits(new Rectangle (0, 0, 
               subtractFrom.Width, subtractFrom.Height), 
               ImageLockMode.ReadOnly, 
               PixelFormat.Format32bppArgb); 

byte[] sourceBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, sourceBuffer.Length);
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
BitmapData subtractData = subtractValue.LockBits(new Rectangle (0, 0, subtractValue.Width, subtractValue.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] subtractBuffer = new byte[subtractData.Stride * subtractData.Height];
Marshal.Copy(subtractData.Scan0, subtractBuffer, 0, subtractBuffer.Length);
subtractFrom.UnlockBits(sourceData); subtractValue.UnlockBits(subtractData);
double blue = 0; double green = 0; double red = 0;
for (int k = 0; k < resultBuffer.Length && k < subtractBuffer.Length; k += 4) { blue = sourceBuffer[k] + (sourceBuffer[k] - subtractBuffer[k]) * factor;
green = sourceBuffer[k + 1] + (sourceBuffer[k + 1] - subtractBuffer[k + 1]) * factor;
red = sourceBuffer[k + 2] + (sourceBuffer[k + 2] - subtractBuffer[k + 2]) * factor;
blue = (blue < 0 ? 0 : (blue > 255 ? 255 : blue)); green = (green < 0 ? 0 : (green > 255 ? 255 : green)); red = (red < 0 ? 0 : (red > 255 ? 255 : red));
resultBuffer[k] = (byte )blue; resultBuffer[k + 1] = (byte )green; resultBuffer[k + 2] = (byte )red; resultBuffer[k + 3] = 255; }
Bitmap resultBitmap = new Bitmap (subtractFrom.Width, subtractFrom.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; }

Matrix Definition

The image blurring filters implemented by the sample source code relies on static / values defined in the Matrix class. The variants of implemented are: 3×3 , 5×5 Gaussian, 3×3 Mean and 5×5 Mean. The definition of the Matrix class is detailed by the following code snippet:

public static class Matrix
{
    public static double[,] Gaussian3x3
    {
        get
        {
            return new double[,]
            { { 1, 2, 1, }, 
              { 2, 4, 2, }, 
              { 1, 2, 1, }, };
        }
    }

public static double[,] Gaussian5x5Type1 { 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[,] 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 }, }; } } }

Implementing Image Unsharpening

This article explores four variants of , relating to the four types of image blurring discussed in the previous section. The sample source code defines the following : UnsharpGaussian3x3, UnsharpGaussian5x5, UnsharpMean3x3 and UnsharpMean5x5. All four methods are defined as targeting the class. When looking at the sample images in the following section you will notice the correlation between increased and enhanced . The definition as follows:

public static Bitmap UnsharpGaussian3x3( 
                                 this Bitmap sourceBitmap,  
                                 float factor = 1.0f) 
{
    Bitmap blurBitmap = ExtBitmap.ConvolutionFilter( 
                                  sourceBitmap,  
                                  Matrix.Gaussian3x3,  
                                  1.0 / 16.0); 

Bitmap resultBitmap = sourceBitmap.SubtractAddFactorImage( blurBitmap, factor);
return resultBitmap; }
public static Bitmap UnsharpGaussian5x5( this Bitmap sourceBitmap, float factor = 1.0f) { Bitmap blurBitmap = ExtBitmap.ConvolutionFilter( sourceBitmap, Matrix.Gaussian5x5Type1, 1.0 / 159.0);
Bitmap resultBitmap = sourceBitmap.SubtractAddFactorImage( blurBitmap, factor);
return resultBitmap; } public static Bitmap UnsharpMean3x3( this Bitmap sourceBitmap, float factor = 1.0f) { Bitmap blurBitmap = ExtBitmap.ConvolutionFilter( sourceBitmap, Matrix.Mean3x3, 1.0 / 9.0);
Bitmap resultBitmap = sourceBitmap.SubtractAddFactorImage( blurBitmap, factor);
return resultBitmap; }
public static Bitmap UnsharpMean5x5( this Bitmap sourceBitmap, float factor = 1.0f) { Bitmap blurBitmap = ExtBitmap.ConvolutionFilter( sourceBitmap, Matrix.Mean5x5, 1.0 / 25.0);
Bitmap resultBitmap = sourceBitmap.SubtractAddFactorImage( blurBitmap, factor);
return resultBitmap; }

Sample Images

The used in rendering the sample images shown in this article is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license and can be from :

The Original Image

W-A-S-D

Unsharp Gaussian 3×3

Unsharp Gaussian 3x3

Unsharp Gaussian 5×5

Unsharp Gaussian 5x5

Unsharp Mean 3×3

Unsharp Mean 3x3

Unsharp Gaussian 5×5

Unsharp Mean 5x5

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:

32 Responses to “C# How to: Image Unsharp Mask”


  1. 1 Wolfgang Kurz November 15, 2020 at 1:58 PM

    Hello Dewald,
    I hope you accept, that i contact you via this way.
    I am currently developing an application to digitize cine films.
    (Normal 8, Super 8, 9.5 mm Pathé, 16 mm)
    see: https://wkurz.com – applications
    Currently I have two main problems.
    1. Unsharp masking.
    You have published an artikel about how to do that, so that can surely be resolved.
    BUT
    2. I also should be able to use negativ footage. And there I need a WhiteBalancing function ( as used ín digital cameras) to eliminate an orange mask most negative footages have.
    Do you know good example how to apply a white balancing filter to a bitmap using C# and .NET (4.7 or above)
    Are you probably thinking about developing such an example ?
    Many thanks for a response and a hint
    Wolfgang


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

Leave a comment




Dewald Esterhuizen

Blog Stats

  • 869,742 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.