Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

I think (and hope) I'm done with it.

Here's my workaround (if not a final solution) in 6 steps:

0. Imports :

import math, numpy as np, cv2

1. Retrieve rvec and tvec from the cv2.solvePnP() (or with RanSaC) function:

retval, rvec, tvec = cv2.solvePnP(obj_pts, img_pts, cam_mat, dist_coeffs, rvec, tvec, flags=cv2.SOLVEPNP_ITERATIVE)

2. Convert rvec to a 3x3 matrix using cv2.Rodrigues():

rmat = cv2.Rodrigues(rvec)[0]

This matrix is a rotation matrix for a x-y'-z" Tait-Bryan sequence if I'm not wrong (that's what I was searching for days!). So r_total = rz·rỵ·rx (rx occurs first). You can imagine you have first a camera frame (z-axis = through the lens, x=right, y=bottom) which is perfectly superposed to the world frame. You rotate it around x first, then y, finally z. The angles are the Euler angles hereafter. Lowercase axis = camera frame axes, uppercase = world frame axes. Camera frame is firmly attached to the body.

2. If you need, the camera position expressed in the world frame (OXYZ) is given by:

cam_pos     = -np.matrix(rmat).T * np.matrix(tvec)

4. Create the projection matrix P = [ R | t ]:

P = np.hstack((rmat,tvec))

5. Use cv2.decomposeProjectionMatrix() (or any home-made function) to retrieve Euler angles:

euler_angles_radians = -cv2.decomposeProjectionMatrix(P)[6]
euler_angles_degrees = 180 * euler_angles_radians/math.pi

I noticed we have to take the negative values here to be compliant with a conventional rotation (while looking in the same direction as the vector perpendicular to the plane where the rotation occurs; clockwise = positive. It's the conventional sense in mathematics also).
Euler angles are the angles making up the total rotation, but expressed separately after the 3 axis of an oxyz frame firmly attached to the the camera.
Initially, the camera is aligned with the world frame, facing upwards if your world frame Z-axis is pointing upward. X=to the right of the cam. Y=normal to the two others, bottom of the cam.
First rotation is around X, then Y and finally Z. This could be written as r_total = rzryrx (rx occurs first).

Euler angles form a 3x1 vector.
Element [1,0] is the rotation around x, element [2,0] rotation around y, and element [2,0] rotation around z.

6. To retrieve attitude of the camera (just like if it was an airplane) in it's own body-attached frame, here's the magic that seems to work for me yet (one would have to check this with more precise instrument as my eyes...):

eul    = euler_angles_radians
yaw    = 180*eul[1,0]/math.pi # warn: singularity if camera is facing perfectly upward. Value 0 yaw is given by the Y-axis of the world frame.
pitch  = 180*((eul[0,0]+math.pi/2)*math.cos(eul[1,0]))/math.pi
roll   = 180*( (-(math.pi/2)-eul[0,0])*math.sin(eul[1,0]) + eul[2,0] )/math.pi

You're normally done. I hope.

The definitions I used, just in case:

Starting from a camera placed horizontally, facing the horizon. Yaw=0, pitch=0, roll=0 at this point.

Yaw is the angle made by the camera x- and z-axes in its oxz plane from its starting position. Rotation axis = camera y-axis (vertical).

Pitch is the angle made by the camera y- and z-axes in its oyz plane. 0-Pitch direction = when horizontally facing the horizon, before the pitch rotation occurs. Rotation axis = camera x-axis.

Finally, roll is the angle made in the camera oxy plane by the x- and y-axis from it's horizontal initial position (before the roll rotation occurs). Rotation axis = camera z-axis.

It's an intrinsic y-x'-z" rotation (relative to the camera frame), occurring in this order; ry first, then rx, finally rz. Dot-product: r=rz·rx·ry.
Known as the Tait-Bryan Z-Y'-X" sequence for a conventional XYZ (Z upwards, X to the front and Y to the left) frame. That's why, in the case of the camera, this sequence is translated y-x'-z".

Tait-Bryan angles
After: Juansempere on wikipedia.

Useful link:

https://en.wikipedia.org/wiki/Euler_angles#Tait.E2.80.93Bryan_angles

And thanks to Tetragramm for his enlightenment.

I think (and hope) I'm done with it.

Here's my workaround (if not a final solution) in 6 steps:

0. Imports :

import math, numpy as np, cv2

1. Retrieve rvec and tvec from the cv2.solvePnP() (or with RanSaC) function:

retval, rvec, tvec = cv2.solvePnP(obj_pts, img_pts, cam_mat, dist_coeffs, rvec, tvec, flags=cv2.SOLVEPNP_ITERATIVE)

2. Convert rvec to a 3x3 matrix using cv2.Rodrigues():

rmat = cv2.Rodrigues(rvec)[0]

This matrix is a rotation matrix for a x-y'-z" Tait-Bryan sequence if I'm not wrong (that's what I was searching for days!). So r_total = rz·rỵ·rx (rx occurs first). You can imagine you have first a camera frame (z-axis = through the lens, x=right, y=bottom) which is perfectly superposed to the world frame. You rotate it around x first, then y, finally z. The angles are the Euler angles hereafter. Lowercase axis = camera frame axes, uppercase = world frame axes. Camera frame is firmly attached to the body.

2. If you need, the camera position expressed in the world frame (OXYZ) is given by:

cam_pos     = -np.matrix(rmat).T * np.matrix(tvec)

4. Create the projection matrix P = [ R | t ]:

P = np.hstack((rmat,tvec))

5. Use cv2.decomposeProjectionMatrix() (or any home-made function) to retrieve Euler angles:

euler_angles_radians = -cv2.decomposeProjectionMatrix(P)[6]
euler_angles_degrees = 180 * euler_angles_radians/math.pi

I noticed we have to take the negative values here to be compliant with a conventional rotation (while looking in the same direction as the vector perpendicular to the plane where the rotation occurs; clockwise = positive. It's the conventional sense in mathematics also).
This is due to the fact OpenCV work with a mobile frame around the vector and I was working in my mind with a fix one. Informations here: mathworld.wolfram.com/RotationMatrix.html )

Euler angles are the angles making up the total rotation, but expressed separately after the 3 axis of an oxyz frame firmly attached to the the camera.
Initially, the camera is aligned with the world frame, facing upwards if your world frame Z-axis is pointing upward. X=to the right of the cam. Y=normal to the two others, bottom of the cam.
First rotation is around X, then Y and finally Z. This could be written as r_total = rzryrx (rx occurs first).

Euler angles form a 3x1 vector.
Element [1,0] is the rotation around x, element [2,0] rotation around y, and element [2,0] rotation around z.

6. To retrieve attitude of the camera (just like if it was an airplane) in it's own body-attached frame, here's the magic that seems to work for me yet (one would have to check this with more precise instrument as my eyes...):

eul    = euler_angles_radians
yaw    = 180*eul[1,0]/math.pi # warn: singularity if camera is facing perfectly upward. Value 0 yaw is given by the Y-axis of the world frame.
pitch  = 180*((eul[0,0]+math.pi/2)*math.cos(eul[1,0]))/math.pi
roll   = 180*( (-(math.pi/2)-eul[0,0])*math.sin(eul[1,0]) + eul[2,0] )/math.pi

You're normally done. I hope.

The definitions I used, just in case:

Starting from a camera placed horizontally, facing the horizon. Yaw=0, pitch=0, roll=0 at this point.

Yaw is the angle made by the camera x- and z-axes in its oxz plane from its starting position. Rotation axis = camera y-axis (vertical).

Pitch is the angle made by the camera y- and z-axes in its oyz plane. 0-Pitch direction = when horizontally facing the horizon, before the pitch rotation occurs. Rotation axis = camera x-axis.

Finally, roll is the angle made in the camera oxy plane by the x- and y-axis from it's horizontal initial position (before the roll rotation occurs). Rotation axis = camera z-axis.

It's an intrinsic y-x'-z" rotation (relative to the camera frame), occurring in this order; ry first, then rx, finally rz. Dot-product: r=rz·rx·ry.
Known as the Tait-Bryan Z-Y'-X" sequence for a conventional XYZ (Z upwards, X to the front and Y to the left) frame. That's why, in the case of the camera, this sequence is translated y-x'-z".

Tait-Bryan angles
After: Juansempere on wikipedia.

Useful link:

https://en.wikipedia.org/wiki/Euler_angles#Tait.E2.80.93Bryan_angles

And thanks to Tetragramm for his enlightenment.

I think (and hope) I'm done with it.

Here's my workaround (if not a final solution) in 6 steps:

0. Imports :

import math, numpy as np, cv2

1. Retrieve rvec and tvec from the cv2.solvePnP() (or with RanSaC) function:

retval, rvec, tvec = cv2.solvePnP(obj_pts, img_pts, cam_mat, dist_coeffs, rvec, tvec, flags=cv2.SOLVEPNP_ITERATIVE)

2. Convert rvec to a 3x3 matrix using cv2.Rodrigues():

rmat = cv2.Rodrigues(rvec)[0]

This matrix is a rotation matrix for a x-y'-z" Tait-Bryan sequence if I'm not wrong (that's what I was searching for days!). So r_total = rz·rỵ·rx (rx occurs first). You can imagine you have first a camera frame (z-axis = through the lens, x=right, y=bottom) which is perfectly superposed to the world frame. You rotate it around x first, then y, finally z. The angles are the Euler angles hereafter. Lowercase axis = camera frame axes, uppercase = world frame axes. Camera frame is firmly attached to the body.

2. If you need, the camera position expressed in the world frame (OXYZ) is given by:

cam_pos     = -np.matrix(rmat).T * np.matrix(tvec)

4. Create the projection matrix P = [ R | t ]:

P = np.hstack((rmat,tvec))

5. Use cv2.decomposeProjectionMatrix() (or any home-made function) to retrieve Euler angles:

euler_angles_radians = -cv2.decomposeProjectionMatrix(P)[6]
euler_angles_degrees = 180 * euler_angles_radians/math.pi

I noticed we have to take the negative values here to be compliant with a conventional rotation (while looking in the same direction as the vector perpendicular to the plane where the rotation occurs; clockwise = positive. It's the conventional sense in mathematics also).
This is due to the fact OpenCV work with a mobile frame around the vector and I was working in my mind with a fix one. Informations here: mathworld.wolfram.com/RotationMatrix.html )

Euler angles are the angles making up the total rotation, but expressed separately after the 3 axis of an oxyz frame firmly attached to the the camera.
Initially, the camera is aligned with the world frame, facing upwards if your world frame Z-axis is pointing upward. X=to the right of the cam. Y=normal to the two others, bottom of the cam.
First rotation is around X, then Y and finally Z. This could be written as r_total = rzryrx (rx occurs first).

Euler angles form a 3x1 vector.
Element [1,0] is the rotation around x, element [2,0] rotation around y, and element [2,0] rotation around z.

6. To retrieve attitude of the camera (just like if it was an airplane) in it's own body-attached frame, here's the magic that seems to work for me yet (one would have to check this with more precise instrument as my eyes...):

eul    = euler_angles_radians
yaw    = 180*eul[1,0]/math.pi # warn: singularity if camera is facing perfectly upward. Value 0 yaw is given by the Y-axis of the world frame.
pitch  = 180*((eul[0,0]+math.pi/2)*math.cos(eul[1,0]))/math.pi
roll   = 180*( (-(math.pi/2)-eul[0,0])*math.sin(eul[1,0]) + eul[2,0] )/math.pi

You're normally done. I hope.
(edit: well, not really, as the dot product of the rotation matrix from these angles and rmat is not really (almost but not really) eye(3,3) and it should be!.

The definitions I used, just in case:

Starting from a camera placed horizontally, facing the horizon. Yaw=0, pitch=0, roll=0 at this point.

Yaw is the angle made by the camera x- and z-axes in its oxz plane from its starting position. Rotation axis = camera y-axis (vertical).

Pitch is the angle made by the camera y- and z-axes in its oyz plane. 0-Pitch direction = when horizontally facing the horizon, before the pitch rotation occurs. Rotation axis = camera x-axis.

Finally, roll is the angle made in the camera oxy plane by the x- and y-axis from it's horizontal initial position (before the roll rotation occurs). Rotation axis = camera z-axis.

It's an intrinsic y-x'-z" rotation (relative to the camera frame), occurring in this order; ry first, then rx, finally rz. Dot-product: r=rz·rx·ry.
Known as the Tait-Bryan Z-Y'-X" sequence for a conventional XYZ (Z upwards, X to the front and Y to the left) frame. That's why, in the case of the camera, this sequence is translated y-x'-z".

Tait-Bryan angles
After: Juansempere on wikipedia.

Useful link:

https://en.wikipedia.org/wiki/Euler_angles#Tait.E2.80.93Bryan_angles

And thanks to Tetragramm for his enlightenment.