与えられたポイントの最小面積の長方形を見つける?


71

図にあるように、問題は次のとおりです。

与えられた点に適合する最小面積長方形(MAR)を見つける方法は?

サポートする質問は次のとおりです。

問題の分析ソリューションはありますか?

(質問の開発は、ボックス(3D)を3D点群の点のクラスターに適合させることです。)

最初の段階として、問題を改善するポイントの凸包を見つけることを提案します(これらのポイントを削除することにより、ソリューションに関与しません) 必要なメソッドは、X(長方形の中心)、D(2次元)、およびA(角度)を提供します。


私の解決策の提案:

  • 多角形の重心を見つけます(オブジェクトのジオメトリの中心を見つけるを参照してください
  • [S]単純な近似長方形、つまり軸XとYに平行に近似します
    • minmax指定されたポイントのXおよびYに関数を使用できます(たとえば、ポリゴンの頂点)
  • フィットした長方形の面積を保存する
  • 重心を中心にポリゴンをたとえば1度回転させます
  • 完全な回転が完了するまで[S]から繰り返します
  • 結果として最小面積の角度を報告する

有望に思えますが、次の問題が存在します。

  • 角度の変化に適した解像度を選択するのは難しいかもしれませんが、
  • 計算コストが高い
  • ソリューションは分析的ではなく実験的です。

ここに画像の説明を入力してください

回答:


45

はい、この問題の分析ソリューションがあります。探しているアルゴリズムは、ポリゴンの一般化では「最小の周囲の長方形」として知られています。

説明するアルゴリズムは問題ありませんが、リストした問題を解決するために、MARの方向が点群凸包のエッジの1つと同じであるという事実を使用できます。したがって、凸包のエッジの方向をテストするだけです。あなたがすべき:

  • クラウドの凸包を計算します。
  • 凸包の各エッジに対して:
    • エッジの方向を計算(arctanを使用)、
    • 回転した凸包のx / yの最小値/最大値を持つ境界矩形領域を簡単に計算するために、この方向を使用して凸包を回転します。
    • 見つかった最小領域に対応する向きを保存します。
  • 見つかった最小面積に対応する長方形を返します。

Javaでの実装の例は、そこにあります。

3Dでは、以下を除いて同じことが適用されます。

  • 凸包はボリュームになり、
  • テストされる方向は、凸包面の方向(3D)です。

幸運を!


11
+1とてもいい答えです!クラウドの実際の回転は不要であることを指摘したいと思います。まず、これを意味するのはおそらく、船体の頂点のみを考慮する必要があるということです。第二に、回転する代わりに、現在の側を直交する単位ベクトルのペアとして表します。ハルの頂点座標(単一の行列演算として実行可能)でドット積を取得すると、回転座標が得られます。三角法は不要で、高速で、完全に正確です。
whuber

2
リンクをありがとう。実際、エッジの数だけ回転させると、提案された方法は非常に効率的になります。私はその論文がそれを証明しているのを見つけることができました。私はこれを最初の良い答えに対する忠誠心の答えとしてマークしましたが(2つ以上の素晴らしい答えを選択することはできません:()以下でwhuberの完全な答えを強くお勧めしたいと思います。信じられないほどで、手順全体はほんの数行のコードです私にとっては、Pythonに簡単に翻訳できます:)
開発者

Java実装リンクを更新してください。
マイラ

はい、完了です!
ジュリアン

1
3Dへの拡張は、それよりも少し複雑であることに注意してください。3D凸包の各面は、境界ボックスの1つの面の可能な方向を定義しますが、それに垂直なの方向は定義しません。その平面でボックスを回転させる方法の問題は、その面の平面での2D最小境界矩形問題になります。特定の平面に投影された雲の凸包の各エッジに対して、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

タイミングは、凸包アルゴリズムの速度によって制限されます。これは、ハルの頂点の数がほとんどの場合、全体の数よりもはるかに少ないためです。ほとんどの凸包アルゴリズムは、nポイントに対して漸近的にO(n * log(n))です。座標を読み取るのとほぼ同じ速度で計算できます。


+1なんて素晴らしいソリューションでしょう!このようなアイデアは、長い経験を経て初めて生まれました。これからは、この素晴らしい答えに触発された既存のコードを最適化することに興味があります。
開発者

これを2回支持できたらと思います。私はRを学んでおり、あなたの答えは常にインスピレーションの源です。
ジョンパウエル

1
@retrovius(回転)ポイントのセットの境界矩形は、4つの数値(最小x座標、最大x座標、最小y座標、最大y座標)によって決定されます。それが「エッジに沿った極値」と呼ばれるものです。
whuber

1
@retrovius原点はこれらの計算で役割を果たしません。なぜなら、回転座標で計算された最適な長方形が単に逆回転される最後を除いて、すべてが座標の差に基づいているからです。(浮動小数点の精度の低下を最小限に抑えるために)原点がポイントに近い座標系を使用するのは賢明な考えですが、それ以外の場合は原点は無関係です。
whuber

1
@Retroviusこれは、回転のプロパティの観点から解釈できます。つまり、回転のマトリックスは直交しています。したがって、1つの種類のリソースは、線形代数(一般的に)または分析ユークリッドジオメトリ(具体的に)の研究です。ただし、平面内の回転(および移動と再スケーリング)を処理する最も簡単な方法は、ポイントを複素数として表示することです。回転は、値に単位長さの数値を乗算することで実行されます。
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つのランダムポイントを生成し、境界ボックスを見つけました。

ここに画像の説明を入力してください

これらのサンプルについても、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に確認されたためには、。

テストコードは単純なポリゴンに含まれており、3D PointCloudの2D最小バウンディングボックスと軸方向を見つけるために使用しています。


回答は削除されましたか?
ポールリヒター

@PaulRichterどうやら。ソースはここにあったgithub.com/dbworth/minimum-area-bounding-rectangleけれども
sehe

3

@whuberの回答に感謝します。これは優れたソリューションですが、大きなポイントクラウドでは低速です。convhullnRパッケージの関数geometryがはるかに高速であることがわかりました(200000ポイントの場合、0.03秒に対して138秒)。ここに自分のコードを貼り付けた人は誰でも、より速い解決策に興味を持っています。

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)   

2つの方法で同じ答えが得られます(2000ポイントの例):

ここに画像の説明を入力してください


この実装を3D空間に拡張することは可能ですか(つまり、3D空間内の指定されたすべてのポイントを含む最小ボリュームボックスを見つけます)。
サーシャ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.