[英]Estimate the rotation between two 2D point clouds
I have two 2D point clouds with an equal number of elements.我有两个元素数量相同的二维点云。 For these elements I know their correspondence, ie for each point in PC1 I know the corresponding element in PC2 and vice versa.对于这些元素,我知道它们的对应关系,即对于 PC1 中的每个点,我知道 PC2 中的相应元素,反之亦然。
I would now like to estimate the rotation between these two point clouds.我现在想估计这两个点云之间的旋转。 That is, I would like to find the angle alpha
by which I must rotate all points in PC1 around the origin such that the distance between corresponding points in PC1 and PC2 is minimized.也就是说,我想找到角度alpha
,我必须围绕原点旋转 PC1 中的所有点,以使 PC1 和 PC2 中对应点之间的距离最小化。
I can solve this using scipy's linear optimizer (see below);我可以使用 scipy 的线性优化器来解决这个问题(见下文); however, this optimization sits inside a loop along the critical path of my code and is the current bottleneck.然而,这种优化位于我代码关键路径的循环内,是当前的瓶颈。
import numpy as np
from scipy.optimize import minimize_scalar
from math import sin, cos
# generate some data for demonstration purposes
# points in each point cloud are ordered by correspondence
num_points = 10
distance = np.random.rand(num_points) * 3
radii = np.random.rand(num_points) * 2*np.pi
pc1 = distance[:, None] * np.stack([np.cos(radii), np.sin(radii)], axis=1)
distance = np.random.rand(num_points) * 3
radii = np.random.rand(num_points) * 2*np.pi
pc2 = distance[:, None] * np.stack([np.cos(radii), np.sin(radii)], axis=1)
# solve using scipy
def score(alpha):
rot_matrix = np.array([
[cos(alpha), -sin(alpha)],
[sin(alpha), cos(alpha)]
])
pc1_rotated = (rot_matrix @ pc1.T).T
sum_of_squares = np.sum((pc2 - pc1_rotated)**2, axis=1)
mse = np.mean(sum_of_squares)
return mse
# simple solution via scipy
result = minimize_scalar(
score,
bounds=(0, 2*np.pi),
method="bounded",
options={"maxiter": 1000},
)
if result.success:
print(f"Best angle: {result.x}")
else:
raise RuntimeError(f"IK failed. Reason: {result.message}")
Is there a faster (potentially analytic) solution to this problem?这个问题有更快的(潜在的分析)解决方案吗?
Since minimize_scalar
only uses derivative-free methods, the optimization runtime depends heavily on the time needed to evaluate your objective function score
.由于minimize_scalar
仅使用无导数方法,因此优化运行时在很大程度上取决于评估目标函数score
所需的时间。 Consequently, I'd recommend accelerating this function as much as possible.因此,我建议尽可能加速此功能。
Let's time your function and the optimizer as benchmark reference:让我们为您的函数和优化器计时作为基准参考:
In [68]: %timeit score(0.5)
20.2 µs ± 203 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [69]: %timeit result = minimize_scalar(score,bounds=(0, 2*np.pi),method="bounded",options={"maxiter": 1000})
415 µs ± 7.27 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Firstly, note that (rot_matrix @ pc1.T).T
is the same as pc1 @ rot_matrix.T
, ie we only need to transpose one matrix instead of two.首先,请注意(rot_matrix @ pc1.T).T
与pc1 @ rot_matrix.T
相同,即我们只需要转置一个矩阵而不是两个。
Next, note that -sin(alpha) = cos(alpha + 5*pi/2)
and sin(alpha) = cos(alpha + 3*pi/2)
.接下来,请注意-sin(alpha) = cos(alpha + 5*pi/2)
和sin(alpha) = cos(alpha + 3*pi/2)
。 This means that we only need one function call of np.cos
to create the rot_matrix
instead of four calls of math.sin
or math.cos
.这意味着我们只需要调用np.cos
一个函数来创建rot_matrix
而不是math.sin
或math.cos
的四个调用。
Lastly, you can compute the mse
more efficiently by np.einsum
.最后,您可以通过np.einsum
更有效地计算mse
。
Considering all points, the function can look like this:考虑到所有要点,该函数可能如下所示:
k1 = 5*np.pi/2
k2 = 3*np.pi/2
def score2(alpha):
rot_matrixT = np.cos((alpha, alpha+k2, alpha + k1, alpha)).reshape(2,2)
pc1_rotated = pc1 @ rot_matrixT
diff = pc2 - pc1_rotated
return np.einsum('ij,ij->', diff, diff) / num_points
Timing the function again yields再次计时函数产生
In [70]: %timeit score(0.5)
9.26 µs ± 84.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
and therefore, the optimizer is much faster:因此,优化器要快得多:
In [71]: %timeit result = minimize_scalar(score, bounds=(0, 2*np.pi), method="bounded", options={"maxiter": 1000})
279 µs ± 1.79 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
If that still is not fast enough, you can just-in-time compile your function by Numba :如果仍然不够快,您可以通过Numba即时编译您的函数:
In [60]: from numba import njit
In [61]: @njit
...: def score3(alpha):
...: rot_matrix = np.array([
...: [cos(alpha), -sin(alpha)],
...: [sin(alpha), cos(alpha)]
...: ])
...: pc1_rotated = (rot_matrix @ pc1.T).T
...: sum_of_squares = np.sum((pc2 - pc1_rotated)**2, axis=1)
...: mse = np.mean(sum_of_squares)
...: return mse
In [62]: %timeit score3(0.5)
2.97 µs ± 47.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
or rewrite it using Cython .或使用Cython重写它。 Just for the sake of completeness, here's a fast Cython implementation:为了完整起见,这里有一个快速的 Cython 实现:
In [45]: %%cython -c=-O3 -c=-march=native -c=-Wno-deprecated-declarations -c=-Wno-#warnings
...:
...: from libc.math cimport cos, sin
...: cimport numpy as np
...: import numpy as np
...: from cython cimport wraparound, boundscheck
...:
...: @wraparound(False)
...: @boundscheck(False)
...: cpdef double score4(double alpha, double[:, ::1] pc1, double[:, ::1] pc2):
...: cdef int i
...: cdef int N = pc1.shape[0]
...: cdef double diff1 = 0.0
...: cdef double diff2 = 0.0
...: cdef double mse = 0.0
...: cdef double rmT00 = cos(alpha)
...: cdef double rmT01 = sin(alpha)
...: cdef double rmT10 = -rmT01
...: cdef double rmT11 = rmT00
...:
...: for i in range(N):
...: diff1 = pc2[i,0] - (pc1[i,0]*rmT00 + pc1[i,1]*rmT10)
...: diff2 = pc2[i,1] - (pc1[i,0]*rmT01 + pc1[i,1]*rmT11)
...: mse += diff1*diff1 + diff2*diff2
...: return mse / N
which yields这产生
In [48]: %timeit score4(0.5, pc1, pc2)
1.05 µs ± 15.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Last but not least, you can write down the first-order necessary condition of your problem and check whether it can be solved analytically.最后但并非最不重要的一点是,您可以写下问题的一阶必要条件并检查它是否可以解析解决。 Otherwise, you can try to solve the resulting nonlinear equation numerically.否则,您可以尝试数值求解所产生的非线性方程。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.