散点拟合圆---最小二乘法

最近工作中遇到一个拟合圆的问题,通过找到的轮廓点(存在缺失的情况),需要找出圆心及半径。这里采用最小二乘法进行拟合,并记录一下具体的推导过程。最小二乘法是解决曲线拟合问题最常用的方法,其基本思路是:令

f(x)=α1ϕ1(x)+α2ϕ2(x)+...+αmϕm(x)

其中ϕk(x)是事先选择的一组线性无关函数,αk是待定系数 ,拟合准则是使yi(i=1,2,...,n)f(xi)的距离δi的平方和最小。注意:最小二乘拟合对于离群点很敏感,如果存在离群点可以采用RANSAC(Random Sample Consensus)算法进行拟合

最小二乘法拟合圆推导流程

1、确定待定系数

设圆的圆心为(A,B),半径为R,则圆的方程可以写成

(1)R2=(xA)2+(yB)2(2)=x22Ax+A2+y22By+B2

a=2A,b=2B,c=A2+B2R2,则圆的曲线方程可写成(3)式:

(3)x2+y2+ax+by+c=0

只需要求出参数a,b,c即可得到圆心及半径:

(4)A=a2(5)B=b2(6)R=12a2+b24c

所以这里待定系数为a,b,c

2、确定距离和

样本集(Xi,Yi),i=1,2,3,...,N中第i个样本到圆心的距离记为di,

(7)di2=(XiA)2+(YiB)2

则其与半径之间的误差为:

(8)δi=di2R2=Xi2+Yi2+aXi+bYi+c

所以所有样本的误差和Q(a,b,c)

(9)Q(a,b,c)=i=1Nδi2(10)=i=1N(Xi2+Yi2+aXi+bYi+c)2

现在问题变成求参数a,b,c使得误差和Q(a,b,c)最小。

3、求解

Q(a,b,c)的每一项都大于等于0,所以函数存在大于或等于零的极小值,极大值为正无穷大。
Q(a,b,c)分别对参数a,b,c求偏导,然后令偏导数为0,即可求出极值点。

(11)dQ(a,b,c)da=i=1N2(Xi2+Yi2+aXi+bYi+c)Xi=0(12)dQ(a,b,c)db=i=1N2(Xi2+Yi2+aXi+bYi+c)Yi=0(13)dQ(a,b,c)dc=i=1N2(Xi2+Yi2+aXi+bYi+c)=0

(11)N(13)i=1NXi消去c

(14)0=Ni=1N2(Xi2+Yi2+aXi+bYi+c)Xii=1N2(Xi2+Yi2+aXi+bYi+c)i=1NXi(15)=Ni=1N2(Xi2+Yi2+aXi+bYi)Xii=1N2(Xi2+Yi2+aXi+bYi)i=1NXi(16)=(Ni=1NXi2i=1NXii=1NXi)a+(Ni=1NXiYii=1NXii=1NYi)b+Ni=1NXi3+Ni=1NXiYi2i=1N(Xi2+Yi2)i=1NXi

(12)N(13)i=1NYi

(17)0=Ni=1N2(Xi2+Yi2+aXi+bYi+c)Yii=1N2(Xi2+Yi2+aXi+bYi+c)i=1NYi(18)=Ni=1N2(Xi2+Yi2+aXi+bYi)Yii=1N2(Xi2+Yi2+aXi+bYi)i=1NYi(19)=(Ni=1NXiYii=1NXii=1NYi)a+(Ni=1NYi2i=1NYii=1NYi)b+Ni=1NYi3+Ni=1NXi2Yii=1N(Xi2+Yi2)i=1NYi

(20)C=Ni=1NXi2i=1NXii=1NXi(21)D=Ni=1NXiYii=1NXii=1NYi(22)E=Ni=1NXi3+Ni=1NXiYi2i=1N(Xi2+Yi2)i=1NXi(23)G=Ni=1NYi2i=1NYii=1NYi(24)H=Ni=1NXi2Yi+Ni=1NYi3i=1N(Xi2+Yi2)i=1NYi

可得:

(25)0=Ca+Db+E(26)0=Da+Gb+H(27)a=HDEGCGD2(28)b=HCEDD2GC(29)c=i=1N(Xi2+Yi2)+ai=1NXi+bi=1NYiN

再结合式(4)(5)(6)即可求得圆心和半径

代码

  • Fitter.h
点击展开
class Fitter
{
public:
	static double FitCircle(const std::vector<Point2>& pointArray, Point2 & center, double& radius);
};
  • Fitter.cpp
点击展开
#include <limits>
#include <cmath>

#include "Fitter.h"

double Fitter::FitCircleByLeastSquare(const std::vector<Point2>& pointArray, Point2& center, double& radius)
{
	int N = pointArray.size();
	if (N < 3) {
		return std::numeric_limits<double>::max();
	}

	double sumX = 0.0; 
	double sumY = 0.0;
	double sumX2 = 0.0;
	double sumY2 = 0.0;
	double sumX3 = 0.0;
	double sumY3 = 0.0;
	double sumXY = 0.0;
	double sumXY2 = 0.0;
	double sumX2Y = 0.0;

	for (int pId = 0; pId < N; ++pId) {
		sumX += pointArray[pId].x;
		sumY += pointArray[pId].y;

		double x2 = pointArray[pId].x * pointArray[pId].x;
		double y2 = pointArray[pId].y * pointArray[pId].y;
		sumX2 += x2;
		sumY2 += y2;

		sumX3 += x2 * pointArray[pId].x;
		sumY3 += y2 * pointArray[pId].y;
		sumXY += pointArray[pId].x * pointArray[pId].y;
		sumXY2 += pointArray[pId].x * y2;
		sumX2Y += x2 * pointArray[pId].y;
 	}

	double C, D, E, G, H;
	double a, b, c;

	C = N * sumX2 - sumX * sumX;
	D = N * sumXY - sumX * sumY;
	E = N * sumX3 + N * sumXY2 - (sumX2 + sumY2) * sumX;
	G = N * sumY2 - sumY * sumY;
	H = N * sumX2Y + N * sumY3 - (sumX2 + sumY2) * sumY;

	a = (H * D - E * G) / (C * G - D * D);
	b = (H * C - E * D) / (D * D - G * C);
	c = -(a * sumX + b * sumY + sumX2 + sumY2) / N;

	center.x = -a / 2.0;
	center.y = -b / 2.0;
	radius = sqrt(a * a + b * b - 4 * c) / 2.0;

	double err = 0.0;
	double e;
	double r2 = radius * radius;
	for (int pId = 0; pId < N; ++pId){
		e = (pointArray[pId] - center).GetSquareSum() - r2;
		if (e > err) {
			err = e;
		}
	}
	return err;
}
  • test.cpp
点击展开
#include "Fitter.h"

int main(int argc, char * argv[])
{
	std::vector<core::Point2> pts;
	pts.push_back(core::Point2(0.0, 1.0));
	pts.push_back(core::Point2(1.0, 0.0));
	pts.push_back(core::Point2(0.0, -1.0));
	pts.push_back(core::Point2(-1.0, 0.0));

	core::Point2 center;
	double r;
	double err = Fitter().FitCircleByLeastSquare(pts, center, r);

	return 0;
}

参考文档

圆拟合算法
最小二乘法拟合圆公式推导及其实现
最小二乘法拟合圆

posted @   半夜打老虎  阅读(5919)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
历史上的今天:
2021-05-29 图像中两点所成线段的像素坐标
2021-05-29 借助libcurl实现C++客户端请求
点击右上角即可分享
微信分享提示