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

ここでの基本的な問題は、円の「上」にあるすべての点が円の中心から数学的に正しい距離を持っていると仮定していることです。

ピクセルは固定グリッド上にあり、円の解像度は無限であるため、実際にはそうではありません。距離がわずかに内側または外側になります。

これは、円の「上」にあると計算される反復ごとにほんの一握りのピクセルしかないことを意味し、したがって、円のすべてのピクセルがカウントされることはありません。

あなたのアルゴリズムの前提は理解していますが、円のピクセルの 1 つを別の方法で見つけたかどうかを調べる必要があります。これは、ピクセル グリッドと最適な円の位置のわずかな違いを説明するものです。

さらに、その特定のタイプの形状に対してのみ行っている場合は、単純に数学的に行ってみませんか? 外接円は簡単に計算できます。その半径は中心から角の 1 つまでの距離であり、内接円は中心からエッジの 1 つの中心までの距離です。

確かに、ランダムな形状に対してそれを行う必要がある場合、それは機能しませんが、別の問題があります。中心をどこに配置しますか?

ここで説明しましょう:

ピクセル グリッドの例

ここでは、半径 4 の円を示しています。黄色のピクセルは中心であり、赤は数式に従って数学的に円に正確に収まるものです。画像以外に2点あります。

ただし、2 つの緑が問題です。

X=-1 (中心から左に 1)、Y=-4 (中心から上に 4) の A の場合、式は次のようになります。

(-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

ご覧のとおり、円を構成するすべてのピクセルを見つけるアプローチには欠陥があります。実際、ほとんどの場合、中央の上下左右の 4 ピクセルだけが見つかり、あちこちに奇数のピクセルが見つかることもあります。

少なくとも、ブレゼンハムのサークルアルゴリズムまたはミッドポイントサークルアルゴリズムでサークル検索アルゴリズムを変更します

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

これにより、円上にあるすべてのポイントがすべて検出されますが、一部のポイントは 2 回返される可能性があることに注意してください。特に、右上、下、左、右のピクセル、および 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 に答える