PCL专栏目录及须知
移动立方体重建是一种体素级的重建方法,即通过
判断原始点云表面(等值面)与每个体素块顶点的相对位置,
查表计算得到与原始点云表面相交的各体素边及三角面片信息,
线性插值修正交点,逼近等值面,
组合形成三角网格。
主要应用于医学领域的可视化场景,例如CT扫描和MRI扫描的3D重建等。
1.Marching Squares(MC移动立方体重建的2D引入)
先以2D的该算法思想举例引入:
(1)待分割对象如下:
(2)对待分割对象二维栅格化。
(3)记录栅格化顶点相对位置信息,即哪些顶点在对象内部(红色),哪些在对象外部(蓝色)。
(4)计算多边形都穿过哪些栅格线。并以一内部对象(红色)及一外部对象(蓝色)的连线的中间位置(1/2处)作为记录值(紫色顶点)。
(5)排序并连接紫色点,得到原始表面的近似值(紫色线)。
(6)精确计算紫色点位置。即计算表面与存在紫色点的各边的详细交点,得到准确表面。
2.Marching cubes(MC移动立方体3D重建)
(1)体素化点云。得到由许多个小立方体组成的体素网格。
等值面的引入:
指三维空间中具有相同数值的点构成的表面。即下文代码中iso_level的值。
(2)判断每个小体素块的各个顶点是否在原始点云内部(顶点只有内外两种可能性)。
比较等值面的值(iso_level)与小立方体8个顶点的数据值:
1)在原始点云表面内部:顶点数据值 高于或等于 等值面的值(iso_level)。
2)在原始点云表面外部:顶点数据值 低于 等值面的值(iso_level)。
(3)整理每个体素块的基本构型状态。
每个体素块由八个顶点组成,1个顶点内外一种状态,8个顶点则有2^8 = 256种状态。根据旋转、映射不变等特性,将体素256种状态归纳为一下15种基本构型:
(4)根据顶点状态设置“边查找表”及“三角形查找表”。
边查找表:edgeTable查找表。例如,0x80c=1000 0000 1100,意味着体素单元的边11、3、2和等值面相交。
三角形查找表:triTable。用于生成对应三角面片的组成情况。
(5)遍历计算每个体素块与原始点云相交的状态。
1)通过边查找表判断等值面和体素单元哪一条边相交
以如下情况为例:
<1>假如某体素块,仅下方的顶点3的值小于等值面值,其他顶点上的值都大于等值面值,即顶点3标记为1、其他顶点标记为0。
<2>用进制表示该体素块的体素顶点状态,二进制的0000 1000或者十进制的8。
<3>查找“边查找表”,即edgeTable[8] =0x80c=1000 0000 1100,得到体素单元的边11、3、2和等值面相交。
2)通过triTable表生成对应三角面片的组成情况
仍以上方情况为例:
triTable查找表得到triTable[8] = {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}。即该顶点状态可以生成三角面,该三角面的3个顶点为边3、11、2和等值面相交的交点。
(7)线性插值计算各交点的位置。
已知各条可与原始点云相交的体素块的边,通过线性插值,计算每个交点在该体素边的位置。
(8)遍历所有体素块,得到最终结果。
3.Hoppe法改进
在点云表面重建中,无法像3D医学图像那样直接获取Cube顶点的数据值,Hoppe等人使用Cube顶点到点云的有符号距离(signed distance function)来表示该数据值。
(1)iso_level一般设置为0。
(2)计算Cube顶点到点云的有符号距离。
首先找到距离Cube顶点最近的数据点n,然后计算Cube顶点到表面在点n处的切平面的距离。实际是计算Cube顶点到点n的向量与切平面法向量的点乘,计算结果保留符号。
(3)使用MC方法确定三角面。
4.使用场景
三维点云重建
5.关键函数
(1)等值面的值iso_level
segmentation->setIsoLevel(0.0f);
(2)移动立方体网格分辨率
segmentation->setGridResolution(50, 50, 50);
(3)定义在点云的边框和网格限制之间的网格内应该保留多少自由空间,不影响网格的分辨率,只是相应地改变体素大小。
segmentation->setPercentageExtendGrid(0.0f);
6.完整代码
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/point_types.h>
#include <pcl/kdtree/kdtree_flann.h>
#include <pcl/features/normal_3d.h>
#include <pcl/surface/marching_cubes_HOPPE.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/thread/thread.hpp>
int main()
{
/****************HOPPE移动立方体重建********************/
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>); // 源点云
pcl::io::loadPCDFile("D:/code/csdn/data/bunny2.pcd", *cloud);
// 计算法线
pcl::NormalEstimation<pcl::PointXYZRGB, pcl::Normal> n;
pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZRGB>);
n.setInputCloud(cloud);
n.setSearchMethod(tree);
n.setKSearch(20); // k邻域搜索范围
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
n.compute(*normals);
pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr cloudNormals(new pcl::PointCloud<pcl::PointXYZRGBNormal>); // 连接点云及法向量
pcl::concatenateFields(*cloud, *normals, *cloudNormals);
// HOPPE移动立方体重建
pcl::MarchingCubes<pcl::PointXYZRGBNormal>::Ptr segmentation(new pcl::MarchingCubesHoppe<pcl::PointXYZRGBNormal>);
pcl::search::KdTree<pcl::PointXYZRGBNormal>::Ptr tree2(new pcl::search::KdTree<pcl::PointXYZRGBNormal>);
tree2->setInputCloud(cloudNormals);
segmentation->setInputCloud(cloudNormals);
segmentation->setIsoLevel(0.0f); // 等值面的iso_level
segmentation->setGridResolution(50, 50, 50); // 移动立方体网格分辨率
segmentation->setPercentageExtendGrid(0.0f); // 定义在点云的边框和网格限制之间的网格内应该保留多少自由空间,不影响网格的分辨率,只是相应地改变体素大小。
pcl::PolygonMesh triangles; // 存储最终三角化的网格模型
segmentation->reconstruct(triangles);
pcl::io::savePLYFile("D:/code/csdn/data/bunny5.ply", triangles);
/****************展示********************/
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer(new pcl::visualization::PCLVisualizer("GridProjection"));
viewer->addPointCloud(cloud, "cloud");
boost::shared_ptr<pcl::visualization::PCLVisualizer> viewer0(new pcl::visualization::PCLVisualizer("GridProjection0"));
viewer0->setBackgroundColor(255, 255, 255);
viewer0->addPolygonMesh(triangles, "triangles");
viewer0->setRepresentationToWireframeForAllActors(); // 网格模型以线框图模式显示
while (!viewer->wasStopped() || !viewer0->wasStopped())
{
viewer->spinOnce(100);
viewer0->spinOnce(100);
boost::this_thread::sleep(boost::posix_time::microseconds(100000));
}
return (0);
}
7.结果展示
原始点云
重建后的网格信息