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

news2024/9/27 2:49:12

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

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

相关背景资料
点击前往

在这里插入图片描述

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

输入

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

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

输出

  1. 1点X0表示 圆心,用三个坐标表示。
  2. 法向A代表圆所在平面法向。
  3. 圆半径r。

精度要求

  1. X0点到标准圆心距离不能超过0.0001mm。
  2. 法向A与圆法向夹角不能超过0.0000001rad。
  3. r与标准半径的差不能超过0.0001mm。

3D圆优化标函数

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

圆参数化表示

  1. 圆心为1点X0 = (x0, y0, z0)。
  2. 法向A=(a,b,c)。
  3. 半径r。

圆方程 ( x − x 0 ) 2 + ( y − y 0 ) 2 + ( z − z 0 ) 2 = r 2 a ( x − x 0 ) + b ( y − y 0 ) + c ( z − z 0 ) = 0 圆方程\begin {array}{c}(x-x_0)^2+(y-y_0)^2+(z-z_0)^2=r^2 \\ a(x-x_0)+b(y-y_0)+c(z-z_0)=0 \end {array} 圆方程(xx0)2+(yy0)2+(zz0)2=r2a(xx0)+b(yy0)+c(zz0)=0

能量方程

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

定义距离如图

在这里插入图片描述
根据勾股定理,黄色线为距离。

d i = e i 2 + f i 2 d_i = \sqrt{e_i^2+f_i^2} di=ei2+fi2

根据定义得到能量方程

E = ∑ 1 n d i 2 = ∑ 1 n ( e i 2 + f i 2 ) E=\displaystyle \sum _1^n {d_i^2}=\displaystyle \sum _1^n {(e_i^2+f_i^2)} E=1ndi2=1n(ei2+fi2)

e i = a ( x i − x 0 ) + b ( y i − y 0 ) + c ( z i − z 0 ) f i = u i 2 + v i 2 + w i 2 − r \begin {array}{l} e_i =a(x_i-x_0)+b(y_i-y_0)+c(z_i-z_0)\\ f_i=\sqrt{u_i^2+v_i^2+w_i^2}-r\end {array} ei=a(xix0)+b(yiy0)+c(ziz0)fi=ui2+vi2+wi2 r

u i = c ( y i − y 0 ) − b ( z i − z 0 ) v i = a ( z i − z 0 ) − c ( x i − x 0 ) w i = b ( x i − x 0 ) − a ( y i − y 0 ) \begin {array}{l}u_i =c(y_i-y_0)-b(z_i-z_0)\\ v_i=a(z_i-z_0)-c(x_i-x_0)\\ w_i=b(x_i-x_0)-a(y_i-y_0)\end {array} ui=c(yiy0)b(ziz0)vi=a(ziz0)c(xix0)wi=b(xix0)a(yiy0)

最小二乘优化能量方程

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

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

上述方程直接求导不好解,可以使用高斯牛顿迭代法。

高斯牛顿迭代法

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

基本原理

设 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足够小

用2个数表示法向

如果直接拿3个参数表示法向去做迭代,1是比较麻烦,会出现比较难解的方向,2是法向长度不固定,结果不唯一。

当直线与Z轴偏差比较小的时候可以使用2个参数来表示。

在这里插入图片描述
如上图,绿线为Z轴,橙色线为XOY平面。

由于法向与Z轴比较相近,可以设法向为(a, b, 1), a,b 是比较小的量。

模型简化

为了使用上述方法,当得到一个拟合初值后,需要先将中心线旋转致Z轴,把圆心移致0点。

此时,
x0=y0=z0=0.
a=b=0, c=1.

e i = z i f i = ( x i 2 + y i 2 ) − r \begin {array}{l} e_i =z_i\\ f_i=\sqrt{(x_i^2+y_i^2)}-r\end {array} ei=zifi=(xi2+yi2) r

算法描述

E = ∑ 1 n ( e i 2 + f i 2 ) = ∑ 1 n e i 2 + ∑ 1 n f i 2 E=\displaystyle \sum _1^n {(e_i^2+f_i^2)}=\displaystyle \sum _1^n {e_i^2}+\displaystyle \sum _1^n{f_i^2} E=1n(ei2+fi2)=1nei2+1nfi2

我们希望ei,fi 都等于0。
可以对ei, fi 分别求偏导

J, D的计算。

J = ∂ e 1 ∂ x 0 ∂ e 1 ∂ y 0 ∂ e 1 ∂ a ∂ e 1 ∂ b ∂ e 1 ∂ r ∂ e 2 ∂ x 0 ∂ e 2 ∂ y 0 ∂ e 2 ∂ a ∂ e 2 ∂ b ∂ e 2 ∂ r . . . . . . . . . . . . . . . ∂ e n ∂ x 0 ∂ e n ∂ y 0 ∂ e n ∂ a ∂ e n ∂ b ∂ e n ∂ r ∂ f 1 ∂ x 0 ∂ f 1 ∂ y 0 ∂ f 1 ∂ a ∂ f 1 ∂ b ∂ f 1 ∂ r ∂ f 2 ∂ x 0 ∂ f 2 ∂ y 0 ∂ f 2 ∂ a ∂ f 2 ∂ b ∂ f 2 ∂ r . . . . . . . . . . . . . . . ∂ f n ∂ x 0 ∂ f n ∂ y 0 ∂ f n ∂ a ∂ f n ∂ b ∂ f n ∂ r ,   D = e 1 e 2 . . . e n f 1 f 2 . . . f n J= \begin{array}{l} \frac {\partial e_1}{\partial x_0}& \frac {\partial e_1}{\partial y_0}& \frac {\partial e_1}{\partial a}& \frac {\partial e_1}{\partial b}& \frac {\partial e_1}{\partial r} \\ \frac {\partial e_2}{\partial x_0}& \frac {\partial e_2}{\partial y_0}& \frac {\partial e_2}{\partial a}& \frac {\partial e_2}{\partial b}& \frac {\partial e_2}{\partial r}\\...&...&...&...&...\\\frac {\partial e_n}{\partial x_0}& \frac {\partial e_n}{\partial y_0}& \frac {\partial e_n}{\partial a}& \frac {\partial e_n}{\partial b}& \frac {\partial e_n}{\partial r}\\ \frac {\partial f_1}{\partial x_0}& \frac {\partial f_1}{\partial y_0}& \frac {\partial f_1}{\partial a}& \frac {\partial f_1}{\partial b}& \frac {\partial f_1}{\partial r} \\ \frac {\partial f_2}{\partial x_0}& \frac {\partial f_2}{\partial y_0}& \frac {\partial f_2}{\partial a}& \frac {\partial f_2}{\partial b}& \frac {\partial f_2}{\partial r}\\...&...&...&...&...\\\frac {\partial f_n}{\partial x_0}& \frac {\partial f_n}{\partial y_0}& \frac {\partial f_n}{\partial a}& \frac {\partial f_n}{\partial b}& \frac {\partial f_n}{\partial r} \end {array}, \ D= \begin{array}{l} e_1\\e_2\\...\\e_n\\ f_1\\f_2\\...\\f_n \end {array} J=x0e1x0e2...x0enx0f1x0f2...x0fny0e1y0e2...y0eny0f1y0f2...y0fnae1ae2...aenaf1af2...afnbe1be2...benbf1bf2...bfnre1re2...renrf1rf2...rfn, D=e1e2...enf1f2...fn

6个未知分别对e_i, f_i求导结果如下:

∂ e i ∂ x 0 = a ( x i − x 0 ) + b ( y i − y 0 ) + c ( z i − z 0 ) ∂ x 0 = − a = 0 \frac {\partial e_i} {\partial x_0}=\frac {a(x_i-x_0)+b(y_i-y_0)+c(z_i-z_0)} {\partial x_0} = -a=0 x0ei=x0a(xix0)+b(yiy0)+c(ziz0)=a=0

∂ e i ∂ y 0 = a ( x i − x 0 ) + b ( y i − y 0 ) + c ( z i − z 0 ) ∂ y 0 = − b = 0 \frac {\partial e_i} {\partial y_0}=\frac {a(x_i-x_0)+b(y_i-y_0)+c(z_i-z_0)} {\partial y_0} = -b=0 y0ei=y0a(xix0)+b(yiy0)+c(ziz0)=b=0

∂ e i ∂ z 0 = a ( x i − x 0 ) + b ( y i − y 0 ) + c ( z i − z 0 ) ∂ z 0 = − c = − 1 \frac {\partial e_i} {\partial z_0}=\frac {a(x_i-x_0)+b(y_i-y_0)+c(z_i-z_0)} {\partial z_0} = -c=-1 z0ei=z0a(xix0)+b(yiy0)+c(ziz0)=c=1

∂ e i ∂ a = a ( x i − x 0 ) + b ( y i − y 0 ) + c ( z i − z 0 ) ∂ a = x i − x 0 = x i \frac {\partial e_i} {\partial a}=\frac {a(x_i-x_0)+b(y_i-y_0)+c(z_i-z_0)} {\partial a} = x_i-x_0=x_i aei=aa(xix0)+b(yiy0)+c(ziz0)=xix0=xi

∂ e i ∂ b = a ( x i − x 0 ) + b ( y i − y 0 ) + c ( z i − z 0 ) ∂ b = y i − y 0 = y i \frac {\partial e_i} {\partial b}=\frac {a(x_i-x_0)+b(y_i-y_0)+c(z_i-z_0)} {\partial b} = y_i-y_0=y_i bei=ba(xix0)+b(yiy0)+c(ziz0)=yiy0=yi

∂ e i ∂ r = a ( x i − x 0 ) + b ( y i − y 0 ) + c ( z i − z 0 ) ∂ r = 0 \frac {\partial e_i} {\partial r}=\frac {a(x_i-x_0)+b(y_i-y_0)+c(z_i-z_0)} {\partial r} = 0 rei=ra(xix0)+b(yiy0)+c(ziz0)=0

回顾一下

u i = c ( y i − y 0 ) − b ( z i − z 0 ) v i = a ( z i − z 0 ) − c ( x i − x 0 ) w i = b ( x i − x 0 ) − a ( y i − y 0 ) \begin {array}{l}u_i =c(y_i-y_0)-b(z_i-z_0)\\ v_i=a(z_i-z_0)-c(x_i-x_0)\\ w_i=b(x_i-x_0)-a(y_i-y_0)\end {array} ui=c(yiy0)b(ziz0)vi=a(ziz0)c(xix0)wi=b(xix0)a(yiy0)

∂ f i ∂ x 0 = u i 2 + v i 2 + w i 2 − r ∂ x 0 = ( x i − x 0 ) 2 ∂ x 0 2 u i 2 + v i 2 + w i 2 = − x i x i 2 + y i 2 \frac {\partial f_i} {\partial x_0}=\frac {\sqrt{u_i^2+v_i^2+w_i^2}-r} {\partial x_0} \\ =\frac {\frac {(x_i-x_0)^2}{\partial x_0}}{2\sqrt{u_i^2+v_i^2+w_i^2}}\\ =\frac {-x_i}{\sqrt{x_i^2+y_i^2}} x0fi=x0ui2+vi2+wi2 r=2ui2+vi2+wi2 x0(xix0)2=xi2+yi2 xi

∂ f i ∂ y 0 = u i 2 + v i 2 + w i 2 − r ∂ y 0 = ( y i − y 0 ) 2 ∂ y 0 2 u i 2 + v i 2 + w i 2 = − y i x i 2 + y i 2 \frac {\partial f_i} {\partial y_0}=\frac {\sqrt{u_i^2+v_i^2+w_i^2}-r} {\partial y_0} \\ =\frac {\frac {(y_i-y_0)^2}{\partial y_0}}{2\sqrt{u_i^2+v_i^2+w_i^2}}\\ =\frac {-y_i}{\sqrt{x_i^2+y_i^2}} y0fi=y0ui2+vi2+wi2 r=2ui2+vi2+wi2 y0(yiy0)2=xi2+yi2 yi

∂ f i ∂ z 0 = u i 2 + v i 2 + w i 2 − r ∂ z 0 = 0 \frac {\partial f_i} {\partial z_0}=\frac {\sqrt{u_i^2+v_i^2+w_i^2}-r} {\partial z_0}=0 z0fi=z0ui2+vi2+wi2 r=0

