【JS开源库】基于最小二乘法的离散点拟合圆形,计算圆心坐标和半径

介绍

使用的开源库:https://github.com/Meakk/circle-fit

一个用于快速拟合一组二维点的JavaScript库。
该实现基于最小二乘法来找到3个未知数:x和y坐标以及半径。


该算法不包含任何迭代求解器,将在线性时间内找到解,这意味着您将立即得到结果。
当您没有足够的非共线点通过返回错误标志来确定正确的圆时,此库会处理退化情况。


该库使用起来很简单。只有一个circlefit.js,全局对象circlefit会自动创建。此对象有3种方法:
addPoint(x,y):在集合中添加一个点
clearPoints():删除所有点
compute():执行求解器并返回一个包含结果的对象。


返回的结果对象不仅包含圆的基本参数,还有其他参数:

  • success(布尔值):计算状态
  • points(数组):用户给出的所有点
  • projections (数组):将每个点投影到圆上的点
  • distances (数组):每个点到圆的距离
  • center(Object):圆的中心
  • radius(数字):圆的半径
  • residue (数字):最小二乘法的残差,可用于定义圆的质量
  • computationTime(Number):计算所花费的时间(以毫秒为单位)

示例

circlefit.js库的源码

出处:https://github.com/Meakk/circle-fit/blob/master/circlefit.js
是MIT协议。

源码复制到下面了:

var CIRCLEFIT = (function () {
  var my = {},
      points = [];

  function linearSolve2x2(matrix, vector) {
    var det = matrix[0]*matrix[3] - matrix[1]*matrix[2];
    if (det < 1e-8) return false; //no solution
    var y = (matrix[0]*vector[1] - matrix[2]*vector[0])/det;
    var x = (vector[0] - matrix[1]*y)/matrix[0];
    return [x,y];
  }

  my.addPoint = function (x, y) {
    points.push({x: x, y: y});
  }

  my.resetPoints = function () {
    points = [];
  }

  my.compute = function () {
    var result = {
      points: points,
      projections: [],
      distances: [],
      success: false,
      center: {x:0, y:0},
      radius: 0,
      residue: 0,
      computationTime: performance.now()
    };

    //means
    var m = points.reduce(function(p, c) {
      return {x: p.x + c.x/points.length,
              y: p.y + c.y/points.length};
    },{x:0, y:0});
    
    //centered points
    var u = points.map(function(e){
      return {x: e.x - m.x,
              y: e.y - m.y};
    });

    //solve linear equation
    var Sxx = u.reduce(function(p,c) {
      return p + c.x*c.x;
    },0);

    var Sxy = u.reduce(function(p,c) {
      return p + c.x*c.y;
    },0);

    var Syy = u.reduce(function(p,c) {
      return p + c.y*c.y;
    },0);

    var v1 = u.reduce(function(p,c) {
      return p + 0.5*(c.x*c.x*c.x + c.x*c.y*c.y);
    },0);

    var v2 = u.reduce(function(p,c) {
      return p + 0.5*(c.y*c.y*c.y + c.x*c.x*c.y);
    },0);

    var sol = linearSolve2x2([Sxx, Sxy, Sxy, Syy], [v1, v2]);

    if (sol === false) {
      //not enough points or points are colinears
      return result;
    }

    result.success = true;

    //compute radius from circle equation
    var radius2 = sol[0]*sol[0] + sol[1]*sol[1] + (Sxx+Syy)/points.length;
    result.radius = Math.sqrt(radius2);

    result.center.x = sol[0] + m.x;
    result.center.y = sol[1] + m.y;

    points.forEach(function(p) {
      var v = {x: p.x - result.center.x, y: p.y - result.center.y};
      var len2 = v.x*v.x + v.y*v.y;
      result.residue += radius2 - len2;
      var len = Math.sqrt(len2);
      result.distances.push(len - result.radius);
      result.projections.push({
        x: result.center.x + v.x*result.radius/len,
        y: result.center.y + v.y*result.radius/len
      });     
    });

    result.computationTime = performance.now() - result.computationTime;

    return result;
  }

  return my;
}());

//为了方便测试,笔者额外加的
module.exports = {
  CIRCLEFIT
}

测试用例1与效果图

var util=require("./circlefit.js");
var CIRCLEFIT = util.CIRCLEFIT;

var pts = [
    [137, 17672.52 ],
    [104.97, 17640.08],
    [12.52, 17539.34],
    [-45.99, 17469.43],
    [-101.92, 17397.44],
    [-180.79, 17285.75],
    [-253.44, 17169.91],
    [-319.65, 17050.28],
    [-379.22, 16927.2],
    [-431.98, 16801.05],
    [-463.28, 16715.43],
    [-491.44, 16628.72],
]

for (let i = 0; i < pts.length; i++) {
    CIRCLEFIT.addPoint(pts[i][0], pts[i][1]);
}

var result = CIRCLEFIT.compute();
if (result.success) {
    var c =  result.center;
    var x = Math.round(c.x);
    var y = Math.round(c.y);
    var r = Math.round(result.radius);
    console.log(`圆心=(${x},${y})`);
    console.log(`半径={${r}}`);
}

运行结果:

圆心=(1900,15900)
半径={2500}

效果图:

在这里插入图片描述

测试用例2与效果图

将点数组换成:

var pts = [
    [0, 0],
    [1, 1.1],
    [1.5, 0.8],
    [2, -0.5],
]

效果图:
在这里插入图片描述

补充

  • 毕竟是最小二乘法来拟合,当点的噪音太大、太多时,可能拟合效果就不太好了。
  • 可根据需要,转用其他语言重写。
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值