多段線簡化算法,看這一篇就夠了

本文參考自:https://www.codeproject.com/Articles/114797/Polyline-Simplification

前言

在計算幾何中,經常我們會碰到需要簡化輸入Polyline的場景。多段線(Polyline)簡化算法可以幫助我們減少Polyline的點數,從而降低輸入規模。對多段線簡化算法,通常的做法是在一定的近似精度下,刪除一些點或者邊。在計算機圖形學中,當多段線分辨率高於顯示分辨率時,多個頂點和邊很可能映射到單個像素上,這意味着計算機將花費一些額外的資源去繪製不可見的頂點。在繪製之前,可以通過多段線簡化算法減少點數,從而輕鬆地避免這種計算資源的浪費。

實踐中,根據不同場景的需要,有下面幾種簡化算法通常比較有用:

  • 臨近點簡化(Radial Distance)
  • 垂直距離簡化(Perpendicular Distance)
  • Douglas-Peucker算法

臨近點簡化

該算法是一個簡單的O(n)複雜度的暴力算法。對某個頂點,接下來連續的頂點與該點(key)的距離如果都在給定的誤差範圍內,那麼將移除這些點。過程如下圖:

我們通常保留Polyline的起始點和終止點作爲結果的一部分。首先將起始點標記爲Key,然後沿着Polyline前進,與Key相鄰的連續頂點到Key的距離如果小於給定誤差就移除,遇到的第一個超過誤差的點標記爲下一個Key;從這個新的key開始重複上面的過程,知道到達之後一個頂點。

垂直距離簡化

臨近點算法將點-點距離作爲誤差判據,而垂直距離簡化則是將點-線段的距離作爲誤差判據。對於每個頂點Vi,需要計算它與線段[Vi-1, Vi+1]的垂直距離,距離比給定誤差小的點都將被移除。過程如下圖所示:

如上圖,首先,對前三個頂點進行處理,計算第二個頂點的垂直距離,將這個距離與容差進行比較,大於設定的誤差,所以第二個頂點作爲Key保留(簡化結果的一部分)。然後算法開始處理接下來的三個頂點。這一次,計算的距離低於公差,因此中間頂點被刪除。如此重複,直到處理完所有頂點。

Note:對於每個被移除的頂點Vi,下一個可能被移除的候選頂點是Vi+2。這意味着原始多段線的點數最多隻能減少50%。爲了獲得更高的頂點減少率,需要多次遍歷。

Douglas-Peucker算法

Douglas-Peucker算法使用點-邊距離作爲誤差衡量標準。該算法從連接原始Polyline的第一個和最後一個頂點的邊開始,計算所有中間頂點到邊的距離,距離該邊最遠的頂點,如果其距離大於指定的公差,將被標記爲Key並添加到簡化結果中。這個過程將對當前簡化中的每條邊進行遞歸,直到原始Polyline的所有頂點與當前考察的邊的距離都在允許誤差範圍內。該過程如下圖所示:

初始時,簡化結果只有一條邊(起點-終點)。第一步中,將第四個頂點標記爲一個Key,並相應地加入到簡化結果中;第二步,處理當前結果中的第一條邊,到該邊的最大頂點距離低於容差,因此不添加任何點;在第三步中,當前簡化的第二個邊找到了一個Key(點到邊的距離大於容差)。這條邊在這個Key處被分割,這個新的Key添加到簡化結果中。這個過程一直繼續下去,直到找不到新的Key爲止。注意,在每個步驟中,只處理當前簡化結果的一條邊。

 這個算法的最壞時間複雜度是O(nm), 平均時間複雜度 O(n log m),其中m是簡化後的Polyline的點數。因此,該算法是Output-sensitive的。當m很小時,該算法會很快。

該算法在實踐中有很好的近似效果,且效率很高,下面給出DP算法的實現:

 

// Assume that classes are already given for the objects:
//    Point and Vector with
//        coordinates {float x, y, z;}     // as many as are needed
//        operators for:
//            == to test equality
//            != to test inequality
//            (Vector)0 = (0,0,0)         (null vector)
//            Point  = Point ± Vector
//            Vector = Point - Point
//            Vector = Vector ± Vector
//            Vector = Scalar * Vector    (scalar product)
//            Vector = Vector * Vector    (cross product)
//    Segment with defining endpoints {Point P0, P1;}
//===================================================================
 

// dot product (3D) which allows vector operations in arguments
#define dot(u,v)  ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
#define norm2(v)  dot(v,v)         // norm2 = squared length of vector
#define norm(v)   sqrt(norm2(v))   // norm = length of vector
#define d2(u,v)   norm2(u-v)       // distance squared = norm2 of difference
#define d(u,v)    norm(u-v)        // distance = norm of difference
 


// poly_decimate(): - remove vertices to get a smaller approximate polygon
//    Input:  tol = approximation tolerance
//            V[] = polyline array of vertex points
//            n   = the number of points in V[]
//    Output: sV[]= reduced polyline vertexes (max is n)
//    Return: m   = the number of points in sV[]
int
poly_decimate( float tol, Point* V, int n, Point* sV )
{
    int    i, k, m, pv;             // misc counters
    float  tol2 = tol * tol;        // tolerance squared
    Point* vt = new Point[n];       // vertex buffer
    int*   mk = new int[n] = {0};   // marker  buffer

    // STAGE 1.  Vertex Reduction within tolerance of  prior vertex cluster
    vt[0] = V[0];               // start at the beginning
    for (i=k=1, pv=0; i<n; i++) {
        if (d2(V[i], V[pv]) < tol2)
            continue;
        vt[k++] = V[i];
        pv = i;
    }
    if (pv < n-1)
        vt[k++] = V[n-1];       // finish at the end

    // STAGE 2.  Douglas-Peucker polyline reduction
    mk[0] = mk[k-1] = 1;       //  mark the first and last vertexes
    poly_decimateDP( tol, vt, 0, k-1, mk  );

    // copy marked vertices to the reduced polyline
    for (i=m=0; i<k; i++) {
        if (mk[i])
            sV[m++] =  vt[i];
    }
    delete vt;
    delete mk;
    return m;         //  m vertices in reduced polyline
}


// poly_decimateDP():
//  This is the Douglas-Peucker recursive reduction routine
//  It marks vertexes that are part of the reduced polyline
//  for approximating the polyline subchain v[j] to v[k].
//    Input:  tol  = approximation tolerance
//            v[]  = polyline array of vertex points
//            j,k  = indices for the subchain v[j] to v[k]
//    Output: mk[] = array of markers matching vertex array v[]
void
poly_decimateDP( float tol, Point* v, int j, int k, int* mk )
{
    if (k <= j+1) // there is nothing to decimate
        return;

    // check for adequate approximation by segment S from v[j] to v[k]
    int     maxi = j;           // index of vertex farthest from S
    float   maxd2 = 0;          // distance squared of farthest vertex
    float   tol2 = tol * tol;   // tolerance squared
    Segment S = {v[j], v[k]};   // segment from v[j] to v[k]
    Vector  u = S.P1 - S.P0;    // segment direction vector
    double  cu = dot(u,u);      // segment length squared

    // test each vertex v[i] for max distance from S
    // compute using the Algorithm dist_Point_to_Segment()
    // Note: this works in any dimension (2D, 3D, ...)
    Vector  w;
    Point   Pb;                 // base of perpendicular from v[i] to S
    double  b, cw, dv2;         // dv2 = distance v[i] to S squared

    for (int i=j+1; i<k; i++)
    {
        // compute distance squared
        w = v[i] - S.P0;
        cw = dot(w,u);
        if ( cw <= 0 )
            dv2 =d2(v[i], S.P0);
        else if ( cu <= cw )
            dv2 =d2(v[i], S.P1);
        else {
            b = cw / cu;
            Pb = S.P0 + b  * u;
            dv2 =d2(v[i], Pb);
        }
        // test with current max distance  squared
        if (dv2 <= maxd2)
            continue;
        // v[i] is a new max vertex
        maxi = i;
        maxd2 = dv2;
    }
    if (maxd2 > tol2)         // error is worse than the tolerance
    {
        // split the polyline at the farthest  vertex from S
        mk[maxi] = 1;       // mark v[maxi] for the reduced polyline
        // recursively decimate the two subpolylines at v[maxi]
        poly_decimateDP(  tol, v, j, maxi, mk );  // polyline v[j] to v[maxi]
        poly_decimateDP(  tol, v, maxi, k, mk );  // polyline v[maxi] to v[k]
    }
    // else the approximation is OK, so ignore intermediate vertexes
    return;
}
//===================================================================
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章