00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
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
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
00091 ros::Subscriber image_sub_;
00092 ros::Subscriber info_sub_;
00093
00094
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
00102 PatternDetector pattern_detector_;
00103
00104
00105 Eigen::Transform<float, 3, Eigen::Affine> transform_;
00106
00107
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
00114 bool calibrated;
00115
00116 ros::Timer timer_;
00117
00118
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
00130 geometry_msgs::PointStamped gripper_tip;
00131
00132 public:
00133 CalibrateKinectCheckerboard() :
00134 nh_("~"), it_(nh_), calibrated(false)
00135 {
00136
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
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
00154 info_sub_ = nh_.subscribe("/camera/rgb/camera_info", 1, &CalibrateKinectCheckerboard::infoCallback, this);
00155
00156
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
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
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
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
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
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);
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
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
00372
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 }