Delaunay 三角剖分 是平面點集劃分的核心算法,具有兩大關鍵性質:
Voronoi 圖 是其對偶圖,每個種子點對應一個多邊形區域,區域內任意點到該種子點距離最近。OpenCV 1.x (C API) 通過 CvSubdiv2D 實現兩者統一計算。
OpenCV 採用 Guibas-Stolfi 的 Quad-Edge 數據結構,每條邊包含 4 個方向指針:
typedef struct CvQuadEdge2D {
int flags;
struct CvSubdiv2DPoint* pt[4]; // 4 個端點
CvSubdiv2DEdge next[4]; // 4 條關聯邊
} CvQuadEdge2D;
四條邊語義 (以邊 e 為基準):
| 代碼 | 宏 | 幾何含義 |
|---|---|---|
| next[0] | CV_NEXT_AROUND_LEFT | e 左側面的下一條邊 (逆時針) |
| next[1] | CV_NEXT_AROUND_RIGHT | e 右側面的下一條邊 (順時針) |
| next[2] | CV_NEXT_AROUND_DEST | 與 e 共終點的下一條邊 |
| next[3] | CV_NEXT_AROUND_ORG | 與 e 共起點的下一條邊 |
對偶邊:eRot = cvSubdiv2DRotateEdge(e, 1) (旋轉 90°)
bool FindTriangleFromEdge(CvSubdiv2DEdge e) {
CvSubdiv2DEdge t = e;
CvPoint buf[3];
for (int j = 0; j < 3; j++) {
CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg(t); // 獲取起點
if (!pt) return false;
buf[j] = cvPoint(cvRound(pt->pt.x), cvRound(pt->pt.y));
t = cvSubdiv2DGetEdge(t, CV_NEXT_AROUND_LEFT); // 逆時針繞左面
}
AddTriangle(buf); // 存儲三角形頂點
return true;
}
// 遍歷所有邊提取三角形
for (int i = 0; i < total; i++) {
CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);
if (CV_IS_SET_ELEM(edge)) {
CvSubdiv2DEdge e = (CvSubdiv2DEdge)edge;
FindTriangleFromEdge(e);
// 必須同時處理對偶邊 (eRot),否則遺漏三角形
CvSubdiv2DEdge e1 = cvSubdiv2DRotateEdge(e, 2); // next[2] 即 eRot
FindTriangleFromEdge(e1);
}
CV_NEXT_SEQ_ELEM(elem_size, reader);
}
關鍵點:Delaunay 邊有方向性,單向遍歷會漏掉三角形,必須同時處理原邊與對偶邊 (旋轉 2 位)。
參考 OpenCV 範例 paint_voronoi 函數,核心邏輯:
CV_NEXT_AROUND_LEFT 繞行收集頂點形成多邊形cvPolyLine 繪製填充![]() |
![]() |
| 16 點 Delaunay 三角剖分 | 對應 Voronoi 劃分 |
cv::Subdiv2D),本文代碼需適配升級// 現代風格
cv::Subdiv2D subdiv(cv::Rect(0, 0, width, height));
for (const auto& pt : points) subdiv.insert(pt);
// 獲取三角形列表
std::vector triangleList;
subdiv.getTriangleList(triangleList);
// 獲取 Voronoi 圖
std::vector> facets;
std::vector centers;
subdiv.getVoronoiFacetList(std::vector(), facets, centers);
深入理解 Quad-Edge 數據結構是掌握 OpenCV 平面劃分的鑰匙。通過邊的拓撲遍歷 (CV_NEXT_AROUND_LEFT 等宏) 可提取豐富的幾何拓撲信息。雖然 OpenCV 1.x API 已過時,但其數據結構設計思想在計算幾何領域極具參考價值。現代項目建議遷移至 cv::Subdiv2D 或更專業的 CGAL 庫。