说明

很多时候,我们需要运动物体的转弯半径去描述其机器性能。但在大多数的现实条件下,我们只能够获取到运动物体的 GPS 位置点集,并不能直接得到转弯半径或者圆心位置。为此,我们可以利用拟合圆的方式得到圆坐标方程,由此得到转弯半径和圆心位置。

解决过程

关于拟合圆方程的方法有很多,曾经在这篇译文中获益良多代数逼近法、最小二乘法、正交距离回归法来拟合圆及其结果对比(Python)。此系列文中也给出了提及的三种方法的性能及效果对比,最终得出最优的解决方案就是最小二乘法。由于最近的学习中又进一步了解到,可以利用线性代数的方法去求解。本着大学课程中曾学过的《线性代数》知识,所以想着用此方法再加以解决该问题,以作最对比。

接下来,本文就最小二乘法和线性代数的方法求取圆方程作一论述。

准备

引用矩阵计算库MathNet.Numerics。该库是一个强大的科学计算库,遵循 .Net Standard,所以可以跨平台使用。

创建描述圆的类

public class Circle
{
/// <summary>
/// 圆心横坐标
/// </summary>
/// <value></value>
public double X { get; set; }
/// <summary>
/// 圆心纵坐标
/// </summary>
/// <value></value>
public double Y { get; set; }
/// <summary>
/// 圆半径
/// </summary>
/// <value></value>
public double R { get; set; }
}

画图,引用System.Drawing.Common库,以实现跨平台的图像生成。接下来,我们简单的实现一个图像帮助类来进行图像绘制。

public class ImageHelp
{
private Image _image;
public ImageHelp(int width, int height)
{
_image = new Bitmap(width, height);
var graph = Graphics.FromImage(_image);
graph.Clear(Color.White);
}
public void DrawCicle(Circle circle, Brush brush)
{
var graph = Graphics.FromImage(_image);
var count=200;
var fitPoints = new Point[count+1];
var step = 2 * Math.PI / count;
for (int i = 0; i < count; i++)
{
//circle
var p = new Point();
p.X = (int)(circle.X + Math.Cos(i * step) * circle.R);
p.Y = (int)(circle.Y + Math.Sin(i * step) * circle.R);
fitPoints[i] = p;
}
fitPoints[count] = fitPoints[0];//闭合圆
graph.DrawLines(new Pen(brush, 2), fitPoints);
graph.Dispose();
}
public void DrawPoints(double[] X, double[] Y, Brush brush)
{
var graph = Graphics.FromImage(_image);
for (int i = 0; i < X.Length; i++)
{
graph.DrawEllipse(new Pen(brush, 2), (int)X[i], (int)Y[i], 6, 6);
}
graph.Dispose();
}
public void SaveImage(string file)
{
_image.Save(file, System.Drawing.Imaging.ImageFormat.Png);
}
}

模拟点集,由于现实中的数据采集存在着精度、数据记录等众多不确定因素的影像。模拟点集中也将加入一定程度的噪音。以下代码中 x 与 y 中存储着我们的点集数据:

var count = 50;
var step = 2 * Math.PI / 100;
var rd = new Random();
//参照圆
var x0 = 204.1;
var y0 = 213.1;
var r0 = 98.4;
//噪音绝对差
var diff = (int)(r0 * 0.1);
var x = new double[count];
var y = new double[count];
//输出点集
for (int i = 0; i < count; i++)
{
//circle
x[i] = x0 + Math.Cos(i * step) * r0;
y[i] = y0 + Math.Sin(i * step) * r0;
//noise
x[i] += Math.Cos(rd.Next() % 2 * Math.PI) * rd.Next(diff);
y[i] += Math.Cos(rd.Next() % 2 * Math.PI) * rd.Next(diff);
}

最小二乘法

网上有很多的原理解析,上文中提到的译文中也有提及,这里不在过多赘述。直接贴出 c#代码实现:

public Circle LeastSquaresFit(double[] X, double[] Y)
{
if (X.Length < 3)
{
return null;
}
double cent_x = 0.0,
cent_y = 0.0,
radius = 0.0;
double sum_x = 0.0f, sum_y = 0.0f;
double sum_x2 = 0.0f, sum_y2 = 0.0f;
double sum_x3 = 0.0f, sum_y3 = 0.0f;
double sum_xy = 0.0f, sum_x1y2 = 0.0f, sum_x2y1 = 0.0f;
int N = X.Length;
double x, y, x2, y2;
for (int i = 0; i < N; i++)
{
x = X[i];
y = Y[i];
x2 = x * x;
y2 = y * y;
sum_x += x;
sum_y += y;
sum_x2 += x2;
sum_y2 += y2;
sum_x3 += x2 * x;
sum_y3 += y2 * y;
sum_xy += x * y;
sum_x1y2 += x * y2;
sum_x2y1 += x2 * y;
}
double C, D, E, G, H;
double a, b, c;
C = N * sum_x2 - sum_x * sum_x;
D = N * sum_xy - sum_x * sum_y;
E = N * sum_x3 + N * sum_x1y2 - (sum_x2 + sum_y2) * sum_x;
G = N * sum_y2 - sum_y * sum_y;
H = N * sum_x2y1 + N * sum_y3 - (sum_x2 + sum_y2) * sum_y;
a = (H * D - E * G) / (C * G - D * D);
b = (H * C - E * D) / (D * D - G * C);
c = -(a * sum_x + b * sum_y + sum_x2 + sum_y2) / N;
cent_x = a / (-2);
cent_y = b / (-2);
radius = Math.Sqrt(a * a + b * b - 4 * c) / 2;
var result = new Circle();
result.X = cent_x;
result.Y = cent_y;
result.R = radius;
return result;
}

