简体   繁体   English

3d 点云中的平面拟合

[英]Plane fitting in a 3d point cloud

I am trying to find planes in a 3d point cloud, using the regression formula Z= a X + b Y +C我正在尝试使用回归公式Z= a X + b Y +C在 3d 点云中找到平面

I implemented least squares and ransac solutions, but the 3 parameters equation limits the plane fitting to 2.5D- the formula can not be applied on planes parallel to the Z-axis.我实现了最小二乘法和 ransac 解决方案,但 3 个参数方程将平面拟合限制为 2.5D - 该公式不能应用于平行于 Z 轴的平面。

My question is how can I generalize the plane fitting to full 3d ?我的问题是如何将平面拟合推广到全 3d I want to add the fourth parameter in order to get the full equation a X +b Y +c*Z + d how can I avoid the trivial (0,0,0,0) solution?我想添加第四个参数以获得完整的方程 a X +b Y +c*Z + d 如何避免琐碎的 (0,0,0,0) 解?

Thanks!谢谢!

The Code I'm using:我正在使用的代码:

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_

Update更新

This functionality is now integrated in https://github.com/daavoo/pyntcloud and makes the plane fitting process much simplier:此功能现已集成在https://github.com/daavoo/pyntcloud 中,并使平面拟合过程更加简单:

Given a point cloud:给定一个点云:

在此处输入图片说明

You just need to add a scalar field like this:你只需要像这样添加一个标量字段:

is_floor = cloud.add_scalar_field("plane_fit")

Wich will add a new column with value 1 for the points of the plane fitted. Wich 将为拟合平面的点添加一个值为 1 的新列。

You can visualize the scalar field:您可以将标量场​​可视化:

在此处输入图片说明


Old answer旧答案

I think that you could easily use PCA to fit the plane to the 3D points instead of regression.我认为您可以轻松地使用PCA将平面拟合到 3D 点而不是回归。

Here is a simple PCA implementation:这是一个简单的 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

And here is how you could fit the points to a plane:这是将点拟合到平面的方法:

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    

However as this method is sensitive to outliers you could use RANSAC to make the fit robust to outliers.但是,由于此方法对异常值很敏感,因此您可以使用RANSAC使拟合对异常值具有鲁棒性。

There is a Python implementation of ransac here .有一个Python实现RANSAC的位置

And you should only need to define a Plane Model class in order to use it for fitting planes to 3D points.您应该只需要定义一个平面模型类,以便使用它来将平面拟合到 3D 点。

In any case if you can clean the 3D points from outliers (maybe you could use a KD-Tree SOR filter to that) you should get pretty good results with PCA.在任何情况下,如果您可以从异常值中清除 3D 点(也许您可以使用 KD-Tree SOR 过滤器),您应该使用 PCA 获得非常好的结果。

Here is an implementation of an SOR :这是一个SOR的实现:

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

You could feed the function with a KDtree of your 3D points computed maybe using this implementation您可以使用可能使用此实现计算的 3D 点的 KDtree 来提供函数

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()

you need to install python-pcl first.你需要先安装python-pcl Feel free to play with the parameters.随意使用参数。 points here is a nx3 numpy array with n 3d points.这里的点是一个带有 n 个 3d 点的 nx3 numpy 数组。 Model will be [a, b, c, d] such that ax + by + cz + d = 0模型将是 [a, b, c, d] 使得 ax + by + cz + d = 0

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM