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
00119
00120
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
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
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
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
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
00255 if (!pcl_isfinite(pt->x))
00256 continue;
00257
00258
00259 Eigen::Vector3f pt2(pt->x, pt->y, pt->z);
00260 float distance = distanceLineFromPoint(rayPt1, rayPt2, pt2);
00261 if (distance < 0.025) {
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: {
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) {
00305 landmarkAvailable[currentLandmark] = false;
00306 newDisplayLandmark = true;
00307 } else {
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) {
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 {
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;
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
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
00402 ros::Subscriber subCloud = nh.subscribe ("input", queue_size, cloudCallback);
00403
00404
00405 image_transport::ImageTransport it(nh);
00406 image_transport::CameraSubscriber subImage = it.subscribeCamera("inputImage", 1, imageCallback);
00407
00408
00409 pcl_visualization::PCLVisualizer visualizer (argc, argv, "Online PointCloud2 Viewer");
00410 visualizer.addCoordinateSystem();
00411
00412
00413 cvStartWindowThread();
00414 cvNamedWindow("ImageCam");
00415 cvMoveWindow("ImageCam", 0,0);
00416 cvSetMouseCallback( "ImageCam", imageMouseCallback);
00417
00418
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
00427 ros::spinOnce ();
00428 ros::Duration (0.001).sleep();
00429 visualizer.spinOnce (10);
00430
00431
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
00475 if (transformationAvailable) {
00476 pcl::transformPointCloud(cloud, transformedCloud, transformMatrix);
00477 displayCloud(visualizer, transformedCloud);
00478 } else {
00479 displayCloud(visualizer, cloud);
00480 }
00481
00482
00483 if (displayGroundPoints != numGroundPoints) {
00484
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
00490 for (; displayGroundPoints > numGroundPoints; displayGroundPoints--) {
00491 visualizer.removeShape(getUniqueName("ground", displayGroundPoints - 1));
00492 }
00493 }
00494
00495
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
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 }