最小封闭筒

时间:2018-07-19 19:57:11

标签: algorithm computational-geometry

是否有一种算法可以找到3D点云的半径最小的封闭圆柱体?我知道可以解决带有最小包围圆的2D情况(例如,该线程Smallest enclosing circle in Python, error in the code),但是3D是否有任何可行的方法?


EDIT1: OBB 。以下是圆弧形点云的示例。该工具https://www.nayuki.io/page/smallest-enclosing-circle

找到了最小的包围圈

圆是由三个点定义的,其中两个点几乎位于直径上,因此很容易估计中心轴的位置。点的“装箱”将使盒子的中心明显偏离真实中心。

我得出结论, OBB方法并不通用

Example of an arc-shaped data set


EDIT2: PCA 。以下是紧密点云 vs 的PCA分析示例。点云与异常值。对于密集的点云,PCA可以令人满意地预测圆柱方向。但是,与主云相比,如果离群值很少,则PCA基本上将其忽略,从而产生距离封闭圆柱体的真实轴非常远的向量。在下面的示例中,封闭圆柱体的真实几何轴以黑色显示。

我得出结论, PCA方法并不通用

PCA with outliers


EDIT3: OBB与PCA和OLS 。主要区别在于-OBB仅依赖于几何形状,而PCA和OLS依赖于总点数(包括集合中间的点),这些点不影响形状。为了使它们更有效,可以包括数据准备步骤。首先,找到凸包。其次,排除所有内部要点。然后,沿船体的点可能会不均匀地分布。我建议删除所有它们,只保留多边形的船体,并用网格覆盖它,其中节点将是新的点。在新的点云中应用PCA或OLS应该可以更准确地估计圆柱轴。

如果OBB提供的轴尽可能平行于封闭圆柱的轴,那么所有这些都是不必要的。


