Алгоритм нахождения прямоугольника минимальной площади для заданных точек для вычисления длины большой и малой оси
У меня есть набор точек (черные точки в значении географических координат), полученных из выпуклой оболочки (синий) многоугольника (красный). см. рисунок: ![enter image description here]()
[(560023.44957588764,6362057.3904932579),
(560023.44957588764,6362060.3904932579),
(560024.44957588764,6362063.3904932579),
(560026.94957588764,6362068.3904932579),
(560028.44957588764,6362069.8904932579),
(560034.94957588764,6362071.8904932579),
(560036.44957588764,6362071.8904932579),
(560037.44957588764,6362070.3904932579),
(560037.44957588764,6362064.8904932579),
(560036.44957588764,6362063.3904932579),
(560034.94957588764,6362061.3904932579),
(560026.94957588764,6362057.8904932579),
(560025.44957588764,6362057.3904932579),
(560023.44957588764,6362057.3904932579)]
Мне нужно вычислить основную и второстепенную длину оси, выполнив следующие действия (образует этот пост записи в R-проекте и в Java), или после этого примера процедуры
![enter image description here]()
- Вычислить выпуклую оболочку облака.
- Для каждого края выпуклой оболочки: 2a. вычислить ориентацию края, 2b. поверните выпуклый корпус, используя эту ориентацию, чтобы легко вычислить площадь ограничивающего прямоугольника с минимумом/максимумом x/y повернутого выпуклого корпуса, 2с. Сохраните ориентацию, соответствующую найденной минимальной площади,
- Вернуть прямоугольник, соответствующий минимальной найденной площади.
После этого мы знаем угол Theta (представляющий ориентацию ограничивающего прямоугольника относительно оси Y изображения). Минимум и максимум a и b по всем граничным точкам находятся:
- a (xi, yi) = xi * cos Theta + yi sin Theta
- b (xi, yi) = xi * sin Theta + yi cos Theta
Значения (a_max - a_min) и (b_max - b_min) определяют длину и ширину, соответственно, ограничительного прямоугольника для направления Theta.
![enter image description here]()
Ответы
Ответ 1
Учитывая упорядоченный по часовой стрелке список n точек в выпуклой оболочке множества точек, это операция O (n) для нахождения прямоугольника, вмещающего минимальную область. (Для нахождения выпуклого корпуса в O (n log n) время см. activestate.com рецепт 66527 или см. Довольно компактный код сканирования Graham на tixxit.net.)
В следующей программе python используются методы, аналогичные методам обычного алгоритма O (n) для вычисления максимального диаметра выпуклого многоугольника. То есть он поддерживает три индекса (iL, iP, iR) до самых левых, противоположных и самых правых точек относительно данной базовой линии. Каждый индекс проходит через не более n пунктов. Пример вывода из программы показан ниже (с добавленным заголовком):
i iL iP iR Area
0 6 8 0 203.000
1 6 8 0 211.875
2 6 8 0 205.800
3 6 10 0 206.250
4 7 12 0 190.362
5 8 0 1 203.000
6 10 0 4 201.385
7 0 1 6 203.000
8 0 3 6 205.827
9 0 3 6 205.640
10 0 4 7 187.451
11 0 4 7 189.750
12 1 6 8 203.000
Например, запись я = 10 указывает, что относительно базовой линии от точки 10 до 11 точка 0 является самой левой, точка 4 противоположна, а точка 7 является самой правой, что дает площадь 187,451 единиц.
Обратите внимание, что код использует mostfar()
для продвижения каждого индекса. Параметры mx, my
для mostfar()
указывают, для какой экстремальности нужно проверить; в качестве примера, с помощью mx,my = -1,0
, mostfar()
будет пытаться максимизировать -rx (где rx - повернутый x точки), тем самым находя самую левую точку. Обратите внимание, что при использовании if mx*rx + my*ry >= best
в неточной арифметике, вероятно, следует использовать эпсилонную надбавку: когда корпус имеет множество точек, ошибка округления может быть проблемой и заставляет метод некорректно не продвигать индекс.
Код показан ниже. Данные о корпусе берутся из вопроса выше, с отмененными значительными смещениями и идентичными десятичными знаками.
#!/usr/bin/python
import math
hull = [(23.45, 57.39), (23.45, 60.39), (24.45, 63.39),
(26.95, 68.39), (28.45, 69.89), (34.95, 71.89),
(36.45, 71.89), (37.45, 70.39), (37.45, 64.89),
(36.45, 63.39), (34.95, 61.39), (26.95, 57.89),
(25.45, 57.39), (23.45, 57.39)]
def mostfar(j, n, s, c, mx, my): # advance j to extreme point
xn, yn = hull[j][0], hull[j][1]
rx, ry = xn*c - yn*s, xn*s + yn*c
best = mx*rx + my*ry
while True:
x, y = rx, ry
xn, yn = hull[(j+1)%n][0], hull[(j+1)%n][1]
rx, ry = xn*c - yn*s, xn*s + yn*c
if mx*rx + my*ry >= best:
j = (j+1)%n
best = mx*rx + my*ry
else:
return (x, y, j)
n = len(hull)
iL = iR = iP = 1 # indexes left, right, opposite
pi = 4*math.atan(1)
for i in range(n-1):
dx = hull[i+1][0] - hull[i][0]
dy = hull[i+1][1] - hull[i][1]
theta = pi-math.atan2(dy, dx)
s, c = math.sin(theta), math.cos(theta)
yC = hull[i][0]*s + hull[i][1]*c
xP, yP, iP = mostfar(iP, n, s, c, 0, 1)
if i==0: iR = iP
xR, yR, iR = mostfar(iR, n, s, c, 1, 0)
xL, yL, iL = mostfar(iL, n, s, c, -1, 0)
area = (yP-yC)*(xR-xL)
print ' {:2d} {:2d} {:2d} {:2d} {:9.3f}'.format(i, iL, iP, iR, area)
Примечание. Чтобы получить длину и ширину прямоугольника, вмещающего минимальную область, измените приведенный выше код, как показано ниже. Это создаст выходную строку, например
Min rectangle: 187.451 18.037 10.393 10 0 4 7
в котором второе и третье числа указывают длину и ширину прямоугольника, а четыре целых числа дают номера индексов точек, лежащих на его сторонах.
# add after pi = ... line:
minRect = (1e33, 0, 0, 0, 0, 0, 0) # area, dx, dy, i, iL, iP, iR
# add after area = ... line:
if area < minRect[0]:
minRect = (area, xR-xL, yP-yC, i, iL, iP, iR)
# add after print ... line:
print 'Min rectangle:', minRect
# or instead of that print, add:
print 'Min rectangle: ',
for x in ['{:3d} '.format(x) if isinstance(x, int) else '{:7.3f} '.format(x) for x in minRect]:
print x,
print
Ответ 2
Я только что реализовал это сам, поэтому решил, что отброшу свою версию для просмотра другими:
import numpy as np
from scipy.spatial import ConvexHull
def minimum_bounding_rectangle(points):
"""
Find the smallest bounding rectangle for a set of points.
Returns a set of points representing the corners of the bounding box.
:param points: an nx2 matrix of coordinates
:rval: an nx2 matrix of coordinates
"""
from scipy.ndimage.interpolation import rotate
pi2 = np.pi/2.
# get the convex hull for the points
hull_points = points[ConvexHull(points).vertices]
# calculate edge angles
edges = np.zeros((len(hull_points)-1, 2))
edges = hull_points[1:] - hull_points[:-1]
angles = np.zeros((len(edges)))
angles = np.arctan2(edges[:, 1], edges[:, 0])
angles = np.abs(np.mod(angles, pi2))
angles = np.unique(angles)
# find rotation matrices
# XXX both work
rotations = np.vstack([
np.cos(angles),
np.cos(angles-pi2),
np.cos(angles+pi2),
np.cos(angles)]).T
# rotations = np.vstack([
# np.cos(angles),
# -np.sin(angles),
# np.sin(angles),
# np.cos(angles)]).T
rotations = rotations.reshape((-1, 2, 2))
# apply rotations to the hull
rot_points = np.dot(rotations, hull_points.T)
# find the bounding points
min_x = np.nanmin(rot_points[:, 0], axis=1)
max_x = np.nanmax(rot_points[:, 0], axis=1)
min_y = np.nanmin(rot_points[:, 1], axis=1)
max_y = np.nanmax(rot_points[:, 1], axis=1)
# find the box with the best area
areas = (max_x - min_x) * (max_y - min_y)
best_idx = np.argmin(areas)
# return the best box
x1 = max_x[best_idx]
x2 = min_x[best_idx]
y1 = max_y[best_idx]
y2 = min_y[best_idx]
r = rotations[best_idx]
rval = np.zeros((4, 2))
rval[0] = np.dot([x1, y2], r)
rval[1] = np.dot([x2, y2], r)
rval[2] = np.dot([x2, y1], r)
rval[3] = np.dot([x1, y1], r)
return rval
Вот четыре разных примера этого в действии. Для каждого примера я сгенерировал 4 случайные точки и нашел ограничительную рамку.
![examples]()
(редактирование @heltonbiker)
Простой код для построения:
import matplotlib.pyplot as plt
for n in range(10):
points = np.random.rand(4,2)
plt.scatter(points[:,0], points[:,1])
bbox = minimum_bounding_rectangle(points)
plt.fill(bbox[:,0], bbox[:,1], alpha=0.2)
plt.axis('equal')
plt.show()
(редактирование)
Это относительно быстро для этих образцов на 4 балла:
>>> %timeit minimum_bounding_rectangle(a)
1000 loops, best of 3: 245 µs per loop
Ссылка на тот же ответ на gis.stackexchange для моей справки.
Ответ 3
Есть модуль, который делает это уже на github. https://github.com/BebeSparkelSparkel/MinimumBoundingBox
Все, что вам нужно сделать, это вставить ваше облако точек в него.
from MinimumBoundingBox import minimum_bounding_box
points = ( (1,2), (5,4), (-1,-3) )
bounding_box = minimum_bounding_box(points) # returns namedtuple
Вы можете получить длину главной и вспомогательной оси:
minor = min(bounding_box.length_parallel, bounding_box.length_orthogonal)
major = max(bounding_box.length_parallel, bounding_box.length_orthogonal)
Он также возвращает площадь, центр прямоугольника, угол прямоугольника и угловые точки.
Ответ 4
OpenCV имеет это. Смотрите это:
http://docs.opencv.org/trunk/dd/d49/tutorial_py_contour_features.html
7.b. Вращающийся прямоугольник
С
cv2.minAreaRect(cnt)
Вы можете получить длину и ширину прямоугольника, а также его угол. Вы также можете вычислить углы, если хотите их нарисовать.
Ответ 5
Я нашел рецепт для вычисления выпуклых оболочек.
Если мы говорим о "полных решениях" (одна функция для целых вещей), я нашел только arcpy
, который является частью ArcGIS
. Он обеспечивает MinimumBoundingGeometry_management
функцию, которая выглядит так, как вы ищите. Но это не с открытым исходным кодом. К сожалению, нет библиотек с открытым исходным кодом на основе python.