Delaunay三角剖分算法

點集的三角剖分(Triangulation),對數值分析(比如有限元分析)以及圖形學來說,都是極爲重要的一項預處理技術。

尤其是Delaunay三角剖分,由於其獨特性,關於點集的很多種幾何圖都和Delaunay三角剖分相關,如Voronoi圖,EMST樹,Gabriel圖等。


Delaunay剖分所具備的優異特性

1.最接近:以最近的三點形成三角形,且各線段(三角形的邊)皆不相交。
2.唯一性:不論從區域何處開始構建,最終都將得到一致的結果。
3.最優性:任意兩個相鄰三角形形成的凸四邊形的對角線如果可以互換的話,那麼兩個三角形六個內角中最小的角度不會變大。
4.最規則:如果將三角網中的每個三角形的最小角進行升序排列,則Delaunay三角網的排列得到的數值最大。
5.區域性:新增、刪除、移動某一個頂點時只會影響臨近的三角形。
6.具有凸多邊形的外殼:三角網最外層的邊界形成一個凸多邊形的外殼。

概念及定義
二維實數域(二維平面)上的三角剖分

定義1:假設V是二維實數域上的有限點集,邊e是由點集中的點作爲端點構成的封閉線段, E爲e的集合。
那麼該點集V的一個三角剖分T=(V,E)是一個平面圖G,該平面圖滿足條件:
1.除了端點,平面圖中的邊不包含點集中的任何點。
2.沒有相交邊。
3.平面圖中所有的面都是三角面,且所有三角面的合集就是點集V的凸包。
那什麼是Delaunay三角剖分呢?不過是一種特殊的三角剖分罷了。從Delaunay邊說起。

Delaunay邊
定義2:假設E中的一條邊e(兩個端點爲a,b),e若滿足下列條件,則稱之爲Delaunay邊:
存在一個圓經過a,b兩點,圓內不含點集V中任何的點,這一特性又稱空圓特性。

Delaunay三角剖分

定義3:如果點集V的一個三角剖分T只包含Delaunay邊,那麼該三角剖分稱爲Delaunay三角剖分。

定義4:假設T爲V的任一三角剖分,則T是V的一個Delaunay三角剖分,當前僅當T中的每個三角形的外接圓的內部不包含V中任何的點。

定義5:V的Voronoi圖是由多邊形區域的集合(有些區域可能不是閉合的),該區域僅含點集中的一個點v,區域中的任何位置到v的距離都比該位置到點集中其它所有點的距離短。

由Voronoi圖和Delaunay三角剖分的關係,可以引出另一個Delaunay三角剖分的定義:
定義6:將Voronoi圖相鄰區域(共邊的區域)中的點連接起來構成的圖,稱爲Delaunay三角剖分。
如下圖:


概念部分到此,下面看看怎麼求Delaunay三角剖分。

計算Delaunay三角剖分

問題1:計算二維Delaunay三角剖分
問題輸入:二維實數域上的點集V
問題輸出:Delaunay三角剖分DT = (V, E).

算法

由不同的定義對應有不同的算法。用得較多的是基於定義3或4的算法。
目前常用的算法又分爲好幾種,被不同的傢伙發現。什麼掃描線法(Sweepline),隨機增量法(Incremental),分治法(Divide and Conquer)

c++實現:(轉載)

//adapted by the example of leanring opencv by crazy_007
//adapted by the OpenCV2.0\samples\c\delaunay.c
//2010-4-22
#include "StdAfx.h"
#include <cv.h>
#include <highgui.h>
void draw_subdiv_edge( IplImage* img, CvSubdiv2DEdge edge, CvScalar color ); //爲了查看代碼方便,不然需調整調用函數順序

/* the script demostrates iterative construction of
   delaunay triangulation and voronoi tesselation */

CvSubdiv2D* init_delaunay( CvMemStorage* storage,
                           CvRect rect )
{
    CvSubdiv2D* subdiv;

    subdiv = cvCreateSubdiv2D( CV_SEQ_KIND_SUBDIV2D, sizeof(*subdiv),
                               sizeof(CvSubdiv2DPoint),
                               sizeof(CvQuadEdge2D),
                               storage );
    cvInitSubdivDelaunay2D( subdiv, rect );//矩形確定的邊界

    return subdiv;
}

//draw subdiv point
void draw_subdiv_point( IplImage* img, CvPoint2D32f fp, CvScalar color )
{
    cvCircle( img, cvPoint(cvRound(fp.x), cvRound(fp.y)), 5, color, CV_FILLED, 8, 0 );//畫fp爲圓心,5爲半徑的實心圓表示delaunay頂點
}
//use an external point to locate an edge or vertex or step around the edges of a delaunay tirangle
//畫出delaunay 頂點
void locate_point( CvSubdiv2D* subdiv, CvPoint2D32f fp, IplImage* img,
                   CvScalar active_color )
{
    CvSubdiv2DEdge e;
    CvSubdiv2DEdge e0 = 0;
    CvSubdiv2DPoint* p = 0;

    cvSubdiv2DLocate( subdiv, fp, &e0, &p );//使用一個外部的點定位邊緣或頂點
                                            //該函數填充三角形的邊緣和頂點或者填充該點所處在的Voronoi面
    if( e0 )
    {
        e = e0;
        do
        {
            draw_subdiv_edge( img, e, active_color );//調用下面函數:畫出紅色直線
            e = cvSubdiv2DGetEdge(e,CV_NEXT_AROUND_LEFT);//遍歷Delaunay圖:返回左區域的下一條的邊緣
        }
        while( e != e0 );
    }

    draw_subdiv_point( img, fp, active_color );//調用上面的函數:實現在該點處畫半徑爲5的圓
}




/*************                 分割線                ************************************/
//draw subdiv edge
void draw_subdiv_edge( IplImage* img, CvSubdiv2DEdge edge, CvScalar color )
{
    CvSubdiv2DPoint* org_pt;
    CvSubdiv2DPoint* dst_pt;
    CvPoint2D32f org;
    CvPoint2D32f dst;
    CvPoint iorg, idst;

    org_pt = cvSubdiv2DEdgeOrg(edge);//Delaunay或者Voronoi邊緣的原始點
    dst_pt = cvSubdiv2DEdgeDst(edge);//Delaunay或者Voronoi邊緣的終點

    if( org_pt && dst_pt )
    {
        org = org_pt->pt;
        dst = dst_pt->pt;

        iorg = cvPoint( cvRound( org.x ), cvRound( org.y ));
        idst = cvPoint( cvRound( dst.x ), cvRound( dst.y ));

        cvLine( img, iorg, idst, color, 1, CV_AA, 0 );//畫紅色直線
    }
}

//draw subdiv:遍歷所有的Delaunay邊
void draw_subdiv( IplImage* img, CvSubdiv2D* subdiv,
                  CvScalar delaunay_color, CvScalar voronoi_color )
{
    CvSeqReader reader;//使用cvSeqReader逐步遍歷邊:獲得細分結構
    int i, total = subdiv->edges->total;
    int elem_size = subdiv->edges->elem_size;

    cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );//initialize reader of the sequence

    for( i = 0; i < total; i++ )//total是邊數目
    {
        CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);
                                                            //CvQuadEdge2D平面劃分中的Quad-edge(四方邊緣結構):四個邊緣 (e, eRot(紅色) 以及它們的逆(綠色)
       if( CV_IS_SET_ELEM( edge ))
        {
            draw_subdiv_edge( img, (CvSubdiv2DEdge)edge + 1, voronoi_color ); //不知如何理解(CvSubdiv2DEdge)edge + 1 
                //書中P346:voronoi_edge=(CvSubdiv2DEdge)edge + 1
            //直接採用數組位移法進行各種邊的對應的(即edge+1),
            //cvSubdiv2DRotateEdge((CvSubdiv2DEdge)edge,1)=(CvSubdiv2DEdge)edge+1
            //參考網址爲:http://tech.ddvip.com/2007-12/119897724239781.html
            draw_subdiv_edge( img, (CvSubdiv2DEdge)edge, delaunay_color ); //調用上面的子函數
        }

        CV_NEXT_SEQ_ELEM( elem_size, reader );
    }
}



