轉載:http://blog.csdn.net/raby_gyl/article/details/17409717
相關文章:OpenCV三角剖分的遍歷和紋理映射:http://blog.csdn.net/raby_gyl/article/details/19758167
Delaunay三角剖分是1934年發明的將空間點連接為三角形,使得所有三角形中最小角最大的一個技術。
如果你熟悉計算機圖形學,你便會知道Delaunay三角剖分是變現三維形狀的基礎。如果我們在三維空間渲染一個,我們可以通過這個物體的投影來建立二維視覺圖,並用二維Delaunay三角剖分來分析識別該物體,或者將它與實物相比較。Delaunay剖分是連接計算機視覺與計算機圖形學的橋梁。然而使用OpenCV實現三角剖分的不足之處就是OpenCV只實現了二維的Delaunay剖分。如果我們能夠對三維點進行三角剖分,也就是說構成立體視覺,那么我們可以在三維的計算機圖形和計算機視覺進行無縫的轉換。然而二維三角剖分通常用於計算機視覺中標記空間目標的特征或運動場景跟蹤,目標識別,或兩個不同的攝像機的場景匹配(如圖從立體圖像中獲得深度信息)。
下面內容摘自:http://www.cnblogs.com/RenLiQQ/archive/2008/02/06/1065399.html
1、三角剖分與Delaunay剖分的定義
如何把一個離散幾何剖分成不均勻的三角形網格,這就是離散點的三角剖分問題,散點集的三角剖分,對數值分析以及圖形學來說,都是極為重要的一項處理技術。該問題圖示如下:

1.1 三角剖分定義
【定義】三角剖分:假設V是二維實數域上的有限點集,邊e是由點集中的點作為端點構成的封閉線段,E為e的集合。那么該點集V的一個三角剖分T=(V,E)是一個平面圖G,該平面圖滿足條件:
1、除了端點,平面圖中的邊不包含點集中的任何點。
2、沒有相交邊。(邊和邊沒有交叉點)
3、平面圖中所有的面都是三角面,且所有三角面的合集是散點集V的凸包。
1.2 Delaunay三角剖分的定義
在實際中運用的最多的三角剖分是Delaunay三角剖分,它是一種特殊的三角剖分。先從Delaunay邊說起:
【定義】Delaunay邊:假設E中的一條邊e(兩個端點為a,b),e若滿足下列條件,則稱之為Delaunay邊:存在一個圓經過a,b亮點,圓內(注意是園內,圓上最多三點共圓)不含點集V中任何其他的點,這一特性又稱空圓特性。
【定義】Delaunay三角剖分:如果點集V的一個三角剖分T只包含Delaunay邊,那么該三角剖分稱為Delaunay三角剖分。
1.3 Delaunay三角剖分的准則
要滿足Delaunay三角剖分的定義,必須符合兩個重要的准則:
1、空圓特性:Delaunay三角網是唯一的(任意四點不能共圓),在Delaunay三角形網中任一三角形的外接圓范圍內不會有其它點存在。如下圖所示:

2、最大化最小角特性:在散點集可能形成的三角剖分中,Delaunay三角剖分所形成的三角形的最小角最大。從這個意義上講,Delaunay三角網是“最接近於規則化的”三角網。具體的說是在兩個相鄰的三角形構成凸四邊形的對角線,在相互交換后,兩個內角的最小角不再增大。如下圖所示:

1.4 Delaunay三角剖分的特性
以下是Delaunay剖分所具備的優異特性:
1、最接近:以最接近的 三點形成三角形,且各線段(三角行的邊)皆不相交。
2、唯一性:不論從區域何處開始構建,最終都將得到一致的結果。
3、最優性:任意兩個相鄰三角形構成的凸四邊形的對角線如何可以互換的話,那么兩個三角形六個內角中最小角度不會變化。
4、最規則:如果將三角網中的每個三角形的最小角進行升序排列,則Delaunay三角網的排列得到的數值最大。
5、區域性:新增、刪除、移動某一個頂點只會影響鄰近的三角形。
6、具有凸邊形的外殼:三角網最外層的邊界形成一個凸多邊形的外殼。
1.5局部最優化處理
理論上為了構造Delaunay三角網,Lawson提出的局部優化過程LOP(Local Optimization Procedure),一般三角網經過LOP處理,即可確保為Delaunay三角網,其基本做法如下所示:
1、將兩個具有共同邊的三角形合成一個多邊形。
2、以最大空圓准則作檢查,看其第四個頂點是否在三角形的外接圓內。
3、如果在,修正對角線即將對角線對調,即完成局部優化過程的處理。
LOP處理過程如下圖所示:

2、Delaunay剖分的算法
Delaunay剖分是一種三角剖分的標准,實現它有多種算法。

由表可以看出,三角網生成法的時間效率最低,分治算法的時間效率最高,逐點插入法效率居中。由於區域生長法本質的缺陷,導致其效率受限,這種方法在80年代中期以后已經很少使用。分治算法時間效率相對較高,但是由於其遞歸執行,所以需要較大的內存空間,導致其空間效率較低。此外,分治法的數據處理及結果的優化需要的工作量也比較大。逐點插入算法實現簡單,時間效率比較高,而運行占用的空間也較小,從時間效率和空間效率綜合考慮,性價比最高,因而應用廣泛。
1977年,Lawson提出了逐點插入法建立Delaunay三角網的算法思想。之后Lee和Schachlter,Bowyer,Watson,Sloan,先后進行了發展和完善。他們的算法在初始化三角網的建立方法、定位點所在三角形的過程、以及插入的過程方面各具特點。
2.1 Lawson算法
逐點插入的Lawson算法是Lawson在1977提出的,該算法思路簡單,易於編程實現。基本原理為:首先建立一個大的三角形或多邊形,把所有數據點包圍起來,向其中插入一點,該點與包含它的三角形三個頂點相連,形成三個新的三角形,然后逐個對它們進行空外接圓檢測,同時用Lawson設計的局部最優化過程LOP進行優化,集通過交換對角線的方法來保證所形成的三角網為Delaunay三角網。
上述基於散點的構網算法理論嚴密、唯一性好,網格滿足空圓特性,較為理想。由其逐點插入的構網過程可知,遇到非Delaunay邊時,通過刪除調整,可以構造形成新的Delaunay邊。在完成構網后,增加新點時,無需對所有點進行重新構網,只需要對新點的影響三角形范圍進行局部聯網,且局部聯網的方法簡單易行。同樣,點的刪除、移動也可快速動態地進行。但在實際應用當中,這種構網算法當點集較大時網速度也較慢,如果點集范圍是非凸區域或者存在內環,則會產生非法三角形。
2.2 Bowyer-Watson算法
目前采用逐點插入方式生成的Delaunay三角網的算法主要基於Bowyer-Watson算法,Bowyer-Watson算法的主要步驟如下:
1)建立初始三角網格:針對給定的點集V,找到一個包含該點集的矩形R,我們稱R為輔助窗口。連接R的任意一條對角線,形成兩個三角形,作為初始Delaunay三角網格。
2)逐點插入:假設目前已經有一個Delaunay三角網格T,現在在它里面再插入一個點P,需要找到該點P所在的三角形。從P所在的三角形開始,搜索該三角形的鄰近三角形,並進行空外接圓檢測。找到外接圓包含點P的所有的三角形並刪除這些三角形,形成一個包含P的多邊形空腔,我們稱之為Delaunay空腔。然后連接P與Delaunay腔的每一個頂點,形成新的Delaunay三角網格。
3)刪除輔助窗口R:重復步驟2),當點集V中所有點都已經插入到三角形網格中后,將頂點包含輔助窗口R的三角形全部刪除。
在這些步驟中,快速定位點所在的三角形、確定點的影響並構建Delaunay腔的過程是每插入一個點都會進行的。隨着點數的增加,三角形數目增加很快,因此縮短這兩個過程的計算時間,是提高算法效率的關鍵。
算法執行圖示如下:
、
3、OpenCV中的Delaunay和Voronoi細分
學習這部分,也是一個頭疼的問題,要理解需要較好的數據結構作為基礎。由於自己對數據結構也是敬畏三分,所以下面理解不免有誤。
OpenCV中現實的Delaunay三角剖分應該是Bowyer-Watson算法。
3.1創建一個Delaunay或Voronoi細分。
我們需要存儲Delaunay的內存空間和一個外接矩形(該矩形盒子用來確定虛擬三角形)
- // STORAGE AND STRUCTURE FOR DELAUNAY SUBDIVISION //存儲和結構 for三角剖分
- //
- CvRect rect = { 0, 0, 600, 600 }; //Our outer bounding box //我們的外接邊界盒子
- CvMemStorage* storage; //Storage for the Delaunay subdivsion //用來存儲三角剖分
- storage = cvCreateMemStorage(0); //Initialize the storage //初始化存儲器
- CvSubdiv2D* subdiv; //The subdivision itself // 細分
- subdiv = init_delaunay( storage, rect); //See this function below //函數返回CvSubdiv類型指針
init_delaunay函數如下,它是一個OpenCV函數,是一個包含一些OpenCV函數的函數包。
- //INITIALIZATION CONVENIENCE FUNCTION FOR DELAUNAY SUBDIVISION //為三角剖分初始化便利函數
- //
- 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 ); //rect sets the bounds
- return subdiv;//返回申請空間的指針
- }
這些點必須是32位浮點型,並通過下面的方式插入點:
- CvPoint2D32f fp; //This is our point holder//這是我們點的持有者(容器)
- for( i = 0; i < as_many_points_as_you_want; i++ ) {
- // However you want to set points //如果我們的點集不是32位的,在這里我們將其轉為CvPoint2D32f,如下兩種方法。
- //
- fp = your_32f_point_list[i];
- cvSubdivDelaunay2DInsert( subdiv, fp );
- }
1)通過宏cvPoint2D32f(double x,double y)
2)通過cxtype.h下的cvPointTo32f(CvPoint point)函數將整形點方便的轉換為32位浮點型。
當可以通過輸入點(散點集)得到Delaunay三角剖分后,接下來,我們用一下兩個函數設置和清除相關的Voronoi划分:
- cvCalcSubdivVoronoi2D( subdiv ); // Fill out Voronoi data in subdiv //在subdiv中填充Vornoi的數據
- cvClearSubdivVoronoi2D( subdiv ); // Clear the Voronoi from subdiv//從subdiv中清除Voronoi的數據
CvSubdiv2D結構如下:
- #define CV_SUBDIV2D_FIELDS() \
- CV_GRAPH_FIELDS() \
- int quad_edges; \
- int is_geometry_valid; \
- CvSubdiv2DEdge recent_edge; \
- CvPoint2D32f topleft; \
- CvPoint2D32f bottomright;
- typedef struct CvSubdiv2D
- {
- CV_SUBDIV2D_FIELDS()
- }
- CvSubdiv2D;
- #define CV_GRAPH_FIELDS() \
- CV_SET_FIELDS() /* set of vertices */ \
- CvSet *edges; /* set of edges */
- #define CV_SET_FIELDS() \
- CV_SEQUENCE_FIELDS() /*inherits from [#CvSeq CvSeq] */ \
- struct CvSetElem* free_elems; /*list of free nodes */
平面划分是將一個平面分割為一組不重疊的、能夠覆蓋整個平面的區域。結構CvSubdiv2D描述了建立在二維點集上的划分結構,其中點集互相連接且構成平面圖形,該圖形通過結合一些無線連接外部划分點(稱為凸形點)的邊緣,將一個平面用按照其邊緣划分成很多小區域。
對於每一個划分操作,都有一個對偶划分與之對應。對偶的意思是小區域與點(划分的頂點)變換角色,即在對偶划分中,小區域被當做一個頂點(以下稱為虛擬點)而原始的划分頂點被當做小區域。如下圖所示,原始的划分用實線表示,而對偶划分用虛線表示。
OpenCV使用Delaunay算法將平面分割成小的三角形區域(該三角形確保包括所有的分割點)開始不斷迭代完成。在這種情況下,對偶划分就是輸入的二維點集的Voronoi圖表。這種划分可以用於對一個平面進行三維分段變換、形態變換、平面點的快速 定位以及建立特定的圖結構(如NNG,RNG)。

