calibrate_kinect_checkerboard.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2011, Willow Garage, Inc.
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions are met:
00007  *
00008  *     * Redistributions of source code must retain the above copyright
00009  *       notice, this list of conditions and the following disclaimer.
00010  *     * Redistributions in binary form must reproduce the above copyright
00011  *       notice, this list of conditions and the following disclaimer in the
00012  *       documentation and/or other materials provided with the distribution.
00013  *     * Neither the name of the Willow Garage, Inc. nor the names of its
00014  *       contributors may be used to endorse or promote products derived from
00015  *       this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00021  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00022  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00023  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00024  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00025  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00026  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00027  * POSSIBILITY OF SUCH DAMAGE.
00028  */
00029 
00030 #include <ros/ros.h>
00031 #include <image_transport/image_transport.h>
00032 
00033 #include <cv_bridge/cv_bridge.h>
00034 #include <image_geometry/pinhole_camera_model.h>
00035 #include <tf/transform_listener.h>
00036 #include <tf/transform_broadcaster.h>
00037 
00038 #include <pcl/point_types.h>
00039 #include <pcl_ros/point_cloud.h>
00040 #include <pcl_ros/transforms.h>
00041 #include <pcl/registration/registration.h>
00042 #include <pcl/registration/transformation_estimation_svd.h>
00043 
00044 #include <sensor_msgs/Image.h>
00045 #include <sensor_msgs/CameraInfo.h>
00046 #include <geometry_msgs/PointStamped.h>
00047 
00048 #include <turtlebot_arm_kinect_calibration/detect_calibration_pattern.h>
00049 
00050 using namespace std;
00051 using namespace Eigen;
00052 
00053 tf::Transform tfFromEigen(Eigen::Matrix4f trans)
00054 {
00055   tf::Matrix3x3 btm;
00056   btm.setValue(trans(0, 0), trans(0, 1), trans(0, 2),
00057                trans(1, 0), trans(1, 1), trans(1, 2),
00058                trans(2, 0), trans(2, 1), trans(2, 2));
00059   tf::Transform ret;
00060   ret.setOrigin(tf::Vector3(trans(0, 3), trans(1, 3), trans(2, 3)));
00061   ret.setBasis(btm);
00062   return ret;
00063 }
00064 
00065 Eigen::Matrix4f EigenFromTF(tf::Transform trans)
00066 {
00067   Eigen::Matrix4f out;
00068   tf::Quaternion quat = trans.getRotation();
00069   tf::Vector3 origin = trans.getOrigin();
00070 
00071   Eigen::Quaternionf quat_out(quat.w(), quat.x(), quat.y(), quat.z());
00072   Eigen::Vector3f origin_out(origin.x(), origin.y(), origin.z());
00073 
00074   out.topLeftCorner<3, 3>() = quat_out.toRotationMatrix();
00075   out.topRightCorner<3, 1>() = origin_out;
00076   out(3, 3) = 1;
00077 
00078   return out;
00079 }
00080 
00081 class CalibrateKinectCheckerboard
00082 {
00083   // Nodes and publishers/subscribers
00084   ros::NodeHandle nh_;
00085   image_transport::ImageTransport it_;
00086   image_transport::Publisher pub_;
00087   ros::Publisher detector_pub_;
00088   ros::Publisher physical_pub_;
00089 
00090   // Image and camera info subscribers;
00091   ros::Subscriber image_sub_;
00092   ros::Subscriber info_sub_;
00093 
00094   // Structures for interacting with ROS messages
00095   cv_bridge::CvImagePtr input_bridge_;
00096   cv_bridge::CvImagePtr output_bridge_;
00097   tf::TransformListener tf_listener_;
00098   tf::TransformBroadcaster tf_broadcaster_;
00099   image_geometry::PinholeCameraModel cam_model_;
00100 
00101   // Calibration objects
00102   PatternDetector pattern_detector_;
00103 
00104   // The optimized transform
00105   Eigen::Transform<float, 3, Eigen::Affine> transform_;
00106 
00107   // Visualization for markers
00108   pcl::PointCloud<pcl::PointXYZ> detector_points_;
00109   pcl::PointCloud<pcl::PointXYZ> ideal_points_;
00110   pcl::PointCloud<pcl::PointXYZ> image_points_;
00111   pcl::PointCloud<pcl::PointXYZ> physical_points_;
00112 
00113   // Have we calibrated the camera yet?
00114   bool calibrated;
00115 
00116   ros::Timer timer_;
00117 
00118   // Parameters
00119   std::string fixed_frame;
00120   std::string camera_frame;
00121   std::string target_frame;
00122   std::string tip_frame;
00123   std::string touch_frame;
00124 
00125   int checkerboard_width;
00126   int checkerboard_height;
00127   double checkerboard_grid;
00128 
00129   // Gripper tip position
00130   geometry_msgs::PointStamped gripper_tip;
00131 
00132 public:
00133   CalibrateKinectCheckerboard() :
00134       nh_("~"), it_(nh_), calibrated(false)
00135   {
00136     // Load parameters from the server.
00137     nh_.param<std::string>("fixed_frame", fixed_frame, "/base_link");
00138     nh_.param<std::string>("camera_frame", camera_frame, "/camera_link");
00139     nh_.param<std::string>("target_frame", target_frame, "/calibration_pattern");
00140     nh_.param<std::string>("tip_frame", tip_frame, "/gripper_link");
00141 
00142     nh_.param<int>("checkerboard_width", checkerboard_width, 6);
00143     nh_.param<int>("checkerboard_height", checkerboard_height, 7);
00144     nh_.param<double>("checkerboard_grid", checkerboard_grid, 0.027);
00145 
00146     // Set pattern detector sizes
00147     pattern_detector_.setPattern(cv::Size(checkerboard_width, checkerboard_height),
00148                                  checkerboard_grid, CHESSBOARD);
00149 
00150     transform_.translation().setZero();
00151     transform_.matrix().topLeftCorner<3, 3>() = Quaternionf().setIdentity().toRotationMatrix();
00152 
00153     // Create subscriptions
00154     info_sub_ = nh_.subscribe("/camera/rgb/camera_info", 1, &CalibrateKinectCheckerboard::infoCallback, this);
00155 
00156     // Also publishers
00157     pub_ = it_.advertise("calibration_pattern_out", 1);
00158     detector_pub_ = nh_.advertise<pcl::PointCloud<pcl::PointXYZ> >("detector_cloud", 1);
00159     physical_pub_ = nh_.advertise<pcl::PointCloud<pcl::PointXYZ> >("physical_points_cloud", 1);
00160 
00161     // Create ideal points
00162     ideal_points_.push_back(pcl::PointXYZ(0, 0, 0));
00163     ideal_points_.push_back(pcl::PointXYZ((checkerboard_width - 1) * checkerboard_grid, 0, 0));
00164     ideal_points_.push_back(pcl::PointXYZ(0, (checkerboard_height - 1) * checkerboard_grid, 0));
00165     ideal_points_.push_back(pcl::PointXYZ((checkerboard_width - 1) * checkerboard_grid,
00166                                           (checkerboard_height - 1) * checkerboard_grid, 0));
00167 
00168     // Create proper gripper tip point
00169     nh_.param<double>("gripper_tip_x", gripper_tip.point.x, 0.0);
00170     nh_.param<double>("gripper_tip_y", gripper_tip.point.y, 0.0);
00171     nh_.param<double>("gripper_tip_z", gripper_tip.point.z, 0.0);
00172     gripper_tip.header.frame_id = tip_frame;
00173 
00174     ROS_INFO("[calibrate] Initialized.");
00175   }
00176 
00177   void infoCallback(const sensor_msgs::CameraInfoConstPtr& info_msg)
00178   {
00179     if (calibrated)
00180       return;
00181     cam_model_.fromCameraInfo(info_msg);
00182     pattern_detector_.setCameraMatrices(cam_model_.intrinsicMatrix(), cam_model_.distortionCoeffs());
00183 
00184     calibrated = true;
00185     image_sub_ = nh_.subscribe("/camera/rgb/image_mono", 1, &CalibrateKinectCheckerboard::imageCallback, this);
00186 
00187     ROS_INFO("[calibrate] Got image info!");
00188   }
00189 
00190   void pointcloudCallback(const pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr& msg)
00191   {
00192     sensor_msgs::ImagePtr image_msg(new sensor_msgs::Image);
00193     sensor_msgs::PointCloud2 cloud;
00194     pcl::toROSMsg(*msg, cloud);
00195     pcl::toROSMsg(cloud, *image_msg);
00196 
00197     imageCallback(image_msg);
00198   }
00199 
00200   void imageCallback(const sensor_msgs::ImageConstPtr& image_msg)
00201   {
00202     try
00203     {
00204       input_bridge_ = cv_bridge::toCvCopy(image_msg, "mono8");
00205       output_bridge_ = cv_bridge::toCvCopy(image_msg, "bgr8");
00206     }
00207     catch (cv_bridge::Exception& ex)
00208     {
00209       ROS_ERROR("[calibrate] Failed to convert image:\n%s", ex.what());
00210       return;
00211     }
00212 
00213     Eigen::Vector3f translation;
00214     Eigen::Quaternionf orientation;
00215 
00216     if (!pattern_detector_.detectPattern(input_bridge_->image, translation, orientation, output_bridge_->image))
00217     {
00218       ROS_INFO("[calibrate] Couldn't detect checkerboard, make sure it's visible in the image.");
00219       return;
00220     }
00221 
00222     tf::Transform target_transform;
00223     tf::StampedTransform base_transform;
00224     try
00225     {
00226       ros::Time acquisition_time = image_msg->header.stamp;
00227       ros::Duration timeout(1.0 / 30.0);
00228 
00229       target_transform.setOrigin(tf::Vector3(translation.x(), translation.y(), translation.z()));
00230       target_transform.setRotation(tf::Quaternion(orientation.x(), orientation.y(), orientation.z(), orientation.w()));
00231       tf_broadcaster_.sendTransform(
00232           tf::StampedTransform(target_transform, image_msg->header.stamp, image_msg->header.frame_id, target_frame));
00233     }
00234     catch (tf::TransformException& ex)
00235     {
00236       ROS_WARN("[calibrate] TF exception:\n%s", ex.what());
00237       return;
00238     }
00239     publishCloud(ideal_points_, target_transform, image_msg->header.frame_id);
00240 
00241     overlayPoints(ideal_points_, target_transform, output_bridge_);
00242 
00243     // Publish calibration image
00244     pub_.publish(output_bridge_->toImageMsg());
00245 
00246     pcl_ros::transformPointCloud(ideal_points_, image_points_, target_transform);
00247 
00248     cout << "Got an image callback!" << endl;
00249 
00250     calibrate(image_msg->header.frame_id);
00251 
00252     ros::shutdown();
00253   }
00254 
00255   void publishCloud(pcl::PointCloud<pcl::PointXYZ> detector_points, tf::Transform &transform,
00256                     const std::string frame_id)
00257   {
00258     // Display to rviz
00259     pcl::PointCloud < pcl::PointXYZ > transformed_detector_points;
00260 
00261     pcl_ros::transformPointCloud(detector_points, transformed_detector_points, transform);
00262 
00263     transformed_detector_points.header.frame_id = frame_id;
00264     detector_pub_.publish(transformed_detector_points);
00265   }
00266 
00267   void overlayPoints(pcl::PointCloud<pcl::PointXYZ> detector_points, tf::Transform &transform,
00268                      cv_bridge::CvImagePtr& image)
00269   {
00270     // Overlay calibration points on the image
00271     pcl::PointCloud < pcl::PointXYZ > transformed_detector_points;
00272 
00273     pcl_ros::transformPointCloud(detector_points, transformed_detector_points, transform);
00274 
00275     int font_face = cv::FONT_HERSHEY_SCRIPT_SIMPLEX;
00276     double font_scale = 1;
00277     int thickness = 2;
00278     int radius = 5;
00279 
00280     for (unsigned int i = 0; i < transformed_detector_points.size(); i++)
00281     {
00282       pcl::PointXYZ pt = transformed_detector_points[i];
00283       cv::Point3d pt_cv(pt.x, pt.y, pt.z);
00284       cv::Point2d uv;
00285       uv = cam_model_.project3dToPixel(pt_cv);
00286 
00287       cv::circle(image->image, uv, radius, CV_RGB(255, 0, 0), -1);
00288       cv::Size text_size;
00289       int baseline = 0;
00290       std::stringstream out;
00291       out << i + 1;
00292 
00293       text_size = cv::getTextSize(out.str(), font_face, font_scale, thickness, &baseline);
00294 
00295       cv::Point origin = cvPoint(uv.x - text_size.width / 2, uv.y - radius - baseline/* - thickness*/);
00296       cv::putText(image->image, out.str(), origin, font_face, font_scale, CV_RGB(255, 0, 0), thickness);
00297     }
00298   }
00299 
00300   bool calibrate(const std::string frame_id)
00301   {
00302     physical_points_.empty();
00303     physical_points_.header.frame_id = fixed_frame;
00304     cout << "Is the checkerboard correct? " << endl;
00305     cout << "Move edge of gripper to point 1 in image and press Enter. " << endl;
00306     cin.ignore();
00307     addPhysicalPoint();
00308     cout << "Move edge of gripper to point 2 in image and press Enter. " << endl;
00309     cin.ignore();
00310     addPhysicalPoint();
00311     cout << "Move edge of gripper to point 3 in image and press Enter. " << endl;
00312     cin.ignore();
00313     addPhysicalPoint();
00314     cout << "Move edge of gripper to point 4 in image and press Enter. " << endl;
00315     cin.ignore();
00316     addPhysicalPoint();
00317 
00318     Eigen::Matrix4f t;
00319 
00320     physical_pub_.publish(physical_points_);
00321 
00322     pcl::registration::TransformationEstimationSVD < pcl::PointXYZ, pcl::PointXYZ > svd_estimator;
00323     svd_estimator.estimateRigidTransformation(physical_points_, image_points_, t);
00324 
00325     // Output       
00326     tf::Transform transform = tfFromEigen(t), trans_full, camera_transform_unstamped;
00327     tf::StampedTransform camera_transform;
00328 
00329     cout << "Resulting transform (camera frame -> fixed frame): " << endl << t << endl << endl;
00330 
00331     try
00332     {
00333       tf_listener_.lookupTransform(frame_id, camera_frame, ros::Time(0), camera_transform);
00334     }
00335     catch (tf::TransformException& ex)
00336     {
00337       ROS_WARN("[calibrate] TF exception:\n%s", ex.what());
00338       return false;
00339     }
00340 
00341     camera_transform_unstamped = camera_transform;
00342     trans_full = camera_transform_unstamped.inverse() * transform;
00343 
00344     Eigen::Matrix4f t_full = EigenFromTF(trans_full);
00345     Eigen::Matrix4f t_full_inv = (Eigen::Transform<float, 3, Affine>(t_full).inverse()).matrix();
00346 
00347     cout << "Resulting transform (fixed frame -> camera frame): " << endl << t_full << endl << endl;
00348     printStaticTransform(t_full_inv, fixed_frame, camera_frame);
00349 
00350     return true;
00351   }
00352 
00353   void printStaticTransform(Eigen::Matrix4f& transform, const std::string frame1, const std::string frame2)
00354   {
00355     Eigen::Quaternionf quat(transform.topLeftCorner<3, 3>());
00356     Eigen::Vector3f translation(transform.topRightCorner<3, 1>());
00357 
00358     cout << "Static transform publisher (use for external kinect): " << endl;
00359 
00360     cout << "rosrun tf static_transform_publisher x y z qx qy qz qw frame_id child_frame_id period_in_ms" << endl;
00361     cout << "rosrun tf static_transform_publisher " << translation.x() << " " << translation.y() << " "
00362          << translation.z() << " " << quat.x() << " " << quat.y() << " " << quat.z() << " " << quat.w()
00363          << " " << frame1 << " " << frame2 << " 100" << endl << endl;
00364 
00365     tf::Transform temp_tf_trans = tfFromEigen(transform);
00366 
00367     double yaw, pitch, roll;
00368 
00369     std::string fixed_frame_urdf(fixed_frame);
00370 
00371     // If there's a leading '/' character, remove it, as xacro can't deal with 
00372     // extra characters in the link name.
00373     if (fixed_frame_urdf.size() > 0 && fixed_frame_urdf[0] == '/')
00374       fixed_frame_urdf.erase(0, 1);
00375 
00376     temp_tf_trans.getBasis().getEulerYPR(yaw, pitch, roll);
00377 
00378     cout << "URDF output (use for kinect on robot): " << endl;
00379 
00380     cout << "<?xml version=\"1.0\"?>\n<robot>\n" << "\t<property name=\"turtlebot_calib_cam_x\" value=\""
00381          << translation.x() << "\" />\n" << "\t<property name=\"turtlebot_calib_cam_y\" value=\"" << translation.y()
00382          << "\" />\n" << "\t<property name=\"turtlebot_calib_cam_z\" value=\"" << translation.z() << "\" />\n"
00383          << "\t<property name=\"turtlebot_calib_cam_rr\" value=\"" << roll << "\" />\n"
00384          << "\t<property name=\"turtlebot_calib_cam_rp\" value=\"" << pitch << "\" />\n"
00385          << "\t<property name=\"turtlebot_calib_cam_ry\" value=\"" << yaw << "\" />\n"
00386          << "\t<property name=\"turtlebot_kinect_frame_name\" value=\"" << fixed_frame_urdf << "\" />\n" << "</robot>"
00387          << endl << endl;
00388   }
00389 
00390   void addPhysicalPoint()
00391   {
00392     geometry_msgs::PointStamped pt_out;
00393 
00394     try
00395     {
00396       tf_listener_.transformPoint(fixed_frame, gripper_tip, pt_out);
00397     }
00398     catch (tf::TransformException& ex)
00399     {
00400       ROS_WARN("[calibrate] TF exception:\n%s", ex.what());
00401       return;
00402     }
00403 
00404     physical_points_.push_back(pcl::PointXYZ(pt_out.point.x, pt_out.point.y, pt_out.point.z));
00405   }
00406 
00407   void convertIdealPointstoPointcloud()
00408   {
00409     detector_points_.points.resize(pattern_detector_.ideal_points.size());
00410     for (unsigned int i = 0; i < pattern_detector_.ideal_points.size(); i++)
00411     {
00412       cv::Point3f pt = pattern_detector_.ideal_points[i];
00413       detector_points_[i].x = pt.x;
00414       detector_points_[i].y = pt.y;
00415       detector_points_[i].z = pt.z;
00416     }
00417   }
00418 
00419 };
00420 
00421 int main(int argc, char** argv)
00422 {
00423   ros::init(argc, argv, "calibrate_kinect_arm");
00424 
00425   CalibrateKinectCheckerboard cal;
00426   ros::spin();
00427 }


turtlebot_arm_kinect_calibration
Author(s): Michael Ferguson, Helen Oleynikova
autogenerated on Thu Jun 6 2019 20:54:25