calibrate.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2018-2019 Michael Ferguson
3  * Copyright (C) 2015 Fetch Robotics Inc.
4  * Copyright (C) 2013-2014 Unbounded Robotics Inc.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 // Author: Michael Ferguson
20 
21 #include <fstream>
22 #include <ctime>
23 
24 #include <ros/ros.h>
25 #include <rosbag/bag.h>
26 #include <rosbag/view.h>
27 #include <rosbag/query.h>
28 
29 #include <std_msgs/String.h>
30 #include <robot_calibration_msgs/CalibrationData.h>
31 #include <robot_calibration_msgs/CaptureConfig.h>
32 
35 
40 
41 #include <boost/foreach.hpp> // for rosbag iterator
42 
67 /*
68  * usage:
69  * calibrate --manual
70  * calibrate calibration_poses.bag
71  * calibrate --from-bag calibration_data.bag
72  */
73 int main(int argc, char** argv)
74 {
75  ros::init(argc, argv,"robot_calibration");
76  ros::NodeHandle nh("~");
77 
78  // Should we be stupidly verbose?
79  bool verbose;
80  nh.param<bool>("verbose", verbose, false);
81 
82  // The calibration data
83  std_msgs::String description_msg;
84  std::vector<robot_calibration_msgs::CalibrationData> data;
85 
86  // What bag to use to load calibration poses out of (for capture)
87  std::string pose_bag_name("calibration_poses.bag");
88  if (argc > 1)
89  pose_bag_name = argv[1];
90 
91  if (pose_bag_name.compare("--from-bag") != 0)
92  {
93  // No name provided for a calibration bag file, must do capture
94  robot_calibration::ChainManager chain_manager_(nh);
95  robot_calibration::FeatureFinderLoader feature_finder_loader;
97  if (!feature_finder_loader.load(nh, finders_))
98  {
99  ROS_FATAL("Unable to load feature finders!");
100  return -1;
101  }
102 
103  ros::Publisher pub = nh.advertise<robot_calibration_msgs::CalibrationData>("/calibration_data", 10);
104  ros::Publisher urdf_pub = nh.advertise<std_msgs::String>("/robot_description", 1, true); // latched
105 
106  // Get the robot_description and republish it
107  if (!nh.getParam("/robot_description", description_msg.data))
108  {
109  ROS_FATAL("robot_description not set!");
110  return -1;
111  }
112  urdf_pub.publish(description_msg);
113 
114  // Load a set of calibration poses
115  std::vector<robot_calibration_msgs::CaptureConfig> poses;
116  if (pose_bag_name.compare("--manual") != 0)
117  {
118  ROS_INFO_STREAM("Opening " << pose_bag_name);
119  rosbag::Bag bag;
120  try
121  {
122  bag.open(pose_bag_name, rosbag::bagmode::Read);
123  }
124  catch (rosbag::BagException&)
125  {
126  ROS_FATAL_STREAM("Cannot open " << pose_bag_name);
127  return -1;
128  }
129  rosbag::View data_view(bag, rosbag::TopicQuery("calibration_joint_states"));
130 
131  BOOST_FOREACH (rosbag::MessageInstance const m, data_view)
132  {
133  robot_calibration_msgs::CaptureConfig::ConstPtr msg = m.instantiate<robot_calibration_msgs::CaptureConfig>();
134  if (msg == NULL)
135  {
136  // Try to load older style bags
137  sensor_msgs::JointState::ConstPtr js_msg = m.instantiate<sensor_msgs::JointState>();
138  if (js_msg != NULL)
139  {
140  robot_calibration_msgs::CaptureConfig config;
141  config.joint_states = *js_msg;
142  // Assume all finders should find this pose (old style config):
143  for (robot_calibration::FeatureFinderMap::iterator it = finders_.begin();
144  it != finders_.end();
145  it++)
146  {
147  config.features.push_back(it->first);
148  }
149  poses.push_back(config);
150  }
151  }
152  else
153  {
154  poses.push_back(*msg);
155  }
156  }
157  }
158  else
159  {
160  ROS_INFO("Using manual calibration mode...");
161  }
162 
163  // For each pose in the capture sequence.
164  for (unsigned pose_idx = 0;
165  (pose_idx < poses.size() || poses.size() == 0) && ros::ok();
166  ++pose_idx)
167  {
168  robot_calibration_msgs::CalibrationData msg;
169 
170  if (poses.size() == 0)
171  {
172  // Manual calibration, wait for keypress
173  ROS_INFO("Press key when arm is ready... (type 'done' to finish capture)");
174  std::string throwaway;
175  std::getline(std::cin, throwaway);
176  if (throwaway.compare("done") == 0)
177  break;
178  if (!ros::ok())
179  break;
180  }
181  else
182  {
183  // Move head/arm to pose
184  if (!chain_manager_.moveToState(poses[pose_idx].joint_states))
185  {
186  ROS_WARN("Unable to move to desired state for sample %u.", pose_idx);
187  continue;
188  }
189  }
190 
191  // Regardless of manual vs. automatic, wait for joints to settle
192  chain_manager_.waitToSettle();
193 
194  // Make sure sensor data is up to date after settling
195  ros::Duration(0.1).sleep();
196 
197  // Get pose of the features
198  bool found_all_features = true;
199  if (poses.size() == 0 || poses[pose_idx].features.empty())
200  {
201  // In manual mode, or if features are unspecified, we need to capture all features
202  for (robot_calibration::FeatureFinderMap::iterator it = finders_.begin();
203  it != finders_.end();
204  it++)
205  {
206  if (!it->second->find(&msg))
207  {
208  ROS_WARN("%s failed to capture features.", it->first.c_str());
209  found_all_features = false;
210  break;
211  }
212  }
213  }
214  else
215  {
216  // Capture only the intended features for this sample
217  for (size_t i = 0; i < poses[pose_idx].features.size(); i++)
218  {
219  std::string feature = poses[pose_idx].features[i];
220  if (!finders_[feature]->find(&msg))
221  {
222  ROS_WARN("%s failed to capture features.", feature.c_str());
223  found_all_features = false;
224  break;
225  }
226  }
227  }
228 
229  // Make sure we succeeded
230  if (found_all_features)
231  {
232  ROS_INFO("Captured pose %u", pose_idx);
233  }
234  else
235  {
236  ROS_WARN("Failed to capture sample %u.", pose_idx);
237  continue;
238  }
239 
240  // Fill in joint values
241  chain_manager_.getState(&msg.joint_states);
242 
243  // Publish calibration data message.
244  pub.publish(msg);
245 
246  // Add to samples
247  data.push_back(msg);
248  }
249 
250  ROS_INFO("Done capturing samples");
251  }
252  else
253  {
254  // Load calibration data from bagfile
255  std::string data_bag_name("/tmp/calibration_data.bag");
256  if (argc > 2)
257  data_bag_name = argv[2];
258  ROS_INFO_STREAM("Loading calibration data from " << data_bag_name);
259 
260  if (!robot_calibration::load_bag(data_bag_name, description_msg, data))
261  {
262  // Error will have been printed in function
263  return -1;
264  }
265  }
266 
267  // Create instance of optimizer
269  robot_calibration::Optimizer opt(description_msg.data);
270 
271  // Load calibration steps (if any)
272  XmlRpc::XmlRpcValue cal_steps;
273  if (nh.getParam("cal_steps", cal_steps))
274  {
275  // Should be a struct (mapping name -> config)
276  if (cal_steps.getType() != XmlRpc::XmlRpcValue::TypeStruct)
277  {
278  ROS_FATAL("Parameter 'cal_steps' should be a struct.");
279  return false;
280  }
281 
283  size_t step;
284  size_t max_step = (cal_steps.size()>0)?cal_steps.size():1;
285  std::vector<std::string> prev_frame_names;
286  std::string prev_params_yaml;
287  for (step = 0, it = cal_steps.begin(); step < max_step; step++, it++)
288  {
289  std::string name = static_cast<std::string>(it->first);
290  ros::NodeHandle cal_steps_handle(nh, "cal_steps/"+name);
291  params.LoadFromROS(cal_steps_handle);
292  opt.optimize(params, data, verbose);
293  if (verbose)
294  {
295  std::cout << "Parameter Offsets:" << std::endl;
296  std::cout << opt.getOffsets()->getOffsetYAML() << std::endl;
297  }
298  }
299  }
300  else
301  {
302  // Single step calibration
303  params.LoadFromROS(nh);
304  opt.optimize(params, data, verbose);
305  if (verbose)
306  {
307  std::cout << "Parameter Offsets:" << std::endl;
308  std::cout << opt.getOffsets()->getOffsetYAML() << std::endl;
309  }
310  }
311 
312  // Generate datecode
313  char datecode[80];
314  {
315  std::time_t t = std::time(NULL);
316  std::strftime(datecode, 80, "%Y_%m_%d_%H_%M_%S", std::localtime(&t));
317  }
318 
319  // Save updated URDF
320  {
321  std::string s = opt.getOffsets()->updateURDF(description_msg.data);
322  std::stringstream urdf_name;
323  urdf_name << "/tmp/calibrated_" << datecode << ".urdf";
324  std::ofstream file;
325  file.open(urdf_name.str().c_str());
326  file << s;
327  file.close();
328  }
329 
330  // Output camera calibration
331  {
332  std::stringstream depth_name;
333  depth_name << "/tmp/depth_" << datecode << ".yaml";
336  opt.getOffsets()->get("camera_fx"),
337  opt.getOffsets()->get("camera_fy"),
338  opt.getOffsets()->get("camera_cx"),
339  opt.getOffsets()->get("camera_cy"),
340  data[0].observations[0].ext_camera_info.camera_info)); // TODO avoid hardcoding index
341 
342  std::stringstream rgb_name;
343  rgb_name << "/tmp/rgb_" << datecode << ".yaml";
346  opt.getOffsets()->get("camera_fx"),
347  opt.getOffsets()->get("camera_fy"),
348  opt.getOffsets()->get("camera_cx"),
349  opt.getOffsets()->get("camera_cy"),
350  data[0].observations[0].ext_camera_info.camera_info)); // TODO avoid hardcoding index
351  }
352 
353  // Output the calibration yaml
354  {
355  std::stringstream yaml_name;
356  yaml_name << "/tmp/calibration_" << datecode << ".yaml";
357  std::ofstream file;
358  file.open(yaml_name.str().c_str());
359  file << opt.getOffsets()->getOffsetYAML();
360  file << "depth_info: depth_" << datecode << ".yaml" << std::endl;
361  file << "rgb_info: rgb_" << datecode << ".yaml" << std::endl;
362  file << "urdf: calibrated_" << datecode << ".urdf" << std::endl;
363  file.close();
364  }
365 
366  ROS_INFO("Done calibrating");
367 
368  return 0;
369 }
#define ROS_FATAL(...)
sensor_msgs::CameraInfo updateCameraInfo(double camera_fx, double camera_fy, double camera_cx, double camera_cy, const sensor_msgs::CameraInfo &info)
Update the camera calibration with the new offsets.
Definition: camera_info.h:54
void publish(const boost::shared_ptr< M > &message) const
std::map< std::string, FeatureFinderPtr > FeatureFinderMap
ValueStruct::iterator iterator
void open(std::string const &filename, uint32_t mode=bagmode::Read)
int optimize(OptimizationParams &params, std::vector< robot_calibration_msgs::CalibrationData > data, bool progress_to_stdout=false)
Run optimization.
Definition: optimizer.cpp:56
Class to hold parameters for optimization.
int size() const
XmlRpcServer s
bool sleep() const
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
Load feature finders, based on param server config.
Manages moving joints to a new pose, determining when they are settled, and returning current joint_s...
Definition: chain_manager.h:37
Type const & getType() const
#define ROS_WARN(...)
geometry_msgs::TransformStamped t
boost::shared_ptr< CalibrationOffsetParser > getOffsets()
Definition: optimizer.h:69
#define ROS_FATAL_STREAM(args)
#define ROS_INFO(...)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
int main(int argc, char **argv)
Definition: calibrate.cpp:73
boost::shared_ptr< T > instantiate() const
ROSCPP_DECL bool ok()
bool load(ros::NodeHandle &nh, FeatureFinderMap &features)
bool writeCalibration(const std::string &file_name, const std::string &camera_name, const sensor_msgs::CameraInfo &cam_info)
bool waitToSettle()
Wait for joints to settle.
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
unsigned int step
bool load_bag(const std::string &file_name, std_msgs::String &description_msg, std::vector< robot_calibration_msgs::CalibrationData > &data)
Load a bagfile of calibration data.
Definition: load_bag.h:42
#define ROS_INFO_STREAM(args)
bool moveToState(const sensor_msgs::JointState &state)
Send commands to all managed joints. The ChainManager automatically figures out which controller to s...
Class to do optimization.
Definition: optimizer.h:45
bool getParam(const std::string &key, std::string &s) const
bool getState(sensor_msgs::JointState *state)
Get the current JointState message.


robot_calibration
Author(s): Michael Ferguson
autogenerated on Tue Nov 3 2020 17:30:30