∂ f i ∂ a = u i 2 + v i 2 + w i 2 − r ∂ a = [ a ( z i − z 0 ) − c ( x i − x 0 ) ] 2 ∂ a 2 u i 2 + v i 2 + w i 2 = − x i z i x i 2 + y i 2 \frac {\partial f_i} {\partial a}=\frac {\sqrt{u_i^2+v_i^2+w_i^2}-r} {\partial a}\\ =\frac {\frac {[a(z_i-z_0)-c(x_i-x_0)]^2}{\partial a}}{2\sqrt{u_i^2+v_i^2+w_i^2}}\\ =\frac {-x_iz_i}{\sqrt{x_i^2+y_i^2}} afi=aui2+vi2+wi2 r=2ui2+vi2+wi2 a[a(ziz0)c(xix0)]2=xi2+yi2 xizi

∂ f i ∂ b = u i 2 + v i 2 + w i 2 − r ∂ b = [ c ( y i − y 0 ) − b ( z i − z 0 ) ] 2 ∂ b 2 u i 2 + v i 2 + w i 2 = − y i z i x i 2 + y i 2 \frac {\partial f_i} {\partial b}=\frac {\sqrt{u_i^2+v_i^2+w_i^2}-r} {\partial b}\\ =\frac {\frac {[c(y_i-y_0)-b(z_i-z_0)]^2}{\partial b}}{2\sqrt{u_i^2+v_i^2+w_i^2}}\\ =\frac {-y_iz_i}{\sqrt{x_i^2+y_i^2}} bfi=bui2+vi2+wi2 r=2ui2+vi2+wi2 b[c(yiy0)b(ziz0)]2=xi2+yi2 yizi

∂ f i ∂ r = u i 2 + v i 2 + w i 2 − r ∂ r = − 1 \frac {\partial f_i} {\partial r}=\frac {\sqrt{u_i^2+v_i^2+w_i^2}-r} {\partial r} = -1 rfi=rui2+vi2+wi2 r=1

  1. 确定圆初值

  2. 将中轴通过刚体变换U至Z轴,U的构建可以参考代码
    [ x i y i z i ] = U ⋅ ( [ x i y i z i ] − [ x 0 y 0 z 0 ] ) \begin {bmatrix}x_i \\ y_i \\ z_i \end {bmatrix} = U \cdot \left (\begin {bmatrix}x_i \\ y_i \\ z_i \end {bmatrix}- \begin{bmatrix}x_0 \\ y_0 \\ z_0 \end {bmatrix}\right ) xiyizi =U xiyizi x0y0z0

  3. 根据上述公式构建 J ⋅ ( [ p x 0 p y 0 p z 0 p a p b p r ] ) = − D J \cdot \left(\begin {bmatrix}p_{x_0} \\ p_{y_0}\\ p_{z_0}\\p_a\\p_b\\p_{r} \end {bmatrix} \right)=-D J px0py0pz0papbpr =D

  4. 求解 Δ p \Delta p Δp

  5. 更新解
    [ x 0 y 0 z 0 ] = [ x 0 y 0 z 0 ] + U T ⋅ [ p x 0 p y 0 p z 0 ] [ a b c ] = U T ⋅ [ p a p b 1 ] . n o r m a l i z e ( ) r = r + p r \begin {array}{l}\\ \begin {bmatrix}x_0 \\ y_0 \\ z_0 \end {bmatrix} = \begin {bmatrix}x_0 \\ y_0 \\ z_0 \end {bmatrix} + U^T \cdot \begin{bmatrix}p_{x_0} \\ p_{y_0}\\ p_{z_0} \end {bmatrix} \\ \\\begin {bmatrix}a \\ b \\ c \end {bmatrix} = U^T \cdot \begin{bmatrix}p_a \\ p_b \\ 1 \end {bmatrix}.normalize() \\\\ r=r+p_r \end {array} x0y0z0 = x0y0z0 +UT px0py0pz0 abc =UT papb1 .normalize()r=r+pr

  6. 重复2直到收敛

初值确定

先对点进行拟合平面点击前往,在平面上拟合出2D圆点击前往。

代码实现

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

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