CvQuadEdge2D
CvQuadEdge2D結構包含了兩個Delaunay點和兩個Vorionoi點以及連接它們的邊緣(假設Voronoi點和邊緣已經由函數計算出來,通過上面的函數:cvCalSubdivVoronoi2D(subdiv))。
CvQuadEdge2D定義平面划分中的Quad-edge(四方邊緣結構),其結構如下:
- /
- *
- one of edges within quad-edge, lower 2 bits is index (0..3)
- and upper bits are quad-edge pointer
- *
- /
- typedef long CvSubdiv2DEdge; //四方邊緣結構中的一條邊緣,低兩位表示該邊緣的索引號,其他高位表示邊緣指針。
- /*quad-edge structure fields*/四方邊緣的結構場
- #define CV_QUADEDGE2D_FIELDS() \
- int flags; \
- struct CvSubdiv2DPoint*pt[4]; \
- CvSubdiv2DEdge next[4];
- typedef struct CvQuadEdge2D
- {
- CV_QUADEDGE2D_FIELDS()
- }
- CvQuadEdge2D;

CvSubdiv2DPoint
CvSubdiv2DPoint結構包含Delaunay邊緣及其相連的頂點。
其結構定義如下:
- #define CV_SUBDIV2D_POINT_FIELDS()\
- int flags; \
- CvSubdiv2DEdge first; \
- CvPoint2D32f pt; \
- int id; //This integer can be used to index auxillary data asscoiated with each vertex of the planar subdivison.
- #define CV_SUBDIV2D_VIRTUAL_POINT_FLAG (1 << 30)
- typedef struct CvSubdiv2DPoint
- {
- CV_SUBDIV2D_POINT_FIELDS()
- }
- CvSubdiv2DPoint;
邊緣的遍歷
Subdiv2DRotateEdge函數
功能:函數Subdiv2DRotateEdge根據輸入的邊緣返回四方邊緣結構中的一條邊緣。
格式:
- CvSubdiv2DEdge cvSubdiv2DRotateEdge(CvSubdiv2DEdge edge,int rotate );
edge划分的邊緣(不是四方邊緣結構,即不是CvQuadEdge2D)
rotate 確定函數根據輸入的邊緣返回同一四方邊緣結構中的哪條邊緣,為下列值之一:
1)0 輸入邊緣(如果e是輸入邊緣,則為e)。
2)1 旋轉比那樣(eRot)
3)2 逆邊緣(e的反向邊緣)。
4)3旋轉比那樣的反向邊緣(eRot的反向邊緣)。
cvSubdiv2DGetEdge函數
使用該函數我們可以遍歷Delaunay圖。該函數返回與輸入邊緣相關的邊緣。
格式:
- CvSubdiv2DEdge cvSubdiv2DGetEdge( CvSubdiv2DEdge edge, CvNextEdgeType type );
參數:
edge 划分的邊緣(不是四方邊緣結構)
type 確定函數返回哪條相關邊緣,為下列值之一:
CV_NEXT_AROUND_ORG 邊緣原點的下一條(eOnext,如果e是輸入邊)。
CV_NEXT_AROUND_DST 邊緣頂點的下一條(eDnext)
CV_PREV_AROUND_ORG 邊緣原點的前一條(eRnext的反向)
CV_PREV_AROUND_DST邊緣終點的前一條(eLnext的反向)
CV_NEXT_AROUND_LEFT 左區域的下一條(eLnext) 或下一個左平面
CV_NEXT_AROUND_RIGHT 右區域的下一條(eRnext) 或下一個右平面
CV_PREV_AROUND_LEFT 左區域的前一條(eOnext的反向)或前一個左平面
CV_PREV_AROUND_RIGHT 右區域的前一條(eDnext的反向)或前一個右平面

來至邊緣的點
我們可以通過下面的兩個函數獲得Delaunay或者Voronoi邊緣的兩個實際點:
- CvSubdiv2DPoint* cvSubdiv2DEdgeOrg( CvSubdiv2DEdge edge );
- CvSubdiv2DPoint* cvSubdiv2DEdgeDst( CvSubdiv2DEdge edge );
下面是將CvSubdiv2DPoint點轉換為更熟悉的點CvPoint2D32f 或者CvPoint:
- CvSubdiv2DPoint ptSub; //Subdivision vertex point
- CvPoint2D32f pt32f = ptSub->pt; // to 32f point
- CvPoint pt = cvPointFrom32f(pt32f); // to an integer point
如何從Delaunay/Voronoi細分得到第一條邊或點?
有兩種方法:1)使用一個外部點定位邊緣或頂點 2)遍歷一系列點或邊緣
方法一:使用外部點定位邊緣或頂點
第一種方法是任取一點,然后在細分中定位該點。該點不一定是三角剖分中點,而可以為任意點。
cvSubdiv2DLocate()函數填充三角形的邊緣和頂點(如果必要)或者填充該點所在的Voronoi面,函數的聲明如下:
- CvSubdiv2DPointLocation cvSubdiv2DLocate(
- CvSubdiv2D* subdiv,
- CvPoint2D32f pt,
- CvSubdiv2DEdge* edge,//要填充的邊緣
- CvSubdiv2DPoint** vertex = NULL//如果需要,則填充頂點
- );
請注意,這些不必是最接近的邊緣或頂點,它們只需要在三角形上。此函數的返回值按下列的方式說明點的位置:
1)CV_PTLOC_INSIDE 點落入某些面;*edge將包含該面的一個邊緣。
2)CV_PTLOC_ON_ENCODE 點落於邊緣;*edge含有這個邊緣。
3)CV_PTLOC_VERTEX 該點與一個細分頂點重合;*vertex將包含該頂點的指針。
4)CV_PTLOC_OUTSIDE_RECT 該點處於細分參考矩形之外;該函數返回后不填充指針。
5)CV_PTLOC_ERROR 輸入變量無效。
方法二:遍歷一系列點或邊緣
外接三角形(虛擬)的三個頂點和三個邊的獲取:
1)首先我們要有一個建立的Delaunay細分。
2)我們還需要調用cvCalcSubdivVoronoi2D(subdiv)函數計算相關的Voronoi划分。
3)然后我們就能用CvSubdiv2DPoint *outer_vtx[3]和CvQuadEdge2D*outer_gedges[3]來存儲三個頂點和三條邊。
如下:
- CvSubdiv2DPoint* outer_vtx[3];
- for( i = 0; i < 3; i++ ) {
- outer_vtx[i] =
- (CvSubdiv2DPoint*)cvGetSeqElem( (CvSeq*)subdiv, I );
- }
- CvQuadEdge2D* outer_ qedges[3];
- for( i = 0; i < 3; i++ ) {
- outer_qedges[i] =
- (CvQuadEdge2D*)cvGetSeqElem( (CvSeq*)(my_subdiv->edges), I );
- }
確定凸包的外接三角形(Bounding triangle)或邊緣並遍歷凸包
我們回想一下,我們通過調用cvInitSubdivDelaunay2D(subdiv,rect)來初始化Delaunay三角剖分。在這種情況下,下面的論述成立:
1、如果邊緣的起點和終點都在矩形之外,那么此邊緣也在細分的外接三角形上。(即如果一個邊緣的兩個點出了矩形邊界,那么該邊緣為虛擬三角形的邊緣)
2、如果邊緣的一端在矩形內,一段在矩形外,那么矩形邊界內的點落在凸集上,凸集上的每個點與虛擬外接三角形的兩頂點相連,這兩邊相繼出現。
(英語原文:If you are on an edge with one point inside and one point outside the rect bounds,then the point in bounds is on the convex hull of the set。。。)
(注釋:Learning OpenCV中第343頁,將In bounds翻譯為在邊界上,這里個人感覺應該是在翻譯為在邊界內,為了方便理解,個人猜測如下圖,可能不對:)

