将旋转矩阵应用于 xy 坐标

Apply a rotation matrix to xy coordinates

我有 xy 坐标表示给定 space 上的主题。它是从另一个点引用的,因此 偏离中心 。由于 longitudinal axes 未沿 x-axis.

对齐

下面随机生成的 ellipse 说明了这一点:

import numpy as np
from matplotlib.pyplot import scatter

xx = np.array([-0.51, 51.2])
yy = np.array([0.33, 51.6])
means = [xx.mean(), yy.mean()]  
stds = [xx.std() / 3, yy.std() / 3]
corr = 0.8         # correlation
covs = [[stds[0]**2          , stds[0]*stds[1]*corr], 
    [stds[0]*stds[1]*corr,           stds[1]**2]] 

m = np.random.multivariate_normal(means, covs, 1000).T
scatter(m[0], m[1])

拉直 我正在考虑将矢量应用于 rotation matrix 的坐标。

这样的东西行得通吗?

angle = 65.
theta = (angle/180.) * np.pi

rotMatrix = np.array([[np.cos(theta), -np.sin(theta)], 
                     [np.sin(theta),  np.cos(theta)]])

这似乎也是一个愚蠢的问题,但有没有办法确定 xy 坐标的结果 vector 是否垂直?或者你只需​​要玩玩 rotation angle?

如果两条线的斜率相乘等于-1,则说明它们是垂直的。 另一种情况是正确的,即一个斜率为 0 而另一个未定义(一条完美的水平线和一条完美的垂直线)。

您可以使用 sklearn.decomposition.PCA(主成分分析)和 n_components=2 来提取旋转点云所需的最小角度,使其主轴水平。

可运行示例

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

np.random.seed(1)

xx = np.array([-0.51, 51.2])
yy = np.array([0.33, 51.6])
means = [xx.mean(), yy.mean()]  
stds = [xx.std() / 3, yy.std() / 3]
corr = 0.8         # correlation
covs = [[stds[0]**2,       stds[0]*stds[1]*corr], 
        [stds[0]*stds[1]*corr, stds[1]**2]]

m = np.random.multivariate_normal(means, covs, 1000)

pca = PCA(2)

# This was in my first answer attempt: fit_transform works fine, but it randomly 
# flips (mirrors) points across one of the principal axes.
# m2 = pca.fit_transform(m)

# Workaround: get the rotation angle from the PCA components and manually 
# build the rotation matrix.

# Fit the PCA object, but do not transform the data
pca.fit(m)

# pca.components_ : array, shape (n_components, n_features)
# cos theta
ct = pca.components_[0, 0]
# sin theta
st = pca.components_[0, 1]

# One possible value of theta that lies in [0, pi]
t = np.arccos(ct)

# If t is in quadrant 1, rotate CLOCKwise by t
if ct > 0 and st > 0:
    t *= -1
# If t is in Q2, rotate COUNTERclockwise by the complement of theta
elif ct < 0 and st > 0:
    t = np.pi - t
# If t is in Q3, rotate CLOCKwise by the complement of theta
elif ct < 0 and st < 0:
    t = -(np.pi - t)
# If t is in Q4, rotate COUNTERclockwise by theta, i.e., do nothing
elif ct > 0 and st < 0:
    pass

# Manually build the ccw rotation matrix
rotmat = np.array([[np.cos(t), -np.sin(t)], 
                   [np.sin(t),  np.cos(t)]])

# Apply rotation to each row of m
m2 = (rotmat @ m.T).T

# Center the rotated point cloud at (0, 0)
m2 -= m2.mean(axis=0)

fig, ax = plt.subplots()
plot_kws = {'alpha': '0.75',
            'edgecolor': 'white',
            'linewidths': 0.75}
ax.scatter(m[:, 0], m[:, 1], **plot_kws)
ax.scatter(m2[:, 0], m2[:, 1], **plot_kws)

输出

警告:pca.fit_transform()有时会翻转(镜像)点云

主成分可以随机显示为正数或负数。在某些情况下,您的点云可能看起来上下颠倒,甚至在其主轴之一上镜像。 (要对此进行测试,请更改随机种子并重新 运行 代码,直到您观察到翻转。)有一个深入的讨论 here(基于 R,但数学是相关的)。要更正此问题,您必须将 fit_transform 行替换为手动翻转一个或两个组件的符号,然后将符号翻转后的组件矩阵乘以点云数组。

这里确实有一个非常有用的概念,它是由矩阵 A 执行的向量 v 的线性变换。如果您将散点视为源自 (0,0) 的向量的尖端,则很容易将它们旋转任意角度 theta。执行这种 theta 旋转的矩阵将是

A = [[cos(theta) -sin(theta]
     [sin(theta)  cos(theta)]]

显然,当 theta 为 90 度时,结果为

A = [[ 0 1]
     [-1 0]]

要应用旋转,您只需执行矩阵乘法 w = A v

有了这个,当前的目标是对存储在 m 中的向量执行矩阵乘法,x,y 提示为 m[0],m[1]。旋转后的矢量将存储在 m2 中。下面是这样做的相关代码。请注意,我已转置 m 以便更轻松地计算矩阵乘法(使用 @ 执行)并且旋转角度逆时针旋转 90 度。

import numpy as np
import matplotlib.pyplot as plt

xx = np.array([-0.51, 51.2])
yy = np.array([0.33, 51.6])
means = [xx.mean(), yy.mean()]  
stds = [xx.std() / 3, yy.std() / 3]
corr = 0.8         # correlation
covs = [[stds[0]**2          , stds[0]*stds[1]*corr], 
    [stds[0]*stds[1]*corr,           stds[1]**2]] 

m = np.random.multivariate_normal(means, covs, 1000).T
plt.scatter(m[0], m[1])

theta_deg = 90
theta_rad = np.deg2rad(theta_deg)
A = np.matrix([[np.cos(theta_rad), -np.sin(theta_rad)],
               [np.sin(theta_rad), np.cos(theta_rad)]])
m2 = np.zeros(m.T.shape)

for i,v in enumerate(m.T):
  w = A @ v.T
  m2[i] = w
m2 = m2.T

plt.scatter(m2[0], m2[1])

这导致了旋转的散点图: 你可以确定旋转后的版本通过线性变换正好逆时针旋转90度。

编辑

要找到您需要应用的旋转角度以使散点图与 x 轴对齐,一个好的方法是使用 numpy.polyfit 找到散点数据的线性近似值。这通过提供 slope 和 y 轴的截距 b 产生线性函数。然后用斜率的 arctan 函数得到旋转角度,并像以前一样计算变换矩阵。您可以通过将以下部分添加到代码中来完成此操作

slope, b = np.polyfit(m[1], m[0], 1)
x = np.arange(min(m[0]), max(m[0]), 1)
y_line = slope*x + b
plt.plot(x, y_line, color='r')
theta_rad = -np.arctan(slope)

以及您正在寻找的情节的结果

编辑 2

因为@Peter Leimbigler 指出 numpy.polyfit 没有找到正确的分散数据的全局方向,我认为你可以通过对 xy 部分数据。这是为了找到另一个斜率,称为 slope2(现在用绿色表示)来应用旋转。如此简单,

slope, b = np.polyfit(m[1], m[0], 1)
x = np.arange(min(m[0]), max(m[0]), 1)
y_line = slope*x + b
slope2 = np.mean(m[1])/np.mean(m[0])
y_line2 = slope2*x + b
plt.plot(x, y_line, color='r')
plt.plot(x, y_line2, color='g')
theta_rad = -np.arctan(slope2)

通过对旋转矩阵应用线性变换,你得到