Sign in to follow this  
Followers 0
Joshua Lamusga

Basic edge detector

17 posts in this topic

Overview

I'm posting an edge detector which uses no advanced algorithms. It just goes from the top left to the bottom right and checks each pixel to see if it contrasts with another pixel by a certain degree in the chosen channels. The pixel is at an angle with a magnitude called the sampling range. This is a fairly basic edge detector that is easy to understand and hopefully inspires others to write better ones (I encourage derivations).

 

 

2OqRGRb.png

mBOd0Ol.png

whnnun3.png

 

Purpose and Applications

This is used to make outlines that reduce the data needed to analyze an image, deducing areas of interest, and enabling the use of many interesting edge-based effects.

 

Options

Sampling range: the magnitude for the vector (the other component is the angle) that determines how thick edges are.

 

Detect opposite edge: This detects the opposite edge in addition to the current one.

 

Edge angle: This is the angle from which edges are detected.

 

Intensity threshold: All thresholds are values that describe how 'different' pixels have to be to be detected as edges. This one deals with intensity.

 

Red, green, blue, and alpha thresholds: These deal with the rgba channels. See above.

 

Background color: This selects how the background appears. It may be black, transparent (replacing the image), or transparent (on top of the image). The foreground is white unless the background is replacing the image, in which case the foreground is black for visibility. You can always invert the colors later.

 

Remarks

Located in the Stylize folder. Using blurs before edge detection greatly improves accuracy. This one has a horizontal line problem, sometimes doesn't render anything with the sampling range at 1 or -1, and leaves a small bar of unchecked pixels on the sides (by nature of its implementation). I would've fixed these already if I easily could and any help is appreciated.

 

Source 

