PCL RANSAC三维圆拟合后拟合质量指标获取异常问题
我想用Point Cloud Library(PCL)对一组三维点做三维圆拟合,同时用RANSAC鲁棒剔除离群点。除了得到圆参数,还想获取最终筛选出的点与模型的拟合质量指标——这个指标可以用来提示用户重新采集数据,也能在标定流程里过滤低质量数据。
我写了如下代码:
PCLCIRCLEFITTING_API bool FitCircle(const vec* const points, const UINT32 point_count, const double threshold, circle3d* circle, double* variance) { const pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); cloud->width = point_count; cloud->height = 1; cloud->is_dense = true; cloud->points.resize(static_cast<size_t>(cloud->width) * cloud->height); // 把自定义结构体的点坐标复制到PCL点云 for (pcl::index_t i = 0; i < static_cast<pcl::index_t>(cloud->size ()); i++) { (*cloud)[i].x = points[i].x; (*cloud)[i].y = points[i].y; (*cloud)[i].z = points[i].z; } // 创建RANSAC模型并计算 const pcl::SampleConsensusModelCircle3D<pcl::PointXYZ>::Ptr circle_model (new pcl::SampleConsensusModelCircle3D<pcl::PointXYZ>(cloud)); pcl::RandomSampleConsensus<pcl::PointXYZ> ransac(circle_model, threshold); // 启用并行处理,自动根据硬件选线程数 ransac.setNumberOfThreads(0); const bool success = ransac.computeModel(); if (success) { Eigen::VectorXf coefficients; ransac.getModelCoefficients(coefficients); circle->center.x = coefficients[0]; circle->center.y = coefficients[1]; circle->center.z = coefficients[2]; circle->radius = coefficients[3]; circle->normal.x = coefficients[4]; circle->normal.y = coefficients[5]; circle->normal.z = coefficients[6]; // 这里返回NaN,明明已经计算出有效模型了? *variance = circle_model->computeVariance(); } else { *variance = 0.0; } return success; }
测试时模型拟合成功,参数也正确,但variance始终返回NaN。我查看了computeVariance()的实现:
/** \brief 基于内部估计的距离向量,计算点到模型的误差方差。必须先计算模型(至少调用selectWithinDistance)。 */ inline double computeVariance () const { if (error_sqr_dists_.empty ()) { PCL_ERROR ("[pcl::SampleConsensusModel::computeVariance] 无法估计采样一致性模型的距离方差,因为模型尚未计算。请先计算模型或至少运行selectWithinDistance。返回NAN!\n"); return (std::numeric_limits<double>::quiet_NaN ()); } return (computeVariance (error_sqr_dists_)); }
error_sqr_dists_是空的,但我明明已经运行RANSAC计算出有效模型参数了。请问我是不是错误使用了computeVariance()?如果是,获取RANSAC拟合质量分数的更好方法是什么?
为什么computeVariance()返回NaN?
PCL的RandomSampleConsensus类执行computeModel()时,只会在内部筛选符合模型的内点,但不会自动调用模型的selectWithinDistance()方法填充error_sqr_dists_数组——而computeVariance()完全依赖这个数组计算方差。所以即使你拿到了有效模型参数,circle_model内部的误差距离数组还是空的,自然返回NaN。
获取拟合质量的正确方法
有两种可靠的方式获取拟合质量指标:
方法1:手动调用selectWithinDistance()填充误差数组
在拿到模型系数后,调用circle_model的selectWithinDistance(),传入模型系数和阈值,这样会自动计算所有内点到模型的平方距离并填充error_sqr_dists_,之后再调用computeVariance()就能得到有效结果:
if (success) { Eigen::VectorXf coefficients; ransac.getModelCoefficients(coefficients); // 先将模型系数传给circle_model,计算内点和误差距离 std::vector<int> inliers; circle_model->selectWithinDistance(coefficients, threshold, inliers); // 现在可以正常调用computeVariance()了 *variance = circle_model->computeVariance(); // 后续赋值圆参数的代码... circle->center.x = coefficients[0]; circle->center.y = coefficients[1]; circle->center.z = coefficients[2]; circle->radius = coefficients[3]; circle->normal.x = coefficients[4]; circle->normal.y = coefficients[5]; circle->normal.z = coefficients[6]; }
方法2:直接用RANSAC的内点计算自定义指标
如果你需要更灵活的质量指标(比如内点占比、平均误差、最大误差),可以通过ransac.getInliers()获取内点索引,然后手动计算这些指标:
if (success) { Eigen::VectorXf coefficients; ransac.getModelCoefficients(coefficients); std::vector<int> inliers; ransac.getInliers(inliers); // 计算内点占比 double inlier_ratio = static_cast<double>(inliers.size()) / point_count; // 计算内点到模型的平均平方误差(可作为方差的近似,或用严格方差公式计算) double total_sqr_error = 0.0; for (int idx : inliers) { pcl::PointXYZ pt = cloud->points[idx]; double dist_sqr = circle_model->getDistToModel(coefficients, pt.x, pt.y, pt.z); total_sqr_error += dist_sqr; } double mean_sqr_error = total_sqr_error / inliers.size(); *variance = mean_sqr_error; // 后续赋值圆参数的代码... circle->center.x = coefficients[0]; circle->center.y = coefficients[1]; circle->center.z = coefficients[2]; circle->radius = coefficients[3]; circle->normal.x = coefficients[4]; circle->normal.y = coefficients[5]; circle->normal.z = coefficients[6]; }
注意事项
selectWithinDistance()会使用你传入的阈值筛选内点,和RANSAC过程中用的阈值一致即可,保证结果和RANSAC的内点集匹配。- 如果需要严格和RANSAC最终选中的内点一致,建议用
ransac.getInliers()获取内点索引再计算误差——因为RANSAC可能在迭代中选择了最优内点集,而selectWithinDistance()是基于最终模型重新筛选的,理论结果一致,但手动计算更可控。
内容的提问来源于stack exchange,提问作者Hydrargyrum

