繁体   English   中英

找到N个矩形重叠的有效方法

[英]Efficient way to find overlapping of N rectangles

我试图找到一个有效的解决方案来查找 n 个矩形的重叠,其中矩形存储在两个单独的列表中。 我们正在寻找listB中与listA中的矩形重叠的所有矩形(反之亦然)。 将第一个列表中的一个元素与第二个列表进行比较可能会花费大量时间。 我正在寻找一个有效的解决方案。

我有两个矩形列表

rect = Rectangle(10, 12, 56, 15)
rect2 = Rectangle(0, 0,1, 15)
rect3 = Rectangle (10,  12, 56, 15)

listA = [rect, rect2]
listB = [rect3]

它是从类创建的:

import numpy as np
import itertools as it

class  Rectangle(object):
    def __init__(self, left, right, bottom, top):
        self.left = left
        self.bottom = right
        self.right = bottom
        self.top = top

    def overlap(r1, r2):
        hoverlaps = True
        voverlaps = True
        if (r1.left > r2.right) or (r1.right < r2.left):
            hoverlaps = False
        if (r1.top < r2.bottom) or (r1.bottom > r2.top):
            voverlaps = False
        return hoverlaps and voverlaps

我需要将listB中的矩形与listA进行比较,代码是这样的,效率非常低 - 一个一个比较

for a in it.combinations(listB):
    for b in it.combinations(listA):
        if a.overlap(b):

有什么更有效的方法来解决这个问题?

首先:与计算几何中的许多问题一样,指定增长顺序分析的参数需要注意:调用列表mn的长度,这些参数中最坏的情况是Ω(m×n) ,因为所有区域都可能重叠(在这方面,问题中的算法是渐近最优的)。 通常包括输出的大小: t = f(m, n, o)输出敏感算法)。
简单地说,对于所提出的问题, f ∈ Ω(m+n+o)


Line Sweep是一种将几何问题减少一维的范例 - 在其原始形式中,从 2D 到 1D,从平面到线。

想象一下平面上的所有矩形,列表的颜色不同。
现在在该平面上扫过一条线 - 按照惯例,从左到右,并且无限地向右“用于低 y 坐标”(以增加x顺序处理坐标,增加y顺序以获得相等的x )。
对于所有此扫描(或扫描),每种颜色保留一组表示当前 x 坐标处所有矩形的“y 间隔”,从空开始。 (在支持插入、删除和枚举与查询间隔重叠的所有间隔的数据结构中:见下文。)
遇到一个矩形的左侧,将该段添加到数据结构中以获取其颜色。 报告任何其他颜色的重叠间隔/矩形。
在右侧,删除段。
根据“重叠”的定义,先处理左侧再处理右侧 - 或反之。


有许多数据结构支持插入和删除区间,以及查找与查询区间重叠的所有区间 目前,我认为增强搜索树可能最容易理解、实现、测试、分析……
使用它,从listAlistB枚举所有o相交的轴对齐矩形对(a, b)应该可以在O((m+n)log(m+n)+o)时间和O(m+n)空间。 对于相当大的问题实例,避免需要超过线性空间的数据结构((原始)段树,例如与区间重叠有关的一个示例)。


算法设计的另一个范式是分治:对于计算几何问题,选择一个可以将问题划分为独立部分的维度,以及一个坐标,使得“坐标下方”和“坐标上方”的子问题接近预期的运行时间。 很可能,需要解决另一个(和不同的)子问题“包括坐标”。 当a)解决子问题的运行时间是“超对数线性”时,这往往是有益的,并且b)有一种廉价(线性)的方法可以从子问题的解决方案中构建整体解决方案.
这有助于同时解决问题,并且可以与任何其他方法一起用于子问题,包括线扫描。


将有许多方法来调整每种方法,首先忽略不可能对输出做出贡献的输入项。 为了“公平地”比较类似增长顺序的算法的实现,不要瞄准公平的“调整水平”:尝试投入大量时间进行调整。

几个潜在的小效率改进。 首先,修复您的overlap()函数,它可能会进行不需要的计算:

def overlap(r1, r2):

    if r1.left > r2.right or r1.right < r2.left:
        return False

    if r1.top < r2.bottom or r1.bottom > r2.top:
        return False

    return True

其次,计算其中一个列表的包含矩形,并用它来筛选另一个列表——任何不与容器重叠的矩形都不需要针对所有对其做出贡献的矩形进行测试:

def containing_rectangle(rectangles):
    return Rectangle(min(rectangles, key=lambda r: r.left).left,
        max(rectangles, key=lambda r: r.right).right,
        min(rectangles, key=lambda r: r.bottom).bottom,
        max(rectangles, key=lambda r: r.top).top
    )

c = containing_rectangle(listA)

for b in listB:
    if b.overlap(c):
        for a in listA:
            if b.overlap(a):

在我对数百个随机矩形的测试中,这避免了按个位数百分比(例如 2% 或 3%)进行比较,并且偶尔会增加比较次数。 但是,您的数据可能不是随机的,并且通过这种类型的筛选可能会更好。

根据数据的性质,您可以将其分解为容器矩形检查,以检查 50K 中的每批 10K 矩形,或者切片可为您带来最大效率。 可能在将矩形分配给容器批次之前对矩形进行预排序(例如按其中心)。

我们可以用容器矩形分解和批处理这两个列表:

listAA = [listA[x:x + 10] for x in range(0, len(listA), 10)]

for i, arrays in enumerate(listAA):
    listAA[i] = [containing_rectangle(arrays)] + arrays

listBB = [listB[x:x + 10] for x in range(0, len(listB), 10)]

for i, arrays in enumerate(listBB):
    listBB[i] = [containing_rectangle(arrays)] + arrays

for bb in listBB:
    for aa in listAA:
        if bb[0].overlap(aa[0]):
            for b in bb[1:]:
                if b.overlap(aa[0]):
                    for a in aa[1:]:
                        if b.overlap(a):

使用我的随机数据,这减少了 15% 到 20% 的比较,甚至包括容器矩形比较。 上面的矩形批处理是任意的,您可能会做得更好。

您得到的异常来自您显示的代码的最后一行。 表达式list[rect]无效,因为list是一个类,并且该上下文中的[]语法试图对其进行索引。 您可能只需要[rect] (它创建一个包含单个项目rect的新列表)。

您的代码还有其他几个基本问​​题。 例如,您的Rect.__init__方法没有设置left属性,这在您的碰撞测试方法中似乎是期望的。 您还在overlap方法的不同部分对r1r2使用了不同的大小写(Python 不认为r1R1相同)。

这些问题与测试两个以上的矩形没有任何关系,这是您的问题所问的。 最简单的方法(如果您遇到上述基本问题,我强烈建议您坚持使用简单的算法),就是使用现有的成对测试简单地将每个矩形与其他矩形进行比较。 您可以使用itertools.combinations从可迭代对象(如列表)中轻松获取所有项目对:

list_of_rects = [rect1, rect2, rect3, rect4] # assume these are defined elsewhere

for a, b in itertools.combinations(list_of_rects, 2):
    if a.overlap(b):
        # do whatever you want to do when two rectangles overlap here

根据我做的测试,这个使用 numpy 的实现快了大约 35-40 倍。 对于 2 个列表,每个列表都有 10000 个随机矩形,此方法耗时 2.5 秒,而问题中的方法耗时约 90 秒。 就复杂性而言,它仍然像问题中的方法一样 O(N^2) 。

import numpy as np

rects1=[
    [0,10,0,10],
    [0,100,0,100],
]

rects2=[
    [20,50,20,50],
    [200,500,200,500],
    [0,12,0,12]
]

data=np.asarray(rects2)


def find_overlaps(rect,data):
    data=data[data[::,0]<rect[1]]
    data=data[data[::,1]>rect[0]]
    data=data[data[::,2]<rect[3]]
    data=data[data[::,3]>rect[2]]
    return data


for rect in rects1:
    overlaps = find_overlaps(rect,data)
    for overlap in overlaps:
        pass#do something here

显然,如果您的列表(至少是 listB)按 r2.xmin 排序,您可以在 listB 中搜索 r1.xmax 并停止测试此 listB 中 r1 的重叠(其余部分在右侧)。 这将是 O(n·log(n))。

排序向量比排序列表具有更快的访问速度。

我假设矩形边缘的方向与轴相同。

还按照 cdlane 的说明修复您的overlap()函数。

如果您知道坐标的上限和下限,您可以通过将坐标空间划分为正方形来缩小搜索范围,例如 100x100。

  • 每个坐标方块制作一个“集合”。
  • 遍历所有方块,将它们放在它们重叠的任何方块的“集合”中。

另请参阅使用分区来加速图形操作的平铺渲染

    // Stores rectangles which overlap (x, y)..(x+w-1, y+h-1)
    public class RectangleSet
    {
       private List<Rectangle> _overlaps;

       public RectangleSet(int x, int y, int w, int h);
    }

    // Partitions the coordinate space into squares
    public class CoordinateArea
    {
       private const int SquareSize = 100;

       public List<RectangleSet> Squares = new List<RectangleSet>();

       public CoordinateArea(int xmin, int ymin, int xmax, int ymax)
       {
          for (int x = xmin; x <= xmax; x += SquareSize)
          for (int y = ymin; y <= ymax; y += SquareSize)
          {
              Squares.Add(new RectangleSet(x, y, SquareSize, SquareSize);
          }
       }

       // Adds a list of rectangles to the coordinate space
       public void AddRectangles(IEnumerable<Rectangle> list)
       {
          foreach (Rectangle r in list)
          {
              foreach (RectangleSet set in Squares)
              {
                  if (r.Overlaps(set))
                      set.Add(r);
              }
          }
       }
    }

现在你有一组小得多的矩形进行比较,这应该可以很好地加快速度。

CoordinateArea A = new CoordinateArea(-500, -500, +1000, +1000);
CoordinateArea B = new CoordinateArea(-500, -500, +1000, +1000);  // same limits for A, B

A.AddRectangles(listA);
B.AddRectangles(listB);

for (int i = 0; i < listA.Squares.Count; i++)
{
    RectangleSet setA = A[i];
    RectangleSet setB = B[i];

    // *** small number of rectangles, which you can now check thoroghly for overlaps ***

}

我认为您必须设置一个额外的数据结构(空间索引),以便快速访问可能重叠的附近矩形,以降低从二次到线性的时间复杂度。

也可以看看:

这是我用来计算许多候选矩形(使用候选坐标 [[l, t, r, b], ...])与目标矩形(target_coords [l, t, r, b])的重叠区域的方法:

comb_tensor = np.zeros((2, candidate_coords.shape[0], 4))

comb_tensor[0, :] = target_coords
comb_tensor[1] = candidate_coords

dx = np.amin(comb_tensor[:, :, 2].T, axis=1) - np.amax(comb_tensor[:, :, 0].T, axis=1)
dy = np.amin(comb_tensor[:, :, 3].T, axis=1) - np.amax(comb_tensor[:, :, 1].T, axis=1)

dx[dx < 0] = 0
dy[dy < 0] = 0 

overlap_areas = dx * dy

这应该是相当有效的,特别是如果有许多候选矩形,因为所有这些都是使用在 ndarrays 上运行的 numpy 函数完成的。 您可以循环计算重叠区域,也可以向 comb_tensor 添加一个维度。

我认为下面的代码会很有用。

print("Identifying Overlap between n number of rectangle")
#List to be used in set and get_coordinate_checked_list
coordinate_checked_list = []

def get_coordinate_checked_list():
    #returns the overlapping coordinates of rectangles
    """
    :return: list of overlapping coordinates
    """
    return coordinate_checked_list

def set_coordinate_checked_list(coordinates):
    #appends the overlapping coordinates of rectangles
    """
    :param coordinates: list of overlapping coordinates to be appended in coordinate_checked_list
    :return:
    """
    coordinate_checked_list.append(coordinates)

def overlap_checked_for(coordinates):
    # to find rectangle overlap is already checked, if checked "True" will be returned else coordinates will be added
    # to coordinate_checked_list and return "False"
    """
    :param coordinates: coordinates of two rectangles
    :return: True if already checked, else False
    """
    if coordinates in get_coordinate_checked_list():
        return True
    else:
        set_coordinate_checked_list(coordinates)
        return False

def __isRectangleOverlap(R1, R2):
    #checks if two rectangles overlap
    """
    :param R1: Rectangle1 with cordinates [x0,y0,x1,y1]
    :param R2: Rectangle1 with cordinates [x0,y0,x1,y1]
    :return: True if rectangles overlaps else False
    """
    if (R1[0] >= R2[2]) or (R1[2] <= R2[0]) or (R1[3] <= R2[1]) or (R1[1] >= R2[3]):
        return False
    else:
        print("Rectangle1 {} overlaps with Rectangle2 {}".format(R1,R2))
        return True


def __splitByHeightandWidth(rectangles):
    # Gets the list of rectangle, divide the paged with respect to height and width and position
    # the rectangle in suitable section say left_up,left_down,right_up,right_down and returns the list of rectangle
    # grouped with respect to section

    """
    :param rectangles: list of rectangle coordinates each designed as designed as [x0,y0,x1,y1]
    :return:list of rectangle grouped with respect to section, suspect list which holds the rectangles
            positioned in more than one section
    """

    lu_Rect = []
    ll_Rect = []
    ru_Rect = []
    rl_Rect = []
    sus_list = []
    min_h = 0
    max_h = 0
    min_w = 0
    max_w = 0
    value_assigned = False
    for rectangle in rectangles:
        if not value_assigned:
            min_h = rectangle[1]
            max_h = rectangle[3]
            min_w = rectangle[0]
            max_w = rectangle[2]
        value_assigned = True
        if rectangle[1] < min_h:
            min_h = rectangle[1]
        if rectangle[3] > max_h:
            max_h = rectangle[3]
        if rectangle[0] < min_w:
            min_w = rectangle[0]
        if rectangle[2] > max_w:
            max_w = rectangle[2]

    for rectangle in rectangles:
        if rectangle[3] <= (max_h - min_h) / 2:
            if rectangle[2] <= (max_w - min_w) / 2:
                ll_Rect.append(rectangle)
            elif rectangle[0] >= (max_w - min_w) / 2:
                rl_Rect.append(rectangle)
            else:
                # if rectangle[0] < (max_w - min_w) / 2 and rectangle[2] > (max_w - min_w) / 2:
                ll_Rect.append(rectangle)
                rl_Rect.append(rectangle)
                sus_list.append(rectangle)
        if rectangle[1] >= (max_h - min_h) / 2:
            if rectangle[2] <= (max_w - min_w) / 2:
                lu_Rect.append(rectangle)
            elif rectangle[0] >= (max_w - min_w) / 2:
                ru_Rect.append(rectangle)
            else:
                # if rectangle[0] < (max_w - min_w) / 2 and rectangle[2] > (max_w - min_w) / 2:
                lu_Rect.append(rectangle)
                ru_Rect.append(rectangle)
                sus_list.append(rectangle)
        if rectangle[1] < (max_h - min_h) / 2 and rectangle[3] > (max_h - min_h) / 2:
            if rectangle[0] < (max_w - min_w) / 2 and rectangle[2] > (max_w - min_w) / 2:
                lu_Rect.append(rectangle)
                ll_Rect.append(rectangle)
                ru_Rect.append(rectangle)
                rl_Rect.append(rectangle)
                sus_list.append(rectangle)
            elif rectangle[2] <= (max_w - min_w) / 2:
                lu_Rect.append(rectangle)
                ll_Rect.append(rectangle)
                sus_list.append(rectangle)
            else:
                # if rectangle[0] >= (max_w - min_w) / 2:
                ru_Rect.append(rectangle)
                rl_Rect.append(rectangle)
                sus_list.append(rectangle)
    return [lu_Rect, ll_Rect, ru_Rect, rl_Rect], sus_list

def find_overlap(rectangles):
    #Find all possible overlap between the list of rectangles
    """
    :param rectangles: list of rectangle grouped with respect to section
    :return:
    """
    split_Rectangles , sus_list = __splitByHeightandWidth(rectangles)
    for section in split_Rectangles:
        for rect in range(len(section)-1):
            for i in range(len(section)-1):
                if section[0] and section[i+1] in sus_list:
                    if not overlap_checked_for([section[0],section[i+1]]):
                        __isRectangleOverlap(section[0],section[i+1])
                else:
                    __isRectangleOverlap(section[0],section[i+1])
            section.pop(0)

arr =[[0,0,2,2],[0,0,2,7],[0,2,10,3],[3,0,4,1],[6,1,8,8],[0,7,2,8],[4,5,5,6],[4,6,10,7],[9,3,10,5],[5,3,6,4],[4,3,6,5],[4,3,5`enter code here`,6]]
find_overlap(arr)

对于一个简单的解决方案,如果矩形相对稀疏,则可以提高纯蛮力:

  • 对单个列表中的所有 Y 坐标进行排序,并为每个坐标存储矩形的索引、原始列表和用于区分底部和顶部的标志;

  • 从下到上扫描列表,维护两个“活动列表”,每个矩形集一个;

    • 当您遇到底部时,在其活动列表中插入矩形索引并与另一个列表中的所有矩形进行比较以检测 X 上的重叠;

    • 当您遇到顶部时,从其活动列表中删除矩形索引。

假设简单的线性列表,更新和搜索将花费时间与活动列表的大小成线性关系。 因此,您将执行 M xn + mx N 比较,而不是 M x N 比较,其中 m 和 n 表示平均列表大小。 (如果矩形在它们的集合内不重叠,则可以预期平均列表长度不超过 √M 和 √N。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM