web-dev-qa-db-ja.com

点が点群の凸包にあるかどうかを見つける効率的な方法は何ですか?

Numpyに座標の点群があります。多数のポイントの場合、ポイントがポイントクラウドの凸包にあるかどうかを確認します。

Pyhullを試しましたが、ポイントがConvexHullにあるかどうかを確認する方法がわかりません。

hull = ConvexHull(np.array([(1, 2), (3, 4), (3, 6)]))
for s in hull.simplices:
    s.in_simplex(np.array([2, 3]))

linAlgErrorを発生させます:配列は正方でなければなりません。

31
AME

Scipyのみを必要とする簡単なソリューションを次に示します。

def in_hull(p, hull):
    """
    Test if points in `p` are in `hull`

    `p` should be a `NxK` coordinates of `N` points in `K` dimensions
    `hull` is either a scipy.spatial.Delaunay object or the `MxK` array of the 
    coordinates of `M` points in `K`dimensions for which Delaunay triangulation
    will be computed
    """
    from scipy.spatial import Delaunay
    if not isinstance(hull,Delaunay):
        hull = Delaunay(hull)

    return hull.find_simplex(p)>=0

ブール配列を返します。True値は、指定された凸包内にあるポイントを示します。次のように使用できます。

tested = np.random.Rand(20,3)
cloud  = np.random.Rand(50,3)

print in_hull(tested,cloud)

Matplotlibがインストールされている場合、最初の関数を呼び出して結果をプロットする次の関数を使用することもできます。 Nx2配列で指定される2Dデータのみ:

def plot_in_hull(p, hull):
    """
    plot relative to `in_hull` for 2d data
    """
    import matplotlib.pyplot as plt
    from matplotlib.collections import PolyCollection, LineCollection

    from scipy.spatial import Delaunay
    if not isinstance(hull,Delaunay):
        hull = Delaunay(hull)

    # plot triangulation
    poly = PolyCollection(hull.points[hull.vertices], facecolors='w', edgecolors='b')
    plt.clf()
    plt.title('in hull')
    plt.gca().add_collection(poly)
    plt.plot(hull.points[:,0], hull.points[:,1], 'o', hold=1)


    # plot the convex hull
    edges = set()
    Edge_points = []

    def add_Edge(i, j):
        """Add a line between the i-th and j-th points, if not in the list already"""
        if (i, j) in edges or (j, i) in edges:
            # already added
            return
        edges.add( (i, j) )
        Edge_points.append(hull.points[ [i, j] ])

    for ia, ib in hull.convex_hull:
        add_Edge(ia, ib)

    lines = LineCollection(Edge_points, color='g')
    plt.gca().add_collection(lines)
    plt.show()    

    # plot tested points `p` - black are inside hull, red outside
    inside = in_hull(p,hull)
    plt.plot(p[ inside,0],p[ inside,1],'.k')
    plt.plot(p[-inside,0],p[-inside,1],'.r')
61
Juh_

こんにちは、これを達成するためにプログラムライブラリを使用する方法についてはわかりません。しかし、これを言葉で説明する簡単なアルゴリズムがあります。

  1. 船体の外側にあるポイントを作成します。それをYと呼ぶ
  2. 問題のポイント(X)を新しいポイントYに接続する線分を作成します。
  3. 凸包のすべてのエッジセグメントをループします。セグメントがXYと交差する場合、それぞれを確認します。
  4. カウントした交差の数が偶数(0を含む)の場合、Xは船体の外側にあります。それ以外の場合、Xは船体の内部にあります。
  5. その場合、XYがハルの頂点の1つを通過するか、ハルのエッジの1つと直接オーバーラップする場合、Yを少し移動します。
  6. 上記は凹型船体にも機能します。下の図で見ることができます(緑色の点は、決定しようとしているX点です。黄色は、交差点を示しています。 illustration
23
firemana

凸包を計算する必要がないため、凸包アルゴリズムを使用しません。サブセットが凸包を定義する点のセットの凸結合として表現できるかどうかを確認するだけです。さらに、凸包を見つけることは、特に高次元では計算的に高価です。

実際、ある点が別の点の集合の凸の組み合わせとして表現できるかどうかを見つけるという単なる問題は、線形計画問題として定式化できます。

import numpy as np
from scipy.optimize import linprog

def in_hull(points, x):
    n_points = len(points)
    n_dim = len(x)
    c = np.zeros(n_points)
    A = np.r_[points.T,np.ones((1,n_points))]
    b = np.r_[x, np.ones(1)]
    lp = linprog(c, A_eq=A, b_eq=b)
    return lp.success

n_points = 10000
n_dim = 10
Z = np.random.Rand(n_points,n_dim)
x = np.random.Rand(n_dim)
print(in_hull(Z, x))

この例では、10次元で10000ポイントの問題を解決しました。実行時間はミリ秒単位です。 QHullでこれがどれくらいかかるか知りたくないでしょう。

16
Nils

完全を期すために、ここに貧しい人の解決策があります:

import pylab
import numpy
from scipy.spatial import ConvexHull

def is_p_inside_points_hull(points, p):
    global hull, new_points # Remove this line! Just for plotting!
    hull = ConvexHull(points)
    new_points = numpy.append(points, p, axis=0)
    new_hull = ConvexHull(new_points)
    if list(hull.vertices) == list(new_hull.vertices):
        return True
    else:
        return False

# Test:
points = numpy.random.Rand(10, 2)   # 30 random points in 2-D
# Note: the number of points must be greater than the dimention.
p = numpy.random.Rand(1, 2) # 1 random point in 2-D
print is_p_inside_points_hull(points, p)

# Plot:
pylab.plot(points[:,0], points[:,1], 'o')
for simplex in hull.simplices:
    pylab.plot(points[simplex,0], points[simplex,1], 'k-')
pylab.plot(p[:,0], p[:,1], '^r')
pylab.show()

考え方は簡単です。点のセットPの凸包の頂点は、ハルの「内側」にある点pを追加しても変化しません。 [P1, P2, ..., Pn][P1, P2, ..., Pn, p]の凸包の頂点は同じです。ただし、pが「外側」にある場合は、頂点を変更する必要があります。これはn次元で機能しますが、ConvexHullを2回計算する必要があります。

2次元の2つのプロット例:

偽:

New point (red) falls outside the convex hull

正しい:

New point (red) falls inside the convex hull

5
feqwix

equationsConvexHull属性を使用します。

def point_in_hull(point, hull, tolerance=1e-12):
    return all(
        (np.dot(eq[:-1], point) + eq[-1] <= tolerance)
        for eq in hull.equations)

言い換えると、すべての方程式(ファセットを記述する)に対して、ポイントと法線ベクトル(eq[:-1])とオフセット(eq[-1])の間のドット積が小さい場合にのみ、ポイントはハル内にありますゼロ以下。数値の精度の問題のため、ゼロではなく小さな正の定数tolerance = 1e-12と比較したい場合があります(そうでない場合、凸包の頂点が凸包にないことがわかります)。

デモンストレーション:

import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull

points = np.array([(1, 2), (3, 4), (3, 6), (2, 4.5), (2.5, 5)])
hull = ConvexHull(points)

np.random.seed(1)
random_points = np.random.uniform(0, 6, (100, 2))

for simplex in hull.simplices:
    plt.plot(points[simplex, 0], points[simplex, 1])

plt.scatter(*points.T, alpha=.5, color='k', s=200, marker='v')

for p in random_points:
    point_is_in_hull = point_in_hull(p, hull)
    marker = 'x' if point_is_in_hull else 'd'
    color = 'g' if point_is_in_hull else 'm'
    plt.scatter(p[0], p[1], marker=marker, color=color)

output of demonstration

5

2Dポイントクラウドを使用しているように見えるので、凸ポリゴンのポイントインポリゴンテストの inclusion test に進みたいと思います。

Scipyの凸包アルゴリズムでは、2次元以上の次元の凸包を見つけることができます。これは、2D点群に必要なものよりも複雑です。したがって、 this one などの別のアルゴリズムを使用することをお勧めします。これは、ポイントインポリゴンの凸包テストに本当に必要なのは、時計回りの順序での凸包ポイントのリストと、ポリゴンの内側にあるポイントだからです。

このアプローチの時間パフォーマンスは次のとおりです。

  • O(N log N)は凸包を構築します
  • O(h)前処理で、内点からくさび角を計算(および保存)する
  • ポイントインポリゴンクエリごとのO(log h)。

ここで、Nはポイントクラウドのポイント数、hはポイントクラウドの凸包のポイント数です。

4
Nuclearman

Scipyを使い続けたい場合は、凸包にする必要があります(そうしました)

>>> from scipy.spatial import ConvexHull
>>> points = np.random.Rand(30, 2)   # 30 random points in 2-D
>>> hull = ConvexHull(points)

次に、船体上のポイントのリストを作成します。船体をプロットするdocのコードを次に示します

>>> import matplotlib.pyplot as plt
>>> plt.plot(points[:,0], points[:,1], 'o')
>>> for simplex in hull.simplices:
>>>     plt.plot(points[simplex,0], points[simplex,1], 'k-')

それから始めて、船体上の点のリストを計算することを提案します

pts_hull = [(points[simplex,0], points[simplex,1]) 
                            for simplex in hull.simplices] 

(私は試しませんでしたが)

また、船体を計算してX、Yポイントを返す独自のコードを用意することもできます。

元のデータセットのポイントが船体上にあるかどうかを知りたい場合、これで完了です。

私が望むのは、船体の内側か外側かを知ることです、もう少し作業が必要です。あなたがしなければならないことは

  • ハルの2つのシンプレックスを結合するすべてのエッジに対して:ポイントが上か下かを決定します

  • ポイントがすべての線より下、またはすべての線より上にある場合、それは船体の外側にあります

スピードアップとして、ポイントが1つのラインより上に、そして互いに下にあるとすぐに、それは船体の中にあります。

1
kiriloff

this postに基づいて、4辺の凸領域に対する迅速で汚い解決策を示します(簡単に拡張できます)。

def same_sign(arr): return np.all(arr > 0) if arr[0] > 0 else np.all(arr < 0)

def inside_quad(pts, pt):
    a =  pts - pt
    d = np.zeros((4,2))
    d[0,:] = pts[1,:]-pts[0,:]
    d[1,:] = pts[2,:]-pts[1,:]
    d[2,:] = pts[3,:]-pts[2,:]
    d[3,:] = pts[0,:]-pts[3,:]
    res = np.cross(a,d)
    return same_sign(res), res

points = np.array([(1, 2), (3, 4), (3, 6), (2.5, 5)])

np.random.seed(1)
random_points = np.random.uniform(0, 6, (1000, 2))

print wlk1.inside_quad(points, random_points[0])
res = np.array([inside_quad(points, p)[0] for p in random_points])
print res[:4]
plt.plot(random_points[:,0], random_points[:,1], 'b.')
plt.plot(random_points[res][:,0], random_points[res][:,1], 'r.')

enter image description here

0
BBDynSys