着色 Voronoi 图
Posted
技术标签:
【中文标题】着色 Voronoi 图【英文标题】:Colorize Voronoi Diagram 【发布时间】:2013-12-29 05:08:53 【问题描述】:我正在尝试为使用 scipy.spatial.Voronoi
创建的 Voronoi 图着色。这是我的代码:
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi, voronoi_plot_2d
# make up data points
points = np.random.rand(15,2)
# compute Voronoi tesselation
vor = Voronoi(points)
# plot
voronoi_plot_2d(vor)
# colorize
for region in vor.regions:
if not -1 in region:
polygon = [vor.vertices[i] for i in region]
plt.fill(*zip(*polygon))
plt.show()
生成的图像:
如您所见,图像边界处的一些 Voronoi 区域没有着色。这是因为这些区域的 Voronoi 顶点的一些索引设置为 -1
,即,对于 Voronoi 图之外的那些顶点。根据文档:
regions: (list of ints, shape (nregions, *)) 构成每个 Voronoi 区域的 Voronoi 顶点的索引。 -1 表示Voronoi 图之外的顶点。
为了也为这些区域着色,我尝试从多边形中移除这些“外部”顶点,但这没有奏效。我想,我需要在图像区域的边界处填充一些点,但我似乎无法弄清楚如何合理地实现这一点。
谁能帮忙?
【问题讨论】:
【参考方案1】:Voronoi 数据结构包含构建“无限远点”位置所需的所有信息。 Qhull 还将它们简单地报告为 -1
索引,因此 Scipy 不会为您计算它们。
https://gist.github.com/pv/8036995
http://nbviewer.ipython.org/gist/pv/8037100
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi
def voronoi_finite_polygons_2d(vor, radius=None):
"""
Reconstruct infinite voronoi regions in a 2D diagram to finite
regions.
Parameters
----------
vor : Voronoi
Input diagram
radius : float, optional
Distance to 'points at infinity'.
Returns
-------
regions : list of tuples
Indices of vertices in each revised Voronoi regions.
vertices : list of tuples
Coordinates for revised Voronoi vertices. Same as coordinates
of input vertices, with 'points at infinity' appended to the
end.
"""
if vor.points.shape[1] != 2:
raise ValueError("Requires 2D input")
new_regions = []
new_vertices = vor.vertices.tolist()
center = vor.points.mean(axis=0)
if radius is None:
radius = vor.points.ptp().max()
# Construct a map containing all ridges for a given point
all_ridges =
for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices):
all_ridges.setdefault(p1, []).append((p2, v1, v2))
all_ridges.setdefault(p2, []).append((p1, v1, v2))
# Reconstruct infinite regions
for p1, region in enumerate(vor.point_region):
vertices = vor.regions[region]
if all(v >= 0 for v in vertices):
# finite region
new_regions.append(vertices)
continue
# reconstruct a non-finite region
ridges = all_ridges[p1]
new_region = [v for v in vertices if v >= 0]
for p2, v1, v2 in ridges:
if v2 < 0:
v1, v2 = v2, v1
if v1 >= 0:
# finite ridge: already in the region
continue
# Compute the missing endpoint of an infinite ridge
t = vor.points[p2] - vor.points[p1] # tangent
t /= np.linalg.norm(t)
n = np.array([-t[1], t[0]]) # normal
midpoint = vor.points[[p1, p2]].mean(axis=0)
direction = np.sign(np.dot(midpoint - center, n)) * n
far_point = vor.vertices[v2] + direction * radius
new_region.append(len(new_vertices))
new_vertices.append(far_point.tolist())
# sort region counterclockwise
vs = np.asarray([new_vertices[v] for v in new_region])
c = vs.mean(axis=0)
angles = np.arctan2(vs[:,1] - c[1], vs[:,0] - c[0])
new_region = np.array(new_region)[np.argsort(angles)]
# finish
new_regions.append(new_region.tolist())
return new_regions, np.asarray(new_vertices)
# make up data points
np.random.seed(1234)
points = np.random.rand(15, 2)
# compute Voronoi tesselation
vor = Voronoi(points)
# plot
regions, vertices = voronoi_finite_polygons_2d(vor)
print "--"
print regions
print "--"
print vertices
# colorize
for region in regions:
polygon = vertices[region]
plt.fill(*zip(*polygon), alpha=0.4)
plt.plot(points[:,0], points[:,1], 'ko')
plt.xlim(vor.min_bound[0] - 0.1, vor.max_bound[0] + 0.1)
plt.ylim(vor.min_bound[1] - 0.1, vor.max_bound[1] + 0.1)
plt.show()
【讨论】:
可能是一个小错误,不确定这是否随着新版本的 numpy 发生了变化,但是执行.ptp()
会找到最大值和最小值之间的差异,然后.max()
什么都不做。我想你想要的是.ptp(axis=0).max()
。
如果我提供 x points = np.random.rand(x, 2) 某些区域保持白色。我猜在这种情况下端点计算不正确还是我遗漏了什么?
这段代码有两个问题:1)半径可能需要任意大。 2) 延伸/重建半线脊 (direction = np.sign(np.dot(midpoint - center, n)) * n
) 的方向并不总是正确的。我一直在努力开发一种一直有效的算法,但我还没有成功。
是的,这绝对是错误的。我正在使用投影数据点(墨卡托),代码中的一些 far_points 变为负数。【参考方案2】:
我有一个更简单的解决方案来解决这个问题,即在调用 Voronoi 算法之前将 4 个遥远的虚拟点添加到您的点列表中。
根据您的代码,我添加了两行。
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi, voronoi_plot_2d
# make up data points
points = np.random.rand(15,2)
# add 4 distant dummy points
points = np.append(points, [[999,999], [-999,999], [999,-999], [-999,-999]], axis = 0)
# compute Voronoi tesselation
vor = Voronoi(points)
# plot
voronoi_plot_2d(vor)
# colorize
for region in vor.regions:
if not -1 in region:
polygon = [vor.vertices[i] for i in region]
plt.fill(*zip(*polygon))
# fix the range of axes
plt.xlim([0,1]), plt.ylim([0,1])
plt.show()
然后生成的图形如下所示。
【讨论】:
我有同样的图表。但是知道如何在没有橙色点的情况下绘制它吗? 只要使用voronoi_plot_2d(vor, show_vertices = False)
这很有吸引力。但是,角度是错误的……坐标中的值与数据集中的质心的比率越大,它就越精确,但它仍然是不真实的。【参考方案3】:
我认为 vor 结构中的可用数据中没有足够的信息来解决这个问题,而无需再次进行至少一些 voronoi 计算。既然是这种情况,这里是原始 voronoi_plot_2d 函数的相关部分,您应该能够使用它们来提取与 vor.max_bound 或 vor.min_bound 相交的点,它们是图表的左下角和右上角命令找出多边形的其他坐标。
for simplex in vor.ridge_vertices:
simplex = np.asarray(simplex)
if np.all(simplex >= 0):
ax.plot(vor.vertices[simplex,0], vor.vertices[simplex,1], 'k-')
ptp_bound = vor.points.ptp(axis=0)
center = vor.points.mean(axis=0)
for pointidx, simplex in zip(vor.ridge_points, vor.ridge_vertices):
simplex = np.asarray(simplex)
if np.any(simplex < 0):
i = simplex[simplex >= 0][0] # finite end Voronoi vertex
t = vor.points[pointidx[1]] - vor.points[pointidx[0]] # tangent
t /= np.linalg.norm(t)
n = np.array([-t[1], t[0]]) # normal
midpoint = vor.points[pointidx].mean(axis=0)
direction = np.sign(np.dot(midpoint - center, n)) * n
far_point = vor.vertices[i] + direction * ptp_bound.max()
ax.plot([vor.vertices[i,0], far_point[0]],
[vor.vertices[i,1], far_point[1]], 'k--')
【讨论】:
我希望自己能够绕过实现多边形点的计算。但是感谢指向vor.min_bound
和vor.max_bound
的指针(以前没有注意到它们)。这些对这项任务很有用,voronoi_plot_2d()
的代码也是如此。以上是关于着色 Voronoi 图的主要内容,如果未能解决你的问题,请参考以下文章