views:

109

answers:

2

Given the image below, what algorithm might I use to detect whether regions one and two (identified by color) have a border?

http://img823.imageshack.us/img823/4477/borders.png

If there's a C# example out there, that would be awesome, but I'm really just looking for any example code.

Edit: Using Jaro's advice, I came up with the following...

public class Shape
{
    private const int MAX_BORDER_DISTANCE = 15;

    public List<Point> Pixels { get; set; }

    public Shape()
    {
        Pixels = new List<Point>();
    }

    public bool SharesBorder(Shape other)
    {
        var shape1 = this;
        var shape2 = other;

        foreach (var pixel1 in shape1.Pixels)
        {
            foreach (var pixel2 in shape2.Pixels)
            {
                var xDistance = Math.Abs(pixel1.X - pixel2.X);
                var yDistance = Math.Abs(pixel1.Y - pixel2.Y);

                if (xDistance > 1 && yDistance > 1)
                {
                    if (xDistance * yDistance < MAX_BORDER_DISTANCE)
                        return true;
                }
                else
                {
                    if (xDistance < Math.Sqrt(MAX_BORDER_DISTANCE) &&
                        yDistance < Math.Sqrt(MAX_BORDER_DISTANCE))
                        return true;
                }
            }
        }

        return false;
    }

    // ...
}

Clicking on two shapes that do share a border returns fairly quickly, but very distance shapes or shapes with a large number of pixels take 3+ seconds at times. What options do I have for optimizing this?

A: 

2 regions having border means that within a certain small area there should be 3 colors present: red, black and green.

So a very ineffective solution presents itself: using Color pixelColor = myBitmap.GetPixel(x, y); you could scan an area for those 3 colors. The area must be larger than the width of the border of course.

There is of course plenty room for optimizations (like going in 50 pixels steps and decreasing the precision continually). Since black is the least used color, you would search around black areas first.

This should explain what I have written in various comments in this topic:

namespace Phobos.Graphics
{
    public class BorderDetector
    {
        private Color region1Color = Color.FromArgb(222, 22, 46);
        private Color region2Color = Color.FromArgb(11, 189, 63);
        private Color borderColor = Color.FromArgb(11, 189, 63);

        private List<Point> region1Points = new List<Point>();
        private List<Point> region2Points = new List<Point>();
        private List<Point> borderPoints = new List<Point>();

        private Bitmap b;

        private const int precision = 10;
        private const int distanceTreshold = 25;

        public long Miliseconds1 { get; set; }
        public long Miliseconds2 { get; set; }

        public BorderDetector(Bitmap b)
        {
            if (b == null) throw new ArgumentNullException("b");

            this.b = b;
        }

        private void ScanBitmap()
        {
            Color c;

            for (int x = precision; x < this.b.Width; x += BorderDetector.precision)
            {
                for (int y = precision; y < this.b.Height; y += BorderDetector.precision)
                {
                    c = this.b.GetPixel(x, y);

                    if (c == region1Color) region1Points.Add(new Point(x, y));
                    else if (c == region2Color) region2Points.Add(new Point(x, y));
                    else if (c == borderColor) borderPoints.Add(new Point(x, y));
                }
            }
        }

        /// <summary>Returns a distance of two points (inaccurate but very fast).</summary>
        private int GetDistance(Point p1, Point p2)
        {
            return Math.Abs(p1.X - p2.X) + Math.Abs(p1.Y - p2.Y);
        }

        /// <summary>Finds the closests 2 points among the points in the 2 sets.</summary>
        private int FindClosestPoints(List<Point> r1Points, List<Point> r2Points, out Point foundR1, out Point foundR2)
        {
            int minDistance = Int32.MaxValue;
            int distance = 0;

            foundR1 = Point.Empty;
            foundR2 = Point.Empty;

            foreach (Point r1 in r1Points)
                foreach (Point r2 in r2Points)
                {
                    distance = this.GetDistance(r1, r2);

                    if (distance < minDistance)
                    {
                        foundR1 = r1;
                        foundR2 = r2;
                        minDistance = distance;
                    }
                }

            return minDistance;
        }

