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