[外链图片转存中…(img-tW5xUJwu-1567856655225)]
给定平面上的一组点,将平面细分成以这些点为顶点的三角形。
假设 V V V是二维实数域上的有限点集,边 e e e是由点集中的点作为端点构成的封闭线段, E E E为 e e e的集合。那么该点集 V V V的一个三角剖分 T = ( V , E ) T=(V,E) T=(V,E)是一个平面图 G G G,该平面图满足条件:
(1)除了端点,平面图中的边不包含点集中的任何点。
(2)没有相交边。
(3)平面图中所有的面都是三角面,且所有三角面的合集是散点集 V V V的凸包。
假设 E E E中的一条边 e e e(两个端点为 a , b a,b a,b), e e e若满足条件:存在一个圆经过 a , b a,b a,b两点,圆内(注意是圆内,圆上最多三点共圆)不含点集 V V V中任何其他的点,则称之为狄洛尼边,这一特性又称空圆特性。
如果点集 V V V的一个三角剖分 T T T只包含狄洛尼边,那么该三角剖分称为狄洛尼三角剖分。
[外链图片转存中…(img-R3dUcXOe-1567856655225)]
(1)空圆特性:狄洛尼三角网是唯一的(任意四点不能共圆),在狄洛尼三角形网中任一三角形的外接圆范围内不会有其它点存在。
(2)最大化最小角特性:在散点集可能形成的三角剖分中,狄洛尼三角剖分所形成的三角形的最小角最大。从这个意义上讲,狄洛尼三角网是“最接近于规则化的”三角网(在两个相邻的三角形构成凸四边形的对角线,在相互交换后,两个内角的最小角不再增大)。
(1)最接近:以最接近的三点形成三角形,且各线段(三角行的边)皆不相交。
(2)唯一性:不论从区域何处开始构建,最终都将得到一致的结果。
(3)最优性:任意两个相邻三角形构成的凸四边形的对角线如何可以互换的话,那么两个三角形六个内角中最小角度不会变化。
(4)最规则:如果将三角网中的每个三角形的最小角进行升序排列,则狄洛尼三角网的排列得到的数值最大。
(5)区域性:新增、删除、移动某一个顶点只会影响邻近的三角形。
(6)具有凸边形的外壳:三角网最外层的边界形成一个凸多边形的外壳。
逐点插入Lawson算法是Lawson在1977年提出的。该算法思路简单,易于编程实现。
(1)将两个具有共同边的三角形合成一个多边形。
(2)以最大空圆准则作检查,看其第四个顶点是否在三角形的外接圆之内。
(3)如果在,修正对角线即将对角线对调,即完成局部优化过程的处理。
一般三角网经过LOP处理,可确保成其为狄洛尼三角网。
(1)建立一个大的三角形或多边形,把所有数据点包围起来;
(2)向其中插入一点,该点与包含它的三角形三个顶点相连,形成三个新的三角形;
(3)逐个对它们进行空外接圆检测,同时用LOP进行优化,即通过交换对角线的方法来保证所形成的三角网为狄洛尼三角网。
(1)理论严密、唯一性好,网格满足空圆特性,较为理想。
(2)完成构网后,增加新点时,无需对所有的点进行重新构网,只需对新点的影响三角形范围进行局部联网,且局部联网的方法简单易行。
(3)点的删除、移动也可快速动态地进行。
(1)点集较大时构网速度慢。
(2)如果点集范围是非凸区域或者存在内环,则会产生非法三角形。
当离散点集构成圆环时,Lawson算法产生的非法三角形
目前广泛采用的生成狄洛尼三角剖分的算法是Bowyer-Watson算法,这是一种逐点插入方式生成狄洛尼三角网的算法,主要步骤如下:
(1)建立初始三角网格:针对给定的点集 V V V,找到一个包含该点集的矩形 R R R,称 R R R为辅助窗口。连接 R R R的任意一条对角线,形成两个三角形,作为初始狄洛尼三角网格。
(2)逐点插入:假设已经有一个狄洛尼三角网格 T T T,在其内部再插入一个点 P P P,找到点 P P P所在三角形。从 P P P所在的三角形开始,搜索该三角形的邻近三角形,并进行空外接圆检测。找到外接圆包含点 P P P的所有的三角形并删除这些三角形,形成一个包含 P P P的多边形空腔,称之为狄洛尼空腔。然后连接 P P P与狄洛尼腔的每一个顶点,形成新的狄洛尼三角网格。
(3)删除辅助窗口 R R R:重复(2),当点集 V V V中所有点都已经插入到三角形网格中后,将顶点包含辅助窗口 R R R的三角形全部删除。
将一个平面分割为一组不重叠的、能够覆盖整个平面的区域。
由俄国数学家格奥尔吉·沃罗诺伊建立的空间分割算法。灵感来源于笛卡尔用凸域分割空间的思想。在几何,晶体学建筑学,地理学,气象学,信息系统等许多领域有广泛的应用。
对三角剖分中的所有三角形各边做垂直平分线,将各相邻三角形三边的垂直平分线的交点(即外接圆圆心)连接起来得到一个多边形,称这个多边形为沃罗诺伊图。
给定点集 V V V,OpenCV提供类Subdiv2D实现狄洛尼三角剖分和沃罗诺伊图
(1)将点集 V V V存入列表
points = []
# This is how you can add one point.
points.append((x, y))
(2)定义一个包含整个点集的矩形,对于图像可取整个图像范围。
img = cv2.imread("image.jpg");
size = img.shape
rect = (0, 0, size[1], size[0])
(3)创建类Subdiv2D的实例,subdiv
subdiv = cv2.Subdiv2D(rect)
(4)将点依次插入subdiv
subdiv.insert(point)
(5)读取狄洛尼三角列表
subdiv.getTriangleList()
(6)读取沃罗诺伊面列表
subdiv.getVoronoiFacetList([])
#!/usr/bin/python
import cv2
import numpy as np
import random
# Check if a point is inside a rectangle
def rect_contains(rect, point) :
if point[0] < rect[0] :
return False
elif point[1] < rect[1] :
return False
elif point[0] > rect[2] :
return False
elif point[1] > rect[3] :
return False
return True
# Draw a point
def draw_point(img, p, color ) :
cv2.circle(img, p, 2, color, -1, cv2.LINE_AA, 0)
# Draw delaunay triangles
def draw_delaunay(img, subdiv, delaunay_color) :
triangleList = subdiv.getTriangleList();
size = img.shape
r = (0, 0, size[1], size[0])
for t in triangleList :
pt1 = (t[0], t[1])
pt2 = (t[2], t[3])
pt3 = (t[4], t[5])
if rect_contains(r, pt1) and rect_contains(r, pt2) and rect_contains(r, pt3) :
cv2.line(img, pt1, pt2, delaunay_color, 1, cv2.LINE_AA, 0)
cv2.line(img, pt2, pt3, delaunay_color, 1, cv2.LINE_AA, 0)
cv2.line(img, pt3, pt1, delaunay_color, 1, cv2.LINE_AA, 0)
# Draw voronoi diagram
def draw_voronoi(img, subdiv) :
(facets, centers) = subdiv.getVoronoiFacetList([])
for i in range(len(facets)) :
ifacet_arr = []
for f in facets[i] :
ifacet_arr.append(f)
ifacet = np.array(ifacet_arr, np.int)
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.fillConvexPoly(img, ifacet, color, cv2.LINE_AA, 0);
ifacets = np.array([ifacet])
cv2.polylines(img, ifacets, True, (0, 0, 0), 1, cv2.LINE_AA, 0)
cv2.circle(img, (centers[i][0], centers[i][1]), 3, (0, 0, 0), -1, cv2.LINE_AA, 0)
%matplotlib inline
import matplotlib.pyplot as plt
import math
def plotImgs(idxFig, lstImgs):
fig = plt.figure(idxFig, figsize=(12, 24))
fig.clf()
intNumImgs = len(lstImgs)
intNumCols = int(math.sqrt(intNumImgs))
intNumRows = int(math.ceil(intNumImgs / intNumCols))
idxImg = 0
for idxRow in range(intNumRows):
for idxCol in range(intNumCols):
ax = fig.add_subplot(intNumRows, intNumCols, idxImg + 1, frameon=False)
if lstImgs[idxImg].dtype == "uint8":
ax.imshow(cv2.cvtColor(lstImgs[idxImg], cv2.COLOR_BGR2RGB))
else:
ax.imshow(lstImgs[idxImg])
ax.get_yaxis().set_visible(False)
ax.get_xaxis().set_visible(False)
idxImg += 1
if idxImg >= intNumImgs:
break
plt.show()
if __name__ == '__main__':
# Define window names
win_delaunay = "Delaunay Triangulation"
win_voronoi = "Voronoi Diagram"
# Turn on animation while drawing triangles
animate = True
# Define colors for drawing.
delaunay_color = (255,255,255)
points_color = (0, 0, 255)
# Read in the image.
img = cv2.imread("donald_trump.jpg");
# Keep a copy around
img_orig = img.copy();
# Rectangle to be used with Subdiv2D
size = img.shape
rect = (0, 0, size[1], size[0])
# Create an instance of Subdiv2D
subdiv = cv2.Subdiv2D(rect);
# Create an array of points.
points = [];
# Read in the points from a text file
with open("donald_trump.jpg.txt") as file :
for line in file :
x, y = line.split()
points.append((int(x), int(y)))
# Insert points into subdiv
for p in points :
subdiv.insert(p)
# Show animation
if animate :
img_copy = img_orig.copy()
# Draw delaunay triangles
draw_delaunay(img_copy, subdiv, (255, 255, 255));
cv2.imshow(win_delaunay, img_copy)
cv2.waitKey(100)
# Draw delaunay triangles
draw_delaunay(img, subdiv, (255, 255, 255));
# Draw points
for p in points :
draw_point(img, p, (0,0,255))
# Allocate space for voronoi Diagram
img_voronoi = np.zeros(img.shape, dtype = img.dtype)
# Draw voronoi diagram
draw_voronoi(img_voronoi, subdiv)
# Show results
cv2.imshow(win_delaunay, img)
cv2.imshow(win_voronoi, img_voronoi)
cv2.waitKey(0)
idxFig = 1
lstImgs = [img, img_voronoi]
plotImgs(idxFig, lstImgs)