Other Posts in Image Editing

  1. Perlin Noise
  2. Fault Formation
  3. Cellular Textures
  4. Resizing an Image in C#
  5. Box Blur and Gaussian Blur... Sort of...
  6. Thermal Erosion
  7. Using Mid Point Displacement to Create Cracks
  8. Fluvial Erosion
  9. Creating Marble Like Textures Procedurally
  10. Procedural Textures and Dilation
  11. Converting Image to Black and White in C#
  12. Getting an HTML Based Color Palette from an Image in C#
  13. Adding Noise/Jitter to an Image in C#
  14. Creating Pixelated Images in C#
  15. Edge detection in C#
  16. Using Sin to Get What You Want... In C#...
  17. Noise Reduction of an Image in C# using Median Filters
  18. Image Dilation in C#
  19. Sepia Tone in C#
  20. Kuwahara Filter in C#
  21. Matrix Convolution Filters in C#
  22. Symmetric Nearest Neighbor in C#
  23. Bump Map Creation Using C#
  24. Normal Map Creation Using C#
  25. Creating Negative Images using C#
  26. Red, Blue, and Green Filters in C#
  27. Converting an Image to ASCII Art in C#
  28. Adjusting Brightness of an Image in C#
  29. Adding Noise to an Image in C#
  30. Adjusting the Gamma of an Image Using C#
  31. Adjusting Contrast of an Image in C#
  32. Drawing a Box With Rounded Corners in C#
  33. Anding Two Images Together Using C#
  34. Motion Detection in C#
  35. Creating Thermometer Chart in C#
  36. Colorizing a Black and White Image in C#
  37. Extracting an Icon From a File
  38. Setting the Pixel Format and Image Format of an Image in .Net
  39. Using Unsafe Code for Faster Image Manipulation
  40. Sobel Edge Detection and Laplace Edge Detection in C#

Sobel Edge Detection and Laplace Edge Detection in C#

9/15/2010

A while back I posted a simple way to do edge detection. However, it's not the only way and thanks to a couple of additions to my code base, it's not even the easiest way. It's actually possible to do edge detection using convolution filters. No if statements, etc. and since I have code to run a convolution filter laying about it's rather simple to do.

Anyway, there's a couple ways to do edge detection, the first and probably most complicated is Sobel edge detection. If you ever read the code for creating a bump map, you may have remembered at the bottom that I mention a Sobel embossing filter:

1  2  1

0  0  0

-1 -2 -1

This filter looks for differences in the Y direction of an image. With Sobel edge detection, we're going to use this along with the X direction:

-1  0  1

-2  0  2

-1  0  1

We take the resulting images, get each individual pixel and add it to each other. For example Image1(0,0)+Image2(0,0)=Resulting pixel for (0,0). Once we do this, we're done... Well except that we need to ensure that the image that we're dealing with is black and white coming in and we may want to take the negative of the image as it will most likely be black with white lines. But other than that we're done. So let's take a look at the code:

   1: public static Bitmap SobelEdgeDetection(Bitmap Input)
   2:  
   3: {
   4:  
   5:     try
   6:  
   7:     {
   8:  
   9:         using (Bitmap TempImage = ConvertBlackAndWhite(Input))
  10:  
  11:         {
  12:  
  13:             Filter TempFilter = new Filter(3, 3);
  14:  
  15:             TempFilter.MyFilter[0, 0] = -1;
  16:  
  17:             TempFilter.MyFilter[0, 1] = 0;
  18:  
  19:             TempFilter.MyFilter[0, 2] = 1;
  20:  
  21:             TempFilter.MyFilter[1, 0] = -2;
  22:  
  23:             TempFilter.MyFilter[1, 1] = 0;
  24:  
  25:             TempFilter.MyFilter[1, 2] = 2;
  26:  
  27:             TempFilter.MyFilter[2, 0] = -1;
  28:  
  29:             TempFilter.MyFilter[2, 1] = 0;
  30:  
  31:             TempFilter.MyFilter[2, 2] = 1;
  32:  
  33:             TempFilter.Absolute = true;
  34:  
  35:             using (Bitmap TempImageX = TempFilter.ApplyFilter(TempImage))
  36:  
  37:             {
  38:  
  39:                 TempFilter = new Filter(3, 3);
  40:  
  41:                 TempFilter.MyFilter[0, 0] = 1;
  42:  
  43:                 TempFilter.MyFilter[0, 1] = 2;
  44:  
  45:                 TempFilter.MyFilter[0, 2] = 1;
  46:  
  47:                 TempFilter.MyFilter[1, 0] = 0;
  48:  
  49:                 TempFilter.MyFilter[1, 1] = 0;
  50:  
  51:                 TempFilter.MyFilter[1, 2] = 0;
  52:  
  53:                 TempFilter.MyFilter[2, 0] = -1;
  54:  
  55:                 TempFilter.MyFilter[2, 1] = -2;
  56:  
  57:                 TempFilter.MyFilter[2, 2] = -1;
  58:  
  59:                 TempFilter.Absolute = true;
  60:  
  61:                 using (Bitmap TempImageY = TempFilter.ApplyFilter(TempImage))
  62:  
  63:                 {
  64:  
  65:                     using (Bitmap NewBitmap = new Bitmap(TempImage.Width, TempImage.Height))
  66:  
  67:                     {
  68:  
  69:                         BitmapData NewData = Image.LockImage(NewBitmap);
  70:  
  71:                         BitmapData OldData1 = Image.LockImage(TempImageX);
  72:  
  73:                         BitmapData OldData2 = Image.LockImage(TempImageY);
  74:  
  75:                         int NewPixelSize = Image.GetPixelSize(NewData);
  76:  
  77:                         int OldPixelSize1 = Image.GetPixelSize(OldData1);
  78:  
  79:                         int OldPixelSize2 = Image.GetPixelSize(OldData2);
  80:  
  81:                         for (int x = 0; x < NewBitmap.Width; ++x)
  82:  
  83:                         {
  84:  
  85:                             for (int y = 0; y < NewBitmap.Height; ++y)
  86:  
  87:                             {
  88:  
  89:                                 Color Pixel1 = Image.GetPixel(OldData1, x, y, OldPixelSize1);
  90:  
  91:                                 Color Pixel2 = Image.GetPixel(OldData2, x, y, OldPixelSize2);
  92:  
  93:                                 Image.SetPixel(NewData, x, y,
  94:  
  95:                                     Color.FromArgb(Math.MathHelper.Clamp(Pixel1.R + Pixel2.R, 255, 0),
  96:  
  97:                                         Math.MathHelper.Clamp(Pixel1.G + Pixel2.G, 255, 0),
  98:  
  99:                                         Math.MathHelper.Clamp(Pixel1.B + Pixel2.B, 255, 0)),
 100:  
 101:                                     NewPixelSize);
 102:  
 103:                             }
 104:  
 105:                         }
 106:  
 107:                         Image.UnlockImage(NewBitmap, NewData);
 108:  
 109:                         Image.UnlockImage(TempImageX, OldData1);
 110:  
 111:                         Image.UnlockImage(TempImageY, OldData2);
 112:  
 113:                         return Image.Negative(NewBitmap);
 114:  
 115:                     }
 116:  
 117:                 }
 118:  
 119:             }
 120:  
 121:         }
 122:  
 123:     }
 124:  
 125:     catch { throw; }
 126:  
 127: }

