【问题标题】:How to quickly count the number of neighboring voxels?如何快速统计相邻体素的数量?
【发布时间】:2010-11-02 17:25:58
【问题描述】:

我有一个 3D 网格(体素),其中一些体素被填充,而另一些则没有。 3D 网格是稀疏填充的,所以我有一组 filledVoxels 带有填充体素的坐标 (x, y, z)。我要做的是找出每个填充的体素,也填充了多少相邻的体素。

这是一个例子:

  • filledVoxels 包含体素 (1, 1, 1)、(1, 2, 1) 和 (1, 3, 1)。
  • 因此,邻居计数为:
    • (1,1,1) 有 1 个邻居
    • (1,2,1) 有 2 个邻居
    • (1,3,1) 有 1 个邻居。

现在我有这个算法:

voxelCount = new Map<Voxel, Integer>();

for (voxel v in filledVoxels)
  count = checkAllNeighbors(v, filledVoxels);
  voxelCount[v] = count;
end

checkAllNeighbors() 查找所有 26 个周围的体素。因此,我总共进行了 26*filledVoxels.size() 查找,这非常慢。

有什么方法可以减少所需的查找次数?当您查看上面的示例时,您可以看到我多次检查相同的体素,因此可以通过一些巧妙的缓存来摆脱查找。

如果这有任何帮助,体素代表一个体素化的 3D 表面(但其中可能有孔)。我通常想获取具有 5 或 6 个邻居的所有体素的列表。

【问题讨论】:

  • 您可能应该添加一些有关填充体素数组中元素排序的信息。没有这些信息,回答这个问题只能基于猜测。
  • 它并没有减少所需的查找次数,但您的问题看起来非常可并行。

标签: algorithm language-agnostic 3d voxels


【解决方案1】:

您可以将体素空间转换为octree,其中每个节点都包含一个标志,指定它是否包含填充体素。

当一个节点不包含填充体素时,你不需要检查它的任何后代。

【讨论】:

  • 这有助于非常稀疏的数据集或本地化数据集(大多数体素接近其他体素),但每个体素只需要一次查找。对于随机分布的体素超过 25% 的体素空间,它没有多大帮助。
【解决方案2】:

我会说如果您的每个查找都很慢(O(size)),您应该通过在有序列表中的二进制搜索来优化它(O(log(size)))。

常数 26,我不会太担心。如果你迭代得更聪明,你可以缓存一些东西,并有 26 -> 10 或其他东西,我认为,但除非你已经分析了整个应用程序并果断地发现它是瓶颈,否则我会专注于其他东西。

