𧩠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
|
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 |
Yes |
reset() |
Method that is called to reset the state of the |
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: