重复x位置上的y点曲线拟合(Galaxy螺旋臂)

时间:2016-03-01 14:39:18

标签: matlab 2d curve-fitting astronomy spiral

我目前有一个MATLAB程序,它从星系中获取跟踪螺旋臂的RGB图像,并选择最大的手臂组件并仅绘制。

Spiral arm ready for curve fitting

我尝试过使用matlab的内置曲线拟合工具和平滑样条来拟合它,我得到以下结果:

Spiral arm with MATLAB "smoothingspline" fit

我尝试过使用interp1和参数拟合来获得不好的结果。

有没有办法适应这种类型的曲线?

1 个答案:

答案 0 :(得分:3)

您的失败是由于您将2D曲线作为函数处理而不是这种情况(您为同一y获得了更多x个值,这就是为什么右边的拟合失败的原因一边(当你击中非功能区域时)。

要解决此问题,您需要将曲线拟合分离到每个维度。因此,您可以将每个轴作为单独的功能。为此,您需要使用不同的函数参数(而不是x)。如果您以某种方式订购点数(例如通过曲线距起点,或通过极角或任何曲线),那么您可以使用点索引作为此类函数参数。

所以你做了这样的事情:

y(x) = fit((x0,y0),(x1,y1),(x2,y2)...)

返回y(x)的多项式。相反,你应该做这样的事情:

x(t) = fit(( 0,x0),( 1,x1),( 2,x2)...)
y(t) = fit(( 0,y0),( 1,y1),( 2,y2)...)

其中t是您的新参数,该参数与有序列表中的点顺序相关。大多数曲线使用范围t=<0.0,1.0>中的参数来简化计算和使用。因此,如果您获得N点,则可以将点索引i=<0,N-1>转换为曲线参数t,如下所示:

t=i/(N-1);

在绘图时,您需要更改

plot(x,y(x))

要:

plot(x(t),y(t))

我在 C ++ / VCL 中为您的任务做了一个简单的单插值三次方的简单示例,以便您更好地了解我的意思:

    picture pic0,pic1;
        // pic0 - source
        // pic1 - output
    int x,y,i,j,e,n;
    double x0,x1,x2,x3,xx;
    double y0,y1,y2,y3,yy;
    double d1,d2,t,tt,ttt;
    double ax[4],ay[4];
    approx a0,a3; double ee,m,dm; int di;
    List<_point> pnt;
    _point p;

    // [extract points from image]
    pic0.load("spiral_in.png");
    pic1=pic0;
    // scan image
    xx=0.0; x0=pic1.xs;
    yy=0.0; y0=pic1.ys;
    for (y=0;y<pic1.ys;y++)
     for (x=0;x<pic1.xs;x++)
      // select red pixels
      if (DWORD(pic1.p[y][x].dd&0x00008080)==0)     // low blue,green
       if (DWORD(pic1.p[y][x].dd&0x00800000)!=0)    // high red
        {
        // recolor to green (just for visual check)
        pic1.p[y][x].dd=0x0000FF00;
        // add found point to a list
        p.x=x;
        p.y=y;
        p.a=0.0;
        pnt.add(p);
        // update bounding box
        if (x0>p.x) x0=p.x;
        if (xx<p.x) xx=p.x;
        if (y0>p.y) y0=p.y;
        if (yy<p.y) yy=p.y;
        }
    // center of bounding box for polar sort origin
    x0=0.5*(x0+xx);
    y0=0.5*(y0+yy);
    // draw cross (for visual check)
    x=x0; y=y0; i=16;
    pic1.bmp->Canvas->Pen->Color=clBlack;
    pic1.bmp->Canvas->MoveTo(x-i,y);
    pic1.bmp->Canvas->LineTo(x+i,y);
    pic1.bmp->Canvas->MoveTo(x,y-i);
    pic1.bmp->Canvas->LineTo(x,y+i);
    pic1.save("spiral_fit_0.png");
    // cpmpute polar angle for sorting
    for (i=0;i<pnt.num;i++)
        {
        xx=atan2(pnt[i].y-y0,pnt[i].x-x0);
        if (xx>0.75*M_PI) xx-=2.0*M_PI; // start is > -90 deg
        pnt[i].a=xx;
        }
    // bubble sort by angle (so index in point list can be used as curve parameter)
    for (e=1;e;)
     for (e=0,i=1;i<pnt.num;i++)
      if (pnt[i].a>pnt[i-1].a)
        {
        p=pnt[i];
        pnt[i]=pnt[i-1];
        pnt[i-1]=p;
        e=1;
        }
    // recolor to grayscale gradient (for visual check)
    for (i=0;i<pnt.num;i++)
        {
        x=pnt[i].x;
        y=pnt[i].y;
        pic1.p[y][x].dd=0x00010101*((250*i)/pnt.num);
        }
    pic1.save("spiral_fit_1.png");

    // [fit spiral points with cubic polynomials]
    n =6;                               // recursions for accuracy boost
    m =fabs(pic1.xs+pic1.ys)*1000.0;    // radius for control points fiting
    dm=m/50.0;                          // starting step for approx search
    di=pnt.num/25; if (di<1) di=1;      // skip most points for speed up
    // fit x axis polynomial
    x1=pnt[0          ].x;  // start point of curve
    x2=pnt[  pnt.num-1].x;  // endpoint of curve
    for (a0.init(x1-m,x1+m,dm,n,&ee);!a0.done;a0.step())
    for (a3.init(x2-m,x2+m,dm,n,&ee);!a3.done;a3.step())
        {
        // compute actual polynomial
        x0=a0.a;
        x3=a3.a;
        d1=0.5*(x2-x0);
        d2=0.5*(x3-x1);
        ax[0]=x1;
        ax[1]=d1;
        ax[2]=(3.0*(x2-x1))-(2.0*d1)-d2;
        ax[3]=d1+d2+(2.0*(-x2+x1));
        // compute its distance to points as the fit error e
        for (ee=0.0,i=0;i<pnt.num;i+=di)
            {
            t=double(i)/double(pnt.num-1);
            tt=t*t;
            ttt=tt*t;
            x=ax[0]+(ax[1]*t)+(ax[2]*tt)+(ax[3]*ttt);
            ee+=fabs(pnt[i].x-x);                   // avg error
//          x=fabs(pnt[i].x-x); if (ee<x) ee=x;     // max error
            }
        }
    // compute final x axis polynomial
    x0=a0.aa;
    x3=a3.aa;
    d1=0.5*(x2-x0);
    d2=0.5*(x3-x1);
    ax[0]=x1;
    ax[1]=d1;
    ax[2]=(3.0*(x2-x1))-(2.0*d1)-d2;
    ax[3]=d1+d2+(2.0*(-x2+x1));
    // fit y axis polynomial
    y1=pnt[0          ].y;  // start point of curve
    y2=pnt[  pnt.num-1].y;  // endpoint of curve
    m =fabs(y2-y1)*1000.0;
    di=pnt.num/50; if (di<1) di=1;
    for (a0.init(y1-m,y1+m,dm,n,&ee);!a0.done;a0.step())
    for (a3.init(y2-m,y2+m,dm,n,&ee);!a3.done;a3.step())
        {
        // compute actual polynomial
        y0=a0.a;
        y3=a3.a;
        d1=0.5*(y2-y0);
        d2=0.5*(y3-y1);
        ay[0]=y1;
        ay[1]=d1;
        ay[2]=(3.0*(y2-y1))-(2.0*d1)-d2;
        ay[3]=d1+d2+(2.0*(-y2+y1));
        // compute its distance to points as the fit error e
        for (ee=0.0,i=0;i<pnt.num;i+=di)
            {
            t=double(i)/double(pnt.num-1);
            tt=t*t;
            ttt=tt*t;
            y=ay[0]+(ay[1]*t)+(ay[2]*tt)+(ay[3]*ttt);
            ee+=fabs(pnt[i].y-y);                   // avg error
//          y=fabs(pnt[i].y-y); if (ee<y) ee=y;     // max error
            }
        }
    // compute final y axis polynomial
    y0=a0.aa;
    y3=a3.aa;
    d1=0.5*(y2-y0);
    d2=0.5*(y3-y1);
    ay[0]=y1;
    ay[1]=d1;
    ay[2]=(3.0*(y2-y1))-(2.0*d1)-d2;
    ay[3]=d1+d2+(2.0*(-y2+y1));
    // draw fited curve in Red
    pic1.bmp->Canvas->Pen->Color=clRed;
    pic1.bmp->Canvas->MoveTo(ax[0],ay[0]);
    for (t=0.0;t<=1.0;t+=0.01)
        {
        tt=t*t;
        ttt=tt*t;
        x=ax[0]+(ax[1]*t)+(ax[2]*tt)+(ax[3]*ttt);
        y=ay[0]+(ay[1]*t)+(ay[2]*tt)+(ay[3]*ttt);
        pic1.bmp->Canvas->LineTo(x,y);
        }
    pic1.save("spiral_fit_2.png");

我使用了您在OP中提供的输入图像。以下是阶段输出

螺旋点选择:

enter image description here

按极角点顺序:

enter image description here

最终匹配结果:

enter image description here

正如您所看到的那样,合适度不是很好,因为:

  • 我对整个手臂使用单个立方体(它可能需要更大程度的多项式或细分到补丁)
  • 我使用图像中的点提取,曲线很粗,所以每个极角有多个点(你得到了原点,所以这不应该是个问题)我应该使用稀释算法但是懒得添加它。

C ++ 示例中,我使用自己的图像类,所以这里有一些成员:

  • xs,ys图片大小(以像素为单位)
  • p[y][x].dd是(x,y)位置的像素,为32位整数类型
  • p[y][x].db[4]是色带(r,g,b,a)
  • 的像素访问
  • p.load(filename),p.save(filename)猜猜...加载/保存图片
  • p.bmp->Canvas GDI 位图访问,所以我也可以使用 GDI 的东西

拟合由我的近似搜索类完成:

所以只需从那里复制class approx

List<T>模板只是动态数组(列表):

  • List<int> q;int q[];
  • 相同
  • q.num包含
  • 中的元素数量
  • q.add()将新的空元素添加到列表末尾
  • q.add(10)将10个新元素添加到列表末尾

<强> [注释]

由于你已经有点列表,所以你不需要扫描输入图像的点...所以你可以忽略那部分代码......

如果你需要 BEZIER 而不是插值多项式,那么你可以直接转换控制点看看:

如果目标曲线形状没有固定,那么你也可以尝试通过一些参数圆直接拟合螺旋方程,如方程式,具有偏移中心和可变半径。这应该更加精确,大多数参数都可以在没有拟合的情况下计算出来。

[Edit1]对矿井多项式拟合的更好描述

我正在使用以上链接中的插值立方体,这些属性:

  • 对于4输入点p0,p1,p2,p3,曲线从p1t=0.0)开始,到p2t=1.0)结束。点p0,p3可通过(t=-1.0t=2.0)到达,并确保补丁之间的连续性条件。因此p0p1的推导对于所有相邻补丁都是相同的。它与将BEZIER补丁合并在一起相同。

多项式拟合很简单:

  1. 我将p1,p2设置为螺旋端点

    所以曲线开始和结束它应该

  2. 我在p0,p3附近p1,p2搜索距离m

    同时记住多项式曲线与原点的最接近匹配。您可以使用平均或最大距离。 approx类只需计算每次迭代中距离ee所需的所有工作。

    for m我使用多个图片大小。如果太大你将失去精度(或需要更多的递归和减慢速度),如果太低,你可以限制控制点应该是的区域,并且拟合将变形。

    迭代开始步骤dmm的一部分,如果计算量太小则会很慢。如果为高,您可能会错过解决方案导致错误拟合的本地最小值/最大值。

    为了加快计算速度,我只使用从积分中均匀选择的25个点(不需要全部使用),步骤在di

  3. 维度分隔x,y与您更改x的所有y相同,否则代码相同。