最小二乘法拟合圆(Python&C++实现)

方法一:矩阵计算法

圆的方程可以表示为:
R 2 = ( x − A ) 2 + ( y − B ) 2 (1) R^2 = (x-A)^2+(y-B)^2\tag{1} R2=(xA)2+(yB)2(1)
展开后为:
R 2 = x 2 − 2 A x + A 2 + y 2 − 2 B y + B 2 R^2 = x^2-2Ax+A^2+y^2-2By+B^2 R2=x22Ax+A2+y22By+B2
a = − 2 A , b = − 2 B , c = A 2 + B 2 − R 2 a=-2A,b=-2B,c=A^2+B^2-R^2 a=2A,b=2B,c=A2+B2R2:
则式 ( 1 ) (1) (1)可表示为:
x 2 + y 2 + a x + b y + c = 0 x^2+y^2+ax+by+c=0 x2+y2+ax+by+c=0
− a x − b y − c = x 2 + y 2 -ax-by-c = x^2+y^2 axbyc=x2+y2
矩阵形式可表示为:
[ − x 1 − y 1 − 1 − x 2 − y 2 − 1 ⋯ ⋯ ⋯ − x n − y n − 1 ] [ a b c ] = [ x 1 2 + y 1 2 x 2 2 + y 2 2 ⋯ x n 2 + y n 2 ] \begin{gathered} \quad \begin{bmatrix} -x_1 & -y_1 & -1 \\ -x_2 & -y_2 & -1 \\ \cdots & \cdots & \cdots \\ -x_n & -y_n & -1 \end{bmatrix} \begin{bmatrix} a \\ b \\ c \end{bmatrix} = \begin{bmatrix} x_1^2+y_1^2 \\ x_2^2+y_2^2 \\ \cdots \\ x_n^2+y_n^2 \end{bmatrix} \end{gathered} x1x2xny1y2yn111 abc = x12+y12x22+y22xn2+yn2
得到 R ⋅ A = Y R \cdot A = Y RA=Y的矩阵形式后,便可利用 A = ( R T ⋅ R ) − 1 ⋅ R T ⋅ Y A = (R^T \cdot R)^{-1} \cdot R^T \cdot Y A=(RTR)1RTY得到 a 、 b 、 c a、b、c abc的值。
Python代码为:

def Fit_Circle(x=None, y=None):
    if x is None:
        x = [1, 0, -1, 0]
    if y is None:
        y = [0, 1, 0, -1]

    Y = []
    R = []

    for i in range(len(x)):
        R.append([-x[i], -y[i], -1])
        Y.append(np.square(x[i]) + np.square(y[i]))

    R = np.mat(R)
    A = np.dot(np.dot(np.linalg.inv(np.dot(R.T, R)), R.T), Y)
    A = np.array(A, dtype='float32').flatten()
    return round(-(A[0] / 2), 2), round(-(A[1] / 2), 2), round(np.sqrt(np.square(A[0]) + np.square(A[1]) - 4 * A[2]) / 2, 2)

根据圆心坐标和半径画出圆的代码为:

def Show_Circle(x=0, y=0, r=2):
    theta = np.arange(0, 2 * np.pi, 0.01)
    x = x + r * np.cos(theta)
    y = y + r * np.sin(theta)
    fig = plt.figure()
    axes = fig.add_subplot(111)
    axes.plot(x, y)
    axes.axis('equal')
    plt.show()

Python完整代码为:

import matplotlib.pyplot as plt
import numpy as np


def Show_Circle(x=0, y=0, r=2):
    theta = np.arange(0, 2 * np.pi, 0.01)
    x = x + r * np.cos(theta)
    y = y + r * np.sin(theta)
    fig = plt.figure()
    axes = fig.add_subplot(111)
    axes.plot(x, y)
    axes.axis('equal')
    plt.show()


def Fit_Circle(x=None, y=None):
    if x is None:
        x = [1, 0, -1, 0]
    if y is None:
        y = [0, 1, 0, -1]

    Y = []
    R = []

    for i in range(len(x)):
        R.append([-x[i], -y[i], -1])
        Y.append(np.square(x[i]) + np.square(y[i]))

    R = np.mat(R)
    A = np.dot(np.dot(np.linalg.inv(np.dot(R.T, R)), R.T), Y)
    A = np.array(A, dtype='float32').flatten()
    return round(-(A[0] / 2), 2), round(-(A[1] / 2), 2), round(np.sqrt(np.square(A[0]) + np.square(A[1]) - 4 * A[2]) / 2, 2)


if __name__ == '__main__':
    a = [2, 0, -2, 0]
    b = [0, 2, 0, -2]
    X, Y, R = Fit_Circle(x=a, y=b)
    Show_Circle(X, Y, R)

C++完整代码为:

