laserscan_kinect.cpp
Go to the documentation of this file.
2 
3 #include <cmath>
4 #include <algorithm>
5 #include <typeinfo>
6 #include <thread>
7 
8 #ifndef M_PI
9 #define M_PI (3.14159265358979323846)
10 #endif
11 
12 namespace laserscan_kinect {
13 
14 sensor_msgs::LaserScanPtr LaserScanKinect::getLaserScanMsg(
15  const sensor_msgs::ImageConstPtr & depth_msg,
16  const sensor_msgs::CameraInfoConstPtr & info_msg) {
17  // Configure message if necessary
19  cam_model_.fromCameraInfo(info_msg);
20 
21  double min_angle, max_angle;
22  using Point = cv::Point2d;
23 
24  // Calculate vertical field of view angles
26  Point(cam_model_.cx(), depth_msg->height - 1), min_angle, max_angle);
27  double vertical_fov = max_angle - min_angle;
28 
29  // Calculate horizontal field of view angles
31  Point(depth_msg->width - 1, cam_model_.cy()), min_angle, max_angle);
32 
34  calcGroundDistancesForImgRows(vertical_fov);
35  }
36 
39  }
40 
41  scan_msg_->angle_min = min_angle;
42  scan_msg_->angle_max = max_angle;
43  scan_msg_->angle_increment = (max_angle - min_angle) / (depth_msg->width - 1);
44  scan_msg_->time_increment = 0.0;
45  scan_msg_->scan_time = 1.0 / 30.0;
46 
47  // Set min and max range in preparing message
49  scan_msg_->range_min = range_min_ * *std::min_element(tilt_compensation_factor_.begin(),
51  scan_msg_->range_max = range_max_ * *std::max_element(tilt_compensation_factor_.begin(),
53  }
54  else {
55  scan_msg_->range_min = range_min_;
56  scan_msg_->range_max = range_max_;
57  }
58 
59  calcScanMsgIndexForImgCols(depth_msg);
60 
61  // Check if scan_height is in image_height
62  if (scan_height_ / 2.0 > cam_model_.cy() || scan_height_ / 2.0 > depth_msg->height - cam_model_.cy()) {
63  std::stringstream ss;
64  ss << "scan_height ( " << scan_height_ << " pixels) is too large for the image height.";
65  throw std::runtime_error(ss.str());
66  }
67  image_vertical_offset_ = static_cast<int>(cam_model_.cy()- scan_height_ / 2.0);
68 
70  }
71 
72  // Prepare laser scan message
73  scan_msg_->header = depth_msg->header;
74  if (output_frame_id_.length() > 0) {
75  scan_msg_->header.frame_id = output_frame_id_;
76  }
77 
78  scan_msg_->ranges.assign(depth_msg->width, std::numeric_limits<float>::quiet_NaN());
79 
80  // Check if image encoding is correct
81  if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) {
82  convertDepthToPolarCoords<uint16_t>(depth_msg);
83  }
84  else if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) {
85  convertDepthToPolarCoords<float>(depth_msg);
86  }
87  else {
88  std::stringstream ss;
89  ss << "Depth image has unsupported encoding: " << depth_msg->encoding;
90  throw std::runtime_error(ss.str());
91  }
92 
93  // Generate and publish debug image if necessary
94  if (publish_dbg_image_) {
96  }
98 
99  return scan_msg_;
100 }
101 
102 void LaserScanKinect::setRangeLimits(const float rmin, const float rmax) {
103  if (rmin >= 0 && rmin < rmax) {
104  range_min_ = rmin;
105  }
106  else {
107  range_min_ = 0;
108  ROS_ERROR("Incorrect value of range minimal parameter. Set default value: 0.");
109  }
110  if (rmax >= 0 && rmin < rmax) {
111  range_max_ = rmax;
112  }
113  else {
114  range_max_ = 10;
115  ROS_ERROR("Incorrect value of range maximum parameter. Set default value: 10.");
116  }
117 }
118 
119 void LaserScanKinect::setScanHeight(const int scan_height) {
120  if (scan_height > 0) {
121  scan_height_ = scan_height;
122  }
123  else {
124  scan_height_ = 10;
125  ROS_ERROR("Incorrect value of scan height parameter. Set default value: 100.");
126  }
127 }
128 
129 void LaserScanKinect::setDepthImgRowStep(const int row_step) {
130  if (row_step > 0) {
131  depth_img_row_step_ = row_step;
132  }
133  else {
135  ROS_ERROR("Incorrect value depth imgage row step parameter. Set default value: 1.");
136  }
137 }
138 
139 void LaserScanKinect::setSensorMountHeight (const float height) {
140  if ( height > 0) {
141  sensor_mount_height_ = height;
142  }
143  else {
145  ROS_ERROR("Incorrect value of sensor mount height parameter. Set default value: 0.");
146  }
147 }
148 
149 void LaserScanKinect::setSensorTiltAngle (const float angle) {
150  if (angle < 90 && angle > -90) {
151  sensor_tilt_angle_ = angle;
152  }
153  else {
154  sensor_tilt_angle_ = 0;
155  ROS_ERROR("Incorrect value of sensor tilt angle parameter. Set default value: 0.");
156  }
157 }
158 
159 void LaserScanKinect::setGroundMargin (const float margin) {
160  if (margin > 0) {
161  ground_margin_ = margin;
162  }
163  else {
164  ground_margin_ = 0;
165  ROS_ERROR("Incorrect value of ground margin parameter. Set default value: 0.");
166  }
167 }
168 
169 void LaserScanKinect::setThreadsNum(unsigned threads_num) {
170  if (threads_num >= 1) {
171  threads_num_ = threads_num;
172  }
173  else {
174  threads_num_ = 1;
175  ROS_ERROR("Incorrect number of threads. Set default value: 1.");
176  }
177 }
178 
179 sensor_msgs::ImageConstPtr LaserScanKinect::getDbgImage() const {
180  return dbg_image_;
181 }
182 
184  const double alpha = sensor_tilt_angle_ * M_PI / 180.0; // Sensor tilt angle in radians
185  const int img_height = cam_model_.fullResolution().height;
186 
187  ROS_ASSERT(img_height >= 0);
188 
189  dist_to_ground_corrected.resize(img_height);
190 
191  // Coefficients calculations for each row of image
192  for(int i = 0; i < img_height; ++i) {
193  // Angle between ray and optical center
194  double delta = vertical_fov * (i - cam_model_.cy() - 0.5) / (static_cast<double>(img_height) - 1);
195 
196  if ((delta - alpha) > 0) {
197  dist_to_ground_corrected[i] = sensor_mount_height_ * sin(M_PI / 2 - delta) / cos(M_PI / 2 - delta + alpha);
198 
200  }
201  else {
202  dist_to_ground_corrected[i] = 100;
203  }
204 
206  }
207 
208  std::ostringstream s;
209  s << " calcGroundDistancesForImgRows:";
210  for (int i = 0; i < img_height; i+=10) {
211  s << i << " : " << dist_to_ground_corrected[i] << " ";
212  }
213  ROS_INFO_STREAM_THROTTLE(1, s.str());
214 }
215 
217  const double alpha = sensor_tilt_angle_ * M_PI / 180.0;
218  const int img_height = cam_model_.fullResolution().height;
219 
220  ROS_ASSERT(img_height >= 0);
221 
222  tilt_compensation_factor_.resize(img_height);
223 
224  for(int i = 0; i < img_height; ++i) { // Processing all rows
225  double delta = vertical_fov * (i - cam_model_.cy() - 0.5) / ((double)img_height - 1);
226 
227  tilt_compensation_factor_[i] = sin(M_PI/2 - delta - alpha) / sin(M_PI/2 - delta);
229  }
230 }
231 
232 void LaserScanKinect::calcScanMsgIndexForImgCols(const sensor_msgs::ImageConstPtr& depth_msg)
233 {
234  scan_msg_index_.resize((int)depth_msg->width);
235 
236  for (size_t u = 0; u < static_cast<size_t>(depth_msg->width); u++) {
237  double th = -atan2((double)(u - cam_model_.cx()) * 0.001f / cam_model_.fx(), 0.001f);
238  scan_msg_index_[u] = (th - scan_msg_->angle_min) / scan_msg_->angle_increment;
239  }
240 }
241 
242 template <typename T>
243 float LaserScanKinect::getSmallestValueInColumn(const sensor_msgs::ImageConstPtr &depth_msg, int col) {
244  float depth_min = std::numeric_limits<float>::max();
245  int depth_min_row = -1;
246 
247  const int row_size = depth_msg->width;
248  const T* data = reinterpret_cast<const T*>(&depth_msg->data[0]);
249 
250  // Loop over pixels in column. Calculate z_min in column
252 
253  float depth_raw = 0.0;
254  float depth_m = 0.0;
255 
256  if (typeid(T) == typeid(uint16_t)) {
257  unsigned depth_raw_mm = static_cast<unsigned>(data[row_size * i + col]);
258  depth_raw = static_cast<float>(depth_raw_mm) / 1000.0;
259  }
260  else if (typeid(T) == typeid(float)) {
261  depth_raw = static_cast<float>(data[row_size * i + col]);
262  }
263 
264  if (tilt_compensation_enable_) { // Check if tilt compensation is enabled
265  depth_m = depth_raw * tilt_compensation_factor_[i];
266  }
267  else {
268  depth_m = depth_raw;
269  }
270 
271  // Check if point is in ranges and find min value in column
272  if (depth_raw >= range_min_ && depth_raw <= range_max_) {
273 
274  if (ground_remove_enable_) {
275  if (depth_m < depth_min && depth_raw < dist_to_ground_corrected[i]) {
276  depth_min = depth_m;
277  depth_min_row = i;
278  }
279  }
280  else {
281  if (depth_m < depth_min) {
282  depth_min = depth_m;
283  depth_min_row = i;
284  }
285  }
286  }
287  }
288 
289  {
290  std::lock_guard<std::mutex> guard(points_indices_mutex_);
291  min_dist_points_indices_.push_back({depth_min_row, col});
292  }
293  return depth_min;
294 }
295 
296 template float LaserScanKinect::getSmallestValueInColumn<uint16_t>(const sensor_msgs::ImageConstPtr&, int);
297 template float LaserScanKinect::getSmallestValueInColumn<float>(const sensor_msgs::ImageConstPtr&, int);
298 
299 #include <chrono>
300 using namespace std::chrono;
301 
302 template <typename T>
303 void LaserScanKinect::convertDepthToPolarCoords(const sensor_msgs::ImageConstPtr &depth_msg) {
304 
305  // Converts depth from specific column to polar coordinates
306  auto convert_to_polar = [&](size_t col, float depth) -> float {
307  if (depth != std::numeric_limits<T>::max()) {
308  // Calculate x in XZ ( z = depth )
309  float x = (col - cam_model_.cx()) * depth / cam_model_.fx();
310 
311  // Calculate distance in polar coordinates
312  return sqrt(x * x + depth * depth);
313  }
314  return NAN; // No information about distances in column
315  };
316 
317  // Processing for specified columns from [left, right]
318  auto process_columns = [&](size_t left, size_t right) {
319  for (size_t i = left; i <= right; ++i) {
320  const auto depth_min = getSmallestValueInColumn<T>(depth_msg, i);
321  const auto range_in_polar = convert_to_polar(i, depth_min);
322  {
323  std::lock_guard<std::mutex> guard(scan_msg_mutex_);
324  scan_msg_->ranges[scan_msg_index_[i]] = range_in_polar;
325  }
326  }
327  };
328 
329  std::stringstream log;
330  auto start = high_resolution_clock::now();
331 
332  if (threads_num_ <= 1) {
333  process_columns(0, static_cast<size_t>(depth_msg->width - 1));
334  }
335  else {
336  std::vector<std::thread> workers;
337  size_t left = 0;
338  size_t step = static_cast<size_t>(depth_msg->width) / threads_num_;
339  log << "step: " << step;
340 
341  for (size_t i = 0; i < threads_num_; ++i) {
342  workers.push_back(std::thread(process_columns, left, left + step - 1));
343  log << " w" << i << ": (" << left << ", " << left + step -1 << ") ";
344  left += step;
345  }
346 
347  for (auto &t : workers) {
348  t.join();
349  }
350  }
351 
352  auto end = (high_resolution_clock::now() - start);
353  log << " time[ms]: " << std::chrono::duration<double, std::milli>(end).count() << "\n";
354  ROS_DEBUG_STREAM(log.str());
355 }
356 
357 sensor_msgs::ImagePtr LaserScanKinect::prepareDbgImage(const sensor_msgs::ImageConstPtr& depth_msg,
358  const std::list<std::pair<int, int>>& min_dist_points_indices) {
359 
360  sensor_msgs::ImagePtr img(new sensor_msgs::Image);
361  img->header = depth_msg->header;
362  img->height = depth_msg->height;
363  img->width = depth_msg->width;
364  img->encoding = "rgb8"; // image_encodings::RGB8
365  img->is_bigendian = depth_msg->is_bigendian;
366  img->step = img->width * 3; // 3 bytes per pixel
367 
368  img->data.resize(img->step * img->height);
369  uint8_t(*rgb_data)[3] = reinterpret_cast<uint8_t(*)[3]>(&img->data[0]);
370 
371  // Convert depth image to RGB
372  if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) {
373  const uint16_t* depth_data = reinterpret_cast<const uint16_t*>(&depth_msg->data[0]);
374  for (unsigned i = 0; i < (img->width * img->height); ++i) {
375  // Scale value to cover full range of RGB 8
376  uint8_t val = 255 * (depth_data[i] - range_min_ * 1000) / (range_max_ * 1000 - range_min_ * 1000);
377  rgb_data[i][0] = 255 - val;
378  rgb_data[i][1] = 255 - val;
379  rgb_data[i][2] = 255 - val;
380  }
381  }
382  else if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) {
383  const float* depth_data = reinterpret_cast<const float*>(&depth_msg->data[0]);
384  for (unsigned i = 0; i < (img->width * img->height); ++i) {
385  // Scale value to cover full range of RGB 8
386  uint8_t val = 255 * (depth_data[i] - range_min_) / (range_max_ - range_min_);
387  rgb_data[i][0] = 255 - val;
388  rgb_data[i][1] = 255 - val;
389  rgb_data[i][2] = 255 - val;
390  }
391  }
392  else {
393  throw std::runtime_error("Unsupported depth image encoding");
394  }
395 
396  // Add ground points to debug image (as red points)
397  for (const auto pt : min_dist_points_indices) {
398  const auto row = pt.first;
399  const auto col = pt.second;
400  if (row >= 0 && col >= 0) {
401  rgb_data[row * img->width + col][0] = 255;
402  rgb_data[row * img->width + col][1] = 0;
403  rgb_data[row * img->width + col][2] = 0;
404  }
405  }
406 
407  // Add line which is the border of the detection area
408  std::list<std::pair<unsigned, unsigned>> pts;
409  for (unsigned i = 0; i < img->width; ++i) {
410  const auto line1_row = cam_model_.cy() - scan_height_ / 2.0;
411  const auto line2_row = cam_model_.cy() + scan_height_ / 2.0;
412  if (line1_row >= 0 && line1_row < img->height && line2_row >= 0 && line2_row < img->height) {
413  pts.push_back({line1_row, i});
414  pts.push_back({line2_row, i});
415  }
416  }
417 
418  for (const auto pt : pts) {
419  const auto row = pt.first;
420  const auto col = pt.second;
421  rgb_data[row * img->width + col][0] = 0;
422  rgb_data[row * img->width + col][1] = 255;
423  rgb_data[row * img->width + col][2] = 0;
424  }
425 
426  return img;
427 }
428 
429 } // namespace laserscan_kinect
void setRangeLimits(const float rmin, const float rmax)
setRangeLimits sets depth sensor min and max ranges
void calcScanMsgIndexForImgCols(const sensor_msgs::ImageConstPtr &depth_msg)
calcScanMsgIndexForImgCols
void setDepthImgRowStep(const int row_step)
setDepthImgRowStep
float getSmallestValueInColumn(const sensor_msgs::ImageConstPtr &depth_msg, int col)
getSmallestValueInColumn finds smallest values in depth image columns
unsigned scan_height_
Number of pixel rows used to scan computing.
f
bool publish_dbg_image_
Determines if debug image should be published.
sensor_msgs::ImageConstPtr getDbgImage() const
bool tilt_compensation_enable_
Determines if tilt compensation feature is on.
XmlRpcServer s
void setSensorTiltAngle(const float angle)
setSensorTiltAngle sets the sensor tilt angle (in degrees)
float sensor_tilt_angle_
Angle of sensor tilt.
float ground_margin_
Margin for floor remove feature (in meters)
bool is_scan_msg_configured_
Determines if laser scan message is configurated.
unsigned depth_img_row_step_
Row step in depth map processing.
std::list< std::pair< int, int > > min_dist_points_indices_
void setThreadsNum(unsigned threads_num)
void setSensorMountHeight(const float height)
setSensorMountHeight sets the height of sensor mount (in meters)
unsigned threads_num_
Determines threads number used in image processing.
image_geometry::PinholeCameraModel cam_model_
Class for managing CameraInfo messages.
float range_min_
Stores the current minimum range to use.
#define M_PI
bool fromCameraInfo(const sensor_msgs::CameraInfo &msg)
const std::string TYPE_32FC1
void convertDepthToPolarCoords(const sensor_msgs::ImageConstPtr &depth_msg)
convertDepthToPolarCoords converts depth map to 2D
const std::string TYPE_16UC1
void calcFieldOfView(const image_geometry::PinholeCameraModel &camera_model, const cv::Point2d &&left, const cv::Point2d &&center, const cv::Point2d &&right, double &min, double &max)
fieldOfView calculates field of view (angle)
Definition: math.h:23
bool cam_model_update_
If continously calibration update required.
#define ROS_DEBUG_STREAM(args)
std::vector< unsigned > scan_msg_index_
Calculated laser scan msg indexes for each depth image column.
sensor_msgs::ImagePtr prepareDbgImage(const sensor_msgs::ImageConstPtr &depth_msg, const std::list< std::pair< int, int >> &min_dist_points_indices)
sensor_msgs::LaserScanPtr getLaserScanMsg(const sensor_msgs::ImageConstPtr &depth_msg, const sensor_msgs::CameraInfoConstPtr &info_msg)
prepareLaserScanMsg converts depthimage and prepare new LaserScan message
std::vector< float > tilt_compensation_factor_
Calculated sensor tilt compensation factors.
int image_vertical_offset_
The vertical offset of image based on calibration data.
void calcGroundDistancesForImgRows(double vertical_fov)
calcGroundDistancesForImgRows calculate coefficients used in ground removing from scan ...
sensor_msgs::LaserScanPtr scan_msg_
Published scan message.
void setScanHeight(const int scan_height)
setScanHeight sets height of depth image which will be used in conversion process ...
void setGroundMargin(const float margin)
setGroundMargin sets the floor margin (in meters)
std::string output_frame_id_
Output frame_id for laserscan message.
void calcTiltCompensationFactorsForImgRows(double vertical_fov)
calcTiltCompensationFactorsForImgRows calculate factors used in tilt compensation ...
float sensor_mount_height_
Height of sensor mount from ground.
#define ROS_ASSERT(cond)
#define ROS_INFO_STREAM_THROTTLE(rate, args)
std::vector< float > dist_to_ground_corrected
Calculated maximal distances for measurements not included as floor.
#define ROS_ERROR(...)
bool ground_remove_enable_
Determines if remove ground from output scan.
float range_max_
Stores the current maximum range to use.


laserscan_kinect
Author(s): Michal Drwiega (http://www.mdrwiega.com)
autogenerated on Wed May 5 2021 02:56:14