当前位置: 首页 > news >正文

并行RANSAC平面拟合(C++)

依赖库

1)Eigen

2)GLM

算法大致思路

Step 1:源点云随机采样3个点;

Step 2:3个点拟合平面,统计符合该平面模型的点,为inlier点;

Step 3:判断inlier点比例是否达到阈值,更新最佳模型和最佳模型对应的inlier点集。若inlier比例不到阈值,则继续Step 1;若已满足阈值或达到最大迭代次数,转Step 4;

Step 4:针对最佳模型的inlier点集,使用最小二乘法,重新拟合平面;

#include <iostream>
#include <vector>
#include <random>
#include <cmath>
#include <limits>
#include <algorithm>
#include <Eigen/Dense>
#include <thread>
#include <mutex>
#include <atomic>
#include<glm/glm.hpp>3D 点结构体
//struct Point3D {
//	double x, y, z;
//	Point3D(double x_ = 0, double y_ = 0, double z_ = 0) : x(x_), y(y_), z(z_) {}
//};struct Plane3D
{double a, b, c, d;Plane3D(){a = b = d = 0.0;c = 1.0;}Plane3D(double _a, double _b, double _c, double _d){a = _a;b = _b;c = _c;d = _d;}};typedef glm::dvec3 Point3d;// 平面模型: ax + by + cz + d = 0
struct PlaneModel {//double a, b, c, d;Plane3D param;std::vector<Point3d> inliers;// 从三个点计算平面方程void computeFrom3Points(const Point3d& p1, const Point3d& p2, const Point3d& p3) {// 计算两个向量Point3d v1(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);Point3d v2(p3.x - p1.x, p3.y - p1.y, p3.z - p1.z);// 计算叉积得到法向量 (a,b,c)param.a = v1.y * v2.z - v1.z * v2.y;param.b = v1.z * v2.x - v1.x * v2.z;param.c = v1.x * v2.y - v1.y * v2.x;// 归一化double norm = std::sqrt(param.a * param.a + param.b * param.b + param.c * param.c);if (norm > 0) {param.a /= norm;param.b /= norm;param.c /= norm;}// 计算 dparam.d = -(param.a * p1.x + param.b * p1.y + param.c * p1.z);
}// 使用最小二乘法从内点拟合平面
void refineWithLeastSquares() {if (inliers.size() < 3) return;// 构建矩阵 A 和向量 b (Ax = b)Eigen::MatrixXd A(inliers.size(), 3);Eigen::VectorXd b(inliers.size());// 计算所有点的质心Point3d centroid(0, 0, 0);for (const auto& p : inliers) {centroid.x += p.x;centroid.y += p.y;centroid.z += p.z;}centroid.x /= inliers.size();centroid.y /= inliers.size();centroid.z /= inliers.size();// 构建矩阵for (size_t i = 0; i < inliers.size(); ++i) {A(i, 0) = inliers[i].x - centroid.x;A(i, 1) = inliers[i].y - centroid.y;A(i, 2) = inliers[i].z - centroid.z;}// 使用SVD求解最小二乘问题Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);Eigen::Vector3d normal = svd.matrixV().col(2);// 更新平面参数param.a = normal(0);param.b = normal(1);param.c = normal(2);param.d = -(param.a * centroid.x + param.b * centroid.y + param.c * centroid.z);// 归一化double norm = std::sqrt(param.a * param.a + param.b * param.b + param.c * param.c);if (norm > 0) {param.a /= norm;param.b /= norm;param.c /= norm;param.d /= norm;}}// 计算点到平面的距离double distanceTo(const Point3d& p) const {return std::abs(param.a * p.x + param.b * p.y + param.c * p.z + param.d);}// 评估模型,收集内点int evaluate(const std::vector<Point3d>& points, double distanceThreshold) {inliers.clear();for (const auto& p : points) {if (distanceTo(p) < distanceThreshold) {inliers.push_back(p);}}return inliers.size();}
};// 并行RANSAC工作函数
void ransacWorker(const std::vector<Point3d>& points,int maxIterations,double distanceThreshold,int minInliers,std::atomic<int>& bestInliers,PlaneModel& bestModel,std::mutex& modelMutex,std::atomic<bool>& stopFlag,double inlierRatioThresh,int threadId)
{std::random_device rd;std::mt19937 gen(rd() + threadId); // 每个线程有不同的种子std::uniform_int_distribution<> dis(0, points.size() - 1);PlaneModel localBestModel;int localBestInliers = -1;for (int i = 0; i < maxIterations && !stopFlag; ++i) {// 随机选择3个不重复的点int idx1 = dis(gen);int idx2, idx3;do { idx2 = dis(gen); } while (idx2 == idx1);do { idx3 = dis(gen); } while (idx3 == idx1 || idx3 == idx2);// 计算平面模型PlaneModel model;model.computeFrom3Points(points[idx1], points[idx2], points[idx3]);// 评估模型int inliers = model.evaluate(points, distanceThreshold);// 更新本地最佳模型if (inliers > localBestInliers && inliers >= minInliers) {localBestInliers = inliers;localBestModel = model;// 检查是否需要更新全局最佳模型if (localBestInliers > bestInliers) {std::lock_guard<std::mutex> lock(modelMutex);if (localBestInliers > bestInliers) {bestInliers = localBestInliers;bestModel = localBestModel;// 动态调整: 如果找到足够好的模型,可以提前停止double inlierRatio = static_cast<double>(bestInliers) / points.size();if (inlierRatio > inlierRatioThresh) { // 如果内点比例超过80%,提前停止stopFlag = true;}}}}}
}// 并行RANSAC平面拟合
// inlierRatioThresh=0.9 表示内点占比超过90%,可以提前终止迭代
PlaneModel parallelRansacFitPlane(const std::vector<Point3d>& points,int maxIterations = 1000,double distanceThreshold = 0.01,int minInliers = 0,int numThreads = 4,double inlierRatioThresh = 0.8) 
{if (points.size() < 3) {throw std::runtime_error("At least 3 points are required to fit a plane");}if (minInliers <= 0) {minInliers = static_cast<int>(points.size() * 0.3); // 默认最小内点数为30%}std::atomic<int> bestInliers(-1);PlaneModel bestModel;std::mutex modelMutex;std::atomic<bool> stopFlag(false);// 计算每个线程的迭代次数int iterationsPerThread = maxIterations / numThreads;std::vector<std::thread> threads;for (int i = 0; i < numThreads; ++i) {threads.emplace_back(ransacWorker,std::ref(points),iterationsPerThread,distanceThreshold,minInliers,std::ref(bestInliers),std::ref(bestModel),std::ref(modelMutex),std::ref(stopFlag),inlierRatioThresh,i);}// 等待所有线程完成for (auto& t : threads) {t.join();}// 如果没有找到足够内点的模型,返回第一个模型if (bestInliers == -1) {bestModel.computeFrom3Points(points[0], points[1], points[2]);}// 使用最小二乘法优化最佳模型bestModel.evaluate(points, distanceThreshold); // 找出所有内点bestModel.refineWithLeastSquares();return bestModel;
}// 示例使用
int main() {// 生成一些测试数据 - 平面上的点加一些噪声std::vector<Point3d> points;// 理想平面 z = 2x + 3y + 1for (int i = 0; i < 1000; ++i) {double x = (rand() % 1000) / 100.0;double y = (rand() % 1000) / 100.0;double z = 2 * x + 3 * y + 1;// 添加一些高斯噪声z += (rand() % 100) / 1000.0;points.emplace_back(x, y, z);}// 添加一些噪声点 (离群值)for (int i = 0; i < 200; ++i) {double x = (rand() % 1000) / 100.0;double y = (rand() % 1000) / 100.0;double z = (rand() % 1000) / 10.0;points.emplace_back(x, y, z);}// 使用并行RANSAC拟合平面PlaneModel plane = parallelRansacFitPlane(points, 10000, 0.1, 300, 4, 0.8);std::cout << "拟合平面方程: " << plane.param.a << "x + "<< plane.param.b << "y + " << plane.param.c << "z + "<< plane.param.d << " = 0" << std::endl;std::cout << "内点数量: " << plane.inliers.size()<< "/" << points.size() << std::endl;// 计算拟合误差double totalError = 0;for (const auto& p : plane.inliers) {totalError += plane.distanceTo(p);}std::cout << "平均拟合误差: " << totalError / plane.inliers.size() << std::endl;return 0;
}

相关文章:

  • [特殊字符]‍[特殊字符]Linux驱动开发入门 | 并发与互斥机制详解
  • ActiveMQ 核心概念与消息模型详解(二)
  • centos7 设置EMQX开机自启动(创建init.d脚本来设置emqx的自启动服务)
  • 文档构建:Sphinx全面使用指南 — 实战篇
  • leetcode 二分查找应用
  • 未曾设想的道路1
  • idea无法下载源代码
  • 使用Python模拟子弹与子弹的碰撞
  • 蓝桥杯 19. 最大比例
  • 阳光乳业2024年营收、利润双降:囿于一隅,市场单一化困局何解?
  • 抱佛脚之学SSM五
  • Rust 学习笔记:安装 Rust
  • Qt实现语言切换的完整方案
  • 一,开发环境安装
  • Java 实现单链表翻转(附详细注释)
  • redis 使用 Docker 部署 简单的Redis 集群(包括哨兵机制)
  • Qt基础007(Tcp网络编程)
  • 32单片机——GPIO的工作模式
  • Redis的string类型使用
  • Redis核心技术知识点全集
  • 上海五五购物节首次推出商圈精品推广节,9个商圈近百个商场参与促销
  • 汪东进卸任中海油董事长,深耕油气领域40余年、已临近退休
  • 海南:谈话提醒9名缺点明显或有苗头性、倾向性问题的省管干部
  • 刘国梁总结发言数度哽咽:乒乓球是事业,更是融入血脉的信仰
  • 2024年我国数字阅读用户规模达6.7亿
  • 电影“名场面”被制成表情包在网站上传播,网站运营者被判赔5500元