将3D平面转换为2D

时间:2014-05-22 18:08:01

标签: 3d geometry transform point-clouds

我有一组点云在3D中形成一个平面,我是从RANSAC平面拟合中获得的。对于某些特定类型的数据分析,我需要将其转换为2D问题,以便我可以使所有z值几乎相同。假设平面的方程是ax + by + cz + 1 = 0。我的问题是:

  1. 如何从原始点云数据中获取a,b,c的值?最小二乘法是最好的方法还是有办法从RANSAC拟合中获得这些值?
  2. 从一些教程中我得到了关于执行以下步骤的想法:转换为质心,围绕x轴旋转,绕y轴旋转,然后再次取消平移。是否有意义? 3.如何根据a,b,c?
  3. 获得旋转角度

    我需要将3D平面转换为2D的一般概念,以便所有z坐标值都相同。

3 个答案:

答案 0 :(得分:4)

您需要更改基础。

让我们创建由三个向量 X Y Z 组成的新的标准正交基。

你的新Z将是飞机的法线向量,(a,b,c)。通过将所有三个分量除以Sqrt(a ^ 2 + b ^ 2 + c ^ 2)来归一化它。

然后取一个任意向量,与第一个不平行,让 U 。计算点积 U.Z 并从 U 中减去( U.Z )。 Z 。将生成的矢量标准化为 X

最后,计算叉积 Y = Z / \ X

然后,通过计算点积( Pi.X Pi.Y )将每个点 Pi 转换为2D。

答案 1 :(得分:1)

  1. 构建协变矩阵:
    • 找到所有积分的平均值
    • 从每个点减去
    • matrix(i,j)=sum(p[i]*p[j])p[1],p[2],p[3]是点p的坐标
  2. 查找矩阵的特征向量和值
  3. 对应于最小特征值的向量是Z,其余为-X和Y

答案 2 :(得分:1)

有两个问题需要解决。

问题1:给定一组3D点,表征它们所在的平面。

问题2:给定一组3D点,将它们投射到该平面上。

问题2由其他答案解决;我想改变基础方法。

对于问题1,您需要找到该地点的法线。为此,从点云中选择一些随机三元组,找到每个形式的三角形的法线。取这些法线的平均值来估计飞机的法线。用它来形成新的标准正交基础的第一个元素。

这里有一些生成测试集的代码。 [Point3D不是生产质量;无论如何,你可能会重复使用其他东西。]

#include <iostream>
#include <random>
#include <vector>
#include <algorithm>    // std::random_shuffle
#include <numeric>    // std::random_shuffle
#include <math.h>


struct Point3D
{
    Point3D(double a = 0, double b  = 0, double c = 0)
    {
        x_[0] = a;
        x_[1] = b;
        x_[2] = c;
    }
    int maxIndex() const
    {
        int maxIndex = 0;
        double maxVal = 0;
        for (int i = 0; i != 3;  ++i)
        {
            if (fabs(x_[i]) > maxVal)
            {
                maxIndex = i;
                maxVal = fabs(x_[i]);
            }
        }
        return maxIndex;
    }
    double lengthSquared() const
    {
        return std::accumulate(std::begin(x_), std::end(x_), 0.0, [](double sum, double x) { return sum + x*x; });
    }
    double length() const { return sqrt(lengthSquared()); }
    double dot(const Point3D& other) const
    {
        double d = 0;
        for (int i = 0; i != 3; ++i)
            d += x_[i] * other.x_[i];
        return d;
    }
    const Point3D& add(const Point3D& other)
    {
        for (int i = 0; i != 3; ++i)
            x_[i] += other.x_[i];
        return *this;
    }
    const Point3D&  subtract(const Point3D& other)
    {
        for (int i = 0; i != 3; ++i)
            x_[i] -= other.x_[i];
        return *this;
    }
    const Point3D&  multiply(double scalar)
    {
        for (int i = 0; i != 3; ++i)
            x_[i] *= scalar;
        return *this;
    }
    const Point3D&  divide(double scalar)
    {
        return multiply(1 / scalar);
    }
    const Point3D&  unitise()
    {
        return divide(length());
    }
    // Returns the component of other parallel to this.
    Point3D  projectionOfOther(const Point3D& other) const
    {
        double factor = dot(other) / lengthSquared();
        Point3D projection(*this);
        projection.multiply(factor);
        return projection;
    }

    double x_[3];
};

void print(const Point3D& p)
{
    std::cout << p.x_[0] << ", " << p.x_[1] << ", " << p.x_[2] << ", " << std::endl;
}

typedef Point3D Point3D;

Point3D difference(const Point3D& a, const Point3D& b)
{
    return  Point3D(a).subtract(b);
}
Point3D sum(const Point3D& a, const Point3D& b)
{
    return  Point3D(a).add(b);
}

Point3D crossProduct(const Point3D& v1 ,const Point3D& v2)
{
    return Point3D(v1.x_[1] * v2.x_[2] - v1.x_[2] * v2.x_[1],
                   v1.x_[2] * v2.x_[0] - v1.x_[0] * v2.x_[2],
                   v1.x_[0] * v2.x_[1] - v1.x_[1] * v2.x_[0]
        );
}

Point3D crossProductFromThreePoints(std::vector<Point3D>::iterator first)
{
    std::vector<Point3D>::iterator second = first + 1;
    std::vector<Point3D>::iterator third = first + 2;
    return crossProduct(difference(*third, *first), difference(*third, *second));
}


std::vector<Point3D> makeCloud(int numPoints);

Point3D determinePlaneFromCloud(const std::vector<Point3D>& cloud)
{
    std::vector<Point3D> randomised(cloud);
    int extra = 3- randomised.size() % 3;
    // Might not need this shuffle; but should reduce problems should neighbouring points in the list tend to be close in 3D space 
    // giving small triangles that might have normals a long way from the main plane.
    random_shuffle(begin(randomised), end(randomised));
    randomised.insert(end(randomised), begin(randomised), begin(randomised) + extra);
    int numTriangles = randomised.size() / 3;
    Point3D normals;
    int primaryDir = -1;
    for (int tri = 0; tri != numTriangles; ++tri )
    {
        Point3D cp = crossProductFromThreePoints(begin(randomised) + tri * 3);
        cp.unitise();
        // The first triangle defines the component we'll look at to determine orientation.
        if (primaryDir < 0)
            primaryDir = cp.maxIndex();
        // Flip the orientation if needed.
        if (cp.x_[primaryDir] < 0)
            cp.multiply(-1);
        normals.add(cp);
    }
    // Now we have a candidate normal to the plane.
    // Scale it so that we have normal.p = 1  for each p in the cloud. This is only an average in the case where the p are not completely planar.
    double sum = std::accumulate(std::begin(cloud), std::end(cloud), 0.0, [normals](double sum, const Point3D& p) { return sum + p.dot(normals); });
    double meanC = sum / cloud.size();
    normals.divide(meanC);
    return normals;

}
// Return an orthonormal basis whose first direction is aligned with v1
std::vector<Point3D> createOrthonormalBasis(const Point3D& v1)
{
    // Need a complete basis as a starting point.
    std::vector<Point3D> basis(3);
    basis[0] = v1;

    // This gives the direction in the current basis with which v1 is closest aligned.
    int mi = v1.maxIndex();
    // start with the other vectors being the other principle directions from mi; this ensures that the basis is complete.
    basis[1].x_[(mi + 1) % 3 ] = 1;
    basis[2].x_[(mi + 2) % 3] = 1;

    // Now perform a Gram–Schmidt process to make that an Orthonormal basis.

    for (int i = 0; i != 3; ++i)
    {
        for (int j = 0; j != i; ++j)
        {
            basis[i].subtract(basis[j].projectionOfOther(basis[i]));
        }
        basis[i].unitise();
    }

    return basis;

}

Point3D convertBasis(const Point3D& p, const std::vector<Point3D>& newBasis)
{
    Point3D newCoords;
    for (int i = 0; i != 3; ++i)
    {
        newCoords.x_[i] = p.dot(newBasis[i]);
    }
    return newCoords;
}

int main()
{
    std::vector<Point3D> cloud = makeCloud(99);
    Point3D normal = determinePlaneFromCloud(cloud);

    print(normal);

    // Now we want to express each point p in the form:
    // p = n + au + bv
    // where n.u = n.v = u.v = 0

    std::vector<Point3D> basis = createOrthonormalBasis(normal);
    // The [1] and [2] components are now the 2D locations in the desired plane.
    for each (Point3D p in cloud)
    {
        Point3D mapped = convertBasis(p, basis);
        print(mapped);
    }

    return 0;
}


std::vector<Point3D> makeCloud(int numPoints)
{
    std::default_random_engine generator;
    std::uniform_real_distribution<double> distribution(0.0, 1.0);
    std::vector<Point3D> cloud(numPoints);
    Point3D planeNormal;
    for (int i = 0; i != 2; ++i)
        planeNormal.x_[i] = distribution(generator);
    // Dodgy!
    while (planeNormal.x_[2] < 1e-2)
        planeNormal.x_[2] = distribution(generator);

    for (int i = 0; i != numPoints; ++i)
    {
        for (int j = 0; j != 2; ++j)
            cloud[i].x_[j] = distribution(generator);
        cloud[i].x_[2] = (1 - cloud[i].x_[0] * planeNormal.x_[0] - cloud[i].x_[1] * planeNormal.x_[1]) / planeNormal.x_[2];
        // Add in some noise.
        //        for (int j = 0; j != 3; ++j) cloud[i].x_[j] += distribution(generator) * 0.05;
    }
    planeNormal.unitise();
    return cloud;
}