这里的基本问题是,您假设在数学上“在”圆上的点与圆心的距离都是正确的。
事实上,它们不会,它们的距离会稍微在内部或外部,因为像素位于固定网格上,并且圆具有无限分辨率。
这意味着每次迭代只有少数像素将被计算为“在”圆上,因此 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;
}
}
}
输出: