$search
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 }