一、定义:
三角剖分:
假设V是二维实数域上的有限点集,边e是由点集中的点作为端点构成的封闭线段, E为e的集合。那么该点集V的一个三角剖分T=(V,E)是一个平面图G,该平面图满足条件(简言之就是满足所有三角形的最小角的和是最大的):
- 1.除了端点,平面图中的边不包含点集中的任何点。
- 2.没有相交边。
- 3.平面图中所有的面都是三角面,且所有三角面的合集是散点集V的凸包
- tips:在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。(用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。)
Voronoi图是Delaunay三角剖分的对偶图,生成它的方法有很多 ,比较有名的有分治算法,扫描线算法,增量法等。但利用Delaunay三角剖分生成Voronoi图的算法是最快的。
- Voronoi图的应用非常广泛。在计算几何中,重心Voronoi图(CVT)方法被用来优化网格,可以使种子点变得更加均匀。在网络通讯中,利用加权Voronoi图设计中继站的位置可以提高利用率,降低成本。
二、特点:
Delaunay三角剖分有最大化最小角,“最接近于规则化的“的三角网和唯一性(任意四点不能共圆)两个特点:
-
1、空圆特性:Delaunay三角网是唯一的(任意四点不能共圆),在Delaunay三角形网中任一三角形的外接圆范围内不会有其它点存在。如下图所示:
-
2、最大化最小角特性:在散点集可能形成的三角剖分中,Delaunay三角剖分所形成的三角形的最小角最大。从这个意义上讲,Delaunay三角网是“最接近于规则化的“的三角网。具体的说是指在两个相邻的三角形构成凸四边形的对角线,在相互交换后,六个内角的最小角不再增大。如下图所示:
Voronoi图特点
- 1、每个多边形内仅含有一个中心点;
- 2、每个多边形区域内的点到相应中心点的距离最近;
- 3、位于多边形边上的点到其两边的中心的距离相等。
三、计算方法:
1.Lawson算法:
基本原理:
- 1.首先建立一个大的三角形或多边形,把所有数据点包围起来
- 2.向其中插入一点,该点与包含它的三角形三个顶点相连,形成三个新的三角形
- 3.然后逐个对它们进行空外接圆检测,同时用Lawson设计的局部优化过程LOP进行优化(即通过交换对角线的方法来保证所形成的三角网为Delaunay三角网)
优点:
- 上述基于散点的构网算法理论严密、唯一性好,网格满足空圆特性,较为理想。
- 由其逐点插入的构网过程可知,遇到非Delaunay边时,通过删除调整,可以构造形成新的Delaunay边。
- 在完成构网后,增加新点时,无需对所有的点进行重新构网,只需对新点的影响三角形范围进行局部联网,且局部联网的方法简单易行。
- 同样,点的删除、移动也可快速动态地进行。
缺点: - 但在实际应用当中,这种构网算法当点集较大时构网速度也较慢,如果点集范围是非凸区域或者存在内环,则会产生非法三角形。
2.Bowyer-Watson算法:
Watson算法的基本步骤是:
-
1、构造一个超级三角形,包含所有散点,放入三角形链表。
-
2、将点集中的散点依次插入,在三角形链表中找出外接圆包含插入点的三角形(称为该点的影响三角形),删除影响三角形的公共边,将插入点同影响三角形的全部顶点连接起来,完成一个点在Delaunay三角形链表中的插入。
-
3、根据优化准则对局部新形成的三角形优化。将形成的三角形放入Delaunay三角形链表。
-
4、循环执行上述第2步,直到所有散点插入完毕。
这一算法的关键的第2步图示如下:
四、实现:
采用opencv2,其他版本大致相同,代码解释可参考:
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
static void draw_subdiv_point( Mat& img, Point2f fp, Scalar color )
{
circle( img, fp, 3, color, CV_FILLED, 8, 0 );
}
static void draw_subdiv( Mat& img, Subdiv2D& subdiv, Scalar delaunay_color )
{
#if 1
vector<Vec6f> triangleList;
subdiv.getTriangleList(triangleList);
vector<Point> pt(3);
for( size_t i = 0; i < triangleList.size(); i++ )
{
Vec6f t = triangleList[i];
pt[0] = Point(cvRound(t[0]), cvRound(t[1]));
pt[1] = Point(cvRound(t[2]), cvRound(t[3]));
pt[2] = Point(cvRound(t[4]), cvRound(t[5]));
line(img, pt[0], pt[1], delaunay_color, 1, CV_AA, 0);
line(img, pt[1], pt[2], delaunay_color, 1, CV_AA, 0);
line(img, pt[2], pt[0], delaunay_color, 1, CV_AA, 0);
}
#else
vector<Vec4f> edgeList;
subdiv.getEdgeList(edgeList);
for( size_t i = 0; i < edgeList.size(); i++ )
{
Vec4f e = edgeList[i];
Point pt0 = Point(cvRound(e[0]), cvRound(e[1]));
Point pt1 = Point(cvRound(e[2]), cvRound(e[3]));
line(img, pt0, pt1, delaunay_color, 1, CV_AA, 0);
}
#endif
}
static void locate_point( Mat& img, Subdiv2D& subdiv, Point2f fp, Scalar active_color )
{
int e0=0, vertex=0;
subdiv.locate(fp, e0, vertex);
if( e0 > 0 )
{
int e = e0;
do
{
Point2f org, dst;
if( subdiv.edgeOrg(e, &org) > 0 && subdiv.edgeDst(e, &dst) > 0 )
line( img, org, dst, active_color, 3, CV_AA, 0 );
e = subdiv.getEdge(e, Subdiv2D::NEXT_AROUND_LEFT);
}
while( e != e0 );
}
draw_subdiv_point( img, fp, active_color );
}
static void paint_voronoi( Mat& img, Subdiv2D& subdiv )
{
vector<vector<Point2f> > facets;
vector<Point2f> centers;
subdiv.getVoronoiFacetList(vector<int>(), facets, centers);
vector<Point> ifacet;
vector<vector<Point> > ifacets(1);
for( size_t i = 0; i < facets.size(); i++ )
{
ifacet.resize(facets[i].size());
for( size_t j = 0; j < facets[i].size(); j++ )
ifacet[j] = facets[i][j];
Scalar color;
color[0] = rand() & 255;
color[1] = rand() & 255;
color[2] = rand() & 255;
fillConvexPoly(img, ifacet, color, 8, 0);
ifacets[0] = ifacet;
polylines(img, ifacets, true, Scalar(), 1, CV_AA, 0);
circle(img, centers[i], 3, Scalar(), -1, CV_AA, 0);
}
}
int main( int, char** )
{
Scalar active_facet_color(0, 0, 255),
delaunay_color(255,255,255);
Rect rect(0, 0, 600, 600);
Subdiv2D subdiv(rect);
Mat img(rect.size(), CV_8UC3);
img = Scalar::all(0);
string win = "Delaunay Demo";
imshow(win, img);
for( int i = 0; i < 200; i++ )
{
Point2f fp( (float)(rand()%(rect.width-10)+5),
(float)(rand()%(rect.height-10)+5));
locate_point( img, subdiv, fp, active_facet_color );
imshow( win, img );
if( waitKey( 100 ) >= 0 )
break;
subdiv.insert(fp);
img = Scalar::all(0);
draw_subdiv( img, subdiv, delaunay_color );
imshow( win, img );
if( waitKey( 100 ) >= 0 )
break;
}
img = Scalar::all(0);
paint_voronoi( img, subdiv );
imshow( win, img );
waitKey(0);
return 0;
}
二维delaunay运行效果:
Voronoi图:
Reference:
https://baike.baidu.com/item/Delaunay%E4%B8%89%E8%A7%92%E5%89%96%E5%88%86%E7%AE%97%E6%B3%95/3779918?fr=aladdin
https://baike.baidu.com/item/%E5%87%B8%E5%8C%85
https://blog.csdn.net/czl389/article/details/62264960?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-3.channel_param