        public bool FindBorder()
        {
            Point r1;
            Point r2;

            Stopwatch watch = new Stopwatch();

            watch.Start();
            this.ScanBitmap();
            watch.Stop();
            this.Miliseconds1 = watch.ElapsedMilliseconds;

            watch.Start();
            int distance = this.FindClosestPoints(this.region1Points, this.region2Points, out r1, out r2);
            watch.Stop();
            this.Miliseconds2 = watch.ElapsedMilliseconds;

            this.b.SetPixel(r1.X, r1.Y, Color.Green);
            this.b.SetPixel(r2.X, r2.Y, Color.Red);

            return (distance <= BorderDetector.distanceTreshold);
        }
    }
}

It is very simple. Searching this way only takes about 2 + 4 ms (scanning and finding the closest points).

You could also do the search recursively: first with precision = 1000, then precision = 100 and finally precision = 10 for large images. FindClosestPoints will practically give you an estimated rectangual area where the border should be situated (usually borders are like that).

Then you could use the vector approach I have described in other comments.

Jaroslav Jandek
Assuming the shapes are all the same color and I have an array of pixels that makes up each shape, I could also test for proximity between pixels from each shape, yeah? Is there an efficient way to do this besides looping through each pixel in one shape and testing for proximity to every pixel in the other shape?
Chris
Each region must be of **different color**, otherwise a **floodfill** algorithm would have to be run first for the method to work (which would also make the method not so effective) and a vector approach would be more useful.
Jaroslav Jandek
Another performance improvement could be achieved by using a simple `X, Y struct` instead of the class `Point`.
Jaroslav Jandek
A: 

I read your question as asking whether the two points exist in different regions. Is this correct? If so, I would probably use a variation of Flood Fill. It's not super difficult to implement (don't implement it recursively, you will almost certainly run out of stack space) and it will be able to look at complex situations like a U-shaped region that has a border between two points, but are not actually different regions. Basically run flood fill, and return true when your coordinate matches the target coordinate (or perhaps when it's close enough for your satisfaction, depending on your use case)

[Edit] Here is an example of flood fill that I wrote for a project of mine. The project is CPAL-licensed, but the implementation is pretty specific to what I use it for anyway, so don't worry about copying parts of it. And it doesn't use recursion, so it should be able to scale to pixel data.

[Edit2] I misunderstood the task. I don't have any example code that does exactly what you're looking for, but I can say that comparing pixel-per-pixel the entire two regions is not something you want to do. You can reduce the complexity by partitioning each region into a larger grid (maybe 25x25 pixels), and comparing those sectors first, and if any of those are close enough, do a pixel-per-pixel comparison just within those two sectors.

[Edit2.5] [Quadtree]3 might be able to help you too. I don't have a lot of experience with it, but I know it's popular in 2D collision detection, which is similar to what you're doing here. Might be worth researching.

Sean Edwards
I'm actually already using a modified flood fill algorithm to collect the pixels in each shape. The task now is to identify if any of the pixels in shape 2 are within N distance of any of the pixels in shape 1, where N is the approximate width of the border.
Chris
Oh, I see, you're determining adjacency of the two regions. Hang on, I will edit.
Sean Edwards
If you're collecting all the pixels in the shapes, and the shapes will look something like your example, then making note of the bounding boxes of the shapes will make life easier.
Yellowfog
Bounding boxes would certainly make a good initial broadphase, but in the example he posted, most of the bounding boxes will overlap, so in practice it's not gonna do much. You'd still have an O(n^2) comparison on pixels unless you subdivide deeper, ruling out early any pixels that can't possibly be close enough.
Sean Edwards
Cool, that is exactly what I am suggesting, didn't know there is a term for it - Quadtree.
Jaroslav Jandek
I don't think that 'most' of the bounding boxes will overlap (even when extended by a few pixels to account for the border). And in the cases that they don't, your speed improvement is huge. And if the pixels were stored in a 2d array the size of the bounding box then you could limit the search to the overlapping pixels. That beig said, absolute agreement about the need to avoid the naive brute force approach with the pixels.
Yellowfog
I was able to cut the time down with a combination of the faster but less accurate distance measurement and a while loop that starts with a high precision value (say, 1000) and loops, dividing the precision by 2 each time until candidate pixels are found or the precision reaches 1. Thanks for your help!
Chris