前言
本文分析RANSAC算法的原理,集合案例深入理解,同时提供源代码。
RANSAC,随机采样一致性,是一种迭代的算法,用于从一组包含异常值的数据中估计模型参数。
应用案例:平面拟合、线段拟合、球体拟合等。
看看基于RANSAC的球体拟合可视化结果,如下图所示:
输出的拟合系数
Model coefficients: 1.96703 0.455202 3.86515 1.24063
对应:球体的中心(x,y,z)和半径r
一、RANSAC 原理分析
RANSAC(Random Sample Consensus,随机采样一致性)是一种迭代的算法,用于从一组包含异常值的数据中估计模型参数。
它由Fischler和Bolles于1981年提出,特别适用于在存在大量噪声或异常值的情况下,估计模型的参数。
-
输入数据和模型:
- 输入数据集,其中包含内点(符合模型的数据点)和外点(异常值)。
- 选择一个适合于问题的模型,例如直线、平面、变换矩阵等。
-
迭代过程:
- 设定最大迭代次数
N
和误差阈值T
。 - 在每次迭代中,随机选择一个最小子集的数据点(样本集),该子集应该足够小以保证计算模型参数。
- 根据样本集计算模型参数。
- 设定最大迭代次数
-
模型评估:
- 使用计算出的模型参数评估所有数据点,计算每个数据点与模型的误差。
- 统计误差在阈值
T
内的内点数目。 - 如果当前模型的内点数目超过之前的最佳模型,则更新最佳模型和最佳内点集。
-
终止条件:
- 迭代达到最大次数
N
或者找到了足够好的模型。
- 迭代达到最大次数
-
输出:
- 最佳模型参数和对应的内点集。
下面分析一下与RANSAC相近的算法
算法 | 简介 | 原理 | 特点 | 计算复杂度 | 优点 | 缺点 |
---|---|---|---|---|---|---|
RANSAC | 随机采样一致性算法 | 迭代随机采样,估计模型参数,选择内点最多的模型 | 通用模型估计 | 中等 | 简单易实现,对异常值鲁棒 | 需要设定参数,效率较低 |
MLESAC | 基于最大似然估计的采样一致性算法 | 结合最大似然估计,通过最大化数据的似然估计来选择模型参数 | 提高了参数估计的准确性 | 较高 | 对噪声和异常值处理更好 | 计算复杂度高,参数选择敏感 |
MSAC | 基于M估计的采样一致性算法 | 类似于RANSAC,但通过最小化所有数据点的代价函数来选择最佳模型 | 更好地考虑所有数据点的误差 | 较高 | 对异常值有更好的容忍度 | 计算复杂度相对较高 |
PROSAC | 进步采样一致性算法 | 利用数据点的排序信息,优先从高质量点中选择样本集,提高采样效率 | 高效准确,快速收敛 | 较高 | 更快地收敛到最佳模型 | 需要对数据点进行排序,预处理时间较长 |
LMedS | 最小中位数平方算法 | 通过最小化残差的中位数来估计模型参数 | 对异常值非常鲁棒 | 很高 | 对异常值非常鲁棒,不需要预设误差阈值 | 计算复杂度高,处理大数据集时效率较低 |
Theil-Sen | 非参数线性回归估计 | 计算所有数据点对之间斜率的中位数来估计回归系数 | 非参数方法,特别适用于线性回归 | 较低 | 对异常值具有较强的抗干扰能力,计算简单,效率较高 | 主要用于线性模型,适用范围较窄 |
RANSAC算法广泛应用于计算机视觉和图像处理领域,如:
- 图像拼接:在图像配准过程中,通过RANSAC算法估计变换矩阵。
- 三维重建:用于点云配准,估计旋转和平移矩阵。
- 平面拟合:在三维点云数据中,拟合平面或其他几何模型。
二、RANSAC常用方法
在PCL中,RANSAC(随机采样一致性)是一种常用的模型拟合算法,用于从点云数据中提取几何模型。
PCL 提供了多个与 RANSAC 相关的函数和类,用于不同类型的模型拟合。
pcl::SampleConsensusModel
:这是一个抽象基类,定义了所有采样一致性模型应实现的接口。具体的模型类继承自这个基类。pcl::SampleConsensusModelPlane
:用于平面模型的拟合。pcl::SampleConsensusModelLine
:用于线模型的拟合。pcl::SampleConsensusModelCircle2D
:用于二维圆模型的拟合。pcl::SampleConsensusModelSphere
:用于球体模型的拟合。pcl::SampleConsensusModelCylinder
:用于圆柱体模型的拟合。pcl::SampleConsensusModelNormalPlane
:用于法线约束的平面模型的拟合。pcl::RandomSampleConsensus
:这是 RANSAC 算法的具体实现类。可以与上述模型类结合使用。
1. pcl::SampleConsensusModel
pcl::SampleConsensusModel
是所有采样一致性模型的基类。
它定义了所有模型应实现的基本接口。具体的模型类会从这个基类继承并实现其方法。
2. pcl::SampleConsensusModelPlane
这个类用于平面模型的拟合。它继承自 pcl::SampleConsensusModel
并实现了平面模型的特定方法。
主要方法:
computeModelCoefficients(const std::vector<int> &samples, Eigen::VectorXf &model_coefficients)
: 计算平面模型的系数。isModelValid(const Eigen::VectorXf &model_coefficients)
: 检查模型是否有效。
3. pcl::SampleConsensusModelLine
这个类用于线模型的拟合。它也继承自 pcl::SampleConsensusModel
并实现了线模型的特定方法。
主要方法:
computeModelCoefficients(const std::vector<int> &samples, Eigen::VectorXf &model_coefficients)
: 计算线模型的系数。isModelValid(const Eigen::VectorXf &model_coefficients)
: 检查模型是否有效。
4. pcl::SampleConsensusModelCircle2D
这个类用于二维圆模型的拟合。它适用于在 XY 平面中的点云数据。
主要方法:
computeModelCoefficients(const std::vector<int> &samples, Eigen::VectorXf &model_coefficients)
: 计算二维圆模型的系数。isModelValid(const Eigen::VectorXf &model_coefficients)
: 检查模型是否有效。
5. pcl::SampleConsensusModelSphere
这个类用于球体模型的拟合。它适用于三维点云数据中的球体检测。
主要方法:
computeModelCoefficients(const std::vector<int> &samples, Eigen::VectorXf &model_coefficients)
: 计算球体模型的系数。isModelValid(const Eigen::VectorXf &model_coefficients)
: 检查模型是否有效。
6. pcl::SampleConsensusModelCylinder
这个类用于圆柱体模型的拟合。它适用于三维点云数据中的圆柱体检测。
主要方法:
computeModelCoefficients(const std::vector<int> &samples, Eigen::VectorXf &model_coefficients)
: 计算圆柱体模型的系数。isModelValid(const Eigen::VectorXf &model_coefficients)
: 检查模型是否有效。
7. pcl::SampleConsensusModelNormalPlane
这个类用于法线约束的平面模型的拟合。它在普通的平面模型拟合基础上增加了法线约束,以提高拟合精度。
主要方法:
computeModelCoefficients(const std::vector<int> &samples, Eigen::VectorXf &model_coefficients)
: 计算带法线约束的平面模型的系数。isModelValid(const Eigen::VectorXf &model_coefficients)
: 检查模型是否有效。
8. pcl::RandomSampleConsensus
pcl::RandomSampleConsensus
是 RANSAC 算法的具体实现类。它可以与上述各种模型类结合使用,以实现不同类型的几何模型拟合。
主要方法:
computeModel(int debug_verbosity_level = 0)
: 运行 RANSAC 算法,计算模型系数。getInliers()
: 获取内点集合。getModelCoefficients()
: 获取模型系数。
三、RANSAC优缺点分析
优点:
- 对于有大量异常值的数据集,RANSAC算法可以有效地估计模型参数。
- 算法简单易实现,且适用于多种不同类型的模型估计问题。
缺点:
- 需要设定最大迭代次数和误差阈值,参数的选择对算法的效果有很大影响。
- 随机采样可能导致计算时间较长,特别是在数据集较大且异常值较多的情况下。
- 如果数据中内点比例非常低,RANSAC可能需要大量的迭代才能找到合适的模型。
用途:
- 平面拟合,可以用于从点云数据中提取平面。这在3D建模、建筑扫描和机器人导航中非常有用。
- 线条检测,在图像处理和计算机视觉中,RANSAC 可以用于从二维数据中检测直线,如道路检测、车道线识别等。
- 圆和球体检测,用于检测二维图像中的圆形或三维点云中的球体。这在物体识别和形状分析中很有用。
- 立体视觉,用于立体匹配中的基础矩阵或单应矩阵的估计。这有助于从两个视点重建3D场景。
- 相机标定,在相机标定中,RANSAC 可用于估计相机的内参和外参,处理噪声和异常值的影响,提高标定精度。
- 运动估计,用于运动估计,如在视频序列中跟踪物体的运动,估计相机运动,处理场景中的动态对象。
- 点云配准,在点云配准中,RANSAC 可用于估计点云之间的变换矩阵,特别是在初始对齐阶段,用于处理错误匹配。
- 特征匹配,可以用于图像特征匹配,如SIFT、SURF等特征点之间的匹配,通过处理错误匹配来提高匹配的可靠性。
四、RANSAC案例——平面拟合
使用 PCL 中的 RANSAC 生成一个随机平面点云,进行平面拟合,并可视化结果。
-
生成随机平面点云:
generateRandomPlane
函数随机生成一个平面上的点云数据。- 平面的参数
a
,b
,c
,d
随机生成,点的坐标(x, y, z)
满足平面方程ax + by + cz + d = 0
。
-
执行平面拟合:
- 创建
pcl::SampleConsensusModelPlane
对象用于定义平面模型。 - 创建
pcl::RandomSampleConsensus
对象并设置适当的距离阈值。 - 调用
ransac.computeModel()
方法执行 RANSAC 算法,计算平面模型参数。 - 获取内点索引和模型系数。
- 创建
核心代码:
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/sample_consensus/ransac.h>
#include <pcl/sample_consensus/sac_model_plane.h>
#include <cstdlib>
#include <ctime>
#include <thread>
#include <chrono>void generateRandomPlane(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, int num_points) {std::srand(std::time(nullptr));float a = static_cast<float>(std::rand()) / RAND_MAX;float b = static_cast<float>(std::rand()) / RAND_MAX;float c = static_cast<float>(std::rand()) / RAND_MAX;float d = static_cast<float>(std::rand()) / RAND_MAX;for (int i = 0; i < num_points; ++i) {pcl::PointXYZ point;point.x = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;point.y = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;point.z = -(a * point.x + b * point.y + d) / c;cloud->points.push_back(point);}cloud->width = static_cast<uint32_t>(cloud->points.size());cloud->height = 1;cloud->is_dense = true;
}int main(int argc, char** argv) {pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);// 生成随机平面点云generateRandomPlane(cloud, 1000);// 创建平面模型对象pcl::SampleConsensusModelPlane<pcl::PointXYZ>::Ptrmodel_plane(new pcl::SampleConsensusModelPlane<pcl::PointXYZ>(cloud));// 创建 RANSAC 对象pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model_plane);ransac.setDistanceThreshold(0.01);ransac.computeModel();// 获取内点索引std::vector<int> inliers;ransac.getInliers(inliers);// 输出模型系数Eigen::VectorXf coefficients;ransac.getModelCoefficients(coefficients);std::cout << "Model coefficients: " << coefficients[0] << " "<< coefficients[1] << " "<< coefficients[2] << " "<< coefficients[3] << std::endl;// 创建可视化对象pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));viewer->setBackgroundColor(0, 0, 0);// 添加原始点云到可视化窗口pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cloud_color(cloud, 255, 255, 255);viewer->addPointCloud<pcl::PointXYZ>(cloud, cloud_color, "sample cloud");// 可视化拟合的平面pcl::ModelCoefficients::Ptr plane_coefficients(new pcl::ModelCoefficients);plane_coefficients->values.resize(4);plane_coefficients->values[0] = coefficients[0];plane_coefficients->values[1] = coefficients[1];plane_coefficients->values[2] = coefficients[2];plane_coefficients->values[3] = coefficients[3];viewer->addPlane(*plane_coefficients, "plane");// 开始主循环while (!viewer->wasStopped()) {viewer->spinOnce(100);std::this_thread::sleep_for(std::chrono::milliseconds(100));}return 0;
}
看看可视化结果,如下图所示:
输出的模型拟合系数:
Model coefficients: 0.837264 0.258466 0.481855 0.425604
对应平面的参数
a
,b
,c
,d
,点的坐标(x, y, z)
满足平面方程ax + by + cz + d = 0
。
五、RANSAC案例——线段拟合
使用 PCL 中的 RANSAC 生成随机线段点云,进行线条拟合,并可视化结果。
- 生成随机线段点云:创建一个函数随机生成线段上的点。
- 执行线条拟合:使用 RANSAC 算法拟合线条模型。
- 可视化结果:使用 PCL 的可视化工具显示点云和拟合的线条。
核心代码:
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/sample_consensus/ransac.h>
#include <pcl/sample_consensus/sac_model_line.h>
#include <cstdlib>
#include <ctime>
#include <thread>
#include <chrono>
#include <random>// 生成随机点云,并添加散乱的噪声
void generateRandomPointCloudWithScatterNoise(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, int num_points, float noise_stddev, int num_noise_points) {std::srand(std::time(nullptr));// 随机生成线段的两个端点pcl::PointXYZ point1, point2;point1.x = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;point1.y = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;point1.z = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;point2.x = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;point2.y = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;point2.z = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;// 创建一个正态分布生成器,用于添加噪声std::default_random_engine generator;std::normal_distribution<float> distribution(0.0, noise_stddev);// 生成线段上的随机点for (int i = 0; i < num_points; ++i) {float t = static_cast<float>(std::rand()) / RAND_MAX;pcl::PointXYZ point;point.x = point1.x + t * (point2.x - point1.x) + distribution(generator);point.y = point1.y + t * (point2.y - point1.y) + distribution(generator);point.z = point1.z + t * (point2.z - point1.z) + distribution(generator);cloud->points.push_back(point);}// 添加散乱的噪声点for (int i = 0; i < num_noise_points; ++i) {pcl::PointXYZ noise_point;noise_point.x = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;noise_point.y = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;noise_point.z = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;cloud->points.push_back(noise_point);}cloud->width = static_cast<uint32_t>(cloud->points.size());cloud->height = 1;cloud->is_dense = true;
}int main(int argc, char** argv) {pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);// 生成带有散乱噪声的随机点云generateRandomPointCloudWithScatterNoise(cloud, 1000, 0.05f, 200);// 创建线段模型对象pcl::SampleConsensusModelLine<pcl::PointXYZ>::Ptrmodel_line(new pcl::SampleConsensusModelLine<pcl::PointXYZ>(cloud));// 创建 RANSAC 对象pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model_line);ransac.setDistanceThreshold(0.05);ransac.computeModel();// 获取内点索引std::vector<int> inliers;ransac.getInliers(inliers);// 输出模型系数Eigen::VectorXf coefficients;ransac.getModelCoefficients(coefficients);std::cout << "Model coefficients: " << coefficients[0] << " "<< coefficients[1] << " "<< coefficients[2] << " "<< coefficients[3] << " "<< coefficients[4] << " "<< coefficients[5] << std::endl;// 创建可视化对象pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));viewer->setBackgroundColor(0, 0, 0);// 添加原始点云到可视化窗口pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cloud_color(cloud, 255, 255, 255);viewer->addPointCloud<pcl::PointXYZ>(cloud, cloud_color, "sample cloud");// 可视化拟合的线条pcl::ModelCoefficients::Ptr line_coefficients(new pcl::ModelCoefficients);line_coefficients->values.resize(6);line_coefficients->values[0] = coefficients[0];line_coefficients->values[1] = coefficients[1];line_coefficients->values[2] = coefficients[2];line_coefficients->values[3] = coefficients[3];line_coefficients->values[4] = coefficients[4];line_coefficients->values[5] = coefficients[5];viewer->addLine(*line_coefficients, "line");// 开始主循环while (!viewer->wasStopped()) {viewer->spinOnce(100);std::this_thread::sleep_for(std::chrono::milliseconds(100));}return 0;
}
-
生成随机点云并添加散乱噪声:
generateRandomPointCloudWithScatterNoise
函数随机生成线段上的点,并在每个点的坐标上添加噪声。- 同时,生成一些散乱的噪声点,分布在整个空间中。
-
执行线条拟合:
- 创建
pcl::SampleConsensusModelLine
对象用于定义线条模型。 - 创建
pcl::RandomSampleConsensus
对象并设置适当的距离阈值(这里设置为0.05
,以考虑添加的噪声)。 - 调用
ransac.computeModel()
方法执行 RANSAC 算法,计算线条模型参数。 - 获取内点索引和模型系数。
- 创建
看看可视化结果,如下图所示:
输出的拟合系数
Model coefficients: 6.4163 6.92066 2.9858 0.919972 -0.385063 0.0733363
对应:线段的两个端点(x1,y1,z1),(x2,y2,z2)
六、RANSAC案例——球体拟合
使用 PCL 中的 RANSAC 生成随机点云,进行球体检测,并可视化结果。
-
生成随机球体点云并添加噪声:
generateRandomSphereWithNoise
函数随机生成球体上的点。- 首先随机生成球体的中心和半径,然后在球体表面生成随机点,并在每个点的坐标上添加噪声。
- 噪声是通过正态分布生成的,标准差由
noise_stddev
参数指定。
-
执行球体检测:
- 创建
pcl::SampleConsensusModelSphere
对象用于定义球体模型。 - 创建
pcl::RandomSampleConsensus
对象并设置适当的距离阈值(这里设置为0.05
,以考虑添加的噪声)。 - 调用
ransac.computeModel()
方法执行 RANSAC 算法,计算球体模型参数。 - 获取内点索引和模型系数。
- 创建
核心代码:
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/sample_consensus/ransac.h>
#include <pcl/sample_consensus/sac_model_sphere.h>
#include <cstdlib>
#include <ctime>
#include <thread>
#include <chrono>
#include <random>// 生成随机球体点云,并添加噪声
void generateRandomSphereWithNoise(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, int num_points, float noise_stddev) {std::srand(std::time(nullptr));// 随机生成球体的中心和半径pcl::PointXYZ center;center.x = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;center.y = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;center.z = 10.0f * static_cast<float>(std::rand()) / RAND_MAX;float radius = 5.0f * static_cast<float>(std::rand()) / RAND_MAX + 1.0f; // 半径在 1.0 到 6.0 之间// 创建一个正态分布生成器,用于添加噪声std::default_random_engine generator;std::normal_distribution<float> distribution(0.0, noise_stddev);// 生成球体上的随机点,并添加噪声for (int i = 0; i < num_points; ++i) {float theta = 2.0f * M_PI * static_cast<float>(std::rand()) / RAND_MAX;float phi = M_PI * static_cast<float>(std::rand()) / RAND_MAX;pcl::PointXYZ point;point.x = center.x + radius * std::sin(phi) * std::cos(theta) + distribution(generator);point.y = center.y + radius * std::sin(phi) * std::sin(theta) + distribution(generator);point.z = center.z + radius * std::cos(phi) + distribution(generator);cloud->points.push_back(point);}cloud->width = static_cast<uint32_t>(cloud->points.size());cloud->height = 1;cloud->is_dense = true;
}int main(int argc, char** argv) {pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);// 生成带噪声的随机球体点云generateRandomSphereWithNoise(cloud, 1000, 0.05f);// 创建球体模型对象pcl::SampleConsensusModelSphere<pcl::PointXYZ>::Ptrmodel_sphere(new pcl::SampleConsensusModelSphere<pcl::PointXYZ>(cloud));// 创建 RANSAC 对象pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(model_sphere);ransac.setDistanceThreshold(0.05);ransac.computeModel();// 获取内点索引std::vector<int> inliers;ransac.getInliers(inliers);// 输出模型系数Eigen::VectorXf coefficients;ransac.getModelCoefficients(coefficients);std::cout << "Model coefficients: " << coefficients[0] << " "<< coefficients[1] << " "<< coefficients[2] << " "<< coefficients[3] << std::endl;// 创建可视化对象pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("3D Viewer"));viewer->setBackgroundColor(0, 0, 0);// 添加原始点云到可视化窗口pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZ> cloud_color(cloud, 255, 255, 255);viewer->addPointCloud<pcl::PointXYZ>(cloud, cloud_color, "sample cloud");// 可视化拟合的球体pcl::ModelCoefficients::Ptr sphere_coefficients(new pcl::ModelCoefficients);sphere_coefficients->values.resize(4);sphere_coefficients->values[0] = coefficients[0];sphere_coefficients->values[1] = coefficients[1];sphere_coefficients->values[2] = coefficients[2];sphere_coefficients->values[3] = coefficients[3];viewer->addSphere(*sphere_coefficients, "sphere");// 开始主循环while (!viewer->wasStopped()) {viewer->spinOnce(100);std::this_thread::sleep_for(std::chrono::milliseconds(100));}return 0;
}
看看可视化结果,如下图所示:
输出的拟合系数
Model coefficients: 1.96703 0.455202 3.86515 1.24063
对应:球体的中心(x,y,z)和半径r
分享完成~