Verilen puanlar için minimum alan-dikdörtgen bulmak?


71

Şekilde gördüğünüz gibi, soru şudur:

Verilen noktalara yerleştirilmiş minimum alan-dikdörtgen (MAR) nasıl bulunur?

ve destekleyici bir soru:

Problem için herhangi bir analitik çözüm var mı?

(Sorunun bir gelişimi, bir 3D nokta bulutundaki bir nokta kümesine bir kutu (3B) sığdırmak olacaktır.)

İlk aşama olarak, sorunu düzelten noktalar için (bu noktaları çözerek dahil olmayanlar) dışbükey gövdeyi bulmayı öneriyorum: MAR'yi poligona yerleştirmek. Gerekli yöntem, X ( dikdörtgenin merkezi ), D ( iki boyut ) ve A ( açı ) sağlayacaktır .


Çözüm önerim:

  • Poligonun centroidini bulun (bkz . Nesnenin geometrisinin merkezini bulma? )
  • [S] Basit bir dikdörtgene oturtun, yani X ve Y eksenlerine paralel
    • minmaxVerilen noktaların X ve Y işlevlerini kullanabilirsiniz (örneğin, çokgen köşeleri)
  • Takılı dikdörtgenin alanını saklayın
  • Çokgeni merkezde yaklaşık 1 derece döndürün
  • Tam dönüş yapılıncaya kadar [S] ' den tekrarlayın
  • Sonuç olarak minimum alanın açısını bildirin

Bana umut verici görünüyor, ancak aşağıdaki sorunlar var:

  • açı değişikliği için iyi bir çözünürlük seçmek zor olabilir,
  • Hesaplama maliyeti yüksek,
  • çözüm analitik değil deneyseldir.

görüntü tanımını buraya girin

Yanıtlar:


45

Evet, bu problem için analitik bir çözüm var. Aradığınız algoritma çokgen genelleştirmesinde "en küçük çevre dikdörtgen" olarak bilinir .

Tanımladığınız algoritma gayet iyi ancak listelediğiniz sorunları çözmek için , MAR'nin yönlendirmesinin, nokta bulutu dışbükey kabuğunun kenarlarından birinin aynısı olduğu gerçeğini kullanabilirsiniz . Bu yüzden sadece dışbükey gövde kenarlarının yönelimlerini test etmeniz gerekir. Malısın:

  • Bulutun dışbükey kabuğunu hesaplayın.
  • Dışbükey gövdenin her kenarı için:
    • Kenar oryantasyonunu hesaplayabilir (ark ile),
    • Döndürme dış bükey gövdesinin min / maks x / y ile sınırlayıcı dikdörtgen alanını kolayca hesaplamak için bu yönü kullanarak dışbükey kabuğunu döndürün,
    • Bulunan minimum alana karşılık gelen oryantasyonu saklayın,
  • Bulunan minimum alana karşılık gelen dikdörtgeni döndür.

Java'da bir uygulama örneği var .

3B'de, aynısı, aşağıdakiler dışında geçerlidir:

  • Dışbükey gövde bir hacim olacaktır,
  • Test edilen oryantasyonlar dışbükey gövde yüzlerinin oryantasyonları (3B) olacaktır.

İyi şanslar!


11
+1 Çok güzel cevap! Bulutun gerçek dönüşünün gereksiz olduğunu belirtmek isterim. İlk önce - muhtemelen bunu kastettin - sadece gövdenin köşeleri göz önünde bulundurulmalı. İkincisi, döndürmek yerine, geçerli tarafı bir çift ortogonal birim vektör olarak temsil eder. Nokta ürünlerini gövde tepe koordinatlarıyla (tek bir matris işlemi olarak yapılabilen) almak, döndürülen koordinatları verir: trigonometri gerekmez, hızlı ve tam olarak doğru.
whuber

2
Bağlantılar için teşekkürler. Aslında, yalnızca kenar # için döndürmek, önerilen yöntemi çok verimli kılar. Bunu kanıtlayan bir kağıt bulabilirim. Bunu ilk iyi cevaba olan sadakat cevabı olarak işaretlememe rağmen (iki veya daha fazla mükemmel cevabı seçemiyorum :() Aşağıda, whuber'in tam cevabını dikkatle değerlendirmeyi şiddetle tavsiye ediyorum . inanılmaz ve tüm prosedür sadece birkaç kod satırı .. Bana göre Python'a kolayca çevrilebilir :)
Geliştirici

Java uygulama bağlantısını güncelleyebilir misiniz?
Myra

evet, bitti!
julien

1
3B'ye yapılan uzantının bundan biraz daha karmaşık olduğunu unutmayın. 3D dışbükey gövdenin her yüzü sınırlayıcı kutunun bir yüzünün olası yönünü tanımlar , ancak dik yüzlerin yönünü tanımlamaz . Kutunun o düzlemde nasıl döndürüleceği sorunu, o yüzün düzleminde 2B minimum sınırlayıcı-dikdörtgen problemi haline gelir. Belirli bir uçağa yansıtılan bulutun dışbükey kabuğunun her kenarı için, size 3B olarak farklı bir hacim verecek bir sınırlama kutusu çizebilirsiniz.
Will

40

@ Julien'in mükemmel çözümünü desteklemek için, işte, RGIS'e özgü herhangi bir uygulamaya rehberlik etmek için (veya Relbette doğrudan uygulanacak) sahte kod olarak hizmet edebilecek bir çalışma uygulaması . Giriş bir nokta koordinatları dizisidir. Çıktı (değeri mbr), minimum sınırlayıcı dikdörtgenin köşelerinin bir dizisidir (ilkini kapatmak için tekrarlanan). Herhangi bir trigonometrik hesaplamanın tamamen bulunmadığına dikkat edin.

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

İşte kullanımına bir örnek:

# 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

Zamanlama, dışbükey gövde algoritmasının hızı ile sınırlıdır, çünkü gövdedeki tepe noktası sayısı neredeyse her zaman toplamdan çok daha azdır. Çoğu dışbükey gövde algoritması, n noktaları için asimptotik olarak O (n * log (n)) : neredeyse koordinatları okuyabildiğiniz kadar hızlı hesaplayabilirsiniz.


+1 Ne muhteşem bir çözüm! Böyle bir fikir ancak uzun deneyimlerden sonra gelir. Şu andan itibaren mevcut kodlarımı bu harika cevaptan ilham alarak optimize etmek isteyeceğim.
Geliştirici

Keşke bunu iki kez yenebilseydim. R öğreniyorum ve cevaplarınız sürekli bir ilham kaynağı.
John Powell,

1
@retrovius (Döndürülmüş) nokta kümesinin sınırlayıcı dikdörtgeni dört sayı ile belirlenir: en küçük x koordinatı, en büyük x koordinatı, en küçük y koordinatı ve en büyük y koordinatı. "Kenarlar boyunca aşırılık" ifadesi budur.
whuber

1
@retrovius Menşei bu hesaplamalarda hiçbir rol oynamaz, çünkü her şey, döndürülen koordinatlarda hesaplanan en iyi dikdörtgenin basitçe geri döndürüldüğü sonun dışındaki koordinat farklılıklarına dayanır. Başlangıç ​​noktasını noktalara yakın olan bir koordinat sistemi kullanmak akıllıca bir fikir olsa da (kayan nokta kesinliği kaybını en aza indirmek için), aksi halde başlangıç ​​alakasızdır.
whuber

1
@Retrovius Bunu bir dönme özelliği olarak yorumlayabilirsiniz: yani bir dönme matrisi diktir. Bu nedenle, bir çeşit kaynak doğrusal cebir (genel olarak) veya analitik Öklid geometrisinin (özellikle) incelenmesi olabilir. Ancak, düzlemdeki dönüşlerle (ve çeviriler ve yeniden ölçeklendirmelerle) başa çıkmanın en kolay yolunun, noktaları karmaşık sayılar olarak görmek olduğunu gördüm: döndürmeler, basitçe değerleri birim uzunluk sayıları ile çarparak gerçekleştirilir.
whuber

8

Bunu sadece kendim uyguladım ve cevabımı StackOverflow'a gönderdim , ancak sürümün başkalarının görmesi için buraya bırakacağımı düşündüm:

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

İşte dört farklı eylem örneği. Her örnek için 4 rastgele nokta oluşturdum ve sınırlayıcı kutuyu buldum.

görüntü tanımını buraya girin

Bu örnekler için 4 noktada nispeten hızlıdır:

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

Merhaba JesseBuesking, 90 derece köşeli dikdörtgenler üretebiliyor musunuz? Kodunuz paralelkenarlar almak için harika çalışıyor ancak benim özel kullanım durumumda 90 derece köşe gerekiyor. Buna ulaşmak için kodunuzun nasıl değiştirilebileceğini tavsiye eder misiniz? Teşekkürler!
Nader Alexan

@ NaderAlexan Eğer kareleri kaldırabilir olup olmadığını soruyorsanız, o zaman evet kesinlikle yapabilir! Ben sadece bir birim kare üzerinde denedim points = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])ve çıktı 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]])hangi birim kare kendisi olduğunu (bazı kayan nokta yuvarlama hataları dahil). Not: bir kare, yalnızca eşit kenarlara sahip bir dikdörtgendir, bu nedenle, tüm dikdörtgenlere yaydığı bir kareyi kaldırabileceğini varsayarım.
JesseBuesking,

Cevabınız için teşekkür ederim. Evet, çok iyi çalışıyor ama diğer 4 taraflı çokgenlerin üzerinde her zaman bir dikdörtgen (her bir taraf için 90 derece açılı 4 taraf), her zaman bir dikdörtgen oluşturmasa da her zaman bir dikdörtgen oluşturmaya zorluyorum. Sabit bir kısıtlama olması için, bu kısıtlamayı eklemek üzere kodu nasıl değiştireceğinizi biliyor musunuz? Teşekkürler!
Nader Alexan

Belki, gis.stackexchange.com/a/22934/48041 yanıtı bu kısıtlamaya sahip görünüyorsa, sizi çözüme doğru yönlendirebilir? Bir çözüm bulduğunuzda, başkalarının da faydalı bulacağından emin olduğum için katkıda bulunmalısınız. İyi şanslar!
JesseBuesking

7

Whitebox GAT’ta ( http://www.uoguelph.ca/~hydrogeo/Whitebox/ ) bu sorunu çözmek için Minimum Sınırlama Kutusu adında bir araç var . Ayrıca orada minimum dışbükey gövde aracı da var. Yama Şekli araç kutusundaki araçlardan bazıları, örneğin yama oryantasyonu ve uzaması, minimum sınırlama kutusunu bulmaya dayanır.

görüntü tanımını buraya girin


4

Minimum alan sınırlayan bir dikdörtgen için bir Python çözümü ararken bu konuya rastladım.

İşte benim uygulama , hangi sonuçların Matlab ile doğrulandı.

Test kodu basit çokgenler için dahil edilmiştir ve 2B minimum sınırlama kutusunu ve 3B PointCloud için eksen yönlerini bulmak için kullanıyorum.


Cevabınız silindi mi?
Paul Richter

@PaulRichter görünüşte. Kaynak buradaydı github.com/dbworth/minimum-area-bounding-rectangle olsa
sehe

3

Teşekkürler @ whuber'ın cevabı. Harika bir çözüm, ancak büyük nokta bulutu için yavaş. convhullnR paketindeki fonksiyonun geometryçok daha hızlı olduğunu buldum (13800 vs 200000 puanları için 0.03 sn). Kodlarımı buraya yapıştırdım herkes için daha hızlı bir çözüm için ilginç.

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)   

İki yöntem aynı cevabı alır (2000 puan için örnek):

görüntü tanımını buraya girin


Bu uygulamayı 3B alana genişletmek mümkün mü (yani 3B alanda verilen tüm noktaları içeren minimum bir hacim kutusu bulmak)?
Sasha

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.