4

我需要找到这个形状的外接圆和内接圆

在此处输入图像描述

我的代码找到了外接圆,但内接圆是错误的

for (int r = 1; ; r++) //r is radius
{
    int found = 0; //counts found pixels
    int wrong = 0; //counts pixels that are on the circle but have different color that desirable
    int good = 0; //counts pixels that are on the circle with desirable color
    for (int y = point.Y - r ; y <= point.Y + r ; y++) //point is the center of the figure (110,110)
    {
        for (int x = point.X - r ; x <= point.X + r; x++)
        {
            if((x - point.X) * (x - point.X) + (y - point.Y)*(y - point.Y) == r*r) //is on the circle
            {
                found++;
                if (img.GetPixel(x, y).ToArgb() != color.ToArgb()) // has given color (black)
                {
                    wrong++;
                }
                else 
                {
                    good++;
                }

            }
        }
    }
    outerRadius = r;
    if (found == wrong) break; //circumcircle found
    if (found == good) innerRadius = r; //incircle found
  }

结果我得到了这个:(红色外接圆,蓝色内接圆)

在此处输入图像描述

我找不到为什么这不起作用。请帮我。

4

2 回答 2

6

这里的基本问题是,您假设在数学上“在”圆上的点与圆心的距离都是正确的。

事实上,它们不会,它们的距离会稍微在内部或外部,因为像素位于固定网格上,并且圆具有无限分辨率。

这意味着每次迭代只有少数像素将被计算为“在”圆上,因此 found 永远不会计算圆的所有像素。

我了解您的算法的前提,但您需要弄清楚您是否以不同的方式找到了圆的像素之一,这将解释像素网格和最佳圆位置之间的微小差异。

此外,如果你只是为特定类型的形状做它,为什么不简单地在数学上做呢?外接圆很容易计算,其半径就是圆心到其中一个角的距离,内圆就是圆心到其中一个边的圆心的距离。

当然,如果您需要对随机形状执行此操作,那将无法正常工作,但是您有一个不同的问题,您将中心放置在哪里?

下面我来说明一下:

像素网格示例

在这里,我已经说明了一个半径为 4 的圆。黄色像素是中心,红色是恰好落在圆上的像素,根据您的公式在数学上。图像外还有 2 个额外的。

然而,这两个绿色是问题所在。

对于 A,其中 X=-1(左侧 1)中心和 Y=-4(中心上方 4),您的公式最终为:

(-1)*(-1) + (-4)*(-4) == 4*4
    1     +    16     ==  16
          17          ==  16

对于稍微靠内的 B,Y=-3:

(-1)*(-1) + (-3)*(-3) == 4*4
    1     +     9     ==  16
         10           ==  16

如您所见,您查找构成圆圈的所有像素的方法是有缺陷的。事实上,您很可能只在中心的正上方、下方、左侧或右侧找到四个像素,而且有时只是在这里和那里找到一个奇数像素。

至少我会用 Bresenham 的圆算法或中点圆算法改变你的圆查找算法

IEnumerable<Point> MidpointCirclePoints(int x0, int y0, int radius)
{
    int x = radius;
    int y = 0;
    int radiusError = 1 - x;

    while (x >= y)
    {
        yield return new Point(x + x0, y + y0);
        yield return new Point(y + x0, x + y0);
        yield return new Point(-x + x0, y + y0);
        yield return new Point(-y + x0, x + y0);
        yield return new Point(-x + x0, -y + y0);
        yield return new Point(-y + x0, -x + y0);
        yield return new Point(x + x0, -y + y0);
        yield return new Point(y + x0, -x + y0);

        y++;
        if (radiusError < 0)
            radiusError += 2 * y + 1;
        else
        {
            x--;
            radiusError += 2 * (y - x) + 1;
        }
    }
}

这将找到圆上的所有点,所有这些点,但请注意,有些点可以返回两次,特别是在右上、下、左、右像素处,以及每 45 度处的点。

但同样,请注意,您的算法不适用于随机像素块,因为您无法正确放置中心,这仅适用于严格对称的形状

这是一个完整的工作示例,您可以在LINQPad中尝试:

