Skip to content

Commit daffac3

Browse files
committed
Added ImageMarker publisher for foxglove
1 parent ec019df commit daffac3

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed

deep_object_detection/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ find_package(rclcpp_lifecycle REQUIRED)
3030
find_package(sensor_msgs REQUIRED)
3131
find_package(cv_bridge REQUIRED)
3232
find_package(vision_msgs REQUIRED)
33+
find_package(visualization_msgs REQUIRED)
3334
find_package(deep_core REQUIRED)
3435
find_package(deep_msgs REQUIRED)
3536
find_package(pluginlib REQUIRED)
@@ -60,6 +61,7 @@ target_link_libraries(deep_object_detection_lib
6061
${pluginlib_TARGETS}
6162
${vision_msgs_TARGETS}
6263
${deep_msgs_TARGETS}
64+
${visualization_msgs_TARGETS}
6365
deep_core::deep_core_lib
6466
PRIVATE
6567
${rclcpp_components_TARGETS}

deep_object_detection/include/deep_object_detection/deep_object_detection_node.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <rclcpp_lifecycle/state.hpp>
4141
#include <sensor_msgs/msg/compressed_image.hpp>
4242
#include <std_msgs/msg/header.hpp>
43+
#include <visualization_msgs/msg/image_marker.hpp>
4344

4445
#include "deep_object_detection/backend_manager.hpp"
4546
#include "deep_object_detection/detection_types.hpp"
@@ -200,6 +201,18 @@ class DeepObjectDetectionNode : public rclcpp_lifecycle::LifecycleNode
200201
const std::vector<std_msgs::msg::Header> & headers,
201202
const std::vector<ImageMeta> & metas);
202203

