11

I have two separate vectors of 3D data points that represent curves and I'm plotting these as scatter data in a 3D plot with matplotlib.

Both the vectors start at the origin, and both are of unit length. The curves are similar to each other, however, there is typically a rotation between the two curves (for test purposes, I've actually being using one curve and applying a rotation matrix to it to create the second curve).

I want to align the two curves so that they line up in 3D e.g. rotate curve b, so that its start and end points line up with curve a. I've been trying to do this by subtracting the final point from the first, to get a direction vector representing the straight line from the start to the end of each curve, converting these to unit vectors and then calculating the cross and dot products and using the methodology outlined in this answer (https://math.stackexchange.com/a/476311/357495) to calculate a rotation matrix.

However, when I do this, the calculated rotation matrix is wrong and I'm not sure why?

My code is below (I'm using Python 2.7):

# curve_1, curve_2 are arrays of 3D points, of the same length (both start at the origin) 

curve_vec_1 = (curve_1[0] - curve_1[-1]).reshape(3,1)
curve_vec_2 = (curve_2[index][0] - curve_2[index][-1]).reshape(3,1)
a,b = (curve_vec_1/ np.linalg.norm(curve_vec_1)).reshape(3), (curve_vec_2/ np.linalg.norm(curve_vec_2)).reshape(3)
v = np.cross(a,b)
c = np.dot(a,b)
s = np.linalg.norm(v)
I = np.identity(3)
vXStr = '{} {} {}; {} {} {}; {} {} {}'.format(0, -v[2], v[1], v[2], 0, -v[0], -v[1], v[0], 0)
k = np.matrix(vXStr)
r = I + k + np.square(k) * ((1 -c)/(s**2))

for i in xrange(item.shape[0]):
    item[i] = (np.dot(r, item[i]).reshape(3,1)).reshape(3)

In my test case, curve 2 is simply curve 1 with the following rotation matrix applied:

[[1  0       0    ]
[ 0  0.5     0.866]
[ 0  -0.866  0.5  ]]

(just a 60 degree rotation around the x axis).

The rotation matrix computed by my code to align the two vectors again is:

[[ 1.         -0.32264329  0.27572962]  
 [ 0.53984249  1.         -0.35320293]
 [-0.20753816  0.64292975  1.        ]]

The plot of the direction vectors for the two original curves (a and b in blue and green respectively) and the result of b transformed with the computed rotation matrix (red) is below. I'm trying to compute the rotation matrix to align the green vector to the blue.Curve plot

Mark
  • 768
  • 3
  • 7
  • 23

5 Answers5

20

Based on Daniel F's correction, here is a function that does what you want:

import numpy as np

def rotation_matrix_from_vectors(vec1, vec2):
    """ Find the rotation matrix that aligns vec1 to vec2
    :param vec1: A 3d "source" vector
    :param vec2: A 3d "destination" vector
    :return mat: A transform matrix (3x3) which when applied to vec1, aligns it with vec2.
    """
    a, b = (vec1 / np.linalg.norm(vec1)).reshape(3), (vec2 / np.linalg.norm(vec2)).reshape(3)
    v = np.cross(a, b)
    c = np.dot(a, b)
    s = np.linalg.norm(v)
    kmat = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
    rotation_matrix = np.eye(3) + kmat + kmat.dot(kmat) * ((1 - c) / (s ** 2))
    return rotation_matrix

Test:

vec1 = [2, 3, 2.5]
vec2 = [-3, 1, -3.4]

mat = rotation_matrix_from_vectors(vec1, vec2)
vec1_rot = mat.dot(vec1)
assert np.allclose(vec1_rot/np.linalg.norm(vec1_rot), vec2/np.linalg.norm(vec2))
Peter
  • 12,274
  • 9
  • 71
  • 86
  • this is not a right hand rule, isn't it? `[1,0,0]` and `[0,0,1]`, which is a rotation by +90 degree (counterclockwise) around the y, axis gives `[[ 0., 0., -1.],[ 0., 1., 0.],[ 1., 0., 0.]]` – Alexander Cska Jul 20 '20 at 12:18
  • @AlexanderCska i think this convention doesn't matter since the translation matrix is a set of influence factors from one vector to another. The selected answer does have the correct definition for rotation matrix. – Kevin R. May 31 '21 at 02:06
7

Problem is here:

r = I + k + np.square(k) * ((1 -c)/(s**2))

np.square(k) squares each element of the matrix. You want np.matmul(k,k) or k @ k which is the matrix multiplied by itself.

I'd also implement the side cases (especially s=0) mentioned in the comments of that answer or you will end up with errors for quite a few cases.

Daniel F
  • 13,620
  • 2
  • 29
  • 55
  • Thanks- this fixes it! As I'm using numpy < 1.10 I used np.dot(k,k) instead of matmul. I am going to implement the edge cases, just needed to get the base case working first. :) – Mark Jul 17 '17 at 12:43
5

Based off of @Peter and @Daniel F's work. The above function worked for me, except for in cases of the same direction vector, where v would be a zero vector. I catch this here, and return the identity vector instead.

def rotation_matrix_from_vectors(vec1, vec2):
    """ Find the rotation matrix that aligns vec1 to vec2
    :param vec1: A 3d "source" vector
    :param vec2: A 3d "destination" vector
    :return mat: A transform matrix (3x3) which when applied to vec1, aligns it with vec2.
    """
    a, b = (vec1 / numpy.linalg.norm(vec1)).reshape(3), (vec2 / numpy.linalg.norm(vec2)).reshape(3)
    v = numpy.cross(a, b)
    if any(v): #if not all zeros then 
        c = numpy.dot(a, b)
        s = numpy.linalg.norm(v)
        kmat = numpy.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
        return numpy.eye(3) + kmat + kmat.dot(kmat) * ((1 - c) / (s ** 2))

    else:
        return numpy.eye(3) #cross of all zeros only occurs on identical directions

Kevin R.
  • 336
  • 4
  • 14
4

One can use scipy for this, reproducing here @Peter answer with scipy Rotation see: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.html?highlight=scipy%20spatial%20transform%20rotation#scipy.spatial.transform.Rotation

from scipy.spatial.transform import Rotation as R
import numpy as np

def get_rotation_matrix(vec2, vec1=np.array([1, 0, 0])):
    """get rotation matrix between two vectors using scipy"""
    vec1 = np.reshape(vec1, (1, -1))
    vec2 = np.reshape(vec2, (1, -1))
    r = R.align_vectors(vec2, vec1)
    return r[0].as_matrix()


vec1 = np.array([2, 3, 2.5])
vec2 = np.array([-3, 1, -3.4])

mat = get_rotation_matrix(vec1=vec1, vec2=vec2)
print(mat)
vec1_rot = mat.dot(vec1)
assert np.allclose(vec1_rot / np.linalg.norm(vec1_rot), vec2 / np.linalg.norm(vec2))

terveisin, Markus

Markus Kaukonen
  • 334
  • 4
  • 10
  • Thanks for pointing out this but I wouldn't recommend use this to align 2 vectors, this function 'Estimate a rotation to optimally align two sets of vectors.' When tried it, sometimes it give me warning 'UserWarning: Optimal rotation is not uniquely or poorly defined for the given sets of vectors' and a not good align. – XueYu Oct 13 '22 at 14:37
  • @XueYu can you give an example when it fails? – Markus Kaukonen Oct 15 '22 at 09:02
  • I might be wrong about the alignment. Because I never really see the assert line goes wrong. You can set vec1 = np.random.rand(3), vec2 = np.random.rand(3), and run a few times you'll see the warning. For example: vec1 = np.array([0.08067752,0.86080858,0.19369599]), vec2 = np.array([0.7616154, 0.17332016, 0.09992052]). – XueYu Oct 17 '22 at 14:46
  • Yes, you are right, there is warning with your numbers (but the resulting rotated unit vector is correct with 6 digits). Maybe it is a good idea to accept the rotation matrix after checking. Maybe someone with more knowledge in maths could comment the warning... – Markus Kaukonen Oct 22 '22 at 18:05
0

I think if you do not have rotation axis, the rotation matrix is not unique.

weeshin
  • 43
  • 1
  • 7