../_images/tiagopro-icon.png ../_images/kangaroo-icon.png ../_images/tiago-icon.png ../_images/ari-icon.png ../_images/talos-icon.png ../_images/mobile-bases-icon.png

🧩 Target Detection Plugins#

In this section, you’ll learn how to create a new Target Detection Plugin and how to use it.

Target Detection Plugins can be dynamically loaded by the target_detector_server which is a Task Server that implements the /detect_target action server.

The detection pipeline is independent of the detector plugin and is composed by the following steps:

  • Initialization: On startup, the server loads the specified detection plugins and subscribes to necessary topics.

  • Detection Request: Upon receiving a detection request, the server invokes the appropriate plugin to process the image data.

  • Pose Calculation: For each detected target, the plugin calculates its pose relative to the camera.

  • Result Publication: Detection results, including pose information, are published on the configured topic for use by other system components.

The target pose must follow the convention defined in REP-103 (x: forward; y: left; z: up). Therefore, plugins are responsible for complying with this requirement.

How to create your Target Detection Plugin#

In this tutorial, you are going to learn how to create a new Target Detection Plugin that detects a fake target 2 meters in front of the robot. For this demonstration, we are going to use a ROS 2 package called target_detection_tutorial in which we are going to create a new C++ source file called dummy_target_detector.cpp.

#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "rclcpp/clock.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
#include "tf2_ros/buffer.h"
#include "pal_nav2_core/target_detector.hpp"

namespace target_detection_tutorial
{

class DummyTargetDetector : public pal_nav2_core::TargetDetector
{
public:
  DummyTargetDetector() = default;
  ~DummyTargetDetector() = default;

  void configure(
    const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent, std::string name,
    std::shared_ptr<tf2_ros::Buffer> ) override
  {
    parent_ = parent;
    auto node = parent_.lock();
    if (!node) {
      throw std::runtime_error{"Failed to lock node in simple target detector plugin!"};
    }
    plugin_name_ = name;
    logger_ = node->get_logger();
    clock_ = node->get_clock();
  }

  void cleanup() override
  {
    RCLCPP_INFO(logger_, "Cleaning up %s target detector", plugin_name_.c_str());
  }

  void activate() override
  {
    RCLCPP_INFO(logger_, "Activating %s target detector", plugin_name_.c_str());
  }

  void deactivate() override
  {
    RCLCPP_INFO(logger_, "Deactivating  %s target detector", plugin_name_.c_str());
  }

  bool detectTarget(
    int id, geometry_msgs::msg::TransformStamped & transform,
    double & accuracy) override
  {
    if (id != 0) {
      RCLCPP_WARN(logger_, "%s: Target not detected", plugin_name_.c_str());
      return false;
    }
    accuracy = 0.8;
    transform.header.frame_id = "base_footprint";
    transform.child_frame_id = "target";
    transform.header.stamp = clock_->now();
    transform.transform.translation.x = 2.0;

    return true;
  }

private:
  rclcpp::Logger logger_{rclcpp::get_logger("dummy_target_detector")};
  rclcpp_lifecycle::LifecycleNode::WeakPtr parent_;
  rclcpp::Clock::SharedPtr clock_;
  std::string plugin_name_;
};
}  // namespace target_detection_tutorial
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(
  target_detection_tutorial::DummyTargetDetector,
  pal_nav2_core::TargetDetector)

Examine the code#

The example plugin inherits from the pal_nav2_core::TargetDetector class, which is the base class for all the Target Detection Plugins.

class SimpleTargetDetector : public pal_nav2_core::TargetDetector

This class provides the following virtual methods that need to be implemented by the plugin class.

Virtual method

Method description

Requires override?

confiugure()

Method that is called when the Target Detection plugin is initialized. This method takes in input a const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent to define the parent node that plugin will be created within, a std::string name to define the name of the plugin and a std::shared_ptr<tf2_ros::Buffer> tf to define a pointer to the tf buffer.

Yes

cleanup()

Method that is used to clean up all the defined resources.

Yes

activate()

Method that is used to active target detector and any threads involved in execution. It is used to setup the plugin with all its publishers and subscribers, or any ROS service that will be used in the plugin.

Yes

deactivate()

Method used to deactive target detector and any threads involved in execution.

Yes

detectTarget()

Method that is called to detect and localize a target given the id. This method takes in input an int id to define the target id, a geometry_msgs::msg::TransformStamped & transform to define the transform betweem the robot and the target and a double & accuracy to define the accuracy of the detection. Being a boolean method, it returns true if the target has been found and localized and false otherwise.

Yes

reset()

Method that is called to reset the state of the TargetDetector if necessary after task is exited

No