204+
/**
205+
* @brief Convert detections to ImageMarker annotations for Foxglove visualization
206+
* @param header ROS header for the message
207+
* @param detections Vector of detections for a single image
208+
* @return ImageMarker message with bounding box and label annotations
209+
*
210+
* Converts SimpleDetection objects to visualization_msgs::msg::ImageMarker format
211+
* for rendering in Foxglove and other visualization tools.
212+
*/
213+
visualization_msgs::msg::ImageMarker detectionsToImageMarker(
214+
const std_msgs::msg::Header & header, const std::vector<SimpleDetection> & detections) const;
215+
203216
/**
204217
* @brief Load class names from file
205218
*
@@ -242,6 +255,10 @@ class DeepObjectDetectionNode : public rclcpp_lifecycle::LifecycleNode
242255
std::string input_topic_;
243256
/// Publisher for Detection2DArray messages
244257
rclcpp_lifecycle::LifecyclePublisher<Detection2DArrayMsg>::SharedPtr detection_pub_;
258+
/// Publisher for ImageMarker annotations (for Foxglove visualization)
259+
rclcpp_lifecycle::LifecyclePublisher<visualization_msgs::msg::ImageMarker>::SharedPtr image_marker_pub_;
260+
/// Output annotations topic name for ImageMarker messages
261+
std::string output_annotations_topic_;
245262
/// Timer for periodic batch processing checks (5ms period)
246263
rclcpp::TimerBase::SharedPtr batch_timer_;
247264

deep_object_detection/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<depend>sensor_msgs</depend>
2020
<depend>cv_bridge</depend>
2121
<depend>vision_msgs</depend>
22+
<depend>visualization_msgs</depend>
2223
<depend>deep_core</depend>
2324
<depend>deep_msgs</depend>
2425
<depend>deep_ort_backend_plugin</depend>

deep_object_detection/src/deep_object_detection_node.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ void DeepObjectDetectionNode::declareAndReadParameters()
134134
input_topic_ = this->declare_parameter<std::string>("input_topic", "");
135135
params_.input_qos_reliability = "best_effort";
136136
params_.output_detections_topic = this->declare_parameter<std::string>("output_detections_topic", "/detections");
137+
output_annotations_topic_ = this->declare_parameter<std::string>("output_annotations_topic", "/image_annotations");
137138

138139
params_.max_batch_size = this->declare_parameter<int>("max_batch_size", 3);
139140
params_.queue_size = this->declare_parameter<int>("queue_size", 10);
@@ -225,6 +226,11 @@ rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn DeepOb
225226
auto pub = this->create_publisher<Detection2DArrayMsg>(params_.output_detections_topic, rclcpp::SensorDataQoS{});
226227
detection_pub_ = std::static_pointer_cast<rclcpp_lifecycle::LifecyclePublisher<Detection2DArrayMsg>>(pub);
227228

229+
auto marker_pub =
230+
this->create_publisher<visualization_msgs::msg::ImageMarker>(output_annotations_topic_, rclcpp::SensorDataQoS{});
231+
image_marker_pub_ =
232+
std::static_pointer_cast<rclcpp_lifecycle::LifecyclePublisher<visualization_msgs::msg::ImageMarker>>(marker_pub);
233+
228234
if (input_topic_.empty()) {
229235
RCLCPP_ERROR(this->get_logger(), "input_topic is empty. Please set input_topic to a valid MultiImage topic.");
230236
cleanupPartialConfiguration();
@@ -269,6 +275,10 @@ rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn DeepOb
269275
detection_pub_->on_activate();
270276
}
271277

278+
if (image_marker_pub_) {
279+
image_marker_pub_->on_activate();
280+
}
281+
272282
RCLCPP_INFO(this->get_logger(), "Deep object detection node activated and ready to process images");
273283
return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
274284
} catch (const std::exception & e) {
@@ -283,6 +293,7 @@ void DeepObjectDetectionNode::cleanupPartialConfiguration()
283293
backend_manager_.reset();
284294
preprocessor_.reset();
285295
detection_pub_.reset();
296+
image_marker_pub_.reset();
286297
}
287298

288299
void DeepObjectDetectionNode::cleanupAllResources()
@@ -293,6 +304,7 @@ void DeepObjectDetectionNode::cleanupAllResources()
293304
postprocessor_.reset();
294305
params_.class_names.clear();
295306
detection_pub_.reset();
307+
image_marker_pub_.reset();
296308
}
297309

298310
void DeepObjectDetectionNode::stopSubscriptionsAndTimer()
@@ -321,6 +333,10 @@ rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn DeepOb
321333
detection_pub_->on_deactivate();
322334
}
323335

336+
if (image_marker_pub_) {
337+
image_marker_pub_->on_deactivate();
338+
}
339+
324340
RCLCPP_INFO(this->get_logger(), "Deep object detection node deactivated");
325341
return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
326342
}
@@ -554,6 +570,19 @@ void DeepObjectDetectionNode::publishDetections(
554570
postprocessor_->fillDetectionMessage(headers[i], batch_detections[i], metas[i], msg);
555571
detection_pub_->publish(msg);
556572
total_published += batch_detections[i].size();
573+
574+
RCLCPP_DEBUG(
575+
this->get_logger(),
576+
" Published [%zu]: frame_id=%s, %zu detections",
577+
i,
578+
headers[i].frame_id.c_str(),
579+
batch_detections[i].size());
580+
581+
// Also publish ImageMarker annotations if publisher is available
582+
if (image_marker_pub_) {
583+
auto marker_msg = detectionsToImageMarker(headers[i], batch_detections[i]);
584+
image_marker_pub_->publish(marker_msg);
585+
}
557586
}
558587
RCLCPP_INFO_THROTTLE(
559588
this->get_logger(),
@@ -589,6 +618,86 @@ void DeepObjectDetectionNode::loadClassNames()
589618
this->get_logger(), "Loaded %zu class names from %s", params_.class_names.size(), class_names_path.c_str());
590619
}
591620

621+
visualization_msgs::msg::ImageMarker DeepObjectDetectionNode::detectionsToImageMarker(
622+
const std_msgs::msg::Header & header, const std::vector<SimpleDetection> & detections) const
623+
{
624+
visualization_msgs::msg::ImageMarker marker_msg;
625+
marker_msg.header = header;
626+
marker_msg.ns = "detections";
627+
marker_msg.id = 0;
628+
marker_msg.type = visualization_msgs::msg::ImageMarker::LINE_LIST;
629+
marker_msg.action = visualization_msgs::msg::ImageMarker::ADD;
630+
631+
// Set marker lifetime
632+
marker_msg.lifetime.sec = 0;
633+
marker_msg.lifetime.nanosec = 50000000; // 0.05 seconds - short lifetime to avoid ghosting
634+
marker_msg.scale = 2.0;
635+
636+
// Set outline color to green
637+
marker_msg.outline_color.r = 0.0;
638+
marker_msg.outline_color.g = 1.0;
639+
marker_msg.outline_color.b = 0.0;
640+
marker_msg.outline_color.a = 1.0;
641+
642+
// Iterate through detections and add bounding box line segments
643+
for (const auto & det : detections) {
644+
// Skip detections with invalid coordinates
645+
if (det.width <= 0 || det.height <= 0) {
646+
continue;
647+
}
648+
649+
// Calculate bounding box corners
650+
float x_min = det.x;
651+
float y_min = det.y;
652+
float x_max = det.x + det.width;
653+
float y_max = det.y + det.height;
654+
655+
// Add line points for bounding box rectangle (4 line segments = 8 points)
656+
// Top-left to top-right
657+
geometry_msgs::msg::Point pt1, pt2;
658+
pt1.x = x_min;
659+
pt1.y = y_min;
660+
pt1.z = 0;
661+
pt2.x = x_max;
662+
pt2.y = y_min;
663+
pt2.z = 0;
664+
marker_msg.points.push_back(pt1);
665+
marker_msg.points.push_back(pt2);
666+
667+
// Top-right to bottom-right
668+
pt1.x = x_max;
669+
pt1.y = y_min;
670+
pt1.z = 0;
671+
pt2.x = x_max;
672+
pt2.y = y_max;
673+
pt2.z = 0;
674+
marker_msg.points.push_back(pt1);
675+
marker_msg.points.push_back(pt2);
676+
677+
// Bottom-right to bottom-left
678+
pt1.x = x_max;
679+
pt1.y = y_max;
680+
pt1.z = 0;
681+
pt2.x = x_min;
682+
pt2.y = y_max;
683+
pt2.z = 0;
684+
marker_msg.points.push_back(pt1);
685+
marker_msg.points.push_back(pt2);
686+
687+
// Bottom-left to top-left
688+
pt1.x = x_min;
689+
pt1.y = y_max;
690+
pt1.z = 0;
691+
pt2.x = x_min;
692+
pt2.y = y_min;
693+
pt2.z = 0;
694+
marker_msg.points.push_back(pt1);
695+
marker_msg.points.push_back(pt2);
696+
}
697+
698+
return marker_msg;
699+
}
700+
592701
std::shared_ptr<rclcpp_lifecycle::LifecycleNode> createDeepObjectDetectionNode(const rclcpp::NodeOptions & options)
593702
{
594703
return std::make_shared<DeepObjectDetectionNode>(options);

0 commit comments

Comments
 (0)