從上述第二個條件可知,我們可以使用cvSubdiv2DNextEdge(),移動到第一條邊上,這條邊的dst端點在邊界內。
兩個端點都在邊界內的這條邊在點集的凸包上,因此記下那個點和邊。一旦它在凸包上,那么你可以如下遍歷所有頂點。(切記參考英文原版,我感覺下面的步驟應該參考具體操作理解。)
1·、將凸包遍歷一周后,通過cvSubdiv2DRotateEdge(CvSubdiv2DEdge edge,0)函數移動到凸包的下一條邊。
2、接着,兩次調用宏cvSubdiv2DNextEdge()就到了凸包的下一條邊。跳轉到第一步。
使用實例:
1、使用函數cvSubdiv2DLoacate()遍歷Delaunay三角剖分的邊。
2、使用函數cvFindNearestPoint2D()函數找到距離輸入最近的點。
3、使用函數draw_subdiv_facet()函數逐步遍歷Voronoi面,這個函數是組合函數,實現參考OpenCV的344頁。
4、使用CvSeqReader逐步遍歷Delaunay或者Voronoi邊。
5、找到了三角剖分的所有頂點,我們就可以利用內斂宏cvTriangleArea()函數來計算剖分的面積。
三角剖分例程序:
在OpenCV的sample的c目錄下:delaunay.c文件
程序一:
- #include <opencv2/imgproc/imgproc_c.h>
- #include <opencv2/legacy/legacy.hpp>
- #include "opencv2/highgui/highgui.hpp"
- #include<opencv2\opencv.hpp>
- #include<iostream>
- #include <stdio.h>
- using namespace std;
- using namespace cv;
- static void help( void )
- {
- printf("\nThis program demostrates iterative construction of\n"//這個程序闡述了delaunay剖分和voronoi細分的迭代構造
- "delaunay triangulation and voronoi tesselation.\n"
- "It draws a random set of points in an image and then delaunay triangulates them.\n"//在圖像上畫出一些隨機點,然后進行delaunay三角剖分
- "Usage: \n"
- "./delaunay \n"
- "\nThis program builds the traingulation interactively, you may stop this process by\n"
- "hitting any key.\n");//迭代構造三角剖分,如果像停止,則按任意鍵
- }
- static 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;
- }
- static 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 );
- }
- static 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);//通過邊獲取頂點
- dst_pt = cvSubdiv2DEdgeDst(edge);
- 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 );
- }
- }
- static void draw_subdiv( IplImage* img, CvSubdiv2D* subdiv,
- CvScalar delaunay_color, CvScalar voronoi_color )//畫出剖分和細分
- {
- CvSeqReader reader;
- int i, total = subdiv->edges->total;//邊的數量
- int elem_size = subdiv->edges->elem_size;//邊的大小
- cout<<typeid(subdiv->edges).name()<<endl;
- cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );//使用CvSeqReader遍歷Delaunay或者Voronoi邊
- for( i = 0; i < total; i++ )
- {
- CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);
- if( CV_IS_SET_ELEM( edge ))
- {
- // draw_subdiv_edge( img, (CvSubdiv2DEdge)edge + 1, voronoi_color );
- draw_subdiv_edge( img, (CvSubdiv2DEdge)edge, delaunay_color );
- }
- CV_NEXT_SEQ_ELEM( elem_size, reader );
- }
- }
- static 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 );
- if( e0 )
- {
- e = e0;
- do
- {
- draw_subdiv_edge( img, e, active_color );
- e = cvSubdiv2DGetEdge(e,CV_NEXT_AROUND_LEFT);
- }
- while( e != e0 );
- }
- draw_subdiv_point( img, fp, active_color );
- }
- //@author andme-單目視覺
- void dashLine(Mat &img, Point2d& pt1, Point2d& pt2, int n)//n為虛線段數
- {
- Point sub = pt2 - pt1;
- for (int i = 0; i < 2*n; i += 2)
- {
- line(img, Point(pt1.x + sub.x * i / (2 * n - 1), pt1.y + sub.y * i / (2 * n - 1)), Point(pt1.x + sub.x * (i+1) / (2 * n - 1), pt1.y + sub.y * (i+1) / (2 * n - 1)), Scalar(0,255,0), 2);
- }
- }
- //調用形式draw_subdiv_facet( img, cvSubdiv2DRotateEdge( e, 1 ));
- static void draw_subdiv_facet( IplImage* img, CvSubdiv2DEdge edge )//畫出voronoi面
- {
- //cout<<edge<<endl;//edge低兩位表示表示索引,高位表示四方邊緣指針。
- //cout<<(edge&3)<<endl;
- CvSubdiv2DEdge t = edge;//當我們按上面的調用形式時,edge為eRot。
- int i, count = 0;
- CvPoint* buf = 0;
- Point2d *buf1=0;
- // count number of edges in facet //面內邊的計數
- do
- {
- count++;
- t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
- } while (t != edge );//我們繞着一個voronoi單元一周,遍歷該vornonoi邊緣所擁有的邊緣數。
- buf = (CvPoint*)malloc( count * sizeof(buf[0]));
- buf1=(Point2d*)malloc(count*sizeof(buf1[0]));
- // gather points
- t = edge;
- for( i = 0; i < count; i++ )
- {
- CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );//第一次獲取eRot邊緣的起始點
- if( !pt ) break;//如果得不到該源點,則退出循環
- buf[i] = cvPoint( cvRound(pt->pt.x), cvRound(pt->pt.y));//將該點轉換為cvPoint類型點,存儲在buf中
- t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );//然后繞着vornonoi單元,左旋轉。
- }
- if( i == count )//如果所有的點都存儲起來了。
- {
- CvSubdiv2DPoint* pt = cvSubdiv2DEdgeDst( cvSubdiv2DRotateEdge( edge, 1 ));//這里eRot的旋轉邊緣應該是reversed e,那么目的點,就是e的源點。
- // cvFillConvexPoly( img, buf, count, CV_RGB(rand()&255,rand()&255,rand()&255), CV_AA, 0 );//填充凸多邊形
- for(i=0;i<count;i++)
- {
- buf1[i].x=buf[i].x;
- buf1[i].y=buf[i].y;
- }
- Mat mat_img(img);
- cvPolyLine( img, &buf, &count, 1, 1, CV_RGB(0,200,0), 1, CV_AA, 0);//畫出線。
- //for(int i=0;i<count-1;i++)
- //{
- //dashLine(mat_img,buf1[i],buf1[i+1],100);
- //}
- //dashLine(mat_img,buf1[i],buf1[0],100);
- draw_subdiv_point( img, pt->pt, CV_RGB(255,0,0));//用黑色畫出畫出剖分頂點。
- }
- free( buf );
- }
- static void paint_voronoi( CvSubdiv2D* subdiv, IplImage* img )//畫出voronoi面
- {
- CvSeqReader reader;
- int i, total = subdiv->edges->total;//邊緣總數
- int elem_size = subdiv->edges->elem_size;//邊緣的大小
- cvCalcSubdivVoronoi2D( subdiv );
- 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;//edge是四方邊緣的指針,而CvSubdiv2DEdge高位表示四方邊緣的指針。
- //cout<<(e&3)<<endl;//通過測試e低2位即索引值應該設置為0了,即輸入邊緣
- // left
- draw_subdiv_facet( img, cvSubdiv2DRotateEdge( e, 1 ));//e為Delaunay邊,獲得Delaunay邊對應的voronoi邊,即e的旋轉邊緣
- // right
- draw_subdiv_facet( img, cvSubdiv2DRotateEdge( e, 3 ));//反向的旋轉邊緣
- }
- CV_NEXT_SEQ_ELEM( elem_size, reader );//移動到下一個位置
- }
- }
- static void run(void)
- {
- char win[] = "source";
- int i;
- CvRect rect = { 0, 0, 600, 600 };
- CvMemStorage* storage;
- 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 );
- storage = cvCreateMemStorage(0);
- subdiv = init_delaunay( storage, rect );
- printf("Delaunay triangulation will be build now interactively.\n"
- "To stop the process, press any key\n\n");
- vector<CvPoint2D32f> points;
- for( i = 0; i < 5; i++ )
- {
- CvPoint2D32f fp = cvPoint2D32f( (float)(rand()%(rect.width-10)),//使點約束在距離邊框10像素之內。
- (float)(rand()%(rect.height-10)));
- points.push_back(fp);
- locate_point( subdiv, fp, img, active_facet_color );//定位點的位置,並畫出點所在voronoi面的邊。
- cvShowImage( win, img );//刷新顯示
- if( cvWaitKey( 100 ) >= 0 )
- break;
- cvSubdivDelaunay2DInsert( subdiv, fp );//向三角剖分中插入該點,即對該點進行三角剖分
- cvCalcSubdivVoronoi2D( subdiv );//計算Voronoi細分,有時候我們不需要
- cvSet( img, bkgnd_color, 0 );//設置圖像的背景顏色為白色
- draw_subdiv( img, subdiv, delaunay_color, voronoi_color);
- cvShowImage( win, img );
- //cvWaitKey();
- if( cvWaitKey( 100 ) >= 0 )
- break;
- }
- for(int i=0;i<points.size();i++)
- draw_subdiv_point( img, points[i], active_facet_color );
- cvShowImage(win,img);
- cvWaitKey();
- // 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 )
- {
- (void)argc; (void)argv;
- help();
- run();
- return 0;
- }
- #ifdef _EiC
- main( 1, "delaunay.c" );
- #endif

