找到不规则形状多边形的“视觉”中心的最快方法是啥?

Posted

技术标签:

【中文标题】找到不规则形状多边形的“视觉”中心的最快方法是啥?【英文标题】:What is the fastest way to find the "visual" center of an irregularly shaped polygon?找到不规则形状多边形的“视觉”中心的最快方法是什么? 【发布时间】:2010-11-15 05:53:16 【问题描述】:

我需要找到一个不规则多边形的视觉中心点。视觉中心是指在视觉上看起来位于多边形大面积中心的点。该应用程序是在多边形内放置一个标签。

这是一个使用内部缓冲的解决方案:

https://web.archive.org/web/20150708063910/http://proceedings.esri.com/library/userconf/proc01/professional/papers/pap388/p388.htm

如果要使用它,找到缓冲区的有效且快速的方法是什么?如果要使用其他方式,那是哪种方式?

真正坚硬的多边形的一个很好的例子是一个巨大的厚 U(用 Arial Black 或 Impact 或类似字体书写)。

【问题讨论】:

如果多边形定义的集合是(高度)非凸的怎么办(en.wikipedia.org/wiki/Convex_set);是否允许中心在多边形之外? 是的,但是为了标记,我们需要在里面找到一个点。 @Mikhil:为了扩展@Pukku 的评论,您能否发布这个问题的“硬”方面,即一个很难标记给定“天真”答案的形状,例如 center-of -大量的?我很容易想到的是一个巨大的 U 或佛罗里达州(这些形状的质心在边界之外) Jason,你说的例子很好!谢谢! 我猜一个小的“U”几乎和测试用例一样难;) 【参考方案1】:

我从 MapBox 中找到了一个非常好的解决方案,名为 Polylabel。完整的源代码也可以在他们的Github 上找到。

正如 T Austin 所说,它基本上试图找到多边形的视觉中心。

某些细节表明这可能是一个实用的解决方案:

不幸的是,计算 [理想解] 既复杂又复杂 和慢。该问题的已发布解决方案需要 受约束的 Delaunay 三角剖分或将直骨架计算为 预处理步骤 — 这两个步骤都很慢且容易出错。

对于我们的用例,我们不需要精确的解决方案 — 我们愿意 交换一些精度以获得更快的速度。当我们贴上标签时 一张地图,以毫秒为单位计算它比 在数学上是完美的。

关于使用的快速说明。源代码非常适合开箱即用的 javascript,但是如果您打算将其与“普通”多边形一起使用,那么您应该将其包装在一个空数组中,因为这里的函数采用 GeoJSONPolygons 而不是普通多边形,即

var myPolygon = [[x1, y1], [x2, y2], [x3, y3]];
var center = polylabel([myPolygon]);

【讨论】:

我怎么会错过对额外阵列的需求……先生,您是救生员! @complistic 哈.. 老实说...我也错过了这个,我花了比它应该找到的时间更长的时间:) 直骨架不会给你最大内接圆盘的中心,但中轴(和 Voronoi 图)可以:***.com/a/46867645/8756717 这个答案对我很有帮助!我在 Dart 中需要这个,所以我移植了它:pub.dev/packages/polylabel【参考方案2】:

如果可以将多边形转换为二值图像,则可以使用图像处理领域存在的基础,例如:A Fast Skeleton Algorithm on Block Represented Binary Images。

但这在一般情况下并不合理,因为离散化错误和额外的工作。

但是,也许您会发现这些有用:

Straight skeleton of a simple polygon Determining the Skeleton of a Simple Polygon in (Almost) Linear Time

编辑:也许您想寻找多边形中包含的最大圆的中心点。它不一定总是在观察中心,但大多数时候可能会给出预期的结果,只有在轻微的病理情况下才会完全偏离。

【讨论】:

