Finden Sie ein Minimum-Flächen-Rechteck für gegebene Punkte?


71

Wie Sie in der Abbildung sehen, lautet die Frage:

Wie finde ich das Minimum-Area-Rectangle (MAR), das auf die gegebenen Punkte passt?

und eine unterstützende Frage ist:

Gibt es eine analytische Lösung für das Problem?

(Eine Weiterentwicklung der Frage wird darin bestehen, ein Kästchen (3D) an einen Punktcluster in einer 3D-Punktwolke anzupassen.)

Als erstes schlage ich vor, die konvexe Hülle für die Punkte zu finden, die das Problem reformieren (indem diese Punkte entfernt werden, die nicht in der Lösung enthalten sind), um: einen MAR an ein Polygon anzupassen. Die erforderliche Methode liefert X ( Mittelpunkt des Rechtecks ), D ( zwei Dimensionen ) und A ( Winkel ).


Mein Lösungsvorschlag:

  • Ermitteln des Schwerpunkts des Polygons (siehe Ermitteln des Mittelpunkts der Objektgeometrie? )
  • [S] Passen Sie ein einfaches angepasstes Rechteck an, dh parallel zu den Achsen X und Y
    • Sie können die minmaxFunktion für X und Y der angegebenen Punkte verwenden (z. B. die Eckpunkte des Polygons).
  • Speichern Sie den Bereich des angepassten Rechtecks
  • Drehen Sie das Polygon um z. B. 1 Grad um den Schwerpunkt
  • Wiederholen Sie diesen Vorgang ab [S], bis eine vollständige Drehung erfolgt ist
  • Geben Sie als Ergebnis den Winkel der minimalen Fläche an

Es scheint mir vielversprechend, aber die folgenden Probleme bestehen:

  • Die Wahl einer guten Auflösung für die Winkeländerung könnte eine Herausforderung sein.
  • der Rechenaufwand ist hoch,
  • Die Lösung ist nicht analytisch, sondern experimentell.

Bildbeschreibung hier eingeben

Antworten:


45

Ja, es gibt eine analytische Lösung für dieses Problem. Der gesuchte Algorithmus ist in der Polygonverallgemeinerung als "kleinstes umgebendes Rechteck" bekannt.

Der von Ihnen beschriebene Algorithmus ist in Ordnung, aber um die von Ihnen aufgelisteten Probleme zu lösen, können Sie die Tatsache verwenden, dass die Ausrichtung des MAR mit der einer Kante der konvexen Hülle der Punktwolke übereinstimmt . Sie müssen also nur die Ausrichtungen der konvexen Rumpfkanten testen. Du solltest:

  • Berechnen Sie die konvexe Hülle der Wolke.
  • Für jede Kante der konvexen Hülle:
    • Berechnen Sie die Kantenausrichtung (mit arctan),
    • Drehen Sie den konvexen Rumpf mit dieser Ausrichtung, um auf einfache Weise den Begrenzungsrechteckbereich mit min / max von x / y des gedrehten konvexen Rumpfs zu berechnen.
    • Speichern Sie die Ausrichtung entsprechend der gefundenen Mindestfläche.
  • Geben Sie das Rechteck zurück, das dem gefundenen Mindestbereich entspricht.

Ein Beispiel für eine Implementierung in Java zur Verfügung steht dort .

In 3D gilt das Gleiche, außer:

  • Der konvexe Rumpf wird ein Volumen sein,
  • Die getesteten Orientierungen sind die Orientierungen (in 3D) der konvexen Rumpfflächen.

Viel Glück!


11
+1 Sehr schöne Antwort! Ich möchte darauf hinweisen, dass eine tatsächliche Drehung der Wolke nicht erforderlich ist. Zuerst - Sie meinten das wahrscheinlich - müssen nur die Scheitelpunkte des Rumpfes berücksichtigt werden. Zweitens, anstatt sich zu drehen, wird die aktuelle Seite als Paar orthogonaler Einheitsvektoren dargestellt. Wenn ihre Punktprodukte mit den Rumpfscheitelpunktkoordinaten (die als Einzelmatrixoperation ausgeführt werden könnten) berechnet werden, ergeben sich die gedrehten Koordinaten: keine Trigonometrie erforderlich, schnell und perfekt genau.
whuber

2
Danke für die Links. In der Tat ist das vorgeschlagene Verfahren sehr effizient, wenn nur für die Anzahl der Kanten gedreht wird. Ich konnte feststellen, dass das Papier das beweist. Obwohl ich dies als die Antwort für die Loyalität zur ersten guten Antwort markiert habe (ich kann nicht zwei / mehr gute Antworten auswählen :(), möchte ich empfehlen, die vollständige Antwort von whuber weiter unten zu berücksichtigen . Die Effizienz der dort angegebenen Methode (Vermeidung von Rotationen!) Ist Unglaublich, und die ganze Prozedur besteht nur aus ein paar Zeilen Code. Für mich ist sie leicht in Python zu übersetzen :)
Entwickler

Können Sie bitte den Java-Implementierungslink aktualisieren?
Myra

ja es ist geschafft
Juli

1
Beachten Sie, dass die Erweiterung in 3D etwas komplizierter ist. Jede Fläche der konvexen 3D-Hülle definiert eine mögliche Ausrichtung einer Fläche des Begrenzungsrahmens, jedoch nicht die Ausrichtung von Flächen senkrecht dazu. Das Problem, wie die Box in dieser Ebene gedreht werden soll, wird zum 2D-Problem des minimalen Begrenzungsrechtecks ​​in der Ebene dieser Fläche. Für jede Kante der konvexen Hülle der Wolke, die auf eine bestimmte Ebene projiziert wird, können Sie einen Begrenzungsrahmen zeichnen, der Ihnen in 3D ein anderes Volumen verleiht.
Will

40

Als Ergänzung zu der großartigen Lösung von @ julien gibt es hier eine funktionierende Implementierung in R, die als Pseudocode für jede GIS-spezifische Implementierung dienen kann (oder Rnatürlich direkt in angewendet werden kann). Die Eingabe ist ein Array von Punktkoordinaten. Die Ausgabe (der Wert von mbr) ist ein Array der Eckpunkte des minimalen Begrenzungsrechtecks ​​(wobei der erste wiederholt wird, um es zu schließen). Beachten Sie, dass keine trigonometrischen Berechnungen vorliegen.

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,])
}

Hier ist ein Beispiel für seine Verwendung:

# 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

Das Timing wird durch die Geschwindigkeit des konvexen Rumpfalgorithmus begrenzt, da die Anzahl der Scheitelpunkte im Rumpf fast immer viel geringer ist als die Gesamtzahl. Die meisten konvexen Rumpfalgorithmen sind asymptotisch O (n * log (n)) für n Punkte: Sie können fast so schnell berechnen, wie Sie die Koordinaten lesen können.


+1 Was für eine tolle Lösung! Eine solche Idee kommt erst nach langen Erfahrungen. Von nun an bin ich gespannt darauf, meine vorhandenen Codes zu optimieren und mich von dieser großartigen Antwort inspirieren zu lassen.
Entwickler

Ich wünschte, ich könnte das zweimal verbessern. Ich lerne R und Ihre Antworten sind eine ständige Quelle der Inspiration.
John Powell

1
@retrovius Das Begrenzungsrechteck eines Satzes (gedrehter) Punkte wird durch vier Zahlen bestimmt: die kleinste x-Koordinate, die größte x-Koordinate, die kleinste y-Koordinate und die größte y-Koordinate. Darauf beziehen sich die "Extreme entlang der Kanten".
whuber

1
@retrovius Der Ursprung spielt bei diesen Berechnungen keine Rolle, da alles auf Koordinatenunterschieden basiert, außer am Ende, wo das beste Rechteck, das in gedrehten Koordinaten berechnet wurde, einfach zurückgedreht wird. Obwohl es eine gute Idee ist, ein Koordinatensystem zu verwenden, bei dem der Ursprung nahe an den Punkten liegt (um den Verlust an Gleitkommapräzision zu minimieren), ist der Ursprung ansonsten irrelevant.
whuber

1
@Retrovius Sie können dies als eine Eigenschaft von Rotationen interpretieren: Die Matrix einer Rotation ist nämlich orthogonal. Eine Art von Ressource wäre daher eine Untersuchung der linearen Algebra (allgemein) oder der analytischen euklidischen Geometrie (speziell). Ich habe jedoch festgestellt, dass der einfachste Weg, mit Rotationen (und Umsetzungen und Neuskalierungen) in der Ebene umzugehen, darin besteht, die Punkte als komplexe Zahlen zu betrachten: Rotationen werden einfach durch Multiplizieren von Werten mit Längeneinheiten durchgeführt.
whuber

8

Ich habe das gerade selbst implementiert und meine Antwort auf StackOverflow gepostet , aber ich dachte, ich würde meine Version hier ablegen , damit andere sie sehen können:

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

Hier sind vier verschiedene Beispiele in Aktion. Für jedes Beispiel habe ich 4 zufällige Punkte generiert und den Begrenzungsrahmen gefunden.

Bildbeschreibung hier eingeben

Auch für diese Beispiele ist es in 4 Punkten relativ schnell:

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

Hallo JesseBuesking, können Sie Rechtecke mit 90-Grad-Ecken erzeugen? Ihr Code funktioniert hervorragend, um Parallelogramme zu erhalten, aber in meinem speziellen Anwendungsfall sind 90-Grad-Ecken erforderlich. Könnten Sie empfehlen, wie Ihr Code geändert werden kann, um dies zu erreichen? Vielen Dank!
Nader Alexan

@ Naderalexan Wenn du fragst, ob es mit Quadraten umgehen kann, dann kann es das mit Sicherheit! Ich habe es gerade mit einem Einheitsquadrat versucht points = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]), und die Ausgabe ist 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]])das Einheitsquadrat selbst (einschließlich einiger Gleitkomma-Rundungsfehler). Hinweis: Ein Quadrat ist nur ein Rechteck mit gleichen Seiten. Ich gehe also davon aus, dass es ein Quadrat verarbeiten kann, das auf alle Rechtecke verallgemeinert wird.
JesseBuesking

Vielen Dank für Ihre Antwort. Ja, es funktioniert hervorragend, aber ich versuche, es zu zwingen, immer ein Rechteck (4 Seiten mit 90-Grad-Winkeln für jede Seite) über einem anderen 4-seitigen Polygon zu erzeugen, obwohl es in bestimmten Fällen ein Rechteck erzeugt, wie es scheint, nicht Wissen Sie, wie Sie den Code ändern, um diese Einschränkung hinzuzufügen, um eine konstante Einschränkung zu sein? Vielen Dank!
Nader Alexan

Vielleicht führt Sie gis.stackexchange.com/a/22934/48041 zu einer Lösung, wenn die Antwort diese Einschränkung zu haben scheint? Sobald Sie eine Lösung gefunden haben, sollten Sie sie beitragen, da ich sicher bin, dass andere sie nützlich finden werden. Viel Glück!
JesseBuesking

7

In Whitebox GAT ( http://www.uoguelph.ca/~hydrogeo/Whitebox/ ) gibt es ein Tool namens Minimum Bounding Box, um genau dieses Problem zu lösen. Es gibt dort auch ein minimales Werkzeug für den konvexen Rumpf. Einige der Werkzeuge in der Patch-Form-Toolbox, z. B. Patch-Ausrichtung und -Dehnung, basieren auf der Ermittlung des minimalen Begrenzungsrahmens.

Bildbeschreibung hier eingeben


4

Ich bin auf diesen Thread gestoßen, als ich nach einer Python-Lösung für ein begrenzendes Rechteck mit minimaler Fläche gesucht habe.

Hier ist meine Implementierung , für die die Ergebnisse mit Matlab überprüft wurden.

Testcode ist für einfache Polygone enthalten, und ich verwende ihn, um den 2D-Mindestbegrenzungsrahmen und die Achsenrichtungen für eine 3D-Punktwolke zu finden.


Wurde Ihre Antwort gelöscht?
Paul Richter

@PaulRichter anscheinend. Die Quelle war hier github.com/dbworth/minimum-area-bounding-rectangle obwohl
sehe

3

Danke @ whubers Antwort. Es ist eine großartige Lösung, aber langsam für große Punktwolken. Ich fand, dass die convhullnFunktion im R-Paket geometryviel schneller ist (138 s gegenüber 0,03 s für 200000 Punkte). Ich habe hier meine Codes eingefügt, für die sich jemand für eine schnellere Lösung interessiert.

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)   

Zwei Methoden erhalten die gleiche Antwort (Beispiel für 2000 Punkte):

Bildbeschreibung hier eingeben


Ist es möglich, diese Implementierung auf den 3D-Raum auszudehnen (dh eine Box mit minimalem Volumen zu finden, die alle gegebenen Punkte im 3D-Raum enthält)?
Sasha

0

Ich empfehle einfach die eingebaute OpenCV-Funktion minAreaRect, die ein gedrehtes Rechteck des minimalen Bereichs findet, der den 2D-Eingabepunktsatz einschließt. Informationen zur Verwendung dieser Funktion finden Sie in diesem Lernprogramm .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.