曲面重建技术在逆向工程、数据可视化、机器视觉、虚拟现实、医疗技术等领域中得到了广泛的应用 。 例如,在汽车、航空等工业领域中,复杂外形产品的设计仍需要根据手工模型,采用逆向工程的手段建立产品的数字化模型,根据测量数据建立人体以及骨骼和器官的计算机模型,在医学、定制生产等方面都有重要意义 。
除了上述传统的行业,随着新兴的廉价 RGBD 获取设备在数字娱乐行业的病毒式扩展,使得更多人开始使用点云来处理对象并进行工程应用 。 根据重建曲面和数据点云之间的关系,可将曲面重建分为两大类:插值法和逼近法。前者得到的重建曲面完全通过原始数据点,而后者则是用分片线性曲面或其他形式的曲面来逼近原始数据点,从而使得得到的重建曲面是原始点集的一个逼近曲面。
关联知识:
Search、KdTree、Octree
本教程说明如何使用移动最小二乘(MLS)曲面重构方法来平滑和重采样噪声数据。
使用统计分析很难消除某些数据不规则性(由较小的距离测量误差引起)。要创建完整的模型,必须考虑光滑的表面以及数据中的遮挡。在无法获取其他扫描的情况下,一种解决方案是使用重采样算法,该算法尝试通过周围数据点之间的高阶多项式插值来重新创建表面的缺失部分。通过执行重采样,可以纠正这些小的错误,并且可以将多个扫描记录在一起执行平滑操作合并成同一个点云。
在上图的左侧,我们在包含两个配准点云的数据集中看到了配准后的效果及表面法线估计。由于对齐错误,所产生的法线有噪声。在右侧,使用移动最小二乘法对表面法线估计进行平滑处理后,在同一数据集中看到了该效果。绘制每个点的曲率,作为重新采样前后特征值关系的度量,我们得到:
#include
#include
//#include
#include
#include
int
main ()
{
// Load input file into a PointCloud with an appropriate type
pcl::PointCloud::Ptr cloud (new pcl::PointCloud ());
// Load bun0.pcd -- should be available with the PCL archive in test
pcl::io::loadPCDFile ("table_scene_lms400_downsampled.pcd", *cloud);
// Create a KD-Tree
//pcl::search::KdTree::Ptr tree (new pcl::search::KdTree);
pcl::search::KdTree::Ptr tree(new pcl::search::KdTree()); // kd树对象
// Output has the PointNormal type in order to store the normals calculated by MLS
pcl::PointCloud mls_points;
// Init object (second point type is for the normals, even if unused)
pcl::MovingLeastSquares mls;
mls.setComputeNormals (true);
// Set parameters
mls.setInputCloud (cloud);
mls.setPolynomialOrder (2);
mls.setSearchMethod (tree);
mls.setSearchRadius (0.03);
// Reconstruct
mls.process (mls_points);
// Save output
pcl::io::savePCDFile ("table_scene_lms400_downsampled-mls.pcd", mls_points);
}
右边是重建后,会发现比左边更平整
学习如何为平面模型上的点集提取其对应的凹多边形的例子,该例首先从点云中提取平面模型,再通过该估计的平面模型系数从滤波后的点云投影一组点集形成点云,最后为投影后的点云计算其对应的二维凸(凹)多边形。
#include
#include
#include
#include
#include
#include
#include
#include
#include
int
main ()
{
pcl::PointCloud::Ptr cloud (new pcl::PointCloud), cloud_filtered (new pcl::PointCloud), cloud_projected (new pcl::PointCloud);
pcl::PCDReader reader;
reader.read ("table_scene_mug_stereo_textured.pcd", *cloud);
// Build a filter to remove spurious NaNs and scene background
pcl::PassThrough pass;
pass.setInputCloud (cloud);
pass.setFilterFieldName ("z");
pass.setFilterLimits (0, 1.1);
pass.filter (*cloud_filtered);
std::cerr << "PointCloud after filtering has: " << cloud_filtered->size () << " data points." << std::endl;
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
// Create the segmentation object
pcl::SACSegmentation seg;
// Optional
seg.setOptimizeCoefficients (true);
// Mandatory
seg.setModelType (pcl::SACMODEL_PLANE);
seg.setMethodType (pcl::SAC_RANSAC);
seg.setDistanceThreshold (0.01);
seg.setInputCloud (cloud_filtered);
seg.segment (*inliers, *coefficients);
// Project the model inliers
pcl::ProjectInliers proj;
proj.setModelType (pcl::SACMODEL_PLANE);
proj.setInputCloud (cloud_filtered);
proj.setIndices (inliers);
proj.setModelCoefficients (coefficients);
proj.filter (*cloud_projected);
// Create a Convex Hull representation of the projected inliers
pcl::PointCloud::Ptr cloud_hull (new pcl::PointCloud);
pcl::ConvexHull chull;
chull.setInputCloud (cloud_projected);
chull.reconstruct (*cloud_hull);
std::cerr << "Convex hull has: " << cloud_hull->size () << " data points." << std::endl;
pcl::PCDWriter writer;
writer.write ("table_scene_mug_stereo_textured_hull.pcd", *cloud_hull, false);
return (0);
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
int
main ()
{
pcl::PointCloud::Ptr cloud (new pcl::PointCloud),
cloud_filtered (new pcl::PointCloud),
cloud_projected (new pcl::PointCloud);
pcl::PCDReader reader;
reader.read ("table_scene_mug_stereo_textured.pcd", *cloud);
// Build a filter to remove spurious NaNs and scene background
pcl::PassThrough pass;
pass.setInputCloud (cloud);
pass.setFilterFieldName ("z");
pass.setFilterLimits (0, 1.1);
pass.filter (*cloud_filtered);
std::cerr << "PointCloud after filtering has: "
<< cloud_filtered->size () << " data points." << std::endl;
pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
// Create the segmentation object
pcl::SACSegmentation seg;
// Optional
seg.setOptimizeCoefficients (true);
// Mandatory
seg.setModelType (pcl::SACMODEL_PLANE);
seg.setMethodType (pcl::SAC_RANSAC);
seg.setDistanceThreshold (0.01);
seg.setInputCloud (cloud_filtered);
seg.segment (*inliers, *coefficients);
std::cerr << "PointCloud after segmentation has: "
<< inliers->indices.size () << " inliers." << std::endl;
// Project the model inliers
pcl::ProjectInliers proj;
proj.setModelType (pcl::SACMODEL_PLANE);
// proj.setIndices (inliers);
proj.setInputCloud (cloud_filtered);
proj.setModelCoefficients (coefficients);
proj.filter (*cloud_projected);
std::cerr << "PointCloud after projection has: "
<< cloud_projected->size () << " data points." << std::endl;
// Create a Concave Hull representation of the projected inliers
pcl::PointCloud::Ptr cloud_hull (new pcl::PointCloud);
pcl::ConcaveHull chull;
chull.setInputCloud (cloud_projected);
chull.setAlpha (0.1);
chull.reconstruct (*cloud_hull);
std::cerr << "Concave hull has: " << cloud_hull->size ()
<< " data points." << std::endl;
pcl::PCDWriter writer;
writer.write ("table_scene_mug_stereo_textured_hull.pcd", *cloud_hull, false);
return (0);
}
本小节介绍了怎样使用贪婪投影三角化算法对有向点云进行三角化,具体方法是先将有向点云投影到某一局部二维坐标平面内,再在坐标平面内进行平面内的三角化,再根据平面内三位点的拓扑连接关系获得一个三角网格曲面模型。贪婪投影三角化算法原理是处理一系列可以使网格“生长扩大”的点(边缘点)延伸这些点直到所有符合几何正确性和拓扑正确性的点都被连上。该算法的优点是可以处理来自一个或者多个扫描仪扫描得到并且有多个连接处的散乱点云。但该算法也有一定的局限性,它更适用于采样点云来自于表面连续光滑的曲面且点云密度
变化比较均匀的情况。该算法的三角化过程是局部进行的,首先沿着一点的法线将该点投影到局部二维坐标平面内并连接其他悬空点,然后再进行下一点。所以这里设置如下参数:
#include
#include
#include // for KdTree
#include
#include
#include
#include
int
main ()
{
// Load input file into a PointCloud with an appropriate type
pcl::PointCloud::Ptr cloud (new pcl::PointCloud);
pcl::PCLPointCloud2 cloud_blob;
pcl::io::loadPCDFile ("bunny.pcd", cloud_blob);
pcl::fromPCLPointCloud2 (cloud_blob, *cloud);
//* the data should be available in cloud
// Normal estimation*
pcl::NormalEstimation n;
pcl::PointCloud::Ptr normals (new pcl::PointCloud);
pcl::search::KdTree::Ptr tree (new pcl::search::KdTree);
tree->setInputCloud (cloud);
n.setInputCloud (cloud);
n.setSearchMethod (tree);
n.setKSearch (20);
n.compute (*normals);
//* normals should not contain the point normals + surface curvatures
// Concatenate the XYZ and normal fields*
pcl::PointCloud::Ptr cloud_with_normals (new pcl::PointCloud);
pcl::concatenateFields (*cloud, *normals, *cloud_with_normals);
//* cloud_with_normals = cloud + normals
// Create search tree*
pcl::search::KdTree::Ptr tree2 (new pcl::search::KdTree);
tree2->setInputCloud (cloud_with_normals);
// Initialize objects
pcl::GreedyProjectionTriangulation gp3;
pcl::PolygonMesh triangles;
// Set the maximum distance between connected points (maximum edge length)
gp3.setSearchRadius (0.02);
// Set typical values for the parameters
gp3.setMu (2.5);
gp3.setMaximumNearestNeighbors (100);
gp3.setMaximumSurfaceAngle(M_PI/4); // 45 degrees
gp3.setMinimumAngle(M_PI/18); // 10 degrees
gp3.setMaximumAngle(2*M_PI/3); // 120 degrees
gp3.setNormalConsistency(false);
// Get result
gp3.setInputCloud (cloud_with_normals);
gp3.setSearchMethod (tree2);
gp3.reconstruct (triangles);
// Additional vertex information
std::vector parts = gp3.getPartIDs();
std::vector states = gp3.getPointStates();
// Finish
pcl::visualization::PCLVisualizer viewer("Triangulation");
viewer.setBackgroundColor(0.0, 0.0, 0.0);
viewer.addPointCloud(cloud, "input_cloud");
viewer.addPolygonMesh(triangles, "triangles", 0);
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1.0, 0.0, 0.0, "input_cloud");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0.0, 1.0, 0.0, "triangles");
viewer.spin();
return (0);
}