生成包含多个 2D 点的矩形的快速算法
Posted
技术标签:
【中文标题】生成包含多个 2D 点的矩形的快速算法【英文标题】:Fast algorithm to generate rectangles that contains a number of 2D points 【发布时间】:2021-12-10 17:06:00 【问题描述】:我有一个问题正在努力解决。 鉴于以下情况:
包含二维点的数组all_points
,每个点表示为一个元组(x, y)
。
一个数组musthave_points
,包含all_points
中的点的索引。
一个整数m
,与m < len(all_points)
。
返回一个矩形列表,其中一个矩形由一个包含其4个顶点((x0, y0), (x1, y1), (x2, y2), (x3, y3))
的元组表示,每个矩形必须满足以下条件:
-
包含来自
all_points
的m
点,这些m
点必须完全位于矩形内,即不在矩形的任何4 个边缘上。
包含来自musthave_points
的所有点。如果musthave_points
为空列表,则矩形只需要满足第一个条件即可。
如果没有这样的矩形,则返回一个空列表。如果两个矩形包含相同的点子集并且输出中不应有“相同”的矩形,则它们被认为是“相同的”。
注意:一种简单的暴力破解解决方案是首先生成m
点的所有组合,每个组合都包含来自musthave_points
的所有点。对于每个组合,创建一个覆盖组合中所有点的矩形。然后计算矩形内的点数,如果点数为m
,则为有效矩形。
但是该解决方案以阶乘时间复杂度运行。你能想出比这更快的方法吗?
我已经实现了如下所示的蛮力,但是速度非常慢。
import itertools
import numpy as np
import cv2
import copy
import sys
from shapely.geometry import Point
from shapely.geometry.polygon import Polygon
# Credit: https://github.com/dbworth/minimum-area-bounding-rectangle/blob/master/python/min_bounding_rect.py
def minBoundingRect(hull_points_2d):
#print "Input convex hull points: "
#print hull_points_2d
# Compute edges (x2-x1,y2-y1)
edges = np.zeros((len(hull_points_2d) - 1, 2)) # empty 2 column array
for i in range(len(edges)):
edge_x = hull_points_2d[i+1, 0] - hull_points_2d[i, 0]
edge_y = hull_points_2d[i+1, 1] - hull_points_2d[i, 1]
edges[i] = [edge_x,edge_y]
# Calculate edge angles atan2(y/x)
edge_angles = np.zeros((len(edges))) # empty 1 column array
for i in range(len(edge_angles)):
edge_angles[i] = np.math.atan2(edges[i,1], edges[i,0])
# Check for angles in 1st quadrant
for i in range(len(edge_angles)):
edge_angles[i] = np.abs(edge_angles[i] % (np.math.pi/2)) # want strictly positive answers
# Remove duplicate angles
edge_angles = np.unique(edge_angles)
# Test each angle to find bounding box with smallest area
min_bbox = (0, sys.maxsize, 0, 0, 0, 0, 0, 0) # rot_angle, area, width, height, min_x, max_x, min_y, max_y
for i in range(len(edge_angles) ):
R = np.array([[np.math.cos(edge_angles[i]), np.math.cos(edge_angles[i]-(np.math.pi/2))], [np.math.cos(edge_angles[i]+(np.math.pi/2)), np.math.cos(edge_angles[i])]])
# Apply this rotation to convex hull points
rot_points = np.dot(R, np.transpose(hull_points_2d)) # 2x2 * 2xn
# Find min/max x,y points
min_x = np.nanmin(rot_points[0], axis=0)
max_x = np.nanmax(rot_points[0], axis=0)
min_y = np.nanmin(rot_points[1], axis=0)
max_y = np.nanmax(rot_points[1], axis=0)
# Calculate height/width/area of this bounding rectangle
width = max_x - min_x
height = max_y - min_y
area = width*height
# Store the smallest rect found first (a simple convex hull might have 2 answers with same area)
if (area < min_bbox[1]):
min_bbox = (edge_angles[i], area, width, height, min_x, max_x, min_y, max_y)
# Re-create rotation matrix for smallest rect
angle = min_bbox[0]
R = np.array([[np.math.cos(angle), np.math.cos(angle-(np.math.pi/2))], [np.math.cos(angle+(np.math.pi/2)), np.math.cos(angle)]])
# Project convex hull points onto rotated frame
proj_points = np.dot(R, np.transpose(hull_points_2d)) # 2x2 * 2xn
#print "Project hull points are \n", proj_points
# min/max x,y points are against baseline
min_x = min_bbox[4]
max_x = min_bbox[5]
min_y = min_bbox[6]
max_y = min_bbox[7]
#print "Min x:", min_x, " Max x: ", max_x, " Min y:", min_y, " Max y: ", max_y
# Calculate center point and project onto rotated frame
center_x = (min_x + max_x)/2
center_y = (min_y + max_y)/2
center_point = np.dot([center_x, center_y], R)
#print "Bounding box center point: \n", center_point
# Calculate corner points and project onto rotated frame
corner_points = np.zeros((4,2)) # empty 2 column array
corner_points[0] = np.dot([max_x, min_y], R)
corner_points[1] = np.dot([min_x, min_y], R)
corner_points[2] = np.dot([min_x, max_y], R)
corner_points[3] = np.dot([max_x, max_y], R)
return (angle, min_bbox[1], min_bbox[2], min_bbox[3], center_point, corner_points) # rot_angle, area, width, height, center_point, corner_points
class PatchGenerator:
def __init__(self, all_points, musthave_points, m):
self.all_points = copy.deepcopy(all_points)
self.n = len(all_points)
self.musthave_points = copy.deepcopy(musthave_points)
self.m = m
@staticmethod
def create_rectangle(points):
rot_angle, area, width, height, center_point, corner_points = minBoundingRect(points)
return corner_points
@staticmethod
def is_point_inside_rectangle(rect, point):
pts = Point(*point)
polygon = Polygon(rect)
return polygon.contains(pts)
def check_valid_rectangle(self, rect, the_complement):
# checking if the rectangle contains any other point from `the_complement`
for point in the_complement:
if self.is_point_inside_rectangle(rect, point):
return False
return True
def generate(self):
rects = []
# generate all combinations of m points, including points from musthave_points
the_rest_indices = list(set(range(self.n)).difference(self.musthave_points))
comb_indices = itertools.combinations(the_rest_indices, self.m - len(self.musthave_points))
comb_indices = [self.musthave_points + list(inds) for inds in comb_indices]
# for each combination
for comb in comb_indices:
comb_points = np.array(self.all_points)[comb]
## create the rectangle that covers all m points
rect = self.create_rectangle(comb_points)
## check if the rectangle is valid
the_complement_indices = list(set(range(self.n)).difference(comb))
the_complement_points = list(np.array(self.all_points)[the_complement_indices])
if self.check_valid_rectangle(rect, the_complement_points):
rects.append([comb, rect]) # indices of m points and 4 vertices of the valid rectangle
return rects
if __name__ == '__main__':
all_points = [[47.43, 20.5 ], [47.76, 43.8 ], [47.56, 23.74], [46.61, 23.73], [47.49, 18.94], [46.95, 25.29], [54.31, 23.5], [48.07, 17.77],
[48.2 , 34.87], [47.24, 22.07], [47.32, 27.05], [45.56, 17.95], [41.29, 19.33], [45.48, 28.49], [42.94, 15.24], [42.05, 34.3 ],
[41.04, 26.3 ], [45.37, 21.17], [45.44, 24.78], [44.54, 43.89], [30.49, 26.79], [40.55, 22.81]]
musthave_points = [3, 5, 9]
m = 17
patch_generator = PatchGenerator(all_points, musthave_points, 17)
patches = patch_generator.generate()
【问题讨论】:
我假设只是随机选择m
点(或第一个 m
或其他)不会是保证可行的解决方案,因为包围这些点的矩形也可能包含其他点,在这种情况下它是无效的。这是一个正确的假设吗?
是的,如果你随机选取m
点,包围这些点的矩形可能包含其他点,这样的矩形是无效的。这就是为什么在我的幼稚解决方案中,在生成封闭矩形后,我必须检查在生成的矩形内的那些 (n - m)
点(其余点)中是否有任何点。
【参考方案1】:
每个这样的矩形都可以缩小到最小尺寸,这样它仍然包含相同的点。因此,您只需要检查这些最小的矩形。设 n 为点的总数。那么左边最多有 n 个可能的坐标,其他边也是如此。对于每对可能的左侧和右侧坐标,您可以对顶部和底部坐标进行线性扫描。最终时间复杂度为 O(n^3)。
【讨论】:
以上是关于生成包含多个 2D 点的矩形的快速算法的主要内容,如果未能解决你的问题,请参考以下文章