球面拟合算法

球面拟合算法

我在以前的博客中写过一篇介绍过最小二乘法圆拟合算法。前段时间有人给我留言,问我他的数据点是分布在一个球的球面上的,该如何拟合这个球的参数。球面拟合与圆的拟合其实是很类似的。只要按照我上篇文章的方法重新推导一下就能出来。正好这两天不太忙,在出差的路上推导了一下,把结果放到这里供大家参考。顺便多说一句,在高铁上用降噪耳机效果真的很好,很安静,值得拥有。

简单描述一下问题:我们有一组数据: ( x i , y i , z i ) , 这些数据是分散在一个球面上的,当然数据中混杂着一些噪声。球面方程

( x x 0 ) 2 + ( y y 0 ) 2 + ( z z 0 ) 2 = R 2

其中 ( x 0 , y 0 , z 0 , R ) 是我们需要求出的参数。

我们构造一个函数:

H ( x 0 , y 0 , z 0 , R ) = i = 1 N ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) 2

我们求的 ( x 0 , y 0 , z 0 , R ) 是使 H 取得最小值的那组参数。也就是满足下面四个方程的 ( x 0 , y 0 , z 0 , R ) .
H x 0 = 4 i = 1 N ( x i x 0 ) ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) = 0 H y 0 = 4 i = 1 N ( y i z 0 ) ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) = 0 H z 0 = 4 i = 1 N ( z i z 0 ) ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) = 0 H R = 4 i = 1 N R ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) = 0

最后一个方程可以化简为:

i = 1 N ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) = 0

利用这个式子可以把前三个方程化简为:
i = 1 N x i ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) = 0 i = 1 N y i ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) = 0 i = 1 N z i ( ( x i x 0 ) 2 + ( y i y 0 ) 2 + ( z i z 0 ) 2 R 2 ) = 0

为了进一步化简,再做一次变换:
u i = x i x ¯ v i = y i y ¯ w i = z i z ¯

这样替换之后有:
i = 1 N u i ( ( u i u 0 ) 2 + ( v i v 0 ) 2 + ( w i w 0 ) 2 R 2 ) = 0 i = 1 N v i ( ( u i u 0 ) 2 + ( v i v 0 ) 2 + ( w i w 0 ) 2 R 2 ) = 0 i = 1 N w i ( ( u i u 0 ) 2 + ( v i v 0 ) 2 + ( w i w 0 ) 2 R 2 ) = 0 i = 1 N ( ( u i u 0 ) 2 + ( v i v 0 ) 2 + ( w i w 0 ) 2 R 2 ) = 0

再次化简后有:

( u i 2 ) u 0 + ( u i v i ) v 0 + ( u i w i ) w 0 = ( u i 3 + u i v i 2 + u i w i 2 ) 2 ( u i v i ) u 0 + ( v i 2 ) v 0 + ( v i w i ) w 0 = ( u i 2 v i + v i 3 + v i w i 2 ) 2 ( u i w i ) u 0 + ( v i w i ) v 0 + ( w i 2 ) w 0 = ( u i 2 w i + v i 2 w i + w i 3 ) 2

解出这个方程就可以得到 ( u 0 , v 0 , w 0 ) , 然后就可以得到 ( x 0 , y 0 , z 0 ) 了。之后带入第四个式子,就可以得到 R 的值:
R = i = 1 N ( ( u i u 0 ) 2 + ( v i v 0 ) 2 + ( w i w 0 ) 2 ) N

下面是代码:

struct Point3D
{
    double x;
    double y;
    double z;
};

// 全选主元高斯消去法
//a-n*n 存放方程组的系数矩阵,返回时将被破坏
//b-常数向量
//x-返回方程组的解向量
//n-存放方程组的阶数
//返回0表示原方程组的系数矩阵奇异
int cagaus(double a[],double b[],int n,double x[])
{
    int *js, l, k, i, j, is, p, q;
    double d, t;
    js=new int [n];
    l=1;
    for (k=0;k<=n-2;k++)
    {
        d=0.0;
        for (i=k;i<=n-1;i++)
        {
            for (j=k;j<=n-1;j++)
            {
                t=fabs(a[i*n+j]);
                if (t>d)
                {
                    d=t;
                    js[k]=j;
                    is=i;
                }
            }
        }
        if (d+1.0==1.0)
        {
            l=0;
        }
        else
        {
            if (js[k]!=k)
            {
                for (i=0;i<=n-1;i++)
                {
                    p=i*n+k;
                    q=i*n+js[k];
                    t=a[p];
                    a[p]=a[q];
                    a[q]=t;
                }
            }
            if (is!=k)
            {
                for (j=k;j<=n-1;j++)
                {
                    p=k*n+j;
                    q=is*n+j;
                    t=a[p];
                    a[p]=a[q];
                    a[q]=t;
                }
                t=b[k];
                b[k]=b[is];
                b[is]=t;
            }
        }
        if (l==0)
        {
            delete js;
            return(0);
        }
        d=a[k*n+k];
        for (j=k+1;j<=n-1;j++)
        {
            p=k*n+j;
            a[p]=a[p]/d;
        }
        b[k]=b[k]/d;
        for (i=k+1;i<=n-1;i++)
        {
            for (j=k+1;j<=n-1;j++)
            {
                p=i*n+j;
                a[p]=a[p]-a[i*n+k]*a[k*n+j];
            }
            b[i]=b[i]-a[i*n+k]*b[k];
        }
    }
    d=a[(n-1)*n+n-1];
    if (fabs(d)+1.0==1.0)
    {
        delete js;
        return(0);
    }
    x[n-1]=b[n-1]/d;
    for (i=n-2;i>=0;i--)
    {
        t=0.0;
        for (j=i+1;j<=n-1;j++)
        {
            t=t+a[i*n+j]*x[j];
        }
        x[i]=b[i]-t;
    }
    js[n-1]=n-1;
    for (k=n-1;k>=0;k--)
    {
        if (js[k]!=k)
        {
            t=x[k];
            x[k]=x[js[k]];
            x[js[k]]=t;
        }
    }
    delete js;
    return 1;
}
bool sphereLeastFit(const std::vector<Point3D> &points,
                    double &center_x, double &center_y, double &center_z, double &radius)
{
    center_x = 0.0f;
    center_y = 0.0f;
    radius = 0.0f;

    int N = points.size();
    if (N < 4) // 最少 4 个点才能拟合出一个球面。
    {
        return false;
    }

    double sum_x = 0.0f, sum_y = 0.0f, sum_z = 0.0f;

    for (int i = 0; i < N; i++)
    {
        double x = points[i].x;
        double y = points[i].y;
        double z = points[i].z;
        sum_x += x;
        sum_y += y;
        sum_z += z;
    }
    double mean_x = sum_x / N;
    double mean_y = sum_y / N;
    double mean_z = sum_z / N;
    double sum_u2 = 0.0f, sum_v2 = 0.0f, sum_w2 = 0.0f;
    double sum_u3 = 0.0f, sum_v3 = 0.0f, sum_w3 = 0.0f;
    double sum_uv = 0.0f, sum_uw = 0.0f, sum_vw = 0.0f;
    double sum_u2v = 0.0f, sum_uv2 = 0.0f, sum_u2w = 0.0f, sum_v2w = 0.0f, sum_uw2 = 0.0f, sum_vw2 = 0.0f;
    for (int i = 0; i < N; i++)
    {
        double u = points[i].x - mean_x;
        double v = points[i].y - mean_y;
        double w = points[i].z - mean_z;
        double u2 = u * u;
        double v2 = v * v;
        double w2 = w * w;
        sum_u2 += u2;
        sum_v2 += v2;
        sum_w2 += w2;
        sum_u3 += u2 * u;
        sum_v3 += v2 * v;
        sum_w3 += w2 * w;
        sum_uv += u * v;
        sum_vw += v * w;
        sum_uw += u * w;
        sum_u2v += u2 * v;
        sum_u2w += u2 * w;
        sum_uv2 += u * v2;
        sum_v2w += v2 * w;
        sum_uw2 += u * w2;
        sum_vw2 += v * w2;
    }

    double A[3][3];
    double B[3] = {(sum_u3 + sum_uv2 + sum_uw2) / 2.0,
                   (sum_u2v + sum_v3 + sum_vw2) / 2.0,
                   (sum_u2w + sum_v2w + sum_w3) / 2.0};

    A[0][0] = sum_u2;
    A[0][1] = sum_uv;
    A[0][2] = sum_uw;
    A[1][0] = sum_uv;
    A[1][1] = sum_v2;
    A[1][2] = sum_vw;
    A[2][0] = sum_uw;
    A[2][1] = sum_vw;
    A[2][2] = sum_w2;

    double ans[3];

    if( cagaus((double *) A, B, 3, ans) == 0)
    {
        return false;
    }
    else
    {
        center_x = ans[0] + mean_x;
        center_y = ans[1] + mean_y;
        center_z = ans[2] + mean_z;

        double sum = 0;
        for(int i = 0; i < N; i++)
        {
            double dx = points[i].x - center_x;
            double dy = points[i].y - center_y;
            double dz = points[i].z - center_z;
            sum += dx * dx + dy * dy + dz * dz;
        }
        sum /= N;
        radius = sqrt(sum);
    }
    return true;
}

猜你喜欢

转载自blog.csdn.net/liyuanbhu/article/details/80201371