Знаходження прямокутника мінімальної площі для заданих точок?


71

Як ви бачите на малюнку, питання:

Як знайти прямокутник мінімальної площі (MAR), встановлений у заданих точках?

і підтримуючим питанням є:

Чи є якесь аналітичне рішення проблеми?

(Розробка питання полягає в тому, щоб встановити вікно (3D) до кластера точок у тривимірній хмарі точок.)

На першому етапі я пропоную знайти опуклий корпус для точок, які реформують проблему (видаляючи ці точки, не задіяні у вирішенні), щоб: встановити MAR на полігон. Необхідний метод забезпечить X ( центр прямокутника ), D ( два виміри ) і A ( кут ).


Моя пропозиція щодо рішення:

  • Знайдіть центроїд багатокутника (див. Пошук центру геометрії об'єкта? )
  • [S] Встановіть простий прямокутник, тобто паралельний осям X і Y
    • ви можете використовувати minmaxфункцію для X і Y заданих точок (наприклад, вершин багатокутника)
  • Зберігайте площу встановленого прямокутника
  • Оберніть багатокутник навколо центральної, наприклад, на 1 градус
  • Повторіть від [S] до повного обертання
  • Повідомте як результат кута мінімальної площі

Мені це здається перспективним, проте існують такі проблеми:

  • вибір гарної роздільної здатності для зміни кута може бути складним,
  • вартість розрахунку висока,
  • рішення не аналітичне, а експериментальне.

введіть тут опис зображення

Відповіді:


45

Так, є аналітичне рішення цієї проблеми. Алгоритм, який ви шукаєте, відомий в узагальненні полігонів як "найменший навколишній прямокутник".

Алгоритм, який ви описуєте, чудовий, але для вирішення перелічених проблем ви можете використовувати той факт, що орієнтація MAR є такою ж, як і одна з ребер опуклого корпусу точки хмари . Тому потрібно просто перевірити орієнтацію опуклих країв корпусу. Ти повинен:

  • Обчисліть опуклий корпус хмари.
  • Для кожного краю опуклого корпусу:
    • обчислити орієнтацію краю (з арктаном),
    • обертати опуклий корпус, використовуючи цю орієнтацію, щоб легко обчислити обмежувальну площу прямокутника з min / max x / y обертового опуклого корпусу,
    • Зберігати орієнтацію, що відповідає мінімальній знайденій площі,
  • Поверніть прямокутник, що відповідає мінімальній знайденій площі.

Приклад реалізації в java є тут .

У 3D застосовується те саме, за винятком:

  • Опуклий корпус буде об'ємом,
  • Тестованими орієнтаціями будуть орієнтації (у 3D) опуклих граней корпусу.

Удачі!


11
+1 Дуже приємна відповідь! Я хотів би зазначити, що фактичне обертання хмари непотрібне. По-перше - ти, мабуть, мав на увазі це - треба вважати лише вершини корпусу. По-друге, замість обертання представляйте поточну сторону як пару ортогональних одиничних векторів. Якщо взяти їх точкові вироби з координатами вершини корпусу (що може бути виконано як операція з однією матрицею), то повертаються координати: тригонометрія не потрібна, швидка та ідеально точна.
whuber

2
Дякуємо за посилання. Дійсно, обертання лише для # ребер робить запропонований метод дуже ефективним. Я міг знайти папір, що це підтверджує. Хоча я позначив це як відповідь на вірність першій хорошій відповіді (не можу вибрати дві / більше чудових відповідей :(), я хотів би рекомендувати настійно розглянути повну відповідь Ваубера нижче. Ефективність даного методу (уникаючи ротацій!) неймовірно, і вся процедура - це лише декілька рядків коду. Мені це легко перекладається на Python :)
Розробник

Чи можете ви оновити посилання на реалізацію Java?
Майра

так, це робиться!
липень

1
Зауважте, що розширення на 3D трохи складніше, ніж це. Кожна грань 3D опуклого корпусу визначає можливу орієнтацію однієї грані обмежувальної коробки, але не орієнтацію граней, перпендикулярних до неї. Проблема того, як повернути коробку в цій площині, стає задачею двовимірного мінімуму-прямокутника в площині цієї грані. Для кожного краю опуклого корпусу хмари, проектованої на задану площину, ви можете намалювати обмежувальний ящик, який надасть вам різний об'єм у 3D.
Буде чи

40

Щоб доповнити чудове рішення @ julien, ось робоча реалізація в R, яка може слугувати псевдокодом для керівництва будь-якою GIS-специфічною реалізацією (або R, звичайно, застосовуватися безпосередньо ). Введення - це масив точкових координат. Вихід (значення mbr) - це масив вершин мінімального обмежувального прямокутника (при цьому перша повторюється для його закриття). Зверніть увагу на повну відсутність будь-яких тригонометричних обчислень.

MBR <- function(p) {
  # Analyze the convex hull edges     
  a <- chull(p)                                   # Indexes of extremal points
  a <- c(a, a[1])                                 # Close the loop
  e <- p[a[-1],] - p[a[-length(a)], ]             # Edge directions
  norms <- sqrt(rowSums(e^2))                     # Edge lengths
  v <- e / norms                                  # Unit edge directions
  w <- cbind(-v[,2], v[,1])                       # Normal directions to the edges

  # Find the MBR
  vertices <- p[a, ]                              # Convex hull vertices
  x <- apply(vertices %*% t(v), 2, range)         # Extremes along edges
  y <- apply(vertices %*% t(w), 2, range)         # Extremes normal to edges
  areas <- (y[1,]-y[2,])*(x[1,]-x[2,])            # Areas
  k <- which.min(areas)                           # Index of the best edge (smallest area)

  # Form a rectangle from the extremes of the best edge
  cbind(x[c(1,2,2,1,1),k], y[c(1,1,2,2,1),k]) %*% rbind(v[k,], w[k,])
}

Ось приклад його використання:

# Create sample data
set.seed(23)
p <- matrix(rnorm(20*2), ncol=2)                 # Random (normally distributed) points
mbr <- MBR(points)

# Plot the hull, the MBR, and the points
limits <- apply(mbr, 2, range) # Plotting limits
plot(p[(function(x) c(x, x[1]))(chull(p)), ], 
     type="l", asp=1, bty="n", xaxt="n", yaxt="n",
     col="Gray", pch=20, 
     xlab="", ylab="",
     xlim=limits[,1], ylim=limits[,2])                # The hull
lines(mbr, col="Blue", lwd=3)                         # The MBR
points(points, pch=19)                                # The points

MBR

Хронометраж обмежений швидкістю алгоритму опуклого корпусу, оскільки кількість вершин у корпусі майже завжди набагато менше загальної. Більшість алгоритмів опуклого корпусу є асимптотично O (n * log (n)) для n точок: ви можете обчислити майже так само швидко, як і зможете прочитати координати.


+1 Яке дивовижне рішення! Така ідея приходить лише після довгого досвіду. Відтепер мені буде цікаво оптимізувати свої існуючі коди, надихаючись на цю чудову відповідь.
Розробник

Я хотів би, щоб я міг підкреслити це двічі. Я вивчаю R, а ваші відповіді - постійне джерело натхнення.
Джон Пауелл

1
@retrovius Обмежувальний прямокутник набору (повернутих) точок визначається чотирма числами: найменша x координата, найбільша x координата, найменша y координата та найбільша y координата. Саме так посилаються на "крайності по краях".
whuber

1
@retrovius Походження не грає ніякої ролі в цих обчисленнях, тому що все базується на відмінностях координат, за винятком кінця, де найкращий прямокутник, обчислений у обернених координатах, просто повертається назад. Хоча розумна ідея використовувати систему координат, у якій походження близьке до точок (щоб мінімізувати втрати точності з плаваючою точкою), походження в іншому випадку не має значення.
whuber

1
@Retrovius Ви можете інтерпретувати це як властивість обертання: а саме матриця обертання є ортогональною. Таким чином, одним із видів ресурсу було б вивчення лінійної алгебри (загалом) або аналітичної евклідової геометрії (конкретно). Однак я виявив, що найпростішим способом розібратися з обертаннями (і перекладами та масштабуванням) у площині є перегляд точок як складних чисел: обертання просто здійснюються шляхом множення значень на одиничні довжини чисел.
whuber

8

Я щойно реалізував це сам і опублікував свою відповідь на StackOverflow , але зрозумів, що перекину свою версію сюди, щоб інші бачили :

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 випадкових точки і знайшов обмежувальне поле.

введіть тут опис зображення

Це порівняно швидко для цих зразків на 4 бали:

>>> %timeit minimum_bounding_rectangle(a)
1000 loops, best of 3: 245 µs per loop

Привіт JesseBuesking, чи вмієш ти генерувати прямокутники з кутами 90 градусів? Ваш код чудово працює для отримання паралелограмів, але в моєму конкретному випадку використання потрібні кути 90 град. Чи можете ви порекомендувати, як ваш код можна змінити, щоб досягти цього? Дякую!
Надер Алексан

@NaderAlexan Якщо ви запитуєте, чи може він обробляти квадрати, то так, звичайно, можна! Я щойно спробував це на одиничному квадраті points = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]), а вихід - array([[1.00000000e+00, 6.12323400e-17], [0.00000000e+00, 0.00000000e+00], [6.12323400e-17, 1.00000000e+00], [1.00000000e+00, 1.00000000e+00]])саме одиничний квадрат (включаючи деякі помилки округлення плаваючої точки). Примітка: квадрат - це просто прямокутник з рівними сторонами, тому я припускаю, що якщо він може обробити квадрат, він узагальнює всі прямокутники.
JesseBuesking

Спасибі за вашу відповідь. Так, він працює чудово, але я намагаюся змусити його завжди створювати прямокутник (4 сторони з кутом 90 градусів для кожної сторони) над будь-яким іншим 4-стороннім багатокутником, хоча в деяких випадках він дає прямокутник. щоб бути постійним обмеженням, чи знаєте ви, як змінити код, щоб додати це обмеження? Дякую!
Надер Алексан

Може бути, gis.stackexchange.com/a/22934/48041 може навести вас на вирішення, враховуючи, що їх відповідь має таке обмеження? Як тільки ви знайдете рішення, ви повинні внести його, оскільки я впевнений, що інші знайдуть його корисним. Удачі!
JesseBuesking

7

У Whitebox GAT є інструмент ( http://www.uoguelph.ca/~hydrogeo/Whitebox/ ) під назвою Мінімальне обмежувальне поле для вирішення цієї точної проблеми. Тут також є мінімальний інструмент для опуклого корпусу. Деякі інструменти в наборі інструментів Patch Shape, наприклад, орієнтація та подовження пластиру, засновані на знаходженні мінімальної рамки обмеження.

введіть тут опис зображення


4

Я натрапив на цю нитку, шукаючи рішення Python для прямокутника, що обмежує мінімальну площу.

Ось моя реалізація , для якої результати були підтверджені Matlab.

Тестовий код включений для простих багатокутників, і я використовую його для пошуку 2D мінімального обмежувального поля та напрямків осей для 3D PointCloud.


Видалено вашу відповідь?
Поль Ріхтер

@PaulRichter мабуть. Джерело було тут github.com/dbworth/minimum-area-bounding-rectangle, хоча
1818

3

Спасибі @ відповіді. Це чудове рішення, але повільне для великих хмарних хмар. Я знайшов convhullnфункцію в пакеті R geometryнабагато швидше (138 с проти 0,03 с на 200000 балів). Я вставив сюди свої коди для тих, хто цікавий для швидшого рішення.

library(alphahull)                                  # Exposes ashape()
MBR <- function(points) {
    # Analyze the convex hull edges                       
    a <- ashape(points, alpha=1000)                 # One way to get a convex hull...
    e <- a$edges[, 5:6] - a$edges[, 3:4]            # Edge directions
    norms <- apply(e, 1, function(x) sqrt(x %*% x)) # Edge lengths
    v <- diag(1/norms) %*% e                        # Unit edge directions
    w <- cbind(-v[,2], v[,1])                       # Normal directions to the edges

    # Find the MBR
    vertices <- (points) [a$alpha.extremes, 1:2]    # Convex hull vertices
    minmax <- function(x) c(min(x), max(x))         # Computes min and max
    x <- apply(vertices %*% t(v), 2, minmax)        # Extremes along edges
    y <- apply(vertices %*% t(w), 2, minmax)        # Extremes normal to edges
    areas <- (y[1,]-y[2,])*(x[1,]-x[2,])            # Areas
    k <- which.min(areas)                           # Index of the best edge (smallest area)

    # Form a rectangle from the extremes of the best edge
    cbind(x[c(1,2,2,1,1),k], y[c(1,1,2,2,1),k]) %*% rbind(v[k,], w[k,])
}

MBR2 <- function(points) {
    tryCatch({
        a2 <- geometry::convhulln(points, options = 'FA')

        e <- points[a2$hull[,2],] - points[a2$hull[,1],]            # Edge directions
        norms <- apply(e, 1, function(x) sqrt(x %*% x)) # Edge lengths

        v <- diag(1/norms) %*% as.matrix(e)                        # Unit edge directions


        w <- cbind(-v[,2], v[,1])                       # Normal directions to the edges

        # Find the MBR
        vertices <- as.matrix((points) [a2$hull, 1:2])    # Convex hull vertices
        minmax <- function(x) c(min(x), max(x))         # Computes min and max
        x <- apply(vertices %*% t(v), 2, minmax)        # Extremes along edges
        y <- apply(vertices %*% t(w), 2, minmax)        # Extremes normal to edges
        areas <- (y[1,]-y[2,])*(x[1,]-x[2,])            # Areas
        k <- which.min(areas)                           # Index of the best edge (smallest area)

        # Form a rectangle from the extremes of the best edge
        as.data.frame(cbind(x[c(1,2,2,1,1),k], y[c(1,1,2,2,1),k]) %*% rbind(v[k,], w[k,]))
    }, error = function(e) {
        assign('points', points, .GlobalEnv)
        stop(e)  
    })
}


# Create sample data
#set.seed(23)
points <- matrix(rnorm(200000*2), ncol=2)                 # Random (normally distributed) points
system.time(mbr <- MBR(points))
system.time(mmbr2 <- MBR2(points))


# Plot the hull, the MBR, and the points
limits <- apply(mbr, 2, function(x) c(min(x),max(x))) # Plotting limits
plot(ashape(points, alpha=1000), col="Gray", pch=20, 
     xlim=limits[,1], ylim=limits[,2])                # The hull
lines(mbr, col="Blue", lwd=10)                         # The MBR
lines(mbr2, col="red", lwd=3)                         # The MBR2
points(points, pch=19)   

Два методи отримують однакову відповідь (приклад для 2000 балів):

введіть тут опис зображення


Чи можна розширити цю реалізацію на 3d-простір (тобто знайти поле мінімальної гучності, яке включає всі задані точки в 3d-просторі)?
Сашко

0

Я просто рекомендую вбудовану функцію OpenCV minAreaRect, яка знаходить повернутий прямокутник мінімальної площі, що охоплює набір вхідної 2D точки. Щоб дізнатися, як використовувати цю функцію, можна звернутися до цього підручника .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.