最小包围球的弹跳泡算法

时间:2013-06-26 21:59:29

标签: algorithm language-agnostic 3d geometry point-clouds

我对Bouncing Bubble Algorithm感兴趣,为一组点找到近似最小的封闭球体。

我理解基本概念:“每次发现球外的一个点时,球会向它移动并同时增加半径。每一步的增长都是为了使它能够不超过最佳半径,因此半径越来越接近最佳值。enter image description here

但我无法在网上找到任何更具体的信息。如何计算增长?转向新点的距离有多远?

我正在寻找一个示例实现或足够的信息来实现我自己的解决方案。

3 个答案:

答案 0 :(得分:10)

我意识到这篇文章已经有一年了,但是我用C ++实现了弹跳泡泡算法,并且可能有些人会发现它很有用。这是基于田波的论文,我以18美元的价格购买。

BoundSphere calculateBoundSphere(vector<Vertex> vertices){
    Vector3D center = vertices[0].position;
    float radius = 0.0001f;
    Vector3D pos, diff;
    float len, alpha, alphaSq;
    vector<Vertex>::iterator it;

    for (int i = 0; i < 2; i++){
        for (it = vertices.begin(); it != vertices.end(); it++){
            pos = it->position;
            diff = pos - center;
            len = diff.length();
            if (len > radius){
                alpha = len / radius;
                alphaSq = alpha * alpha;
                radius = 0.5f * (alpha + 1 / alpha) * radius;
                center = 0.5f * ((1 + 1 / alphaSq) * center + (1 - 1 / alphaSq) * pos);
            }
        }
    }

    for (it = vertices.begin(); it != vertices.end(); it++){
        pos = it->position;
        diff = pos - center;
        len = diff.length();
        if (len > radius){
            radius = (radius + len) / 2.0f;
            center = center + ((len - radius) / len * diff);
        }
    }

    return BoundSphere(center, radius);
}

答案 1 :(得分:2)

Here是您要求的实施方式。对于分析部分,您可以阅读以下内容。

这些分析用于圆(2d),但球体(3d)的扩展应该很容易。我相信分析会有所帮助。

一个O(n2)时间算法

  • 在中心绘制一个圆圈,c,使得给定集合的点位于其中 圆圈。显然,这个圆圈可以做得更小(或者我们 有解决方案)。
  • 通过找到离A最远的点A使圆圈变小 圆圈的中心,并绘制一个具有相同中心的新圆圈 通过A点。这两个步骤产生的更小 围成一圈。新圆圈较小的原因是 这个新的圆圈仍然包含给定集合的所有点,除了 现在它通过最远点x,而不是封闭它。
  • 如果圆圈经过2个或更多点,请继续执行步骤4。 否则,通过将中心移向,使圆圈变小 A点,直到圆圈与另一个B点接触 集。
  • 在这个阶段,我们是一个圆圈,C,穿过两个或更多 给定集合的点。如果圆圈包含间隔 (无点间隔)弧大于圆的一半 没有点的圆周,可以制作圆 小。设D和E为这个无点的末端的点 间隔。在将D和E保持在圆的边界上时,减少 直到我们有案例(a)或案例(b)的圆的直径。
    • 案例(a)直径是距离DE 我们完成了!
    • 案例(b)圆圈C接触集合中的另一个点,即。
      检查是否存在长度超过C圆周长度的无点弧间隔。
      IF
      没有这样的无点弧间隔退出那么 我们完成了!
      否则
      转到第4步。 在这种情况下,三个点必须位于长度小于圆周一半的圆弧上。我们在弧上三个点的外两个上重复步骤4。

分析

此算法很简单,但实施起来很昂贵。步骤1,2和3需要给定组中的点数的线性时间。在上面的步骤4中,找到每个新点F需要线性时间。但是,找到点F并不能保证算法的终止。必须重复步骤4,直到圆圈不包含长于其圆周一半的无点间隔。在最坏的情况下,这需要步骤4的(n-2)次迭代,这意味着在步骤4中花费的总时间可以是点集大小的平方的量级。

因此,该算法为O(n2)。


O(n)算法

Nimrod Megiddo提出​​了算法,并使用剪枝和搜索技术进行线性规划,以便在O(n)时间内找到最小的封闭圆。

剪枝和 - 搜索

Megiddo算法的本质是修剪和搜索。在剪枝和搜索算法中,在每个步骤完成线性量的工作以将输入大小减小恒定分数f。如果可以实现这一点,则完成的工作总量将减少到O(n)*(1 +(1-f)+(1-f)2 + ...)。在该公式中,无穷级数是几何的并且总和为常数值,因此总运行时间为O(n)。

例如,假设通过检查我们的n个输入元素集合,我们可以丢弃其中1/4与解决方案无关。通过对剩余元素重复应用这种检查,我们可以将输入减少到一个很容易解决的大小,比如n≤3。实现这种减少所需的总时间将与(n + 3n / 4 + 9n / 16 + ...)成比例。很容易证明这个系列接近并且永远不会超过4n的限制。因此,根据需要,总运行时间与n成正比。

使用几何级数将算法简化为线性时间的想法早于Megiddo的工作;特别是,它先前已被用于开发O(n)中值发现算法。然而,他是第一个将其应用于计算几何中的一些基本问题的人。

Megiddo的线性时间算法

为了找到一组点的最小包围圆(MEC),Megiddo算法在每次(线性时间)迭代时丢弃至少n / 16个点。也就是说,给定n个点的集合S,算法识别可以从S中移除的n / 16个点而不影响S的MEC。可以重复应用该过程直到达到一些普通的基本情况(例如n = 3)。 ),总运行时间与(n + 15n / 16 + 225n / 256 + ...)= 16n成正比。

为了找到丢弃的n / 16分,需要很大的聪明才智。该算法大量使用两个子程序:

  • median(S,&gt;):获取元素集S和用于比较的度量 它们成对,并返回元素的中位数。
  • MEC-center(S,L):取一组S点和一条线L,和 确定S的MEC中心位于L的哪一侧。

如上所述,中位数()早于Megiddo的工作,而此处描述为MEC-center()的算法是1983年论文的一部分。详细探讨这些过程将超出本大纲的范围,但每个过程都使用剪枝和搜索来在线性时间内运行。 MEC-center()使用的算法相当于整个算法的简化版本。

鉴于这些原语,丢弃n / 16个输入点的算法运行如下:

  • 任意配对S中的n个点,得到n / 2对。
  • 为每对点构造一条二等分线,得到n / 2 平分线。
  • 调用median()找到中间斜率的平分线。叫这个斜坡 MMID。
  • 将斜率≥mmid的每个平分线与另一个斜率&lt; MMID, 得到n / 4个交叉点。调用这些点的集合我。
  • 调用中位数()以找到具有中值y值的I点。叫这个 y-value ymid。
  • 调用MEC-center()来查找y = ymid的哪一侧 MEC-center C依赖于。 (不失一般性,假设它是谎言 的上方。)
  • 让我&#39;是y值小于的I点的子集 ymid。 (我&#39;包含n / 8分。)
  • 找到一条斜率mmid的线L,使得I&#39;骗 L左,右边一半。
  • 在L上调用MEC-center()而不失一般性,假设C依赖于 对了。
  • 让我&#39;&#39;是I&#39;的子集它的点位于L的左边。(我&#39;&#39; 包含n / 16分。)

我们现在可以在S中为I&#39;中的每个n / 16交叉点丢弃一个点。推理如下。在两次调用MEC-center()之后,我们发现MEC中心C必须位于ymid之上且位于L的右侧,而I&#39;中的任何一点都在在低于ymid和L的左边。

I&#39;中的每一点。是在两个平分线的交汇点。这些平分线中的一个必须具有≥mmid的斜率,因此绝不能穿过我们知道C所在的象限。称这个平分线B.现在,我们知道B C的哪一侧所在,所以在平分线为B的两个点上,让PC成为与C位于同一侧的那个,让另一个成为PX。