namespace Gauss {
	double F(Fitting::Circle circle, const Point& p)
	{
		double ei = (p - circle.center).dot(circle.orient);
		double fi = (p - circle.center).cross(circle.orient).norm() - circle.r;
		return sqrt(ei * ei + fi * fi);
	}
	double GetError(Fitting::Circle 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 CircleFitter::Jacobi(const std::vector<Eigen::Vector3d>& points)
	{
		int n = points.size();
		Fitting::Matrix J(2 * n, 6);
		for (int i = 0; i < n; ++i) {
			auto& p = points[i];

			//ei求导
			double ri = p.norm();
			J(i, 0) = 0;
			J(i, 1) = 0;
			J(i, 2) = -1;
			J(i, 3) = p.x();
			J(i, 4) = p.y();
			J(i, 5) = 0;

			//fi求导
			J(i + n, 0) = -p.x() / ri;
			J(i + n, 1) = -p.y() / ri;
			J(i + n, 2) = 0;
			J(i + n, 3) = -p.x() * p.z() / ri;
			J(i + n, 4) = -p.y() * p.z() / ri;
			J(i + n, 5) = -1;
		}
		return J;
	}

	void CircleFitter::beforHook(const std::vector<Eigen::Vector3d>& points)
	{
		U = Fitting::getRotationByOrient(circle.orient);
		for (int i = 0; i < points.size(); ++i) {
			transPoints[i] = U * (points[i] - circle.center);
		}
	}
	void CircleFitter::afterHook(const Eigen::VectorXd& xp)
	{
		circle.center += U.transpose() * Eigen::Vector3d(xp(0), xp(1), xp(2));
		circle.orient = U.transpose() * Eigen::Vector3d(xp(3), xp(4), 1).normalized();
		circle.r += xp(5);
	}
	Eigen::VectorXd CircleFitter::getDArray(const std::vector<Eigen::Vector3d>& points)
	{
		int n = points.size();
		Eigen::VectorXd D(2*points.size());
		for (int i = 0; i < points.size(); ++i) {
			auto& p = points[i];
			// ei
			D(i) = p.z();

			//fi
			D(i + n) = p.norm() - circle.r;
		}
		return D;
	}
	bool CircleFitter::GetInitFit(const std::vector<Eigen::Vector3d>& points)
	{
		if (points.size() < 3)return false;
		// 拟合平面
		Fitting::Plane plane;
		Fitting::FittingBase *fb = new FittingPlane();
		fb->Fitting(points, &plane);
		delete fb;

		// 投影到平面,并旋转致xoy平面
		U = Fitting::getRotationByOrient(plane.Orient);
		transPoints.resize(points.size());
		for (int i = 0; i < points.size(); ++i) {
			auto p = points[i];
			double d = (plane.BasePoint - p).dot(plane.Orient);
			p += d * plane.Orient;
			transPoints[i] = U * (p - plane.BasePoint);
		}

		// 拟合圆
		Fitting::Circle2D circle2d;
		fb = new FittingCircle2D();
		fb->Fitting(transPoints, &circle2d);
		delete fb;

		// 旋转回来
		circle.center = U.transpose() * Eigen::Vector3d(circle2d.center.x(), circle2d.center.y(), 0) + plane.BasePoint;
		circle.orient = plane.Orient;
		circle.r = circle2d.r;
		
		return true;
	}
	double CircleFitter::F(const Eigen::Vector3d& p)
	{
		return Gauss::F(circle, p);
	}
	double CircleFitter::GetError(const std::vector<Eigen::Vector3d>& points)
	{
		return Gauss::GetError(circle, points);
	}
	void CircleFitter::Copy(void* ele)
	{
		memcpy(ele, &circle, sizeof(Fitting::Circle));
	}
}

测试代码

#include "TestCircle.h"
#include "CircleFitter.h"
#include <iostream>

namespace Gauss {