另见***.com/questions/1109536/… 我认为这些是您迄今为止最好的选择。您可以通过将多边形垂直拉伸 2 或 3 倍来调整上述内容,然后搜索拉伸多边形中包含的最大圆。这将为您提供多边形中包含的最大椭圆,这将为您提供放置标签的最佳位置。 此答案中的三个链接中有两个已失效。 直骨架不会给你最大内切圆盘的中心,但中轴(和 Voronoi 图)可以:***.com/a/46867645/8756717【参考方案3】:

怎么样:

如果多边形的质心在多边形内部,则使用它,否则:

1) 从质心延伸一条线穿过多边形,将多边形分成等面积的两半

2) “视觉中心”是直线与周边接触的最近点与沿远离质心的方向切割周边的下一个点之间的中间点

这里有几张图片来说明它:

【讨论】:

爱它,伙计!真是聪明!现在在实施方面,你和或其他人解决了吗? @MaraisRossouw 我已经向使用此方法的 OP 发布了一个类似问题的答案:***.com/a/39408054/3628232【参考方案4】:

您是否考虑过使用质心公式?

http://en.wikipedia.org/wiki/Centroid

http://en.wikipedia.org/wiki/K-means_algorithm

【讨论】:

质心 == 均匀密度的质心【参考方案5】:

这是我尝试过的四种不同方法。

    cv2 基于质心 (get_center_of_mass) shapely 基于代表点 (get_representative_point) cv2 + skimage.skeleton 基于 skeletonized 形状的质心 (get_skeleton_center_of_mass) scipy 基于到边界的最远距离 (get_furthest_point_from_edge)
import numpy as np
import cv2
from shapely.geometry import Polygon
from skimage.morphology import skeletonize, medial_axis
from scipy.ndimage.morphology import distance_transform_edt
import matplotlib.pyplot as plt
H, W = 300, 300

def get_random_contour():
    xs = np.random.randint(0, W, 4)
    ys = np.random.randint(0, H, 4)
    cnt = np.array([[x,y] for x,y in zip(xs,ys)])
    mask = draw_contour_on_mask((H,W), cnt)
    cnt, _ = cv2.findContours(mask, 1, 2)
    cnt = cnt[0]
    return cnt

def draw_contour_on_mask(size, cnt):
    mask = np.zeros(size, dtype='uint8')
    mask = cv2.drawContours(mask, [cnt], -1, 255, -1)
    return mask

def get_center_of_mass(cnt):
    M = cv2.moments(cnt)
    cx = int(M['m10']/M['m00'])
    cy = int(M['m01']/M['m00'])
    return cx, cy

def get_representative_point(cnt):
    poly = Polygon(cnt.squeeze())
    cx = poly.representative_point().x
    cy = poly.representative_point().y
    return cx, cy