void Main()
{
    int size = 256;
    int radius = 110; // of the square, not of the circles

    var b = new Bitmap(size, size);
    using (Graphics g = Graphics.FromImage(b))
    {
        g.Clear(Color.White);
        g.FillPolygon(Brushes.Black, new[]
        {
            new Point(size / 2, size / 2 - radius),
            new Point(size / 2 + radius, size / 2),
            new Point(size / 2, size / 2 + radius),
            new Point(size / 2 - radius, size / 2)
        });
    }

    int incircleRadius;
    int circumcircleRadius;
    if (FindCircles(b, out incircleRadius, out circumcircleRadius))
    {
        using (Graphics g = Graphics.FromImage(b))
        {
            g.DrawEllipse(Pens.Red, new Rectangle(
                size / 2 - circumcircleRadius, size / 2 - circumcircleRadius,
                circumcircleRadius * 2 + 1, circumcircleRadius * 2 + 1));
            g.DrawEllipse(Pens.Blue, new Rectangle(
                size / 2 - incircleRadius, size / 2 - incircleRadius,
                incircleRadius * 2 + 1, incircleRadius * 2 + 1));
        }
    }
    b.Dump();
}

bool FindCircles(Bitmap input, out int incircleRadius, out int circumcircleRadius)
{
    int midX = input.Width / 2; // already we're introducing inaccuracies
    int midY = input.Height / 2; // what if the bitmap is an even number?
    int largestPossibleRadius = Math.Min(midX, midY);

    incircleRadius = 0;
    circumcircleRadius = 0;

    for (int r = 30; r < largestPossibleRadius; r++)
    {
        bool allBlack = true;
        bool allWhite = true;

        // Bresenhams Circle Algorithm
        foreach (Point p in MidpointCirclePoints(midX, midY, r))
        {
            // input.GetPixel(p.X, p.Y).R.Dump();
            bool isBlack = input.GetPixel(p.X, p.Y).R < 128; // dummy test
            if (isBlack)
            {
                // input.SetPixel(p.X, p.Y, Color.Green);
                allWhite = false;
            }
            else
            {
                // input.SetPixel(p.X, p.Y, Color.Green);
                allBlack = false;
            }

            // Debug
            // input.SetPixel(p.X, p.Y, Color.Green);
        }
        if (allBlack)
        {
            incircleRadius = r;
        }
        else if (allWhite)
        {
            circumcircleRadius = r - 1;
            break;
        }
    }

    return incircleRadius > 0 && circumcircleRadius > 0;;
}

IEnumerable<Point> MidpointCirclePoints(int x0, int y0, int radius)
{
    int x = radius;
    int y = 0;
    int radiusError = 1 - x;

    while (x >= y)
    {
        yield return new Point(x + x0, y + y0);
        yield return new Point(y + x0, x + y0);
        yield return new Point(-x + x0, y + y0);
        yield return new Point(-y + x0, x + y0);
        yield return new Point(-x + x0, -y + y0);
        yield return new Point(-y + x0, -x + y0);
        yield return new Point(x + x0, -y + y0);
        yield return new Point(y + x0, -x + y0);

        y++;
        if (radiusError < 0)
            radiusError += 2 * y + 1;
        else
        {
            x--;
            radiusError += 2 * (y - x) + 1;
        }
    }
}

输出:

程序的输出

于 2013-05-19T19:50:53.287 回答
3

我猜你的算法一直在搜索。看起来当它找到一个黑色像素时,它假设它对内接圆来说是“好”的。在您的情况下,我认为它最终会沿着矩形的角一直到外接半径不断撞击黑色像素。

真的,一旦你找到任何白色像素,最后一个好的值应该是你得到的那个。我没有对此进行测试,但我认为它会起作用,或者至少让你找到解决方案:

bool isStillInscribed = true;
int inscribedCircleRadius = 0;
for (int r = 1; ; r++) //r is radius
{   
    int found = 0; //counts found pixels
    int wrong = 0; //counts pixels that are on the circle but have different color that desirable
    for (int y = point.Y - r ; y <= point.Y + r ; y++) //point is the center of the figure (110,110)
    {
        for (int x = point.X - r ; x <= point.X + r; x++)
        {
            if((x - point.X) * (x - point.X) + (y - point.Y)*(y - point.Y) == r*r) //is on the circle
            {
                found++;
                if (img.GetPixel(x, y).ToArgb() != color.ToArgb()) // has given color (black)
                {
                    wrong++;
                    //we are now outside the shape; cannot be inscribed anymore
                    isStillInscribed = false;
                }
            }
        }
    }

    //if the last checked circle radius still did not find any different 
    //coloured pixels, then this radius is still inscribed
    if (isStillInscribed)
        inscribedCircleRadius = r;

    outerRadius = r;
    if (found == wrong) break; //circumcircle found
}
于 2013-05-19T19:52:48.983 回答