mirror of
https://github.com/introlab/rtabmap_ros.git
synced 2026-10-11 20:19:50 +08:00
* rtabmap_demos tests and docs * added bag testing * added rtabmap_examples launch tests * updating demo bag download paths * added netherdrone demo * Fixed rgb-only callback with lidar rejected. Updated lidar params * Added back OrbitOriented rviz view to ros2, with optional octomap wll clipping * fixing ci * lidar demo added intermediate_nodes option * added netherdrone as demo test * running rtabmap_demos tests on ci * added rtabmap_launch tests, fixed ground_truth_base_frame_id usage * ficing rolling * updating demo test harnest * densify golden trajectories to avoid tf missing * fixing tf steps * updated min icp ratio for netherdrone demo * fixing image_transport arg->params * export pose opt=0 * lets process all frames * updated netherdrone golden * fixing publishers queue size just for tests * added playdback demo doc * Adding more logs to debug ci * Fixing QOS for CI to reliable, added find-object demo test * name threads * fixing camera info expected transient on lyrical/rolling. Fixing find_object not appearing idle * 30 Hz polling backward comp * fixing test tf sim lock * fixing lyrical qos bag parsing * faster replay * lockstep * fixing clock deadlock * Added test on shutdown * updated netherdrone golden poses * Extended stereo outdoor test * updated shutdown test * updated test * multi-thread flaky test * adding backtrace when test fails * increased closure slack for netherdrone * g2o gauss newton on stereo * adjusted maximum optimizer iterations * updated default iterations * Added netherdrone in list of demos
146 lines
6.7 KiB
Python
Executable File
146 lines
6.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Republish find_object_2d's detections as landmark detections for rtabmap.
|
|
|
|
find_object_2d publishes which objects it detected on `objectsStamped`, with their
|
|
homography in the image only; their 3D pose, when the depth allows it, goes on TF only,
|
|
as a frame <object_prefix>_<id> relative to the camera. For each detected object, this
|
|
node looks that frame up at the detection's stamp and publishes it on
|
|
`landmark_detections` (rtabmap_msgs/LandmarkDetections), with the object's id as the
|
|
landmark's id. An object detected twice in the same image is used once.
|
|
|
|
Covariance: rtabmap's Marker/* parameters apply only to the markers it detects itself,
|
|
a landmark detection is used with the covariance it carries. This node sets it from the
|
|
same parameters, as rtabmap does for its own markers, so that both can be given the same
|
|
values:
|
|
Marker/VarianceLinear linear variance (m^2), or with the orientation
|
|
ignored and GTSAM, the range variance (9999: bearing
|
|
only)
|
|
Marker/VarianceAngular angular variance (rad^2), or with the orientation
|
|
ignored and GTSAM, the bearing variance
|
|
Marker/VarianceOrientationIgnored ignore the object's orientation, only its position
|
|
(g2o) or its range and bearing (GTSAM) constrain the
|
|
map
|
|
Optimizer/Strategy rtabmap's optimizer: 2 is GTSAM. Needed with the
|
|
orientation ignored, set it to rtabmap's.
|
|
Reg/Force3DoF rtabmap's, for the 2D range and bearing layout
|
|
"""
|
|
|
|
import signal
|
|
|
|
import rclpy
|
|
from find_object_2d.msg import ObjectsStamped
|
|
from rclpy.duration import Duration
|
|
from rclpy.node import Node
|
|
from rclpy.time import Time
|
|
from rtabmap_msgs.msg import LandmarkDetection, LandmarkDetections
|
|
from tf2_ros import Buffer, TransformException, TransformListener
|
|
|
|
# objectsStamped's objects.data: for each object, its id, width, height and 3x3 homography.
|
|
VALUES_PER_OBJECT = 12
|
|
IGNORED = 9999.0 # a variance this large disables that part of the constraint
|
|
|
|
|
|
def _bool(value: str) -> bool:
|
|
return str(value).strip().lower() == 'true'
|
|
|
|
|
|
class FindObjectToLandmarks(Node):
|
|
|
|
def __init__(self):
|
|
super().__init__('find_object_to_landmarks')
|
|
self.object_prefix = self.declare_parameter('object_prefix', 'object').value
|
|
self.wait_for_transform = self.declare_parameter('wait_for_transform', 0.2).value
|
|
# As rtabmap's parameters: strings.
|
|
linear = float(self.declare_parameter('Marker/VarianceLinear', '0.001').value)
|
|
angular = float(self.declare_parameter('Marker/VarianceAngular', '0.01').value)
|
|
orientation_ignored = _bool(
|
|
self.declare_parameter('Marker/VarianceOrientationIgnored', 'false').value)
|
|
strategy = self.declare_parameter('Optimizer/Strategy', '').value
|
|
force_3dof = _bool(self.declare_parameter('Reg/Force3DoF', 'false').value)
|
|
if orientation_ignored and not strategy:
|
|
self.get_logger().warn(
|
|
'Marker/VarianceOrientationIgnored is true but Optimizer/Strategy is not set: '
|
|
'assuming it is not GTSAM. Set it to rtabmap\'s.')
|
|
self.covariance = self._covariance(
|
|
linear, angular, orientation_ignored, strategy == '2', force_3dof)
|
|
self.get_logger().info(
|
|
f'object_prefix={self.object_prefix}, covariance diagonal='
|
|
f'{[self.covariance[i * 7] for i in range(6)]}')
|
|
|
|
self.tf_buffer = Buffer()
|
|
# Its own thread: on_objects() waits for the object's frame.
|
|
self.tf_listener = TransformListener(self.tf_buffer, self, spin_thread=True)
|
|
self.publisher = self.create_publisher(LandmarkDetections, 'landmark_detections', 10)
|
|
self.create_subscription(ObjectsStamped, 'objectsStamped', self.on_objects, 10)
|
|
|
|
@staticmethod
|
|
def _covariance(linear, angular, orientation_ignored, gtsam, force_3dof):
|
|
"""The 6x6 covariance rtabmap gives its own markers (see Memory.cpp), row-major."""
|
|
diagonal = [linear] * 3 + [angular] * 3
|
|
if orientation_ignored:
|
|
diagonal = [linear] * 3 + [IGNORED] * 3
|
|
if gtsam and force_3dof:
|
|
# 2D bearing and range: x is the bearing, y the range (see OptimizerGTSAM).
|
|
diagonal[0:3] = [angular, linear, 1.0]
|
|
elif gtsam:
|
|
# 3D bearing and range: x and y are the bearing, z the range.
|
|
diagonal[0:3] = [angular, angular, linear]
|
|
covariance = [0.0] * 36
|
|
for i, value in enumerate(diagonal):
|
|
covariance[i * 7] = value
|
|
return covariance
|
|
|
|
def on_objects(self, msg: ObjectsStamped):
|
|
detections = LandmarkDetections()
|
|
detections.header = msg.header
|
|
data = msg.objects.data
|
|
used = set()
|
|
for i in range(0, len(data) - VALUES_PER_OBJECT + 1, VALUES_PER_OBJECT):
|
|
object_id = int(data[i])
|
|
if object_id <= 0 or object_id in used:
|
|
continue
|
|
used.add(object_id)
|
|
frame = f'{self.object_prefix}_{object_id}'
|
|
try:
|
|
transform = self.tf_buffer.lookup_transform(
|
|
msg.header.frame_id, frame, Time.from_msg(msg.header.stamp),
|
|
Duration(seconds=self.wait_for_transform))
|
|
except TransformException as e:
|
|
# find_object_2d publishes no frame for an object without valid depth.
|
|
self.get_logger().debug(f'object {object_id} skipped: {e}')
|
|
continue
|
|
landmark = LandmarkDetection()
|
|
landmark.header = msg.header
|
|
landmark.landmark_frame_id = frame
|
|
landmark.id = object_id
|
|
t, r = transform.transform.translation, transform.transform.rotation
|
|
pose = landmark.pose.pose
|
|
pose.position.x, pose.position.y, pose.position.z = t.x, t.y, t.z
|
|
pose.orientation = r
|
|
landmark.pose.covariance = self.covariance
|
|
detections.landmarks.append(landmark)
|
|
if detections.landmarks:
|
|
self.publisher.publish(detections)
|
|
|
|
|
|
def main():
|
|
rclpy.init()
|
|
node = FindObjectToLandmarks()
|
|
try:
|
|
rclpy.spin(node)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
finally:
|
|
# A second Ctrl-C (one from the terminal, one from ros2 launch) would interrupt
|
|
# the cleanup.
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
# Its thread before rclpy: torn down after, it raises at exit.
|
|
node.tf_listener.unregister()
|
|
node.destroy_node()
|
|
rclpy.try_shutdown()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|