#include<iostream>
#include <iomanip>
#include<math.h>

using namespace std;

double a[][2] = {{2,0},{0,2},{-2,0},{0,-2}};
int M = sizeof(a) / sizeof(a[0]);

void matrix_inverse(double(*a)[3], double(*b)[3]);
	
int main() 
{
	double R[M][3] = {0};
	double R_T[3][M] = {0};
	double RR[3][3] = {0};	// R_T * R
	double RR_1[3][3] = {0};
	double RR_1R_T[3][M] = {0};	// RR_1 * R_T
	double Y[M][1] = {0};
	double A[3][1] = {0};
	
	for(int i = 0; i < M; i++)
	{
		R[i][0] = -a[i][0];
		R[i][1] = -a[i][1];
		R[i][2] = -1;
		
		R_T[0][i] = -a[i][0];
		R_T[1][i] = -a[i][1];
		R_T[2][i] = -1;
		
		Y[i][0] = pow(a[i][0], 2) + pow(a[i][1], 2);
	}
	
	for(int i = 0; i < 3; ++i)		// 矩阵相乘 (k的取值来源于左边矩阵的列数或右边矩阵的行数) 
		for(int j = 0; j < 3; ++j)
		{
			RR[i][j] = 0;
			for(int k = 0; k < M; ++k)
				RR[i][j] += R_T[i][k] * R[k][j];
		}
			
	matrix_inverse(RR, RR_1);
	
	for(int i = 0; i < 3; ++i)		// 矩阵相乘 (k的取值来源于左边矩阵的列数或右边矩阵的行数) 
		for(int j = 0; j < M; ++j)
		{
			RR_1R_T[i][j] = 0; 
			for(int k = 0; k < 3; ++k)
				RR_1R_T[i][j] += RR_1[i][k] * R_T[k][j];
		}
				
	for(int i = 0; i < 3; ++i)		// 矩阵相乘 (k的取值来源于左边矩阵的列数或右边矩阵的行数) 
		for(int j = 0; j < 1; ++j)
		{
			A[i][j] = 0;
			for(int k = 0; k < M; ++k)
				A[i][j] += RR_1R_T[i][k] * Y[k][j];
		}
	
	double x = -(A[0][0] / 2);
	double y = -(A[1][0] / 2);
	double r = pow(pow(x, 2) + pow(y, 2) - A[2][0], 0.5);
	
	cout.setf(ios::fixed);
	cout << "圆心坐标:(" << setprecision(2) << x << "," << setprecision(2) << y << ")" << endl;
	cout << "半径:" << setprecision(2) << r << endl;
	
	return 0;
}

void matrix_inverse(double(*a)[3], double(*b)[3])			// 矩阵求逆 
{
	using namespace std;
	int i, j, k;
	double max, temp;
	// 定义一个临时矩阵t
	double t[3][3];
	// 将a矩阵临时存放在矩阵t[n][n]中
	for (i = 0; i < 3; i++)
		for (j = 0; j < 3; j++)
			t[i][j] = a[i][j];
	// 初始化B矩阵为单位矩阵
	for (i = 0; i < 3; i++)
		for (j = 0; j < 3; j++)
			b[i][j] = (i == j) ? (double)1 : 0;
	// 进行列主消元,找到每一列的主元
	for (i = 0; i < 3; i++)
	{
		max = t[i][i];
		// 用于记录每一列中的第几个元素为主元
		k = i;
		// 寻找每一列中的主元元素
		for (j = i + 1; j < 3; j++)
		{
			if (fabs(t[j][i]) > fabs(max))
			{
				max = t[j][i];
				k = j;
			}
		}
		//cout<<"the max number is "<<max<<endl;
		// 如果主元所在的行不是第i行,则进行行交换
		if (k != i)
		{
			// 进行行交换
			for (j = 0; j < 3; j++)
			{
				temp = t[i][j];
				t[i][j] = t[k][j];
				t[k][j] = temp;
				// 伴随矩阵B也要进行行交换
				temp = b[i][j];
				b[i][j] = b[k][j];
				b[k][j] = temp;
			}
		}
		if (t[i][i] == 0)
		{
			cout << "\nthe matrix does not exist inverse matrix\n";
			break;
		}
		// 获取列主元素
		temp = t[i][i];
		// 将主元所在的行进行单位化处理
		//cout<<"\nthe temp is "<<temp<<endl;
		for (j = 0; j < 3; j++)
		{
			t[i][j] = t[i][j] / temp;
			b[i][j] = b[i][j] / temp;
		}
		for (j = 0; j < 3; j++)
		{
			if (j != i)
			{
				temp = t[j][i];
				//消去该列的其他元素
				for (k = 0; k < 3; k++)
				{
					t[j][k] = t[j][k] - temp * t[i][k];
					b[j][k] = b[j][k] - temp * b[i][k];
				}
			}
		}
	}
}

