【问题标题】:smallest enclosing cylinder最小封闭圆柱体
【发布时间】:2018-07-19 19:57:10
【问题描述】:

对于 3D 点云,是否有一种算法可以找到半径最小的封闭圆柱体?我知道具有最小封闭圆的 2D 案例已解决(例如此线程 Smallest enclosing circle in Python, error in the code),但是 3D 有什么可行的方法吗?


EDIT1:OBB。下面是弧形点云的示例。这个工具找到了最小的封闭圆https://www.nayuki.io/page/smallest-enclosing-circle

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

我的结论是,OBB 方法并不通用


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

我的结论是 PCA 方法并不通用


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

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


EDIT4:已发布的方法。 @meowgoesthedog:Michel Petitjean 的论文(“About the Algebraic Solutions of Smallest Enclosure Cylinders Problems”)可能会有所帮助,但我没有足够的资格将其转换为工作程序。作者自己做到了(这里是模块 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 个点,适合圆柱体。两点之间的最大距离 - 绿色对角线。明显不平行于圆柱轴。

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

【问题讨论】:

  • 圆柱体是否需要轴对齐?
  • 不,不需要。任何方向和任何长度都是可以接受的 - 直径应该是最小的
  • 直径最小的圆柱体不一定是体积最小的圆柱体
  • 这个问题让人想起PCA。

标签: algorithm computational-geometry


【解决方案1】:

概念性答案

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

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

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

  4. 包含所有点的体积最小的圆柱体有

.

* 这假设只有一对点之间的最大距离定义了圆柱体的轴。如果有可能两对点共享最大值,则对每对点重复步骤 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()

这是相同的旋转图,因此我们沿着两个红点之间的轴查看。

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

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

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

注意事项

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

  • 如果np.cross 部分让您摸不着头脑,这只是为了找到点和线之间的最小距离。如果您有兴趣,我可以跟进更多细节,但是如果您绘制两条线的叉积,您会得到一个平行四边形,其中两条不平行的边由线给出。这个平行四边形的面积与一个矩形的面积相同,长度等于其中一条线,宽度等于点到线的距离。

【讨论】:

  • 请检查顶部的问题 - EDIT5。我需要粘贴一张图片,但在 cmets 中不能这样做。
【解决方案2】:
  1. 计算 OBB

    所以要么使用 PCA 或这个

    获取3DOBB。链接中的代码必须移植到3D,但原理是一样的。这里我的更多 advanced 3D OBB approximation 在 cube_map 上使用递归搜索(这里的代码和方法不如它)。

  2. 初步猜测

    所以 OBB 会给你定向边界框。它最大的一侧将平行于气缸的旋转轴。因此,让我们从描述这个 OBB 的圆柱体开始。所以中心轴将是 OBB 的中心并且平行于它的最大边。 (如果你没有最大的一面,那么你需要检查所有 3 个组合)。直径将是其余边中较大的一个。

  3. 装缸

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

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

[Edit1] C++ 中的 3D OBB

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

我使用 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,vw 记住最小体积OBB。然后以较小的步长仅递归搜索找到的最佳解决方案附近的位置以提高准确性。

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

代码尚未优化(某些部分如w 轴检查)可以移动到嵌套for循环的较低层。但我想尽可能保持简单易懂。

[Edit2] C++ 中的 3D OBC

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

蓝色是3D OBB,棕色/橙色是找到的3D OBC。代码如下:

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&lt;double&gt; pnt 是我的动态数组模板double pnt[]。在这里哪个不重要。

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

    • 我同意你找到凸包并删除所有内部点的建议。让我感兴趣的是——圆柱轴与 OBB 轴的近似有多好。
    • @JRP:你叫什么OBB?
    • 定向边界框 - 查看 Spektre 的最佳答案
    • @JRP:但是方向是什么???差异至少与 2D 中的顺序相同。
    • OBB最长边的方向应该非常接近封闭圆柱体的轴线
    【解决方案4】:

    首先找到点云的定向边界框 (OBB)。有几种算法可以做到这一点。这个可能是最优的:

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

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

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

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

    这种方法可能有效,但在计算上不是最优的,因为您必须计算所有点到圆柱体的距离。也许内圆柱可用于剔除其中的所有点。我没有详细阐述这个想法。

    更新:

    似乎这个问题并不清楚什么是“最小”,实际上是要求超出“最小”的东西,而且提出的不是很好。包围点云的“最小”圆柱体应该最小化圆柱体内的空白空间(至少我理解为最小)。但是 OP 也施加了最小圆柱体应该适合输入数据的形状的约束。这意味着,如果输入数据是对圆柱体的一半进行采样(被其孤边切割),则答案应该是最适合该半部形状的圆柱体。不管那个圆柱体是否比其他包含数据的圆柱体有更多的空间。

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

    我基于 OBB 的答案(和其他答案)确实回答了关于封闭数据的“最小”圆柱体的问题,以最大限度地减少圆柱体内的空白空间。

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

    【讨论】:

    • 问题第一句表示需要“最小半径”。标题不准确,有点像“最小圆”问题。我了解您对 OBB 和气缸体积优化的回答。但是,OBB 中最长边的方向有可能近似于圆柱轴的位置。如果是真的,那么它可以简化任务
    【解决方案5】:

    蛮力算法

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

    最小的圆柱体表面有三个点。最小半径意味着两个点在横截面的直径内,即使该横截面不垂直于圆柱体的轴线。第三点也是一样。
    所以轴穿过直径的中心,这是由两点定义的线段的中点。
    因此,轴由两个中间点定义。

    我们有三个中点和三个可能的轴:

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

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


    通用,n 点案例

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

    解决方案是最小di 的轴。半径就是这个最小值di

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

    改进

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

    编辑

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

    这可能是一个很好的近似值,但不是最优解。

    【讨论】:

    • 在 3D 案例中可能很难找到中间点 - 请参阅主要问题中的 EDIT5,三角棱镜案例。
    • @JRP 嗯,看来我的方法比您绘制的三角形中心情况提供了更好的解决方案(最小半径)。
    • 我的印象是在某些情况下找不到坐标轴。我是不是误会了什么?
    • @JRP。你对我的解决方案是正确的。也许使用三角形的中心而不是中间的线段可能会起作用。
    • 在这篇论文arxiv.org/pdf/1008.5259.pdf 中,他们从 4 点云(四面体)和 5 点云(三角双锥)开始分析 3D 案例,并设法通过分析找到轴。
    【解决方案6】:

    首先做一个linear regression,例如Ordinary least squares。这将为您提供圆柱体的轴。

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

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

    【讨论】:

    • 你有证据证明这是最低限度吗?
    • 不会是最小值:OLS最小化平方误差之和,解决问题需要最小化最大误差
    • 这是我的考虑。假设我们找到了所需的圆柱体。将 3D 中的点集分为两个子集:子集 1 = 圆柱体表面附近薄层中的点,子集 2 = 圆柱体内的点,它们更靠近轴而不是表面。如果 subset1 的人口更多,那么您的方法将更精确地近似轴位置。但是,如果子集 2 的人口较多,则 OLS 找到的轴位置将位于圆柱体内的任何随机位置。
    • 举个反例,假设我在 (0,0,0) 处有 10,000 个点,在 (1,0,0) 处有 10,000 个点,在 (0,5,0) 处有 1 个点)。线性回归会使圆柱体的轴与 x 轴非常接近,但 y 轴会产生一个半径较小的圆柱体。
    猜你喜欢
    • 1970-01-01
    • 2015-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-07
    • 1970-01-01
    • 1970-01-01
    • 2014-01-14
    相关资源
    最近更新 更多