The code above uses some code that you'll need from my utility library, but the two files are here and here. In fact that actually contains the final bits of code. Anyway the code above, as I said, takes the image, converts it to black and white, runs the X direction filter and Y direction filter (note that it sets the Absolute property on these filters as this will help us detect edges in all four directions while only running two filters), adds the pixels together, and then returns the negative of the resulting image.

That's a lot of steps and if I didn't have the code already there, it would be a pain to write. However there is a simpler approach that you can use for edge detection. It tends to result in slightly less optimal results, but it only uses one filter (so you skip the addition phase also). This approach is referred to as Laplace. So let's look at the code to accomplish this approach:

   1: public static Bitmap LaplaceEdgeDetection(Bitmap Image)
   2:  
   3: {
   4:  
   5:     try
   6:  
   7:     {
   8:  
   9:         using (Bitmap TempImage = ConvertBlackAndWhite(Image))
  10:  
  11:         {
  12:  
  13:             Filter TempFilter = new Filter(5, 5);
  14:  
  15:             TempFilter.MyFilter[0, 0] = -1;
  16:  
  17:             TempFilter.MyFilter[0, 1] = -1;
  18:  
  19:             TempFilter.MyFilter[0, 2] = -1;
  20:  
  21:             TempFilter.MyFilter[0, 3] = -1;
  22:  
  23:             TempFilter.MyFilter[0, 4] = -1;
  24:  
  25:             TempFilter.MyFilter[1, 0] = -1;
  26:  
  27:             TempFilter.MyFilter[1, 1] = -1;
  28:  
  29:             TempFilter.MyFilter[1, 2] = -1;
  30:  
  31:             TempFilter.MyFilter[1, 3] = -1;
  32:  
  33:             TempFilter.MyFilter[1, 4] = -1;
  34:  
  35:             TempFilter.MyFilter[2, 0] = -1;
  36:  
  37:             TempFilter.MyFilter[2, 1] = -1;
  38:  
  39:             TempFilter.MyFilter[2, 2] = 24;
  40:  
  41:             TempFilter.MyFilter[2, 3] = -1;
  42:  
  43:             TempFilter.MyFilter[2, 4] = -1;
  44:  
  45:             TempFilter.MyFilter[3, 0] = -1;
  46:  
  47:             TempFilter.MyFilter[3, 1] = -1;
  48:  
  49:             TempFilter.MyFilter[3, 2] = -1;
  50:  
  51:             TempFilter.MyFilter[3, 3] = -1;
  52:  
  53:             TempFilter.MyFilter[3, 4] = -1;
  54:  
  55:             TempFilter.MyFilter[4, 0] = -1;
  56:  
  57:             TempFilter.MyFilter[4, 1] = -1;
  58:  
  59:             TempFilter.MyFilter[4, 2] = -1;
  60:  
  61:             TempFilter.MyFilter[4, 3] = -1;
  62:  
  63:             TempFilter.MyFilter[4, 4] = -1;
  64:  
  65:             using (Bitmap NewImage = TempFilter.ApplyFilter(TempImage))
  66:  
  67:             {
  68:  
  69:                 return Negative(NewImage);
  70:  
  71:             }
  72:  
  73:         }
  74:  
  75:     }
  76:  
  77:     catch { throw; }
  78:  
  79: }

As you can see, we still have to convert the image to black and white, set up our filter and run it, and return the negative, but that's it. The filter itself is different from the Sobel one. It's 5x5 and looks like this:

-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

Since it only uses the one filter, it does tend to pick up noise a bit more (hence I said it tends to be suboptimal). But it's fast, which is always nice. But that's all there is to it. Download the code from the links I provided above (should direct you to the Codeplex repository), try it out, and happy coding.



Comments

James Craig
September 08, 2011 6:57 PM

The links in the post should be updated to:
http://cul.codeplex.com/SourceControl/changeset/view/046f054f7164#Media%2fImage%2fImage.cs
And:
http://cul.codeplex.com/SourceControl/changeset/view/046f054f7164#Media%2fImage%2fFilter.cs
The reason they don't work above is my library was switched over to mercurial.