web-dev-qa-db-ja.com

3D点群での平面フィッティング

回帰式を使用して、3D点群で平面を見つけようとしています Z = aX + bY + C

最小二乗法とランサック解を実装しましたが、3つのパラメーターの方程式により、平面フィッティングが2.5Dに制限されます。この公式は、Z軸に平行な平面には適用できません。

私の質問は平面フィッティングを完全な3dに一般化するにはどうすればよいですか?完全な方程式aX + bY + c * Z + dを取得するために、4番目のパラメーターを追加したいのですが、どうすれば自明な(0,0,0、 0)解決策?

ありがとう!

私が使用しているコード:

from sklearn import linear_model

def local_regression_plane_ransac(neighborhood):
    """
    Computes parameters for a local regression plane using RANSAC
    """

    XY = neighborhood[:,:2]
    Z  = neighborhood[:,2]
    ransac = linear_model.RANSACRegressor(
                                          linear_model.LinearRegression(),
                                          residual_threshold=0.1
                                         )
    ransac.fit(XY, Z)

    inlier_mask = ransac.inlier_mask_
    coeff = model_ransac.estimator_.coef_
    intercept = model_ransac.estimator_.intercept_
8
Tom

更新

この機能は現在 https://github.com/daavoo/pyntcloud に統合されており、平面フィッティングプロセスがはるかに簡単になっています。

点群が与えられた場合:

enter image description here

次のようなスカラーフィールドを追加する必要があります。

is_floor = cloud.add_scalar_field("plane_fit")

Wichは、フィットした平面のポイントに値1の新しい列を追加します。

スカラー場を視覚化できます。

enter image description here


古い答え

[〜#〜] pca [〜#〜] を使用して、回帰の代わりに平面を3Dポイントに簡単に合わせることができると思います。

簡単なPCA実装は次のとおりです。

def PCA(data, correlation = False, sort = True):
""" Applies Principal Component Analysis to the data

Parameters
----------        
data: array
    The array containing the data. The array must have NxM dimensions, where each
    of the N rows represents a different individual record and each of the M columns
    represents a different variable recorded for that individual record.
        array([
        [V11, ... , V1m],
        ...,
        [Vn1, ... , Vnm]])

correlation(Optional) : bool
        Set the type of matrix to be computed (see Notes):
            If True compute the correlation matrix.
            If False(Default) compute the covariance matrix. 

sort(Optional) : bool
        Set the order that the eigenvalues/vectors will have
            If True(Default) they will be sorted (from higher value to less).
            If False they won't.   
Returns
-------
eigenvalues: (1,M) array
    The eigenvalues of the corresponding matrix.

eigenvector: (M,M) array
    The eigenvectors of the corresponding matrix.

Notes
-----
The correlation matrix is a better choice when there are different magnitudes
representing the M variables. Use covariance matrix in other cases.

"""

mean = np.mean(data, axis=0)

data_adjust = data - mean

#: the data is transposed due to np.cov/corrcoef syntax
if correlation:

    matrix = np.corrcoef(data_adjust.T)

else:
    matrix = np.cov(data_adjust.T) 

eigenvalues, eigenvectors = np.linalg.eig(matrix)

if sort:
    #: sort eigenvalues and eigenvectors
    sort = eigenvalues.argsort()[::-1]
    eigenvalues = eigenvalues[sort]
    eigenvectors = eigenvectors[:,sort]

return eigenvalues, eigenvectors

そして、これがポイントを平面に合わせる方法です:

def best_fitting_plane(points, equation=False):
""" Computes the best fitting plane of the given points

Parameters
----------        
points: array
    The x,y,z coordinates corresponding to the points from which we want
    to define the best fitting plane. Expected format:
        array([
        [x1,y1,z1],
        ...,
        [xn,yn,zn]])

equation(Optional) : bool
        Set the oputput plane format:
            If True return the a,b,c,d coefficients of the plane.
            If False(Default) return 1 Point and 1 Normal vector.    
Returns
-------
a, b, c, d : float
    The coefficients solving the plane equation.

or

point, normal: array
    The plane defined by 1 Point and 1 Normal vector. With format:
    array([Px,Py,Pz]), array([Nx,Ny,Nz])

"""

w, v = PCA(points)

#: the normal of the plane is the last eigenvector
normal = v[:,2]

#: get a point from the plane
point = np.mean(points, axis=0)


if equation:
    a, b, c = normal
    d = -(np.dot(normal, point))
    return a, b, c, d

else:
    return point, normal    

ただし、この方法は外れ値に敏感であるため、 [〜#〜] ransac [〜#〜] を使用して、外れ値に対してロバストにフィットさせることができます。

Python ransacの実装 ここ があります。

また、平面を3Dポイントにフィッティングするために使用するには、平面モデルクラスを定義するだけで済みます。

いずれにせよ、外れ値から3Dポイントをクリーンアップできれば(おそらくKD-Tree S.O.Rフィルターを使用して)、PCAでかなり良い結果が得られるはずです。

S.O.R の実装は次のとおりです。

def statistical_outilier_removal(kdtree, k=8, z_max=2 ):
""" Compute a Statistical Outlier Removal filter on the given KDTree.

Parameters
----------                        
kdtree: scipy's KDTree instance
    The KDTree's structure which will be used to
    compute the filter.

k(Optional): int
    The number of nearest neighbors wich will be used to estimate the 
    mean distance from each point to his nearest neighbors.
    Default : 8

z_max(Optional): int
    The maximum Z score wich determines if the point is an outlier or 
    not.

Returns
-------
sor_filter : boolean array
    The boolean mask indicating wherever a point should be keeped or not.
    The size of the boolean mask will be the same as the number of points
    in the KDTree.

Notes
-----    
The 2 optional parameters (k and z_max) should be used in order to adjust
the filter to the desired result.

A HIGHER 'k' value will result(normally) in a HIGHER number of points trimmed.

A LOWER 'z_max' value will result(normally) in a HIGHER number of points trimmed.

"""

distances, i = kdtree.query(kdtree.data, k=k, n_jobs=-1) 

z_distances = stats.zscore(np.mean(distances, axis=1))

sor_filter = abs(z_distances) < z_max

return sor_filter

おそらく この実装 を使用して計算された3DポイントのKDtreeを関数にフィードできます。

11
import pcl
cloud = pcl.PointCloud()
cloud.from_array(points)
seg = cloud.make_segmenter_normals(ksearch=50)
seg.set_optimize_coefficients(True)
seg.set_model_type(pcl.SACMODEL_PLANE)
seg.set_normal_distance_weight(0.05)
seg.set_method_type(pcl.SAC_RANSAC)
seg.set_max_iterations(100)
seg.set_distance_threshold(0.005)
inliers, model = seg.segment()

最初に python-pcl をインストールする必要があります。パラメータを自由に試してみてください。ここでのポイントは、n個の3dポイントを持つnx3numpy配列です。モデルは[a、b、c、d]になり、ax + by + cz + d = 0

0
Mehdi