#region UICode
double Amount1 = 1; // [-10,10] Sampling range
bool Amount2 = false; // [0,1] Detect opposite edge
double Amount3 = 0; // [-180,180] Edge angle
double Amount4 = 0.05; // [0,1] Intensity threshold
double Amount5 = 255; // [0,255] Red threshold
double Amount6 = 255; // [0,255] Green threshold
double Amount7 = 255; // [0,255] Blue threshold
double Amount8 = 255; // [0,255] Alpha threshold
byte Amount9 = 0; // Background Color|Black|Transparent, no image|Transparent, with image
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{  
    //Creates variables to store the colors.
    ColorBgra CurrentPixel, Col1, Col2;

    //Calculates the x and y components of the vector created by the angle chooser.
    double rads = Amount3 * Math.PI /180;
    double magX = Math.Cos(rads) * Amount1;
    double magY = Math.Sin(rads) * Amount1;
    
    //The for loops iterate through all pixels.
    for (float y = rect.Top; y < rect.Bottom; y++)
    {
        //Cancels by user request.
        if (IsCancelRequested)
        {
            return;
        }
        for (float x = rect.Left; x < rect.Right; x++)
        {
            //Sets the value of the current pixel to be transparent or black.
            //The pixel color will later become white if an edge is detected.
            if (Amount9 == 0)
            {
                CurrentPixel = ColorBgra.Black;
            }
            else if (Amount9 == 1)
            {
                CurrentPixel = ColorBgra.FromBgra(0, 0, 0, 0);
            }
            else
            {
                CurrentPixel = src[(int)x,(int)y];
            }
            
            //Gets the intensities of the pixels.
            Col1 = src.GetBilinearSampleClamped(x, y);
            Col2 = src.GetBilinearSampleClamped(x + (float)magX, y + (float)magY);
            
            if (Amount4 < 1)
            {                
                //The intensity threshold has been reached.
                if ((Col1.GetIntensity()- Col2.GetIntensity()) > Amount4 ||
	(Col2.GetIntensity()- Col1.GetIntensity()) < -Amount4)
                {
                    CurrentPixel = ColorBgra.White;
                }
            }
            //If red is being calculated.
            if (Amount5 < 255)
            {
                //The redness threshold has been reached.
                if ((Col1.R - Col2.R > Amount5) ||
                    (Col2.R - Col1.R < -Amount5))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If green is being calculated.
            if (Amount6 < 255)
            {
                //The green-ness threshold has been reached.
                if ((Col1.G - Col2.G > Amount6) ||
                    (Col2.G - Col1.G < -Amount6))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If blue is being calculated.
            if (Amount7 < 255)
            {
                //The blueness threshold has been reached.
                if ((Col1.B - Col2.B > Amount7) ||
                    (Col2.B - Col1.B < -Amount7))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If alpha is being calculated.
            if (Amount8 < 255)
            {
                //The alpha threshold has been reached.
                if ((Col1.A - Col2.A > Amount8) ||
                    (Col2.A - Col1.A < -Amount8))
                {
                    CurrentPixel = Color.White;
                }
            }
            
            //Changes the foreground color to black for visibility if the background is transparent.
            if (Amount9 == 1 && CurrentPixel.Equals(Color.White))
            {
                CurrentPixel = Color.Black;
            }

            //Applies everything again (in the next iteration), but from the exact opposite angle.
            if (Amount2)
            {
                magX = Math.Cos(rads + Math.PI) * Amount1;
                magY = Math.Sin(rads + Math.PI) * Amount1;
            }
            
            //Applies all changes to the destination surface.
            dst[(int)x, (int)y] = CurrentPixel;
        }
    }
}

Basic Edge Detector.zip

Edited by AnthonyScoffler
0

Share this post


Link to post
Share on other sites

Fancy! Although I'd recommend not having the default settings maxed out to '255'....makes images completely black.

 

The Edge Detection built into paint.net is much more basic than this.... you could jump on the 'plus' bandwagon and call this 'Edge Detect+'

Edited by toe_head2001
0

Share this post


Link to post
Share on other sites

 

 

This one has a horizontal line problem, sometimes doesn't render anything with the sampling range at 1 or -1, and leaves a small bar of unchecked pixels on the sides (by nature of its implementation). I would've fixed these already if I easily could and any help is appreciated.

your rounding back to zero

Set up a temp variable before the loops

ColorBgra  tmp2;

Change this

                    double pix2Int = src[(int)(x + magX), (int)(y + magY)].GetIntensity();
                    

to this

                    //Gets the intensities of the pixels.
                    tmp2 =  src.GetBilinearSample((float)x + (float)magX, (float)y + (float)magY);
                    double pix2Int = tmp2.GetIntensity();
0

Share this post


Link to post
Share on other sites

It's been updated for your changes, TechnoRobbo. The three errors described still persist, though. I added pictures a link to pictures because this forum hates pictures and is a picture Nazi, even though the pictures are hosted elsewhere. But I digress :)

0

Share this post


Link to post
Share on other sites

I use PNGs all the time. It might be because they were uploaded to Google Drive; I don't think Drive supports hotlinking. Try a different image host.

0

Share this post


Link to post
Share on other sites

I only addressed 1 and -1 error but the rounding issues are all over. Try type casting as in my example. Can examine more later on tonight.

0

Share this post


Link to post
Share on other sites

How much different is this than the built-in Effects > Artistic > Ink Sketch ?

0

Share this post


Link to post
Share on other sites

 

How much different is this than the built-in Effects > Artistic > Ink Sketch?

 

Very. This is guaranteed to be open-source. Ink sketch is a combined effect and is not meant to be an edge detector anyway. This can operate on individual channels instead of the image in its entirety. It can use an angle to detect edges from. I plan on fixing the lines, but I don't think Ink Sketch does this:

N84sDCu.png

0

Share this post


Link to post
Share on other sites

I couldn't remember if it was open-source or not, but now that I think about it, you talked about it in the CodeLab tutorials. It's okay though, since both are different effects. If Paint.NET really needs a dedicated edge detector, I can try to find an (actual) algorithm online and make one. My 'algorithm' is child's play compared to real ones... :)

0

Share this post


Link to post
Share on other sites

Here you go - it's your code, I just took out all that confusing rounding, works fine now.

#region UICode
double Amount1 = 2; // [-10,10] Sampling range
bool Amount2 = false; // [0,1] Detect opposite edge
double Amount3 = 45; // [-180,180] Edge angle
double Amount4 = 1; // [0,1] Intensity threshold
double Amount5 = 255; // [0,255] Red threshold
double Amount6 = 255; // [0,255] Green threshold
double Amount7 = 255; // [0,255] Blue threshold
double Amount8 = 255; // [0,255] Alpha threshold
byte Amount9 = 0; // Background Color|Black|Transparent, no image|Transparent, with image
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    //debug section
    //Amount4 = .1;
    //end debug
    
    //Creates a variable to store the current pixel.
    ColorBgra CurrentPixel,Col1,Col2;

    //Calculates the x and y components of the vector created by the angle chooser.
    //The Pi/180 part converts the degrees to radians so the functions work.
    double rads = Amount3 * Math.PI /180;
    double magX = Math.Cos(rads) * Amount1;
    double magY = Math.Sin(rads) * Amount1;
    
    //The for loops iterate through all pixels.
    for (float y = rect.Top; y < rect.Bottom; y++)
    {
        //Cancels by user request.
        if (IsCancelRequested)
        {
            return;
        }
        for (float x = rect.Left; x < rect.Right; x++)
        {
            //Sets the value of the current pixel to be transparent or black.
            //The pixel color will later become white if an edge is detected.
            if (Amount9 == 0)
            {
                CurrentPixel = ColorBgra.Black;
            }
            else if (Amount9 == 1)
            {
                CurrentPixel = ColorBgra.FromBgra(0, 0, 0, 0);
            }
            else
            {
                CurrentPixel = src[(int)x,(int)y];
            }
            
            
            //Checks if the pixels to be looked at in the sampling range actually exist.
            //If intensity is being calculated.

                //Gets the intensities of the pixels.
                Col1 = src.GetBilinearSampleClamped(x, y);
                Col2 = src.GetBilinearSampleClamped(x + (float)magX, y + (float)magY);

            if (Amount4 < 1)
            {

                
                //The intensity threshold has been reached.
                if ((Col1.GetIntensity()- Col2.GetIntensity()) > Amount4 ||
					(Col2.GetIntensity()- Col1.GetIntensity()) < -Amount4)
                {
                    CurrentPixel = ColorBgra.White;
                }
            }
            //If red is being calculated.
            if (Amount5 < 255)
            {
                //The redness threshold has been reached.
                if ((Col1.R - Col2.R > Amount5) ||
                    (Col2.R - Col1.R < -Amount5))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If green is being calculated.
            if (Amount6 < 255)
            {
                //The green-ness threshold has been reached.
                if ((Col1.G - Col2.G > Amount6) ||
                    (Col2.G - Col1.G < -Amount6))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If blue is being calculated.
            if (Amount7 < 255)
            {
                //The blueness threshold has been reached.
                if ((Col1.B - Col2.B > Amount7) ||
                    (Col2.B - Col1.B < -Amount7))
                {
                    CurrentPixel = Color.White;
                }
            }
            //If alpha is being calculated.
            if (Amount8 < 255)
            {
                //The alpha threshold has been reached.
                if ((Col1.A - Col2.A > Amount8) ||
                    (Col2.A - Col1.A < -Amount8))
                {
                    CurrentPixel = Color.White;
                }
            }
            
            //Changes the foreground color to black for visibility if the background is transparent.
            if (Amount9 == 1 && CurrentPixel.Equals(Color.White))
            {
                CurrentPixel = Color.Black;
            }

            //
            //Applies everything again (in the next iteration), but from the exact opposite angle.
            if (Amount2)
            {
                magX = Math.Cos(rads + Math.PI) * Amount1;
                magY = Math.Sin(rads + Math.PI) * Amount1;
            }
            
            //Applies all changes to the destination surface.
            dst[(int)x, (int)y] = CurrentPixel;
        }
    }
}
1

Share this post


Link to post
Share on other sites

Is this good practice or unnecessary?
 

 //Cancels by user request.
        if (IsCancelRequested)
        {
            return;
        }
0

Share this post


Link to post
Share on other sites

I'm assuming it's good practice because it allows the effect to exit the render function so that the effect stops. I think it operates for all threads in a multi-threaded effect, but I'm not sure because I don't understand multiple threads too well. It's included in default CodeLab scripts now, so I left it. Boltbait seems to know what he's doing.

0

Share this post


Link to post
Share on other sites

 

Here you go - it's your code, I just took out all that confusing rounding, works fine now.

Thanks a ton for fixing my disaster-piece. I'll give you +1 when my rep cap resets (why does it even exist?)

Also, I updated my code to reflect the changes. You're written in as an author.

Edited by AnthonyScoffler
0

Share this post


Link to post
Share on other sites

 

 

Ego  - Is this good practice or unnecessary?

Necessity for cancelling long complex loops. Integral part of CodLab 2.3 generated code.

 

 

AS - Thanks a ton for fixing my disaster-piece. I'll give you +1 when my rep cap resets (why does it even exist?)

Also, I updated my code to reflect the changes. You're written in as an author.

No need for that, I just proof read it, Wordpad copy pasting.

And, I assume the reps cap exists so as not to de-value it's importance

0

Share this post


Link to post
Share on other sites

Boltbait seems to know what he's doing.

Boy, have I got you fooled! :D

Anyway, it allows an effect that is running to end faster if the user changes a UI control or cancels an effect. It is not necessary, but sometimes it can make your UI more responsive.

I put it in the Y loop so it only checks once per row. If your effect is especially slow, you might want to move it to the X loop. I did that ONCE in all my plugins.

0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0