线性代数

从标准圆方程(x-c1)^2+(y-c2)^2=r^2中进行方程变换得到2xc1+2yc2+(r^2−c1^2−c2^2)=x^2+y^2,其中,我们c3替换常量值r^2−c1^2−c2^2,即:r^2−c1^2−c2^2=c3。由此,我们得到2xc1+2yc2+c3=x^2+y^2,将点集带入,方程就只剩三个未知数`c1,c2 和 c3。

简单起见,假设我们有四个点{[0,5],[0,-5],[5,0],[-5,0]},代入方程可得到四个方程:

  0c1 + 10c2 + c3 = 25
0c1 - 10c2 + c3 = 25
10c1 + 0c2 + c3 = 25
-10c1 + 0c2 + c3 = 25

该方程组比较简单,一眼便能看出解。但用线性代数我们可以得到矩阵:

/***************************A**********B******C*/
| 0c1 10c2 1c3| | 0 10 1| |c1| |25|
| 0c1 -10c2 1c3| = | 0 -10 1| * |c2| = |25|
| 10c1 0c2 1c3| | 10 0 1| |c3| |25|
|-10c1 0c2 1c3| |-10 0 1| |25|

在矩阵方程中A*B=C,只需求出矩阵B即可得到方程组的解。c#中MathNet.Numerics可以轻松胜任这一工作:

public Circle LinearAlgebraFit(double[] X, double[] Y)
{
if (X.Length < 3)
{
return null;
}
var count = X.Length;
var a = new double[count, 3];
var c = new double[count, 1];
for (int i = 0; i < count; i++)
{
//matrix
a[i, 0] = 2 * X[i];
a[i, 1] = 2 * Y[i];
a[i, 2] = 1;
c[i, 0] = X[i] * X[i] + Y[i] * Y[i];
}
var A = DenseMatrix.OfArray(a);
var C = DenseMatrix.OfArray(c);
//A*B=C
var B = A.Solve(C);
double c1 = B.At(0, 0),
c2 = B.At(1, 0),
r = Math.Sqrt(B.At(2, 0) + c1 * c1 + c2 * c2);
var result = new Circle();
result.X = c1;
result.Y = c2;
result.R = r;
return result;
}

最后总结

Console.WriteLine($"raw   c1:{x0}, c2:{y0}, r:{r0}");
var fit = new FitCircle();
var sth = new Stopwatch();
sth.Start();
var lsf = fit.LeastSquaresFit(x, y);![](https://img2018.cnblogs.com/blog/1214143/201908/1214143-20190804173821455-1022769486.jpg) Console.WriteLine($"LeastSquaresFit c1:{lsf.X}, c2:{lsf.Y}, r:{lsf.R}, time:{sth.Elapsed}");
sth.Restart();
var laf = fit.LinearAlgebraFit(x, y);
Console.WriteLine($"LinearAlgebraFit c1:{laf.X}, c2:{laf.Y}, r:{laf.R}, time:{sth.Elapsed}");
var img = new ImageHelp(512, 512);
img.DrawPoints(x, y, Brushes.Red);
img.DrawCicle(lsf, Brushes.Green);
img.DrawCicle(laf, Brushes.Orange);
img.SaveImage("graph.jpeg");

控制台输出:

raw   c1:204.1, c2:213.1, r:98.4
LeastSquaresFit c1:204.791071061878, c2:210.86075318831, r:100.436594821545, time:00:00:00.0011029
LinearAlgebraFit c1:204.791071061878, c2:210.860753188315, r:100.436594821541, time:00:00:00.1691119

从结果中可以看出,两种方法的结果基本一样,在小数点后好几位才出现差别。但是其计算效率却差异巨大,最小二乘法比线性代数快上 100 多倍。

在图中,二者重合(绿色被后面的橙色覆盖)。

在最小二乘法中,只有一个及其简单的 for 循环,很少涉及内存写。但在线性代数中,需要进行矩阵的生成DenseMatrix.OfArray,以及矩阵运算,这二者都需要内存写。再者,矩阵计算有着繁重的计算量,这些都在影响着线性代数拟合圆的效率。最终的胜利还是属于最小二乘法。

.net core(c#)拟合圆测试的更多相关文章

  1. (转)最小二乘法拟合圆公式推导及vc实现[r]

    (下文内容为转载,不过已经不清楚原创的是哪里了,特此说明) 转自: http://www.cnblogs.com/dotLive/archive/2006/10/09/524633.html 该网址下 ...

  2. dotnet core TargetFramework 解析顺序测试

    dotnet core TargetFramework 解析顺序测试 Intro 现在 dotnet 的 TargetFramework 越来越多,抛开 .NET Framework 不谈,如果一个类 ...

  3. .NET Core系列 :4 测试

    2016.6.27 微软已经正式发布了.NET Core 1.0 RTM,但是工具链还是预览版,同样的大量的开源测试库也都是至少发布了Alpha测试版支持.NET Core, 这篇文章 The Sta ...

  4. 好代码是管出来的——.Net Core集成测试与数据驱动测试

    软件的单元测试关注是的软件最小可执行单元是否能够正常执行,但是软件是由一个个最小执行单元组成的集合体,单元与单元之间存在着种种依赖或联系,所以在软件开发时仅仅确保最小单元的正确往往是不够的,为了保证软 ...

  5. dotnet core 发布配置(测试数据库和正式数据库自动切换)

    一.起源 在进行项目开发时,常常要求开发环境,测试环境及正式环境的分离,并且不同环境运行的参数都是不一样的,比如监听地址,数据库连接信息等.当然我们把配置信息保存到一个文件中,每次发布的时候,可以先修 ...

  6. [原]Asp.net Core 2.1.2 测试成功Ajax上传文件新解法

    利用layui框架可以上传文件调试拦截成功! [HttpPost] public IActionResult Method1(IFormFile file) { return Json(new{suc ...

  7. opencv——拟合圆

    #include "stdafx.h" #include "cv.h" #include "highgui.h" #include &quo ...

  8. net core 下 接受文件 测试

    /* IFormFileCollection Files 再Request对象下的From对象下的Files对象 public interface IFormFileCollection : IRea ...

  9. 使用Http-Repl工具测试ASP.NET Core 2.2中的Web Api项目

    今天,Visual Studio中没有内置工具来测试WEB API.使用浏览器,只能测试http GET请求.您需要使用Postman,SoapUI,Fiddler或Swagger等第三方工具来执行W ...

随机推荐

  1. RabbitMq-安装篇

    嘿,大家好,今天更新的内容是rabbitMq的安装篇~~ windows下安装rabbitMq rabbitMq下载地址:点我下载 1.由于rabbitMq用erlang语言开发,所以安装rabbit ...

  2. Google浏览器插件之闪存过滤器

    一件很有意思的事情引发的无聊尝试. 博客园有个很有趣的功能,就是闪存,翻阅到07年园长对闪存的定义:      记录一闪而过的想法,高兴或者不高兴都可以发一下.我用这个一直以来的想法就是,想到点啥发点 ...

  3. Springboot 连接 使用 Redis Example

    通过一个简单的例子使用Springboot 连接并使用Redis. 本文假设已经安装好Redis. 1.首先将URL转换为一个ID ,并使用 StringRedisTemplate 将ID 和 URL ...

  4. Selenium Grid分布式测试环境搭建

    Selenium Grid简介 Selenium Grid实际上是基于Selenium RC的,而所谓的分布式结构就是由一个hub节点和若干个node代理节点组成.Hub用来管理各个代理节点的注册信息 ...

  5. JS处理时间戳、前台拿到日期时间戳,时间戳转日期格式

    今晚做分页的时候,遇到后台往前台传日期类型,会出现这种情况,好像是微软为了解决操作系统兼容性问题,将日期全部转换为从1970年1月1日至现在时间的时间戳.为了解决这个问题,特意百度了一番,整理了处理日 ...

  6. 关于AndroidStudio在编译时无法解析和拉取依赖的问题和无法访问Jcenter服务器的问题

    问题描述:在编译时出现如下错误:Unknown host 'd29vzk4ow07wi7.cloudfront.net'. You may need to adjust the....一般是被墙了.偶 ...

  7. 从URL获取图片并保存到本地

    /// <summary> /// HttpWebRequest Property /// </summary> /// <param name="fileNa ...

  8. 小白教程 Java web maven项目Windows下部署阿里云服务器全过程(买服务器,打包项目,连接服务器,配置服务器,部署项目)

    -------------首先:购买服务器---------------- Step1: 在https://www.aliyun.com/?utm_content=se_1043015注册登录阿里云 ...

  9. 这样子来理解C语言中指针的指针

    友情提示:阅读本文前,请先参考我的之前的文章<从四个属性的角度来理解C语言的指针也许会更好理解>,若已阅读,请继续往下看. 我从4个属性的角度来总结了C语言中的指针概念.对于C语言的一个指 ...

  10. 获取一个整数所有的质因数(C语言实现)

    一.题目要求 1. 用户输入任意一个整数,要求程序输出此整数所有的质因数: 2. 用户可以反复输入,直至输入字符'q'退出程序. 二.分析 质因数的概念大家可以问度娘. 题目关键有几个要点,分析透了这 ...