我正在使用CGAL(最新的)KD树实现来搜索点集中的最近邻居。维基百科和其他资源似乎也暗示KD树是可行的方式。但不知怎的,它们太慢了,而且Wiki也暗示了O(n)的最坏情况,这远非理想。
[BEGIN-EDIT] 我现在正在使用“nanoflann”,这比用于K邻居搜索的CGAL中的等效物快约100-1000倍。我正在使用“英特尔Embree”进行光线投射,这比CGAL的AABB树快100-200倍。 [END-EDIT]
我的任务如下:
我有一个巨大的点数,比如高达几百万。点!它们的分布在三角形几何体的表面上(是的,光子示踪剂)。所以可以说它们的分布在3D空间中是2D的,因为它在3D中是稀疏的,但在观察表面时是密集的......这可能是问题对吗?因为对我而言,这似乎触发了KD树的最坏情况性能,这可能会更好地处理3D密集点...
CGAl非常擅长它的功能,所以我有点怀疑它们的实现很糟糕。我用它进行光线追踪的AABB树在地上几分钟内燃烧了十亿个光线......我猜这是非常了不起的。但另一方面,他们的KD树甚至无法处理mio。在任何合理的时间点和250k样本(点查询)......
我提出了两个解决方案,它们可以解雇KD树:
1)使用纹理贴图将光子存储在几何体上的链接列表中。这总是一个O(1)操作,因为你无论如何都必须进行光线投射......
2)使用视图相关切片哈希集。这是你得到的越远,哈希集越粗。所以基本上你可以想到NDC坐标中的1024x1024x1024光栅,但是使用哈希集来节省稀疏区域的内存。这基本上具有O(1)复杂性,并且可以有效地并行化,用于插入(微分片)和查询(无锁)。
前一种解决方案的缺点是几乎不可能对相邻光子列表进行平均,这在较暗区域中很重要,以避免噪声。 后一种解决方案没有这个问题,并且应该与KD树明显相同,只是它具有O(1)最差情况下的性能,哈哈。
那你觉得怎么样?糟糕的KD树实施?如果没有,对于有界最近邻居查询,是否存在比KD树“更好”的东西?我的意思是我没有反对上面的第二个解决方案,但提供类似性能的“经过验证的”数据结构会更好!
谢谢!
以下是我使用的代码(不可编译):
#include "stdafx.h"
#include "PhotonMap.h"
#pragma warning (push)
#pragma warning (disable: 4512 4244 4061)
#pragma warning (disable: 4706 4702 4512 4310 4267 4244 4917 4820 4710 4514 4365 4350 4640 4571 4127 4242 4350 4668 4626)
#pragma warning (disable: 4625 4265 4371)
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Orthogonal_incremental_neighbor_search.h>
#include <CGAL/basic.h>
#include <CGAL/Search_traits.h>
#include <CGAL/point_generators_3.h>
#pragma warning (pop)
struct PhotonicPoint
{
float vec[3];
const Photon* photon;
PhotonicPoint(const Photon& photon) : photon(&photon)
{
vec[0] = photon.hitPoint.getX();
vec[1] = photon.hitPoint.getY();
vec[2] = photon.hitPoint.getZ();
}
PhotonicPoint(Vector3 pos) : photon(nullptr)
{
vec[0] = pos.getX();
vec[1] = pos.getY();
vec[2] = pos.getZ();
}
PhotonicPoint() : photon(nullptr) { vec[0] = vec[1] = vec[2] = 0; }
float x() const { return vec[0]; }
float y() const { return vec[1]; }
float z() const { return vec[2]; }
float& x() { return vec[0]; }
float& y() { return vec[1]; }
float& z() { return vec[2]; }
bool operator==(const PhotonicPoint& p) const
{
return (x() == p.x()) && (y() == p.y()) && (z() == p.z()) ;
}
bool operator!=(const PhotonicPoint& p) const
{
return ! (*this == p);
}
};
namespace CGAL
{
template <>
struct Kernel_traits<PhotonicPoint>
{
struct Kernel
{
typedef float FT;
typedef float RT;
};
};
}
struct Construct_coord_iterator
{
typedef const float* result_type;
const float* operator()(const PhotonicPoint& p) const
{
return static_cast<const float*>(p.vec);
}
const float* operator()(const PhotonicPoint& p, int) const
{
return static_cast<const float*>(p.vec+3);
}
};
typedef CGAL::Search_traits<float, PhotonicPoint, const float*, Construct_coord_iterator> Traits;
typedef CGAL::Orthogonal_incremental_neighbor_search<Traits> NN_incremental_search;
typedef NN_incremental_search::iterator NN_iterator;
typedef NN_incremental_search::Tree Tree;
struct PhotonMap_Impl
{
Tree tree;
PhotonMap_Impl(const PhotonAllocator& allocator) : tree()
{
int counter = 0, maxCount = allocator.GetAllocationCounter();
for(auto& list : allocator.GetPhotonLists())
{
int listLength = std::min((int)list.size(), maxCount - counter);
counter += listLength;
tree.insert(std::begin(list), std::begin(list) + listLength);
}
tree.build();
}
};
PhotonMap::PhotonMap(const PhotonAllocator& allocator)
{
impl = std::make_shared<PhotonMap_Impl>(allocator);
}
void PhotonMap::Sample(Vector3 where, float radius, int minCount, std::vector<const Photon*>& outPhotons)
{
NN_incremental_search search(impl->tree, PhotonicPoint(where));
int count = 0;
for(auto& p : search)
{
if((p.second > radius) && (count > minCount) || (count > 50))
break;
count++;
outPhotons.push_back(p.first.photon);
}
}
答案 0 :(得分:12)
答案不是提问的地方,但你的问题不是问题,而是CGAL的kd树很糟糕的说法。
读取地质数据模型的1.8个点,并计算每个点的50个最佳点,在我的Dell Precision,Windows7,64bit,VC10上具有以下性能:
此外,我想知道您的查询点在哪里,靠近曲面,或靠近骨架。
答案 1 :(得分:6)
我几个月前对快速KD树实施做了一些研究,我同意Anony-Mousse的观点,即质量(和图书馆的“权重”)变化很大。以下是我的一些发现:
kdtree2是一个鲜为人知且非常简单的KD树实现,我发现3D问题非常快,特别是如果你允许它复制和重新排序你的数据。此外,它很小,很容易合并和适应。 Here是一篇关于作者实施的文章(不要因标题中提到Fortran而被推迟)。这是我最终使用的库。我的同事们根据VLFeat's KD树和另一个我不记得的图书馆(许多FLANN,见下文)对3D点的速度进行了基准测试,并获胜。
FLANN的声誉很快,最近经常被使用和推荐。它针对的是更高维度的情况,它提供近似算法,但也用于处理3D问题的Point Cloud Library。
我没有尝试CGAL,因为我发现图书馆太重了。我同意CGAL有良好的声誉,但我觉得它偶尔会受到过分夸大的影响。
答案 2 :(得分:3)
根据我的经验,不幸的是,实施质量差异很大。但是,我从来没有看过CGAL实现。
k-d-tree的最坏情况通常是由于增量变化,它变得太不平衡,应该重新加载。
但是,通常这种树在您不知道数据分布时效率最高。
在您的情况下,听起来好像一个简单的基于网格的方法可能是最好的选择。如果需要,可以将纹理视为密集的2d网格。所以也许你可以找到一个2d投影,其中网格效果很好,然后与这个投影相交。
答案 3 :(得分:0)
根据MPL许可证查看ApproxMVBB库:
https://github.com/gabyx/ApproxMVBB:
kdTree实现应该与PCL(FLANN)相当,可能更快。 (我的实现对PCL的测试似乎更快!)
Diclaimer:我是这个图书馆的所有者,并不是这个图书馆声称还没有进行任何更快,更严肃的性能测试,但我正在使用这个图书馆成功的粒状刚体动力学,速度为王! 但是,这个库非常小,并且kdTree实现非常通用(参见示例)并且允许您自定义分裂heurstics和其他花哨的东西: - )。
实现了与nanoflann(直接数据访问等,通用数据,n维)类似的改进和注意事项......(参见KdTree.hpp)标题。
关于时间安排的一些更新:
示例kdTreeFiltering
包含一些小基准:
装有35947点的标准兔子装满了(开箱即用的回购中的完整工作示例):
结果:
<强> Bunny.txt 强>
Loaded: 35947 points
KDTree:: Exotic point traits , Vector3* + id, start: =====
KdTree build took: 3.1685ms.
Tree Stats:
nodes : 1199
leafs : 600
tree level : 11
avg. leaf data size : 29.9808
min. leaf data size : 0
max. leaf data size : 261
min. leaf extent : 0.00964587
max. leaf extent : 0.060337
SplitHeuristics Stats:
splits : 599
avg. split ratio (0,0.5] : 0.5
avg. point ratio [0,0.5] : 0.22947
avg. extent ratio (0,1] : 0.616848
tries / calls : 599/716 = 0.836592
Neighbour Stats (if computed):
min. leaf neighbours : 6
max. leaf neighbours : 69
avg. leaf neighbours : 18.7867
(Built with methods: midpoint, no split heuristic optimization loop)
Saving KdTree XML to: KdTreeResults.xml
KDTree:: Simple point traits , Vector3 only , start: =====
KdTree build took: 18.3371ms.
Tree Stats:
nodes : 1199
leafs : 600
tree level : 10
avg. leaf data size : 29.9808
min. leaf data size : 0
max. leaf data size : 306
min. leaf extent : 0.01
max. leaf extent : 0.076794
SplitHeuristics Stats:
splits : 599
avg. split ratio (0,0.5] : 0.448302
avg. point ratio [0,0.5] : 0.268614
avg. extent ratio (0,1] : 0.502048
tries / calls : 3312/816 = 4.05882
Neighbour Stats (if computed):
min. leaf neighbours : 6
max. leaf neighbours : 43
avg. leaf neighbours : 21.11
(Built with methods: midpoint, median,geometric mean, full split heuristic optimization)
Lucy.txt 模型有1400万点:
Loaded: 14027872 points
KDTree:: Exotic point traits , Vector3* + id, start: =====
KdTree build took: 3123.85ms.
Tree Stats:
nodes : 999999
leafs : 500000
tree level : 25
avg. leaf data size : 14.0279
min. leaf data size : 0
max. leaf data size : 159
min. leaf extent : 2.08504
max. leaf extent : 399.26
SplitHeuristics Stats:
splits : 499999
avg. split ratio (0,0.5] : 0.5
avg. point ratio [0,0.5] : 0.194764
avg. extent ratio (0,1] : 0.649163
tries / calls : 499999/636416 = 0.785648
(Built with methods: midpoint, no split heuristic optimization loop)
KDTree:: Simple point traits , Vector3 only , start: =====
KdTree build took: 7766.79ms.
Tree Stats:
nodes : 1199
leafs : 600
tree level : 10
avg. leaf data size : 11699.6
min. leaf data size : 0
max. leaf data size : 35534
min. leaf extent : 9.87306
max. leaf extent : 413.195
SplitHeuristics Stats:
splits : 599
avg. split ratio (0,0.5] : 0.297657
avg. point ratio [0,0.5] : 0.492414
avg. extent ratio (0,1] : 0.312965
tries / calls : 5391/600 = 8.985
Neighbour Stats (if computed):
min. leaf neighbours : 4
max. leaf neighbours : 37
avg. leaf neighbours : 12.9233
(Built with methods: midpoint, median,geometric mean, full split heuristic optimization)
注意解释!并查看示例File中使用的设置。 然而,与其他人的结果相比:14 *10⁶点的~3100ms非常光滑: - )
使用的处理器:英特尔®酷睿™i7 CPU 970 @ 3.20GHz×12,12GB Ram
答案 4 :(得分:-1)
如果kdtree对于小集很快,但是&#34;慢&#34;对于大型(> 100000?)集,您可能会遇到刷新处理器缓存的问题。如果前几个节点与很少使用的叶节点交错,那么您将适应处理器缓存中较少使用的节点。这可以通过最小化节点的大小和内存中节点的仔细布局来改进。但最终你将刷新相当数量的节点。你最终可以访问作为瓶颈的主存储器。
Valgrind告诉我,我的代码的一个版本减少了5%的指令,但我相信秒表,当它告诉我相同的输入慢约10%。我怀疑valgrind做一个完整的缓存模拟会告诉我正确的答案。
如果您是多线程的,您可能希望鼓励线程在类似区域中进行搜索以重用缓存...假定单个多核处理器 - 多个处理器可能需要相反的方法。
提示:在内存中打包的32位索引比64位指针多。