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

高精度并行2D圆弧拟合(C++)

依赖库

Eigen3 + GLM + Ceres-2.1.0 + glog-0.6.0 + gflag-2.2.2

基本思路

Step 1: RANSAC找到圆弧,保留inliers点;

Step 2:使用ceres非线性优化的方法,拟合inliers点,得到圆心和半径;

-------------------------------------------------

PCL拟合3D圆弧的代码参见    PCL拟合空间3D圆周 fit3DCircle

PCL库拟合圆弧的问题:PCL中坐标值默认使用float类型,在某些高精度场景中不适用。

代码

pcl2d_circle.h

// 2D圆类
#include<common.h>
#include<Eigen/Dense>
#include<mutex>namespace pcl2d
{PCL2D_API class Circle2D {public:Point2d center = Point2d(0.0);double radius = 0.0;Circle2D(){center = Point2d(0.0);radius = 0.0;}Circle2D(const Point2d& c, double r) : center(c), radius(r) {}// 计算点到平面的距离double distanceTo(const Point2d& p) const {return std::abs(glm::distance(p, center) - radius);}friend std::ostream& operator<<(std::ostream& os, const Circle2D& circle) {os << "Circle2D(center: " << circle.center.x << ", " << circle.center.y<< ", radius: " << circle.radius << ")";return os;}};struct Circle2DModel{//double a, b, c, d;Circle2D param;std::vector<Point2d> inliers;// 从三个点计算平面方程bool computeFrom3Points(const Point2d& p1, const Point2d& p2, const Point2d& p3);Circle2D fitCircleAlgebraic(const std::vector<Point2d>& points);// 使用最小二乘法从内点拟合平面double refineWithLeastSquares();// 计算点到2D圆的距离double distanceTo(const Point2d& p) const {return param.distanceTo(p);}// 评估模型,收集内点int evaluate(const std::vector<Point2d>& points, double distanceThreshold);};// 输入原始二维点和圆模型,计算拟合圆的RMSEPCL2D_API double calcRMSE(std::vector<Point2d>& points, Circle2D circle);// 并行RANSAC平面拟合// inlierRatioThresh=0.9 表示内点占比超过90%,可以提前终止迭代PCL2D_API Circle2DModel parallelRansacFitCircle2D(const std::vector<Point2d>& points,int maxIterations = 1000,double distanceThreshold = 0.01,int minInliers = 0,int numThreads = 4,double inlierRatioThresh = 0.8);// 生成带噪声的圆弧PCL2D_API std::vector<Point2d> generateNoisyArc2D(Point2d center,double radius,double startAngle, double endAngle,int numPoints,double noiseLevel);}

pcl2d_circle.cpp

#include"pcl2d_circle.h"
#include<thread>
#include<random>using namespace pcl2d;double pcl2d::calcRMSE(std::vector<Point2d>& points, Circle2D circle)
{double sum = 0., dx, dy;int n = points.size();for (int i = 0; i < n; i++){dx = points[i].x - circle.center.x;dy = points[i].y - circle.center.y;sum += SQR(sqrt(dx * dx + dy * dy) - circle.radius);}return sqrt(sum / (double)n);
}// 从三个点计算平面方程
bool Circle2DModel::computeFrom3Points(const Point2d& p1, const Point2d& p2, const Point2d& p3) {double A = p2.x - p1.x;double B = p2.y - p1.y;double C = p3.x - p1.x;double D = p3.y - p1.y;double E = A * (p1.x + p2.x) + B * (p1.y + p2.y);double F = C * (p1.x + p3.x) + D * (p1.y + p3.y);double G = 2.0 * (A * (p3.y - p2.y) - B * (p3.x - p2.x));if (G == 0) { // 如果三点共线,则无法形成圆return false;}param.center.x = (D * E - B * F) / G;param.center.y = (A * F - C * E) / G;param.radius = std::sqrt(std::pow(param.center.x - p1.x, 2) +std::pow(param.center.y - p1.y, 2));return true;
}Circle2D Circle2DModel::fitCircleAlgebraic(const std::vector<Point2d>& points) 
{int n = points.size();if (n < 3) throw std::runtime_error("至少需要3个点来拟合圆");Eigen::MatrixXd A(n, 3);Eigen::VectorXd b(n);for (int i = 0; i < n; ++i) {double x = points[i].x, y = points[i].y;A(i, 0) = x;A(i, 1) = y;A(i, 2) = 1.0;b(i) = -(x * x + y * y);}// 解线性方程组 ATAx = ATbEigen::Vector3d solution = (A.transpose() * A).ldlt().solve(A.transpose() * b);double a = solution(0);double b_val = solution(1);double c = solution(2);Circle2D circle;circle.center.x = -a / 2.0;circle.center.y = -b_val / 2.0;circle.radius = std::sqrt(a * a + b_val * b_val - 4.0 * c) / 2.0;return circle;
}int Circle2DModel::evaluate(const std::vector<Point2d>& points, double distanceThreshold)
{inliers.clear();for (const auto& p : points) {if (distanceTo(p) < distanceThreshold) {inliers.push_back(p);}}return inliers.size();
}#include<ceres/ceres.h>struct CircleCostFunctor {CircleCostFunctor(double x, double y) : x_(x), y_(y) {}template <typename T>bool operator()(const T* const center, const T* const radius, T* residual) const {residual[0] = ceres::sqrt(ceres::pow(x_ - center[0], 2) + ceres::pow(y_ - center[1], 2)) - radius[0];return true;}
private:double x_, y_;
};bool refineCircleWithCeres(const std::vector<Point2d>& points,Point2d& center, double& radius) {ceres::Problem problem;double center_ceres[2] = { center.x, center.y };double radius_ceres = radius;for (const auto& p : points) {ceres::CostFunction* cost_function =new ceres::AutoDiffCostFunction<CircleCostFunctor, 1, 2, 1>(new CircleCostFunctor(p.x, p.y));problem.AddResidualBlock(cost_function, nullptr, center_ceres, &radius_ceres);}ceres::Solver::Options options;options.minimizer_progress_to_stdout = false;ceres::Solver::Summary summary;ceres::Solve(options, &problem, &summary);center.x = center_ceres[0];center.y = center_ceres[1];radius = radius_ceres;return summary.IsSolutionUsable();
}// 使用最小二乘法从内点拟合平面
double Circle2DModel::refineWithLeastSquares()
{int N = inliers.size();if (N < 3) {std::cerr << "At least 3 points required for circle fitting" << std::endl;return 0.0;}refineCircleWithCeres(inliers, param.center, param.radius);double err = 0.0;double e;double r2 = param.radius * param.radius;for (int pId = 0; pId < N; ++pId){auto v = inliers[pId] - param.center;e = glm::dot(v, v) - r2;if (e > err) {err = e;}}return err;
}// 并行RANSAC工作函数
void ransacWorkerFitCircle2D(const std::vector<Point2d>& points,int maxIterations,double distanceThreshold,int minInliers,std::atomic<int>& bestInliers,Circle2DModel& 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);Circle2DModel 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);// 计算2D圆模型Circle2DModel model;if (!model.computeFrom3Points(points[idx1], points[idx2], points[idx3]))continue;// 评估模型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%,可以提前终止迭代
Circle2DModel pcl2d::parallelRansacFitCircle2D(const std::vector<Point2d>& 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);Circle2DModel 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(ransacWorkerFitCircle2D,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;
}std::vector<Point2d> pcl2d::generateNoisyArc2D(Point2d center,double radius,double startAngle, double endAngle,int numPoints,double noiseLevel)
{std::vector<Point2d> points;points.reserve(numPoints);startAngle = startAngle * glm::pi<double>() / 180.0; // 转换为弧度endAngle = endAngle * glm::pi<double>() / 180.0; // 转换为弧度std::random_device rd;std::mt19937 gen(rd());std::uniform_real_distribution<> angleDist(startAngle, endAngle);std::normal_distribution<> noiseDist(0.0, noiseLevel);for (int i = 0; i < numPoints; ++i) {double angle = angleDist(gen);double x = center.x + radius * std::cos(angle)/* + noiseDist(gen)*/;double y = center.y + radius * std::sin(angle);points.push_back({ x, y });}for (int i = 0; i < points.size(); i++){points[i] += noiseDist(gen) * glm::normalize(points[i] - center);}return points;
}

main.cpp

	auto pts = pcl2d::generateNoisyArc2D(pcl2d::Point2d(100, 77), 2.0, 20, 120, 200, 0.1);auto model = pcl2d::parallelRansacFitCircle2D(pts, 1000, 0.2, 0, 4, 0.99);

相关文章:

高精度并行2D圆弧拟合(C++)

依赖库 Eigen3 GLM Ceres-2.1.0 glog-0.6.0 gflag-2.2.2 基本思路 Step 1&#xff1a; RANSAC找到圆弧&#xff0c;保留inliers点&#xff1b; Step 2&#xff1a;使用ceres非线性优化的方法&#xff0c;拟合inliers点&#xff0c;得到圆心和半径&#xff1b; -------…...

Linux端口占用问题排查与解决

在 Linux 中,当遇到端口被占用的情况(如你遇到的 8000 端口),可以通过以下步骤查看并处理: 1. 查看占用端口的进程 使用 netstat 或 ss 命令(推荐 ss,更现代): sudo netstat -tulnp | grep :8000 # 或 sudo ss -tulnp | grep :8000输出示例: tcp 0 0 0.0.0.0:…...

PostgreSQL 分区表——范围分区SQL实践

PostgreSQL 分区表——范围分区SQL实践 1、环境准备1-1、新增原始表1-2、执行脚本新增2400w行1-3、创建pg分区表-分区键为创建时间1-4、创建24年所有分区1-5、设置默认分区&#xff08;兜底用&#xff09;1-6、迁移数据1-7、创建分区表索引 2、SQL增删改查测试2-1、查询速度对比…...

4.3 工具调用与外部系统集成:API调用、MCP(模型上下文协议)、A2A、数据库查询与信息检索的实现

工具调用与外部系统集成是智能代理&#xff08;Agent&#xff09;系统实现复杂功能和企业级应用的核心支柱。Agent通过API调用访问实时服务&#xff0c;**模型上下文协议&#xff08;Model Context Protocol, MCP&#xff09;**标准化数据交互&#xff0c;Agent-to-Agent&#…...

展锐Android13电池问题导致系统的崩溃,(2)电池电压计算和电池曲线

先看is_bat_low函数的代码&#xff1a; #ifndef LOW_BAT_VOL //# define LOW_BAT_VOL 3400 #define LOW_BAT_VOL 3672 #endif #ifndef LOW_BAT_VOL_CHG //# define LOW_BAT_VOL_CHG 3500 #define LOW_BAT_VOL_CHG 3719 #endifint is_bat_low(void) {int32_t vbat_vol;uin…...

SpringCloud 微服务复习笔记

文章目录 微服务概述单体架构微服务架构 微服务拆分微服务拆分原则拆分实战第一步&#xff1a;创建一个新工程第二步&#xff1a;创建对应模块第三步&#xff1a;引入依赖第四步&#xff1a;被配置文件拷贝过来第五步&#xff1a;把对应的东西全部拷过来第六步&#xff1a;创建…...

【Python爬虫基础篇】--4.Selenium入门详细教程

先解释&#xff1a;Selenium&#xff1a;n.硒&#xff1b;硒元素 目录 1.Selenium--简介 2.Selenium--原理 3.Selenium--环境搭建 4.Selenium--简单案例 5.Selenium--定位方式 6.Selenium--常用方法 6.1.控制操作 6.2.鼠标操作 6.3.键盘操作 6.4.获取断言信息 6.5.…...

【Python爬虫详解】第四篇:使用解析库提取网页数据——XPath

在前一篇文章中&#xff0c;我们介绍了如何使用BeautifulSoup解析库从HTML中提取数据。本篇文章将介绍另一个强大的解析工具&#xff1a;XPath。XPath是一种在XML文档中查找信息的语言&#xff0c;同样适用于HTML文档。它的语法简洁而强大&#xff0c;特别适合处理结构复杂的网…...

二分小专题

P1102 A-B 数对 P1102 A-B 数对 暴力枚举还是很好做的&#xff0c;直接上双层循环OK 二分思路:查找边界情况&#xff0c;找出最大下标和最小下标&#xff0c;两者相减1即为答案所求 废话不多说&#xff0c;上代码 //暴力O(n^3) 72pts // #include<bits/stdc.h> // usin…...

Langchain检索YouTube字幕

创建一个简单搜索引擎&#xff0c;将用户原始问题传递该搜索系统 本文重点&#xff1a;获取保存文档——保存向量数据库——加载向量数据库 专注于youtube的字幕&#xff0c;利用youtube的公开接口&#xff0c;获取元数据 pip install youtube-transscript-api pytube 初始化 …...

【Linux网络】应用层自定义协议与序列化及Socket模拟封装

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

客户案例:西范优选通过日事清实现流程与项目管理的优化

近几年来&#xff0c;新零售行业返璞归真&#xff0c;从线上销售重返线下发展&#xff0c;满足消费者更加多元化的需求&#xff0c;国内家居集合店如井喷式崛起。为在激烈的市场竞争中立于不败之地&#xff0c;西范优选专注于加强管理能力、优化协作效率的“内功修炼”&#xf…...

LabVIEW实现Voronoi图绘制功能

该 LabVIEW 虚拟仪器&#xff08;VI&#xff09;借助 MathScript 节点&#xff0c;实现基于手机信号塔位置计算 Voronoi 图的功能。通过操作演示&#xff0c;能直观展示 Voronoi 图在空间划分上的应用。 各部分功能详细说明 随机地形创建部分 功能&#xff1a;根据 “Maximum a…...

【C++基础知识】namespace前加 inline

在C中&#xff0c;inline namespace&#xff08;内联命名空间&#xff09;是一种特殊的命名空间声明方式&#xff0c;inline关键字在这里的含义是让该命名空间的内容在其外层命名空间中“直接可见”&#xff0c;从而简化代码的版本管理和符号查找规则。以下是详细解释&#xff…...

离线部署kubernetes

麒麟Linux服务器 AMR架构 &#x1f9f0; 离线部署 Kubernetes v1.25.9&#xff08;麒麟系统 Docker&#xff09; 一、验证Docker部署状态 ‌检查Docker服务运行状态‌ systemctl status docker 预期输出应显示 Active: active (running)&#xff0c;表明服务已启动‌18。 ‌…...

【AI提示词】私人教练

提示说明 以专业且细致的方式帮助客户实现健康与健身目标&#xff0c;提升整体生活质量。 提示词 # Role: 私人教练## Profile - language: 中文 - description: 以专业且细致的方式帮助客户实现健康与健身目标&#xff0c;提升整体生活质量 - background: 具备丰富的健身经…...

爬虫学习——获取动态网页信息

对于静态网页可以直接研究html网页代码实现内容获取&#xff0c;对于动态网页绝大多数都是页面内容是通过JavaScript脚本动态生成(也就是json数据格式)&#xff0c;而不是静态的&#xff0c;故需要使用一些新方法对其进行内容获取。凡是通过静态方法获取不到的内容&#xff0c;…...

第54讲:总结与前沿展望——农业智能化的未来趋势与研究方向

目录 一、本板块内容回顾:人工智能助力农业的多元化应用 ✅ 精准农业与AI ✅ 农业金融与AI ✅ AI与农业政策 ✅ 农业物联网与AI 二、前沿趋势与研究方向:迈向智能、可持续农业的未来 1. AIGC(生成式AI)在农业中的应用 2. 数字孪生农业:虚拟与现实的无缝对接 3. A…...

创新项目实训开发日志4

一、开发简介 核心工作内容&#xff1a;logo实现、注册实现、登录实现、上传gitee 工作时间&#xff1a;第十周 二、logo实现 1.设计logo 2.添加logo const logoUrl new URL(/assets/images/logo.png, import.meta.url).href <div class"aside-first">…...

常见接口测试常见面试题(JMeter)

JMeter 是 Apache 提供的开源性能测试工具&#xff0c;主要用于对 Web 应用、REST API、数据库、FTP 等进行性能、负载和功能测试。​它支持多种协议&#xff0c;如 HTTP、HTTPS、JDBC、SOAP、FTP 等。 在一个线程组中&#xff0c;JMeter 的执行顺序通常为&#xff1a;配置元件…...

发布事件和Insert数据库先后顺序

代码解释 csharp await PublishCreatedAsync(entity).ConfigureAwait(false); await Repository.InsertAsync(entity).ConfigureAwait(false);PublishCreatedAsync(entity)&#xff1a;这是一个异步方法&#xff0c;其功能是发布与实体创建相关的事件。此方法或许会通知其他组…...

函数重载(Function Overloading)

1. 函数重载的核心概念 函数重载允许在 同一作用域内定义多个同名函数&#xff0c;但它们的 参数列表&#xff08;参数类型、顺序或数量&#xff09;必须不同。编译器在编译时根据 调用时的实参类型和数量 静态选择最匹配的函数版本。 2. 源码示例&#xff1a;基础函数重载 示…...

CGAL 网格等高线计算

文章目录 一、简介二、实现代码三、实现效果一、简介 这里等高线的计算其实很简单,使用不同高度的水平面与网格进行相交,最后获取不同高度的相交线即可。 二、实现代码 #include <iostream> #include <iterator> #include <map>...

计算机组成与体系结构:缓存(Cache)

目录 为什么需要 Cache&#xff1f; &#x1f9f1; Cache 的分层设计 &#x1f539; Level 1 Cache&#xff08;L1 Cache&#xff09;一级缓存 &#x1f539; Level 2 Cache&#xff08;L2 Cache&#xff09;二级缓存 &#x1f539; Level 3 Cache&#xff08;L3 Cache&am…...

Flutter 在全新 Platform 和 UI 线程合并后,出现了什么大坑和变化?

Flutter 在全新 Platform 和 UI 线程合并后&#xff0c;出现了什么大坑和变化&#xff1f; 在两个月前&#xff0c;我们就聊过 3.29 上《Platform 和 UI 线程合并》的具体原因和实现方式&#xff0c;而事实上 Platform 和 UI 线程合并&#xff0c;确实为后续原生语言和 Dart 的…...

开发 MCP Proxy(代理)也可以用 Solon AI MCP 哟!

MCP 有三种通讯方式&#xff1a; 通道说明备注stdio本地进程内通讯现有sse http远程 http 通讯现有streamable http远程 http 通讯&#xff08;MCP 官方刚通过决定&#xff0c;mcp-java-sdk 还没实现&#xff09; 也可以按两大类分&#xff1a; 本地进程间通讯远程通讯&…...

JetBrains GoLang IDE无限重置试用期,适用最新2025版

注意本文仅用于学习使用&#xff01;&#xff01;&#xff01; 本文在重置2024.3.5版本亲测有效&#xff0c;环境为window(mac下应该也一样奏效) 之前eval-reset插件只能在比较低的版本才能起作用。 总结起来就一句&#xff1a;卸载重装&#xff0c;额外要删掉旧安装文件和注册…...

python中socket(套接字)库详细解析

目录 1. 前言 2. socket 库基础 2.1 什么是 socket&#xff1f; 2.2 socket 的类型 3. 基于 TCP 的 socket 编程 3.1 TCP 服务器端代码示例 3.2 TCP 客户端代码示例 3.3 代码分析 4. 基于 UDP 的 socket 编程 4.1 UDP 服务器端代码示例 4.2 UDP 客户端代码示例 4.3…...

鸿蒙-状态管理V1和V2在ForEach循环渲染的表现

目录 前提遇到的问题换V2呗 状态管理V2已经出来好长时间了&#xff0c;移除GAP说明也有一段时间了&#xff0c;相信有一部分朋友已经开始着手从V1迁移到V2了&#xff0c;应该也踩了不少坑。 下面向大家分享一下我使用状态管理V1和Foreach时遇到的坑&#xff0c;以及状态管理V2在…...

深入了解递归、堆与栈:C#中的内存管理与函数调用

在编程中&#xff0c;理解如何有效地管理内存以及如何控制程序的执行流程是每个开发者必须掌握的基本概念。C#作为一种高级编程语言&#xff0c;其内存管理和函数调用机制包括递归、堆与栈。本文将详细讲解这三者的工作原理、用途以及它们在C#中的实现和应用。 1. 递归 (Recur…...