在本教程中,我们将学习如何在平面模型中使用RandomSampleConsensus
来获得适合该模型的云。
#理论入门
RANdom SAmple Consensus
的缩写是RANSAC
,它是一种迭代方法,用于从包含异常值的一组数据中估计数学模型的参数。该算法由Fischler和Bolles于1981年发表.RANSAC
算法假设我们所看到的所有数据都是由内点(inliers)
和外点(outliers)
构成的。Inlier
可以用具有一组特定参数值的模型来解释,而异常值在任何情况下都不适合该模型。另一个必要的假设是可以从数据中最优地估计所选模型的参数的过程。
???????以下来自Wikipedia???????
RANSAC算法的输入是一组观测数据值,一个可以解释或适合观测值的参数化模型,以及一些置信度参数。
RANSAC通过迭代选择原始数据的随机子集来实现其目标。这些数据是假设的内点,然后这个假设测试如下:
一个模型拟合到假设的内部,即模型的所有自由参数从内部重建。
所有其他数据然后对拟合模型进行测试,如果一个点适合估计模型,也被认为是一个假设inlier。
如果足够多的点被归类为假想内点,则估计的模型是相当好的。
这个模型是从所有假想的内点估算的,因为它只是从最初的一组假设的内点估计出来的。
最后,通过估计内部相对于模型的误差来评估模型。
这个过程被重复固定的次数,每次产生一个被拒绝的模型,因为太少的点被归类为inlier
或者一个改进的模型以及一个相应的误差测量。在后一种情况下,如果它的误差低于上次保存的模型,我们保留精化模型(refined model)。
RANSAC的一个优点是它能够对模型参数进行鲁棒估计,即,即使在数据集中存在大量的异常值时,它也可以高准确度地估计参数。 RANSAC的缺点是计算这些参数所花的时间没有上限。 当计算出的迭代次数有限时,得到的解决方案可能不是最优的,甚至可能不是一个很好的适合数据的解决方案。 通过这种方式,RANSAC提供了一个权衡。 通过计算更多的迭代次数,增加了产生合理模型的概率。 RANSAC的另一个缺点是需要设置特定于问题的阈值。
RANSAC只能估计一个特定数据集的一个模型。至于任何一个模型的方法,当两个(或更多)模型存在时,RANSAC可能找不到任一个。
_images / random_sample_example1.png _images / random_sample_example2.png
左侧和右侧的图片显示了RANSAC算法在二维数据集上的简单应用。我们左边的图像是包含内点和外点的数据集的直观表示。我们右侧的图像以红色显示所有异常值,并以蓝色显示异常值。蓝线是RANSAC完成的工作的结果。在这种情况下,我们试图拟合数据的模型是一条线,它看起来非常适合我们的数据。
???????以上来自Wikipedia???????
#代码
用你最喜欢的编辑器创建一个random_sample_consensus.cpp
文件,并在里面放置以下内容:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
boost::shared_ptr
simpleVis (pcl::PointCloud::ConstPtr cloud)
{
// --------------------------------------------
// -----Open 3D viewer and add point cloud-----
// --------------------------------------------
boost::shared_ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
viewer->setBackgroundColor (0, 0, 0);
viewer->addPointCloud (cloud, "sample cloud");
viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "sample cloud");
//viewer->addCoordinateSystem (1.0, "global");
viewer->initCameraParameters ();
return (viewer);
}
int
main(int argc, char** argv)
{
// initialize PointClouds
pcl::PointCloud::Ptr cloud (new pcl::PointCloud);
pcl::PointCloud::Ptr final (new pcl::PointCloud);
// populate our PointCloud with points
cloud->width = 500;
cloud->height = 1;
cloud->is_dense = false;
cloud->points.resize (cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size (); ++i)
{
if (pcl::console::find_argument (argc, argv, "-s") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0)
{
cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0);
cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0);
if (i % 5 == 0)
cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0);
else if(i % 2 == 0)
cloud->points[i].z = sqrt( 1 - (cloud->points[i].x * cloud->points[i].x)
- (cloud->points[i].y * cloud->points[i].y));
else
cloud->points[i].z = - sqrt( 1 - (cloud->points[i].x * cloud->points[i].x)
- (cloud->points[i].y * cloud->points[i].y));
}
else
{
cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0);
cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0);
if( i % 2 == 0)
cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0);
else
cloud->points[i].z = -1 * (cloud->points[i].x + cloud->points[i].y);
}
}
std::vector inliers;
// created RandomSampleConsensus object and compute the appropriated model
pcl::SampleConsensusModelSphere::Ptr
model_s(new pcl::SampleConsensusModelSphere (cloud));
pcl::SampleConsensusModelPlane::Ptr
model_p (new pcl::SampleConsensusModelPlane (cloud));
if(pcl::console::find_argument (argc, argv, "-f") >= 0)
{
pcl::RandomSampleConsensus ransac (model_p);
ransac.setDistanceThreshold (.01);
ransac.computeModel();
ransac.getInliers(inliers);
}
else if (pcl::console::find_argument (argc, argv, "-sf") >= 0 )
{
pcl::RandomSampleConsensus ransac (model_s);
ransac.setDistanceThreshold (.01);
ransac.computeModel();
ransac.getInliers(inliers);
}
// copies all inliers of the model computed to another PointCloud
pcl::copyPointCloud(*cloud, inliers, *final);
// creates the visualization object and adds either our orignial cloud or all of the inliers
// depending on the command line arguments specified.
boost::shared_ptr viewer;
if (pcl::console::find_argument (argc, argv, "-f") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0)
viewer = simpleVis(final);
else
viewer = simpleVis(cloud);
while (!viewer->wasStopped ())
{
viewer->spinOnce (100);
boost::this_thread::sleep (boost::posix_time::microseconds (100000));
}
return 0;
}
#说明
以下源代码初始化两个PointCloud,并用点填充其中的一个。这些点中的大部分根据模型被放置在云中,但其中的一小部分(1/5)被赋予任意位置。
// initialize PointClouds
pcl::PointCloud::Ptr cloud (new pcl::PointCloud);
pcl::PointCloud::Ptr final (new pcl::PointCloud);
// populate our PointCloud with points
cloud->width = 500;
cloud->height = 1;
cloud->is_dense = false;
cloud->points.resize (cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size (); ++i)
{
if (pcl::console::find_argument (argc, argv, "-s") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0)
{
cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0);
cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0);
if (i % 5 == 0)
cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0);
else if(i % 2 == 0)
cloud->points[i].z = sqrt( 1 - (cloud->points[i].x * cloud->points[i].x)
- (cloud->points[i].y * cloud->points[i].y));
else
cloud->points[i].z = - sqrt( 1 - (cloud->points[i].x * cloud->points[i].x)
- (cloud->points[i].y * cloud->points[i].y));
}
else
{
cloud->points[i].x = 1024 * rand () / (RAND_MAX + 1.0);
cloud->points[i].y = 1024 * rand () / (RAND_MAX + 1.0);
if( i % 2 == 0)
cloud->points[i].z = 1024 * rand () / (RAND_MAX + 1.0);
else
cloud->points[i].z = -1 * (cloud->points[i].x + cloud->points[i].y);
}
}
接下来,我们创建一个int值向量,它可以存储我们的PointCloud中的内点的位置,现在我们可以使用来自输入云的平面或球面模型来构建RandomSampleConsensus对象。
std::vector inliers;
// created RandomSampleConsensus object and compute the appropriated model
pcl::SampleConsensusModelSphere::Ptr
model_s(new pcl::SampleConsensusModelSphere (cloud));
pcl::SampleConsensusModelPlane::Ptr
model_p (new pcl::SampleConsensusModelPlane (cloud));
if(pcl::console::find_argument (argc, argv, "-f") >= 0)
{
pcl::RandomSampleConsensus ransac (model_p);
ransac.setDistanceThreshold (.01);
ransac.computeModel();
ransac.getInliers(inliers);
}
else if (pcl::console::find_argument (argc, argv, "-sf") >= 0 )
{
pcl::RandomSampleConsensus ransac (model_s);
ransac.setDistanceThreshold (.01);
ransac.computeModel();
ransac.getInliers(inliers);
}
最后一点代码将所有适合我们模型的点复制到另一个云,然后在查看器中显示该云或原始云。
// copies all inliers of the model computed to another PointCloud
pcl::copyPointCloud(*cloud, inliers, *final);
// creates the visualization object and adds either our orignial cloud or all of the inliers
// depending on the command line arguments specified.
boost::shared_ptr viewer;
if (pcl::console::find_argument (argc, argv, "-f") >= 0 || pcl::console::find_argument (argc, argv, "-sf") >= 0)
viewer = simpleVis(final);
else
viewer = simpleVis(cloud);
还有一些额外的代码与3D查看器中PointClouds的显示有关,但我不打算在这里解释。
#编译和运行程序
将下面的行添加到您的CMakeLists.txt
文件中:
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project(random_sample_consensus)
find_package(PCL 1.2 REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
link_directories(${PCL_LIBRARY_DIRS})
add_definitions(${PCL_DEFINITIONS})
add_executable (random_sample_consensus random_sample_consensus.cpp)
target_link_libraries (random_sample_consensus ${PCL_LIBRARIES})
在生成可执行文件之后,可以运行它。简单地做:
./random_sample_consensus
有一个查看器窗口显示,向您显示我们创建的原始PointCloud(异常值)。
_images / ransac_outliers_plane.png
键盘上敲击r
以缩放和居中查看器。 然后,您可以点击并拖动来旋转视图。 你可以说这个PointCloud几乎没有什么组织,而且它包含了许多异常值。 按键盘上的q
将关闭查看器并结束程序。 现在如果你用下面的参数运行程序:
./random_sample_consensus -f
程序将只显示原始PointCloud的索引,它们满足我们在查看器中通过RandomSampleConsens
所找到的特定模型(在这种情况下为平面)。
_images / ransac_inliers_plane.png
再次按r
来缩放和居中视图,然后单击并用鼠标拖动以围绕云旋转。在这个PointCloud中,你可以看到在平面模型中不再有任何点。点击q
退出查看器和程序。
在这个程序中也有一个使用球体的例子。如果你运行它:
./random_sample_consensus -s
它会产生并显示一个球形的云和一些outliers
。
_images/ransac_outliers_sphere.png
然后当你运行以下程序时:
./random_sample_consensus -sf
它将向您显示将RandomSampleConsensus
应用于具有球形模型的此数据集的结果。
How to use Random Sample Consensus model