如何检测图像中的不规则边框?

时间:2010-07-01 18:47:23

标签: c# language-agnostic

如下图所示,我可以用什么算法来检测区域1和区域2(由颜色标识)是否有边框?

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

如果那里有一个C#示例,那就太棒了,但我真的只是在寻找任何示例代码。

编辑:使用Jaro的建议,我想出了以下内容......

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;
    }

    // ...
}

单击两个共享边框的形状返回相当快,但非常距离的形状或具有大量像素的形状有时需要3秒以上。我有什么选择来优化它?

2 个答案:

答案 0 :(得分:0)

2个有边框的区域意味着在某个小区域内应该有3种颜色:红色,黑色和绿色。

因此,一个非常无效的解决方案呈现出来: 使用Color pixelColor = myBitmap.GetPixel(x, y);您可以扫描区域中的这3种颜色。当然,该区域必须大于边界的宽度。

当然有足够的优化空间(比如以50像素的步长并不断降低精度)。 由于黑色是最少使用的颜色,因此您首先会搜索黑色区域。

这应该解释我在本主题的各种评论中所写的内容:

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);
        }
    }
}

很简单。以这种方式搜索只需要 2 + 4 ms (扫描并找到最近的点)。

你也可以递归地进行搜索:首先是精度= 1000,然后是精度= 100,最后精度= 10表示大图像。 FindClosestPoints实际上会给你一个估计的矩形区域,边界应该位于该区域(通常边界就是这样)。

然后你可以使用我在其他评论中描述的矢量方法。

答案 1 :(得分:0)

我把你的问题看作是问这两个点是否存在于不同的地区。它是否正确?如果是这样,我可能会使用Flood Fill的变体。它实现起来并不困难(不要递归地实现它,你几乎肯定会耗尽堆栈空间)并且它将能够看到复杂的情况,比如在之间的边界的U形区域。 em>两点,但实际上并不是不同的区域。基本上运行泛洪填充,并在您的坐标与目标坐标匹配时返回true(或者当它足够接近以满足您的用例时,返回true)

[编辑]这是我为我的一个项目写的洪水填充an example。该项目是CPAL许可的,但实现非常具体到我用它的方式,所以不要担心复制它的一部分。并且它不使用递归,因此它应该能够缩放到像素数据。

[编辑2]我误解了这个任务。我没有任何示例代码可以完全满足您的需求,但我可以说,比较每像素像素并不是您想要做的事情。您可以通过将每个区域划分为更大的网格(可能是25x25像素)来降低复杂性,并首先比较这些扇区,如果其中任何一个足够接近,则在这两个扇区内进行像素/像素比较。

[Edit2.5] [四叉树] 3也可以帮到你。我没有很多经验,但我知道它在2D碰撞检测中很受欢迎,这与你在这里做的很相似。可能值得研究。