/*************                 分割線                ************************************/
//draw the voronoi facet:遍歷Voronoi面
void draw_subdiv_facet( IplImage* img, CvSubdiv2DEdge edge )
{
    CvSubdiv2DEdge t = edge;
    int i, count = 0;
    CvPoint* buf = 0;

    // count number of edges in facet
    do
    {
        count++;
        t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );//返回左區域的下一條的邊緣
    } while (t != edge );

    buf = (CvPoint*)malloc( count * sizeof(buf[0]));

    // gather points
    t = edge;
    for( i = 0; i < count; i++ )
    {
        CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );//獲得邊緣的起點
        if( !pt ) break;
        buf[i] = cvPoint( cvRound(pt->pt.x), cvRound(pt->pt.y));//點記錄在buf中
        t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
    }

    if( i == count )
    {
        CvSubdiv2DPoint* pt = cvSubdiv2DEdgeDst( cvSubdiv2DRotateEdge( edge, 1 ));//獲得邊緣的終點
        cvFillConvexPoly( img, buf, count, CV_RGB(rand()&255,rand()&255,rand()&255), CV_AA, 0 );//一次只能畫一個多邊形,而且只能畫凸多邊形
        cvPolyLine( img, &buf, &count, 1, 1, CV_RGB(0,0,0), 1, CV_AA, 0);//一次調用中繪製多個多邊形
        draw_subdiv_point( img, pt->pt, CV_RGB(0,0,0));//畫圓
    }
    free( buf );
}
//draw & paint voronoi graph
void paint_voronoi( CvSubdiv2D* subdiv, IplImage* img )
{
    CvSeqReader reader;
    int i, total = subdiv->edges->total;
    int elem_size = subdiv->edges->elem_size;

    cvCalcSubdivVoronoi2D( subdiv );//計算Voronoi圖表的細胞結構

    cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );

    for( i = 0; i < total; i++ )
    {
        CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);

        if( CV_IS_SET_ELEM( edge ))
        {
            CvSubdiv2DEdge e = (CvSubdiv2DEdge)edge;
            // left
            draw_subdiv_facet( img, cvSubdiv2DRotateEdge( e, 1 ));//調用上面的子函數

            // right
            draw_subdiv_facet( img, cvSubdiv2DRotateEdge( e, 3 ));
        }

        CV_NEXT_SEQ_ELEM( elem_size, reader );
    }
}
/*************                 分割線                ************************************/

void run(void)
{
    char win[] = "source";
    int i;
    CvRect rect = { 0, 0, 600, 600 };//外界邊界矩形大小
    CvMemStorage* storage;//爲delaunay申請內存空間
    CvSubdiv2D* subdiv;//細分
    IplImage* img;
    CvScalar active_facet_color, delaunay_color, voronoi_color, bkgnd_color;

    active_facet_color = CV_RGB( 255, 0, 0 );
    delaunay_color = CV_RGB( 0,0,0);
    voronoi_color = CV_RGB(0, 180, 0);
    bkgnd_color = CV_RGB(255,255,255);

    img = cvCreateImage( cvSize(rect.width,rect.height), 8, 3 );//創建白色背景的圖像
    cvSet( img, bkgnd_color, 0 );

    cvNamedWindow( win, 1 );//窗口名字爲"source"

    storage = cvCreateMemStorage(0);//初始化內存空間
    subdiv = init_delaunay( storage, rect );//initialization convenience function for delaunay subdivision:調用子函數確定矩形邊界

    printf("Delaunay triangulation will be build now interactively.\n"
           "To stop the process, press any key\n\n");

    for( i = 0; i < 20; i++ )
    {
        CvPoint2D32f fp = cvPoint2D32f( (float)(rand()%(rect.width-10)+5),
                                        (float)(rand()%(rect.height-10)+5));//This is our point holder

        locate_point( subdiv, fp, img, active_facet_color );//調用函數
        cvShowImage( win, img );

        if( cvWaitKey( 100 ) >= 0 ) //等待600ms
            break;

        cvSubdivDelaunay2DInsert( subdiv, fp );//向Delaunay三角測量中插入一個點
        cvCalcSubdivVoronoi2D( subdiv );//計算Voronoi圖表的細胞結構
        cvSet( img, bkgnd_color, 0 ); // 給一個對象全部元素賦值:void cvSet( CvArr* arr, CvScalar value, const CvArr* mask=NULL );
        draw_subdiv( img, subdiv, delaunay_color, voronoi_color );//調用子函數:cvSeqReader逐步遍歷邊來獲得細分結構
        cvShowImage( win, img );

        if( cvWaitKey(100) >= 0 )
            break;
    }

    cvSet( img, bkgnd_color, 0 );
    paint_voronoi( subdiv, img );//調用子函數:
    cvShowImage( win, img );

    cvWaitKey(0);

    cvReleaseMemStorage( &storage );
    cvReleaseImage(&img);
    cvDestroyWindow( win );
}

int main( int argc, char** argv )
{
    run();
    return 0;
}

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