tracker-client.cpp
Go to the documentation of this file.
00001 #include <cstdlib>
00002 #include <fstream>
00003 #include <sstream>
00004 #include <stdexcept>
00005 
00006 #include <boost/filesystem.hpp>
00007 #include <boost/filesystem/fstream.hpp>
00008 #include <boost/format.hpp>
00009 #include <boost/optional.hpp>
00010 #include <boost/version.hpp>
00011 
00012 #include <ros/ros.h>
00013 #include <ros/param.h>
00014 #include <dynamic_reconfigure/server.h>
00015 #include <geometry_msgs/PoseWithCovarianceStamped.h>
00016 #include <image_proc/advertisement_checker.h>
00017 #include <image_transport/image_transport.h>
00018 #include <visp_tracker/Init.h>
00019 #include <visp_tracker/ModelBasedSettingsConfig.h>
00020 
00021 #include <visp/vpMe.h>
00022 #include <visp/vpPixelMeterConversion.h>
00023 #include <visp/vpPose.h>
00024 
00025 #define protected public
00026 #include <visp/vpMbEdgeTracker.h>
00027 #include <visp/vpMbKltTracker.h>
00028 #include <visp/vpMbEdgeKltTracker.h>
00029 #undef protected
00030 
00031 #include <visp/vpDisplayX.h>
00032 
00033 #include "conversion.hh"
00034 #include "callbacks.hh"
00035 #include "file.hh"
00036 #include "names.hh"
00037 
00038 #include "tracker-client.hh"
00039 
00040 
00041 namespace visp_tracker
00042 {
00043   TrackerClient::TrackerClient(ros::NodeHandle& nh,
00044                                ros::NodeHandle& privateNh,
00045                                volatile bool& exiting,
00046                                unsigned queueSize)
00047     : exiting_ (exiting),
00048       queueSize_(queueSize),
00049       nodeHandle_(nh),
00050       nodeHandlePrivate_(privateNh),
00051       imageTransport_(nodeHandle_),
00052       image_(),
00053       modelPath_(),
00054       modelName_(),
00055       cameraPrefix_(),
00056       rectifiedImageTopic_(),
00057       cameraInfoTopic_(),
00058       vrmlPath_(),
00059       initPath_(),
00060       cameraSubscriber_(),
00061       mutex_(),
00062       reconfigureSrv_(mutex_, nodeHandlePrivate_),
00063       movingEdge_(),
00064       kltTracker_(),
00065       cameraParameters_(),
00066       tracker_(),
00067       startFromSavedPose_(),
00068       checkInputs_(),
00069       resourceRetriever_()
00070   {
00071     // Parameters.
00072     nodeHandlePrivate_.param<std::string>("model_path", modelPath_,
00073                                           visp_tracker::default_model_path);
00074     nodeHandlePrivate_.param<std::string>("model_name", modelName_, "");
00075 
00076     nodeHandlePrivate_.param<bool>
00077       ("start_from_saved_pose", startFromSavedPose_, false);
00078 
00079     nodeHandlePrivate_.param<bool>
00080       ("confirm_init", confirmInit_, true);
00081 
00082     nodeHandlePrivate_.param<std::string>("tracker_type", trackerType_, "mbt");
00083     if(trackerType_=="mbt")
00084       tracker_ = new vpMbEdgeTracker();
00085     else if(trackerType_=="klt")
00086       tracker_ = new vpMbKltTracker();
00087     else
00088       tracker_ = new vpMbEdgeKltTracker();
00089 
00090     //tracker_->resetTracker(); // TO CHECK
00091 
00092     if (modelName_.empty ())
00093       throw std::runtime_error
00094         ("empty model\n"
00095          "Relaunch the client while setting the model_name parameter, i.e.\n"
00096          "$ rosrun visp_tracker client _model_name:=my-model"
00097          );
00098 
00099     // Compute topic and services names.
00100 
00101     ros::Rate rate (1);
00102     while (cameraPrefix_.empty ())
00103       {
00104         if (!nodeHandle_.getParam ("camera_prefix", cameraPrefix_))
00105           {
00106             ROS_WARN
00107               ("the camera_prefix parameter does not exist.\n"
00108                "This may mean that:\n"
00109                "- the tracker is not launched,\n"
00110                "- the tracker and viewer are not running in the same namespace."
00111                );
00112           }
00113         else if (cameraPrefix_.empty ())
00114           {
00115             ROS_INFO
00116               ("tracker is not yet initialized, waiting...\n"
00117                "You may want to launch the client to initialize the tracker.");
00118           }
00119         if (this->exiting())
00120           return;
00121         rate.sleep ();
00122       }
00123 
00124     rectifiedImageTopic_ =
00125       ros::names::resolve(cameraPrefix_ + "/image_rect");
00126     cameraInfoTopic_ =
00127       ros::names::resolve(cameraPrefix_ + "/camera_info");
00128 
00129     // Check for subscribed topics.
00130     checkInputs();
00131 
00132     // Set callback for dynamic reconfigure.
00133     if(trackerType_!="klt"){
00134       vpMbEdgeTracker* t = dynamic_cast<vpMbEdgeTracker*>(tracker_);
00135       t->setMovingEdge(movingEdge_);
00136     }
00137     
00138     if(trackerType_!="mbt"){
00139       vpMbKltTracker* t = dynamic_cast<vpMbKltTracker*>(tracker_);
00140       t->setKltOpencv(kltTracker_);
00141     }
00142     
00143     // Dynamic reconfigure.
00144     reconfigureSrv_t::CallbackType f =
00145       boost::bind(&reconfigureCallback, boost::ref(tracker_),
00146                   boost::ref(image_), boost::ref(movingEdge_), boost::ref(kltTracker_),
00147                   boost::ref(trackerType_), boost::ref(mutex_), _1, _2);
00148     reconfigureSrv_.setCallback(f);
00149 
00150     // Camera subscriber.
00151     cameraSubscriber_ = imageTransport_.subscribeCamera
00152       (rectifiedImageTopic_, queueSize_,
00153        bindImageCallback(image_, header_, info_));
00154 
00155     // Model loading.
00156     vrmlPath_ = getModelFileFromModelName(modelName_, modelPath_);
00157     initPath_ = getInitFileFromModelName(modelName_, modelPath_);
00158 
00159     ROS_INFO_STREAM("VRML file: " << vrmlPath_);
00160     ROS_INFO_STREAM("Init file: " << initPath_);
00161 
00162     // Check that required files exist.
00163     // if (!boost::filesystem::is_regular_file(vrmlPath_))
00164     //   {
00165     //  boost::format fmt("VRML model %1% is not a regular file");
00166     //  fmt % vrmlPath_;
00167     //  throw std::runtime_error(fmt.str());
00168     //   }
00169     // if (!boost::filesystem::is_regular_file(initPath_))
00170     //   {
00171     //  boost::format fmt("Initialization file %1% is not a regular file");
00172     //  fmt % initPath_;
00173     //  throw std::runtime_error(fmt.str());
00174     //   }
00175 
00176     // Load the 3d model.
00177     loadModel();
00178 
00179     // Wait for the image to be initialized.
00180     waitForImage();
00181     if (this->exiting())
00182       return;
00183     if (!image_.getWidth() || !image_.getHeight())
00184       throw std::runtime_error("failed to retrieve image");
00185 
00186     // Tracker initialization.
00187     // - Camera
00188     initializeVpCameraFromCameraInfo(cameraParameters_, info_);
00189     tracker_->setCameraParameters(cameraParameters_);
00190     tracker_->setDisplayFeatures(true);
00191 
00192     // - Moving edges.
00193     movingEdge_.initMask();
00194     if(trackerType_!="klt"){
00195       vpMbEdgeTracker* t = dynamic_cast<vpMbEdgeTracker*>(tracker_);
00196       t->setMovingEdge(movingEdge_);
00197     }
00198     
00199     if(trackerType_!="mbt"){
00200       vpMbKltTracker* t = dynamic_cast<vpMbKltTracker*>(tracker_);
00201       t->setKltOpencv(kltTracker_);
00202     }
00203 
00204     // Display camera parameters and moving edges settings.
00205     ROS_INFO_STREAM(cameraParameters_);
00206     movingEdge_.print();
00207   }
00208 
00209   void
00210   TrackerClient::checkInputs()
00211   {
00212     ros::V_string topics;
00213     topics.push_back(rectifiedImageTopic_);
00214     topics.push_back(cameraInfoTopic_);
00215     checkInputs_.start(topics, 60.0);
00216   }
00217 
00218   void
00219   TrackerClient::spin()
00220   {
00221     boost::format fmtWindowTitle ("ViSP MBT tracker initialization - [ns: %s]");
00222     fmtWindowTitle % ros::this_node::getNamespace ();
00223 
00224     vpDisplayX d(image_, image_.getWidth(), image_.getHeight(),
00225                  fmtWindowTitle.str().c_str());
00226 
00227     ros::Rate loop_rate_tracking(200);
00228     bool ok = false;
00229     vpHomogeneousMatrix cMo;
00230     vpImagePoint point (10, 10);
00231     while (!ok && !exiting())
00232       {
00233         try
00234           {
00235             // Initialize.
00236             vpDisplay::display(image_);
00237             vpDisplay::flush(image_);
00238             if (!startFromSavedPose_)
00239               init();
00240             else
00241               {
00242                 cMo = loadInitialPose();
00243                 startFromSavedPose_ = false;
00244         tracker_->initFromPose(image_, cMo);
00245               }
00246         tracker_->getPose(cMo);
00247 
00248             ROS_INFO_STREAM("initial pose [tx,ty,tz,tux,tuy,tuz]:\n"
00249                             << vpPoseVector(cMo));
00250 
00251             // Track once to make sure initialization is correct.
00252             if (confirmInit_)
00253               {
00254                 vpImagePoint ip;
00255                 vpMouseButton::vpMouseButtonType button =
00256                   vpMouseButton::button1;
00257                 do
00258                   {
00259                     vpDisplay::display(image_);
00260             tracker_->track(image_);
00261             tracker_->display(image_, cMo, cameraParameters_,
00262                                      vpColor::red, 2);
00263                     vpDisplay::displayCharString
00264                       (image_, point, "tracking, click to initialize tracker",
00265                        vpColor::red);
00266                     vpDisplay::flush(image_);
00267             tracker_->getPose(cMo);
00268 
00269                     ros::spinOnce();
00270                     loop_rate_tracking.sleep();
00271                     if (exiting())
00272                       return;
00273                   }
00274                 while(!vpDisplay::getClick(image_, ip, button, false));
00275                 ok = true;
00276               }
00277             else
00278               ok = true;
00279           }
00280         catch(const std::runtime_error& e)
00281           {
00282             ROS_ERROR_STREAM("failed to initialize: "
00283                              << e.what() << ", retrying...");
00284           }
00285         catch(const std::string& str)
00286           {
00287             ROS_ERROR_STREAM("failed to initialize: "
00288                              << str << ", retrying...");
00289           }
00290         catch(...)
00291           {
00292             ROS_ERROR("failed to initialize, retrying...");
00293           }
00294       }
00295 
00296     ROS_INFO_STREAM("Initialization done, sending initial cMo:\n" << cMo);
00297     try
00298       {
00299         sendcMo(cMo);
00300       }
00301     catch(std::exception& e)
00302       {
00303         ROS_ERROR_STREAM("failed to send cMo: " << e.what ());
00304       }
00305     catch(...)
00306       {
00307         ROS_ERROR("unknown error happened while sending the cMo, retrying...");
00308       }
00309   }
00310   
00311   TrackerClient::~TrackerClient()
00312   {
00313     delete tracker_;
00314   }
00315 
00316   void
00317   TrackerClient::sendcMo(const vpHomogeneousMatrix& cMo)
00318   {
00319     ros::ServiceClient client =
00320       nodeHandle_.serviceClient<visp_tracker::Init>(visp_tracker::init_service);
00321     visp_tracker::Init srv;
00322 
00323     // Load the model and send it to the parameter server.
00324     std::string modelDescription = fetchResource
00325       (getModelFileFromModelName (modelName_, modelPath_));
00326     nodeHandle_.setParam (model_description_param, modelDescription);
00327 
00328     vpHomogeneousMatrixToTransform(srv.request.initial_cMo, cMo);
00329     
00330     if(trackerType_!="klt"){
00331       convertVpMeToInitRequest(movingEdge_, tracker_, srv);
00332     }
00333     
00334     if(trackerType_!="mbt"){
00335       convertVpKltOpencvToInitRequest(kltTracker_, tracker_, srv);
00336     }
00337 
00338     ros::Rate rate (1);
00339     while (!client.waitForExistence ())
00340       {
00341         ROS_INFO
00342           ("Waiting for the initialization service to become available.");
00343         rate.sleep ();
00344       }
00345 
00346     if (client.call(srv))
00347       {
00348         if (srv.response.initialization_succeed)
00349           ROS_INFO("Tracker initialized with success.");
00350         else
00351           throw std::runtime_error("failed to initialize tracker.");
00352       }
00353     else
00354       throw std::runtime_error("failed to call service init");
00355   }
00356 
00357   void
00358   TrackerClient::loadModel()
00359   {
00360     try
00361       {
00362         ROS_DEBUG_STREAM("Trying to load the model "
00363                          << vrmlPath_.native());
00364 
00365         std::string modelPath;
00366         boost::filesystem::ofstream modelStream;
00367         if (!makeModelFile(modelStream,
00368                            vrmlPath_.native(),
00369                            modelPath))
00370           throw std::runtime_error ("failed to retrieve model");
00371 
00372     tracker_->loadModel(modelPath.c_str());
00373         ROS_INFO("VRML model has been successfully loaded.");
00374 
00375   if(trackerType_=="mbt"){
00376     vpMbEdgeTracker* t = dynamic_cast<vpMbEdgeTracker*>(tracker_);
00377     ROS_DEBUG_STREAM("Nb faces: "
00378                      << t->getFaces().getPolygon().size());
00379     ROS_DEBUG_STREAM("Nb visible faces: " << t->getFaces().getNbVisiblePolygon());
00380 
00381     std::list<vpMbtDistanceLine *> linesList;
00382     t->getLline(linesList);
00383     ROS_DEBUG_STREAM("Nb line: " << linesList.size());
00384     ROS_DEBUG_STREAM("nline: " << t->nline);
00385   }
00386   else if(trackerType_=="klt"){
00387     vpMbKltTracker* t = dynamic_cast<vpMbKltTracker*>(tracker_);
00388     ROS_DEBUG_STREAM("Nb faces: "
00389                      << t->getFaces().getPolygon().size());
00390     ROS_DEBUG_STREAM("Nb visible faces: " << t->getFaces().getNbVisiblePolygon());
00391     ROS_DEBUG_STREAM("Nb KLT points: " << t->getNbKltPoints());
00392   }
00393   else {
00394     vpMbEdgeKltTracker* t = dynamic_cast<vpMbEdgeKltTracker*>(tracker_);
00395     ROS_DEBUG_STREAM("Nb hidden faces: "
00396                      << t->getFaces().getPolygon().size());
00397     ROS_DEBUG_STREAM("Nb visible faces: " << t->getFaces().getNbVisiblePolygon());
00398     ROS_DEBUG_STREAM("Nb KLT points: " << t->getNbKltPoints());
00399 
00400     std::list<vpMbtDistanceLine *> linesList;
00401     t->getLline(linesList);
00402     ROS_DEBUG_STREAM("Nb line: " << linesList.size());
00403     ROS_DEBUG_STREAM("nline: " << t->nline);
00404   }
00405       }
00406     catch(...)
00407       {
00408         boost::format fmt
00409           ("Failed to load the model %1%\n"
00410            "Do you use resource_retriever syntax?\n"
00411            "I.e. replace /my/model/path by file:///my/model/path");
00412         fmt % vrmlPath_;
00413         throw std::runtime_error(fmt.str());
00414       }
00415   }
00416 
00417   vpHomogeneousMatrix
00418   TrackerClient::loadInitialPose()
00419   {
00420     vpHomogeneousMatrix cMo;
00421     cMo.eye();
00422 
00423     std::string initialPose =
00424       getInitialPoseFileFromModelName (modelName_, modelPath_);
00425     std::string resource;
00426     try
00427       {
00428         resource = fetchResource (initialPose);
00429       }
00430     catch (...)
00431       {
00432         ROS_WARN_STREAM
00433           ("failed to retrieve initial pose: " << initialPose << "\n"
00434            << "using identity as initial pose");
00435         return cMo;
00436       }
00437     std::stringstream file;
00438     file << resource;
00439 
00440     if (!file.good())
00441       {
00442         ROS_WARN_STREAM("failed to load initial pose: " << initialPose << "\n"
00443                         << "using identity as initial pose");
00444         return cMo;
00445       }
00446 
00447     vpPoseVector pose;
00448     for (unsigned i = 0; i < 6; ++i)
00449       if (file.good())
00450         file >> pose[i];
00451       else
00452         {
00453           ROS_WARN("failed to parse initial pose file");
00454           return cMo;
00455         }
00456     cMo.buildFrom(pose);
00457     return cMo;
00458   }
00459 
00460   void
00461   TrackerClient::saveInitialPose(const vpHomogeneousMatrix& cMo)
00462   {
00463     boost::filesystem::path initialPose =
00464       getInitialPoseFileFromModelName(modelName_, modelPath_);
00465     boost::filesystem::ofstream file(initialPose);
00466     if (!file.good())
00467       {
00468         ROS_WARN_STREAM
00469           ("failed to save initial pose: " << initialPose
00470            <<"\n"
00471            <<"Note: this is normal is you use a remote resource."
00472            <<"\n"
00473            <<"I.e. your model path starts with http://, package://, etc.");
00474         return;
00475       }
00476     vpPoseVector pose;
00477     pose.buildFrom(cMo);
00478     file << pose;
00479   }
00480 
00481   TrackerClient::points_t
00482   TrackerClient::loadInitializationPoints()
00483   {
00484     points_t points;
00485 
00486     std::string init =
00487       getInitFileFromModelName(modelName_, modelPath_);
00488     std::string resource = fetchResource(init);
00489     std::stringstream file;
00490     file << resource;
00491 
00492     if (!file.good())
00493       {
00494         boost::format fmt("failed to load initialization points: %1");
00495         fmt % init;
00496         throw std::runtime_error(fmt.str());
00497       }
00498 
00499     unsigned npoints = 0;
00500     file >> npoints;
00501     if (!file.good())
00502       throw std::runtime_error("failed to read initialization file");
00503 
00504     double X = 0., Y = 0., Z = 0.;
00505     vpPoint point;
00506     for (unsigned i = 0; i < npoints; ++i)
00507       {
00508         if (!file.good())
00509           throw std::runtime_error("failed to read initialization file");
00510         file >> X >> Y >> Z;
00511         point.setWorldCoordinates(X,Y,Z);
00512         points.push_back(point);
00513       }
00514     return points;
00515   }
00516 
00517   bool
00518   TrackerClient::validatePose(const vpHomogeneousMatrix &cMo){
00519     ros::Rate loop_rate(200);
00520     vpImagePoint ip;
00521     vpMouseButton::vpMouseButtonType button = vpMouseButton::button1;
00522 
00523     vpDisplay::display(image_);
00524     tracker_->display(image_, cMo, cameraParameters_, vpColor::green);
00525     vpDisplay::displayFrame(image_, cMo, cameraParameters_,0.05, vpColor::green);
00526     vpDisplay::displayCharString(image_, 15, 10,
00527         "left click to validate, right click to modify initial pose",
00528         vpColor::red);
00529     vpDisplay::flush(image_);
00530 
00531     do
00532     {
00533       ros::spinOnce();
00534       loop_rate.sleep();
00535       if (!ros::ok())
00536         return false;
00537     }
00538     while(ros::ok() && !vpDisplay::getClick(image_, ip, button, false));
00539 
00540     if(button == vpMouseButton::button1)
00541       return true;
00542 
00543     return false;
00544   }
00545 
00546   void
00547   TrackerClient::init()
00548   {
00549     ros::Rate loop_rate(200);
00550     vpHomogeneousMatrix cMo;
00551     vpImagePoint point (10, 10);
00552 
00553     cMo = loadInitialPose();
00554 
00555     // Show last cMo.
00556     vpImagePoint ip;
00557     vpMouseButton::vpMouseButtonType button = vpMouseButton::button1;
00558 
00559     if(validatePose(cMo))
00560     {
00561       tracker_->initFromPose(image_, cMo);
00562       return;
00563     }
00564 
00565     points_t points = loadInitializationPoints();
00566     imagePoints_t imagePoints;
00567 
00568     bool done = false;
00569     while(!done){
00570       vpDisplay::display(image_);
00571       vpDisplay::flush(image_);
00572 
00573       imagePoints.clear();
00574       for(unsigned i = 0; i < points.size(); ++i)
00575       {
00576         do
00577         {
00578           ros::spinOnce();
00579           loop_rate.sleep();
00580           if (!ros::ok())
00581             return;
00582         }
00583         while(ros::ok() && !vpDisplay::getClick(image_, ip, button, false));
00584 
00585         imagePoints.push_back(ip);
00586         vpDisplay::displayCross(image_, imagePoints.back(), 5, vpColor::green);
00587         vpDisplay::flush(image_);
00588       }
00589 
00590       tracker_->initFromPoints(image_,imagePoints,points);
00591       tracker_->getPose(cMo);
00592       if(validatePose(cMo))
00593         done = true;
00594     }
00595     tracker_->initFromPose(image_, cMo);
00596     saveInitialPose(cMo);
00597   }
00598 
00599   void
00600   TrackerClient::initPoint(unsigned& i,
00601                            points_t& points,
00602                            imagePoints_t& imagePoints,
00603                            ros::Rate& rate,
00604                            vpPose& pose)
00605   {
00606     vpImagePoint ip;
00607     double x = 0., y = 0.;
00608     boost::format fmt("click on point %u/%u");
00609     fmt % (i + 1) % points.size(); // list points from 1 to n.
00610 
00611     // Click on the point.
00612     vpMouseButton::vpMouseButtonType button = vpMouseButton::button1;
00613     do
00614       {
00615         vpDisplay::display(image_);
00616         vpDisplay::displayCharString
00617           (image_, 15, 10,
00618            fmt.str().c_str(),
00619            vpColor::red);
00620 
00621         for (unsigned j = 0; j < imagePoints.size(); ++j)
00622           vpDisplay::displayCross(image_, imagePoints[j], 5, vpColor::green);
00623 
00624         vpDisplay::flush(image_);
00625         ros::spinOnce();
00626         rate.sleep();
00627         if (exiting())
00628           return;
00629       }
00630     while(!vpDisplay::getClick(image_, ip, button, false));
00631 
00632     imagePoints.push_back(ip);
00633     vpPixelMeterConversion::convertPoint(cameraParameters_, ip, x, y);
00634     points[i].set_x(x);
00635     points[i].set_y(y);
00636     pose.addPoint(points[i]);
00637   }
00638 
00639   void
00640   TrackerClient::waitForImage()
00641   {
00642     ros::Rate loop_rate(10);
00643     while (!exiting()
00644            && (!image_.getWidth() || !image_.getHeight()))
00645       {
00646         ROS_INFO_THROTTLE(1, "waiting for a rectified image...");
00647         ros::spinOnce();
00648         loop_rate.sleep();
00649       }
00650   }
00651 
00652   std::string
00653   TrackerClient::fetchResource(const std::string& s)
00654   {
00655     resource_retriever::MemoryResource resource =
00656       resourceRetriever_.get(s);
00657     std::string result;
00658     result.resize(resource.size);
00659     unsigned i = 0;
00660     for (; i < resource.size; ++i)
00661       result[i] = resource.data.get()[i];
00662     return result;
00663   }
00664 
00665   bool
00666   TrackerClient::makeModelFile(boost::filesystem::ofstream& modelStream,
00667                                const std::string& resourcePath,
00668                                std::string& fullModelPath)
00669   {
00670     resource_retriever::MemoryResource resource =
00671       resourceRetriever_.get(resourcePath);
00672     std::string result;
00673     result.resize(resource.size);
00674     unsigned i = 0;
00675     for (; i < resource.size; ++i)
00676       result[i] = resource.data.get()[i];
00677     result[resource.size];
00678 
00679     char* tmpname = strdup("/tmp/tmpXXXXXX");
00680     if (mkdtemp(tmpname) == NULL)
00681       {
00682         ROS_ERROR_STREAM
00683           ("Failed to create the temporary directory: " << strerror(errno));
00684         return false;
00685       }
00686     boost::filesystem::path path(tmpname);
00687     path /= "model.wrl";
00688     free(tmpname);
00689 
00690     fullModelPath = path.native();
00691 
00692     modelStream.open(path);
00693     if (!modelStream.good())
00694       {
00695         ROS_ERROR_STREAM
00696           ("Failed to create the temporary file: " << path);
00697         return false;
00698       }
00699     modelStream << result;
00700     modelStream.flush();
00701     return true;
00702   }
00703 } // end of namespace visp_tracker.


visp_tracker
Author(s): Thomas Moulard
autogenerated on Mon Oct 6 2014 08:40:35