In the configure() function you can setup the ROS 2 Interface of your plugin (e.g Subscribers, Publishers, Services and so on) using the parent_ variable which is an instance of the rclcpp_lifecycle::LifecycleNode::WeakPtr class and which allows you to access the base ROS 2 Node.

rclcpp_lifecycle::LifecycleNode::WeakPtr parent_;
auto node = parent_.lock();
if (!node) {
  throw std::runtime_error{"Failed to lock node in simple target detector plugin!"};
}

The detectTarget() funcion function is where the real logic of the plugin is implemented. It should generate a geometry_msgs::msg::TransformStamped message with the pose of the detected target and the accuracy of the detection. In this tutorial, this pose is simply hardcoded to be 2 meters in front of the robot.

transform.header.frame_id = "base_footprint";
transform.child_frame_id = "target";
transform.header.stamp = clock_->now();
transform.transform.translation.x = 2.0;

Compile and install#

To compile and install the plugin, the file CMakeLists.txt should declare the following (minimum) dependencies:

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(pal_nav2_core REQUIRED)
find_package(pluginlib REQUIRED)
find_package(tf2_ros REQUIRED)

set(dependencies
  rclcpp
  rclcpp_lifecycle
  pal_nav2_core
  pluginlib
  tf2_ros
)

To compile and install the plugin, you need to add and install a new library

add_library(dummy_target_detector SHARED src/dummy_target_detector.cpp)
ament_target_dependencies(dummy_target_detector ${dependencies})

install(TARGETS dummy_target_detector
  DESTINATION lib/${PROJECT_NAME})

Also, it should be created a new XML file called plugins.xml within the target_detection_tutorial package with the declaration of the plugin.

<class_libraries>
    <library path="dummy_target_detector">
        <class type="target_detection_tutorial::DummyTargetDetector" base_class_type="pal_nav2_core::TargetDetector">
            <description>Detects a fake target 2mts in front of the robot</description>
        </class>
    </library>
</class_libraries>

Finally, the package.xml file should include the required dependencies:

<depend>rclcpp</depend>
<depend>rclcpp_lifecycle</depend>
<depend>pal_nav2_core</depend>
<depend>pluginlib</depend>
<depend>tf2_ros</depend>

And should export the plugin:

<export>
  <build_type>ament_cmake</build_type>
  <target_detection_tutorial plugin="${prefix}/plugins.xml" />
</export>

Usage#

To start using your new plugin, you can create a new target_detector_server configuration file. Start by creating a new folder in the .pal directory of your robot.

mkdir -p ~/.pal/pal_navigation_cfg_params/params/pal_nav2_target_detector

and then, within this folder, you can create a new configuration file:

touch ~/.pal/pal_navigation_cfg_params/params/pal_nav2_target_detector/my_params.yaml

Configure your file following the guide in the πŸ“” Navigation Configuration Guide section and then add your new entry to the detector_plugins list:

sudo vi ~/.pal/pal_nav2_target_detector/params/my_params.yaml
target_detector_server:
  ros__parameters:
      # Other pal_nav2_target_detector parameters

      detector_plugins: [... , "dummy_target_detector"]

      #Other pal_nav2_target_detector plugin configurations
      dummy_target_detector:
        plugin: "target_detection_tutorial::DummyTargetDetector"

Note

You can use the file /opt/pal/$PAL_DISTRO/share/pal_nav2_target_detector/params/default.yaml as a reference to create your custom configuration file.

Once created, to start using your custom Detector, you need to set it in the navigation pipeline by modifying the <robot>_advanced_nav.yaml file in the <robot>_advanced_2dnav package.

sudo vi /opt/pal/$PAL_DISTRO/share/<robot>_advanced_2dnav/params/<robot>_advanced_nav.yaml

Attention

Make sure to replace the <robot> placeholder with the name of your robot (e.g. tiago, omni_base, tiago_pro, etc.).

Then, change the params field of the target_detector_server to the name of your custom Detector configuration file.

nodes:
  # Other nodes
    target_detector_server:
      app: target_detector_server
      params: [my_params.yaml]

Attention

Make sure that the name of the custom Detector configuration file you use in the params field (my_params) is the same as the name of the file you created in the .pal folder (my_params.yaml).

Finally, to apply the changes and start using the new Detector configuration, you need to restart the advanced navigation pipeline with the command:

pal module restart advanced_navigation

This will restart the target_detector_server with your new plugin that you can use to detect a new target with the command:

ros2 action send_goal /detect_target pal_nav2_msgs/action/DetectTarget "{id: 0, detector: 'dummy_target_detector'}"

If you want to visualize the results, you can add the /updated_goal to the RViz configuration and check the /tf.

You should see something like this:

../_images/target_detected.svg