stereo_pattern.cpp
Go to the documentation of this file.
1 /*
2  velo2cam_calibration - Automatic calibration algorithm for extrinsic parameters of a stereo camera and a velodyne
3  Copyright (C) 2017-2018 Jorge Beltran, Carlos Guindel
4 
5  This file is part of velo2cam_calibration.
6 
7  velo2cam_calibration is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 2 of the License, or
10  (at your option) any later version.
11 
12  velo2cam_calibration is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with velo2cam_calibration. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 /*
22  stereo_pattern: Find the circle centers in the stereo cloud
23 */
24 
25 #include <ros/ros.h>
26 #include <sensor_msgs/PointCloud2.h>
27 #include <pcl_msgs/PointIndices.h>
28 #include <pcl_msgs/ModelCoefficients.h>
33 #include <pcl/ModelCoefficients.h>
34 #include <pcl/sample_consensus/method_types.h>
35 #include <pcl/sample_consensus/model_types.h>
36 #include <pcl/sample_consensus/sac_model_plane.h>
37 #include <pcl/sample_consensus/sac_model_line.h>
38 #include <pcl/segmentation/sac_segmentation.h>
39 #include <pcl/segmentation/extract_clusters.h>
40 #include <pcl/filters/extract_indices.h>
41 #include <dynamic_reconfigure/server.h>
42 
43 #include <velo2cam_calibration/CameraConfig.h>
44 #include "velo2cam_utils.h"
45 #include <velo2cam_calibration/ClusterCentroids.h>
46 
47 using namespace std;
48 using namespace sensor_msgs;
49 
50 int nFrames;
52 
64 
80 
81 pcl::PointCloud<pcl::PointXYZ>::Ptr cumulative_cloud;
82 
83 std_msgs::Header header_;
84 
85 void publish_cloud(pcl::PointCloud<pcl::PointXYZ>::Ptr input_cloud, ros::Publisher pub){
86  sensor_msgs::PointCloud2 ros_cloud;
87  pcl::toROSMsg(*input_cloud, ros_cloud);
88  ros_cloud.header = header_;
89  pub.publish(ros_cloud);
90 }
91 
92 void callback(const PointCloud2::ConstPtr& camera_cloud,
93  const pcl_msgs::ModelCoefficients::ConstPtr& cam_plane_coeffs){
94 
95  if(DEBUG) ROS_INFO("[Camera] Processing image...");
96 
97  images_proc_++;
98 
99  header_ = camera_cloud->header;
100 
101  pcl::PointCloud<pcl::PointXYZ>::Ptr cam_cloud (new pcl::PointCloud<pcl::PointXYZ> ());
102 
103  pcl::fromROSMsg(*camera_cloud,*cam_cloud);
104 
105  // 1.FILTER THE ONLY-EDGES-CLOUD ACCORDING TO THE DETECTED PLANE
106  // Make sure that the plane makes sense
107  if (cam_plane_coeffs->values[2]<min_plane_normal_z_){
108  ROS_WARN("[Camera] Estimated plane is not valid.");
109  return;
110  }
111 
112  // Segment planecircles_pub2
113  Eigen::VectorXf coefficients_v(4);
114  coefficients_v(0) = cam_plane_coeffs->values[0];
115  coefficients_v(1) = cam_plane_coeffs->values[1];
116  coefficients_v(2) = cam_plane_coeffs->values[2];
117  coefficients_v(3) = cam_plane_coeffs->values[3];
118 
119  pcl::PointCloud<pcl::PointXYZ>::Ptr cam_plane_cloud (new pcl::PointCloud<pcl::PointXYZ> ());
120  pcl::SampleConsensusModelPlane<pcl::PointXYZ>::Ptr dit (new pcl::SampleConsensusModelPlane<pcl::PointXYZ> (cam_cloud));
121  std::vector<int> plane_inliers;
122  dit->selectWithinDistance (coefficients_v, plane_distance_inliers_, plane_inliers);
123  pcl::copyPointCloud<pcl::PointXYZ>(*cam_cloud, plane_inliers, *cam_plane_cloud);
124 
125  // Publish plane as "plane_edges_cloud"
126  PointCloud2 plane_edges_ros;
127  pcl::toROSMsg(*cam_plane_cloud, plane_edges_ros);
128  plane_edges_ros.header = camera_cloud->header;
129  plane_edges_pub.publish(plane_edges_ros);
130 
131  // 2.REMOVE BORDER LINES
132  // Find them
133  pcl::PointCloud<pcl::PointXYZ>::Ptr temp_line_cloud(new pcl::PointCloud<pcl::PointXYZ>);
134  pcl::PointCloud<pcl::PointXYZ>::Ptr temp_cloud(new pcl::PointCloud<pcl::PointXYZ>);
135  pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients);
136  pcl::PointIndices::Ptr inliers (new pcl::PointIndices);
137  pcl::SACSegmentation<pcl::PointXYZ> lineseg;
138  pcl::ExtractIndices<pcl::PointXYZ> extract;
139  std::vector<pcl::ModelCoefficients> out_lines;
140 
141  lineseg.setModelType (pcl::SACMODEL_LINE);
142  lineseg.setDistanceThreshold (line_threshold_);
143  lineseg.setMethodType (pcl::SAC_RANSAC);
144  lineseg.setOptimizeCoefficients (true);
145  lineseg.setMaxIterations(1000);
146 
147  pcl::copyPointCloud<pcl::PointXYZ>(*cam_plane_cloud, *temp_line_cloud);
148 
149  float first_line_x = -1.0, first_line_y = -1.0;
150  int last_inliers;
151 
152  do{
153  lineseg.setInputCloud (temp_line_cloud);
154  lineseg.segment (*inliers, *coefficients);
155  if (inliers->indices.size () == 0)
156  {
157  if (DEBUG) ROS_INFO("Could not estimate a line model for the given dataset.");
158  break;
159  }
160 
161  last_inliers = inliers->indices.size ();
162 
163  if (inliers->indices.size () < min_line_inliers_){
164  continue;
165  }
166 
167  // Extract the inliers
168  extract.setInputCloud (temp_line_cloud);
169  extract.setIndices (inliers);
170 
171  // Remove inliers from cloud to find next line
172  extract.setNegative (true);
173  extract.filter(*temp_cloud);
174  temp_line_cloud.swap(temp_cloud);
175 
176  //if(DEBUG) ROS_INFO("Line orientation %f %f %f", coefficients->values[3], coefficients->values[4], coefficients->values[5 ]);
177 
178  if (fabs(coefficients->values[3])<min_border_x_ && fabs(coefficients->values[4])<min_border_x_){
179  //if(DEBUG) ROS_INFO("Invalid line (orientation)");
180  continue;
181  }
182 
183  if (fabs(coefficients->values[3]) >= min_border_x_){
184  if (first_line_x<0.0) first_line_x = coefficients->values[0];
185  else if (fabs(coefficients->values[0]-first_line_x) < min_distance_between_borders_x_){
186  //if(DEBUG) ROS_INFO("Invalid line (proximity)");
187  continue;
188  }
189  }else if(fabs(coefficients->values[4]) >= min_border_x_){
190  if (first_line_y<0.0) first_line_y = coefficients->values[1];
191  else if (fabs(coefficients->values[1]-first_line_y) < min_distance_between_borders_y_){
192  //if(DEBUG) ROS_INFO("Invalid line (proximity)");
193  continue;
194  }
195  }
196 
197  if(DEBUG) ROS_INFO("[Camera] Removed line: %d inliers / orientation: %f %f %f",
198  last_inliers, coefficients->values[3], coefficients->values[4],
199  coefficients->values[5]);
200 
201  out_lines.push_back(*coefficients);
202 
203  }while (last_inliers > min_line_inliers_ && out_lines.size() < 4);
204 
205  pcl::PointCloud<pcl::PointXYZ>::Ptr cam_plane_wol_cloud (new pcl::PointCloud<pcl::PointXYZ> ());
206 
207  // Remove them
208  for (vector<pcl::ModelCoefficients>::iterator it=out_lines.begin(); it<out_lines.end(); ++it){
209 
210  pcl::PointIndices::Ptr line_ind (new pcl::PointIndices);
211  pcl::SampleConsensusModelLine<pcl::PointXYZ>::Ptr dil (new pcl::SampleConsensusModelLine<pcl::PointXYZ> (cam_plane_cloud));
212  Eigen::VectorXf coefficients_l(6);
213  for(int j=0; j<6; j++){
214  coefficients_l(j) = it->values[j];
215  }
216  std::vector<int> line_inliers;
217  dil->selectWithinDistance (coefficients_l, border_distance_inliers_, line_inliers);
218 
219  line_ind->indices.resize(line_inliers.size());
220 
221  std::copy(line_inliers.begin(), line_inliers.end(), line_ind->indices.begin());
222 
223  // Extract the inliers
224  extract.setInputCloud (cam_plane_cloud);
225  extract.setIndices (line_ind);
226  extract.setNegative (true);
227  extract.filter (*cam_plane_wol_cloud);
228 
229  cam_plane_wol_cloud.swap(cam_plane_cloud);
230  }
231 
232  PointCloud2 circles_ros;
233  pcl::toROSMsg(*cam_plane_cloud, circles_ros);
234  circles_ros.header = camera_cloud->header;
235  circles_pub.publish(circles_ros);
236 
237  // 3.ROTATE CLOUD TO FACE PATTERN PLANE
238  pcl::PointCloud<pcl::PointXYZ>::Ptr xy_cloud(new pcl::PointCloud<pcl::PointXYZ>);
239  Eigen::Vector3f xy_plane_normal_vector, floor_plane_normal_vector;
240  xy_plane_normal_vector[0] = 0.0;
241  xy_plane_normal_vector[1] = 0.0;
242  xy_plane_normal_vector[2] = -1.0;
243 
244  floor_plane_normal_vector[0] = cam_plane_coeffs->values[0];
245  floor_plane_normal_vector[1] = cam_plane_coeffs->values[1];
246  floor_plane_normal_vector[2] = cam_plane_coeffs->values[2];
247 
248  std::vector<int> indices;
249  pcl::removeNaNFromPointCloud (*cam_plane_cloud, *cam_plane_cloud, indices);
250 
251  Eigen::Affine3f rotation = getRotationMatrix(floor_plane_normal_vector, xy_plane_normal_vector);
252  pcl::transformPointCloud(*cam_plane_cloud, *xy_cloud, rotation);
253 
254  pcl::PointCloud<pcl::PointXYZ>::Ptr aux_cloud(new pcl::PointCloud<pcl::PointXYZ>);
255  pcl::PointXYZ aux_point;
256  aux_point.x = 0;
257  aux_point.y = 0;
258  aux_point.z = (-coefficients_v(3)/coefficients_v(2));
259  aux_cloud->push_back(aux_point);
260 
261  pcl::PointCloud<pcl::PointXYZ>::Ptr auxrotated_cloud(new pcl::PointCloud<pcl::PointXYZ>);
262  pcl::transformPointCloud(*aux_cloud, *auxrotated_cloud, rotation);
263 
264  sensor_msgs::PointCloud2 ros_auxpoint;
265  pcl::toROSMsg(*aux_cloud, ros_auxpoint);
266  ros_auxpoint.header = camera_cloud->header;
267  auxpoint_pub.publish(ros_auxpoint);
268 
269  double zcoord_xyplane = auxrotated_cloud->at(0).z;
270 
271  // Force pattern points to belong to computed plane
272  for (pcl::PointCloud<pcl::PointXYZ>::iterator pt = xy_cloud->points.begin();
273  pt < xy_cloud->points.end(); ++pt){
274  pt->z = zcoord_xyplane;
275  }
276 
277  PointCloud2 xy_pattern_ros;
278  pcl::toROSMsg(*xy_cloud, xy_pattern_ros);
279  xy_pattern_ros.header = camera_cloud->header;
280  xy_pattern_pub.publish(xy_pattern_ros);
281 
282  // 4.FIND CIRCLES
283  pcl::SACSegmentation<pcl::PointXYZ> seg;
284 
285  seg.setModelType (pcl::SACMODEL_CIRCLE2D);
286  seg.setDistanceThreshold (circle_threshold_);
287  seg.setMethodType (pcl::SAC_RANSAC);
288  seg.setOptimizeCoefficients (true);
289  seg.setMaxIterations(1000);
290  seg.setRadiusLimits(0.11,0.13);
291 
292  pcl::PointCloud<pcl::PointXYZ>::Ptr circle_cloud(new pcl::PointCloud<pcl::PointXYZ>);
293 
294  std::vector< std::vector<float> > found_centers;
295  bool valid = true;
296 
297  do{
298 
299  seg.setInputCloud (xy_cloud);
300  seg.segment (*inliers, *coefficients);
301  if (inliers->indices.size () == 0)
302  {
303  ROS_WARN ("[Camera] Could not estimate a circle model for the given dataset.");
304  break;
305  }else{
306  if(DEBUG) ROS_INFO ("[Camera] Found circle: %lu inliers", inliers->indices.size ());
307  }
308 
309  // Extract the inliers
310  extract.setInputCloud (xy_cloud);
311  extract.setIndices (inliers);
312  extract.setNegative (false);
313  extract.filter (*circle_cloud);
314 
315  // Add center point to cloud
316  pcl::PointXYZ center;
317  center.x = *coefficients->values.begin();
318  center.y = *(coefficients->values.begin()+1);
319  for(std::vector<std::vector <float> >::iterator it = found_centers.begin(); it != found_centers.end(); ++it) {
320  if (sqrt(pow(fabs((*it)[0]-center.x),2) + pow(fabs((*it)[1]-center.y),2))<0.25){
321  valid = false;
322  }
323  }
324 
325  center.z = zcoord_xyplane;
326 
327  if (valid){
328  std::vector<float> found_center;
329  found_center.push_back(center.x);
330  found_center.push_back(center.y);
331  found_center.push_back(center.z);
332  found_centers.push_back(found_center);
333  }
334 
335  // Remove inliers from pattern cloud to find next circle
336  extract.setNegative (true);
337  extract.filter(*temp_cloud);
338  xy_cloud.swap(temp_cloud);
339 
340  PointCloud2 no_circles_ros;
341  pcl::toROSMsg(*xy_cloud, no_circles_ros);
342  no_circles_ros.header = camera_cloud->header;
343  no_circles_pub.publish(no_circles_ros);
344 
345  }while(found_centers.size() < 4 && valid);
346 
347  if (found_centers.size() < min_centers_found_ || found_centers.size() > 4){
348  ROS_WARN("Not enough centers: %ld", found_centers.size());
349  return;
350  }else{
351  for (std::vector<std::vector<float> >::iterator it = found_centers.begin(); it < found_centers.end(); ++it){
352  pcl::PointXYZ center;
353  center.x = (*it)[0];
354  center.y = (*it)[1];
355  center.z = (*it)[2];;
356  pcl::PointXYZ center_rotated_back = pcl::transformPoint(center, rotation.inverse());
357  center_rotated_back.z = (-cam_plane_coeffs->values[0]*center_rotated_back.x - cam_plane_coeffs->values[1]*center_rotated_back.y - cam_plane_coeffs->values[3])/cam_plane_coeffs->values[2];
358  cumulative_cloud->push_back(center_rotated_back);
359  }
360  }
361 
362  PointCloud2 cum_ros;
363  pcl::toROSMsg(*cumulative_cloud, cum_ros);
364  cum_ros.header = camera_cloud->header;
365  cumulative_pub.publish(cum_ros);
366 
367  pcl_msgs::PointIndices p_ind;
368 
369  pcl_conversions::moveFromPCL(*inliers, p_ind);
370  p_ind.header = camera_cloud->header;
371 
372  pcl_msgs::ModelCoefficients m_coeff;
373 
374  pcl_conversions::moveFromPCL(*coefficients, m_coeff);
375  m_coeff.header = camera_cloud->header;
376 
377  inliers_pub.publish(p_ind);
378  coeff_pub.publish(m_coeff);
379 
380  nFrames++;
382  ROS_INFO("[Camera] %d/%d frames: %ld pts in cloud",
383  images_used_, images_proc_, cumulative_cloud->points.size());
384  pcl::PointCloud<pcl::PointXYZ>::Ptr final_cloud(new pcl::PointCloud<pcl::PointXYZ>);
385 
386  getCenterClusters(cumulative_cloud, final_cloud, 0.1, nFrames/2, nFrames);
387  if (final_cloud->points.size()>4){
388  getCenterClusters(cumulative_cloud, final_cloud, 0.1, 3.0*nFrames/4.0, nFrames);
389  }
390 
391  if (final_cloud->points.size()==4){
392 
393  sensor_msgs::PointCloud2 final_ros;
394  pcl::toROSMsg(*final_cloud, final_ros);
395  final_ros.header = camera_cloud->header;
396 
397  velo2cam_calibration::ClusterCentroids to_send;
398  to_send.header = camera_cloud->header;
399  to_send.total_iterations = images_proc_;
400  to_send.cluster_iterations = images_used_;
401  to_send.cloud = final_ros;
402 
403  final_pub.publish(to_send);
404  }
405 }
406 
407 void param_callback(velo2cam_calibration::CameraConfig &config, uint32_t level){
408  circle_threshold_ = config.circle_threshold;
409  ROS_INFO("New circle threshold: %f", circle_threshold_);
410  line_threshold_ = config.line_threshold;
411  ROS_INFO("New line threshold: %f", line_threshold_);
412 }
413 
414 int main(int argc, char **argv){
415  ros::init(argc, argv, "stereo_pattern");
416  ros::NodeHandle nh_("~");
417 
420 
421  camera_cloud_sub_.subscribe(nh_, "cloud2", 1);
422  cam_plane_coeffs_sub_.subscribe(nh_, "cam_plane_coeffs", 1);
423 
424  inliers_pub = nh_.advertise<pcl_msgs::PointIndices> ("inliers", 1);
425  coeff_pub = nh_.advertise<pcl_msgs::ModelCoefficients> ("model", 1);
426  plane_edges_pub = nh_.advertise<PointCloud2> ("plane_edges_cloud", 1);
427  circles_pub = nh_.advertise<PointCloud2> ("circles_cloud", 1);
428  xy_pattern_pub = nh_.advertise<PointCloud2> ("xy_pattern", 1);
429  no_circles_pub = nh_.advertise<PointCloud2> ("no_circles", 1);
430  cumulative_pub = nh_.advertise<PointCloud2> ("cumulative_cloud", 1);
431  final_pub = nh_.advertise<velo2cam_calibration::ClusterCentroids> ("centers_cloud", 1);
432  transf_pub = nh_.advertise<PointCloud2> ("transf_cam", 1);
433  auxpoint_pub= nh_.advertise<PointCloud2> ("aux_point", 1);
434 
435  cumulative_cloud = pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>);
436 
438  message_filters::Synchronizer<MySyncPolicy> sync_(MySyncPolicy(10), camera_cloud_sub_, cam_plane_coeffs_sub_);
439  sync_.registerCallback(boost::bind(&callback, _1, _2));
440 
441  nh_.param("min_plane_normal_z", min_plane_normal_z_, 0.96);
442  nh_.param("plane_distance_inliers", plane_distance_inliers_, 0.01);
443  nh_.param("min_border_x", min_border_x_, 0.96);
444  nh_.param("min_distance_between_borders_x", min_distance_between_borders_x_, 1.0);
445  nh_.param("min_distance_between_borders_y", min_distance_between_borders_y_, 0.6);
446  nh_.param("border_distance_inliers", border_distance_inliers_, 0.05);
447  nh_.param("min_line_inliers", min_line_inliers_, 1200); //TODO: Adapt to the distance to the plane
448  nh_.param("cluster_size", cluster_size_, 0.02);
449  nh_.param("min_centers_found", min_centers_found_, 4);
450 
451  dynamic_reconfigure::Server<velo2cam_calibration::CameraConfig> server;
452  dynamic_reconfigure::Server<velo2cam_calibration::CameraConfig>::CallbackType f;
453  f = boost::bind(param_callback, _1, _2);
454  server.setCallback(f);
455 
456  ros::spin();
457  return 0;
458 }
ros::Publisher occluded_edges_pub
double line_threshold_
ros::Publisher coeff_pub
pcl::PointCloud< pcl::PointXYZ >::Ptr camera_cloud
ros::Publisher inliers_pub
void publish(const boost::shared_ptr< M > &message) const
double cluster_size_
f
int nFrames
image_transport::Publisher pub
ros::Publisher circles_pub
ros::Publisher plane_edges_pub
void param_callback(velo2cam_calibration::CameraConfig &config, uint32_t level)
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
std_msgs::Header header_
ros::Publisher no_circles_pub
void fromROSMsg(const sensor_msgs::PointCloud2 &cloud, pcl::PointCloud< T > &pcl_cloud)
ros::Publisher final_pub
int min_centers_found_
double min_distance_between_borders_y_
void callback(const PointCloud2::ConstPtr &camera_cloud, const pcl_msgs::ModelCoefficients::ConstPtr &cam_plane_coeffs)
#define ROS_WARN(...)
int images_used_
ROSCPP_DECL void spin(Spinner &spinner)
ros::Publisher high_curvature_edges_pub
double plane_distance_inliers_
#define DEBUG
Connection registerCallback(C &callback)
ros::Publisher occluding_edges_pub
#define ROS_INFO(...)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
double min_plane_normal_z_
ros::Publisher boundary_edges_pub
ros::Publisher cumulative_pub
double min_border_x_
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
INLINE Rall1d< T, V, S > sqrt(const Rall1d< T, V, S > &arg)
Eigen::Affine3f getRotationMatrix(Eigen::Vector3f source, Eigen::Vector3f target)
double min_distance_between_borders_x_
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 rgb_edges_pub
ros::Publisher xy_pattern_pub
int images_proc_
INLINE Rall1d< T, V, S > pow(const Rall1d< T, V, S > &arg, double m)
double border_distance_inliers_
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)
void toROSMsg(const sensor_msgs::PointCloud2 &cloud, sensor_msgs::Image &image)
pcl::PointCloud< pcl::PointXYZ >::Ptr cumulative_cloud
int min_line_inliers_
void moveFromPCL(pcl::PCLImage &pcl_image, sensor_msgs::Image &image)
ros::Publisher transf_pub
double circle_threshold_
ros::Publisher auxpoint_pub
void publish_cloud(pcl::PointCloud< pcl::PointXYZ >::Ptr input_cloud, ros::Publisher pub)


velo2cam_calibration
Author(s): Jorge Beltran , Carlos Guindel
autogenerated on Thu Feb 28 2019 03:24:25