urdf_filtered_tracker.cpp
Go to the documentation of this file.
00001 /* 
00002  * Copyright (c) 2011, Nico Blodow <blodow@cs.tum.edu>, Matteo Saveriano
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 Intelligent Autonomous Systems Group/
00014  *       Technische Universitaet Muenchen nor the names of its contributors 
00015  *       may be used to endorse or promote products derived from this software 
00016  *       without specific prior written permission.
00017  * 
00018  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00019  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00020  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00021  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00022  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00023  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00024  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00025  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00026  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00027  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00028  * POSSIBILITY OF SUCH DAMAGE.
00029  */
00030 
00031 #include "realtime_urdf_filter/urdf_filter.h"
00032 #include "realtime_urdf_filter/urdf_renderer.h"
00033 
00034 #include <ros/node_handle.h>
00035 #include <ros/package.h>
00036 #include <tf/transform_broadcaster.h>
00037 #include <tf/tfMessage.h>
00038 
00039 #include <std_msgs/String.h>
00040 
00041 #include <sstream>
00042 
00043 #include <XnOpenNI.h>
00044 #include <XnCodecIDs.h>
00045 #include <XnCppWrapper.h>
00046 #include <XnPropNames.h>
00047 
00048 #define CHECK_RC(nRetVal, what)                                                                         \
00049         if (nRetVal != XN_STATUS_OK)                                                                    \
00050         {                                                                                                                               \
00051                 printf("%s failed: %s\n", what, xnGetStatusString(nRetVal));\
00052         }
00053                 // TODO: return nRetVal;                                                                                        
00054 
00055 class OpenNITrackerLoopback
00056 {
00057 public:
00058   OpenNITrackerLoopback (ros::NodeHandle &nh, int argc, char **argv)
00059     : g_bNeedPose (false)
00060     , nh_ (nh)
00061     , argc_ (argc), argv_(argv)
00062   {
00063     g_strPose[0] = 0x0;
00064     XnStatus nRetVal = XN_STATUS_OK;
00065 
00066     xn::EnumerationErrors errors;
00067     // TODO: Matteo had: "/opt/ros/diamondback/ros/BodyTracker/SamplesConfig.xml"
00068     std::string configFilename = ros::package::getPath("realtime_urdf_filter") + "/include/SamplesConfig.xml";
00069     nRetVal = g_Context.InitFromXmlFile(configFilename.c_str(), g_scriptNode, &errors);
00070     if (nRetVal == XN_STATUS_NO_NODE_PRESENT)
00071     {
00072       XnChar strError[1024];
00073       errors.ToString(strError, 1024);
00074       printf("%s\n", strError);
00075       // TODO: return (nRetVal);
00076     }
00077     else if (nRetVal != XN_STATUS_OK)
00078     {
00079       printf("Open failed: %s\n", xnGetStatusString(nRetVal));
00080       // TODO: return (nRetVal);
00081     }
00082 
00083     nRetVal = g_Context.FindExistingNode(XN_NODE_TYPE_DEPTH, g_DepthGenerator);
00084     // TODO: can we remove this?
00085     if (nRetVal != XN_STATUS_OK)
00086     {
00087       printf("No depth generator found. Using a default one...");
00088       xn::MockDepthGenerator _mockDepth;
00089       nRetVal = mockDepth.Create(g_Context);
00090       CHECK_RC(nRetVal, "Create mock depth");
00091 
00092       // set some defaults
00093       XnMapOutputMode defaultMode;
00094       defaultMode.nXRes = 320;
00095       defaultMode.nYRes = 240;
00096       defaultMode.nFPS = 30;
00097       nRetVal = mockDepth.SetMapOutputMode(defaultMode);
00098       CHECK_RC(nRetVal, "set default mode");
00099 
00100       // set FOV
00101       XnFieldOfView fov;
00102       fov.fHFOV = 1.0225999419141749;
00103       fov.fVFOV = 0.79661567681716894;
00104       nRetVal = mockDepth.SetGeneralProperty(XN_PROP_FIELD_OF_VIEW, sizeof(fov), &fov);
00105       CHECK_RC(nRetVal, "set FOV");
00106 
00107       XnUInt32 nDataSize = defaultMode.nXRes * defaultMode.nYRes * sizeof(XnDepthPixel);
00108       XnDepthPixel* pData = (XnDepthPixel*)xnOSCallocAligned(nDataSize, 1, XN_DEFAULT_MEM_ALIGN);
00109 
00110       nRetVal = mockDepth.SetData(1, 0, nDataSize, pData);
00111       CHECK_RC(nRetVal, "set empty depth map");
00112 
00113       g_DepthGenerator = mockDepth;
00114     }
00115 
00116     // by Heresy, create mock node
00117     mockDepth.CreateBasedOn( g_DepthGenerator, "mock-depth" );
00118     g_DepthGenerator.GetMetaData(depthMD);
00119 
00120     // by Heresy, create an user generator fot mock depth node
00121     xn::Query xQuery;
00122     xQuery.AddNeededNode( "mock-depth" );
00123     nRetVal = g_UserGenerator.Create( g_Context, &xQuery );
00124     CHECK_RC(nRetVal, "Find user generator");
00125 
00126     XnCallbackHandle hUserCallbacks, hCalibrationComplete, hPoseDetected;
00127     if (!g_UserGenerator.IsCapabilitySupported(XN_CAPABILITY_SKELETON))
00128     {
00129       printf("Supplied user generator doesn't support skeleton\n");
00130       // TODO: return 1;
00131     }
00132     nRetVal = g_UserGenerator.RegisterUserCallbacks(User_NewUser, User_LostUser, this, hUserCallbacks);
00133     CHECK_RC(nRetVal, "Register to user callbacks");
00134     nRetVal = g_UserGenerator.GetSkeletonCap().RegisterToCalibrationComplete(UserCalibration_CalibrationComplete, this, hCalibrationComplete);
00135     CHECK_RC(nRetVal, "Register to calibration complete");
00136 
00137     if (g_UserGenerator.GetSkeletonCap().NeedPoseForCalibration())
00138     {
00139       g_bNeedPose = TRUE;
00140       if (!g_UserGenerator.IsCapabilitySupported(XN_CAPABILITY_POSE_DETECTION))
00141       {
00142         printf("Pose required, but not supported\n");
00143         // TODO: return 1;
00144       }
00145       nRetVal = g_UserGenerator.GetPoseDetectionCap().RegisterToPoseDetected(UserPose_PoseDetected, this, hPoseDetected);
00146       CHECK_RC(nRetVal, "Register to Pose Detected");
00147       g_UserGenerator.GetSkeletonCap().GetCalibrationPose(g_strPose);
00148     }
00149 
00150     g_UserGenerator.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);
00151 
00152     nRetVal = g_Context.StartGeneratingAll();
00153     CHECK_RC(nRetVal, "StartGenerating");
00154 
00155     tf_pub_ = nh_.advertise<tf::tfMessage> ("/tf_human", 1);
00156     event_pub_ = nh_.advertise<std_msgs::String> ("user_event", 1);
00157 
00158     setupURDFSelfFilter ();
00159   }
00160 
00161   void setupURDFSelfFilter ()
00162   {
00163     filter = new realtime_urdf_filter::RealtimeURDFFilter (nh_, argc_, argv_);
00164     filter->width_ = 640;
00165     filter->height_ = 480;
00166     filter->initGL ();
00167   }
00168 
00169   ~OpenNITrackerLoopback ()
00170   {
00171     g_scriptNode.Release();
00172     g_DepthGenerator.Release();
00173     mockDepth.Release();
00174     g_UserGenerator.Release();
00175     g_Player.Release();
00176     g_Context.Release();
00177   }
00178 
00179   void runOnce (void)
00180   {
00181     xn::SceneMetaData sceneMD;
00182     xn::DepthMetaData depthMD_;
00183 
00184     // Read next available data
00185     // by Heresy, wait original depth generator
00186     g_Context.WaitOneUpdateAll( g_DepthGenerator );
00187 
00188     // Process the data
00189     g_DepthGenerator.GetMetaData (depthMD_);
00190     g_DepthGenerator.GetMetaData (depthMD);
00191     depthMD_.MakeDataWritable();
00192     xn::DepthMap& depthMap_ = depthMD_.WritableDepthMap();
00193     xn::DepthMap& depthMap = depthMD.WritableDepthMap();
00194 
00195     static float *buffer = 0;
00196     if (buffer == 0)
00197     {
00198       std::cout << "(re)allocating depth buffer" << std::endl;
00199       buffer = (float*) malloc (depthMap.XRes() * depthMap.YRes() * sizeof(float));
00200     }
00201     for (XnUInt y = 0; y < depthMap.YRes(); y++)
00202     {
00203       for (XnUInt x = 0; x < depthMap.XRes(); x++)
00204       {
00205         buffer [x + y * depthMap.XRes()] = depthMap (depthMap.XRes() - x - 1, y) * 0.001;
00206       }
00207     }
00208 
00209     float P[12];
00210     P[0] = 585.260; P[1] = 0.0;     P[2]  = 317.387; P[3]  = 0.0;
00211     P[4] = 0.0;     P[5] = 585.028; P[6]  = 239.264; P[7]  = 0.0;
00212     P[8] = 0.0;     P[9] = 0.0;     P[10] = 1.0;     P[11] = 0.0;
00213   
00214     double fx = P[0];
00215     double fy = P[5];
00216     double cx = P[2];
00217     double cy = P[6];
00218     double far_plane_ = 8;
00219     double near_plane_ = 0.1;
00220   
00221     double glTf[16];
00222     for (unsigned int i = 0; i < 16; ++i)
00223       glTf[i] = 0.0;
00224 
00225     // calculate the projection matrix
00226     // NOTE: this minus is there to flip the x-axis of the image.
00227     glTf[0]= -2.0 * fx / depthMap.XRes();
00228     glTf[5]= 2.0 * fy / depthMap.YRes();
00229 
00230     glTf[8]= 2.0 * (0.5 - cx / depthMap.XRes());
00231     glTf[9]= 2.0 * (cy / depthMap.YRes() - 0.5);
00232 
00233     glTf[10]= - (far_plane_ + near_plane_) / (far_plane_ - near_plane_);
00234     glTf[14]= -2.0 * far_plane_ * near_plane_ / (far_plane_ - near_plane_);
00235 
00236     glTf[11]= -1;
00237 
00238 
00239     filter->filter ((unsigned char*)buffer, glTf, depthMap.XRes(), depthMap.YRes());
00240 
00241     GLfloat* masked_depth = filter->getMaskedDepth();
00242 
00243     for (XnUInt y = 0; y < depthMap.YRes(); y++)
00244     {
00245       for (XnUInt x = 0; x < depthMap.XRes(); x++)
00246       {
00247           depthMap_(x, y) = XnDepthPixel (masked_depth[x + y * depthMap.XRes()] * 1000); 
00248       }
00249     }
00250 
00251     mockDepth.SetData(depthMD_);
00252 
00253         publishTransforms (std::string("openni_depth_frame"));
00254   }
00255 
00256   void publishEvent (std::string msg, XnUserID nId)
00257   {
00258       std::stringstream ss;
00259       ss << msg << " " << nId;
00260       std_msgs::String out;
00261       out.data = ss.str();
00262       event_pub_.publish (out);
00263   }
00264 
00265   static void XN_CALLBACK_TYPE UserPose_PoseDetected(xn::PoseDetectionCapability& capability, const XnChar* strPose, XnUserID nId, void* pCookie)
00266   {
00267     OpenNITrackerLoopback* self = (OpenNITrackerLoopback*) pCookie;
00268     self->g_UserGenerator.GetPoseDetectionCap().StopPoseDetection (nId);
00269     self->g_UserGenerator.GetSkeletonCap().RequestCalibration (nId, true);
00270     self->publishEvent ("calibrating", nId);
00271   }
00272 
00273   static void XN_CALLBACK_TYPE UserCalibration_CalibrationEnd (xn::SkeletonCapability& capability, XnUserID nId, XnBool bSuccess, void* pCookie)
00274   {
00275     OpenNITrackerLoopback* self = (OpenNITrackerLoopback*) pCookie;
00276     if (bSuccess)
00277     {
00278       self->g_UserGenerator.GetSkeletonCap().StartTracking (nId);
00279       self->publishEvent ("tracking", nId);
00280     }
00281     else
00282     {
00283       if (self->g_bNeedPose)
00284       {
00285         self->g_UserGenerator.GetPoseDetectionCap().StartPoseDetection (self->g_strPose, nId);
00286         self->publishEvent ("posedetection", nId);
00287       }
00288       else
00289       {
00290         self->g_UserGenerator.GetSkeletonCap().RequestCalibration (nId, true);
00291         self->publishEvent ("calibrating", nId);
00292       }
00293     }
00294   }
00295 
00296   // Callback: An existing user was lost
00297   static void XN_CALLBACK_TYPE User_LostUser(xn::UserGenerator& generator, XnUserID nId, void* pCookie)
00298   {
00299     OpenNITrackerLoopback* self = (OpenNITrackerLoopback*) pCookie;
00300     XnUInt32 epochTime = 0;
00301     xnOSGetEpochTime(&epochTime);
00302     printf("%d Lost user %d\n", epochTime, nId);        
00303     self->publishEvent ("lost", nId);
00304   }
00305 
00306   // Callback: New user was detected
00307   static void XN_CALLBACK_TYPE User_NewUser(xn::UserGenerator& generator, XnUserID nId, void* pCookie)
00308   {
00309     OpenNITrackerLoopback* self = (OpenNITrackerLoopback*) pCookie;
00310     if(nId > 15) 
00311     {   
00312       XnStatus res = self->g_UserGenerator.GetPoseDetectionCap().StopPoseDetection(nId);
00313       if (res != XN_CALIBRATION_STATUS_OK)
00314       { 
00315         std::cout << "Error StopPoseDetection";
00316       }
00317       res = self->g_UserGenerator.GetSkeletonCap().AbortCalibration(nId);
00318       if (res != XN_CALIBRATION_STATUS_OK)
00319       { 
00320         std::cout << "Error AbortCalibration";
00321       }
00322       res = self->g_UserGenerator.GetSkeletonCap().StopTracking(nId);
00323       if (res != XN_CALIBRATION_STATUS_OK)
00324       { 
00325         std::cout << "Error StopTracking";
00326       }
00327       self->g_UserGenerator.GetPoseDetectionCap().Release();
00328       self->g_UserGenerator.GetSkeletonCap().Release();
00329       return;
00330     }
00331     // New user found
00332     XnUInt32 epochTime = 0;
00333     xnOSGetEpochTime(&epochTime);
00334     printf("%d New User %d\n", epochTime, nId);
00335     self->publishEvent ("new", nId);
00336 
00337     if (self->g_bNeedPose)
00338     {
00339       std::cerr << "starting pose deteciton: " << __FILE__ << " : " << __LINE__ << std::endl;
00340       self->g_UserGenerator.GetPoseDetectionCap().StartPoseDetection(self->g_strPose, nId);
00341       self->publishEvent ("posedetection", nId);
00342     }
00343     else
00344     {
00345       std::cerr << "request calibration: " << __FILE__ << " : " << __LINE__ << std::endl;
00346       self->g_UserGenerator.GetSkeletonCap().RequestCalibration(nId, TRUE);
00347       self->publishEvent ("calibrating", nId);
00348     }
00349   }
00350 
00351   // Callback: Finished calibration
00352   static void XN_CALLBACK_TYPE UserCalibration_CalibrationComplete (xn::SkeletonCapability& capability, XnUserID nId, XnCalibrationStatus eStatus, void* pCookie)
00353   {
00354     XnUInt32 epochTime = 0;
00355     xnOSGetEpochTime(&epochTime);
00356     OpenNITrackerLoopback* self = (OpenNITrackerLoopback*) pCookie;
00357     std::cerr << "got here: " << __FILE__ << " : " << __LINE__ << std::endl;
00358     if (eStatus == XN_CALIBRATION_STATUS_OK)
00359     {
00360       // Calibration succeeded
00361       printf("%d Calibration complete, start tracking user %d\n", epochTime, nId);              
00362       self->g_UserGenerator.GetSkeletonCap().StartTracking(nId);
00363       self->publishEvent ("tracking", nId);
00364     }
00365     else
00366     {
00367       // Calibration failed
00368       printf("%d Calibration failed for user %d\n", epochTime, nId);
00369       if (self->g_bNeedPose)
00370       {
00371         self->g_UserGenerator.GetPoseDetectionCap().StartPoseDetection(self->g_strPose, nId);
00372         self->publishEvent ("posedetection", nId);
00373       }
00374       else
00375       {
00376         self->g_UserGenerator.GetSkeletonCap().RequestCalibration(nId, TRUE);
00377         self->publishEvent ("calibrating", nId);
00378       }
00379     }
00380   }
00381 
00382   // publishes TF tree for each tracked user
00383   void publishTransforms (std::string const& frame_id)
00384   {
00385     XnUserID users[15];
00386     XnUInt16 users_count = 15;
00387     g_UserGenerator.GetUsers (users, users_count);
00388 
00389     tf::tfMessage tfs;
00390 
00391     for (int i = 0; i < users_count; ++i)
00392     {
00393       XnUserID user = users[i];
00394       if (!g_UserGenerator.GetSkeletonCap().IsTracking (user))
00395         continue;
00396 
00397       std::stringstream ss;
00398       std::string user_suffix;
00399       ss << "_" << user;
00400       user_suffix = ss.str ();
00401 
00402       std::cerr << "publishTransforms: " << __FILE__ << " : " << __LINE__ << std::endl;
00403 
00404       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_HEAD,           frame_id, "head" + user_suffix));
00405       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_NECK,           frame_id, "neck" + user_suffix));
00406       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_TORSO,          frame_id, "torso" + user_suffix));
00407 
00408       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_LEFT_SHOULDER,  frame_id, "left_shoulder" + user_suffix));
00409       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_LEFT_ELBOW,     frame_id, "left_elbow" + user_suffix));
00410       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_LEFT_HAND,      frame_id, "left_hand" + user_suffix));
00411 
00412       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_RIGHT_SHOULDER, frame_id, "right_shoulder" + user_suffix));
00413       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_RIGHT_ELBOW,    frame_id, "right_elbow" + user_suffix));
00414       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_RIGHT_HAND,     frame_id, "right_hand" + user_suffix));
00415 
00416       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_LEFT_HIP,       frame_id, "left_hip" + user_suffix));
00417       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_LEFT_KNEE,      frame_id, "left_knee" + user_suffix));
00418       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_LEFT_FOOT,      frame_id, "left_foot" + user_suffix));
00419 
00420       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_RIGHT_HIP,      frame_id, "right_hip" + user_suffix));
00421       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_RIGHT_KNEE,     frame_id, "right_knee" + user_suffix));
00422       tfs.transforms.push_back (getUserTransform (user, XN_SKEL_RIGHT_FOOT,     frame_id, "right_foot" + user_suffix));
00423     }
00424     tf_pub_.publish (tfs);
00425   }
00426   
00427   // computes a stamped transform for each user joint
00428   geometry_msgs::TransformStamped 
00429     getUserTransform(XnUserID const& user, XnSkeletonJoint joint, std::string const& frame_id, std::string const& child_frame_id)
00430   {
00431       XnSkeletonJointPosition joint_position;
00432       g_UserGenerator.GetSkeletonCap().GetSkeletonJointPosition(user, joint, joint_position);
00433       double x = -joint_position.position.X / 1000.0;
00434       double y = joint_position.position.Y / 1000.0;
00435       double z = joint_position.position.Z / 1000.0;
00436 
00437       XnSkeletonJointOrientation joint_orientation;
00438       g_UserGenerator.GetSkeletonCap().GetSkeletonJointOrientation(user, joint, joint_orientation);
00439 
00440       XnFloat* m = joint_orientation.orientation.elements;
00441       btMatrix3x3 mat (m[0], m[1], m[2],
00442                        m[3], m[4], m[5],
00443                        m[6], m[7], m[8]);
00444       btQuaternion q;
00445       mat.getRotation (q);
00446       q.setY(-q.y());
00447       q.setZ(-q.z());
00448 
00449       tf::Transform transform;
00450       transform.setOrigin(tf::Vector3(x, y, z));
00451       transform.setRotation(q);
00452 
00453       geometry_msgs::TransformStamped msg;
00454       
00455       // see openni_tracker ticket #4994
00456       tf::Transform change_frame;
00457       change_frame.setOrigin(tf::Vector3(0, 0, 0));
00458       tf::Quaternion frame_rotation;
00459       frame_rotation.setRPY(1.5708, 0, 1.5708);
00460       change_frame.setRotation(frame_rotation);
00461 
00462       transform = change_frame * transform;
00463 
00464       transformStampedTFToMsg (tf::StampedTransform(transform, ros::Time::now(), frame_id, child_frame_id), msg);
00465       return msg;
00466   }
00467 
00468 protected:
00469   xn::Context g_Context;
00470   xn::ScriptNode g_scriptNode;
00471   xn::DepthGenerator g_DepthGenerator;
00472   xn::MockDepthGenerator mockDepth;
00473   xn::UserGenerator g_UserGenerator;
00474   xn::Player g_Player;
00475 
00476   xn::DepthMetaData depthMD;
00477 
00478   XnBool g_bNeedPose;
00479   XnChar g_strPose[20];
00480   realtime_urdf_filter::RealtimeURDFFilter *filter;
00481 
00482   // ROS stuff
00483   ros::NodeHandle nh_;
00484   ros::Publisher tf_pub_;
00485   ros::Publisher event_pub_;
00486 
00487   // neccesary for glutInit()..
00488   int argc_;
00489   char **argv_;
00490 };
00491 
00492 
00493 // main function
00494 int main (int argc, char **argv)
00495 {
00496         sleep(1);
00497 
00498   // set up ROS
00499   ros::init (argc, argv, "urdf_filtered_tracker");
00500   ros::NodeHandle nh ("~");
00501 
00502   OpenNITrackerLoopback *lo = new OpenNITrackerLoopback (nh, argc, argv);
00503 
00504   // since OpenNI keeps giving you data even if no new data is available, #
00505   // we get crazy frequencies if we don't limit it artificially
00506   ros::Rate r(30);
00507 
00508         sleep(1);
00509   while (nh.ok())
00510   {
00511     lo->runOnce ();
00512     r.sleep ();
00513   }
00514 
00515   return 0;
00516 }
00517 
00518 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines


realtime_urdf_filter
Author(s): Nico Blodow
autogenerated on Thu May 23 2013 16:50:36