【问题标题】:Solving equation to find center point of circle from 3 points求解方程以从 3 个点找到圆的中心点
【发布时间】:2020-06-20 16:49:29
【问题描述】:

我正在寻找一种高精度解决方案来从画布上的 3 个数据点 (x,y) 中找到圆的中心点。我在上面的截图中找到了这个例子,现在我正在使用 Math.NET 包来求解方程,并将结果与​​这个在线工具进行比较:https://planetcalc.com/8116/

但是,当我计算半径时,它完全关闭并且通常是负数???

using MathNet.Numerics.LinearAlgebra.Double.Solvers;
using MathNet.Numerics.LinearAlgebra.Double;
using System;

namespace ConsoleAppTestBed
{
  class Program
  {
    static void Main(string[] args)
    {
        var dataPoints = new double[,]
        {

            { 5, 80 },
            { 20, 100 },
            { 40, 140 }
        };


        var fitter = new CircleFitter();
        var result = fitter.Fit(dataPoints);

        var x = -result[0];
        var y = -result[1];
        var c = result[2];

        Console.WriteLine("Center Point:");
        Console.WriteLine(x);
        Console.WriteLine(y);
        Console.WriteLine(c);

        //// (x^2 + y^2 - c^2)
        var radius = Math.Pow(x, 2) + Math.Pow(y, 2) - Math.Pow(c, 2);
        //// sqrt((x^2 + y^2 - c^2))
        radius =  Math.Sqrt(radius);
        Console.WriteLine("Radius:");
        Console.WriteLine(radius);
        Console.ReadLine();
    }

    public class CircleFitter
    {
        public double[] Fit(double[,] v)
        {
            var xy1 = new double[] { v[0,0], v[0,1] };
            var xy2= new double[] { v[1, 0], v[1, 1] };
            var xy3 = new double[] { v[2, 0], v[2, 1] };

            // Create Left Side Matrix of Equation
            var a = CreateLeftSide_(xy1);
            var b = CreateLeftSide_(xy2);
            var c = CreateLeftSide_(xy3);
            var matrixA = DenseMatrix.OfArray(new[,] 
            {
                { a[0], a[1], a[2] },
                { b[0], b[1], b[2] },
                { c[0], c[1], c[2] }
            });

            // Create Right Side Vector of Equation
            var d = CreateRightSide_(xy1);
            var e = CreateRightSide_(xy2);
            var f = CreateRightSide_(xy3);
            double[] vector = { d, e, f };
            var vectorB =  Vector<double>.Build.Dense(vector);

            // Solve Equation
            var r = matrixA.Solve(vectorB);
            var result = r.ToArray();

            return result;
        }

        //2x, 2y, 1
        public double[] CreateLeftSide_(double[] d) 
        {
            return new double[] { (2 * d[0]), (2 * d[1]) , 1};
        
        }

        // -(x^2 + y^2)
        public double CreateRightSide_(double[] d) 
        { 
            return -(Math.Pow(d[0], 2) + Math.Pow(d[1], 2));

        }
    }
  }

}

有什么想法吗?

提前致谢。

【问题讨论】:

  • 请定义“高精度” ....您可以从尽可能使用小数开始。
  • 我正在 wpf 画布上绘制圆圈和三个点。我最初是在寻找垂直线相交的地方。但是,当计算出的圆的半径穿过某些点时,有时会有一个或两个像素的偏移。我正在使用双精度自动取款机,我会尝试使用小数。

标签: c# .net .net-core


【解决方案1】:

您的问题的解决方案在这里:The NumberDecimalDigits property

代码:

using System;
using System.Globalization;
namespace ConsoleApp1
{
class Program
{
    static void Main()
    {
        double x1 = 1, y1 = 1;
        double x2 = 2, y2 = 4;
        double x3 = 5, y3 = -3;
        findCircle(x1, y1, x2, y2, x3, y3);
        Console.ReadKey();
    }
    static void findCircle(double x1, double y1,
                           double x2, double y2,
                           double x3, double y3)
    {
        NumberFormatInfo setPrecision = new NumberFormatInfo();
        setPrecision.NumberDecimalDigits = 3; // 3 digits after the double point

        double x12 = x1 - x2;
        double x13 = x1 - x3;

        double y12 = y1 - y2;
        double y13 = y1 - y3;

        double y31 = y3 - y1;
        double y21 = y2 - y1;

        double x31 = x3 - x1;
        double x21 = x2 - x1;

        double sx13 = (double)(Math.Pow(x1, 2) -
                        Math.Pow(x3, 2));

        double sy13 = (double)(Math.Pow(y1, 2) -
                        Math.Pow(y3, 2));

        double sx21 = (double)(Math.Pow(x2, 2) -
                        Math.Pow(x1, 2));

        double sy21 = (double)(Math.Pow(y2, 2) -
                        Math.Pow(y1, 2));

        double f = ((sx13) * (x12)
                + (sy13) * (x12)
                + (sx21) * (x13)
                + (sy21) * (x13))
                / (2 * ((y31) * (x12) - (y21) * (x13)));
        double g = ((sx13) * (y12)
                + (sy13) * (y12)
                + (sx21) * (y13)
                + (sy21) * (y13))
                / (2 * ((x31) * (y12) - (x21) * (y13)));

        double c = -(double)Math.Pow(x1, 2) - (double)Math.Pow(y1, 2) -
                                    2 * g * x1 - 2 * f * y1;
        double h = -g;
        double k = -f;
        double sqr_of_r = h * h + k * k - c;

        // r is the radius
        double r = Math.Round(Math.Sqrt(sqr_of_r), 5);

        Console.WriteLine("Center of a circle: x = " + h.ToString("N", setPrecision) + 
        ", y = " + k.ToString("N", setPrecision));
        Console.WriteLine("Radius: " + r.ToString("N", setPrecision));
        }
        }
        }

【讨论】:

    【解决方案2】:

    我刚刚将 William Li 的 答案转换为 Swift 5 喜欢 cmd+C 和 cmd+V 的人跟我一样 :)

     func calculateCircle(){
        
        
        let x1:Float = 0
        let y1:Float = 0
        
        let x2:Float = 0.5
        let y2:Float = 0.5
        
        let x3:Float = 1
        let y3:Float = 0
        
        let x12 = x1 - x2
        let x13 = x1 - x3
        
        let y12 = y1 - y2
        let y13 = y1 - y3
        
        let y31 = y3 - y1
        let y21 = y2 - y1
        
        let x31 = x3 - x1
        let x21 = x2 - x1
        
        
        let sx13 = pow(x1, 2) - pow(x3, 2)
    
        let sy13 = pow(y1, 2) - pow(y3, 2)
    
        let sx21 = pow(x2, 2) - pow(x1, 2)
    
        let sy21 = pow(y2, 2) - pow(y1, 2)
    
        
        let f = ((sx13) * (x12)
                        + (sy13) * (x12)
                        + (sx21) * (x13)
                        + (sy21) * (x13))
                        / (2 * ((y31) * (x12) - (y21) * (x13)))
        
        
        let g = ((sx13) * (y12)
                        + (sy13) * (y12)
                        + (sx21) * (y13)
                        + (sy21) * (y13))
                        / (2 * ((x31) * (y12) - (x21) * (y13)))
        
        
        let c = -pow(x1, 2) - pow(y1, 2) - 2 * g * x1 - 2 * f * y1
                                
        let h = -g
        let k = -f
        let r = sqrt(h * h + k * k - c)
                                
     
        
        print("center x  = \(h)")
        print("center y = \(k)")
        print("r = \(r)")
        
        
                                
    }
    

    【讨论】:

    • OP 询问为什么计算的精度会关闭。尽管这个答案令人印象深刻,但我看不出它如何阐明这个问题。
    • 我使用了 Float。如果使用 Double,精度会更好。我正在使用它来制作游戏中对象的路线,所以 Float 对我有好处。
    • 在这之后我应该承认 Swift 是好的。
    【解决方案3】:

    更新答案

    半径公式不正确; it should be(不是 c 平方):

    这就是为什么你会得到不正确的半径值。

    原来的答案不正确,但它仍然“有趣”。

    (不正确)原始答案

    计算有问题的原因不仅仅是因为数字的精度,更多的是因为问题是病态的。如果您查看三个点以及它们在圆上的位置,您会发现它们聚集在圆周的一小段上。当这些点彼此如此接近时,很难准确地找到圆的中心和半径。

    所以中间计算,会有小的舍入误差,会导致极大的夸大误差。

    您可以通过添加ConditionNumber() 方法来查看问题的ill-conditioned 本质。

        // Solve Equation
        Console.WriteLine(matrixA.ConditionNumber()); // <<< Returns 5800 -> Big!
        var r = matrixA.Solve(vectorB);               // Existing code
    

    较大的结果表明存在病态问题。在这种情况下,返回值 5800,它很大。使用带有partial pivoting 的高斯消元法可能会得到更好的结果,但它仍然没有解决基本问题是病态的事实,这就是为什么你会得到非常错误的答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-24
      • 1970-01-01
      • 2011-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-02
      • 1970-01-01
      相关资源
      最近更新 更多