Skip to content

Commit f771426

Browse files
committed
fix(ardupilot_node, pose_estimation_node): Fix gimbal quaternion handling when gimbal pitch is lower than -90 degrees
Fixes naive interpretation of Euler roll and pitch whan handling cases where the gimbal pitch goes lower than -90 degrees. [skip ci]
1 parent c78633b commit f771426

File tree

3 files changed

+27
-17
lines changed

3 files changed

+27
-17
lines changed

gisnav/nodes/ardupilot_node.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def _vehicle_nav_sat_fix_callback(self, msg: NavSatFix) -> None:
5757
self._vehicle_nav_sat_fix = msg
5858
self.publish_vehicle_geopose()
5959
self.publish_vehicle_altitude()
60+
self.publish_gimbal_quaternion() # TODO: temporarily assuming static camera so publishing gimbal quat here
6061

6162
def _vehicle_pose_stamped_callback(self, msg: PoseStamped) -> None:
6263
"""Handles latest :class:`mavros_msgs.msg.PoseStamped` message
@@ -102,16 +103,15 @@ def vehicle_geopose(self) -> Optional[GeoPoseStamped]:
102103
latitude, longitude = self._vehicle_nav_sat_fix.latitude, self._vehicle_nav_sat_fix.longitude
103104
altitude = self._vehicle_nav_sat_fix.altitude
104105

105-
# Attitude
106-
attitude_ned_q = np.array([self._vehicle_pose_stamped.pose.orientation.y,
107-
self._vehicle_pose_stamped.pose.orientation.x,
108-
-self._vehicle_pose_stamped.pose.orientation.z,
109-
self._vehicle_pose_stamped.pose.orientation.w])
110-
111106
# Convert ENU->NED + re-center yaw
112-
attitude_ned = Rotation.from_quat(attitude_ned_q) * Rotation.from_rotvec(np.array([0, 0, np.pi / 2]))
113-
#attitude_ned = Attitude(attitude_ned.as_quat(), extrinsic=True)
114-
orientation = messaging.as_ros_quaternion(attitude_ned.as_quat())
107+
enu_to_ned = Rotation.from_euler('XYZ', np.array([np.pi, 0, np.pi / 2]))
108+
attitude_ned = Rotation.from_quat(messaging.as_np_quaternion(self._vehicle_pose_stamped.pose.orientation)) \
109+
* enu_to_ned.inv()
110+
rpy = attitude_ned.as_euler('XYZ', degrees=True)
111+
rpy[0] = (rpy[0] + 180) % 360
112+
attitude_ned = Rotation.from_euler('XYZ', rpy, degrees=True)
113+
attitude_ned = attitude_ned.as_quat()
114+
orientation = messaging.as_ros_quaternion(attitude_ned)
115115

116116
return GeoPoseStamped(header=messaging.create_header('base_link'),
117117
pose=GeoPose(
@@ -165,11 +165,11 @@ def gimbal_quaternion(self) -> Optional[Quaternion]:
165165
"""
166166
# TODO: assumes static nadir facing camera, do proper implementation
167167
if self.vehicle_geopose is not None:
168-
vehicle_attitude = Attitude(q=messaging.as_np_quaternion(self.vehicle_geopose.pose.orientation))
169-
vehicle_attitude = vehicle_attitude.as_rotation()
170-
gimbal_attitude = vehicle_attitude * Rotation.from_euler('XYZ', [0, -np.pi / 2, 0])
171-
gimbal_attitude = messaging.as_ros_quaternion(gimbal_attitude.as_quat())
172-
return gimbal_attitude
168+
vehicle_attitude = Rotation.from_quat(messaging.as_np_quaternion(self.vehicle_geopose.pose.orientation))
169+
gimbal_rpy = vehicle_attitude.as_euler('xyz', degrees=True)
170+
gimbal_rpy[1] -= 90
171+
gimbal_attitude = Rotation.from_euler('xyz', gimbal_rpy, degrees=True)
172+
return messaging.as_ros_quaternion(gimbal_attitude.as_quat())
173173
else:
174174
self.get_logger().warn('Vehicle GeoPose unknown, cannot determine gimbal quaternion for static camera.')
175175
return None

gisnav/nodes/bbox_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def _guess_fov_center(self, xy: GeoPt) -> Optional[GeoPt]:
235235
:return: Center of the projected FOV, or None if not available
236236
"""
237237
if self._gimbal_quaternion is None:
238-
self.get_logger().warn('Gimbal set attitude not available, cannot project gimbal FOV.')
238+
self.get_logger().warn('Gimbal quaternion not available, cannot project gimbal FOV.')
239239
return None
240240
else:
241241
gimbal_attitude = Attitude(q=messaging.as_np_quaternion(self._gimbal_quaternion))

gisnav/nodes/pose_estimation_node.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,19 @@ def _contextual_map_data(self) -> Optional[ContextualMapData]:
193193
# Get cropped and rotated map
194194
if self._gimbal_quaternion is not None:
195195
gimbal_attitude = Attitude(q=messaging.as_np_quaternion(self._gimbal_quaternion), extrinsic=True)
196+
roll = gimbal_attitude.roll
196197
camera_yaw = gimbal_attitude.yaw
198+
if abs(roll) > np.pi / 2:
199+
# Assume camera pitch (NED frame) is lower than -90 degrees
200+
# A naive interpretation of the Euler angles would assume the camera (and the vehicle) is upside down
201+
# cause the map being rotated incorrectly by 180 degrees compared to what is needed for pose estimation
202+
# This might happen e.g. if gimbal has been pointing straight down nadir and vehicle body has suddenly
203+
# gained additional negative pitch before gimbal has had time to stabilize
204+
self.get_logger().debug(f'Gimbal absolute Euler roll angle over 90 degrees: assuming gimbal is not '
205+
f'upside down and that gimbal absolute pitch is over 90 degrees instead.')
206+
camera_yaw = (camera_yaw + np.pi / 2) % (2 * np.pi)
197207
assert_type(camera_yaw, float)
198-
assert -np.pi <= camera_yaw <= np.pi, f'Unexpected gimbal yaw value: {camera_yaw} ([-pi, pi] expected).'
208+
assert -2 * np.pi <= camera_yaw <= 2 * np.pi, f'Unexpected gimbal yaw value: {camera_yaw} ([-2*pi, 2*pi] expected).'
199209
else:
200210
self.get_logger().warn(f'Camera yaw unknown, cannot estimate pose.')
201211
return None
@@ -269,7 +279,7 @@ def image_callback(self, msg: Image) -> None:
269279
pose = Pose(*pose)
270280
self._post_process_pose(pose, image_pair)
271281
else:
272-
self.get_logger().warn(f'Worker did not return a pose, skipping this frame.')
282+
self.get_logger().warn(f'Could not estimate a pose, skipping this frame.')
273283
return None
274284

275285
def _orthoimage_3d_callback(self, msg: OrthoImage3D) -> None:

0 commit comments

Comments
 (0)