	double TestCircle::Fitting() {
		Fitting::FittingBase* fb = new Gauss::CircleFitter();
		auto err = fb->Fitting(points, &fitResult);
		return err;
	}
	bool TestCircle::JudgeAnswer(FILE* fp) {
		ReadAnswer();
		if (!positionCmp(ans.center, fitResult.center))return false;
		if (!orientationCmp(ans.orient, fitResult.orient))return false;
		if (!radiusCmp(ans.r, fitResult.r))return false;
		return true;
	}
	void TestCircle::ReadAnswer() {
		vector<double> nums;
		if (PointCloud::readNums((suffixName + "/answer/" + fileName + ".txt"), nums)) {
			for (int i = 0; i < 3; ++i) ans.center[i] = nums[i];
			for (int i = 0; i < 3; ++i) ans.orient[i] = nums[i+3];
			ans.r = nums[6];
		}
		else {
			std::cout << "read answer error" << std::endl;
		}
	}
	void TestCircle::SaveAnswer(FILE* fp) {
		writePoint(fp, fitResult.center);
		writePoint(fp, fitResult.orient);
		writeNumber(fp, fitResult.r);
	}
}

测试结果

https://gitcode.com/chenbb1989/3DAlgorithm/blob/master/CBB3DAlgorithm/Fitting/gauss/fitting_result/result.txt

b09 : CIRCLE : pass
b10 : CIRCLE : pass
b11 : CIRCLE : pass
b12 : CIRCLE : pass
b13 : CIRCLE : pass
b14 : CIRCLE : pass
b15 : CIRCLE : pass


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

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

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

相关文章

pom文件首行报错问题处理

项目开发过程中&#xff0c;有时候在田间某个以来的时&#xff0c;会遇到pom文件首行报错&#xff0c;如下图所示 1、将鼠标移动到首行报错位置&#xff0c;点击红色❌&#xff0c;便会显示报错原因&#xff0c;这个项目遇到报错原因为“Missing artifact jdk.tools:jdk.tools:…

两个让你心跳加速的网站,赶紧收藏吧

1、方小童在线工具集 网址&#xff1a; 方小童 该网站是一款在线工具集合的网站&#xff0c;目前包含PDF文件在线转换、随机生成美女图片、精美壁纸等功能&#xff0c;喜欢的可以赶紧去试试&#xff01; 2、电子书搜索 网址&#xff1a;https://libstc.cc 很强大一个网站&a…

搭建nginx图片服务器

&#xff08;1&#xff09;将图片存储于/home/data/images目录&#xff1b; &#xff08;2&#xff09;配置nginx.conf user nginx; worker_processes 4;error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;events {worker_connections 10000; }ht…

架构师的36项修炼-08系统的安全架构设计

本课时讲解系统的安全架构。 本节课主要讲 Web 的攻击与防护、信息的加解密与反垃圾。其中 Web 攻击方式包括 XSS 跨站点脚本攻击、SQL 注入攻击和 CSRF 跨站点请求伪造攻击&#xff1b;防护手段主要有消毒过滤、SQL 参数绑定、验证码和防火墙&#xff1b;加密手段&#xff0c…

「研发部」GitFlow规范-升级版(二)

前言 上一篇文章简单整理过一次产研团队的GitFlow《Git 分支管理及Code Review 流程 (一)》 GitFlow是一种流行的Git分支管理策略&#xff0c;它提供了一种结构化的方式来管理项目的开发和发布流程。以下是GitFlow规范的主要组成部分&#xff1a; 主要分支&#xff1a; mast…

计算机毕业设计 | SpringBoot 求职招聘管理系统(附源码)

1&#xff0c;绪论 1.1 开发背景 高学历人群是网络求职者的主体&#xff0c;且结构趋向固定。而在疫情肆虐的今日&#xff0c;线上招聘成了越来越多企业和个人选择的方式。在疫情期间线下招聘转为线上招聘&#xff0c;是疫情防控的需要。不能否定的是新的招聘模式的出现一定会…

【Git】windows系统安装git教程和配置

一、何为Git Git(读音为/gɪt/)是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。 二、git安装包 有2种版本&#xff0c;Git for Windows Setup和Git for Windows Portable(便携版)两个版本都可以。 三、Git for Windows Por…

【PyQt】01-PyQt下载

文章目录 前言静态库 一、PyQt是什么&#xff1f;二、安装1.Windows环境下安装安装PyQt5Designer 2.Liunx环境下安装 总结 前言 拜吾师 PyQt5 快速入门 静态库 补充一点知识&#xff1a; Windows&#xff1a; .lib Linux: .a .so(动态库) 简单描述PyQt就是python调用C的Qt文…

9.异步爬虫

异步爬虫可以理解为非只单线程爬虫 我们下面做个例子&#xff0c;之前我们通过单线程爬取过梨视频 https://blog.csdn.net/potato123232/article/details/135672504 在保存视频的时候会慢一些&#xff0c;为了提升效率&#xff0c;我们使用异步爬虫爬取 目录 1 线程池 2 …

Numpy应用-股价分析实战

股价统计分析 数据样本 股价常用指标 极差 越高说明波动越明显 股价近期最高价的最大值和最小值的差价 成交量加权平均价格 英文名VWAP&#xff08;Volume-Weighted Average Price&#xff0c;成交量加权平均价格&#xff09;是一个非常重要的经济学量&#xff0c;代表着金融…

vcruntime140.dll丢失问题全面分析,解决vcruntime140.dll丢失的办法

当vcruntime140.dll文件缺失时&#xff0c;系统会显示错误信息来提示用户。这些错误信息可能会包含类似于"vcruntime140.dll未找到"或"找不到vcruntime140.dll"等字样。通常出现这样的字样那就是导致应用程序通常无法正常启动或执行相关功能。那么出现这样…

【排序4】探秘归并排序:提高程序效率的必备技巧

&#x1f60a;归并排序 &#x1f38a;1、基本思想&#x1f38a;2、代码示例&#x1f38a;3、非递归实现&#x1f38a;4、归并排序的性能分析&#x1f38a;5、归并排序的优缺点&#x1f38a;6、归并排序的应用场景&#x1f38a;7、总结 &#x1f38a;1、基本思想 归并排序&…

ssh异常报错:Did not receive identification string from

一、问题描述 某次外出在异地工作场所xshell炼乳远程服务器时&#xff0c;报错&#xff1a;Connection closed by foreign host. D&#xff0c;服务器查看secure日志或sshd服务状态会显示&#xff1a;id not receive identification string from client_ip; 二、分析处理 1&a…

J9数字论:什么是公链、联盟链、私有链?它们之间区别在哪?

公有链是任何人都能参与读取、交易、写入的区块链&#xff0c;完全去中心化&#xff0c;账本信息公开透明&#xff0c;不受任何机构控制。公有链一般都需要挖矿来达成共识&#xff0c;因此带来了交易延时高、成本高和效率低等缺点。公有链的典型代表有比特币、以太坊、EOS等。私…

Vite学习指南

那本课程都适合哪些人群呢&#xff1f; 想要学习前端工程化&#xff0c;在新项目中投入使用 Vite 构建工具的朋友 Webpack 转战到 Vite 的小伙伴 前端架构师们&#xff0c;可以充实自己的工具箱 当然如果你没有项目相关开发经验&#xff0c;也可以从本课程中受益&#xff0…

你应该知道的GNU C语句表达式

许多写C语言的同道们或许都知道C语言中的表达式和语句&#xff0c;一般常见的语句都是在表达式后跟分号做结尾。例如&#xff0c; a 10 /*赋值表达式*/a 10; /*赋值语句*/当然语句不止有这一种&#xff0c;暂不过多引入。 我们都知道有些表达式是有其值的&#xff0c;例如上…

查询redis路径,清除redis缓存

查询redis路径 1、执行ps -ef | grep redis 命令&#xff0c;结果如下&#xff08;记住PID&#xff09; 2、执行ps -u 系统用户名&#xff0c;进一步确定进程id, 我这里的系统用户名是root&#xff0c;执行ps -u root&#xff0c;结果如下&#xff1a; 结合1的操作结果图可知…

taro3 + vue3 + ts 跨平台体验记录

taro3 vue3 ts 跨平台体验记录&#xff0c;根据进度不定期更新。 目标平台包含&#xff1a;H5、微信小程序、APP。开发环境&#xff1a;windows 安装cli【官方安装文档】 npm install -g tarojs/cli常用命令 // 查看taro版本 npm info tarojs/cli创建demo项目 taro init…

个体诊所电子处方系统设计,社区门诊处方开单管理系统软件教程

个体诊所电子处方系统设计&#xff0c;社区门诊处方开单管理系统软件教程 一、前言 以下软件程序操作教程以 佳易王诊所电子处方管理系统软件V17.3为例说明 如图&#xff0c;在基本信息设置里&#xff0c;可以设置处方配方模板&#xff0c;这样在开电子处方的时候可以一键导入…

C++中map和set的使用

&#xff08;图片来源于网络&#xff09; &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶…