我的新博客:http://ryuzhihao.cc/
本文在我的新博客中的链接为:http://ryuzhihao.cc/?p=725
从Kinect获取植物的三维点云是一件很轻松的事情。
那么,能否利用这些点云,重建植物的三维模型?如果可以实现,将意味着我们只需用扫描到的植物点云,就可以快速重建出植物的真实三维模型。与手工建模相比,这将更加接近真实的植物形状。
本文参考的文献主要是下面这一篇:
Knowledge and Heuristic Based Modeling of Laser-Scanned Trees. Hui Xu,Nathan Gossett, Baoquan Chen.
在阅读上述参考文献后,我实现了其中的核心算法。其流程图和最终效果如下:
图1:本文程序的效果以及流程。(a) 输入的原始点云数据,共计33956个点。(b) 分割后,形成的红色的树叶区域和白色的枝干区域。(c) 将枝干点云划分为若干个bin。(d) 将bins得到树的骨架。(e)树木的最终重建效果。
实验数据可采用Kinect采集的单视角点云(single scan)。输入的点云如图2。
图2:输入的点云数据(共计33956pts)
首先,假定我们已经知道了根节点root的位置。根节点root可以是人工选定的、也可以来自竖直方向最低的点。
然后,从根节点开始,不断加入相邻的点到一个枝干点集合(vector
建议使用基于kd-Tree进行范围搜索(range search),在30000的数据量上计算,总耗时约为0.2秒。
下面是该部分的伪代码和分离的效果图:
假设点云总数为N,且存储在vector m_vertexs中。
定义
vector m_branchPts; // 枝干点云
bool m_visited[N]; // 标记各点是否已经被访问过
int m_rootIndex; // 根节点在m_vertexs中的索引
初始化
foreach i = 0,1,...N-1
m_visited[i] = false;
// 将根节点放入
m_visited[m_rootIndex] = true;
m_branchPts.push_back(m_vertexs[m_rootIndex]);
算法循环:不断加入新的点到m_branchPts中
double radius = 0.3px; // 搜索半径
for(int i=0; i neighbors =
KdTree_RangeSearch(radius,m_branchPts[i]);
foreach k = 0,1,....,neighbors.size()
{
if(m_visited[neighbors[k]] == false)
{
m_branchPts.push_back(neighbors[k]);
m_visited[neighbors[k]] = true;
}
}
}
图3:分离枝干点云和树冠点云。红色为叶片、白色为树干。
在第二步,我们已经得到了一个仅仅包含枝干部分的点云集合(即vector
首先,将枝干部分的点云集合m_branchPts中的所有点,与其相邻的点连接起来,形成一个连通图(这里仍然可以定义“相邻”为间距0.3px)。
然后,我们需要一种单源点最短路径算法来寻找从根节点到其他所有结点的最短路径长度。当然使用Dijkstra最短路径算法是完全可行的。
一旦最短路径长度确立了,我们可以将所有结点按照与root的距离划分为若干个区域,将距离长度相近的点加入到一个Bin中。可以认为:bin就是一段最小的枝元。
图4:将枝干部分点云划分为若干个bins
图5:划分后的若干个bins和树叶区域点云的效果
对于刚求解出来的每一个bin,都计算其中点。取每个bin的中点作为骨架图的结点,通过对连通图的深度优先搜索+KdTree的最邻近查询的策略,可以连接相邻的结点形成骨架。
同时,对初步连接成的骨架图,若存在环路,则可应用Prim算法可得到其最小生成树。骨架的最终效果图如下:
图6:树的骨架图(红色为结点,黄色为连线,白色为原枝干区域点云)
在得到skeleton之后,工作就变得简单了。用一个Cylinder或者三角网格的枝段模型将骨架图的每一段显示出来,就能得到最终的三维树模型。其效果如下:
图7:树重建的最终效果。