Click here to Skip to main content
Click here to Skip to main content

Image Processing for Dummies with C# and GDI+ Part 3 - Edge Detection Filters

, 31 Mar 2002
Rate this:
Please Sign up or sign in to vote.
The third in a series of articles which will build an image processing library in C# and GDI+

Sample Image

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.

My kids    Sobel Edge Detection

Prewitt Edge Detection    Kirsh Edge Detection

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.

Horizontal Edge Detection Vertical Edge Detection

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)
{
    // This one works by working out the greatest difference between a 
    // pixel and it's eight neighbours. The threshold allows softer edges to 
    // be forced down to black, use 0 to negate it's effect.
    Bitmap b2 = (Bitmap) b.Clone();

    // GDI+ still lies to us - the return format is BGR, NOT RGB.
    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.Height-1;++y)
        {
            p += 3;
            p2 += 3;

            for(int x=3; x < nWidth-3; ++x )
            {
                nPixelMax = Math.Abs(p2[0] - (p2+stride-3)[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;
    
}

Homogenousl Edge Detection, 0 threshold Homogenousl Edge Detection, 127 threshold

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)
{
    // This one works by working out the greatest difference between a 
    // pixel and it's eight neighbours. The threshold allows softer edges 
    // to be forced down to black, use 0 to negate it's effect.
    Bitmap b2 = (Bitmap) b.Clone();

    // GDI+ still lies to us - the return format is BGR, NOT RGB.
    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.Height-1;++y)
        {
            p += 3;
            p2 += 3;

            for(int x=3; x < nWidth-3; ++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 = 0;

                p[0] = (byte) nPixelMax;

                ++ p;
                ++ p2;
            }

            p += 3 + nOffset;
            p2 += 3 + nOffset;
        }
    }

    b.UnlockBits(bmData);
    b2.UnlockBits(bmData2);

    return true;
    
}

Difference Edge Detection, 0 threshold Difference Edge Detection, 0 threshold

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 Smile | :)

public static bool EdgeEnhance(Bitmap b, byte nThreshold)
{
    // This one works by working out the greatest difference between a 
    // nPixel and it's eight neighbours. The threshold allows softer 
    // edges to be forced down to black, use 0 to negate it's effect.
    Bitmap b2 = (Bitmap) b.Clone();

    // GDI+ still lies to us - the return format is BGR, NOT RGB.
    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.Height-1; ++y)
        {
            p += 3;
            p2 += 3;

            for (int x = 3; x < nWidth-3; ++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;

My Kids Enhanced with a threshold of 0

My Kids Enhanced with a threshold of 127

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'.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Christian Graus
Software Developer (Senior)
Australia Australia
Programming computers ( self taught ) since about 1984 when I bought my first Apple ][. Was working on a GUI library to interface Win32 to Python, and writing graphics filters in my spare time, and then building n-tiered apps using asp, atl and asp.net in my job at Dytech. After 4 years there, I've started working from home, at first for Code Project and now for a vet telemedicine company. I owned part of a company that sells client education software in the vet market, but we sold that and I worked for the owners for five years before leaving to get away from the travel, and spend more time with my family. I now work for a company here in Hobart, doing all sorts of Microsoft based stuff in C++ and C#, with a lot of T-SQL in the mix.

Comments and Discussions

 
GeneralMy vote of 5 Pinmember Gun Gun Febrianza5-May-14 15:44 
QuestionMatrix PinmemberMember 1039191130-Nov-13 9:19 
AnswerRe: Matrix PinmvpChristian Graus30-Nov-13 10:29 
NewsEdge detection in OpenCL PinmemberSGreth15-Mar-12 9:53 
Questionhow to get different regions in one image after edge detection? Pinmembermike.kong26-May-10 12:03 
AnswerRe: how to get different regions in one image after edge detection? PinmvpChristian Graus24-Aug-10 23:41 
Generalquestion Pinmemberlukasz199913-Jan-10 9:38 
GeneralRe: question PinmvpChristian Graus24-Aug-10 23:39 
GeneralAbout Sobel and Prewitt algorithms PinmemberMember 86987015-May-09 4:47 
GeneralRe: About Sobel and Prewitt algorithms PinmvpChristian Graus24-Aug-10 23:40 
Questionlicense plate edge detection PinmemberSwati Khanna13-Jan-09 2:13 
GeneralPNG image with Transparent : Use C# code and .Net GDI+ Pinmember-- Abhi --9-Jan-08 19:18 
Questionbrilliant articles, can I ask your advice ? Pinmembergt11222-Dec-07 6:55 
GeneralPorted EdgeDetectDifference function to VB.NET PinmemberJRSofty12-Sep-07 21:20 
GeneralImage outline width PinmemberX3m29-Jun-07 5:43 
QuestionOverlay lines Pinmemberallenmpcx8-Jun-07 8:39 
QuestionEdge, Blobs, Objects, and Alpha Pinmemberstixoffire25-Mar-07 21:46 
Questionsobel??? Pinmemberdrago124-Jan-07 5:15 
Questionsubpixel precission Pinmemberfranzu18-Nov-06 9:31 
AnswerRe: subpixel precission PinstaffChristian Graus18-Nov-06 12:53 
QuestionHow create a dll with EdgeDetectDifference function Pinmemberfloydus2711-Sep-06 7:53 
AnswerRe: How create a dll with EdgeDetectDifference function PinstaffChristian Graus18-Nov-06 12:53 
Generaledge and width detection Pinmemberfdapro29-Jun-06 11:36 
Generaledge and width detection Pinmemberfdapro29-Jun-06 11:33 
GeneralRe: edge and width detection PinstaffChristian Graus29-Jun-06 11:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 1 Apr 2002
Article Copyright 2002 by Christian Graus
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid