将法向量旋转到轴平面上

11

我有一组数据点在三维空间中,它们显然都落在一个特定的平面上。 我使用主成分分析(PCA)计算平面参数。 PCA的第三个分量给出平面的法向量(最弱分量)。

接下来我想要做的是将所有点转换到所述平面上并在二维平面上查看它。

我的想法是进行以下操作:

  • 在平面上找到一个中心点(平均点)
  • 从所有数据点中减去它以使它们围绕原点排列
  • 旋转法向量,使其变为 (0,0,-1)
  • 将此旋转应用于所有数据点
  • 使用正交投影(基本上跳过z轴)

现在我卡在寻找正确的旋转操作上。 我尝试使用acos或atan,并设置两个旋转矩阵。 似乎这两种方法(使用acos、使用atan)都给了我错误的结果。也许你可以在这里帮助我!

以下是 Matlab 代码:

b = atan(n(1) / n(2));
rotb = [cos(b) -sin(b) 0; sin(b) cos(b) 0; 0 0 1];
n2 = n * rotb;
a = atan(n(1) / n(3));
rota = [cos(a) 0 sin(a); 0 1 0; -sin(a) 0 cos(a)];
n3 = n2 * rotaows:

我期望n2的y分量为零。然而,对于向量(-0.6367,0.7697,0.0467),这个期望已经失败。


为什么不直接将所有点投影到平面上,然后旋转整个图形,这样你就可以只使用它们的xz(或xy)坐标绘制点了呢? - Jasper Bekkers
那么,或者直接将相机固定在飞机上,使其直接对着它。 - Jasper Bekkers
摄像机的想法很好。您可以将摄像机从平面的原点沿着法线方向移动一定距离,然后将其对准原点。当然,这假设您已经有了摄像机投影代码,可以使用旋转或向量来完成。 - Nosredna
好的建议。可惜我那里没有相机代码,而且我也不想引入任何我无法完全控制的操作。 - ypnos
4个回答

14
如果你有一个平面,它就会有一个法向量和一个原点。我不会做任何“旋转”。你离答案只差几个向量运算。
  • 让我们把你的平面法向量称为新的 z 轴。
  • 通过用旧的 x 轴与新的 z 轴(平面的法向量)进行叉乘,可以生成新的 y 轴。
  • 通过用新的 z 与新的 y 进行叉乘来生成新的 x 轴。
  • 将所有新轴向量变成单位向量(长度为1)。
  • 对于每个点,创建一个从新原点到该点的向量(点-平面原点的向量减法)。只需用新的 x 和新的 y 单位向量做点积,就可以获得一对(x,y)可以绘图!
如果你已经有了叉积和点积函数,那么这只需要几行代码。我知道它有效,因为我写的大部分 3D 视频游戏都是这样工作的。
提示:
  • 注意向量指向哪个方向。如果它们指错了方向,可以取反结果向量或更改叉积顺序。
  • 如果你的平面法向量恰好与原始 x 轴相同,则会出现问题。

1
太棒了!这解决了我一直以来处理旋转误差(通过三角函数)遇到的许多问题。我想我需要学习线性代数课程。 - Kaiged

1

怎么样:

将法向量分解为一个在XY平面内的向量和一个Z向量。然后绕Z轴旋转,使XY向量与其中一个轴对齐。接着找到法向量与Z轴的点积,并沿着你对齐的X或Y轴进行旋转。

这个想法是将法向量与Z轴对齐,这样你的平面现在就是XY平面了。


1

这是使用Python编写的接受答案:

import numpy as np

def rotate(points, normal, around):
  # Let's rotate the points such that the normal is the new Z axis
  # Following https://dev59.com/UXNA5IYBdhLWcg3wSrua
  old_x_axis = np.array([1, 0, 0])

  z_axis = normal
  y_axis = np.cross(old_x_axis, z_axis)
  x_axis = np.cross(z_axis, y_axis)
  
  axis = np.stack([x_axis, y_axis, z_axis])

  return np.dot(points - around, axis.T)

points = np.array([
    [0, 1, 1],
    [0, 1, 0.2],
    [1, 0, -7]
])

v1 = points[1] - points[0]
v2 = points[2] - points[0]

normal = np.cross(v1, v2)
print("rotated points", rotate(points, normal, points[0]))

0

虽然还有其他有趣的回答,但这是我们在等待答案时想出来的解决方案:

function roti = magic_cosini(n)
    b = acos(n(2) / sqrt(n(1)*n(1) + n(2)*n(2)));
    bwinkel = b * 360 / 2 / pi;
    if (n(1) >= 0)
        rotb = [cos(-b) -sin(-b) 0; sin(-b) cos(-b) 0; 0 0 1];
    else
        rotb = [cos(-b) sin(-b) 0; -sin(-b) cos(-b) 0; 0 0 1];
    end
    n2 = n * rotb;
    a = acos(n2(3) / sqrt(n2(2)*n2(2) + n2(3)*n2(3)));
    awinkel = a * 360 / 2 / pi;
    rota = [1 0 0; 0 cos(-a) -sin(-a); 0 sin(-a) cos(-a)];
    roti = rotb * rota;

(它返回一个希望正确的双旋转矩阵)

我们之前存在的缺陷并在此处修复的是特别处理X分量的符号,这在余弦计算中没有涵盖。这使我们一次旋转时旋转方向错误(以180°-角度旋转)。

我希望我也能找到时间尝试Nosredna的解决方案!避免三角函数总是好的。


我想提一下,每当你选择三角函数而不是向量函数,并且发现自己使用 atan 函数时,请尝试看看是否可以使用 atan2 函数。它接受 x 和 y 作为参数,而不是单个参数。它将答案放在正确的象限中,因此您可以避免通常会出现的条件混乱。http://en.wikipedia.org/wiki/Atan2 - Nosredna
是的,atan2非常棒,以前它为我节省了很多时间。一个好的提示。 - ypnos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接