calibrate.cc
Go to the documentation of this file.
00001 
00017 #include <ros/ros.h>
00018 #include <image_transport/image_transport.h>
00019 #include <image_geometry/pinhole_camera_model.h>
00020 
00021 #include <boost/thread/mutex.hpp>
00022 #include <boost/lexical_cast.hpp>
00023 
00024 #include <pcl/point_types.h>
00025 #include <pcl_visualization/pcl_visualizer.h>
00026 #include <pcl/common/transformation_from_correspondences.h>
00027 #include <pcl/registration/transforms.h>
00028 #include <pcl/features/normal_3d.h>
00029 #include <pcl/filters/extract_indices.h>
00030 #include <terminal_tools/parse.h>
00031 
00032 #include <Eigen/Core>
00033 
00034 #include <opencv/cv.h>
00035 #include <opencv/highgui.h>
00036 #include <cv_bridge/CvBridge.h>
00037 
00038 #include <ground_truth/field_provider.h>
00039 
00040 namespace {
00041 
00042   sensor_msgs::PointCloud2ConstPtr cloudPtr, oldCloudPtr;
00043   pcl::PointCloud<pcl::PointXYZRGB> cloud, transformedCloud;
00044   boost::mutex mCloud;
00045   pcl_visualization::PointCloudColorHandler<pcl::PointXYZRGB>::Ptr colorHandler;
00046   pcl_visualization::PointCloudGeometryHandler<pcl::PointXYZRGB>::Ptr geometryHandler;
00047   ground_truth::FieldProvider fieldProvider;
00048 
00049   IplImage* rgbImage = NULL;
00050   boost::mutex mImage;
00051   sensor_msgs::CvBridge bridge;
00052   image_geometry::PinholeCameraModel model;
00053 
00054   Eigen::Vector3f rayPt1, rayPt2;
00055 
00056   const int SELECTOR_IMAGE_WIDTH = 240;
00057   const int SELECTOR_IMAGE_HEIGHT = 180;
00058 
00059   IplImage * selectorImage;
00060   pcl::TransformationFromCorrespondences rigidBodyTransform;
00061 
00062   const int MAX_GROUND_POINTS = 5;
00063   Eigen::Vector3f groundPoints[MAX_GROUND_POINTS];
00064   int numGroundPoints = 0;
00065   int displayGroundPoints = 0;
00066 
00067   Eigen::Vector3f landmarkPoints[ground_truth::NUM_GROUND_PLANE_POINTS];
00068   bool landmarkAvailable[ground_truth::NUM_GROUND_PLANE_POINTS];
00069   int currentLandmark = 0;
00070   int displayLandmark = 0;
00071   bool newDisplayLandmark;
00072 
00073   bool transformationAvailable = false;
00074   Eigen::Affine3f transformMatrix;
00075   Eigen::Vector4f groundPlaneParameters;
00076 
00077   int queue_size = 1;
00078   std::string calibFile = "data/calib.txt";
00079 
00080   std::string status;
00081   bool stayAlive = true;
00082 
00083   enum State {
00084     COLLECT_GROUND_POINTS,
00085     GET_GROUND_POINT_INFO,
00086     TRANSITION_TO_LANDMARK_COLLECTION,
00087     COLLECT_LANDMARKS,
00088     GET_LANDMARK_INFO,
00089     TRANSFORMATION_CALCULATED,
00090   };
00091 
00092   State state = COLLECT_GROUND_POINTS;
00093 
00094 }
00095 
00100 inline std::string getUniqueName(const std::string &baseName, int uniqueId) {
00101   return baseName + boost::lexical_cast<std::string>(uniqueId);
00102 }
00103 
00107 template <typename T>
00108 void noDelete(T *ptr) {
00109 }
00110 
00114 void displayCloud(pcl_visualization::PCLVisualizer &visualizer, pcl::PointCloud<pcl::PointXYZRGB> &cloudToDisplay) {
00115   
00116   pcl::PointCloud<pcl::PointXYZRGB> displayCloud;
00117 
00118   /* This Filter code is currently there due to some failure for nan points while displaying */
00119 
00120   // Filter to remove NaN points
00121   pcl::PointIndices inliers;
00122   for (unsigned int i = 0; i < cloudToDisplay.points.size(); i++) {
00123     pcl::PointXYZRGB *pt = &cloud.points[i];
00124     if (pcl_isfinite(pt->x))
00125       inliers.indices.push_back(i);
00126   }
00127   pcl::ExtractIndices<pcl::PointXYZRGB> extract;
00128   pcl::PointCloud<pcl::PointXYZRGB>::ConstPtr ptr(&cloudToDisplay, noDelete<pcl::PointCloud<pcl::PointXYZRGB> >);
00129   extract.setInputCloud(ptr);
00130   extract.setIndices(boost::make_shared<pcl::PointIndices>(inliers));
00131   extract.setNegative(false);
00132   extract.filter(displayCloud);
00133 
00134   /* Filter code Ends */
00135 
00136   visualizer.removePointCloud();
00137   colorHandler.reset (new pcl_visualization::PointCloudColorHandlerRGBField<pcl::PointXYZRGB> (displayCloud));
00138   geometryHandler.reset (new pcl_visualization::PointCloudGeometryHandlerXYZ<pcl::PointXYZRGB> (displayCloud));
00139   visualizer.addPointCloud<pcl::PointXYZRGB>(displayCloud, *colorHandler, *geometryHandler);
00140 }
00141 
00145 void calculateTransformation() {
00146   for (int i = 0; i < ground_truth::NUM_GROUND_PLANE_POINTS; i++) {
00147     if (landmarkAvailable[i]) {
00148       rigidBodyTransform.add(landmarkPoints[i], fieldProvider.getGroundPoint(i), 1.0 / (landmarkPoints[i].norm() * landmarkPoints[i].norm()));
00149     }
00150   }
00151   transformMatrix = rigidBodyTransform.getTransformation();
00152   transformationAvailable = true;
00153 
00154   // Output to file
00155   std::ofstream fout(calibFile.c_str());
00156   for (int i = 0; i < 4; i++) {
00157     for (int j = 0; j < 4; j++) {
00158       fout << transformMatrix(i,j) << " ";
00159     }
00160     fout << endl;
00161   }
00162   fout.close();
00163 
00164 }
00165 
00169 void calculateGroundPlane() {
00170   pcl::PointCloud<pcl::PointXYZ> groundPlaneCloud;
00171   pcl::PointIndices inliers;
00172   pcl::NormalEstimation<pcl::PointXYZ,pcl::Normal> normalEstimator;
00173 
00174   for (int i = 0; i < MAX_GROUND_POINTS; i++) {
00175     pcl::PointXYZ point;
00176     point.x = groundPoints[i].x();
00177     point.y = groundPoints[i].y();
00178     point.z = groundPoints[i].z();
00179     groundPlaneCloud.points.push_back(point);
00180     inliers.indices.push_back(i);
00181   }
00182 
00183   float curvature;
00184   normalEstimator.computePointNormal(groundPlaneCloud, inliers.indices, groundPlaneParameters, curvature);
00185 }
00186 
00191 Eigen::Vector3f getPointFromGroundPlane() {
00192   
00193   //Obtain a point and normal for the plane
00194   Eigen::Vector3f c(0,0,-groundPlaneParameters(3)/groundPlaneParameters(2));
00195   Eigen::Vector3f n(groundPlaneParameters(0), groundPlaneParameters(1), groundPlaneParameters(2));
00196 
00197   float t = (c - rayPt1).dot(n) / (rayPt2 - rayPt1).dot(n);
00198   
00199   Eigen::Vector3f point;  
00200   point = rayPt1 + t*(rayPt2 - rayPt1);
00201 
00202   return point;
00203 }
00204 
00208 void displayStatus(const char* format, ...) {
00209 
00210   char buffer[1024];
00211   va_list args;
00212   va_start(args, format);
00213   vsprintf(buffer, format, args);
00214   va_end(args);
00215 
00216   status = std::string(buffer);
00217   //ROS_INFO(buffer);
00218 } 
00219 
00223 void collectRayInfo(int x, int y) {
00224   cv::Point2d origPt(x, y), rectPt;
00225   rectPt = model.rectifyPoint(origPt);
00226   cv::Point3d ray = model.projectPixelTo3dRay(rectPt);
00227   rayPt1 = Eigen::Vector3f(0,0,0);
00228   rayPt2 = Eigen::Vector3f(ray.x, ray.y, ray.z);
00229 } 
00230 
00236 float distanceLineFromPoint(Eigen::Vector3f ep1, Eigen::Vector3f ep2, Eigen::Vector3f point) {
00237   return ((point - ep1).cross(point - ep2)).norm() / (ep2 - ep1).norm();
00238 } 
00239 
00245 bool getPointFromCloud(Eigen::Vector3f &point) {
00246   
00247   unsigned int count = 0;
00248   Eigen::Vector3f averagePt(0, 0, 0);
00249 
00250   for (unsigned int i = 0; i < cloud.points.size(); i++) {
00251 
00252     pcl::PointXYZRGB *pt = &cloud.points[i];
00253 
00254     // Failed Points
00255     if (!pcl_isfinite(pt->x))
00256       continue;
00257 
00258     // Calculate Distance for valid points
00259     Eigen::Vector3f pt2(pt->x, pt->y, pt->z); 
00260     float distance = distanceLineFromPoint(rayPt1, rayPt2, pt2);
00261     if (distance < 0.025) { // within 2.5 cm of the ray
00262       averagePt+= pt2;
00263       count++;
00264     }
00265   }
00266   averagePt /= count;
00267 
00268   point = averagePt;
00269 
00270   return count > 10;
00271 
00272 }
00273 
00280 void imageMouseCallback(int event, int x, int y, int flags, void* param) {
00281 
00282   switch(event) {
00283 
00284     case CV_EVENT_LBUTTONDOWN: {
00285 
00286       switch(state) {
00287         case COLLECT_GROUND_POINTS: {             // Obtain ground point (lclick)
00288           collectRayInfo(x, y);
00289           state = GET_GROUND_POINT_INFO;
00290           break;
00291         }
00292         case TRANSITION_TO_LANDMARK_COLLECTION: {  // 
00293           calculateGroundPlane();
00294           numGroundPoints = 0;
00295           state = COLLECT_LANDMARKS;
00296           fieldProvider.get2dField(selectorImage, currentLandmark);
00297           cvNamedWindow("Selector");
00298           cvMoveWindow("Selector", 10, 700);
00299           cvShowImage("Selector", selectorImage);
00300           displayStatus("Select Landmark (%i of %i) (LClick), Next(RClick), Prev(Ctrl+RClick)", currentLandmark+1, ground_truth::NUM_GROUND_PLANE_POINTS);
00301           break;
00302         }
00303         case COLLECT_LANDMARKS: {
00304           if (flags & CV_EVENT_FLAG_CTRLKEY) {    // Deselect Landmark (ctrl + lclick)
00305             landmarkAvailable[currentLandmark] = false;
00306             newDisplayLandmark = true;
00307           } else {                                // Obtain current landmark (lclick)
00308             collectRayInfo(x, y);
00309             state = GET_LANDMARK_INFO;
00310           }
00311           break;
00312         }
00313         case TRANSFORMATION_CALCULATED: {
00314           stayAlive = false;
00315           break;
00316         }
00317         default:
00318           break;
00319       }
00320 
00321       break; 
00322     }
00323 
00324     case CV_EVENT_RBUTTONDOWN: {
00325 
00326       switch(state) {
00327         case TRANSITION_TO_LANDMARK_COLLECTION:
00328         case COLLECT_GROUND_POINTS: {
00329           numGroundPoints--;
00330           if (numGroundPoints == 0) {
00331             displayStatus("Select Ground Point (%i of %i)", numGroundPoints+1, MAX_GROUND_POINTS);
00332           } else {
00333             displayStatus("Select Ground Point (%i of %i) (LClick), Deselect (RClick)", numGroundPoints+1, MAX_GROUND_POINTS);
00334           }
00335           state = COLLECT_GROUND_POINTS;
00336           break;
00337         }
00338         case COLLECT_LANDMARKS: {
00339           if (flags & CV_EVENT_FLAG_CTRLKEY) {    // Go back to previous landmark (ctrl + rclick)
00340             currentLandmark--;
00341             currentLandmark = (currentLandmark < 0) ? 0 : currentLandmark;
00342             fieldProvider.get2dField(selectorImage, currentLandmark);
00343             cvShowImage("Selector", selectorImage);
00344             displayStatus("Select Landmark (%i of %i) (LClick), Next(RClick), Prev(Ctrl+RClick)", currentLandmark+1, ground_truth::NUM_GROUND_PLANE_POINTS);
00345           } else {                                // Go to next landmark (rclick)
00346             currentLandmark++;
00347             if (currentLandmark == ground_truth::NUM_GROUND_PLANE_POINTS) {
00348               calculateTransformation();
00349               displayStatus("Transformation calculated and saved. Exit (LClick)");
00350               state = TRANSFORMATION_CALCULATED;
00351             } else {
00352               fieldProvider.get2dField(selectorImage, currentLandmark);
00353               cvShowImage("Selector", selectorImage);
00354               displayStatus("Select Landmark (%i of %i) (LClick), Next(RClick), Prev(Ctrl+RClick)", currentLandmark+1, ground_truth::NUM_GROUND_PLANE_POINTS);
00355             }
00356           }
00357           break;
00358         }
00359         default:
00360           break;
00361       }
00362     
00363       break; // outer
00364     }
00365   }
00366 }
00367 
00371 void imageCallback(const sensor_msgs::ImageConstPtr& image,
00372     const sensor_msgs::CameraInfoConstPtr& camInfo) {
00373   ROS_DEBUG("Image received height %i, width %i", image->height, image->width);
00374   mImage.lock();
00375   rgbImage = bridge.imgMsgToCv(image, "bgr8");
00376   model.fromCameraInfo(camInfo);
00377   mImage.unlock();
00378 }
00379 
00383 void cloudCallback (const sensor_msgs::PointCloud2ConstPtr& cloudPtrMsg) {
00384   ROS_DEBUG("PointCloud with %d, %d data points (%s), stamp %f, and frame %s.", cloudPtrMsg->width, cloudPtrMsg->height, pcl::getFieldsList (*cloudPtrMsg).c_str (), cloudPtrMsg->header.stamp.toSec (), cloudPtrMsg->header.frame_id.c_str ()); 
00385   mCloud.lock();
00386   cloudPtr = cloudPtrMsg;
00387   mCloud.unlock();
00388 }
00389 
00390 int main (int argc, char** argv) {
00391 
00392   ros::init (argc, argv, "kinect_position_calibrator");
00393   ros::NodeHandle nh;
00394 
00395   // Get the queue size from the command line
00396   terminal_tools::parse_argument (argc, argv, "-qsize", queue_size);
00397 
00398   terminal_tools::parse_argument (argc, argv, "-calibFile", calibFile);
00399   ROS_INFO("Calib File: %s", calibFile.c_str());
00400 
00401   // Create a ROS subscriber for the point cloud
00402   ros::Subscriber subCloud = nh.subscribe ("input", queue_size, cloudCallback);
00403 
00404   // Subscribe to image using image transport
00405   image_transport::ImageTransport it(nh);
00406   image_transport::CameraSubscriber subImage = it.subscribeCamera("inputImage", 1, imageCallback);
00407 
00408   // Stuff to display the point cloud properly
00409   pcl_visualization::PCLVisualizer visualizer (argc, argv, "Online PointCloud2 Viewer");
00410   visualizer.addCoordinateSystem(); // Good for reference
00411 
00412   // Stuff to display the rgb image
00413   cvStartWindowThread();
00414   cvNamedWindow("ImageCam");
00415   cvMoveWindow("ImageCam", 0,0);
00416   cvSetMouseCallback( "ImageCam", imageMouseCallback);
00417 
00418   // Stuff to display the selector
00419   currentLandmark = 0;
00420   selectorImage = cvCreateImage(cvSize(SELECTOR_IMAGE_WIDTH, SELECTOR_IMAGE_HEIGHT), IPL_DEPTH_8U, 3);
00421   
00422   displayStatus("Select Ground Point (%i of %i) (LClick)", numGroundPoints+1, MAX_GROUND_POINTS);
00423 
00424   while (nh.ok() && stayAlive) {
00425 
00426     // Spin
00427     ros::spinOnce ();
00428     ros::Duration (0.001).sleep();
00429     visualizer.spinOnce (10);
00430 
00431     // If no cloud received yet, continue
00432     if (!cloudPtr)
00433       continue;
00434 
00435     if (cloudPtr == oldCloudPtr)
00436       continue;
00437 
00438     mCloud.lock ();
00439     pcl::fromROSMsg(*cloudPtr, cloud);
00440 
00441     switch (state) {
00442 
00443       case GET_GROUND_POINT_INFO: {
00444         bool pointAvailable = getPointFromCloud(groundPoints[numGroundPoints]);
00445         if (!pointAvailable) {
00446           displayStatus("Unable to get point data. Select Ground Point (%i of %i) (LClick)", numGroundPoints+1, MAX_GROUND_POINTS);
00447           state = COLLECT_GROUND_POINTS;
00448           break;
00449         }
00450         numGroundPoints++;
00451         if (numGroundPoints == MAX_GROUND_POINTS) {
00452           displayStatus("Proceed to Landmark Selection (LClick), Deselect (RClick)", currentLandmark+1, ground_truth::NUM_GROUND_PLANE_POINTS);
00453           state = TRANSITION_TO_LANDMARK_COLLECTION;
00454         } else {
00455           displayStatus("Select Ground Point (%i of %i) (LClick), Deselect (RClick)", numGroundPoints+1, MAX_GROUND_POINTS);
00456           state = COLLECT_GROUND_POINTS; 
00457         }
00458         break;
00459       }
00460 
00461       case GET_LANDMARK_INFO: {
00462         landmarkPoints[currentLandmark] = getPointFromGroundPlane();
00463         landmarkAvailable[currentLandmark] = true;
00464         state = COLLECT_LANDMARKS;
00465         displayStatus("Landmark Info obtained (%i of %i), Redo(LClick), Deselect(Ctrl+LClick), Next(RClick), Prev(Ctrl+RClick)", currentLandmark+1, ground_truth::NUM_GROUND_PLANE_POINTS);
00466         newDisplayLandmark = true;
00467         break;
00468       }
00469 
00470       default:
00471         break;
00472     }
00473 
00474     // Display point cloud
00475     if (transformationAvailable) {
00476       pcl::transformPointCloud(cloud, transformedCloud, transformMatrix);
00477       displayCloud(visualizer, transformedCloud);
00478     } else {
00479       displayCloud(visualizer, cloud);
00480     }
00481 
00482     // Display spheres during ground point selection
00483     if (displayGroundPoints != numGroundPoints) {
00484       // Add necessary spheres
00485       for (; displayGroundPoints < numGroundPoints; displayGroundPoints++) {
00486         pcl::PointXYZ point(groundPoints[displayGroundPoints].x(), groundPoints[displayGroundPoints].y(), groundPoints[displayGroundPoints].z());
00487         visualizer.addSphere(point, 0.05, 0,1,0, getUniqueName("ground", displayGroundPoints));
00488       }
00489       // Remove unnecessary spheres
00490       for (; displayGroundPoints > numGroundPoints; displayGroundPoints--) {
00491         visualizer.removeShape(getUniqueName("ground", displayGroundPoints - 1));
00492       }
00493     }
00494 
00495     // Display spheres during landmark selection
00496     if (displayLandmark != currentLandmark || newDisplayLandmark) {
00497       visualizer.removeShape(getUniqueName("landmark", displayLandmark));
00498       if (currentLandmark != ground_truth::NUM_GROUND_PLANE_POINTS)
00499         displayLandmark = currentLandmark;
00500     }
00501     if (displayLandmark == currentLandmark && landmarkAvailable[displayLandmark] && newDisplayLandmark) {
00502       pcl::PointXYZ point(landmarkPoints[displayLandmark].x(), landmarkPoints[displayLandmark].y(), landmarkPoints[displayLandmark].z());
00503       visualizer.addSphere(point, 0.05, 0,1,0, getUniqueName("landmark", displayLandmark));
00504       newDisplayLandmark = false;
00505     }
00506 
00507     // Use old pointer to prevent redundant display
00508     oldCloudPtr = cloudPtr;
00509     mCloud.unlock();
00510 
00511     mImage.lock();
00512     if (rgbImage) {
00513       cvShowImage("ImageCam", rgbImage);
00514     }
00515     mImage.unlock();
00516 
00517     visualizer.removeShape("status");
00518     visualizer.addText(status, 75, 0, "status");
00519   }
00520 
00521   return (0);
00522 }


ground_truth
Author(s): Piyush Khandelwal
autogenerated on Mon Jan 6 2014 11:54:38