[3D Vision] 空間点セットの最小境界ボックスの計算

0 問題の説明

空間点セットがあり、N 個の重複しない点があると仮定します。
N=1 の場合、最小境界ボックスは点です。中心はそれ自体であり、半径は無限に小さくなります。N
=2 の場合、最小境界ボックスは円です。中心は接続線の中点であり、半径は辺の長さの半分です
。直線の 3 点を結んで三角形を形成し、鋭角三角形と直角三角形を囲む最小の円が外接円です。鈍角三角形の最長辺を直径とする円が最小の外接円
N=4で、同一平面上にない4つの点で四面体を作り、それを囲む最小の球は?計算方法は?それは彼の受信機ですか?そんなはずはないのですが、外角のボールはどう計算すればいいのでしょうか?考える価値のある質問。
すると、
N>4、...
ここに画像の説明を挿入

1 境界ボックスの概要

境界ボックスには次の 4 つの一般的なタイプがあります。

①軸合わせバウンディングボックス AABB(軸合わせバウンディングボックス)
②バウンディング球(Bounding Sphere)
③配向バウンディングボックス OBB(配向バウンディングボックス)
④固定方向凸包 FDH(固定方向包または k-DOP)

ここに画像の説明を挿入

1.1 AABB 軸に合わせたバウンディング ボックス

これは、座標軸に平行な辺を持つオブジェクトを含む最小の六面体として定義されます。したがって、AABB を記述するために必要なスカラーは 6 つだけです。AABB の構造は比較的単純で、格納スペースは小さいですが、コンパクト性が悪く、特に不規則な幾何学的形状の場合、余分なスペースが大きく、オブジェクトを回転すると、それに応じて回転できません。処理オブジェクトは剛体で凸状であるため、ソフト ボディの変形を伴う複雑な仮想環境の状況には適していません。

1.2 BS周囲ボール

これは、オブジェクトを含む最小の球として定義されます。囲む球を決定するには、まずオブジェクトを構成する基本的な幾何学的要素のすべての要素の頂点の x、y、z 座標の平均値を計算して、囲む球の中心を決定する必要があります。球を作成し、球の中心と 3 つの最大値の座標を決定し、それらの点間の距離によって半径 r が決まります。周囲の球の衝突検出は、主に 2 つの球間の半径と球の中心からの距離を比較します。

1.3 OBB 指示バウンディング ボックス

OBB は、より一般的に使用されるバウンディング ボックス タイプです。これは、オブジェクトを含み、座標軸を基準にして配向された最小の直方体です。OBB の最大の特徴は方向の任意性であり、包囲する物体の形状特性に応じて可能な限り密に包囲することが可能ですが、交差テストが複雑になります。OBB バウンディング ボックスは、AABB バウンディング ボックスおよびバウンディング 球よりもオブジェクトに近いため、バウンディング ボリュームの数を大幅に減らすことができ、それによって多数のバウンディング ボリューム間の交差検出を回避できます。ただし、OBB 間の交差検出は、AABB または境界球間の交差検出よりも時間がかかります。

1.4 FDH 固定方向凸包

FDH (k-DOP) は、AABB の単純さを継承する特殊な凸包ですが、良好な空間的コンパクト性を実現するには、十分な固定方向を使用する必要があります。は、オブジェクトを含み、そのすべての面の法線ベクトルが固定された方向のセット (k ベクトル) から取得される凸包として定義されます。FDH は他のバウンディング ボリュームよりも元のオブジェクトをより密に取り囲み、作成された階層ツリーのノード数が少なくなるため、交差検出時の冗長な計算が減りますが、それらの間の交差計算はより複雑になります。

2 バウンディングボックス計算アルゴリズム

2.1 BS境界球計算アルゴリズム

2.2.1 単純なアルゴリズム

最も単純なアイデアの 1 つは、X、Y、Z 方向の空間頂点の最大値と最小値を計算することです。そうすれば、8 つの頂点で構成される境界ボックスを取得できます。囲む球の中心を境界ボックスの中心点として取り、囲む球の半径は中心点から 8 つの頂点までの最大距離になると考える人もいますが、これは実際には厳密ではありません。中心点からすべての頂点までの最大距離を計算することをお勧めします。

2.2.2 リッターアルゴリズム

もう一つのアルゴリズムは ritter という人によって提案されているため、ritter アルゴリズムと呼ばれます。

まず、X 方向の最も遠い 2 点、Y 方向の最も遠い 2 点、Z 方向の最も遠い 2 点を計算します。最も遠い 3 つの距離の範囲が初期直径として使用され、3 つの距離の中心点が球の初期中心として使用されます。

次に、すべてのポイントを順番に走査して、そのポイントが周囲の球内にあるかどうかを判断します。そうでない場合は、境界球を更新します。以下に示すように:

2.2.3 Welzlアルゴリズム

再帰アルゴリズムを使用すると簡単に実装できます。

最初に np-1 個の点に基づいて球を生成します (再帰的)。np
番目の点が球内にあるかどうかを判断し、存在する場合は球を保持します。
そうでない場合は、古い球と np 番目の点に基づいて新しい球を生成する必要があります (再帰的)。このうち、np 番目の点は新しく生成された球上にある必要があります;
再帰終了条件: 1 点または 2 点の場合、球を直接生成できます; 3 点すべてが球上にある場合、三角形の外接球が生成されます。

3. 既成のコードライブラリ(パッケージ)

PCL
scipy
trimesh
open3d

4. 外接球と最小囲い球の違い

外球は私が理解している最小囲い球とは異なることに注意してください。
黄色が最小囲い球、緑が外接球です。
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入

5. 4点外接球と最小囲み球の計算

import numpy as np
import matplotlib.pyplot as plt


def get_min_sphere_4points(points):
    """
    Get the minimum radius of a circumscribed sphere that encloses all the points
    """
    def minimum_enclosing_sphere_3points(triangle):
        # Compute the circumcenter of the triangle
        a, b, c = triangle
        ab = b - a
        ac = c - a
        ab_cross_ac = np.cross(ab, ac)
        ab_cross_ac_norm_sq = np.dot(ab_cross_ac, ab_cross_ac)
        if ab_cross_ac_norm_sq == 0:
            # Points are colinear, return a point and radius of infinity
            return a, np.inf
        ab_norm_sq = np.dot(ab, ab)
        ac_norm_sq = np.dot(ac, ac)
        circumcenter = a + (np.cross(ab_norm_sq * ac - ac_norm_sq * ab, ab_cross_ac) / (2 * ab_cross_ac_norm_sq))
        # Calculate the radius of the circumcircle
        radius = np.linalg.norm(circumcenter - a)
        # Check if the circumcenter lies inside the triangle
        if np.all(np.logical_and(circumcenter >= a, circumcenter <= c)):
            return circumcenter, radius
        # Otherwise, the minimum enclosing sphere is the circumcircle
        else:
            center = np.mean(triangle, axis=0)
            radius = np.max(np.linalg.norm(triangle - center, axis=1))
            return center, radius
    def _min_sphere(points, center, radius):
        if len(points) == 0 or len(center) == 3:
            if len(center) == 3:
                # c1, c2, c3 = center
                # return np.array([(c1 + c2 + c3) / 3]), 0
                return minimum_enclosing_sphere_3points(center)
            elif len(center) == 2:
                c1, c2 = center
                return (c1 + c2) / 2, np.linalg.norm(c1 - c2) / 2
            elif len(center) == 1:
                return center[0], 0
            else:
                return None, 0
        else:
            p = points[0]
            points = points[1:]
            c, r = _min_sphere(points, center, radius)
            if c is None or np.linalg.norm(p - c) > r:
                center.append(p)
                c, r = _min_sphere(points, center, radius)
                center.pop()
            return c, r

    if len(points) < 4:
        raise ValueError("At least 4 points are required.")
    np.random.shuffle(points)
    center, radius = _min_sphere(points, [], 0)
    print("Center:", center)
    print("Radius:", radius)
    return center, radius


def fit_circumscribed_sphere_4points(array, tol=1e-6):
    # Check if the the points are co-linear
    D12 = array[1] - array[0]
    D12 = D12 / np.linalg.norm(D12)
    D13 = array[2] - array[0]
    D13 = D13 / np.linalg.norm(D13)
    D14 = array[3] - array[0]
    D14 = D14 / np.linalg.norm(D14)

    chk1 = np.clip(np.abs(np.dot(D12, D13)), 0., 1.)  # 如果共线,chk1=1
    chk2 = np.clip(np.abs(np.dot(D12, D14)), 0., 1.)
    # 求的是反余弦值,如果是1,反余弦值为0(弧度),乘以180/pi,就是0(度),说明共线
    if np.arccos(chk1) / np.pi * 180 < tol or np.arccos(chk2) / np.pi * 180 < tol:
        R = np.inf
        C = np.full(3, np.nan)
        return R, C

    # Check if the the points are co-planar
    n1 = np.linalg.norm(np.cross(D12, D13))
    n2 = np.linalg.norm(np.cross(D12, D14))

    chk = np.clip(np.abs(np.dot(n1, n2)), 0., 1.)
    if np.arccos(chk) / np.pi * 180 < tol:
        R = np.inf
        C = np.full(3, np.nan)
        return R, C

    # Centroid of the sphere
    A = 2 * (array[1:] - np.full(len(array) - 1, array[0]))
    b = np.sum((np.square(array[1:]) - np.square(np.full(len(array) - 1, array[0]))), axis=1)
    C = np.transpose(np.linalg.solve(A, b))

    # Radius of the sphere
    R = np.sqrt(np.sum(np.square(array[0] - C), axis=0))
    print("Center:", C)
    print("Radius:", R)

    return C, R


if __name__ == '__main__':

    # # Define the four points
    p1 = np.array([0, 0, 0])
    p2 = np.array([0, 4, 0])
    p3 = np.array([4, 0, 0])
    p4 = np.array([1, 2, 0])

    points1 = np.array([p1, p2, p3, p4])

    points1 = np.random.rand(4, 3)
    # show_tetrahedron(points1)
    center0, radius0 = fit_circumscribed_sphere_4points(points1)

    center1, radius1 = get_min_sphere_4points(points1)


    from mpl_toolkits.mplot3d import Axes3D

    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    # Plot the points
    ax.scatter(points1[:, 0], points1[:, 1], points1[:, 2], c='b')
    # plot the tetrahedron
    ax.plot(points1[:, 0], points1[:, 1], points1[:, 2], c='b')

    # Plot the sphere1
    u, v = np.mgrid[0:2 * np.pi:20j, 0:np.pi:10j]
    x = center0[0] + radius0 * np.cos(u) * np.sin(v)
    y = center0[1] + radius0 * np.sin(u) * np.sin(v)
    z = center0[2] + radius0 * np.cos(v)
    ax.plot_wireframe(x, y, z, color="g")

    # Plot the sphere2
    u, v = np.mgrid[0:2 * np.pi:20j, 0:np.pi:10j]
    x = center1[0] + radius1 * np.cos(u) * np.sin(v)
    y = center1[1] + radius1 * np.sin(u) * np.sin(v)
    z = center1[2] + radius1 * np.cos(v)
    ax.plot_wireframe(x, y, z, color="y")

    # Set the axes properties
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_aspect('equal')
    # Show the plot
    print('Showing the plot...')
    plt.show()

参考文献

最小包囲球:ナイーブアルゴリズムとリッターアルゴリズム
最小包囲球:welzlアルゴリズム

おすすめ

転載: blog.csdn.net/weixin_43693967/article/details/131162207