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.
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".
After: Juansempere on wikipedia.
https://en.wikipedia.org/wiki/Euler_angles#Tait.E2.80.93Bryan_angles
And thanks to Tetragramm for his enlightenment.
2 | No.2 Revision |
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.
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".
After: Juansempere on wikipedia.
https://en.wikipedia.org/wiki/Euler_angles#Tait.E2.80.93Bryan_angles
And thanks to Tetragramm for his enlightenment.
3 | No.3 Revision |
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!.
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".
After: Juansempere on wikipedia.
https://en.wikipedia.org/wiki/Euler_angles#Tait.E2.80.93Bryan_angles
And thanks to Tetragramm for his enlightenment.