Sobel Edge Detection and Laplace Edge Detection in C#

Simple edge detection using sobel and laplace filters.
Sep 15 2010 by James Craig

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:

 public static Bitmap SobelEdgeDetection(Bitmap Input)

{

try

{

using (Bitmap TempImage = ConvertBlackAndWhite(Input))

{

Filter TempFilter = new Filter(3, 3);

TempFilter.MyFilter\[0, 0\] = -1;

TempFilter.MyFilter\[0, 1\] = 0;

TempFilter.MyFilter\[0, 2\] = 1;

TempFilter.MyFilter\[1, 0\] = -2;

TempFilter.MyFilter\[1, 1\] = 0;

TempFilter.MyFilter\[1, 2\] = 2;

TempFilter.MyFilter\[2, 0\] = -1;

TempFilter.MyFilter\[2, 1\] = 0;

TempFilter.MyFilter\[2, 2\] = 1;

TempFilter.Absolute = true;

using (Bitmap TempImageX = TempFilter.ApplyFilter(TempImage))

{

TempFilter = new Filter(3, 3);

TempFilter.MyFilter\[0, 0\] = 1;

TempFilter.MyFilter\[0, 1\] = 2;

TempFilter.MyFilter\[0, 2\] = 1;

TempFilter.MyFilter\[1, 0\] = 0;

TempFilter.MyFilter\[1, 1\] = 0;

TempFilter.MyFilter\[1, 2\] = 0;

TempFilter.MyFilter\[2, 0\] = -1;

TempFilter.MyFilter\[2, 1\] = -2;

TempFilter.MyFilter\[2, 2\] = -1;

TempFilter.Absolute = true;

using (Bitmap TempImageY = TempFilter.ApplyFilter(TempImage))

{

using (Bitmap NewBitmap = new Bitmap(TempImage.Width, TempImage.Height))

{

BitmapData NewData = Image.LockImage(NewBitmap);

BitmapData OldData1 = Image.LockImage(TempImageX);

BitmapData OldData2 = Image.LockImage(TempImageY);

int NewPixelSize = Image.GetPixelSize(NewData);

int OldPixelSize1 = Image.GetPixelSize(OldData1);

int OldPixelSize2 = Image.GetPixelSize(OldData2);

for (int x = 0; x < NewBitmap.Width; ++x)

{

for (int y = 0; y < NewBitmap.Height; ++y)

{

Color Pixel1 = Image.GetPixel(OldData1, x, y, OldPixelSize1);

Color Pixel2 = Image.GetPixel(OldData2, x, y, OldPixelSize2);

Image.SetPixel(NewData, x, y,

Color.FromArgb(Math.MathHelper.Clamp(Pixel1.R + Pixel2.R, 255, 0),

Math.MathHelper.Clamp(Pixel1.G + Pixel2.G, 255, 0),

Math.MathHelper.Clamp(Pixel1.B + Pixel2.B, 255, 0)),

NewPixelSize);

}

}

Image.UnlockImage(NewBitmap, NewData);

Image.UnlockImage(TempImageX, OldData1);

Image.UnlockImage(TempImageY, OldData2);

return Image.Negative(NewBitmap);

}

}

}

}

}

catch { throw; }

}

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:

 public static Bitmap LaplaceEdgeDetection(Bitmap Image)

{

try

{

using (Bitmap TempImage = ConvertBlackAndWhite(Image))

{

Filter TempFilter = new Filter(5, 5);

TempFilter.MyFilter\[0, 0\] = -1;

TempFilter.MyFilter\[0, 1\] = -1;

TempFilter.MyFilter\[0, 2\] = -1;

TempFilter.MyFilter\[0, 3\] = -1;

TempFilter.MyFilter\[0, 4\] = -1;

TempFilter.MyFilter\[1, 0\] = -1;

TempFilter.MyFilter\[1, 1\] = -1;

TempFilter.MyFilter\[1, 2\] = -1;

TempFilter.MyFilter\[1, 3\] = -1;

TempFilter.MyFilter\[1, 4\] = -1;

TempFilter.MyFilter\[2, 0\] = -1;

TempFilter.MyFilter\[2, 1\] = -1;

TempFilter.MyFilter\[2, 2\] = 24;

TempFilter.MyFilter\[2, 3\] = -1;

TempFilter.MyFilter\[2, 4\] = -1;

TempFilter.MyFilter\[3, 0\] = -1;

TempFilter.MyFilter\[3, 1\] = -1;

TempFilter.MyFilter\[3, 2\] = -1;

TempFilter.MyFilter\[3, 3\] = -1;

TempFilter.MyFilter\[3, 4\] = -1;

TempFilter.MyFilter\[4, 0\] = -1;

TempFilter.MyFilter\[4, 1\] = -1;

TempFilter.MyFilter\[4, 2\] = -1;

TempFilter.MyFilter\[4, 3\] = -1;

TempFilter.MyFilter\[4, 4\] = -1;

using (Bitmap NewImage = TempFilter.ApplyFilter(TempImage))

{

return Negative(NewImage);

}

}

}

catch { throw; }

}

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.