web-dev-qa-db-fra.com

Plan des moindres carrés 3D

Quel est l'algorithme pour calculer un plan des moindres carrés dans l'espace (x, y, z), étant donné un ensemble de points de données 3D? En d'autres termes, si j'avais un tas de points comme (1, 2, 3), (4, 5, 6), (7, 8, 9), etc., comment procéder pour calculer le plan le mieux adapté f (x, y) = ax + par + c? Quel est l'algorithme pour extraire a, b et c d'un ensemble de points 3D?

25
soapergem

Si vous avez n points de données (x [i], y [i], z [i]), calculez la matrice symétrique 3x3 A dont les entrées sont:

sum_i x[i]*x[i],    sum_i x[i]*y[i],    sum_i x[i]
sum_i x[i]*y[i],    sum_i y[i]*y[i],    sum_i y[i]
sum_i x[i],         sum_i y[i],         n

Calculez également le vecteur à 3 éléments b:

{sum_i x[i]*z[i],   sum_i y[i]*z[i],    sum_i z[i]}

Résoudre ensuite Ax = b pour les A et b donnés. Les trois composantes du vecteur solution sont les coefficients du plan d'ajustement le moins carré {a, b, c}.

Notez qu'il s'agit de l'ajustement des "moindres carrés ordinaires", qui n'est approprié que lorsque z devrait être une fonction linéaire de x et y. Si vous recherchez plus généralement un "plan le mieux adapté" dans 3 espaces, vous voudrez peut-être en savoir plus sur les moindres carrés "géométriques".

Notez également que cela échouera si vos points sont sur une ligne, comme le sont vos exemples de points.

41
Stephen Canon

sauf si quelqu'un me dit comment taper des équations ici, permettez-moi de noter les calculs finaux que vous devez faire:

tout d'abord, étant donné les points r_i\n\R, i = 1..N, calculez le centre de masse de tous les points:

r_G = \frac{\sum_{i=1}^N r_i}{N}

puis, calculez le vecteur normal n, qui avec le vecteur de base r_G définit le plan en calculant la matrice 3x3 A comme

A = \sum_{i=1}^N (r_i - r_G)(r_i - r_G)^T

avec cette matrice, le vecteur normal n est maintenant donné par le vecteur propre de A correspondant à la valeur propre minimale de A.

Pour en savoir plus sur les paires vecteur propre/valeur propre, utilisez n'importe quelle bibliothèque d'algèbre linéaire de votre choix.

Cette solution est basée sur le théorème de Rayleight-Ritz pour la matrice hermitienne A.

5
josch

L'équation pour un avion est: ax + by + c = z. Configurez donc des matrices comme celle-ci avec toutes vos données:

    x_0   y_0   1  
A = x_1   y_1   1  
          ... 
    x_n   y_n   1  

Et

    a  
x = b  
    c

Et

    z_0   
B = z_1   
    ...   
    z_n

En d'autres termes: Axe = B. Maintenant, résolvez pour x qui sont vos coefficients. Mais puisque (je suppose) vous avez plus de 3 points, le système est surdéterminé, vous devez donc utiliser le pseudo-inverse gauche. La réponse est donc:

a 
b = (A^T A)^-1 A^T B
c

Et voici un simple code Python avec un exemple:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np

N_POINTS = 10
TARGET_X_SLOPE = 2
TARGET_y_SLOPE = 3
TARGET_OFFSET  = 5
EXTENTS = 5
NOISE = 5

# create random data
xs = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
ys = [np.random.uniform(2*EXTENTS)-EXTENTS for i in range(N_POINTS)]
zs = []
for i in range(N_POINTS):
    zs.append(xs[i]*TARGET_X_SLOPE + \
              ys[i]*TARGET_y_SLOPE + \
              TARGET_OFFSET + np.random.normal(scale=NOISE))

# plot raw data
plt.figure()
ax = plt.subplot(111, projection='3d')
ax.scatter(xs, ys, zs, color='b')

# do fit
tmp_A = []
tmp_b = []
for i in range(len(xs)):
    tmp_A.append([xs[i], ys[i], 1])
    tmp_b.append(zs[i])
b = np.matrix(tmp_b).T
A = np.matrix(tmp_A)
fit = (A.T * A).I * A.T * b
errors = b - A * fit
residual = np.linalg.norm(errors)

print "solution:"
print "%f x + %f y + %f = z" % (fit[0], fit[1], fit[2])
print "errors:"
print errors
print "residual:"
print residual

# plot plane
xlim = ax.get_xlim()
ylim = ax.get_ylim()
X,Y = np.meshgrid(np.arange(xlim[0], xlim[1]),
                  np.arange(ylim[0], ylim[1]))
Z = np.zeros(X.shape)
for r in range(X.shape[0]):
    for c in range(X.shape[1]):
        Z[r,c] = fit[0] * X[r,c] + fit[1] * Y[r,c] + fit[2]
ax.plot_wireframe(X,Y,Z, color='k')

ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()

fitted plane

5
Ben

Voir 'Least Squares Fitting of Data' de David Eberly pour savoir comment j'ai trouvé celui-ci pour minimiser l'ajustement géométrique (distance orthogonale des points au plan).

bool Geom_utils::Fit_plane_direct(const arma::mat& pts_in, Plane& plane_out)
{
    bool success(false);
    int K(pts_in.n_cols);
    if(pts_in.n_rows == 3 && K > 2)  // check for bad sizing and indeterminate case
    {
        plane_out._p_3 = (1.0/static_cast<double>(K))*arma::sum(pts_in,1);
        arma::mat A(pts_in);
        A.each_col() -= plane_out._p_3; //[x1-p, x2-p, ..., xk-p]
        arma::mat33 M(A*A.t());
        arma::vec3 D;
        arma::mat33 V;
        if(arma::eig_sym(D,V,M))
        {
            // diagonalization succeeded
            plane_out._n_3 = V.col(0); // in ascending order by default
            if(plane_out._n_3(2) < 0)
            {
                plane_out._n_3 = -plane_out._n_3; // upward pointing
            }
            success = true;
        }
    }
    return success;
}

Temporisé à 37 micro secondes ajustement d'un avion à 1000 points (programme Windows 7, i7, 32 bits)

enter image description here

4
Patrick K

Comme pour toute approche des moindres carrés, vous procédez comme suit:

Avant de commencer à coder

  1. Notez une équation pour un avion dans un certain paramétrage, dites 0 = ax + by + z + d Dans vos paramètres (a, b, d).

  2. Trouvez une expression D(\vec{v};a, b, d) pour la distance d'un point arbitraire \vec{v}.

  3. Notez la somme S = \sigma_i=0,n D^2(\vec{x}_i) et simplifiez jusqu'à ce qu'elle soit exprimée en termes de sommes simples des composants de v comme \sigma v_x, \sigma v_y^2, \sigma v_x*v_z ...

  4. Notez les expressions de minimisation par paramètre dS/dx_0 = 0, dS/dy_0 = 0 ... qui vous donne un ensemble de trois équations en trois paramètres et les sommes de l'étape précédente.

  5. Résolvez cet ensemble d'équations pour les paramètres.

(ou pour des cas simples, recherchez simplement le formulaire). L'utilisation d'un package d'algèbre symbolique (comme Mathematica) pourrait vous faciliter la vie.

Le codage

  • Écrivez du code pour former les sommes nécessaires et trouvez les paramètres du dernier ensemble ci-dessus.

Alternatives

Notez que si vous n'aviez en fait que trois points, vous feriez mieux de trouver l'avion qui les traverse.

De plus, si la solution analytique n'est pas réalisable (pas le cas pour un avion, mais possible en général), vous pouvez effectuer les étapes 1 et 2, et utiliser un Monte Carlo minimizer sur la somme à l'étape 3.

CGAL::linear_least_squares_fitting_3

La fonction linear_least_squares_fitting_3 calcule la ligne ou le plan 3D le mieux ajusté (dans le sens des moindres carrés) d'un ensemble d'objets 3D tels que des points, des segments, des triangles, des sphères, des boules, des cuboïdes ou des tétraèdres.

http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Principal_component_analysis_ref/Function_linear_least_squares_fitting_3.html

2
Alessandro Jacopson

Cela se réduit au problème Total Least Squares , qui peut être résolu en utilisant la décomposition SVD .

Code C++ utilisant OpenCV:

float fitPlaneToSetOfPoints(const std::vector<cv::Point3f> &pts, cv::Point3f &p0, cv::Vec3f &nml) {
    const int SCALAR_TYPE = CV_32F;
    typedef float ScalarType;

    // Calculate centroid
    p0 = cv::Point3f(0,0,0);
    for (int i = 0; i < pts.size(); ++i)
        p0 = p0 + conv<cv::Vec3f>(pts[i]);
    p0 *= 1.0/pts.size();

    // Compose data matrix subtracting the centroid from each point
    cv::Mat Q(pts.size(), 3, SCALAR_TYPE);
    for (int i = 0; i < pts.size(); ++i) {
        Q.at<ScalarType>(i,0) = pts[i].x - p0.x;
        Q.at<ScalarType>(i,1) = pts[i].y - p0.y;
        Q.at<ScalarType>(i,2) = pts[i].z - p0.z;
    }

    // Compute SVD decomposition and the Total Least Squares solution, which is the eigenvector corresponding to the least eigenvalue
    cv::SVD svd(Q, cv::SVD::MODIFY_A|cv::SVD::FULL_UV);
    nml = svd.vt.row(2);

    // Calculate the actual RMS error
    float err = 0;
    for (int i = 0; i < pts.size(); ++i)
        err += powf(nml.dot(pts[i] - p0), 2);
    err = sqrtf(err / pts.size());

    return err;
}
1
Michael Litvin

Il semble que tout ce que vous voulez faire soit une régression linéaire avec 2 régresseurs. La page wikipedia sur le sujet devrait vous dire tout ce que vous devez savoir et puis certains.

0
Jonathan Chang

Tout ce que vous aurez à faire est de résoudre le système d'équations.

Si ce sont vos points: (1, 2, 3), (4, 5, 6), (7, 8, 9)

Cela vous donne les équations:

3=a*1 + b*2 + c
6=a*4 + b*5 + c
9=a*7 + b*8 + c

Votre question devrait donc être: comment résoudre un système d'équations?

Par conséquent, je recommande de lire this SO question.

Si j'ai mal compris votre question, faites-le nous savoir.

[~ # ~] modifier [~ # ~] :

Ignorez ma réponse car vous vouliez probablement dire autre chose.

0
André Hoffmann