Introduction
Welcome back. This is probably goign to be the last in this series for a
while, I want to focus on some other things to learn some more C#, and come
back to this when I have some more time.
Overview
This article will focus on one of the most common image processing tasks,
detecting edges. We will look at a number of ways to do this, and also
look at one use for such information, an edge enhance filter. We will
start with what we know from the last article, using convolution filters to
detect edges.
Convolution Filters  Sobel, Prewitt and Kirsh
We will use three different convolution masks to detect edges, named presumably
after their inventors. In each case, we apply a horizontal version of the
filter to one bitmap, a vertical version to another, and the formula pixel =
sqrt(pixel1 * pixel1 + pixel2 * pixel2) to merge them together.
Hopefully you're familiar enough with the previous articles to know what the
code would look like to do this. The convolution masks look like this:
Sobell 
1 
2 
1 
0 
0 
0 
1 
2 
1 
/1+0 

Prewitt 
1 
1 
1 
0 
0 
0 
1 
1 
1 
/1+0 

Kirsh 
5 
5 
5 
3 
3 
3 
3 
3 
3 
/1+0 

These filters perform the horizontal edge detect, rotating them 90 degrees gives
us the vertical, and then the merge takes place.
How do they work ?
Edge detection filters work essentially by looking for contrast in an
image. This can be done a number of different ways, the convolution
filters do it by applying a negative weight on one edge, and a positive on the
other. This has the net effect of trending towards zero if the values are
the same, and trending upwards as contrast exists. This is precisely how
our emboss filter worked, and using an offset of 127 would again make
these filters look similar to our previous embossing filter. The
following examples follow the different filter types in the same order as the
filters above. The images have a tooltip if you want to be sure which is
which. These three filters also allow specification of a threshold.
Any value below this threshold will be clamped to it. For the test I have
kept the threshold at 0.
Horizontal and Vertical Edge Detection
To perform an edge detection operation in just the horizontal or vertical
planes, we can again use a convolution method. However, rather than use
our framework for 3x3 filters, we are better off writing the code from scratch
so that our filter ( which will be a Prewitt filter ) will be either very wide,
or very high. I've chosen 7 as a good umber, our horizontal filter is 7x3
and our vertical filter is 3x7. The code is not dissimilar enough from
what we've already done to warrant showing it to you especially, but it's there
if you want to have a look. Following is the result first of our
horizontal filter, and then the vertical one.
There's more to life than convolution
Convolution filters can do some cool stuff, and if you did a search online,
you'd be forgiven for thinking that they are behind all image processing.
However, it's probably more true that the sort of filters you see in Photoshop
as especially written to directly do what a convolution filter can only
imitate. I'd again point to the Photoshop embossing filter with it's
range of options as evidence of this.
The problem with convolution for edge detection is not so much that the process
is unsatisfactory, as much as unnecessarily expensive. I'm going to cover
two more methods of edge detection, which both involve us iterating through the
image directly and doing a number of compares on neighbouring pixels, but which
treat the resultant values differently to a convolution filter.
Homogenity Edge Detection
If we are to perceive an edge in an image, it follows that there is a change in
colour between two objects, for an edge to be apparent. To put it another
way, if we were to take a pixel and store as it's value the greatest difference
between it's starting value and the values of it's eight neighbours, we would
come up with black where the pixels are the same, and trend towards white the
harder the colour difference was. We would detect the edges in the
image. Furthermore, if we allowed a threshold to be set, and set values
below this to 0, we could eliminate soft edges to whatever degree we
desires. The code to do this is followed by an example at threshold 0 and
one at threshold 127.
public static bool EdgeDetectHomogenity(Bitmap b, byte nThreshold)
{
Bitmap b2 = (Bitmap) b.Clone();
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData bmData2 = b2.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
System.IntPtr Scan02 = bmData2.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
byte * p2 = (byte *)(void *)Scan02;
int nOffset = stride  b.Width*3;
int nWidth = b.Width * 3;
int nPixel = 0, nPixelMax = 0;
p += stride;
p2 += stride;
for(int y=1;y<b.Height1;++y)
{
p += 3;
p2 += 3;
for(int x=3; x < nWidth3; ++x )
{
nPixelMax = Math.Abs(p2[0]  (p2+stride3)[0]);
nPixel = Math.Abs(p2[0]  (p2 + stride)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs(p2[0]  (p2 + stride + 3)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs(p2[0]  (p2  stride)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs(p2[0]  (p2 + stride)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs(p2[0]  (p2  stride  3)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs(p2[0]  (p2  stride)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs(p2[0]  (p2  stride + 3)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
if (nPixelMax < nThreshold) nPixelMax = 0;
p[0] = (byte) nPixelMax;
++ p;
++ p2;
}
p += 3 + nOffset;
p2 += 3 + nOffset;
}
}
b.UnlockBits(bmData);
b2.UnlockBits(bmData2);
return true;
}
Difference Edge Detection
The difference edge detection works in a similar way, but it detects the
difference between pairs of pixel around the pixel we are setting. It
works out the highest value from the difference of the four pairs of pixels
that can be used to form a line through the middle pixel. The threshold
works the same as the homogenity filter. Again, here is the code,
followed by two examples, one with no threshold, one with a threshold of 127.
public static bool EdgeDetectDifference(Bitmap b, byte nThreshold)
{
Bitmap b2 = (Bitmap) b.Clone();
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData bmData2 = b2.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
System.IntPtr Scan02 = bmData2.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
byte * p2 = (byte *)(void *)Scan02;
int nOffset = stride  b.Width*3;
int nWidth = b.Width * 3;
int nPixel = 0, nPixelMax = 0;
p += stride;
p2 += stride;
for(int y=1;y<b.Height1;++y)
{
p += 3;
p2 += 3;
for(int x=3; x < nWidth3; ++x )
{
nPixelMax = Math.Abs((p2  stride + 3)[0]  (p2+stride3)[0]);
nPixel = Math.Abs((p2 + stride + 3)[0]  (p2  stride  3)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs((p2  stride)[0]  (p2 + stride)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs((p2+3)[0]  (p2  3)[0]);
if (nPixel>nPixelMax) nPixelMax = nPixel;
if (nPixelMax < nThreshold) nPixelMax = 0;
p[0] = (byte) nPixelMax;
++ p;
++ p2;
}
p += 3 + nOffset;
p2 += 3 + nOffset;
}
}
b.UnlockBits(bmData);
b2.UnlockBits(bmData2);
return true;
}
Edge Enhancement
One thing we can use edge detection for is to enhance edges in an image.
The concept is simple  we apply an edge filter, but we only store the value we
derive if it is greater than the value already present. Therefore if we
find an edge, we will brighten it. The end result is a filter which
fattens the outline of objects within it. We again apply a threshold, so
that we can control how harsh an edge must be before we enhance it.
Again, I am going to give you the code, and an example of edge enhancement with
values of 0 and 127, but because the result is a bit harder to see, I'll also
give you the original image next to each for comparison. Don't worry,
your browser cached the starting image, so it won't slow the page down
public static bool EdgeEnhance(Bitmap b, byte nThreshold)
{
Bitmap b2 = (Bitmap) b.Clone();
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData bmData2 = b2.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
System.IntPtr Scan02 = bmData2.Scan0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
byte * p2 = (byte *)(void *)Scan02;
int nOffset = stride  b.Width*3;
int nWidth = b.Width * 3;
int nPixel = 0, nPixelMax = 0;
p += stride;
p2 += stride;
for (int y = 1; y < b.Height1; ++y)
{
p += 3;
p2 += 3;
for (int x = 3; x < nWidth3; ++x)
{
nPixelMax = Math.Abs((p2  stride + 3)[0]  (p2 + stride  3)[0]);
nPixel = Math.Abs((p2 + stride + 3)[0]  (p2  stride  3)[0]);
if (nPixel > nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs((p2  stride)[0]  (p2 + stride)[0]);
if (nPixel > nPixelMax) nPixelMax = nPixel;
nPixel = Math.Abs((p2 + 3)[0]  (p2  3)[0]);
if (nPixel > nPixelMax) nPixelMax = nPixel;
if (nPixelMax > nThreshold && nPixelMax > p[0])
p[0] = (byte) Math.Max(p[0], nPixelMax);
++ p;
++ p2;
}
p += nOffset + 3;
p2 += nOffset + 3;
}
}
b.UnlockBits(bmData);
b2.UnlockBits(bmData2);
return true;
I have to say the effect here is somewhat muted  I can't readily see it in the
images but it is very apparent when I have the program open and can swap
between them.
I hope you've fond this article useful, I have a lot more to say on the subject
of image processing, but it's not going to be for a little while, as I have
other articles I want to get done and also projects I need to undertake to
increase my skillset in areas pertaining to my work. But, to quote my
favourite actor, 'I'll be back'.