ed_sensor_integration
sam_seg_module.cpp
Go to the documentation of this file.
2 
3 #include <filesystem>
4 #include <utility>
5 #include <sam_onnx_ros/segmentation.hpp>
6 #include <sensor_msgs/Image.h>
7 #include <sensor_msgs/PointCloud2.h>
8 #include <yolo_onnx_ros/detection.hpp>
9 #include <chrono>
10 #include <cstdio>
11 #include <limits>
12 
13 
14 
16 {
18  DL_INIT_PARAM yolo_params;
19 
20  SamWrapper samWrapper;
21  SEG::DL_INIT_PARAM sam_encoder_params;
22  SEG::DL_INIT_PARAM sam_decoder_params;
23  SEG::DL_RESULT sam_res;
25 };
26 
27 SamSegPipeline::SamSegPipeline() : pimpl_(std::make_unique<Impl>()), is_initialized_(false) {}
28 
30 
32 {
33  if (is_initialized_.load(std::memory_order_acquire))
34  return;
35 
37  std::string yolo_model, sam_encoder, sam_decoder;
38  config.value("yolo_model", yolo_model);
39  std::tie(pimpl_->yoloDetector, pimpl_->yolo_params) = Initialize(yolo_model);
40 
42  config.value("sam_encoder", sam_encoder);
43  config.value("sam_decoder", sam_decoder);
44 
45  std::string backend_str;
46  SEG::Backend backend = SEG::Backend::kOnnx;
47  if (config.value("sam_backend", backend_str))
48  {
49  if (backend_str == "tensorRT")
50  {
51  backend = SEG::Backend::kSpeedSam;
52  ROS_INFO("Using SpeedSAM (TensorRT) backend for SAM inference.");
53  }
54  else if (backend_str == "onnx")
55  {
56  backend = SEG::Backend::kOnnx;
57  ROS_INFO("Using ONNX backend for SAM inference.");
58  }
59  else
60  {
61  ROS_WARN("Unsupported SAM backend '%s'. Defaulting to ONNX.", backend_str.c_str());
62  }
63  }
64 
65  std::tie(pimpl_->samWrapper, pimpl_->sam_encoder_params, pimpl_->sam_decoder_params, pimpl_->sam_res, pimpl_->resSam) = Initialize(sam_encoder, sam_decoder, backend);
66 
67  // Publish initialized model state to readers in process().
68  is_initialized_.store(true, std::memory_order_release);
69 }
70 
71 SegmentationResult SamSegPipeline::process(const cv::Mat& img, const cv::Mat& depth_image, const std::string& ignore_label, bool verbose)
72 {
73  SegmentationResult seg_result;
74 
75  if (!is_initialized_.load(std::memory_order_acquire)) {
76  ROS_WARN_THROTTLE(1.0, "SamSegPipeline is currently initializing in the background. Skipping frame...");
77  return seg_result;
78  }
79 
80  // Total pipeline timing
81  auto pipeline_start = std::chrono::high_resolution_clock::now();
82 
84  auto yolo_infer_start = std::chrono::high_resolution_clock::now();
85 
86  std::vector<DL_RESULT> resYolo;
87  resYolo = Detector(pimpl_->yoloDetector, img);
88 
89  auto yolo_infer_end = std::chrono::high_resolution_clock::now();
90 
92  pimpl_->sam_res.boxes.clear();
93  pimpl_->resSam.clear();
94 
95  for (const auto& result : resYolo)
96  {
97  std::string yolo_class = pimpl_->yoloDetector->classes[result.classId];
98 
99  // Filter by ignore_label (typically the supporting surface itself)
100  if (!ignore_label.empty() && yolo_class == ignore_label)
101  {
102  ROS_DEBUG("Pre-filtering box for '%s' (matches ignore_label '%s')", yolo_class.c_str(), ignore_label.c_str());
103  continue;
104  }
105 
106  // Filter out boxes with no valid depth points within the target volume frustum
107  if (!depth_image.empty())
108  {
109  // Compute intersection of the YOLO box with image bounds
110  int x0 = std::max(0, result.box.x);
111  int y0 = std::max(0, result.box.y);
112  int x1 = std::min(depth_image.cols, result.box.x + result.box.width);
113  int y1 = std::min(depth_image.rows, result.box.y + result.box.height);
114 
115  if (x1 <= x0 || y1 <= y0)
116  {
117  // Box lies entirely outside the depth image — reject
118  ROS_WARN("Pre-filtering box for '%s' (entirely outside depth image bounds)", yolo_class.c_str());
119  continue;
120  }
121 
122  int w = x1 - x0;
123  int h = y1 - y0;
124 
125  cv::Mat box_depth = depth_image(cv::Rect(x0, y0, w, h));
126  // Count pixels that are positive and finite (non-zero, non-NaN, non-inf) — at least 30% of
127  // the visible portion must have valid depth; lower threshold if too aggressive
128  cv::Mat valid_mask = (box_depth > 0.0f) & (box_depth < std::numeric_limits<float>::infinity());
129  if (cv::countNonZero(valid_mask) < 0.3 * w * h)
130  {
131  ROS_WARN("Pre-filtering box for '%s' (less than 30%% depth points within frustum volume)", yolo_class.c_str());
132  continue;
133  }
134  }
135 
136  pimpl_->sam_res.boxes.push_back(result.box);
137  seg_result.labels.push_back(yolo_class);
138  seg_result.confidences.push_back(result.confidence);
139  ROS_DEBUG("Confidence: %f", result.confidence);
140  ROS_DEBUG("Class: %s", yolo_class.c_str());
141  ROS_DEBUG("Class ID: %d", result.classId);
142  }
143 
144  auto sam_infer_start = std::chrono::high_resolution_clock::now();
145 
146  SegmentAnything(pimpl_->samWrapper, pimpl_->sam_encoder_params, pimpl_->sam_decoder_params, img, pimpl_->resSam, pimpl_->sam_res);
147 
148  auto sam_infer_end = std::chrono::high_resolution_clock::now();
149 
150  auto pipeline_end = std::chrono::high_resolution_clock::now();
151 
152  // Compute latency measurements if requested
153  if (verbose)
154  {
155  seg_result.latency.yolo_init_ms = 0.0;
156  seg_result.latency.yolo_infer_ms = std::chrono::duration<double, std::milli>(yolo_infer_end - yolo_infer_start).count();
157  seg_result.latency.sam_init_ms = 0.0;
158  seg_result.latency.sam_infer_ms = std::chrono::duration<double, std::milli>(sam_infer_end - sam_infer_start).count();
159  seg_result.latency.total_pipeline_ms = std::chrono::duration<double, std::milli>(pipeline_end - pipeline_start).count();
160 
161  ROS_WARN("\n=== Segmentation Pipeline Latency ===\n");
162  ROS_WARN(" YOLO infer: %7.2f ms (%zu bboxs)\n", seg_result.latency.yolo_infer_ms, resYolo.size());
163  ROS_WARN(" SAM infer: %7.2f ms (%zu masks)\n", seg_result.latency.sam_infer_ms, pimpl_->resSam.empty() ? 0 : pimpl_->resSam.front().masks.size());
164  ROS_WARN(" Total pipeline: %7.2f ms\n", seg_result.latency.total_pipeline_ms);
165  }
166 
167  if (!pimpl_->resSam.empty())
168  {
169  seg_result.masks = std::move(pimpl_->resSam.front().masks);
170  }
171  seg_result.boxes = std::move(pimpl_->sam_res.boxes);
172 
173 
174  // Verify 1:1 alignment between masks and labels.
175  // Normally guaranteed because SAM processes boxes sequentially and pushes one mask per box.
176  // If SAM failed for any box it pushes an empty placeholder (see sam_onnx_ros/src/utils.cpp),
177  // so sizes should always match. If they don't, we cannot know which label belongs to which
178  // mask — discard labels for this frame so objects are still detected but not mislabeled.
179  if (seg_result.masks.size() != seg_result.labels.size())
180  {
181  ROS_WARN("SAM produced %zu masks for %zu YOLO boxes — alignment broken, discarding labels this frame",
182  seg_result.masks.size(), seg_result.labels.size());
183  seg_result.labels.clear();
184  seg_result.confidences.clear();
185  }
186 
187  return seg_result;
188 }
189 
190 
191 void overlayMasksOnImage_(cv::Mat& rgb, const std::vector<cv::Mat>& masks)
192 {
193  // Define colors in BGR format for OpenCV (high contrast)
194  std::vector<cv::Scalar> colors = {
195  cv::Scalar(0, 0, 255), // Red
196  cv::Scalar(0, 255, 0), // Green
197  cv::Scalar(255, 0, 0), // Blue
198  cv::Scalar(0, 255, 255), // Yellow
199  cv::Scalar(255, 0, 255), // Magenta
200  cv::Scalar(255, 255, 0), // Cyan
201  cv::Scalar(128, 0, 128), // Purple
202  cv::Scalar(0, 128, 128) // Brown
203  };
204 
205  // Create a copy for the overlay (preserves original for contours)
206  cv::Mat overlay = rgb.clone();
207 
208  for (size_t i = 0; i < masks.size(); i++)
209  {
210  // Get a working copy of the mask
211  cv::Mat working_mask = masks[i].clone();
212 
213  // Check if mask needs resizing
214  if (working_mask.rows != rgb.rows || working_mask.cols != rgb.cols)
215  cv::resize(working_mask, working_mask, rgb.size(), 0, 0, cv::INTER_NEAREST);
216 
217  // Ensure the mask is binary (values 0 or 255)
218  if (cv::countNonZero((working_mask > 0) & (working_mask < 255)) > 0)
219  cv::threshold(working_mask, working_mask, 127, 255, cv::THRESH_BINARY);
220 
221  // Use a different color for each mask
222  cv::Scalar color = colors[i % colors.size()];
223 
224  // Create the colored overlay with this mask's specific color
225  cv::Mat colorMask = cv::Mat::zeros(rgb.size(), CV_8UC3);
226  colorMask.setTo(color, working_mask);
227 
228  // Add this mask's overlay to the combined overlay
229  cv::addWeighted(overlay, 1.0, colorMask, 0.2, 0, overlay);
230 
231  // Find contours of the mask - use CHAIN_APPROX_NONE for most accurate contours
233  cv::findContours(working_mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
234 
235  // Draw double contours for better visibility (outer black, inner colored)
236  cv::drawContours(rgb, contours, -1, cv::Scalar(0, 0, 0), 2); // Outer black border
237  cv::drawContours(rgb, contours, -1, color, 1); // Inner colored line
238  }
239 
240  // Apply the semi-transparent overlay with all masks
241  cv::addWeighted(rgb, 0.7, overlay, 0.3, 0, rgb);
242 }
SegmentationResult
Result of the segmentation pipeline containing masks, bounding boxes, and YOLO classification info fo...
Definition: sam_seg_module.h:34
SegmentationResult::labels
std::vector< std::string > labels
Definition: sam_seg_module.h:38
std::string
utility
sam_seg_module.h
SegmentationLatency::sam_infer_ms
double sam_infer_ms
Definition: sam_seg_module.h:26
SegmentationLatency::total_pipeline_ms
double total_pipeline_ms
Definition: sam_seg_module.h:27
std::vector< SEG::DL_RESULT >
std::vector::size
T size(T... args)
std::chrono::duration
SamSegPipeline::pimpl_
std::unique_ptr< Impl > pimpl_
Definition: sam_seg_module.h:70
filesystem
SamSegPipeline::initialize
void initialize(tue::Configuration &config)
Initialize the YOLO and SAM models from configuration.
Definition: sam_seg_module.cpp:31
std::vector::clear
T clear(T... args)
std::tie
T tie(T... args)
std::vector::push_back
T push_back(T... args)
SamSegPipeline::Impl::sam_res
SEG::DL_RESULT sam_res
Definition: sam_seg_module.cpp:23
tue::config::ReaderWriter
SamSegPipeline::Impl::sam_decoder_params
SEG::DL_INIT_PARAM sam_decoder_params
Definition: sam_seg_module.cpp:22
overlayMasksOnImage_
void overlayMasksOnImage_(cv::Mat &rgb, const std::vector< cv::Mat > &masks)
Overlay segmentation masks on the RGB image for visualization purposes.
Definition: sam_seg_module.cpp:191
SegmentationResult::masks
std::vector< cv::Mat > masks
Definition: sam_seg_module.h:36
SamSegPipeline::Impl::yolo_params
DL_INIT_PARAM yolo_params
Definition: sam_seg_module.cpp:18
SamSegPipeline::Impl::sam_encoder_params
SEG::DL_INIT_PARAM sam_encoder_params
Definition: sam_seg_module.cpp:21
SamSegPipeline::Impl::yoloDetector
std::unique_ptr< YOLO_V8 > yoloDetector
Definition: sam_seg_module.cpp:17
std::atomic::load
T load(T... args)
chrono
SegmentationResult::confidences
std::vector< float > confidences
Definition: sam_seg_module.h:39
std::string::c_str
T c_str(T... args)
SamSegPipeline::Impl::resSam
std::vector< SEG::DL_RESULT > resSam
Definition: sam_seg_module.cpp:24
SegmentationResult::boxes
std::vector< cv::Rect > boxes
Definition: sam_seg_module.h:37
SegmentationResult::latency
SegmentationLatency latency
Definition: sam_seg_module.h:40
SamSegPipeline::SamSegPipeline
SamSegPipeline()
Definition: sam_seg_module.cpp:27
SamSegPipeline::Impl
Definition: sam_seg_module.cpp:15
std::min
T min(T... args)
tue::config::ReaderWriter::value
bool value(const std::string &name, T &value, RequiredOrOptional opt=REQUIRED)
SamSegPipeline::is_initialized_
std::atomic< bool > is_initialized_
Definition: sam_seg_module.h:72
SegmentationLatency::yolo_infer_ms
double yolo_infer_ms
Definition: sam_seg_module.h:24
SegmentationLatency::sam_init_ms
double sam_init_ms
Definition: sam_seg_module.h:25
limits
std
SamSegPipeline::process
SegmentationResult process(const cv::Mat &img, const cv::Mat &depth_image=cv::Mat(), const std::string &ignore_label="", bool verbose=false)
Process the input image and generate segmentation masks.
Definition: sam_seg_module.cpp:71
std::chrono::duration::count
T count(T... args)
std::string::empty
T empty(T... args)
std::atomic::store
T store(T... args)
SegmentationLatency::yolo_init_ms
double yolo_init_ms
Definition: sam_seg_module.h:23
SamSegPipeline::Impl::samWrapper
SamWrapper samWrapper
Definition: sam_seg_module.cpp:20
SamSegPipeline::~SamSegPipeline
~SamSegPipeline()
std::max
T max(T... args)
std::unique_ptr< YOLO_V8 >
std::numeric_limits
depth_image
cv::Mat depth_image
cstdio
config
tue::config::ReaderWriter config
std::chrono::high_resolution_clock::now
T now(T... args)