我最近遇到一个问题,我有四个圆(中点和半径),必须计算这些圆的并集面积。
示例图片:
对于两个圈子来说,这很容易
我可以计算出不在三角形内的每个圆面积的分数,然后计算三角形的面积。
但是,当有两个以上的圆圈时,是否可以使用一种聪明的算法?
我最近遇到一个问题,我有四个圆(中点和半径),必须计算这些圆的并集面积。
示例图片:
对于两个圈子来说,这很容易
我可以计算出不在三角形内的每个圆面积的分数,然后计算三角形的面积。
但是,当有两个以上的圆圈时,是否可以使用一种聪明的算法?
Answers:
在外周上找到所有圆的交点(例如下图的B,D,F,H)。将它们与相应圆的中心连接在一起以形成多边形。圆并集的面积是多边形的面积+由连续相交点和它们之间的圆心定义的圆片的面积。您还需要解决所有漏洞。
我敢肯定有一个聪明的算法,但这是一个愚蠢的算法,省去了寻找它的麻烦。
当然是愚蠢的,但是:
蚂蚁·阿斯玛(Ants Aasma)的答案给出了基本概念,但我想使其更为具体。看一下下面的五个圆圈以及它们的分解方式。
识别这三种类型的点很容易。现在构造一个图形数据结构,其中节点是内部为白色的蓝点和红点。对于每个圆,在圆心(蓝色圆点)与其交点(内部为白色的红点)的边界之间放置一条边。
这会将圆形并集分解为一组成对不相交并覆盖原始并集(即一个分区)的多边形(蓝色阴影)和圆形饼图(绿色阴影)。由于这里的每个零件都易于计算面积,因此您可以通过将零件的面积相加来计算并集的面积。
对于与前一个解决方案不同的解决方案,您可以使用四叉树产生具有任意精度的估计。
如果您可以判断正方形是在内部还是外部或与该形状相交,则此方法也适用于任何形状并集。
每个单元格都具有以下状态之一:空,满,部分
该算法包括以低分辨率(例如4个单元标记为空)开始“绘制”四叉树中的圆。每个单元格为:
完成后,您可以计算面积的估计值:完整的单元格给出较低的边界,空白的单元格给出较高的边界,部分单元格给出最大的面积误差。
如果误差对您来说太大,则可以优化部分像元,直到获得正确的精度为止。
我认为这比处理许多特殊情况可能需要的几何方法更容易实现。
我喜欢2个相交圆的情况下的方法-在更复杂的示例中,我将使用相同方法的细微变化。
它可能会更好地了解泛化大量半重叠圆的算法。
此处的区别在于,我从链接中心开始(因此,在圆心之间有一个顶点,而不是在圆心相交的地方之间有一个顶点),我认为这样可以更好地进行概括。
(实际上,也许蒙特卡洛方法值得)
(来源:secretGeek.net)
嗯,非常有趣的问题。我的方法可能类似于以下内容:
(对于任何形状,无论是圆形还是其他形状,都是如此)
area(A∪B) = area(A) + area(B) - area(A∩B)
哪里 A ∪ B
意思是A的并集B和A ∩ B
A的相交B(您可以从第一步开始计算。
(与上面A
替换为的情况相同A∪B
)
area((A∪B)∪C) = area(A∪B) + area(C) - area((A∪B)∩C)
当area(A∪B)
我们刚制定出来的,并area((A∪B)∩C)
可以发现:
area((A∪B)nC) = area((A∩C)∪(B∩C)) = area(A∩C) + area(A∩B) - area((A∩C)∩(B∩C)) = area(A∩C) + area(A∩B) - area(A∩B∩C)
再次可以从上方找到区域(A∩B∩C)。
棘手的是最后一步-添加的圆圈越多,它变得越复杂。我相信可以用有限的联合来计算交叉点的面积,或者您可以递归地进行求解。
另外,关于使用蒙特卡洛近似计算迭代区域,我相信可以将任意数量的圆的交点减少为这些圆的4个交点,这可以精确计算(不知道如何执行此操作)然而)。
顺便说一句,也许有更好的方法-添加的每个额外的圆的复杂度会显着增加(可能呈指数增长,但我不确定)。
使用所谓的功率图,可以有效解决此问题。虽然这确实是沉重的数学运算,但我不想立即解决。对于“简单”的解决方案,请查找行扫描算法。这里的基本原理是将图形划分为条带,在其中计算每个条带的面积相对容易。
因此,在包含所有未擦掉的所有圆的图形上,在每个位置绘制一条水平线,该位置是圆的顶部,圆的底部或2个圆的交点。请注意,在这些条带中,您需要计算的所有区域看起来都相同:一个“梯形”,其两侧被圆形线段代替。因此,如果您能算出如何计算这种形状,则只需对所有单个形状进行处理,然后将它们加在一起即可。这种幼稚方法的复杂度为O(N ^ 3),其中N是图中的圆圈数。通过使用一些巧妙的数据结构,可以将此行扫描方法改进为O(N ^ 2 * log(N)),但是除非您确实需要,否则可能不值得麻烦。
根据您要解决的问题,它可能足以获得一个上限和下限。上限很容易,只是所有圆的总和。对于下限,您可以选择一个半径,以使所有圆都不重叠。为了更好地找到每个圆的最大半径(不超过实际半径),使其不重叠。删除所有完全重叠的圆(所有这些圆都满足| P_a-P_b | <= r_a)也应该是微不足道的,其中P_a是圆A的中心,P_b是圆B的中心,r_a是A的半径),这样可以同时提高上下限。如果您在任意对上使用对公式,而不仅仅是所有圆的总和,则还可以获得更好的上限。可能有一个选择“最佳”的好方法
给定一个上限和下限,您也许可以更好地调整蒙特卡洛方法,但是没有什么特别的主意。另一个选择(同样取决于您的应用程序)是栅格化圆圈并计算像素。它基本上是具有固定分布的蒙特卡洛方法。
这可以使用格林定理来解决,其复杂度为n ^ 2log(n)。如果您不熟悉格林定理,并且想了解更多,这里是可汗学院的视频和笔记。但是为了我们的问题,我认为我的描述就足够了。
很抱歉,无法链接到照片,因为我无法发布图像。(信誉评分不足)
如果我把L和M这样
那么RHS就是区域R的面积,可以通过求解闭合积分或LHS来获得,这正是我们要做的。
因此,沿逆时针方向的路径积分可以得到该区域的面积,而沿顺时针方向的积分则可以得到该面积的负数。所以
AreaOfUnion =(逆时针方向沿红色弧的积分+顺时针方向沿蓝色弧的积分)
但是很酷的窍门是,如果对于每个圆,如果我们对不在任何其他圆内的弧进行积分,则得到所需的面积,即沿所有红色弧在逆时针方向上进行积分,而沿顺时针方向在所有蓝弧上进行积分。任务完成!!!
即使一个圆不与其他圆相交的情况也可以解决。
这是我的C ++代码的GitHub链接
像素绘制方法(由@Loadmaster建议)在多种方面优于数学解决方案:
像素绘制的一个缺点是解决方案的精度有限。但这可以通过根据情况需要简单地渲染到更大或更小的画布来调整。还要注意,2D渲染代码中的抗锯齿(通常默认情况下处于启用状态)将产生优于像素级别的精度。因此,例如,我认为将100x100的图形渲染到相同尺寸的画布中时,产生的精度应为1 /(100 x 100 x 255)= .000039%的数量级……这可能“足够好”除了最苛刻的问题之外的所有问题。
<p>Area computation of arbitrary figures as done thru pixel-painting, in which a complex shape is drawn into an HTML5 canvas and the area determined by comparing the number of white pixels found in the resulting bitmap. See javascript source for details.</p>
<canvas id="canvas" width="80" height="100"></canvas>
<p>Area = <span id="result"></span></p>
// Get HTML canvas element (and context) to draw into
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Lil' circle drawing utility
function circle(x,y,r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2);
ctx.fill();
}
// Clear canvas (to black)
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Fill shape (in white)
ctx.fillStyle = 'white';
circle(40, 50, 40);
circle(40, 10, 10);
circle(25, 15, 12);
circle(35, 90, 10);
// Get bitmap data
var id = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = id.data; // Flat array of RGBA bytes
// Determine area by counting the white pixels
for (var i = 0, area = 0; i < pixels.length; i += 4) {
area += pixels[i]; // Red channel (same as green and blue channels)
}
// Normalize by the max white value of 255
area /= 255;
// Output result
document.getElementById('result').innerHTML = area.toFixed(2);