方法二:直接计算法

圆的表达式为:
R 2 = ( x − A ) 2 + ( y − B ) 2 (1) R^2 = (x-A)^2+(y-B)^2\tag{1} R2=(xA)2+(yB)2(1)
化简得:
x 2 + y 2 = a x + b y + c = 0 x^2+y^2=ax+by+c=0 x2+y2=ax+by+c=0
样本点到拟合圆心的距离的平方与半径平方的差值为:
δ i = d i 2 − R 2 = ( X i − A ) 2 + ( Y i − B ) 2 − R 2 = X i 2 + Y i 2 + a X i + b Y i + c δ_i=d_i^2-R^2=(X_i-A)^2+(Y_i-B)^2-R^2=X_i^2+Y_i^2+aX_i+bY_i+c δi=di2R2=(XiA)2+(YiB)2R2=Xi2+Yi2+aXi+bYi+c
分别对a、b、c求偏导后可得:
∂ Q ( a , b , c ) ∂ a = Σ 2 ( X i 2 + Y i 2 + a X i + b Y i + c ) X i = 0 \frac{∂Q(a,b,c)}{∂a}=Σ2(X_i^2+Y_i^2+aX_i+bY_i+c)X_i=0 aQ(a,b,c)=Σ2(Xi2+Yi2+aXi+bYi+c)Xi=0
∂ Q ( a , b , c ) ∂ b = Σ 2 ( X i 2 + Y i 2 + a X i + b Y i + c ) Y i = 0 \frac{∂Q(a,b,c)}{∂b}=Σ2(X_i^2+Y_i^2+aX_i+bY_i+c)Y_i=0 bQ(a,b,c)=Σ2(Xi2+Yi2+aXi+bYi+c)Yi=0
∂ Q ( a , b , c ) ∂ c = Σ 2 ( X i 2 + Y i 2 + a X i + b Y i + c ) = 0 \frac{∂Q(a,b,c)}{∂c}=Σ2(X_i^2+Y_i^2+aX_i+bY_i+c)=0 cQ(a,b,c)=Σ2(Xi2+Yi2+aXi+bYi+c)=0
用以下参数表示:
C = N Σ X i 2 − Σ X i Σ X i C=NΣX_i^2-ΣX_iΣX_i C=NΣXi2ΣXiΣXi
D = N Σ X i Y i − Σ X i Σ Y i D=NΣX_iY_i-ΣX_iΣY_i D=NΣXiYiΣXiΣYi
E = N Σ X i 3 + N Σ X i Y i 2 − Σ ( X i 2 + Y i 2 ) Σ X i E=NΣX_i^3+NΣX_iY_i^2-Σ(X_i^2+Y_i^2)ΣX_i E=NΣXi3+NΣXiYi2Σ(Xi2+Yi2)ΣXi
G = N Σ Y i 2 − Σ Y i Σ Y i G=NΣY_i^2-ΣY_iΣY_i G=NΣYi2ΣYiΣYi
H = N Σ X i 2 Y i + N Σ Y i 3 − Σ ( X i 2 + Y i 2 ) Σ Y i H=NΣX_i^2Y_i+NΣY_i^3-Σ(X_i^2+Y_i^2)ΣY_i H=NΣXi2Yi+NΣYi3Σ(Xi2+Yi2)ΣYi
即可得到:
a = H D − E G C G − D 2 a=\frac{HD-EG}{CG-D^2} a=CGD2HDEG
b = H C − E D D 2 − C G b=\frac{HC-ED}{D^2-CG} b=D2CGHCED
c = − Σ ( X i 2 + Y i 2 ) + a Σ X i + b Σ Y i N c=-\frac{Σ(X_i^2+Y_i^2)+aΣX_i+bΣY_i}{N} c=NΣ(Xi2+Yi2)+aΣXi+bΣYi
然后利用 A = − a 2 A=-\frac{a}{2} A=2a B = − b 2 B=-\frac{b}{2} B=2b R = 1 2 a 2 + b 2 − a c R=\frac{1}{2}\sqrt{a^2+b^2-ac} R=21a2+b2ac 便可获得具体的方程表达式。
C++程序为:

#include<iostream>
#include<iomanip>
#include<math.h>
#define PAI acos(-1)

using namespace std;
double point[][2] = {{0,0},{2,0},{1,-1},{1,1}};
int M = sizeof(point) / sizeof(point[0]);

