我有一组点云在3D中形成一个平面,我是从RANSAC平面拟合中获得的。对于某些特定类型的数据分析,我需要将其转换为2D问题,以便我可以使所有z值几乎相同。假设平面的方程是ax + by + cz + 1 = 0。我的问题是:
我需要将3D平面转换为2D的一般概念,以便所有z坐标值都相同。
答案 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)
matrix(i,j)=sum(p[i]*p[j])
(p[1],p[2],p[3]
是点p的坐标答案 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;
}