最小二乘2D圆拟合(高斯牛顿法)

news2024/9/25 2:23:25

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

本期话题:最小二乘2D圆拟合

相关背景资料
点击前往

2D圆拟合输入和输出要求

输入

  1. 8到50个点,全部采样自圆上,z轴坐标都为0。
  2. 每个点3个坐标,坐标精确到小数点后面20位。
  3. 坐标单位是mm, 范围[-500mm, 500mm]。

tips
输入虽然是严格来自2D圆,但是由于浮点表示,记录的值和真实值始终是有微小的误差,点云到拟合圆的距离不可能完全为0。

输出

  1. 1点X0表示 圆心,用三个坐标表示。
  2. 圆半径r。

精度要求

  1. X0点到标准圆心距离不能超过0.0001mm。
  2. r与标准半径的差不能超过0.0001mm。

2D圆优化标函数

根据论文,圆拟合转化成数学表示如下:

圆参数化表示

  1. 圆心为1点X0 = (x0, y0)。
  2. 半径r。

圆方程 ( x − x 0 ) 2 + ( y − y 0 ) 2 = r 2 圆方程 (x-x_0)^2+(y-y_0)^2=r^2 圆方程(xx0)2+(yy0)2=r2

点到2D圆距离

第i个点 pi(xi, yi, zi)。

根据点乘得到距离

d i = ∥ ( p i − X 0 ) ∥ − r d_i =\left \| (p_i-X_0) \right \|-r di=(piX0)r

展开一下:

d i = r i − r d_i = r_i -r di=rir

r i = ( x i − x 0 ) 2 + ( y i − y 0 ) 2 r_i = \sqrt{(x_i-x_0)^2 + (y_i-y_0)^2} ri=(xix0)2+(yiy0)2

最小二乘优化能量方程

能量方程 E = f ( X 0 , r ) = ∑ 1 n d i 2 E=f(X0, r)=\displaystyle \sum _1^n {d_i^2} E=f(X0,r)=1ndi2

上式是一个4元二次函数中,X0, r是未知量,拟合2D圆的过程也可以理解为优化X0, r使得方程E最小。

可以对上述方程求导,使得导数等于0取得最值。但是求导后会变成一个比较复杂的方程组,不好解。可以使用高斯牛顿迭代法来求解。

高斯牛顿迭代法

用于解非线性最小二乘问题。同时,高斯牛顿法需要比较可靠的初值,所以寻找目标函数的初值也是一个比较关键的步骤。

基本原理

设 a = ( a 0 , a 1 , . . . , a n ) 是待求解向量, a ^ 是初始给定值, a = a ^ + Δ a , Δ a 是我们每次迭代后移动的量 设 a=(a_0, a_1,...,a_n)是待求解向量,\widehat {a} 是初始给定值,a = \widehat {a} +\Delta a, \Delta a 是我们每次迭代后移动的量 a=(a0,a1,...,an)是待求解向量,a 是初始给定值,a=a +Δa,Δa是我们每次迭代后移动的量

定义距离函数为 F ( x , a ) , d i = F ( x i , a ) , 进行泰勒 1 阶展开, F ( x , a ) = F ( x , a ^ ) + ∂ F ∂ a ^ Δ a = F ( x , a ^ ) + J Δ a 定义距离函数为 F(x, a), d_i = F(x_i, a), 进行泰勒1阶展开, F(x, a) = F(x, \widehat a) + \frac {\partial F}{\partial \widehat a}\Delta a = F(x, \widehat a) + J\Delta a 定义距离函数为F(x,a),di=F(xi,a),进行泰勒1阶展开,F(x,a)F(x,a )+a FΔa=F(x,a )+JΔa

每次迭代,其实就是希望通过调整 Δ a 使得 J Δ a = − F ( x , a ^ ) 每次迭代,其实就是希望通过调整\Delta a 使得 J\Delta a = -F(x, \widehat a) 每次迭代,其实就是希望通过调整Δa使得JΔaF(x,a )