EDIT4:已发布的方法。 @meowgoesthedog:Michel Petitjean撰写的论文(“关于最小封闭圆柱体问题的代数解”)可能会有所帮助,但我没有足够的资格将其转换为工作程序。作者自己完成了此操作(此处为模块CYL,http://petitjeanmichel.free.fr/itoweb.petitjean.freeware.html)。但是,他在论文的结论中说:“ 和名为CYL的当前软件,可以从http://petitjeanmichel.free.fr/itoweb.petitjean.freeware.html免费下载,既没有声称可以提供该方法的最佳实现,也没有声称可以更好地工作。 “本文中的其他短语也给人一种印象,即这是一种实验性方法,尚未得到充分验证。无论如何,我都会尝试使用它。

@ Ripi2:Timothy M. Chan的这篇论文对我来说也太复杂了。我不是那种能够转换为工具的数学专家。

@ Helium_1s2:也许,这是一个很好的建议,但是,与上面的两篇论文相比,它的详细程度要低得多。另外,未经验证。


EDIT5:回复user1717828 。两个最远的点与圆柱轴的关系。 一个反例-安装在圆柱体中的8个立方体形状的点。两点之间的最大距离-绿色对角线。显然不平行于圆柱轴。

Cube in cylinder

Ripi2的

“中点”方法:仅适用于2D。在3D情况下,圆柱轴可能不会与任何两点之间的单个线段相交。

Triangular prism in cylinder

6 个答案:

答案 0 :(得分:1)

首先找到点云的定向边界框(OBB)。有几种算法。这可能是最佳选择:

https://pdfs.semanticscholar.org/a76f/7da5f8bae7b1fb4e85a65bd3812920c6d142.pdf

现在,可以通过围绕OBB的最长轴旋转OBB来轻松找到包围OBB的非最佳定向圆柱体。同样,被OBB包围的圆柱可以发现与另一个圆柱具有相同的轴,但是半径是OBB面垂直于该轴的最短边的一半。

我的设想是,最佳圆柱半径在这两个圆柱之间。

如果计算所有点到外圆柱的最小距离并调整其半径以使该距离等于零,则可以轻松找到最佳圆柱。

由于您必须计算所有点到圆柱的距离,因此该方法可能有效,但在计算上并不是最佳的。也许可以使用内部圆柱体剔除其中的所有点。我还没有详细说明这个想法。

更新:

似乎什么是“最小”问题还不清楚,实际上要求的是“最小”以外的东西,而且提出的条件不正确。包围点云的“最小”圆柱体应将圆柱体内的空白最小化(至少我知道是最小的)。但是OP还施加了这样的约束,即最小的圆柱体应适合输入数据的形状。这意味着,如果输入数据是对圆柱的一半进行采样(以其最长边截取),则答案应该是最适合该一半形状的圆柱。不管那个圆柱体是否比封闭数据的其他圆柱体有更多的空白空间。

这两个要求是矛盾的。由于最小的圆柱体可能不适合数据的弯曲形状,而最适合数据的曲面形状的圆筒可能不是最小的圆柱体。

基于OBB的我的答案(和其他答案)确实回答了有关“最小”圆柱体的问题,该圆柱体包含了最小化圆柱体内的空白空间的数据。

将圆柱体拟合为数据形状的另一种情况也可以使用优化方法来解决。但是没有普遍的答案。 “最佳”圆柱体取决于应用程序的需求,并且必须根据数据使用至少两种不同的策略进行计算。

答案 1 :(得分:1)

  1. 计算OBB

    因此请使用 PCA 或此

    获取3D OBB 。链接中的代码必须移植到3D,但原理是相同的。

  2. 初始猜测

    因此 OBB 将为您提供定向的边界框。它的最大一侧将平行于圆柱体的旋转轴。因此,让我们从圆柱体上标出此 OBB 开始。因此,中心轴将是 OBB 的中心,并与其最大边平行。 (如果您没有最大的一面,则需要检查所有3种组合)。直径将是其余边中的较大者。

  3. 油缸

    现在,只需尝试将偏移量和半径(也可能是高度)的“所有”组合括在初始猜测附近,并将所有最佳点记住(根据您所需的规格)。您可以为此使用任何优化方法,但我最喜欢的是:

结果的有效性取决于拟合过程。但是,嵌套嵌套不要太复杂,因为复杂性也会变得疯狂。

[Edit1] C ++中的3D OBB

我很好奇,今天有空,所以我对 3D OBB 进行了编码,类似于上面链接的 2D 示例。看起来像它的工作。这是预览:

3D OBB preview

我使用2个群集来验证方向...这里的代码(以简单C ++类的形式):

//---------------------------------------------------------------------------
class OBB3D
    {
public:
    double p0[3],u[3],v[3],w[3];    // origin,3 axises sorted by size asc
    double V,l[3];                  // volume, and { |u|,|v|,|w| }
    double p[8*3];                  // corners

    OBB3D()     {}
    OBB3D(OBB3D& a) { *this=a; }
    ~OBB3D()    {}
    OBB3D* operator = (const OBB3D *a) { *this=*a; return this; }
    //OBB3D* operator = (const OBB3D &a) { ...copy... return this; }

    void compute(double *pnt,int num)       // pnt[num] holds num/3 points
        {
        OBB3D o;                            // temp OBB values
        int i,j;
        double a,_a,a0,a1,da,ca,sa; int ea; // search angles
        double b,_b,b0,b1,db,cb,sb; int eb;
        double c,_c,c0,c1,dc,cc,sc; int ec;
        double u0[3],v0[3],pmin[3],pmax[3],q,*qq;
        const double deg=M_PI/180.0;
        p0[0]=0.0; u[0]=1.0; v[0]=0.0; w[0]=0.0; l[0]=0.0; V=-1.0;
        p0[1]=0.0; u[1]=0.0; v[1]=1.0; w[1]=0.0; l[1]=0.0;
        p0[2]=0.0; u[2]=0.0; v[2]=0.0; w[2]=1.0; l[2]=0.0;
        if (num<3) { V=0.0; return; }

        a0=0; a1=360.0*deg; da=10.0*deg; _a=a0;
        b0=0; b1= 90.0*deg; db=10.0*deg; _b=b0;
        c0=0; c1= 90.0*deg; dc=10.0*deg; _c=c0;
        // recursively increase precision
        for (j=0;j<5;j++)
            {
            // try all 3D directions with some step da,db
            for (ea=1,a=a0;ea;a+=da){ if (a>=a1) { a=a1; ea=0; } ca=cos(a); sa=sin(a);
             for (eb=1,b=b0;eb;b+=db){ if (b>=b1) { b=b1; eb=0; } cb=cos(b); sb=sin(b);
                // spherical to cartesian direction
                o.w[0]=cb*ca;
                o.w[1]=cb*sa;
                o.w[2]=sb;
                // init u,v from cross product
                vector_ld(u0,1.0,0.0,0.0);
                if (fabs(vector_mul(u0,o.w))>0.75)  // |dot(u,w)>0.75| measn near (anti)parallel
                 vector_ld(u0,0.0,1.0,0.0);
                vector_mul(v0,o.w,u0);  // v0 = cross(w,u0)
                vector_mul(u0,v0,o.w);  // u0 = cross(v0,w)
                vector_one(u0,u0);      // u0/=|u0|
                vector_one(v0,v0);      // v0/=|v0|
                // try all rotations within u0,v0 plane
                for (ec=1,c=c0;ec;c+=dc){ if (c>=c1) { c=c1; ec=0; } cc=cos(c); sc=sin(c);
                    for (i=0;i<3;i++)
                        {
                        o.u[i]=(u0[i]*cc)-(v0[i]*sc);
                        o.v[i]=(u0[i]*sc)+(v0[i]*cc);
                        }
                    // now u,v,w holds potential obb axises socompute min,max
                    pmin[0]=pmax[0]=vector_mul(pnt,o.u);    // dot(pnt,u);
                    pmin[1]=pmax[1]=vector_mul(pnt,o.v);    // dot(pnt,v);
                    pmin[2]=pmax[2]=vector_mul(pnt,o.w);    // dot(pnt,w);
                    for (i=0;i<num;i+=3)
                        {
                        q=vector_mul(pnt+i,o.u); if (pmin[0]>q) pmin[0]=q; if (pmax[0]<q) pmax[0]=q;
                        q=vector_mul(pnt+i,o.v); if (pmin[1]>q) pmin[1]=q; if (pmax[1]<q) pmax[1]=q;
                        q=vector_mul(pnt+i,o.w); if (pmin[2]>q) pmin[2]=q; if (pmax[2]<q) pmax[2]=q;
                        }
                    // compute V,l from min,max
                    for (o.V=1.0,i=0;i<3;i++) { o.l[i]=pmax[i]-pmin[i]; o.V*=o.l[i]; }
                    // remember best solution u,v,w,V,l and compute p0
                    if ((V<0.0)||(V>o.V))
                        {
                        *this=o; _a=a; _b=b; _c=c;
                        for (i=0;i<3;i++) p0[i]=(pmin[0]*u[i])+(pmin[1]*v[i])+(pmin[2]*w[i]);
                        }
                    }
                }}
            a0=(_a-0.5*da); a1=a0+da; da*=0.1;
            b0=(_b-0.5*db); b1=b0+db; db*=0.1;
            c0=(_c-0.5*dc); c1=c0+dc; dc*=0.1;
            }
        // sort axises
                      { i=0; qq=u; }    // w is max
        if (l[1]>l[i]){ i=1; qq=v; }
        if (l[2]>l[i]){ i=2; qq=w; }
        for (j=0;j<3;j++) { q=w[j]; w[j]=qq[j]; qq[j]=q; } q=l[2]; l[2]=l[i]; l[i]=q;
                      { i=0; qq=u; }    // v is 2nd max
        if (l[1]>l[i]){ i=1; qq=v; }
        for (j=0;j<3;j++) { q=v[j]; v[j]=qq[j]; qq[j]=q; } q=l[1]; l[1]=l[i]; l[i]=q;
        // compute corners from p0,u,v,w,l
        for (i=0;i<3;i++)
            {
            j=i;
            p[j]=p0[i]                                    ; j+=3;
            p[j]=p0[i]+(l[0]*u[i])                        ; j+=3;
            p[j]=p0[i]+(l[0]*u[i])+(l[1]*v[i])            ; j+=3;
            p[j]=p0[i]            +(l[1]*v[i])            ; j+=3;
            p[j]=p0[i]                        +(l[2]*w[i]); j+=3;
            p[j]=p0[i]+(l[0]*u[i])            +(l[2]*w[i]); j+=3;
            p[j]=p0[i]+(l[0]*u[i])+(l[1]*v[i])+(l[2]*w[i]); j+=3;
            p[j]=p0[i]            +(l[1]*v[i])+(l[2]*w[i]); j+=3;
            }
        }
    void gl_draw()
        {
        glBegin(GL_LINES);
        glVertex3dv(p+ 0); glVertex3dv(p+ 3);
        glVertex3dv(p+ 3); glVertex3dv(p+ 6);
        glVertex3dv(p+ 6); glVertex3dv(p+ 9);
        glVertex3dv(p+ 9); glVertex3dv(p+ 0);
        glVertex3dv(p+12); glVertex3dv(p+15);
        glVertex3dv(p+15); glVertex3dv(p+18);
        glVertex3dv(p+18); glVertex3dv(p+21);
        glVertex3dv(p+21); glVertex3dv(p+12);
        glVertex3dv(p+ 0); glVertex3dv(p+12);
        glVertex3dv(p+ 3); glVertex3dv(p+15);
        glVertex3dv(p+ 6); glVertex3dv(p+18);
        glVertex3dv(p+ 9); glVertex3dv(p+21);
        glEnd();
        }
    } obb;
//---------------------------------------------------------------------------

您只需使用点云数据调用计算,其中num是点数的3倍。结果存储为单位基础向量u,v,w和原点p0以及每个轴的大小l[] OBB p的8个角点

这些东西的工作原理很简单,只需尝试w轴的某一步的“所有”球形位置,然后尝试垂直于每个u,v的所有极性位置,w记住最小体积< strong> OBB 。然后以较小的步长递归搜索仅在找到最佳解决方案附近的位置,以提高准确性。

我认为这应该提供一个很好的起点。如果您实现最小圆弧而不是旋转u,v(循环for (ec=1,c=c0;ec;c+=dc)),则可以直接从此搜索中获取圆柱体。

代码尚未优化(某些部分,例如w轴检查)可以移到嵌套的for循环的下层。但是我想尽可能地简化和理解。

[Edit2] C ++中的3D OBC

我设法通过用最小的封闭圆代替U,V搜索来修改我的3D OBB(希望我正确实现了它,但看起来像...),从而找到了投影在所有点上的最小的封闭2D圆UV平面,使其成为平行于W的定向边界圆柱。我使用了pdf from your link中的第一种方法(使用等分线)。结果如下:

3D OBC

找到的 3D OBC 是蓝色的 3D OBB ,褐色/橙色是。这里的代码:

class OBC3D                         // 3D Oriented Bounding Cylinder
    {
public:
    double p0[3],u[3],v[3],w[3];    // basecenter,3 axises
    double V,r,h;                   // volume, radius height
    double p1[3];                   // other base center

    OBC3D()     {}
    OBC3D(OBC3D& a) { *this=a; }
    ~OBC3D()    {}
    OBC3D* operator = (const OBC3D *a) { *this=*a; return this; }
    //OBC3D* operator = (const OBC3D &a) { ...copy... return this; }

    void compute(double *pnt,int num)       // pnt[num] holds num/3 points
        {
        OBC3D o;                            // temp OBB values
        int i,j,k,kk,n;
        double a,_a,a0,a1,da,ca,sa; int ea; // search angles
        double b,_b,b0,b1,db,cb,sb; int eb;
        double pmin[3],pmax[3],q,qq,*pnt2,p[3],c0,c1,u0,v0,du,dv,dr;
        const double deg=M_PI/180.0;
        p0[0]=0.0; u[0]=1.0; v[0]=0.0; w[0]=0.0; V=-1.0;
        p0[1]=0.0; u[1]=0.0; v[1]=1.0; w[1]=0.0; r=0.0;
        p0[2]=0.0; u[2]=0.0; v[2]=0.0; w[2]=1.0; h=0.0;
        if (num<3) { V=0.0; return; }
        // prepare buffer for projected points
        pnt2=new double[num];

        a0=0; a1=360.0*deg; da=10.0*deg; _a=a0;
        b0=0; b1= 90.0*deg; db=10.0*deg; _b=b0;
        // recursively increase precision
        for (k=0;k<5;k++)
            {
            // try all 3D directions with some step da,db
            for (ea=1,a=a0;ea;a+=da){ if (a>=a1) { a=a1; ea=0; } ca=cos(a); sa=sin(a);
             for (eb=1,b=b0;eb;b+=db){ if (b>=b1) { b=b1; eb=0; } cb=cos(b); sb=sin(b);
                // spherical to cartesian direction
                o.w[0]=cb*ca;
                o.w[1]=cb*sa;
                o.w[2]=sb;
                // init u,v from cross product
                vector_ld(o.u,1.0,0.0,0.0);
                if (fabs(vector_mul(o.u,o.w))>0.75) // |dot(u,w)>0.75| measn near (anti)parallel
                 vector_ld(o.u,0.0,1.0,0.0);
                vector_mul(o.v,o.w,o.u);    // v0 = cross(w,u0)
                vector_mul(o.u,o.v,o.w);    // u0 = cross(v0,w)
                vector_one(o.u,o.u);        // u0/=|u0|
                vector_one(o.v,o.v);        // v0/=|v0|
                // now u,v,w holds potential obb axises so compute min,max and convert to local coordinates
                pmin[0]=pmax[0]=vector_mul(pnt,o.u);    // dot(pnt,u);
                pmin[1]=pmax[1]=vector_mul(pnt,o.v);    // dot(pnt,v);
                pmin[2]=pmax[2]=vector_mul(pnt,o.w);    // dot(pnt,w);
                for (i=0;i<num;i+=3)
                    {
                    q=vector_mul(pnt+i,o.u); if (pmin[0]>q) pmin[0]=q; if (pmax[0]<q) pmax[0]=q; pnt2[i+0]=q;
                    q=vector_mul(pnt+i,o.v); if (pmin[1]>q) pmin[1]=q; if (pmax[1]<q) pmax[1]=q; pnt2[i+1]=q;
                    q=vector_mul(pnt+i,o.w); if (pmin[2]>q) pmin[2]=q; if (pmax[2]<q) pmax[2]=q; pnt2[i+2]=q;
                    }
                // [compute min enclosing circle]
                n=0;
                // center (u0,v0) = avg( pnt2 )
                for (u0=0.0,v0=0.0,i=0;i<num;i+=3)
                    {
                    u0+=pnt2[i+0];
                    v0+=pnt2[i+1];
                    } q=3.0/double(num); u0*=q; v0*=q;
                // r = max(|pnt2 - (u0,v0)|)
                for (o.r=0.0,i=0;i<num;i+=3)
                    {
                    c0=pnt2[i+0]-u0;
                    c1=pnt2[i+1]-v0;
                    q=(c0*c0)+(c1*c1);
                    if (o.r<q) o.r=q;
                    } o.r=sqrt(o.r);
                for (kk=0;kk<4;kk++)
                    {
                    // update edgepoints count n
                    qq=o.r*o.r;
                    for (i=n;i<num;i+=3)
                        {
                        c0=pnt2[i+0]-u0;
                        c1=pnt2[i+1]-v0;
                        q=fabs((c0*c0)+(c1*c1)-qq);
                        if (q<1e-10)
                            {
                            pnt2[n+0]=pnt2[i+0];
                            pnt2[n+1]=pnt2[i+1];
                            pnt2[n+2]=pnt2[i+2]; n+=3;
                            }
                        }
                    // compute bisector (du,dv)
                    for (du=0.0,dv=0.0,i=0;i<n;i+=3)
                        {
                        du+=pnt2[i+0]-u0;
                        dv+=pnt2[i+1]-v0;
                        } q=1.0/sqrt((du*du)+(dv*dv)); du*=q; dv*=q;
                    // try to move center towards edge points as much as possible
                    for (dr=0.1*o.r,j=0;j<5;)
                        {
                        u0+=dr*du;
                        v0+=dr*dv;
                        // q = max(|pnt2 - (u0,v0)|)
                        for (qq=0.0,i=0;i<num;i+=3)
                            {
                            c0=pnt2[i+0]-u0;
                            c1=pnt2[i+1]-v0;
                            q=(c0*c0)+(c1*c1);
                            if (qq<q) qq=q;
                            } qq=sqrt(qq);
                        // recursively increase precision
                        if (qq>o.r)
                            {
                            u0-=dr*du;
                            v0-=dr*dv;
                            dr*=0.1;
                            j++;
                            }
                        else o.r=qq;
                        }
                    }

                // compute h,V
                o.h=pmax[2]-pmin[2];
                o.V=M_PI*o.r*o.r*o.h;
                // remember best solution u,v,w,V,l and compute p0
                if ((V<0.0)||(V>o.V))
                    {
                    *this=o; _a=a; _b=b;
                    for (i=0;i<3;i++) p0[i]=(u0*u[i])+(v0*v[i])+(pmin[2]*w[i]);
                    }
                }}
            a0=(_a-0.5*da); a1=a0+da; da*=0.1;
            b0=(_b-0.5*db); b1=b0+db; db*=0.1;
            }
        // compute corners from p0,u,v,w,l
        for (i=0;i<3;i++) p1[i]=p0[i]+(h*w[i]);
        delete[] pnt2;
        }
    void gl_draw()
        {
        int i,j,n=36;
        double a,da=2.0*M_PI/double(n),p[3],uu,vv;
        glBegin(GL_LINES);
        glVertex3dv(p0); glVertex3dv(p1);
        glEnd();
        glBegin(GL_LINE_LOOP);
        for (a=0.0,i=0;i<n;i++,a+=da)
            {
            uu=r*cos(a);
            vv=r*sin(a);
            for (j=0;j<3;j++) p[j]=p0[j]+(u[j]*uu)+(v[j]*vv);
            glVertex3dv(p);
            }
        glEnd();
        glBegin(GL_LINE_LOOP);
        for (a=0.0,i=0;i<n;i++,a+=da)
            {
            uu=r*cos(a);
            vv=r*sin(a);
            for (j=0;j<3;j++) p[j]=p1[j]+(u[j]*uu)+(v[j]*vv);
            glVertex3dv(p);
            }
        glEnd();
        }
    };
//---------------------------------------------------------------------------

用法是相同的...我对此进行了测试:

OBB3D obb;
OBC3D obc;
void compute()
    {
    int i,n=500;
    // random pnt cloud
    Randomize();
    RandSeed=98123456789;
    pnt.allocate(3*n); pnt.num=0;

    // random U,V,W basis vectors
    double u[3],v[3],w[3],x,y,z,a;
    for (i=0;i<3;i++) w[i]=Random()-0.5;    // random direction
    vector_one(w,w);        // w/=|w|
    vector_ld(u,1.0,0.0,0.0);
    if (fabs(vector_mul(u,w))>0.75) // |dot(u,w)>0.75| measn near (anti)parallel
     vector_ld(u,0.0,1.0,0.0);
    vector_mul(v,w,u);      // v = cross(w,u)
    vector_mul(u,v,w);      // u = cross(v,w)
    vector_one(u,u);        // u/=|u|
    vector_one(v,v);        // v/=|v|
    // random cylinder point cloud
    for (i=0;i<n;i++)
        {
        a=2.0*M_PI*Random();
        x= 0.5+(0.75*(Random()-0.5))*cos(a);
        y=-0.3+(0.50*(Random()-0.5))*sin(a);
        z= 0.4+(0.90*(Random()-0.5));
        pnt.add((x*u[0])+(y*v[0])+(z*w[0]));
        pnt.add((x*u[1])+(y*v[1])+(z*w[1]));
        pnt.add((x*u[2])+(y*v[2])+(z*w[2]));
        }
    obb.compute(pnt.dat,pnt.num);
    obc.compute(pnt.dat,pnt.num);
    }

其中List<double> pnt是我的动态数组模板double pnt[]。这里不重要。

请注意,如果您为da,db方向搜索选择的初始步长(W)过大,则可能会将自己陷入局部最小值内,从而错过正确的解决方案。

答案 2 :(得分:1)

找到确切的解决方案似乎是一个非常困难的问题。通过在轴方向上进行假设,并旋转云(仅保留凸包的顶点)并将点投影到XY上,您确实可以转向最小的封闭圆问题。

但是您必须对可能的方向做出几个假设。对于确切的解决方案,应尝试所有方向,以使包围圆由不同的成对顶点或三重顶点定义。这在旋转角度的空间中定义了复杂的区域,并且对于每个区域,都有一个可以达到最佳效果的点。这涉及具有非线性约束的高度非线性最小化问题,似乎很难处理。

在这个阶段,我只能推荐一种近似的解决方案,以便您尝试固定数量的预定义方向并求解相应的圆拟合。由于无论如何都是近似解,近似圆拟合也可以。

答案 3 :(得分:1)

概念性答案

  1. 找到两个点之间的最大距离 h 。它们在圆柱体的表面上,连接它们的线将平行于圆柱体的轴线。

  2. 在垂直于该轴的平面上投影所有点。

  3. 在该平面上找到两点之间具有最大距离 d 的点。他们定义了直径为 d 的圆柱体。

  4. 具有最小可能体积*的圆柱体包含所有点

formula

*假设只有一对点之间的最大距离定义了圆柱体的轴。如果有可能两对点共享最大值,请对每对重复步骤2-4,并选择直径最小的圆柱。

Python答案

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib notebook

from numpy.linalg import norm
from scipy.spatial.distance import pdist, squareform

如果还没有积分,则生成积分:

np.random.seed(0)
N = 30
M = np.random.randint(-3,3,(N,3))
print(M)

[[ 1  2 -3]
 [ 0  0  0]
 [-2  0  2]
 [-1  1 -3]
 [-3  1 -1]
 ...
 [ 1 -3  1]
 [ 0 -1  2]]

计算每对可能的点对之间的距离,然后选择距离最大的那对点。

max_dist_pair = list(pd.DataFrame(squareform(pdist(M))).stack().idxmax())
p1 = M[max_dist_pair[0]]
p2 = M[max_dist_pair[1]]
print(f"Points defining cylinder faces: {p1}, {p2}")
print(f"Length of cylinder: {norm(p1-p2)}")

Points defining cylinder faces: [-1 -3 -3], [1 2 2]
Length of cylinder: 7.3484692283495345

用点画出所有点,将其显示为蓝色,将最大分隔的点显示为红色。

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(*M.T, c = ['red' if i in max_dist_pair else 'blue' for i in range(N)])

ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")

plt.show()

enter image description here

这里是旋转的图,所以我们沿着两个红点之间的轴看。

enter image description here

以上视图与在垂直于圆柱轴的平面上投影的点相同。找到包含该平面上的点的最小圆。为此,我们找到每个点到轴的位移,然后找到两个点之间的最大距离。

perp_disp = (np.cross(p2-p1, M-p1))/norm(p2-p1) # Get perpendicular displacement vectors.
print(perp_disp)

[[-3.40206909  1.36082763  0.        ]
 [ 0.         -0.13608276  0.13608276]
 [ 1.36082763 -2.04124145  1.4969104 ]
 [-2.72165527  0.          1.08866211]
 [-1.36082763 -1.90515869  2.44948974]
 [ 0.68041382 -0.95257934  0.68041382]
 [ 2.72165527  0.68041382 -1.76907593]
 ...
 [ 0.          0.27216553 -0.27216553]
 [ 0.         -0.40824829  0.40824829]
 [ 2.72165527  0.27216553 -1.36082763]
 [ 2.04124145 -0.68041382 -0.13608276]]

通过执行与上面相同的pdist技巧来找到最大距离。

max_perp_disp_pair = list(pd.DataFrame(squareform(pdist(perp_disp))).stack().idxmax())
perp_p1 = M[max_perp_disp_pair[0]]
perp_p2 = M[max_perp_disp_pair[1]]
print(perp_p1, perp_p2)

[ 1  2 -3] [-3 -2  1]

最后,我们得到圆柱体的直径。

print(norm(perp_p1 - perp_p2))
6.92820323028

可以包含这些点的圆柱体的最小体积为

formula

注释

  • 使用Numpy的成对距离函数pdist找到了点之间的最大距离。然后使用squareform对其进行格式化,以将其放入熊猫DataFrame中,以便可以使用idxmax轻松找到两个点的索引。如果没有熊猫,可能是更好的方法。

  • 如果np.cross部分让您挠头,这仅仅是在寻找点与线之间的最小距离。如果您有兴趣,我可以提供更多详细信息,但是如果您绘制两条线的叉积,则会得到一个平行四边形,其中两条非平行边由两条线给出。此平行四边形与矩形的面积相同,长度等于其中一条线,宽度等于从点到线的距离。

答案 4 :(得分:0)

蛮力算法

让我们从最简单的情况开始:3分。

最小的圆柱体在其表面上具有三个点。最小半径表示横截面的直径为两点,即使该横截面不垂直于圆柱体的轴也是如此。第三点也是一样。
因此,轴穿过直径的中心,该中心是由两个点定义的线段的中点。
因此,轴由两个中间点定义。

我们有三个中间点和三个可能的轴: enter image description here

通过在解中找到最小的ri来选择最佳解(最小半径)。

请注意,每个轴ai与某个Pi,Pj线段平行。


GENERIC,n个要点案例

假设我们已经找到了解决方案。该解决方案的至少三个点位于表面上,这类似于三点情况。如果找到该三元组,则可以应用中点方法。
因此,解决方案的轴是一条穿过两个中间点的线。在这种蛮力算法中,我们对它们全部进行了测试。对于每个轴,计算垂直于该轴的所有点的距离,并得到最大di,以便获得每个轴的包围半径。

解决方案是最小di的轴。半径是此最小值di

此算法的顺序是什么?
对于n点,我们有C(n,2)= n(n-1)/2个中间点。和n(n-1)(n(n-1)-2)/8轴。对于每个轴,我们测试n的半径。这样我们得到 O(n 5

改进

  • 首先构建凸包并丢弃所有内部点
  • 我觉得解决方案的轴由最长的Mi,Mj段定义。

编辑

如@JRP所示,此方法未找到棱镜的最佳解决方案。尝试使用三角形的中心(而不是线段的中间)也不起作用,请考虑在立方体顶点中有8个点的点云。

这可能是很好的替代方法,但不是最佳解决方案。

答案 5 :(得分:-2)

首先执行linear regression,例如通过Ordinary least squares。这将为您提供圆柱的轴线。

现在计算与该3D轴垂直的每个点的所有距离。最大值是圆柱体的半径。

如果在计算垂直距离的过程中,您还获得了轴内 的对齐距离(将原点放置在较远的位置),那么最小和最大对齐距离就是顶部和底部的对齐距离脸。