object_tracker.cc
Go to the documentation of this file.
00001 /*
00002  * object_tracker.cc
00003  * Mac Mason <mmason@willowgarage.com>
00004  *
00005  * This is the tied-for-second (with the plane tracker) stage in our pipeline.
00006  * We listen for cluster objects from the filter (which, at the moment,
00007  * publishes only the currently-largest cluster in the scene) and then
00008  * associate them together in useful ways. We make various things visible,
00009  * too.
00010  */
00011 
00012 #include <cassert>
00013 #include <limits>
00014 #include <vector>
00015 #include <sstream>
00016 
00017 #include <boost/foreach.hpp>
00018 #define foreach BOOST_FOREACH
00019 #include <boost/thread.hpp>
00020 
00021 #include "ros/ros.h"
00022 #include "tf/transform_listener.h"
00023 #include "tf/transform_broadcaster.h"
00024 #include "semanticmodel/blob_store.h"
00025 #include "pcl16/point_types.h"
00026 #include "pcl16_ros/point_cloud.h"
00027 #include "pcl16_ros/transforms.h"
00028 #include "pcl16/surface/convex_hull.h"
00029 #include "pcl16/segmentation/extract_clusters.h"
00030 #include "pcl16/segmentation/sac_segmentation.h"
00031 #include "pcl16/filters/extract_indices.h"
00032 #include "pcl16/sample_consensus/method_types.h"
00033 #include "pcl16/sample_consensus/model_types.h"
00034 #include "image_geometry/pinhole_camera_model.h"
00035 #include "opencv2/opencv.hpp"
00036 
00037 #include "semanticmodel/centroid.hh"
00038 #include "semanticmodel/miniatureoccupancygrid.hh"
00039 #include "semanticmodel/Blobs.h"
00040 #include "semanticmodel/Segment.h"
00041 #include "semanticmodel/blob.hh"
00042 #include "semanticmodel/GetCollectionNamespace.h"
00043 
00044 #include "visualization_msgs/MarkerArray.h"
00045 
00046 #include "dynamic_reconfigure/server.h"
00047 #include "semanticmodel/ObjectTrackerConfig.h"
00048 
00049 using namespace std;
00050 using visualization_msgs::Marker;
00051 using visualization_msgs::MarkerArray;
00052 using semanticmodel::Blob;
00053 using semanticmodel::Blobs;
00054 using semanticmodel::Segment;
00055 using semanticmodel::GetCollectionNamespace;
00056 using sensor_msgs::PointCloud2;
00057 using semanticmodel::MiniatureOccupancyGrid;
00058 
00059 typedef pcl16::PointXYZRGB Point;
00060 typedef pcl16::PointCloud<Point> PointCloud;
00061 
00062 semanticmodel::ObjectTrackerConfig config;
00063 void config_cb(const semanticmodel::ObjectTrackerConfig& _config, uint32_t i)
00064 {
00065   // See the note in filter.cc's version of config_cb about race conditions.
00066   config = _config;
00067 }
00068 
00069 // The data we're storing.
00070 vector<Blob*> objects;
00071 
00072 // Interface to db
00073 boost::shared_ptr<semanticmodel::BlobStore> blob_store;
00074 
00075 // Our occupancy grid. Note that we keep just one for the duration; that means
00076 // we're going to track this over the duration of the run.
00077 MiniatureOccupancyGrid OG(0.05);
00078 
00079 // The (randomly generated) colors we're using to show the object clouds.
00080 // These are stored as [r g b r g b r g b], all in the range [0, 255]. There
00081 // should be three times as many of these as there are elements of objects.
00082 vector<int> colors;
00083 
00084 cv::RNG prng(2);
00085 
00086 /* Talking to the outside world. */
00087 ros::Subscriber cluster_sub;
00088 ros::Subscriber full_cloud_sub;  // Used for populating our OG.
00089 ros::Publisher all_objects_pub;  // The objects (as one point cloud).
00090 ros::Publisher flat_frames_publisher;  // Push out 2D frames.
00091 ros::Publisher viz_pub; // The objects (as vis markers)
00092 ros::Publisher og_pub;  // Our occupancy grid.
00093 
00094 // These have to be pointers, or we'll build a node before ::init.
00095 boost::shared_ptr<tf::TransformListener> listener; 
00096 boost::shared_ptr<tf::TransformBroadcaster> broadcaster; 
00097 
00098 void push_one_frame_segment(const Blob* blob, 
00099                             const sensor_msgs::Image& rgb_img,
00100                             const sensor_msgs::CameraInfo& rgb_info,
00101                             const sensor_msgs::Image& depth_img,
00102                             const sensor_msgs::CameraInfo& depth_info);
00103 
00104 // Originally part of Segmenter; this is likely better.
00105 void cluster_objects(const PointCloud::ConstPtr& aboves,
00106                      std::vector<PointCloud::Ptr>& clusters)
00107 {
00108   if (!aboves)
00109     return;
00110   if (aboves->points.size() < 100)
00111     return;
00112   if (!ros::ok())
00113     return;
00114 
00115   clusters.clear();
00116   pcl16::search::KdTree<Point>::Ptr tree(new pcl16::search::KdTree<Point>());
00117   tree->setInputCloud(aboves);
00118 
00119   pcl16::EuclideanClusterExtraction<Point> euclid;
00120   std::vector<pcl16::PointIndices> indices;
00121   euclid.setClusterTolerance(config.cluster_tolerance);
00122   euclid.setMinClusterSize(config.min_cluster_size);
00123   euclid.setMaxClusterSize(config.max_cluster_size);
00124   euclid.setSearchMethod(tree);
00125   euclid.setInputCloud(aboves);
00126   ROS_DEBUG_NAMED("cluster", 
00127                   "About to cluster %zu points", aboves->points.size());
00128   euclid.extract(indices);
00129 
00130   // Fill in our output vector.
00131   pcl16::ExtractIndices<Point> extractor;
00132   foreach(pcl16::PointIndices& index, indices)
00133   {
00134     if (!ros::ok())
00135       return;
00136     PointCloud::Ptr new_cluster(new PointCloud());
00137     extractor.setInputCloud(aboves);
00138     pcl16::PointIndices::Ptr indexptr(new pcl16::PointIndices(index));
00139     extractor.setIndices(indexptr); // This is SO STUPID. Egad.
00140     extractor.filter(*new_cluster);
00141     clusters.push_back(new_cluster);
00142   }
00143 }
00144 
00145 void deplane_objects(std::vector<PointCloud::Ptr>& clusters)
00146 {
00147   if (!ros::ok())
00148     return;
00149   // Fill this in with those that pass the test; then do a swap.
00150   std::vector<PointCloud::Ptr> newclusters;
00151 
00152   // Shared stuff.
00153   pcl16::SACSegmentation<Point> segmenter;
00154   segmenter.setOptimizeCoefficients(true);
00155   segmenter.setModelType(pcl16::SACMODEL_PLANE);
00156   segmenter.setMethodType(pcl16::SAC_RANSAC);
00157   segmenter.setDistanceThreshold(config.plane_tolerance);
00158   segmenter.setMaxIterations(config.object_ransac_iterations);
00159 
00160   foreach(PointCloud::Ptr& cluster, clusters)
00161   {
00162     if (cluster->points.size() == 0)
00163       continue;
00164     if (!ros::ok())
00165       return;
00166 
00167     pcl16::ModelCoefficients::Ptr model(new pcl16::ModelCoefficients());
00168     pcl16::PointIndices::Ptr indices(new pcl16::PointIndices());
00169     segmenter.setInputCloud(cluster);
00170     segmenter.segment(*indices, *model);
00171 
00172     double fraction = (float)indices->indices.size() / cluster->points.size();
00173 //    ROS_FATAL("Fit %zu indices (%f%%), eq: [%f %f %f %f]",
00174 //             indices->indices.size(),
00175 //             100 * fraction,
00176 //             model->values[0],
00177 //             model->values[1],
00178 //             model->values[2],
00179 //             model->values[3]);
00180 
00181     if (fraction <= config.object_plane_percentage)
00182     {
00183       newclusters.push_back(cluster);
00184       ROS_DEBUG_NAMED("object_tracker", "Kept cluster with %zu points.", 
00185                       indices->indices.size());
00186     }
00187     else
00188     {
00189       ROS_DEBUG_NAMED("object_tracker", "Rejected cluster with %zu points.", 
00190                       indices->indices.size());
00191     }
00192   }
00193 
00194   // And we're done.
00195   std::swap(clusters, newclusters);
00196 }
00197 
00198 // Use this cloud to update our occupancy grid.
00199 void fc_cb(const PointCloud::ConstPtr& input)
00200 {
00201   if (!ros::ok())
00202     return;
00203 
00204   PointCloud::Ptr canonical(new PointCloud());
00205   if (!pcl16_ros::transformPointCloud(config.canonical_frame, 
00206                                     *input, 
00207                                     *canonical, 
00208                                     *listener))
00209     return;
00210 
00211   // Step 1: fill in the grid.
00212   boost::scoped_ptr<boost::mutex::scoped_lock> lock(OG.get_lock());
00213   foreach(const Point& p, *input)
00214   {
00215     // Because +z is "up" in /map, we can just ignore the other coordinates.
00216     if (p.z >= config.occ_trick_low && p.z <= config.occ_trick_high)
00217     {
00218       OG.set(p.x, p.y, MiniatureOccupancyGrid::OCCUPIED);
00219     }
00220   }
00221   nav_msgs::OccupancyGrid og;
00222   OG.to_msg(og);
00223   og_pub.publish(og);
00224 }
00225 
00226 // Perform the occupancy grid trick to throw away those objects we consider
00227 // unlikely. input is both an input and output argument, as you would expect
00228 // by now.
00229 void occupancy_trick(PointCloud::Ptr& input)
00230 {
00231   if (!ros::ok())
00232     return;
00233 
00234   /*
00235    * We're going to need to walk input twice. The first time, we check for
00236    * points in the "bad range", and set the appropriate flags in the (2D)
00237    * occupancy grid. The second time, we check each point against that grid,
00238    * and only keep those that we like. 
00239    */
00240 
00241   // This is our output; where we're done, std::swap takes care of things.
00242   PointCloud::Ptr output(new PointCloud());
00243   output->header = input->header;
00244 
00245   // Now, filter.
00246   boost::scoped_ptr<boost::mutex::scoped_lock> lock(OG.get_lock());
00247   foreach(const Point& p, *input)
00248   {
00249     if (OG.get(p.x, p.y) == MiniatureOccupancyGrid::EMPTY)
00250       output->points.push_back(p);
00251   }
00252 
00253   std::swap(input, output);
00254 }
00255 
00256 void blobs_cb(const Blobs::ConstPtr& blobs)
00257 {
00258   // These days, you just get one.
00259   ROS_ASSERT(blobs->blobs.size() == 1);
00260 
00261   /* Convert point cloud message into pcl16 point cloud */
00262   PointCloud pcl16_cloud;
00263   pcl16::fromROSMsg(blobs->blobs[0], pcl16_cloud);
00264 
00265   /* We need our inputs to be in the canonical frame. */
00266   PointCloud::Ptr cluster(new PointCloud());
00267   pcl16_ros::transformPointCloud(config.canonical_frame, 
00268                                  pcl16_cloud, 
00269                                  *cluster, 
00270                                  *listener);
00271 
00272   /* 
00273    * Get rid of this that are unlikely to actually be objects, but instead are
00274    * probably parts of walls, using Bhaskara's occupancy grid trick.
00275    */
00276   occupancy_trick(cluster);
00277   if (cluster->points.size() == 0 || !ros::ok())
00278   {
00279     ROS_INFO_NAMED("object_tracker", "Occupancy trick erased everything.");
00280     return;
00281   }
00282 
00283   /* Now, cluster the points. */
00284   vector<PointCloud::Ptr> clusters;
00285   cluster_objects(cluster, clusters);
00286   if (clusters.size() == 0 || !ros::ok())
00287   {
00288     ROS_INFO_NAMED("object_tracker", "Clustering went nowhere.");
00289     return;
00290   }
00291 
00292   /* And throw away objects that are too planar. */
00293   deplane_objects(clusters);
00294   if (clusters.size() == 0 || !ros::ok())
00295   {
00296     ROS_INFO_NAMED("object_tracker", "Deplaning erased everything.");
00297     return;
00298   }
00299 
00300   foreach(PointCloud::Ptr cluster, clusters)
00301   {
00302     /* Build our new blob. */
00303     Blob* newblob = new Blob(cluster);
00304 
00305     push_one_frame_segment(newblob, 
00306                            blobs->rgb_image, 
00307                            blobs->rgb_info,
00308                            blobs->depth_image,
00309                            blobs->depth_info);
00310 
00311     /* See if it "belongs to" any of the other blobs. */
00312     Blob::MergeBlobWithSet(objects, newblob);
00313   }
00314 
00315   // Check after every long operation, of course.
00316   if (!ros::ok())
00317     return;
00318 
00319   //ROS_INFO("There are now %zu objects.", objects.size());
00320   
00321   /* Time to publish our data. */
00322   int id = 50;
00323   foreach(Blob* blob, objects)
00324   {
00325     Marker marker;
00326     marker.header.frame_id = blob->cloud->header.frame_id;
00327     marker.header.stamp = ros::Time::now();
00328     marker.id = id;
00329     marker.ns = "objects";
00330     marker.type = Marker::CUBE;
00331     marker.action = Marker::ADD;
00332     // We need the center of the object.
00333     Eigen::Vector4f center(semanticmodel::centroid(blob->cloud));
00334     marker.pose.position.x = center[0];
00335     marker.pose.position.y = center[1];
00336     marker.pose.position.z = center[2];
00337     marker.pose.orientation.x = 0.0;
00338     marker.pose.orientation.y = 0.0;
00339     marker.pose.orientation.z = 0.0;
00340     marker.pose.orientation.w = 1.0;
00341     blob->size(marker.scale.x, marker.scale.y, marker.scale.z);
00342     marker.color.a = 0.75;
00343     blob->RGB(marker.color.r, marker.color.g, marker.color.b);
00344     id++;
00345     if (config.send_boxes)
00346       viz_pub.publish(marker);
00347     // Either way, we do publish the tf frames.
00348     tf::Transform transform;
00349     transform.setOrigin(tf::Vector3(center[0], center[1], center[2]));
00350     transform.setRotation(tf::Quaternion(0, 0, 1.0, 0.0));
00351     std::string name = (boost::format("object_%02d") % blob->id).str();
00352     tf::StampedTransform trans(transform,
00353                          blob->cloud->header.stamp,
00354                          config.canonical_frame,
00355                          name);
00356     broadcaster->sendTransform(trans);
00357   }
00358 
00359   // Make sure we have enough colors for the point cloud.
00360   while (colors.size() < 3 * objects.size())
00361     colors.push_back(int(prng.uniform(0.0, 1.0) * 255));
00362 
00363   // Send 'em to the database.
00364   blob_store->update(objects, blobs->rgb_image, blobs->rgb_info);
00365 
00366   // Publish every blob at once, in one pointcloud.
00367   if (objects.size() == 0 || !ros::ok())
00368     return;
00369 
00370   PointCloud::Ptr all_objects(new PointCloud());
00371   all_objects->header.frame_id = objects.at(0)->cloud->header.frame_id;
00372   foreach(Blob* blob, objects)
00373   {
00374     (*all_objects) += *(blob->cloud);
00375   }
00376   all_objects_pub.publish(all_objects);
00377 }
00378 
00379 int main(int argc, char** argv)
00380 {
00381   ros::init(argc, argv, "object_tracker");
00382   ros::NodeHandle node("~"); // Private nodehandle
00383 
00384   cluster_sub = node.subscribe<Blobs>("blobs", 100, blobs_cb);
00385   full_cloud_sub = node.subscribe<PointCloud>("/map_points", 100, fc_cb);
00386   all_objects_pub = node.advertise<PointCloud>("all_objects_cloud", 100, true);
00387   viz_pub = node.advertise<visualization_msgs::Marker>(
00388       "visualization_marker", 0);
00389 
00390   flat_frames_publisher = node.advertise<Segment>("/flat_frames", 10);
00391   og_pub = node.advertise<nav_msgs::OccupancyGrid>("/og", 100);
00392   std::string camera_frame;
00393   bool found = node.getParam("camera_frame", camera_frame);
00394   ROS_ASSERT(found);
00395 
00396   // Lifted straight from the dynamic_reconfigure docs.
00397   dynamic_reconfigure::Server<semanticmodel::ObjectTrackerConfig> srv;
00398   dynamic_reconfigure::Server<semanticmodel::ObjectTrackerConfig>::
00399     CallbackType f;
00400   f = boost::bind(&config_cb, _1, _2);
00401   srv.setCallback(f);
00402 
00403   ROS_INFO_STREAM("Waiting for service get_db_name");
00404   GetCollectionNamespace coll_ns_srv;
00405   ros::service::waitForService("get_collection_namespace");
00406   ros::service::call("get_collection_namespace", coll_ns_srv);
00407   ROS_INFO_STREAM ("Db name is " << coll_ns_srv.response.db_name << "/"
00408                                  << coll_ns_srv.response.collection_namespace);
00409   blob_store.reset(
00410       new semanticmodel::BlobStore(config.canonical_frame,
00411                                    camera_frame,
00412                                    coll_ns_srv.response.db_name,
00413                                    coll_ns_srv.response.collection_namespace));
00414   
00415   listener.reset(new tf::TransformListener(ros::Duration(600)));
00416   broadcaster.reset(new tf::TransformBroadcaster());
00417   ros::spin();
00418 
00419   // Just to be sure.
00420   foreach(Blob* blob, objects)
00421     delete blob;
00422 
00423   return 0;
00424 }
00425 
00426 void push_one_frame_segment(const Blob* blob, 
00427                             const sensor_msgs::Image& rgb_img,
00428                             const sensor_msgs::CameraInfo& rgb_info,
00429                             const sensor_msgs::Image& depth_img,
00430                             const sensor_msgs::CameraInfo& depth_info)
00431 {
00432   // The easy parts.
00433   Segment seg;
00434   seg.rgb_image = rgb_img;
00435   seg.rgb_info = rgb_info;
00436   seg.depth_image = depth_img;
00437   seg.depth_info = depth_info;
00438 
00439   // Now I need project the blob into the image plane and push that.
00440   image_geometry::PinholeCameraModel pcm;
00441   pcm.fromCameraInfo(rgb_info);
00442   PointCloud::Ptr camcloud(new PointCloud());
00443   pcl16_ros::transformPointCloud(rgb_img.header.frame_id,
00444                                  rgb_img.header.stamp,
00445                                  *(blob->cloud),
00446                                  blob->cloud->header.frame_id,
00447                                  *camcloud,
00448                                  *listener);
00449   
00450   for (size_t i = 0 ; i < camcloud->size() ; ++i)
00451   {
00452     cv::Point3d cvpt(camcloud->points[i].x,
00453                      camcloud->points[i].y,
00454                      camcloud->points[i].z);
00455     cv::Point2d pt = pcm.project3dToPixel(cvpt);
00456     if (pt.x >= 0 && pt.x < rgb_img.width && 
00457         pt.y >= 0 && pt.y < rgb_img.height)
00458     {
00459       seg.coordinates.push_back(pt.y);
00460       seg.coordinates.push_back(pt.x);
00461     }
00462   }
00463   flat_frames_publisher.publish(seg);
00464 }


semanticmodel
Author(s): Julian ("Mac") Mason
autogenerated on Thu Dec 12 2013 12:39:10