【讨论】:

    【解决方案3】:

    正如 ilya 所说,要绕过 26 个邻居查找,您无能为力。您必须在有效识别给定邻居是否已填满方面取得最大收益。鉴于蛮力解决方案本质上是 O(N^2),因此您在该领域有很多可能的优势。由于您必须至少对所有填充体素进行一次迭代,因此我将采用类似于以下的方法:

    voxelCount = new Map<Voxel, Integer>();
    visitedVoxels = new EfficientSpatialDataType();
    
    for (voxel v in filledVoxels)
      for (voxel n in neighbors(v))
        if (visitedVoxels.contains(n))
          voxelCount[v]++;
          voxelCount[n]++;
        end
      next
      visitedVoxels.add(v);
    next
    

    对于您的高效空间数据类型,正如 Zifre 建议的那样,kd-tree 可能是一个好主意。在任何情况下,您都希望通过对访问过的体素进行分箱来减少搜索空间。

    【讨论】:

      【解决方案4】:

      如果您一次一个地沿着体素行进,您可以保留一个与网格相对应的查找表,以便在您使用IsFullVoxel() 检查一次后,将值放入此网格中。对于您正在行进的每个体素,您可以检查其查找表值是否有效,并且只调用 IsFullVoxel() 它不是。

      OTOH,您似乎无法避免使用IsFullVoxel() 或LUT 迭代所有相邻体素。如果您有更多先验信息,它可能会有所帮助。例如,如果您知道最多有 x 个相邻的填充体素,或者您知道每个方向最多有 y 个相邻的填充体素。例如,如果您知道要查找具有 5 到 6 个邻居的体素,则可以在找到 7 个完整邻居或 22 个空邻居后停止。

      我假设存在一个函数 IsFullVoxel(),如果体素已满则返回 true。

      【讨论】:

      • 嗨,我认为有一些误解,filledVoxels.size() 给了我所有填充体素的数量。我迭代这个集合,并为每个条目执行 26 次查找以计算邻居数
      【解决方案5】:

      如果您迭代中的大部分移动都是针对邻居的,那么您可以通过在迈出这一步之前不回头查看刚刚检查的那些移动来减少大约 25% 的检查。

      【讨论】:

        【解决方案6】:

        您可能会在这里找到一个有用的概念Z-order curve。它允许您(在某些条件下)在您当前查询的点周围保持一个滑动的数据窗口,这样当您移动到下一个点时,您不必丢弃许多已经执行的查询.

        【讨论】:

          【解决方案7】:

          嗯,你的问题不是很清楚。我假设您只有一个已填充点的列表。在那种情况下,这个会很慢,因为你必须遍历它(或者使用某种树结构,比如kd-tree,但这仍然是O(log n)) .

          如果可以(即网格不是太大),只需制作一个 3d 布尔数组。 3d 数组中的 26 次查找应该不会花费那么长时间(而且确实没有办法减少查找次数)。

          实际上,现在我想到了,您可以将其设为 3d 长数组(64 位)。每个 64 位块将包含 64 (4 x 4 x 4) 体素。当您检查块中间体素的邻居时,您可以进行一次 64 位读取(这会快得多)。

          【讨论】:

            【解决方案8】:

            有什么方法可以减少所需的查找次数?

            您必须至少对每个体素执行 1 次查找。既然这是最低要求,那么每个体素只执行一次查找的任何算法都将满足您的要求。

            一个简单的想法是初始化一个数组来保存每个体素的计数,然后查看每个体素并增加数组中该体素的邻居。

            伪 C 可能看起来像这样:

            #define MAXX 100
            #define MAXY 100
            #define MAXZ 100
            
            int x, y, z
            char countArray[MAXX][MAXY][MAXZ];
            
            initializeCountArray(MAXX, MAXY, MAXZ);  // Set all array elements to 0
            
            for(x=0; x<MAXX; x++)
               for(y=0;y<MAXY;y++)
                  for(z=0;z<MAXZ;z++)
                     if(VoxelExists(x,y,z))
                        incrementNeighbors(x,y,z);
            

            您需要编写 initializeCountArray 以便将所有数组元素设置为 0。

            更重要的是,您还需要编写 incrementNeighbors 以便它不会在数组之外递增。这里稍微提高速度是只对内部的所有体素执行上述算法,然后使用修改后的 incrementNeighbrs 例程对所有外部边缘体素进行单独运行,该例程知道一侧不会有邻居。

            此算法导致每个体素进行 1 次查找,每个体素最多添加 26 个字节。如果您的体素空间稀疏,那么这将导致很少(相对)添加。如果您的体素空间非常密集,您可以考虑反转算法 - 将每个条目的数组初始化为 26 的值,然后在体素不存在时递减邻居。

            给定体素的结果(即,我有多少邻居?)驻留在数组中。如果您需要知道体素 2,3,5 有多少个邻居,只需查看 countArray[2][3][5] 中的字节即可。

            数组将消耗每个体素 1 个字节。您可以使用更少的空间,并可能通过打包字节来提高速度。

            如果您了解数据的详细信息,会有更好的算法。例如,一个非常稀疏的体素空间将极大地受益于八叉树,当您已经知道内部没有填充体素时,您可以跳过大块的查找。然而,这些算法中的大多数仍然需要每个体素至少进行一次查找来填充它们的矩阵,但是如果您执行多个操作,那么它们可能比这一次操作受益更多。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-07-07
              • 1970-01-01
              • 2017-01-09
              • 2013-06-24
              • 1970-01-01
              • 1970-01-01
              • 2014-06-06
              相关资源
              最近更新 更多