程序二:
和程序一類似,只是加了根據一個點,然后獲取該點在所在的剖分平面(即包含該點的三角形),然后遍歷該三角形:
- #include <opencv2/imgproc/imgproc_c.h>
- #include <opencv2/legacy/legacy.hpp>
- #include "opencv2/highgui/highgui.hpp"
- #include<opencv2\opencv.hpp>
- #include<iostream>
- #include <stdio.h>
- using namespace std;
- using namespace cv;
- static void help( void )
- {
- printf("\nThis program demostrates iterative construction of\n"//這個程序闡述了delaunay剖分和voronoi細分的迭代構造
- "delaunay triangulation and voronoi tesselation.\n"
- "It draws a random set of points in an image and then delaunay triangulates them.\n"//在圖像上畫出一些隨機點,然后進行delaunay三角剖分
- "Usage: \n"
- "./delaunay \n"
- "\nThis program builds the traingulation interactively, you may stop this process by\n"
- "hitting any key.\n");//迭代構造三角剖分,如果像停止,則按任意鍵
- }
- static 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;
- }
- static 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 );
- }
- static 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);//通過邊獲取頂點
- dst_pt = cvSubdiv2DEdgeDst(edge);
- 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 );
- }
- }
- static void draw_subdiv( IplImage* img, CvSubdiv2D* subdiv,
- CvScalar delaunay_color, CvScalar voronoi_color )//畫出剖分和細分
- {
- CvSeqReader reader;
- int i, total = subdiv->edges->total;//邊的數量
- int elem_size = subdiv->edges->elem_size;//邊的大小
- cout<<typeid(subdiv->edges).name()<<endl;
- cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );//使用CvSeqReader遍歷Delaunay或者Voronoi邊
- for( i = 0; i < total; i++ )
- {
- CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);
- if( CV_IS_SET_ELEM( edge ))
- {
- // draw_subdiv_edge( img, (CvSubdiv2DEdge)edge + 1, voronoi_color );
- draw_subdiv_edge( img, (CvSubdiv2DEdge)edge, delaunay_color );
- }
- CV_NEXT_SEQ_ELEM( elem_size, reader );
- }
- }
- static 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 );
- if( e0 )
- {
- e = e0;
- do
- {
- draw_subdiv_edge( img, e, active_color );
- e = cvSubdiv2DGetEdge(e,CV_NEXT_AROUND_LEFT);
- }
- while( e != e0 );
- }
- draw_subdiv_point( img, fp, active_color );
- }
- //@author andme-單目視覺
- void dashLine(Mat &img, Point2d& pt1, Point2d& pt2, int n)//n為虛線段數
- {
- Point sub = pt2 - pt1;
- for (int i = 0; i < 2*n; i += 2)
- {
- line(img, Point(pt1.x + sub.x * i / (2 * n - 1), pt1.y + sub.y * i / (2 * n - 1)), Point(pt1.x + sub.x * (i+1) / (2 * n - 1), pt1.y + sub.y * (i+1) / (2 * n - 1)), Scalar(0,255,0), 2);
- }
- }
- //調用形式draw_subdiv_facet( img, cvSubdiv2DRotateEdge( e, 1 ));
- static void draw_subdiv_facet( IplImage* img, CvSubdiv2DEdge edge )//畫出voronoi面
- {
- //cout<<edge<<endl;//edge低兩位表示表示索引,高位表示四方邊緣指針。
- //cout<<(edge&3)<<endl;
- CvSubdiv2DEdge t = edge;//當我們按上面的調用形式時,edge為eRot。
- int i, count = 0;
- CvPoint* buf = 0;
- Point2d *buf1=0;
- // count number of edges in facet //面內邊的計數
- do
- {
- count++;
- t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
- } while (t != edge );//我們繞着一個voronoi單元一周,遍歷該vornonoi邊緣所擁有的邊緣數。
- buf = (CvPoint*)malloc( count * sizeof(buf[0]));
- buf1=(Point2d*)malloc(count*sizeof(buf1[0]));
- // gather points
- t = edge;
- for( i = 0; i < count; i++ )
- {
- CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );//第一次獲取eRot邊緣的起始點
- if( !pt ) break;//如果得不到該源點,則退出循環
- buf[i] = cvPoint( cvRound(pt->pt.x), cvRound(pt->pt.y));//將該點轉換為cvPoint類型點,存儲在buf中
- t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );//然后繞着vornonoi單元,左旋轉。
- }
- if( i == count )//如果所有的點都存儲起來了。
- {
- CvSubdiv2DPoint* pt = cvSubdiv2DEdgeDst( cvSubdiv2DRotateEdge( edge, 1 ));//這里eRot的旋轉邊緣應該是reversed e,那么目的點,就是e的源點。
- // cvFillConvexPoly( img, buf, count, CV_RGB(rand()&255,rand()&255,rand()&255), CV_AA, 0 );//填充凸多邊形
- for(i=0;i<count;i++)
- {
- buf1[i].x=buf[i].x;
- buf1[i].y=buf[i].y;
- }
- Mat mat_img(img);
- cvPolyLine( img, &buf, &count, 1, 1, CV_RGB(0,200,0), 1, CV_AA, 0);//畫出線。
- //for(int i=0;i<count-1;i++)
- //{
- //dashLine(mat_img,buf1[i],buf1[i+1],100);
- //}
- //dashLine(mat_img,buf1[i],buf1[0],100);
- draw_subdiv_point( img, pt->pt, CV_RGB(255,0,0));//用黑色畫出畫出剖分頂點。
- }
- free( buf );
- }
- /**********************************************重點部分:如何實現變量所有的Delauany或者Voronoi邊*****************************/
- static void paint_voronoi( CvSubdiv2D* subdiv, IplImage* img )//畫出voronoi面
- {
- CvSeqReader reader;
- int i, total = subdiv->edges->total;//邊緣總數
- int elem_size = subdiv->edges->elem_size;//邊緣的大小
- cvCalcSubdivVoronoi2D( subdiv );
- 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;//edge是四方邊緣的指針,而CvSubdiv2DEdge高位表示四方邊緣的指針。
- //cout<<(e&3)<<endl;//通過測試e低2位即索引值應該設置為0了,即輸入邊緣
- // left
- draw_subdiv_facet( img, cvSubdiv2DRotateEdge( e, 1 ));//e為Delaunay邊,獲得Delaunay邊對應的voronoi邊,即e的旋轉邊緣
- // right
- draw_subdiv_facet( img, cvSubdiv2DRotateEdge( e, 3 ));//反向的旋轉邊緣
- }
- CV_NEXT_SEQ_ELEM( elem_size, reader );//移動到下一個位置
- }
- }
- /*************************************************************************************/
- void draw_edge(CvSubdiv2DEdge e0,IplImage *img,const char *cp )
- {
- CvSubdiv2DPoint *point1_org=cvSubdiv2DEdgeOrg(e0);
- CvSubdiv2DPoint *point1_dst=cvSubdiv2DEdgeDst(e0);
- CvPoint pt_org=cvPointFrom32f(point1_org->pt);
- CvPoint pt_dst=cvPointFrom32f(point1_dst->pt);
- CvPoint pt;
- pt.x=(pt_org.x+pt_dst.x)/2;
- pt.y=(pt_org.y+pt_dst.y)/2;
- CvFont font;
- cvInitFont(&font,CV_FONT_ITALIC,1,1,0,2,8);
- cvLine(img,pt_org,pt_dst,cvScalar(255,0,0),2,8);
- cvCircle(img,pt,6,cvScalar(0,0,0),2,CV_AA);
- cout<<"點位於"<<pt.x<<" "<<pt.y<<endl;
- cvPutText(img,cp,pt,&font,cvScalar(0,255,0));
- if(pt_dst.y-pt_org.y>0)
- {
- cout<<"箭頭朝下"<<endl;
- }else
- {
- cout<<"箭頭朝上"<<endl;
- }
- }
- static void run(void)
- {
- char win[] = "source";
- int i;
- CvRect rect = { 0, 0, 600, 600 };
- CvMemStorage* storage;
- 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 );
- storage = cvCreateMemStorage(0);
- subdiv = init_delaunay( storage, rect );
- printf("Delaunay triangulation will be build now interactively.\n"
- "To stop the process, press any key\n\n");
- vector<CvPoint2D32f> points;
- for( i = 0; i < 5; i++ )
- {
- CvPoint2D32f fp = cvPoint2D32f( (float)(rand()%(rect.width-10)),//使點約束在距離邊框10像素之內。
- (float)(rand()%(rect.height-10)));
- points.push_back(fp);
- locate_point( subdiv, fp, img, active_facet_color );//定位點的位置,並畫出點所在voronoi面的邊。
- cvShowImage( win, img );//刷新顯示
- if( cvWaitKey( 100 ) >= 0 )
- break;
- cvSubdivDelaunay2DInsert( subdiv, fp );//向三角剖分中插入該點,即對該點進行三角剖分
- cvCalcSubdivVoronoi2D( subdiv );//計算Voronoi細分,有時候我們不需要
- cvSet( img, bkgnd_color, 0 );//設置圖像的背景顏色為白色
- draw_subdiv( img, subdiv, delaunay_color, voronoi_color);
- cvShowImage( win, img );
- //cvWaitKey();
- if( cvWaitKey( 100 ) >= 0 )
- break;
- }
- for(int i=0;i<points.size();i++)
- draw_subdiv_point( img, points[i], active_facet_color );
- cvShowImage(win,img);
- cvWaitKey();
- // cvSet( img, bkgnd_color, 0 );//重新刷新畫布,即設置背景顏色為白色
- paint_voronoi( subdiv, img );//畫出細分
- CvPoint2D32f point1=cvPoint2D32f(300,300);//圖像中心選擇一點。
- CvSubdiv2DEdge e;
- CvSubdiv2DEdge e0 = 0;
- CvSubdiv2DPoint* p = 0;
- CvSubdiv2DPointLocation loc=cvSubdiv2DLocate( subdiv, point1, &e0, &p );
- if(loc==CV_PTLOC_INSIDE)
- {
- cout<<"落入某些面,箭頭的方向用來說明圖的方向"<<endl;
- draw_edge(e0,img,"fisrt");
- CvSubdiv2DEdge e_lnext=cvSubdiv2DGetEdge(e0,CV_NEXT_AROUND_LEFT);
- draw_edge(e_lnext,img,"second");
- CvSubdiv2DEdge e_Onext=cvSubdiv2DGetEdge(e_lnext,CV_NEXT_AROUND_LEFT);
- draw_edge(e_Onext,img,"third");
- }else if(loc==CV_PTLOC_ON_EDGE)
- {
- cout<<"點落在邊緣上"<<endl;
- }else if(loc==CV_PTLOC_VERTEX)
- {
- cout<<"和頂點重合"<<endl;
- }else
- {
- cout<<"輸入變量無效"<<endl;
- }
- cvShowImage( win, img );//
- cvWaitKey(0);
- cvReleaseMemStorage( &storage );
- cvReleaseImage(&img);
- cvDestroyWindow( win );
- }
- int main( int argc, char** argv )
- {
- (void)argc; (void)argv;
- help();
- run();
- return 0;
- }
- #ifdef _EiC
- main( 1, "delaunay.c" );
- #endif
執行效果:

上圖中我們選擇了圖像的中心點,圖像大小為600×600,故中心點為300×300,我們通過函數cvSubdiv2DLocate()得知該點落在了面內,然后我們該函數得到該面的一個邊緣,我們有了邊緣信息,如果我們幫它當做程序1上的參考圖中的e,那么我就可以獲得參考圖中的所有邊的信息,我們也可以根據這些邊的信息獲取的這些邊的端點。
並注意一下控制台的輸出信息:即first邊的方向向下,second為方向向上,third是方向向下。
程序三:
- #include <opencv2/legacy/legacy.hpp>
- #include <opencv2/opencv.hpp>
- #include <opencv2/nonfree/nonfree.hpp>
- #include <opencv2/nonfree/features2d.hpp>
- #include <atlstr.h> // use STL string instead, although not as convenient...
- #include <atltrace.h>
- #include <iostream>
- #include <fstream>
- #include <string>
- #include<time.h>
- using namespace std;
- using namespace cv;
- /*
- pts,要剖分的散點集,in
- img,剖分的畫布,in
- tri,存儲三個表示頂點變換的正數,out
- */
- // used for doing delaunay trianglation with opencv function
- //該函數用來防止多次重畫並消去虛擬三角形的 頂點
- bool isGoodTri( Vec3i &v, vector<Vec3i> & tri )
- {
- int a = v[0], b = v[1], c = v[2];
- v[0] = min(a,min(b,c));//v[0]找到點插入的先后順序(0....N-1,N為點的個數)的最小值
- v[2] = max(a,max(b,c));//v[2]存儲最大值.
- v[1] = a+b+c-v[0]-v[2];//v[1]為中間值
- if (v[0] == -1) return false;
- vector<Vec3i>::iterator iter = tri.begin();//開始時為空
- for(;iter!=tri.end();iter++)
- {
- Vec3i &check = *iter;//如果當前待壓入的和存儲的重復了,則停止返回false。
- if (check[0]==v[0] &&
- check[1]==v[1] &&
- check[2]==v[2])
- {
- break;
- }
- }
- if (iter == tri.end())
- {
- tri.push_back(v);
- return true;
- }
- return false;
- }
- /*
- pts,要剖分的散點集,in
- img,剖分的畫布,in
- tri,存儲三個表示頂點變換的正數,out
- */
- void TriSubDiv( vector<Point2f> &pts, Mat &img, vector<Vec3i> &tri )
- {
- CvSubdiv2D* subdiv;
- CvMemStorage* storage = cvCreateMemStorage(0); //創建存儲器
- Rect rc = Rect(0,0, img.cols, img.rows);//矩形是圖像的大小
- subdiv = cvCreateSubdiv2D( CV_SEQ_KIND_SUBDIV2D, sizeof(*subdiv),
- sizeof(CvSubdiv2DPoint),
- sizeof(CvQuadEdge2D),
- storage );//為剖分數據分配空間
- cvInitSubdivDelaunay2D( subdiv, rc );
- for (size_t i = 0; i < pts.size(); i++)
- {
- CvSubdiv2DPoint *pt = cvSubdivDelaunay2DInsert( subdiv, pts[i] );//利用插入法進行剖分
- pt->id = i;//為每一個頂點分配一個id
- }
- CvSeqReader reader;//利用CvSeqReader遍歷
- int total = subdiv->edges->total;//邊的總數
- int elem_size = subdiv->edges->elem_size;//邊的大小
- cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );
- Point buf[3];
- const Point *pBuf = buf;
- Vec3i verticesIdx;
- Mat imgShow = img.clone();
- srand( (unsigned)time( NULL ) );
- for( int i = 0; i < total; i++ )
- {
- CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);
- if( CV_IS_SET_ELEM( edge ))
- {
- CvSubdiv2DEdge t = (CvSubdiv2DEdge)edge;
- int iPointNum = 3;
- Scalar color = CV_RGB(rand()&255,rand()&255,rand()&255);
- //Scalar color=CV_RGB(255,0,0);
- //bool isNeg = false;
- int j;
- for(j = 0; j < iPointNum; j++ )
- {
- CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );//獲取t邊的源點
- if( !pt ) break;
- buf[j] = pt->pt;//將點存儲起來
- //if (pt->id == -1) isNeg = true;
- verticesIdx[j] = pt->id;//獲取頂點的Id號,將三個點的id存儲到verticesIdx中
- t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );//獲取下一條邊
- }
- if (j != iPointNum) continue;
- if (isGoodTri(verticesIdx, tri))
- {
- //tri.push_back(verticesIdx);
- polylines( imgShow, &pBuf, &iPointNum,
- 1, true, color,
- 1, CV_AA, 0);//畫出三條邊
- //printf("(%d, %d)-(%d, %d)-(%d, %d)\n", buf[0].x, buf[0].y, buf[1].x, buf[1].y, buf[2].x, buf[2].y);
- //printf("%d\t%d\t%d\n", verticesIdx[0], verticesIdx[1], verticesIdx[2]);
- //imshow("Delaunay", imgShow);
- //waitKey();
- }
- t = (CvSubdiv2DEdge)edge+2;//相反邊緣 reversed e
- for(j = 0; j < iPointNum; j++ )
- {
- CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );
- if( !pt ) break;
- buf[j] = pt->pt;
- verticesIdx[j] = pt->id;
- t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
- }
- if (j != iPointNum) continue;
- if (isGoodTri(verticesIdx, tri))
- {
- //tri.push_back(verticesIdx);
- polylines( imgShow, &pBuf, &iPointNum,
- 1, true, color,
- 1, CV_AA, 0);
- //printf("(%d, %d)-(%d, %d)-(%d, %d)\n", buf[0].x, buf[0].y, buf[1].x, buf[1].y, buf[2].x, buf[2].y);
- //printf("%d\t%d\t%d\n", verticesIdx[0], verticesIdx[1], verticesIdx[2]);
- //imshow("Delaunay", imgShow);
- //waitKey();
- }
- }
- CV_NEXT_SEQ_ELEM( elem_size, reader );
- }
- //RemoveDuplicate(tri);
- char title[100];
- sprintf_s(title, 100, "Delaunay: %d Triangles", tri.size());//tri存儲的為3個頂點為一個vec3i,故tri.size()表示三角形的個數。
- imshow(title, imgShow);
- waitKey();
- }
- void main(int argc, char* argv[])
- {
- Mat imgL(600,600,CV_8UC3);
- /************************************************************************/
- /* Delaunay triangulation */
- /************************************************************************/
- cout<<"doing triangulation..."<<endl;
- vector<Vec3i> tri;
- vector<Point2f> vec_points;
- for(int i = 0; i < 60; i++ )
- {
- Point2f fp = cvPoint2D32f( (float)(rand()%(imgL.cols-10)),//使點約束在距離邊框10像素之內。
- (float)(rand()%(imgL.rows-10)));
- vec_points.push_back(fp);
- }
- TriSubDiv(vec_points, imgL, tri);
- }
執行結果:

上程序中,通過isGoodTri()函數可以有效地防止重畫並且消除虛擬三角形的頂點。