def get_skeleton_center_of_mass(cnt):
    mask = draw_contour_on_mask((H,W), cnt)
    skel = medial_axis(mask//255).astype(np.uint8) #<- medial_axis wants binary masks with value 0 and 1
    skel_cnt,_ = cv2.findContours(skel,1,2)
    skel_cnt = skel_cnt[0]
    M = cv2.moments(skel_cnt) 
    if(M["m00"]==0): # this is a line
        cx = int(np.mean(skel_cnt[...,0]))
        cy = int(np.mean(skel_cnt[...,1]))
    else:
        cx = int(M['m10']/M['m00'])
        cy = int(M['m01']/M['m00'])
    return cx, cy

def get_furthest_point_from_edge(cnt):
    mask = draw_contour_on_mask((H,W), cnt)
    d = distance_transform_edt(mask)
    cy, cx = np.unravel_index(d.argmax(), d.shape)
    return cx, cy

以下是我对该主题的分析:

get_center_of_mass 是最快的,但正如该线程中所述,对于非凸形,质心可以位于形状之外。 get_representative_point 也很快,但识别点虽然始终保证保持在形状内(或进行少量编辑甚至多个断开的形状!),但与对象的中心没有太大关系 get_skeleton_center_of_mass 返回一个感知上不错的中心点,但速度很慢,并且需要断开形状的逻辑 get_furthest_point_from_edge 相对较快,很容易泛化到不连贯的形状,并且中心点在视觉上令人愉悦
rows = 4
cols = 4
markers = ['x', '+', "*", "o"]
colors = ['r','b','g','orange']
functions = [get_center_of_mass, get_representative_point, get_skeleton_center_of_mass, get_furthest_point_from_edge]

plt.figure(figsize=(2*cols, 2*rows, ))
for i in range(rows*cols): 
    cnt = get_random_contour()
    mask = draw_contour_on_mask((H,W), cnt)
    
    plt.subplot(cols,rows, i+1)
    plt.imshow(mask, cmap='gray')
    for c, m, f in zip(colors, markers, functions):
        l = f.__name__
        cx, cy = f(cnt)
        plt.scatter(cx, cy, c=c, s=100, label=l, marker=m, alpha=0.7)

plt.tight_layout()    
plt.legend(loc=3)
plt.show()

以下是算法在 100 个随机示例上运行的速度比较:

N_EXAMPLES = 100
cnts = [get_random_contour() for _ in range(N_EXAMPLES)]

%%time
_ = [get_center_of_mass(cnt) for cnt in cnts]
CPU times: user 7.07 ms, sys: 155 µs, total: 7.23 ms
Wall time: 5.75 ms

%%time
_ = [get_representative_point(cnt) for cnt in cnts]
CPU times: user 23.6 ms, sys: 7.84 ms, total: 31.4 ms
Wall time: 28.5 ms

%%time
_ = [get_skeleton_center_of_mass(cnt) for cnt in cnts]
CPU times: user 5.56 s, sys: 3.31 ms, total: 5.56 s
Wall time: 5.55 s

%%time
_ = [get_furthest_point_from_edge(cnt) for cnt in cnts]
CPU times: user 486 ms, sys: 53 µs, total: 486 ms
Wall time: 485 ms

【讨论】:

这些算法是否恰好与 Polylabel 的算法相同? (上面提到***.com/a/40464906/3195477) 是的,看来get_furthest_point_from_edge实现了和Polylabel一样的功能:“一种快速查找算法——离多边形轮廓最远的内部点”(@ 987654324@) 感谢您的澄清。我使用 polylabel 已经有一段时间了,主要是因为它在有用的位置产生了一个点,并且看起来稳定/可靠。但总体而言,这种比较比这里的信息更客观。【参考方案6】:

计算多边形每条边的中心位置 (x,y)。您可以通过查找每条边的末端位置之间的差异来做到这一点。取每个维度中每个中心的平均值。这将是多边形的中心。

【讨论】:

当涉及到高度非凸的形状时,我认为这与我的解决方案存在同样的问题...... 是的,如果不采用加权平均,即使多边形是凸面,它也会过分强调短边。【参考方案7】:

Centroid 方法已被多次建议。我认为这是一个很好的资源,它非常直观地描述了这个过程(以及许多其他有用的多边形技巧):

http://paulbourke.net/geometry/polygonmesh/centroid.pdf

另外,为了放置一个简单的 UI 标签,只计算多边形的边界框(由多边形中任何顶点的最低和最高 x 和 y 坐标定义的矩形)并获取其中心可能就足够了在:


    x = min_x + (max_x - min_x)/2,
    y = min_y + (max_y - min_y)/2

这比计算质心要快一些,这对于实时或嵌入式应用程序可能很重要。

另外请注意,如果您的多边形是静态的(它们不会改变形状),您可以通过将 BB 中心/质心计算的结果(例如,相对于多边形的第一个顶点)保存到多边形的数据结构。

【讨论】:

好主意,但并不总是有效,因为边界框的中心可能远离多边形本身。 !Center of bounding box outside polygon (img)【参考方案8】:

我并不是说这是最快的,但它会给你一个多边形内的点。计算Straight Skeleton。你要找的点就在这个骨架上。例如,您可以选择到边界框中心的法线距离最短的那个。

【讨论】:

直骨架不会给你最大内切圆盘的中心,但中轴(和 Voronoi 图)可以:***.com/a/46867645/8756717【参考方案9】:

如何找到多边形的“内圆”(最大的圆适合它),然后将标签居中在它的中心?这里有几个链接可以帮助您入门:

http://www.mathopenref.com/polygonincircle.htmlhttps://nrich.maths.org/discus/messages/145082/144373.html?1219439473

这很可能不会在每个多边形上都完美运行;一个看起来像 C 的多边形会在一个有点不可预测的地方有标签。但好处是标签总是会与多边形的实体部分重叠。

【讨论】:

如果多边形有多个三角剖分会不会很慢?【参考方案10】:

如果我理解您所链接的论文的要点(顺便说一句,这是一个非常有趣的问题),这种“内部缓冲”技术有点类似于用一块被酸溶解的糖建模所讨论的形状边缘。(例如,随着缓冲距离的增加,保留的原始形状会减少)最后一位是放置标签的理想位置。

不幸的是,我不太清楚如何在算法中实现这一点......

【讨论】:

像 PostGIS 这样的 GIS 软件有像 ST_Buffer 这样的功能。我不知道怎么这么快。【参考方案11】:

我认为,如果您将多边形重新分解为顶点,然后应用一个函数来找到最大的凸包,然后找到该凸包的中心,它将与“明显”中心紧密匹配。

找到给定顶点的最大凸包:Look under the Simple Polygon paragraph.

平均凸包的顶点以找到中心。

【讨论】:

它将选择一侧。在这种情况下,期望的行为是什么? 对于一个巨大的 U,可接受的解决方案是下厚部分的中间。 如果下厚截面是最大的凸包,那么它将被选中。是否有某种类型的标准可以让选定的凸包更像一个正方形? 最大的凸包不会覆盖整个 U 并且是一个矩形吗? 哦,您需要修改算法以不包含任何内部顶点。【参考方案12】:

您能否将标签放置在(可能是边界框的)朴素中心,然后根据局部多边形边缘和标签的 BB 的交点移动它?沿着相交边的法线移动,如果多条边相交,求和它们的法线移动?

这里只是猜测;在这类问题中,只要性能不是太大问题,我可能会尝试迭代解决。

【讨论】:

【参考方案13】:

现在没有太多时间来详细说明或测试这个,但有机会我会尝试做更多。

使用质心作为您的主要方法。测试质心是否在多边形内;如果不是,请在最近的点通过并到多边形的另一侧画一条线。在多边形内该线部分的中点,放置您的标签。

因为离质心最近的点可能会限制相当大的区域,我认为这可能会产生类似于 Kyralessa 的内圆的结果。当然,如果你有一个带孔的多边形,这可能会发疯。在那种情况下,内圈可能会好得多。另一方面,对于典型情况,它默认为(快速?)质心方法。

【讨论】:

病理测试用例#3:一个对称的杠铃状形状,有一个细长的矩形,两端有两个大八边形。质心在多边形内,但矩形不适合标注,因为它可能不适合。【参考方案14】:

这个问题可能类似于在假设密度均匀的情况下找到“质心”。

编辑:如果多边形有“洞”,此方法将不起作用

【讨论】:

没有。请参阅 OP 链接到的 ESRI 论文中的图 #4。 看来我的假设是他们在#2中使用的;它唯一的故障是在这种情况下:“但是,如果多边形有洞,这种方法会提供不正确的结果” 没有。想象一个巨大的 U。没有孔,质心不在多边形的边界内。我认为你的答案只对凸多边形是正确的。 谢谢;如果提问者也给我们一些边界条件来处理,那会很有帮助!【参考方案15】:

您可以使用土木工程中使用的质心(或重心)方法,这是来自***的有用链接:

http://en.wikipedia.org/wiki/Center_of_mass

【讨论】:

以上是关于找到不规则形状多边形的“视觉”中心的最快方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

怎么判断某一点在一个不规则的图形内部

图像处理中ROI是啥意思如题

比较多边形的最快方法

找到某个坐标在某个周长内的最快方法是啥?

在向量中找到最长的“连续数字”条纹的最快方法是啥?

在C中找到整数中最高设置位(msb)的最快/最有效方法是啥?