RANSAC(RAndom SAmple Consensus,随机采样一致)是一种随机参数估计算法,常常应用于二维图像的拟合、分割等等,由于是估计数学模型参数的迭代算法,因此也被用于三维平面、球的估计,这次笔者将主要介绍其算法原理及在点云拟合、分割及粗配准的代码应用分析,希望能够对读者们的工程研究有所帮助。
1. 算法详解
RANSAC算法由Fischler和Bolles于1981年提出,是一种从数据集合中迭代稳健估计模型参数的方法。该算法的基本思想是不断地从数据集合中随机抽取样本集,寻求支持更多局内点的模型参数,然后利用模型余集检验获得的模型参数,通过一定次数的迭代来达到最大的一致性概率,并将该采样样本集作为理解的样本集,且参数解的正确性由样本余集检验支撑。其中数据集合中包含正确数据(即内点inliers)和异常数据(外点outliers)。同时RANSAC假设:在给定一组含有少部分“内点”的数据,存在一个程序可以估计出符合“内点”的模型。
RANSAC的算法比较简单,主要有以下几步:
1)给定一个数据集SSS,从中选择建立模型所需的最小样本数(例如直线最少由两个点确定,所以最小样本数是2,而平面可以根据不共线三点确定,所以最小样本数为3),记选择数据集为SSS;
2)使用选择的数据集SSS计算得到一个数学模型MMM;
3)用计算的模型MMM去测试数据集中剩余的点,如果测试的数据点在误差允许的范围内,则将该数据点判为内点(inlier),否则判为外点(outlier),记所有内点组成的数据集为S∗S^*S∗,表示为S1S_1S1的一致性集合;
4)比较当前模型和之前推出的最好的模型的“内点”的数量,记录最大“内点”数量时模型参数和“内点”数量;
5)重复1-4步,直到迭代结束或者当前模型已经足够好了(“内点数目大于设定的阈值”);
这里的迭代次数可以通过理论推算获得。在一定的置信概率下,其最小样本数SSS与至少取得一个良性采样子集的概率PPP满足:
P=1−(1−tS)mP=1-(1-t^{S})^mP=1−(1−tS)m
其中,t=内点数内点数+外点数t=\frac{内点数}{内点数+外点数}t=内点数+外点数内点数表示为数据集合中内点概率,NNN是计算模型参数需要的最小数据量。
因此,在基本子集的在迭代次数内,至少有一次采样使得采样子集内的NNN个点均为内点,从而保证在迭代次数中,至少存在一次采样能够取得目标函数的最大值,因此终止循环条件应满足:
m≥ln(1−P)ln(1−1N)m\ge \frac{ln(1-P)}{ln(1-1^N)}m≥ln(1−1N)ln(1−P)
2. 算法应用
2.1 拟合
因此,利用上述思想最开始可以进行拟合数据,这里给出C++的点云直线拟合代码及解析:
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/visualization/cloud_viewer.h>
using namespace std;
int main() {
//----------------读取点云数据---------------------
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PCDReader reader;
reader.read<pcl::PointXYZ>("L.pcd", *cloud);
//---------------创建拟合模型---------------------
pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
//inliers用来存储直线上点的索引
pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
//创建一个分割器对象
pcl::SACSegmentation<pcl::PointXYZ> seg;
seg.setModelType(pcl::SACMODEL_LINE); //设置目标几何形状
seg.setMethodType(pcl::SAC_RANSAC); //拟合方法:随机采样法
seg.setDistanceThreshold(0.05); //设置误差容忍范围(即阈值)
seg.setMaxIterations(500); //最大迭代次数
seg.setInputCloud(cloud); //输入点云
seg.segment(*inliers, *coefficients); //拟合点云
//拟合直线的模型系数为:coefficients
//--------------提取拟合的直线------------------
pcl::PointCloud<pcl::PointXYZ>::Ptr line(new pcl::PointCloud<pcl::PointXYZ>);
pcl::ExtractIndices<pcl::PointXYZ> extract; //创建点云提取对象
extract.setInputCloud(cloud); //设置输入点云
extract.setIndices(inliers); //设置分割后的内点为需要提取的点集
extract.setNegative(false); //false提取内点, true提取外点
extract.filter(*line);
//-----------------点云可视化---------------
pcl::visualization::PCLVisualizer viewer;
viewer.addPointCloud(cloud, "cloud"); // 加载比对点云
pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> line_color(line, 255, 0, 0);
viewer.addPointCloud(line, line_color, "line");
viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "line");
//可视化拟合出来的直线
viewer.addLine(*coefficients, "line");
viewer.spin();
return 0;
}
2.2 分割
分割也是利用拟合数据的思想,代码类似,这里给出主要部分的C++点云直线分割代码及解析:
//--------------------RANSAC分割直线--------
void fitMultipleLines(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, std::vector<pcl::ModelCoefficients>& lineCoff)
{
pcl::PointIndices::Ptr inliers(new pcl::PointIndices());
pcl::SACSegmentation<pcl::PointXYZ> seg; // 创建拟合对象
// 设置对估计模型参数进行优化处理
seg.setOptimizeCoefficients(true);
// 设置拟合模型为直线模型
seg.setModelType(pcl::SACMODEL_LINE);
// 设置拟合方法为RANSAC
seg.setMethodType(pcl::SAC_RANSAC);
// 设置最大迭代次数
seg.setMaxIterations(1000);
seg.setDistanceThreshold(0.1); //判断是否为模型内点的距离阈值
int i = 0, nr_points = cloud->points.size();
int k = 0;
while (k < 5 && cloud->points.size() > 0.1 * nr_points)// 从0循环到5执行6次,并且每次cloud的点数必须要大于原始总点数的0.1倍
{
pcl::ModelCoefficients coefficients;
seg.setInputCloud(cloud); // 输入点云
seg.segment(*inliers, coefficients); //内点的索引
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_line(new pcl::PointCloud<pcl::PointXYZ>);
pcl::PointCloud<pcl::PointXYZ>::Ptr outside(new pcl::PointCloud<pcl::PointXYZ>);
if (inliers->indices.size() > 20) // 小于20则该直线不合格
{
// 将参数保存进vector中
lineCoff.push_back(coefficients);
// 创建点云提取对象
pcl::ExtractIndices<pcl::PointXYZ> extract;
extract.setInputCloud(cloud);
extract.setIndices(inliers);
// 设置为false,表示提取内点
extract.setNegative(false);
extract.filter(*cloud_line);
// true提取外点(该直线之外的点)
extract.setNegative(true);
extract.filter(*outside);
// 将cloud_f中的点云赋值给cloud
cloud.swap(outside);
}
else
{
PCL_ERROR("Could not estimate a line model for the given dataset.\n");
break;
}
std::stringstream ss;
ss << "line_" << i + 1 << ".pcd";
pcl::PCDWriter writer;
writer.write<pcl::PointXYZ>(ss.str(), *cloud_line, false);
i++;
k++;
}
}
cout << "一共拟合出" << LinesCoefficients.size() << "条直线“;
2.3 粗配准
同理,在点云粗配准中也是通过对应点集中随机选取3个对应点对,并求解刚体变换矩阵。然后计算对应点中剩余点对,在上一步所求的刚体变换矩阵的作用下点对的距离误差,若其中一点对的距离误差小于设定的阅值误差,则该点为样本内点,否则为样本外点,并统计前者数目。最后,达到指定选代次数。这里给出C++的点云粗配准代码及解析:
//--------------------RANSAC点云配准--------
pcl::SampleConsensusPrerejective<PointT, PointT, pcl::FPFHSignature33> r_sac;
r_sac.setInputSource(source); // 源点云
r_sac.setInputTarget(target); // 目标点云
r_sac.setSourceFeatures(source_fpfh); // 源点云FPFH特征
r_sac.setTargetFeatures(target_fpfh); // 目标点云FPFH特征
// 选择随机特征对应的邻居的数量,数值越大,特征匹配的随机性越大。
r_sac.setCorrespondenceRandomness(5);
// 所需的(输入的)inlier分数
r_sac.setInlierFraction(0.5f);
// 每次迭代中使用的采样点数量
r_sac.setNumberOfSamples(3);
r_sac.setMaxCorrespondenceDistance(1.0f);// 内点,阈值
r_sac.setMaximumIterations(100); // RANSAC 最大迭代次数
pointcloud::Ptr align(new pointcloud);
r_sac.align(*align);
pcl::transformPointCloud(*source,*align,
r_sac.getFinalTransformation());
cout << "变换矩阵:\n" << r_sac.getFinalTransformation() << endl;
}
3. 算法分析
RANSAC的优点是能鲁棒的估计模型参数。例如,它能从包含大量局外点的数据集中估计出高精度的参数。
RANSAC的缺点是计算参数的迭代次数没有上限;如果设置迭代次数的上限,得到的结果可能不是最优的结果,甚至可能得到错误的结果。且要求设置跟问题相关的阈值。
笔者|William
审核|Los
移步公众号【深蓝AI】,第一时间获取自动驾驶、人工智能与机器人行业最新最前沿论文和科技动态。

1万+

被折叠的 条评论
为什么被折叠?