很容易证明PC必须比PX更接近C。因此,PC不能位于最小的封闭圆上,因此我们可以安全地丢弃I&#39;中每个n / 16交叉点的点PC。

我们这里没有讨论如何使用这种算法来处理退化输入(并行平分线,共线点等),但事实证明我们在这种情况下得到了相同的性能保证。事实上,对于退化输入,该算法能够丢弃超过n / 16点。简而言之,Megiddo的算法保证在每次迭代中至少修剪n / 16个点而不依赖于输入。

因此,通过基于几何级数的论证,Megiddo算法计算线性时间内的最小包围圆。

有关O(n)算法

的更多信息,请参阅this文章

答案 2 :(得分:0)

更新:仔细阅读动画和维基百科文章,我认为我的算法不是Bouncing Bubble。从动画中可以清楚地看到Bouncing Bubble移动了边界球体,而我的算法一次只能在一个方向上增长。此外,我的理解是Bouncing Bubble使用近似值并且可能不包含所有点;但是,似乎有一个可以控制的公差参数。

我认为我的算法仍有一些不错的属性:

  1. 这是O(n)。
  2. 这是增量的。
  3. 保证包含所有积分。
  4. 这很简单。唯一更简单的方法是找到轴对齐的边界框,然后在其周围放置一个球体,但这肯定会比这个算法产生的体积更大。

  5. 我自己想出了一个O(n)增量算法,根据维基百科上的描述我认为必须是Bouncing Bubble算法。我不知道它与最小边界圈有多接近。首先,我将尝试解释算法背后的原因。它可能有助于在纸上绘制。

    想象一下,你有一个带有中心 C 和半径 r 的边界圆。您想要扩展圆圈以包含新点 P 。您计算 d C P 的距离,发现它大于 r ,所以它必须是在圈外。现在,如果您从 P C 投射光线,它会在 A 处退出圆圈背面。

    现在假设你将圆圈固定在 A 上,然后沿着光线向 P 展开。它将继续包含所有先前的点,现在它包含 P 。这个新圆的直径是 r + d ,因此它的半径是 r'=(r + d)/ 2 。沿着从 P A 的光线沿着新半径的距离走,你就在新圆的中心。

    以下是该算法的草图:

    1. 将边界球体初始化为第一个点 C 的中心,半径 r 为0。
    2. 对于每个剩余点 P
      1. 如果从 P C 的距离 d &lt; = r ,请跳过它。
      2. 如果 d&gt; [R
        1. 新半径 r'=(r + d)/ 2
        2. 新中心 C'= P + r'(C - P)/ d
    3. 这是我今天写的一些Objective-C代码:

      - (void)updateBoundingVolume {
          self.boundingSphereCenter = GLKVector3Make(0, 0, 0);
          self.boundingSphereRadius = 0;
      
          GLfloat* vertex = self.vertices;
          GLfloat* endV = vertex + 3 * self.vertNum;
      
          if (vertex < endV) {
              // initialize to zero radius sphere centered on the first point
              self.boundingSphereCenter = GLKVector3Make(vertex[0], vertex[1], vertex[2]);
              vertex += 3;
          }
      
          while (vertex < endV) {
              GLKVector3 p = GLKVector3Make(vertex[0], vertex[1], vertex[2]);
              vertex += 3;
      
              float d = GLKVector3Distance(self.boundingSphereCenter, p);
              if (d <= self.boundingSphereRadius) {
                  // already inside the sphere
                  continue;
              }
      
              // length of line from other side of sphere through the center to the new point
              // divide by 2 for radius
              self.boundingSphereRadius = (self.boundingSphereRadius + d) / 2;
      
              // unit vector from new point to old center
              GLKVector3 u = GLKVector3DivideScalar(GLKVector3Subtract(self.boundingSphereCenter, p), d);
              // step back from the new point by the new radius to get the new center
              self.boundingSphereCenter = GLKVector3Add(p, GLKVector3MultiplyScalar(u, self.boundingSphereRadius));
          }
      }