ed_sensor_integration
kinect/updater.cpp
Go to the documentation of this file.
1 #include <cv_bridge/cv_bridge.h>
2 
3 #include "ed/kinect/updater.h"
4 #include <ed/world_model.h>
5 #include <ed/entity.h>
6 #include <ed/error_context.h>
7 #include <ed/update_request.h>
8 
9 #include <geolib/Shape.h>
10 
11 #include "ed/kinect/association.h"
12 #include "ed/kinect/renderer.h"
13 #include "ed/convex_hull_calc.h"
14 #include <filesystem>
15 #include <opencv2/highgui/highgui.hpp>
16 #include <pcl/point_cloud.h>
17 #include <rgbd/view.h>
18 #include <ros/console.h>
19 #include <ros/node_handle.h>
20 #include <sensor_msgs/Image.h>
21 #include <sensor_msgs/PointCloud2.h>
22 
23 
25 // ----------------------------------------------------------------------------------------------------
26 
27 Updater::Updater(tue::Configuration config) : verbose(false)
28 {
29  if (config.readGroup("segmenter", tue::config::REQUIRED))
30  {
31  segmenter_ = std::make_unique<Segmenter>(config);
32  }
33  else
34  {
35  ROS_ERROR("Failed to read segmenter configuration group from config file, cannot initialize Updater");
36  throw std::runtime_error("Failed to read segmenter configuration group from config file");
37  }
38 
39  // Initialize the image publisher
41  if (verbose)
42  {
43  ros::NodeHandle nh("~");
44  mask_pub_ = nh.advertise<sensor_msgs::Image>("segmentation_masks_verbose", 1);
45  cloud_pub_ = nh.advertise<sensor_msgs::PointCloud2>("point_cloud_verbose", 1);
46  box_pub_ = nh.advertise<sensor_msgs::Image>("bounding_boxes_verbose", 1);
47  }
48 }
49 
50 // ----------------------------------------------------------------------------------------------------
51 
53 {
54 }
55 
56 // ----------------------------------------------------------------------------------------------------
57 
58 bool Updater::update(const ed::WorldModel& world, const rgbd::ImageConstPtr& image, const geo::Pose3D& sensor_pose_const,
59  const UpdateRequest& req, UpdateResult& res)
60 {
61  ed::ErrorContext errc("Kinect::Updater", "update");
62  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
63  // Prepare some things
64 
65  // will contain depth image filtered with given update shape and world model (background) subtraction
66  cv::Mat filtered_depth_image;
67 
68  // sensor pose might be update, so copy (making non-const)
69  geo::Pose3D sensor_pose = sensor_pose_const;
70 
71  // depth image
72  const cv::Mat& depth_image = image->getDepthImage();
73  const cv::Mat& rgb_image = image->getRGBImage();
74 
75  // Determine depth image camera model
76  rgbd::View view(*image, depth_image.cols);
77  const geo::DepthCamera& cam_model = view.getRasterizer();
78 
79  std::string area_description;
80 
81  if (!req.area_description.empty())
82  {
83  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
84  // Check if the update_command is a segmented entity.
85  // If so, lookup the corresponding area_description
86 
87  bool fit_supporting_entity = req.fit_supporting_entity;
88 
90  if (it_area_descr != id_to_area_description_.end())
91  {
92  area_description = it_area_descr->second;
93  fit_supporting_entity = false; // We are only interested in the supported entity, so don't fit the supporting entity
94  }
95  else
96  area_description = req.area_description;
97 
98  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
99  // Parse space description (split on space)
100 
101  std::size_t i_space = area_description.find(' ');
102 
103  ed::UUID entity_id;
104  std::string area_name;
105 
106  if (i_space == std::string::npos)
107  {
108  entity_id = area_description;
109  }
110  else
111  {
112  area_name = area_description.substr(0, i_space);
113  entity_id = area_description.substr(i_space + 1);
114  }
115 
116  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
117  // Check for entity
118 
119  ed::EntityConstPtr e = world.getEntity(entity_id);
120 
121  if (!e)
122  {
123  res.error << "No such entity: '" << entity_id.str() << "'.";
124  return false;
125  }
126  else if (!e->has_pose())
127  {
128  res.error << "Entity: '" << entity_id.str() << "' has no pose.";
129  return false;
130  }
131 
132  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
133  // Update entity position
134 
135  geo::Pose3D new_pose;
136 
137  if (fit_supporting_entity)
138  {
139  if (!fitter_.isConfigured())
140  {
141  fitter_.configureBeamModel(image->getCameraModel());
142  }
143  FitterData fitter_data;
144  fitter_.processSensorData(*image, sensor_pose, fitter_data);
145 
146  if (fitter_.estimateEntityPose(fitter_data, world, entity_id, e->pose(), new_pose, req.max_yaw_change))
147  {
148  res.update_req.setPose(entity_id, new_pose);
149  }
150  else
151  {
152  // res.error << "Could not determine pose of '" << entity_id.str() << "'.";
153  // return false;
154 
155  // Could not fit entity, so keep the old pose
156  new_pose = e->pose();
157  }
158  }
159  else
160  {
161  new_pose = e->pose();
162  }
163 
164  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
165  // Optimize sensor pose
166 
167  if (false)
168  {
169  fitZRP(*e->visual(), new_pose, *image, sensor_pose_const, sensor_pose);
170 
171  ROS_DEBUG_STREAM("Old sensor pose: " << sensor_pose_const);
172  ROS_DEBUG_STREAM("New sensor pose: " << sensor_pose);
173  }
174 
175  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
176  // Determine segmentation area
177 
178  if (!area_name.empty())
179  {
180  // Determine segmentation area (the geometrical shape in which the segmentation should take place)
181 
183  if (it == e->volumes().end())
184  {
185  res.error << "No area '" << area_name << "' for entity '" << entity_id.str() << "'.";
186  return false;
187  }
188  geo::Shape shape = *(it->second);
189  if (shape.getMesh().empty())
190  {
191  // Empty shapes shouldn't be stored at all, but check for robustness
192  res.error << "Could not load shape of area '" << area_name << "' for entity '" << entity_id.str() << "'.";
193  return false;
194  }
195 
196  // - - - - - - - - - - - - - - - - - - - - - - - -
197  // Segment
198 
199  geo::Pose3D shape_pose = sensor_pose.inverse() * new_pose;
200  segmenter_->calculatePointsWithin(*image, shape, shape_pose, filtered_depth_image);
201  }
202  }
203  else
204  {
205  filtered_depth_image = image->getDepthImage().clone();
206  }
207 
208  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
209  // Remove background
210 
211  // The world model may have been updated above, but the changes are only captured in
212  // an update request. Therefore, make a (shallow) copy of the world model and apply
213  // the changes, and use this for the background removal
214 
215  errc.change("Kinect::Updater", "update: preparing world model");
216 
217 
218  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
219  // Clear convex hulls that are no longer there
220 
221  std::vector<ed::EntityConstPtr> associatable_entities;
222  for(ed::WorldModel::const_iterator e_it = world.begin(); e_it != world.end(); ++e_it)
223  {
224  const ed::EntityConstPtr& e = *e_it;
225  if (e->visual() || !e->has_pose() || e->convexHull().points.empty())
226  continue;
227 
228  associatable_entities.push_back(e);
229  /*
230  geo::Vec3 p_3d = sensor_pose.inverse() * e->pose().t;
231 
232  cv::Point p_2d = cam_model.project3Dto2D(p_3d);
233  if (p_2d.x < 0 || p_2d.y < 0 || p_2d.x >= depth_image.cols || p_2d.y >= depth_image.rows)
234  continue;
235  */
236  /*float d = depth_image.at<float>(p_2d);
237  if (d > 0 && d == d && -p_3d.z < d)
238  {
239  ROS_INFO("Request to remove entity %s", e->id().c_str());
240  res.update_req.removeEntity(e->id());
241  associatable_entities.pop_back();
242  }*/
243  }
244 
245  // - - - - - - - - - - - - - - - - - - - - - - - -
246  // Cluster
247  errc.change("Kinect::Updater", "update: clustering");
248 
249  // Do preprocessRGBForSegmentation if you want to treat the rgb image as the depth image for segmentation, meaning that only the pixels where depth is non-zero will be kept in the RGB image. This can be used to improve the segmentation results of the RGB image, but it is not necessary for the depth segmentation.
250  // cv::Mat filtered_rgb_image;
251  // filtered_rgb_image = segmenter_->preprocessRGBForSegmentation(rgb_image, filtered_depth_image);
252  SegmentationResult cluster_result = segmenter_->cluster(filtered_depth_image, cam_model, sensor_pose, res.entity_updates, rgb_image, area_description, verbose);
253 
254  std::vector<cv::Mat>& clustered_images = cluster_result.masks;
255 
256  if (verbose)
257  {
258  publishSegmentationResults(filtered_depth_image, rgb_image, sensor_pose, clustered_images, cluster_result.boxes, res.entity_updates);
259  }
260  // - - - - - - - - - - - - - - - - - - - - - - - -
261  // Perform association and update
262  errc.change("Kinect::Updater", "update: association");
263  associateAndUpdate(associatable_entities, image, sensor_pose, res.entity_updates, res.update_req);
264 
265  // - - - - - - - - - - - - - - - - - - - - - - - -
266  // Remove entities that are not associated
267  errc.change("Kinect::Updater", "update: remove unassociated entities");
268  for (std::vector<ed::EntityConstPtr>::const_iterator it = associatable_entities.begin(); it != associatable_entities.end(); ++it)
269  {
270  ed::EntityConstPtr e = *it;
271 
272  // Check if entity is in frustum
273  const geo::Vec3 p_3d = sensor_pose.inverse() * e->pose().t; // Only taking into account the pose, not the shape
274  const cv::Point p_2d = cam_model.project3Dto2D(p_3d);
275  if (p_2d.x < 0 || p_2d.y < 0 || p_2d.x >= depth_image.cols || p_2d.y >= depth_image.rows)
276  continue; // Outside of frustum
277 
278  // If the entity is not updated, remove it
279  if (res.update_req.updated_entities.find(e->id()) == res.update_req.updated_entities.end())
280  {
281  ROS_INFO("Entity not associated and not seen in the frustum, while seeable");
282 
283  float d = depth_image.at<float>(p_2d);
284  if (d > 0 && d == d && -p_3d.z < d)
285  {
286  ROS_INFO_STREAM("We can shoot a ray through the center(" << d << " > " << -p_3d.z << "), removing entity " << e->id());
287  res.update_req.removeEntity(e->id());
288  res.removed_entity_ids.push_back(e->id());
289  }
290  }
291 
292  }
293 
294  // - - - - - - - - - - - - - - - - - - - - - - - -
295  // Remember the area description with which the segments where found
296  errc.change("Kinect::Updater", "update: store area description to segments");
297  if (!area_description.empty())
298  {
300  {
301  const EntityUpdate& up = *it;
302  id_to_area_description_[up.id] = area_description;
303  }
304  }
305 
306  return true;
307 }
308 
309 void Updater::publishSegmentationResults(const cv::Mat& filtered_depth_image, const cv::Mat& rgb,
310  const geo::Pose3D& sensor_pose, std::vector<cv::Mat>& clustered_images,
311  const std::vector<cv::Rect>& boxes, std::vector<EntityUpdate>& res_updates)
312 {
313  // Guard: all three publishers must have been advertised before calling this function.
314  // They are only advertised when verbose == true in the Updater constructor.
315  // If they evaluate to false (meaning they are null/unadvertised), abort publishing.
316  if (!this->box_pub_ || !this->mask_pub_ || !this->cloud_pub_)
317  {
318  ROS_ERROR_THROTTLE(1.0, "publishSegmentationResults called but publishers are not active. Did you set verbose: true?");
319  return;
320  }
321 
322  // Overlay masks on the RGB image
323  cv::Mat visualization = rgb.clone();
324  cv::Mat box_visualization = rgb.clone();
325  for(const auto& box : boxes)
326  {
327  cv::rectangle(box_visualization, box, cv::Scalar(0, 255, 0), 2);
328  }
329 
330  sensor_msgs::ImagePtr box_msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", box_visualization).toImageMsg();
331  box_msg->header.stamp = ros::Time::now();
332  this->box_pub_.publish(box_msg);
333 
334  // Create a path to save the image using platform-independent temp directory
336  cv::imwrite((temp_dir / "visualization.png").string(), visualization);
337 
338  // Create a properly normalized depth visualization
339  cv::Mat depth_vis;
340  double min_val, max_val;
341  cv::minMaxLoc(filtered_depth_image, &min_val, &max_val);
342 
343  // Handle empty depth image case
344  if (max_val == 0)
345  {
346  depth_vis = cv::Mat::zeros(filtered_depth_image.size(), CV_8UC1);
347  }
348  else
349  {
350  // Scale to full 8-bit range and convert to 8-bit
351  filtered_depth_image.convertTo(depth_vis, CV_8UC1, 255.0 / max_val);
352 
353  // Apply a colormap for better visibility
354  cv::Mat depth_color;
355  cv::applyColorMap(depth_vis, depth_color, cv::COLORMAP_JET);
356  cv::imwrite((temp_dir / "visualization_depth_color.png").string(), depth_color);
357  }
358 
359  // Save both grayscale and color versions
360  cv::imwrite((temp_dir / "visualization_depth.png").string(), depth_vis);
361  overlayMasksOnImage_(visualization, clustered_images);
362  // save after overlaying masks
363  cv::imwrite((temp_dir / "visualization_with_masks.png").string(), visualization);
364 
365  // Convert to ROS message
366  sensor_msgs::ImagePtr msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", visualization).toImageMsg();
367  msg->header.stamp = ros::Time::now();
368 
369  typedef pcl::PointCloud<pcl::PointXYZRGB> PointCloud;
370  PointCloud::Ptr combined_cloud (new PointCloud);
371 
372  combined_cloud->header.frame_id = "map";
373 
374  // Add points from all entity updates
375  for (const EntityUpdate& update : res_updates)
376  {
377  for (const geo::Vec3& point : update.points)
378  {
379  // Transform from camera to map frame
380  geo::Vec3 p_map = sensor_pose * point;
381  pcl::PointXYZRGB pcl_point;
382  pcl_point.x = p_map.x;
383  pcl_point.y = p_map.y;
384  pcl_point.z = p_map.z;
385  pcl_point.r = 255; // White
386  pcl_point.g = 255;
387  pcl_point.b = 255;
388  combined_cloud->push_back(pcl_point);
389  }
390 
391  // Add outlier points (red)
392  for (const geo::Vec3& point : update.outlier_points) {
393  geo::Vec3 p_map = sensor_pose * point;
394  pcl::PointXYZRGB pcl_point;
395  pcl_point.x = p_map.x;
396  pcl_point.y = p_map.y;
397  pcl_point.z = p_map.z;
398  pcl_point.r = 255; // Red
399  pcl_point.g = 0;
400  pcl_point.b = 0;
401  combined_cloud->push_back(pcl_point);
402  }
403  }
404 
405  sensor_msgs::PointCloud2 cloud_msg;
406  pcl::toROSMsg(*combined_cloud, cloud_msg);
407  cloud_msg.header.stamp = ros::Time::now();
408  cloud_msg.header.frame_id = "map"; // Use appropriate frame ID
409 
410  // Publish
411  this->mask_pub_.publish(msg);
412  this->cloud_pub_.publish(cloud_msg);
413 }
SegmentationResult
Result of the segmentation pipeline containing masks, bounding boxes, and YOLO classification info fo...
Definition: sam_seg_module.h:34
Updater::verbose
bool verbose
Definition: kinect/updater.h:94
ed::WorldModel::getEntity
EntityConstPtr getEntity(const ed::UUID &id) const
UpdateResult::update_req
ed::UpdateRequest & update_req
Definition: kinect/updater.h:48
geo::Mesh::empty
bool empty() const
std::string
std::shared_ptr
EntityUpdate
collection structure for laser entities
Definition: kinect/entity_update.h:10
sam_seg_module.h
update_request.h
std::filesystem::temp_directory_path
T temp_directory_path(T... args)
std::vector
std::map::find
T find(T... args)
rgb_image
cv::Mat rgb_image
geo::Vec3T
Shape.h
association.h
associateAndUpdate
void associateAndUpdate(const std::vector< ed::EntityConstPtr > &entities, const rgbd::ImageConstPtr &image, const geo::Pose3D &sensor_pose, std::vector< EntityUpdate > &clusters, ed::UpdateRequest &req)
Definition: association.cpp:18
geo::Transform3T
ed::UpdateRequest::removeEntity
void removeEntity(const UUID &id)
filesystem
Fitter::estimateEntityPose
bool estimateEntityPose(const FitterData &data, const ed::WorldModel &world, const ed::UUID &id, const geo::Pose3D &expected_pose, geo::Pose3D &fitted_pose, double max_yaw_change=M_PI) const
estimateEntityPose performs the entity pose estimation. Basically, tries to call estimateEntityPoseIm...
Definition: fitter.cpp:258
FitterData
The FitterData struct contains the downprojected (hence 2D) sensor readings as well as the sensor pos...
Definition: fitter.h:68
geo::Shape::getMesh
virtual const Mesh & getMesh() const
ed::ErrorContext
ed::WorldModel::begin
const_iterator begin() const
std::filesystem::path
ed::WorldModel::const_iterator
EntityIterator const_iterator
ed::EntityConstPtr
shared_ptr< const Entity > EntityConstPtr
ed::WorldModel::end
const_iterator end() const
std::vector::push_back
T push_back(T... args)
image
cv::Mat image
UpdateResult::entity_updates
std::vector< EntityUpdate > entity_updates
Definition: kinect/updater.h:46
geo::Vec3T::y
T y
tue::config::ReaderWriter
geo::Transform3T::inverse
Transform3T inverse() const
SegmentationResult::masks
std::vector< cv::Mat > masks
Definition: sam_seg_module.h:36
rgbd::View::getRasterizer
const geo::DepthCamera & getRasterizer() const
ed::UpdateRequest::updated_entities
std::set< UUID > updated_entities
Updater::~Updater
~Updater()
Definition: kinect/updater.cpp:52
UpdateResult::error
std::stringstream error
Definition: kinect/updater.h:49
ed::UpdateRequest::setPose
void setPose(const UUID &id, const geo::Pose3D &pose)
fitZRP
void fitZRP(const geo::Shape &shape, const geo::Pose3D &shape_pose, const rgbd::Image &image, const geo::Pose3D &sensor_pose, geo::Pose3D &updated_sensor_pose)
Definition: renderer.cpp:49
geo::Transform3T::t
Vec3T< T > t
std::runtime_error
tue::config::OPTIONAL
OPTIONAL
ed::UUID
SegmentationResult::boxes
std::vector< cv::Rect > boxes
Definition: sam_seg_module.h:37
Updater::Updater
Updater(tue::Configuration config)
Definition: kinect/updater.cpp:27
Updater::cloud_pub_
ros::Publisher cloud_pub_
Definition: kinect/updater.h:92
req
string req
Updater::fitter_
Fitter fitter_
Definition: kinect/updater.h:83
std::map
Fitter::isConfigured
bool isConfigured()
Definition: fitter.h:125
ed::WorldModel
UpdateResult::removed_entity_ids
std::vector< ed::UUID > removed_entity_ids
Definition: kinect/updater.h:47
std::string::substr
T substr(T... args)
view.h
tue::config::ReaderWriter::value
bool value(const std::string &name, T &value, RequiredOrOptional opt=REQUIRED)
world_model.h
Updater::publishSegmentationResults
void publishSegmentationResults(const cv::Mat &filtered_depth_image, const cv::Mat &rgb, const geo::Pose3D &sensor_pose, std::vector< cv::Mat > &clustered_images, const std::vector< cv::Rect > &boxes, std::vector< EntityUpdate > &res_updates)
Publish segmentation results and pointcloud estimation as ROS messages.
Definition: kinect/updater.cpp:309
Updater::update
bool update(const ed::WorldModel &world, const rgbd::ImageConstPtr &image, const geo::Pose3D &sensor_pose, const UpdateRequest &req, UpdateResult &res)
Definition: kinect/updater.cpp:58
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
std::vector::begin
T begin(T... args)
geo::DepthCamera::project3Dto2D
cv::Point_< Tout > project3Dto2D(const geo::Vec3T< Tin > &p) const
UpdateRequest
Definition: kinect/updater.h:21
tue::config::ReaderWriter::readGroup
bool readGroup(const std::string &name, const RequiredOrOptional opt=OPTIONAL)
Updater::mask_pub_
ros::Publisher mask_pub_
Definition: kinect/updater.h:91
geo::Vec3T::z
T z
std::string::empty
T empty(T... args)
Fitter::configureBeamModel
void configureBeamModel(const image_geometry::PinholeCameraModel &cammodel)
configure the beam model (nr of data points and focal length) according to the camera you are using.
Definition: fitter.cpp:475
geo::DepthCamera
std::size_t
Fitter::processSensorData
void processSensorData(const rgbd::Image &image, const geo::Pose3D &sensor_pose, FitterData &data) const
processSensorData pre-processes sensor data, i.e., performs a downprojection of the input depth image...
Definition: fitter.cpp:486
std::map::end
T end(T... args)
tue::config::REQUIRED
REQUIRED
updater.h
entity.h
renderer.h
convex_hull_calc.h
EntityUpdate::id
ed::UUID id
Definition: kinect/entity_update.h:14
Updater::box_pub_
ros::Publisher box_pub_
Definition: kinect/updater.h:93
Updater::id_to_area_description_
std::map< ed::UUID, std::string > id_to_area_description_
Definition: kinect/updater.h:88
depth_image
cv::Mat depth_image
rgbd::View
geo::Vec3T::x
T x
error_context.h
Updater::segmenter_
std::unique_ptr< Segmenter > segmenter_
Definition: kinect/updater.h:85
ed::ErrorContext::change
void change(const char *msg, const char *value=0)
ed::UUID::str
const std::string & str() const
UpdateResult
Definition: kinect/updater.h:42
geo::Shape
config
tue::config::ReaderWriter config