J = [ ∂ F ( x 0 , a ) ∂ a 0 ∂ F ( x 0 , a ) ∂ a 1 . . . ∂ F ( x 0 , a ) ∂ a n ∂ F ( x 1 , a ) ∂ a 0 ∂ F ( x 1 , a ) ∂ a 1 . . . ∂ F ( x 1 , a ) ∂ a n . . . . . . . . . . . . ∂ F ( x n , a ) ∂ a 0 ∂ F ( x n , a ) ∂ a 1 . . . ∂ F ( x n , a ) ∂ a n ] J = \begin {bmatrix} \frac {\partial F(x_0, a)} {\partial a_0} & \frac {\partial F(x_0, a)} {\partial a_1} & ...& \frac {\partial F(x_0, a)} {\partial a_n} \\ \\ \frac {\partial F(x_1, a)} {\partial a_0} & \frac {\partial F(x_1, a)} {\partial a_1} & ...& \frac {\partial F(x_1, a)} {\partial a_n} \\\\ ... & ... & ...& ... \\ \\ \frac {\partial F(x_n, a)} {\partial a_0} & \frac {\partial F(x_n, a)} {\partial a_1} & ...& \frac {\partial F(x_n, a)} {\partial a_n} \end {bmatrix} J= a0F(x0,a)a0F(x1,a)...a0F(xn,a)a1F(x0,a)a1F(x1,a)...a1F(xn,a)............anF(x0,a)anF(x1,a)...anF(xn,a)

F ( x , a ^ ) = [ d 1 d 2 . . . d m ] F(x, \widehat a) = \begin {bmatrix} d_1 \\ d_2 \\... \\ d_m \end {bmatrix} F(x,a )= d1d2...dm

J Δ a = − F ( x , a ^ ) , 解出 Δ a , 更新 a = a ^ + Δ a , 持续迭代直到 Δ a 足够小 J\Delta a = -F(x,\widehat a), 解出\Delta a ,更新 a = \widehat {a} +\Delta a, 持续迭代直到\Delta a足够小 JΔa=F(x,a ),解出Δa,更新a=a +Δa,持续迭代直到Δa足够小

算法描述

Ji, di的计算。

对3个未知数求导结果如下:

∂ d i ∂ x 0 = 1 2 ( x i − x 0 ) 2 + ( y i − y 0 ) 2 ⋅ ( x i − x 0 ) ⋅ − 1 = − ( x i − x 0 ) / r i \frac {\partial d_i} {\partial x_0}=\frac {1} {2 \sqrt{(x_i-x_0)^2 + (y_i-y_0)^2}}\cdot(x_i-x_0)\cdot -1 = -(x_i-x_0)/r_i x0di=2(xix0)2+(yiy0)2 1(xix0)1=(xix0)/ri

∂ d i ∂ y 0 = 1 2 ( x i − x 0 ) 2 + ( y i − y 0 ) 2 ⋅ ( y i − y 0 ) ⋅ − 1 = − ( y i − y 0 ) / r i \frac {\partial d_i} {\partial y_0}=\frac {1} {2 \sqrt{(x_i-x_0)^2 + (y_i-y_0)^2}}\cdot(y_i-y_0)\cdot -1 = -(y_i-y_0)/r_i y0di=2(xix0)2+(yiy0)2 1(yiy0)1=(yiy0)/ri

∂ d i ∂ r = − 1 \frac {\partial d_i} {\partial r}=-1 rdi=1

  1. 确定圆初值

  2. 根据上述公式构建 J ⋅ ( [ p x 0 p y 0 p r ] ) = − D = − [ d 1 d 2 . . . d n ] J \cdot \left(\begin {bmatrix}p_{x_0} \\ p_{y_0}\\p_{r} \end {bmatrix} \right)=-D=-\begin {bmatrix}d_1 \\ d_2\\...\\d_n \end {bmatrix} J px0py0pr =D= d1d2...dn

  3. 求解 Δ p \Delta p Δp

  4. 更新解
    x 0 = x 0 + p x 0 y 0 = y 0 + p y 0 r = r + p r \begin {array}{c} x_0 = x_0+p_{x_0} \\y_0 = y_0+p_{y_0} \\ r=r+p_r \end {array} x0=x0+px0y0=y0+py0r=r+pr

  5. 重复2直到收敛

初值确定

先将圆建立线性的能量方程。
可以作一个近似转换,把半径先平方再相减。

F = ∑ i = 1 n f i 2 F=\displaystyle \sum_{i=1}^{n} f_i^2 F=i=1nfi2

f i = r i 2 − r 2 f_i = r_i^2-r^2 fi=ri2r2

展开后

f i = ( x i − x 0 ) 2 + ( y i − y 0 ) 2 − r 2 = − 2 x i x 0 − 2 y i y 0 + ( x 0 2 + y 0 2 − r 2 ) + ( x i 2 + y i 2 ) f_i = (x_i-x_0)^2 + (y_i-y_0)^2-r^2 \\ = -2x_ix_0-2y_iy_0 + (x_0^2+y_0^2-r^2)+(x_i^2+y_i^2) fi=(xix0)2+(yiy0)2r2=2xix02yiy0+(x02+y02r2)+(xi2+yi2)

我们希望fi都为0。

可以令 ρ = x 0 2 + y 0 2 − r 2 可以令\rho=x_0^2+y_0^2-r^2 可以令ρ=x02+y02r2

建立方程组

A [ x 0 y 0 ρ ] = B A i = ( 2 x i , 2 y i , − 1 ) , b i = x i 2 + y i 2 解得上述方程后 r = x 0 2 + y 0 2 − ρ A \left [\begin {array}{c} x_0\\y_0\\\rho \end{array}\right ]=B \\A_i=(2x_i, 2y_i, -1), b_i=x_i^2+y_i^2 \\解得上述方程后 r=\sqrt{x_0^2+y_0^2-\rho} A x0y0ρ =BAi=(2xi,2yi,1),bi=xi2+yi2解得上述方程后r=x02+y02ρ

代码实现

代码链接:https://gitcode.com/chenbb1989/3DAlgorithm/tree/master/CBB3DAlgorithm/Fitting

#include "FittingCircle2D.h"
#include <Eigen/Dense>


namespace Gauss {
	double F(Fitting::Circle2D circle, const Point& p)
	{
		double ri = Eigen::Vector2d(p.x() - circle.center.x(), p.y() - circle.center.y()).norm();
		return ri-circle.r;
	}
	double GetError(Fitting::Circle2D circle, const std::vector<Eigen::Vector3d>& points)
	{
		double err = 0;
		for (auto& p : points) {
			double d = F(circle, p);
			err += d * d;
		}
		err /= points.size();
		return err;
	}
	Fitting::Matrix FittingCircle2D::Jacobi(const std::vector<Eigen::Vector3d>& points)
	{
		Fitting::Matrix J(points.size(), 3);
		for (int i = 0; i < points.size(); ++i) {
			auto& p = points[i];
			double ri = (Eigen::Vector2d(p.x(), p.y()) - circle.center).norm();
			J(i, 0) = -(p.x()-circle.center.x())/ri;
			J(i, 1) = -(p.y() - circle.center.y()) / ri;
			J(i, 2) = -1;
		}
		return J;
	}

	void FittingCircle2D::afterHook(const Eigen::VectorXd& xp)
	{
		circle.center += Eigen::Vector2d(xp(0), xp(1));
		circle.r = xp(2);
	}
	Eigen::VectorXd FittingCircle2D::getDArray(const std::vector<Eigen::Vector3d>& points)
	{
		Eigen::VectorXd D(points.size());
		for (int i = 0; i < points.size(); ++i)D(i) =F(points[i]);
		return D;
	}
	bool FittingCircle2D::GetInitFit(const std::vector<Eigen::Vector3d>& points)
	{
		if (points.size() < 3)return false;
		Fitting::Matrix A(points.size(), 3);
		Eigen::VectorXd B(points.size());

		for (int i = 0; i < points.size(); ++i) {
			A(i, 0) = 2 * points[i].x();
			A(i, 1) = 2 * points[i].y();
			A(i, 2) = -1;

			B(i) = Eigen::Vector2d(points[i].x(), points[i].y()).squaredNorm();
		}
		Eigen::VectorXd xp;
		// 求解 Axp = B  https://blog.csdn.net/ABC_ORANGE/article/details/104489257/
		xp = A.colPivHouseholderQr().solve(B);
		circle.center.x() = xp(0);
		circle.center.y() = xp(1);
		circle.r = sqrt(xp(0) * xp(0) + xp(1) * xp(1) - xp(2));

		return true;
	}
	double FittingCircle2D::F(const Eigen::Vector3d& p)
	{
		return Gauss::F(circle, p);
	}
	double FittingCircle2D::GetError(const std::vector<Eigen::Vector3d>& points)
	{
		return Gauss::GetError(circle, points);
	}
	void FittingCircle2D::Copy(void* ele)
	{
		memcpy(ele, &circle, sizeof(Fitting::Circle2D));
	}
}

测试结果

PS D:\selfad\3DAlgorithm_build\CBB3DAlgorithm\FittingTest\bin> .\FittingRun.exe FittingCircle2DTest
2.7131524091739308875e-25
1.2353000000002221093
22.332233299999675324
12222.234300000000076
circle2d test pass

本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。创作不易,帮忙点击公众号的链接。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1408521.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法练习-螺旋矩阵(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;数组 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。以下内容均为个人笔记&#xff0c;旨在督促自己认真学习。 题目 给定一个正整数n&#xff0c;生成一个包含1到 n^2 所有元…

基于Vue+Canvas实现的画板绘画以及保存功能,解决保存没有背景问题

基于VueCanvas实现的画板绘画以及保存功能 本文内容设计到的画板的js部分内容来源于灵感来源引用地址&#xff0c;然后我在此基础上&#xff0c;根据自己的需求做了修改&#xff0c;增加了其他功能。 下面展示了完整的前后端代码 这里写目录标题 基于VueCanvas实现的画板绘…

面向对象、封装、继承、多态、JavaBean

二、面向对象 什么是对象 什么是对象&#xff1f;之前我们讲过&#xff0c;对象就是计算机中的虚拟物体。例如 System.out&#xff0c;System.in 等等。然而&#xff0c;要开发自己的应用程序&#xff0c;只有这些现成的对象还远远不够。需要我们自己来创建新的对象。 1. 抽…

confluence模版注入漏洞_CVE-2023-22527

1. 漏洞简介 Confluence是Atlassian公司开发的一款专业的企业知识管理与协同软件&#xff0c;可用于构建企业wiki。 Confluence Data Center和Confluence Server多个受影响版本中存在模板注入漏洞&#xff0c;未经身份验证的威胁者可利用该漏洞在受影响的实例上实现远程代码执…

暗藏危险,警惕钓鱼邮件!

叮 您有一份福利待查收 您的信息资产需要排查 您的账户异常需要验证 这些看似“重要”的邮件 都藏着攻击者的恶意嘴脸 随着网络安全防护和建设的重要性日益凸显&#xff0c;国家安全、企业安全、合规需求及业务驱动等各个方面都亟需将网络安全作为基石。在企业业务转型发展…

v-on、事件修饰符、v-model、一些常用指令

v-on 事件修饰符 Vue.js 为 v-on 提供了事件修饰符来处理 DOM 事件细节&#xff0c;如&#xff1a;event.preventDefault() 或 event.stopPropagation()。 Vue.js 通过由点 . 表示的指令后缀来调用修饰符。 .stop - 阻止冒泡 .prevent - 阻止默认事件 .capture - 阻止捕获 .s…

【jetson笔记】解决vscode远程调试qt.qpa.xcb: could not connect to display报错

配置x11转发 jetson远程安装x11转发 安装Xming Xming下载 安装完成后打开安装目录C:\Program Files (x86)\Xming 用记事本打开X0.hosts文件&#xff0c;添加jetson IP地址 后续IP改变需要重新修改配置文件 localhost 192.168.107.57打开Xlaunch Win菜单搜索Xlaundch打开 一…

递归和尾递归(用C语言解斐波那契和阶乘问题)

很多人都对递归有了解&#xff0c;但是为尾递归很少&#xff0c;所以这次来专门讲一讲关于尾递归的一些问题。 什么是尾递归 如果一个函数中所有递归形式的调用都出现在函数的末尾&#xff0c;我们称这个递归函数是尾递归的。因为在一些题目的做法中&#xff0c;我们可以发现…

JavaFX场景入门

目录 JAVAFX jdk1.8以上引入javafx类库 JDK11JAVAFX(eclipse) 小知识点 舞台Stage platform、screen类 Scene场景类 查看电脑屏幕宽高 Group容器 JAVAFX项目 Image javafx场景 javaFx文本 javaFX颜色 JAVAFX jdk1.8以上引入javafx类库 JDK11JAVAFX(eclipse) 方式…

MySQL分组,获取组内最新的10条数据

一、记录 记录一次SQL&#xff0c;最近在项目中遇到了一个相对比较复杂的SQL。 要求依据分组&#xff0c;获取每个分组后的前10条数据。 分组查询最新的数据&#xff0c;应该都做过&#xff0c;但是获取前10条数据&#xff0c;还是没处理过的。 二、处理 2.1 前期数据准备 …

数据分析-Pandas如何用图把数据展示出来

数据分析-Pandas如何用图把数据展示出来 俗话说&#xff0c;一图胜千语&#xff0c;对人类而言一串数据很难立即洞察出什么&#xff0c;但如果展示图就能一眼看出来门道。数据整理后&#xff0c;如何画图&#xff0c;画出好的图在数据分析中成为关键的一环。 数据表&#xff…

【进入游戏行业选游戏特效还是技术美术?】

进入游戏行业选游戏特效还是技术美术&#xff1f; 游戏行业正处于蓬勃发展的黄金时期&#xff0c;科技的进步推动了游戏技术和视觉艺术的飞速革新。在这个创意和技术挑战交织的领域里&#xff0c;游戏特效和技术美术岗位成为了许多人追求的职业目标。 这两个岗位虽然紧密关联…

5.ROC-AUC机器学习模型性能的常用的评估指标

最近回顾机器学习基础知识部分的时候&#xff0c;看到了用于评估机器学习模型性能的ROC曲线。再次记录一下&#xff0c;想起之前学习的时候的茫然&#xff0c;希望这次可以更加清晰的了解这一指标。上课的时候听老师提起过&#xff0c;当时没有认真去看&#xff0c;所以这次可以…

硅像素传感器文献调研(八)

2000硅微带探测器的物理模拟&#xff1a;电极几何形状对临界电场的影响。 2000硅微带探测器的物理模拟&#xff1a;电极几何形状对临界电场的影响 摘要 本文介绍了交流耦合硅微带探测器的计算机分析。这项研究的目的是调查的主要几何参数负责潜在的关键影响&#xff0c;如早期…

【GitHub项目推荐--多功能 Steam 工具箱】【转载】

Steam 是一个开源跨平台的多功能 Steam 工具箱&#xff0c;此工具的大部分功能都是需要下载安装 Steam 才能使用。 功能包括网络加速、脚本配置、账号切换、库存管理、自动挂卡、游戏工具&#xff0c;比如强制游戏窗口使用无边框窗口化。 开源地址&#xff1a;https://github…

c++:类和对象(5),运算符重载

目录 运算符重载概念&#xff1a; 运算符重载 1.成员函数重载号 2.全局函数重载号 打印结果&#xff1a; <<运算符重载 递增运算符重载 简单例子 输出结果为&#xff1a; 赋值运算符重载 如何重载 输出结果为&#xff1a; 什么时候重载 关系运算符重载 简单例…

【并发编程】同步模式之保护性暂停

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程 ⛺️稳中求进&#xff0c;晒太阳 同步模式之保护性暂停 这个模式用到的基础就是wait-notify 详情可以看这篇文章》:【并发编程】wait/notify 即Guarded Suspension,用在一个线…

鸿蒙开发 状态管理

最近学习鸿蒙开发。 状态管理&#xff1a; State -> Prop 单向传递&#xff1b; stateprop: State -> Prop 单向传递 State -> Link 双向传递&#xff1b;

Windows脚本:监控并自动重启某个进程

Windows脚本&#xff1a;监控自动并重启某个进程 一、简介二 .bat脚本方式2.1 编制脚本2.2 创建并运行脚本2.3 设置关闭cmd窗口 三、使用VBScript脚本方式3.1 编制脚本3.2 运行脚本 四、设置脚本开机自启动五、某些软件加入启动项后&#xff0c;开机不会自动启动的解决方法 在实…

代码随想录算法训练营第十四天|二叉树基础-二叉树迭代-二叉树

文章目录 二叉树基础二叉树种类满二叉树完全二叉树二叉搜索树平衡二叉搜索树 二叉树的存储方式链式存储顺序存储 二叉树的遍历方式二叉树的定义 二叉树的递归遍历144.二叉树的前序遍历代码&#xff1a; 145.二叉树的后序遍历代码&#xff1a; 94. 二叉树的中序遍历代码 二叉树的…