散点拟合圆---最小二乘法
最近工作中遇到一个拟合圆的问题,通过找到的轮廓点(存在缺失的情况),需要找出圆心及半径。这里采用最小二乘法进行拟合,并记录一下具体的推导过程。最小二乘法是解决曲线拟合问题最常用的方法,其基本思路是:令
其中是事先选择的一组线性无关函数,是待定系数 ,拟合准则是使与的距离的平方和最小。注意:最小二乘拟合对于离群点很敏感,如果存在离群点可以采用RANSAC(Random Sample Consensus)算法进行拟合。
最小二乘法拟合圆推导流程
1、确定待定系数
设圆的圆心为,半径为,则圆的方程可以写成
令,则圆的曲线方程可写成(3)式:
只需要求出参数即可得到圆心及半径:
所以这里待定系数为。
2、确定距离和
样本集中第个样本到圆心的距离记为,
则其与半径之间的误差为:
所以所有样本的误差和为
现在问题变成求参数使得误差和最小。
3、求解
的每一项都大于等于0,所以函数存在大于或等于零的极小值,极大值为正无穷大。
分别对参数求偏导,然后令偏导数为0,即可求出极值点。
消去c
得
令
可得:
再结合式(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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2021-05-29 图像中两点所成线段的像素坐标
2021-05-29 借助libcurl实现C++客户端请求