stereo_pattern.cpp
Go to the documentation of this file.
1 /*
2  velo2cam_calibration - Automatic calibration algorithm for extrinsic
3  parameters of a stereo camera and a velodyne Copyright (C) 2017-2021 Jorge
4  Beltran, Carlos Guindel
5 
6  This file is part of velo2cam_calibration.
7 
8  velo2cam_calibration is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 2 of the License, or
11  (at your option) any later version.
12 
13  velo2cam_calibration is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with velo2cam_calibration. If not, see <http://www.gnu.org/licenses/>.
20 */
21 
22 /*
23  stereo_pattern: Find the circle centers in the stereo cloud
24 */
25 
26 #define TARGET_NUM_CIRCLES 4
27 
28 #include <dynamic_reconfigure/server.h>
31 #include <pcl/common/transforms.h>
32 #include <pcl/filters/extract_indices.h>
33 #include <pcl/sample_consensus/sac_model_plane.h>
34 #include <pcl/segmentation/sac_segmentation.h>
36 #include <pcl_msgs/ModelCoefficients.h>
37 #include <pcl_msgs/PointIndices.h>
38 #include <ros/ros.h>
39 #include <sensor_msgs/PointCloud2.h>
40 #include <std_msgs/Empty.h>
41 #include <velo2cam_calibration/StereoConfig.h>
42 #include <velo2cam_calibration/ClusterCentroids.h>
43 #include <velo2cam_utils.h>
44 
45 using namespace std;
46 using namespace sensor_msgs;
47 
49 
57 bool WARMUP_DONE = false;
60 std::ofstream savefile;
61 
68 
69 pcl::PointCloud<pcl::PointXYZ>::Ptr cumulative_cloud;
70 
71 std_msgs::Header header_;
72 
73 void callback(const PointCloud2::ConstPtr &camera_cloud,
74  const pcl_msgs::ModelCoefficients::ConstPtr &cam_plane_coeffs) {
75  if (DEBUG) ROS_INFO("[Stereo] Processing image...");
76 
77  images_proc_++;
78 
79  header_ = camera_cloud->header;
80 
81  pcl::PointCloud<pcl::PointXYZ>::Ptr cam_cloud(
82  new pcl::PointCloud<pcl::PointXYZ>());
83 
84  pcl::fromROSMsg(*camera_cloud, *cam_cloud);
85 
86  // 1.FILTER THE EDGES-CLOUD ACCORDING TO THE DETECTED PLANE
87  // Segment inliers
88  Eigen::VectorXf coefficients_v(4);
89  coefficients_v(0) = cam_plane_coeffs->values[0];
90  coefficients_v(1) = cam_plane_coeffs->values[1];
91  coefficients_v(2) = cam_plane_coeffs->values[2];
92  coefficients_v(3) = cam_plane_coeffs->values[3];
93 
94  pcl::PointCloud<pcl::PointXYZ>::Ptr cam_plane_cloud(
95  new pcl::PointCloud<pcl::PointXYZ>());
96  pcl::SampleConsensusModelPlane<pcl::PointXYZ>::Ptr dit(
97  new pcl::SampleConsensusModelPlane<pcl::PointXYZ>(cam_cloud));
98  std::vector<int> plane_inliers;
99  dit->selectWithinDistance(coefficients_v, plane_distance_inliers_,
100  plane_inliers);
101  pcl::copyPointCloud<pcl::PointXYZ>(*cam_cloud, plane_inliers,
102  *cam_plane_cloud);
103 
104  // Publish plane as "plane_edges_cloud"
105  if (DEBUG) {
106  PointCloud2 plane_edges_ros;
107  pcl::toROSMsg(*cam_plane_cloud, plane_edges_ros);
108  plane_edges_ros.header = camera_cloud->header;
109  plane_edges_pub.publish(plane_edges_ros);
110  }
111 
112  pcl::PointCloud<pcl::PointXYZ>::Ptr temp_cloud(
113  new pcl::PointCloud<pcl::PointXYZ>);
114  pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
115  pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
116  pcl::ExtractIndices<pcl::PointXYZ> extract;
117 
118  // 2.ROTATE CLOUD TO FACE PATTERN PLANE
119  pcl::PointCloud<pcl::PointXYZ>::Ptr xy_cloud(
120  new pcl::PointCloud<pcl::PointXYZ>);
121  Eigen::Vector3f xy_plane_normal_vector, floor_plane_normal_vector;
122  xy_plane_normal_vector[0] = 0.0;
123  xy_plane_normal_vector[1] = 0.0;
124  xy_plane_normal_vector[2] = -1.0;
125 
126  floor_plane_normal_vector[0] = cam_plane_coeffs->values[0];
127  floor_plane_normal_vector[1] = cam_plane_coeffs->values[1];
128  floor_plane_normal_vector[2] = cam_plane_coeffs->values[2];
129 
130  std::vector<int> indices;
131  pcl::removeNaNFromPointCloud(*cam_plane_cloud, *cam_plane_cloud, indices);
132 
133  Eigen::Affine3f rotation =
134  getRotationMatrix(floor_plane_normal_vector, xy_plane_normal_vector);
135  pcl::transformPointCloud(*cam_plane_cloud, *xy_cloud, rotation);
136 
137  pcl::PointCloud<pcl::PointXYZ>::Ptr aux_cloud(
138  new pcl::PointCloud<pcl::PointXYZ>);
139  pcl::PointXYZ aux_point;
140  aux_point.x = 0;
141  aux_point.y = 0;
142  aux_point.z = (-coefficients_v(3) / coefficients_v(2));
143  aux_cloud->push_back(aux_point);
144 
145  pcl::PointCloud<pcl::PointXYZ>::Ptr auxrotated_cloud(
146  new pcl::PointCloud<pcl::PointXYZ>);
147  pcl::transformPointCloud(*aux_cloud, *auxrotated_cloud, rotation);
148 
149  double zcoord_xyplane = auxrotated_cloud->at(0).z;
150 
151  // Force pattern points to belong to computed plane
152  for (pcl::PointCloud<pcl::PointXYZ>::iterator pt = xy_cloud->points.begin();
153  pt < xy_cloud->points.end(); ++pt) {
154  pt->z = zcoord_xyplane;
155  }
156 
157  // Publishing "xy_pattern" (pattern transformed to be aligned with XY)
158  if (DEBUG) {
159  PointCloud2 xy_pattern_ros;
160  pcl::toROSMsg(*xy_cloud, xy_pattern_ros);
161  xy_pattern_ros.header = camera_cloud->header;
162  xy_pattern_pub.publish(xy_pattern_ros);
163  }
164 
165  // 3.FIND CIRCLES
166  pcl::SACSegmentation<pcl::PointXYZ> seg;
167 
168  seg.setModelType(pcl::SACMODEL_CIRCLE2D);
169  seg.setDistanceThreshold(circle_threshold_);
170  seg.setMethodType(pcl::SAC_RANSAC);
171  seg.setOptimizeCoefficients(true);
172  seg.setMaxIterations(1000);
173  seg.setRadiusLimits(TARGET_RADIUS - target_radius_tolerance_,
175 
176  pcl::PointCloud<pcl::PointXYZ>::Ptr circle_cloud(
177  new pcl::PointCloud<pcl::PointXYZ>);
178 
179  std::vector<std::vector<float>> found_centers;
180 
181  do {
182  seg.setInputCloud(xy_cloud);
183  seg.segment(*inliers, *coefficients);
184  if (inliers->indices.size() == 0) {
185  if (found_centers.size() < 1) {
186  ROS_WARN(
187  "[Stereo] Could not estimate a circle model for the given "
188  "dataset.");
189  }
190  break;
191  } else {
192  if (DEBUG)
193  ROS_INFO("[Stereo] Found circle: %lu inliers", inliers->indices.size());
194  }
195 
196  // Extract the inliers
197  extract.setInputCloud(xy_cloud);
198  extract.setIndices(inliers);
199  extract.setNegative(false);
200  extract.filter(*circle_cloud);
201 
202  // Add center point to cloud (only if it makes sense)
203  pcl::PointXYZ center;
204  center.x = *coefficients->values.begin();
205  center.y = *(coefficients->values.begin() + 1);
206  center.z = zcoord_xyplane;
207 
208  std::vector<float> found_center;
209  found_center.push_back(center.x);
210  found_center.push_back(center.y);
211  found_center.push_back(center.z);
212  found_centers.push_back(found_center);
213 
214  // Remove inliers from pattern cloud to find next circle
215  extract.setNegative(true);
216  extract.filter(*temp_cloud);
217  xy_cloud.swap(temp_cloud);
218 
219  } while (xy_cloud->points.size() > 3);
220 
221  if (found_centers.size() <
222  min_centers_found_) { // Usually min_centers_found_ = TARGET_NUM_CIRCLES
223  // Exit 1: centers not found
224  ROS_WARN("Not enough centers: %ld", found_centers.size());
225  return;
226  }
227 
238  std::vector<std::vector<int>> groups;
239  comb(found_centers.size(), TARGET_NUM_CIRCLES, groups);
240  double groups_scores[groups.size()]; // -1: invalid; 0-1 normalized score
241  for (int i = 0; i < groups.size(); ++i) {
242  std::vector<pcl::PointXYZ> candidates;
243  // Build candidates set
244  for (int j = 0; j < groups[i].size(); ++j) {
245  pcl::PointXYZ center;
246  center.x = found_centers[groups[i][j]][0];
247  center.y = found_centers[groups[i][j]][1];
248  center.z = found_centers[groups[i][j]][2];
249  candidates.push_back(center);
250  }
251 
252  // Compute candidates score
253  Square square_candidate(candidates, delta_width_circles_,
255  groups_scores[i] = square_candidate.is_valid()
256  ? 1.0
257  : -1; // -1 when it's not valid, 1 otherwise
258  }
259 
260  int best_candidate_idx = -1;
261  double best_candidate_score = -1;
262  for (int i = 0; i < groups.size(); ++i) {
263  if (best_candidate_score == 1 && groups_scores[i] == 1) {
264  // Exit 2: Several candidates fit target's geometry
265  ROS_ERROR(
266  "[Stereo] More than one set of candidates fit target's geometry. "
267  "Please, make sure your parameters are well set. Exiting callback");
268  return;
269  }
270  if (groups_scores[i] > best_candidate_score) {
271  best_candidate_score = groups_scores[i];
272  best_candidate_idx = i;
273  }
274  }
275 
276  if (best_candidate_idx == -1) {
277  // Exit 3: No candidates fit target's geometry
278  ROS_WARN(
279  "[Stereo] Unable to find a candidate set that matches target's "
280  "geometry");
281  return;
282  }
283 
284  pcl::PointCloud<pcl::PointXYZ>::Ptr rotated_back_cloud(
285  new pcl::PointCloud<pcl::PointXYZ>());
286  for (int j = 0; j < groups[best_candidate_idx].size(); ++j) {
287  pcl::PointXYZ point;
288  point.x = found_centers[groups[best_candidate_idx][j]][0];
289  point.y = found_centers[groups[best_candidate_idx][j]][1];
290  point.z = found_centers[groups[best_candidate_idx][j]][2];
291 
292  pcl::PointXYZ center_rotated_back =
293  pcl::transformPoint(point, rotation.inverse());
294  center_rotated_back.z =
295  (-cam_plane_coeffs->values[0] * center_rotated_back.x -
296  cam_plane_coeffs->values[1] * center_rotated_back.y -
297  cam_plane_coeffs->values[3]) /
298  cam_plane_coeffs->values[2];
299 
300  rotated_back_cloud->push_back(center_rotated_back);
301  cumulative_cloud->push_back(center_rotated_back);
302  }
303 
304  if (save_to_file_) {
305  std::vector<pcl::PointXYZ> sorted_centers;
306  sortPatternCenters(rotated_back_cloud, sorted_centers);
307  for (std::vector<pcl::PointXYZ>::iterator it = sorted_centers.begin();
308  it < sorted_centers.end(); ++it) {
309  savefile << it->x << ", " << it->y << ", " << it->z << ", ";
310  }
311  }
312 
313  // Publishing "cumulative_cloud" (centers found from the beginning)
314  if (DEBUG) {
315  PointCloud2 cumulative_ros;
316  pcl::toROSMsg(*cumulative_cloud, cumulative_ros);
317  cumulative_ros.header = camera_cloud->header;
318  cumulative_pub.publish(cumulative_ros);
319  }
320 
321  pcl_msgs::PointIndices p_ind;
322 
323  pcl_conversions::moveFromPCL(*inliers, p_ind);
324  p_ind.header = camera_cloud->header;
325 
326  pcl_msgs::ModelCoefficients m_coeff;
327 
328  pcl_conversions::moveFromPCL(*coefficients, m_coeff);
329  m_coeff.header = camera_cloud->header;
330 
331  if (DEBUG) {
332  inliers_pub.publish(p_ind);
333  coeff_pub.publish(m_coeff);
334  }
335 
336  images_used_++;
337  if (DEBUG) {
338  ROS_INFO("[Stereo] %d/%d frames: %ld pts in cloud", images_used_,
339  images_proc_, cumulative_cloud->points.size());
340  }
341  pcl::PointCloud<pcl::PointXYZ>::Ptr final_cloud(
342  new pcl::PointCloud<pcl::PointXYZ>);
343 
344  // Compute circles centers
345  if (!WARMUP_DONE) { // Compute clusters from detections in the latest frame
347  } else { // Use cumulative information from previous frames
349  min_cluster_factor_ * images_used_, images_used_);
350  if (final_cloud->points.size() > TARGET_NUM_CIRCLES) {
352  3.0 * images_used_ / 4.0, images_used_);
353  }
354  }
355 
356  // Exit 4: clustering failed
357  if (final_cloud->points.size() == TARGET_NUM_CIRCLES) {
358  if (save_to_file_) {
359  std::vector<pcl::PointXYZ> sorted_centers;
360  sortPatternCenters(final_cloud, sorted_centers);
361  for (std::vector<pcl::PointXYZ>::iterator it = sorted_centers.begin();
362  it < sorted_centers.end(); ++it) {
363  savefile << it->x << ", " << it->y << ", " << it->z << ", ";
364  }
365  savefile << cumulative_cloud->width;
366  }
367 
368  sensor_msgs::PointCloud2 final_ros;
369  pcl::toROSMsg(*final_cloud, final_ros);
370  final_ros.header = camera_cloud->header;
371 
372  velo2cam_calibration::ClusterCentroids to_send;
373  to_send.header = camera_cloud->header;
374  to_send.total_iterations = images_proc_;
375  to_send.cluster_iterations = images_used_;
376  to_send.cloud = final_ros;
377 
378  final_pub.publish(to_send);
379  }
380 
381  if (save_to_file_) {
382  savefile << endl;
383  }
384 
385  // Clear cumulative cloud during warm-up phase
386  if (!WARMUP_DONE) {
387  cumulative_cloud->clear();
388  images_proc_ = 0;
389  images_used_ = 0;
390  }
391 }
392 
393 void param_callback(velo2cam_calibration::StereoConfig &config,
394  uint32_t level) {
395  circle_threshold_ = config.circle_threshold;
396  ROS_INFO("[Stereo] New circle threshold: %f", circle_threshold_);
397 }
398 
399 void warmup_callback(const std_msgs::Empty::ConstPtr &msg) {
401  if (WARMUP_DONE) {
402  ROS_INFO("[Stereo] Warm up done, pattern detection started");
403  } else {
404  ROS_INFO("[Stereo] Detection stopped. Warm up mode activated");
405  }
406 }
407 
408 int main(int argc, char **argv) {
409  ros::init(argc, argv, "stereo_pattern");
410  ros::NodeHandle nh; // GLOBAL
411  ros::NodeHandle nh_("~"); // LOCAL
412 
415  cam_plane_coeffs_sub_;
416 
417  camera_cloud_sub_.subscribe(nh_, "cloud2", 1);
418  cam_plane_coeffs_sub_.subscribe(nh_, "cam_plane_coeffs", 1);
419 
420  if (DEBUG) {
421  inliers_pub = nh_.advertise<pcl_msgs::PointIndices>("inliers", 1);
422  coeff_pub = nh_.advertise<pcl_msgs::ModelCoefficients>("model", 1);
423  plane_edges_pub = nh_.advertise<PointCloud2>("plane_edges_cloud", 1);
424  xy_pattern_pub = nh_.advertise<PointCloud2>("xy_pattern", 1);
425  cumulative_pub = nh_.advertise<PointCloud2>("cumulative_cloud", 1);
426  }
427 
428  final_pub =
429  nh_.advertise<velo2cam_calibration::ClusterCentroids>("centers_cloud", 1);
430 
432  pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>);
433 
434  typedef message_filters::sync_policies::ExactTime<PointCloud2,
435  pcl_msgs::ModelCoefficients>
436  ExSync;
437  message_filters::Synchronizer<ExSync> sync_(ExSync(10), camera_cloud_sub_,
438  cam_plane_coeffs_sub_);
439  sync_.registerCallback(boost::bind(&callback, _1, _2));
440 
441  string csv_name;
442 
443  nh.param("delta_width_circles", delta_width_circles_, 0.5);
444  nh.param("delta_height_circles", delta_height_circles_, 0.4);
445  nh_.param("plane_distance_inliers", plane_distance_inliers_, 0.1);
446  nh_.param("target_radius_tolerance", target_radius_tolerance_, 0.01);
447  nh_.param("min_centers_found", min_centers_found_, TARGET_NUM_CIRCLES);
448  nh_.param("cluster_tolerance", cluster_tolerance_, 0.05);
449  nh_.param("min_cluster_factor", min_cluster_factor_, 0.5);
450  nh_.param("skip_warmup", skip_warmup_, false);
451  nh_.param("save_to_file", save_to_file_, false);
452  nh_.param("csv_name", csv_name,
453  "stereo_pattern_" + currentDateTime() + ".csv");
454 
455  dynamic_reconfigure::Server<velo2cam_calibration::StereoConfig> server;
456  dynamic_reconfigure::Server<velo2cam_calibration::StereoConfig>::CallbackType
457  f;
458  f = boost::bind(param_callback, _1, _2);
459  server.setCallback(f);
460 
461  ros::Subscriber warmup_sub =
462  nh.subscribe("warmup_switch", 1, warmup_callback);
463 
464  if (skip_warmup_) {
465  ROS_WARN("Skipping warmup");
466  WARMUP_DONE = true;
467  }
468 
469  // Just for statistics
470  if (save_to_file_) {
471  ostringstream os;
472  os << getenv("HOME") << "/v2c_experiments/" << csv_name;
473  if (save_to_file_) {
474  if (DEBUG) ROS_INFO("Opening %s", os.str().c_str());
475  savefile.open(os.str().c_str());
476  savefile << "det1_x, det1_y, det1_z, det2_x, det2_y, det2_z, det3_x, "
477  "det3_y, det3_z, det4_x, det4_y, det4_z, cent1_x, cent1_y, "
478  "cent1_z, cent2_x, cent2_y, cent2_z, cent3_x, cent3_y, "
479  "cent3_z, cent4_x, cent4_y, cent4_z, it"
480  << endl;
481  }
482  }
483 
484  ros::spin();
485  return 0;
486 }
bool WARMUP_DONE
ros::Publisher coeff_pub
ros::Publisher inliers_pub
void publish(const boost::shared_ptr< M > &message) const
double target_radius_tolerance_
f
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
bool is_valid()
ros::Publisher plane_edges_pub
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
std_msgs::Header header_
void fromROSMsg(const sensor_msgs::PointCloud2 &cloud, pcl::PointCloud< T > &pcl_cloud)
ros::Publisher final_pub
int min_centers_found_
ros::NodeHandle * nh_
void callback(const PointCloud2::ConstPtr &camera_cloud, const pcl_msgs::ModelCoefficients::ConstPtr &cam_plane_coeffs)
std::ofstream savefile
#define ROS_WARN(...)
void param_callback(velo2cam_calibration::StereoConfig &config, uint32_t level)
int images_used_
ROSCPP_DECL void spin(Spinner &spinner)
double plane_distance_inliers_
#define DEBUG
Connection registerCallback(C &callback)
#define ROS_INFO(...)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
ros::Publisher cumulative_pub
double cluster_tolerance_
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
Eigen::Affine3f getRotationMatrix(Eigen::Vector3f source, Eigen::Vector3f target)
void getCenterClusters(pcl::PointCloud< pcl::PointXYZ >::Ptr cloud_in, pcl::PointCloud< pcl::PointXYZ >::Ptr centers_cloud, double cluster_tolerance=0.10, int min_cluster_size=15, int max_cluster_size=200, bool verbosity=true)
ros::Publisher xy_pattern_pub
int images_proc_
void sortPatternCenters(pcl::PointCloud< pcl::PointXYZ >::Ptr pc, vector< pcl::PointXYZ > &v)
double min_cluster_factor_
bool save_to_file_
void subscribe(ros::NodeHandle &nh, const std::string &topic, uint32_t queue_size, const ros::TransportHints &transport_hints=ros::TransportHints(), ros::CallbackQueueInterface *callback_queue=0)
int main(int argc, char **argv)
bool skip_warmup_
const std::string currentDateTime()
void warmup_callback(const std_msgs::Empty::ConstPtr &msg)
void toROSMsg(const sensor_msgs::PointCloud2 &cloud, sensor_msgs::Image &image)
pcl::PointCloud< pcl::PointXYZ >::Ptr cumulative_cloud
#define TARGET_RADIUS
#define TARGET_NUM_CIRCLES
double delta_height_circles_
void moveFromPCL(pcl::PCLImage &pcl_image, sensor_msgs::Image &image)
double delta_width_circles_
#define ROS_ERROR(...)
double circle_threshold_
void comb(int N, int K, std::vector< std::vector< int >> &groups)


velo2cam_calibration
Author(s): Jorge Beltran , Carlos Guindel
autogenerated on Fri Feb 26 2021 03:40:57