點集的三角剖分(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;
}