int main()
{
	double e=0,e11=0,e1=0,e12=0,e2=0,e111=0,e122=0,e1122=0,e22=0,e112=0,e222=0;//calculate
	double ti,ri,yi;
	for(int i=0;i<M;i++)
	{
		ti=point[i][0];
		ri=point[i][1];
		yi=ti*ti+ri*ri;
		e+=1;
		e1+=ti;
		e2+=ri;
		e11+=ti*ti;
		e12+=ti*ri;
		e22+=ri*ri;
		e111+=ti*ti*ti;
		e122+=ti*ri*ri;
		e1122+=yi;
		e112+=ti*ti*ri;
		e222+=ri*ri*ri;
	}
	double a = ((e*e112+e*e222-e1122*e2)*(e*e12-e1*e2)-(e*e111+e*e122-e1122*e1)*(e*e22-e2*e2))/((e*e11-e1*e1)*(e*e22-e2*e2)-(e*e12-e1*e2)*(e*e12-e1*e2));
	double b = ((e*e112+e*e222-e1122*e2)*(e*e11-e1*e1)-(e*e111+e*e122-e1122*e1)*(e*e12-e1*e2))/((e*e12-e1*e2)*(e*e12-e1*e2)-(e*e11-e1*e1)*(e*e22-e2*e2));
	double c = -(e1122+a*e1+b*e2)/e;
	double t0,r0,d0;
	t0 = -a/2;
	r0 = -b/2;
	d0 = sqrt(a*a+b*b-4*c);
	
	// 重复计算
	e=0,e11=0,e1=0,e12=0,e2=0,e111=0,e122=0,e1122=0,e22=0,e112=0,e222=0;
	for(int i=0;i<M;i++)
	{
		ti=point[i][0]-t0;
		ri=point[i][1]-r0;
		yi=ti*ti+ri*ri;
		e+=1;
		e1+=ti;
		e2+=ri;
		e11+=ti*ti;
		e12+=ti*ri;
		e22+=ri*ri;
		e111+=ti*ti*ti;
		e122+=ti*ri*ri;
		e1122+=yi;
		e112+=ti*ti*ri;
		e222+=ri*ri*ri;
	}
	a = ((e*e112+e*e222-e1122*e2)*(e*e12-e1*e2)-(e*e111+e*e122-e1122*e1)*(e*e22-e2*e2))/((e*e11-e1*e1)*(e*e22-e2*e2)-(e*e12-e1*e2)*(e*e12-e1*e2));
	b = ((e*e112+e*e222-e1122*e2)*(e*e11-e1*e1)-(e*e111+e*e122-e1122*e1)*(e*e12-e1*e2))/((e*e12-e1*e2)*(e*e12-e1*e2)-(e*e11-e1*e1)*(e*e22-e2*e2));
	c = -(e1122+a*e1+b*e2)/e;
	double t1,r1,d1;
	t1 = -a/2;
	r1 = -b/2;
	d1 = sqrt(a*a+b*b-4*c);
	
	t1+=t0;
	r1+=r0;
	d1=(d0+d1)/2;
	
	
	cout.setf(ios::fixed);
	cout << "圆心坐标:(" << setprecision(2) << t1 << "," << setprecision(2) << r1 << ")" << endl;
	cout << "半径:" << setprecision(2) << d1/2 << endl;
	
	return 0;
} 
  • 8
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
实验目的与实验环境 掌握曲线拟合最小二乘法 探求拟合函数的选择与拟合精度间的关系 实验环境 操作系统:Windows XP 程序语言:Python 2.5和C++ 二、实验内容与实验步骤 实验内容: 利用数据拟合最小二乘法从一组数据中找出其规律性,并给出其数学模型的近似表达式。 三、实验过程与分析 1.在试验过程中,由于计算公式较为复杂,在编程实现过程中遇到很多逻辑错误。例如在使用SOR方法解正则方程组时,计算迭代向量时错误的把下标值写错,从而导致计算结果错误,而这类错误很难发现,为了解决此类问题,故在调试阶段,所进行测试的数据为已知精确函数的一些数据值,每完成一系列计算,就打印出计算结果,与已知精确函数相比较,从而找出这些错误的出处。 2.在使用SOR方法求解正则方程组时,由于松弛因子选择不当,使得达到精度要求所进行的迭代次数很大,为了能够较快的达到精度要求,多次改变了松弛因子的取值,在经过多次修改取值后发现当松弛因子取值为1.36时相对较快。 四、实验结果总结 实验测试中分别用最小二乘法进行三次多项式、二次多项式、四次多项式的曲线拟合,并计算出了三个拟合曲线的均方误差。由运行结果可知,三次多项式的均方误差小于二次多项式的均方误差,二次多项式的均方误差小于四次多项式的均方误差,因此,三次多项式的拟合曲线相对较好。 通过此次试验,使我对最小二乘法有了更加深刻的认识,对其在生产实践和科学实验中的应用也有了一定的了解。在求解正则方程组时使用了SOR方法,也使得我更加熟练的掌握SOR求解方程组的方法。同时,通过将书中的理论公式转换为代码,使得我的编程能力有了一定的提高。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值