pcl_video.cpp
Go to the documentation of this file.
00001 /*
00002  * Software License Agreement (BSD License)
00003  *
00004  *  Copyright (c) 2011, Geoffrey Biggs
00005  *  All rights reserved.
00006  *
00007  *  Redistribution and use in source and binary forms, with or without
00008  *  modification, are permitted provided that the following conditions
00009  *  are met:
00010  *
00011  *   * Redistributions of source code must retain the above copyright
00012  *     notice, this list of conditions and the following disclaimer.
00013  *   * Redistributions in binary form must reproduce the above
00014  *     copyright notice, this list of conditions and the following
00015  *     disclaimer in the documentation and/or other materials provided
00016  *     with the distribution.
00017  *   * Neither the name of Willow Garage, Inc. nor the names of its
00018  *     contributors may be used to endorse or promote products derived
00019  *     from this software without specific prior written permission.
00020  *
00021  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032  *  POSSIBILITY OF SUCH DAMAGE.
00033  *
00034  *  \author Geoffrey Biggs
00035  */
00036 
00037 #include <boost/date_time/gregorian/gregorian_types.hpp>
00038 #include <boost/date_time/posix_time/posix_time.hpp>
00039 #include <boost/date_time/posix_time/posix_time_types.hpp>
00040 #include <boost/thread/thread.hpp>
00041 #include <boost/uuid/uuid.hpp>
00042 #include <boost/uuid/uuid_generators.hpp>
00043 #include <iostream>
00044 #include <string>
00045 #include <tide/ebml_element.h>
00046 #include <tide/file_cluster.h>
00047 #include <tide/segment.h>
00048 #include <tide/simple_block.h>
00049 #include <tide/tide_impl.h>
00050 #include <tide/tracks.h>
00051 #include <tide/track_entry.h>
00052 #include <pcl/point_cloud.h>
00053 #include <pcl/point_types.h>
00054 #include <pcl/io/openni_grabber.h>
00055 #include <sensor_msgs/PointCloud2.h>
00056 #include <pcl/ros/conversions.h>
00057 #include <pcl/visualization/cloud_viewer.h>
00058 #include <pcl/console/parse.h>
00059 #include <pcl/common/time.h>
00060 
00061 namespace bpt = boost::posix_time;
00062 
00063 
00064 class Recorder
00065 {
00066     public:
00067         Recorder(std::string const& filename, std::string const& title)
00068             : filename_(filename), title_(title),
00069             stream_(filename, std::ios::in|std::ios::out|std::ios::trunc),
00070             count_(0)
00071         {
00072         }
00073 
00074         void Callback(pcl::PointCloud<pcl::PointXYZ>::ConstPtr const& cloud)
00075         {
00076             // When creating a block, the track number must be specified. Currently,
00077             // all blocks belong to track 1 (because this program only records one
00078             // track). A timecode must also be given.  It is an offset from the
00079             // cluster's timecode measured in the segment's timecode scale.
00080             bpt::ptime blk_start(bpt::microsec_clock::local_time());
00081             bpt::time_duration blk_offset = blk_start - cltr_start_;
00082             tide::BlockElement::Ptr block(new tide::SimpleBlock(1,
00083                         blk_offset.total_microseconds() / 10000));
00084             // Here the frame data itself is added to the block
00085             sensor_msgs::PointCloud2 blob;
00086             pcl::toROSMsg(*cloud, blob);
00087             tide::Block::FramePtr frame_ptr(new tide::Block::Frame(blob.data.begin(),
00088                         blob.data.end()));
00089             block->push_back(frame_ptr);
00090             cluster_->push_back(block);
00091             // Benchmarking
00092             if (++count_ == 30)
00093             {
00094                 double now = pcl::getTime();
00095                 std::cerr << "Average framerate: " <<
00096                     static_cast<double>(count_) / (now - last_) << "Hz\n";
00097                 count_ = 0;
00098                 last_ = now;
00099             }
00100             // Check if the cluster has enough data in it.
00101             // What "enough" is depends on your own needs. Generally, a cluster
00102             // shouldn't be allowed to get too big in data size or too long in time, or
00103             // it has an adverse affect on seeking through the file. We will aim for 1
00104             // second of data per cluster.
00105             bpt::time_duration cluster_len(
00106                     bpt::microsec_clock::local_time() - cltr_start_);
00107             if (cluster_len.total_seconds() >= 1)
00108             {
00109                 // Finalise the cluster
00110                 cluster_->finalise(stream_);
00111                 // Create a new cluster
00112                 cltr_start_ = bpt::microsec_clock::local_time();
00113                 bpt::time_duration cltr_offset = cltr_start_ - seg_start_;
00114                 cluster_.reset(new tide::FileCluster(
00115                             cltr_offset.total_microseconds() / 10000));
00116                 cluster_->write(stream_);
00117             }
00118         }
00119 
00120         int Run()
00121         {
00122             // Write the EBML Header. This specifies that the file is an EBML
00123             // file, and is a Tide document.
00124             tide::EBMLElement ebml_el;
00125             ebml_el.write(stream_);
00126 
00127             // Open a new segment in the file. This will write some initial meta-data
00128             // and place some padding at the start of the file for final meta-data to
00129             // be written after tracks, clusters, etc. have been written.
00130             tide::Segment segment;
00131             segment.write(stream_);
00132             // Set up the segment information so it can be used while writing tracks
00133             // and clusters.
00134             // A UID is not required, but is highly recommended.
00135             boost::uuids::random_generator gen;
00136             boost::uuids::uuid uuid = gen();
00137             std::vector<char> uuid_data(uuid.size());
00138             std::copy(uuid.begin(), uuid.end(), uuid_data.begin());
00139             segment.info.uid(uuid_data);
00140             // The filename can be nice to know.
00141             segment.info.filename(filename_);
00142             // The segment's timecode scale is possibly the most important value in the
00143             // segment meta-data data. Without it, timely playback of frames is not
00144             // possible. It has a sensible default (defined in the Tide specification),
00145             // but here we set it to ten milliseconds for demonstrative purposes.
00146             segment.info.timecode_scale(10000000);
00147             // The segment's date should be set. It is the somewhat-awkward value of
00148             // the number of seconds since the start of the millennium. Boost::Date_Time
00149             // to the rescue!
00150             bpt::ptime basis(boost::gregorian::date(2001, 1, 1));
00151             seg_start_ = boost::posix_time::microsec_clock::local_time();
00152             bpt::time_duration td = seg_start_ - basis;
00153             segment.info.date(td.total_microseconds() * 1000);
00154             // Let's give the segment an inspirational title.
00155             segment.info.title(title_);
00156             // It sometimes helps to know what created a Tide file.
00157             segment.info.muxing_app("libtide-0.1");
00158             segment.info.writing_app("pcl_video");
00159 
00160             // Set up the tracks meta-data and write it to the file.
00161             tide::Tracks tracks;
00162             // Each track is represented in the Tracks information by a TrackEntry.
00163             // This specifies such things as the track number, the track's UID and the
00164             // codec used.
00165             tide::TrackEntry::Ptr track(new tide::TrackEntry(1, 1, "pointcloud2"));
00166             track->name("3D video");
00167             track->codec_name("sensor_msgs::PointCloud2");
00168             // Adding each level 1 element (only the first occurance, in the case of
00169             // clusters) to the index makes opening the file later much faster.
00170             segment.index.insert(std::make_pair(tracks.id(),
00171                         segment.to_segment_offset(stream_.tellp())));
00172             // Now we can write the Tracks element.
00173             tracks.insert(track);
00174             tracks.write(stream_);
00175             // The file is now ready for writing the data. The data itself is stored in
00176             // clusters. Each cluster contains a number of blocks, with each block
00177             // containing a single frame of data. Different cluster implementations are
00178             // (will be) available using different optimisations. Here, we use the
00179             // implementation that stores all its blocks in memory before writing them
00180             // all to the file at once. As with the segment, clusters must be opened
00181             // for writing before blocks are added. Once the cluster is complete, it is
00182             // finalised. How many blocks each cluster contains is relatively flexible:
00183             // the only limitation is on the range of block timecodes that can be
00184             // stored. Each timecode is a signed 16-bit integer, and usually blocks
00185             // have timecodes that are positive, limiting the range to 32767. The unit
00186             // of this value is the segment's timecode scale. The default timecode
00187             // scale therefore gives approximately 65 seconds of total range, with 32
00188             // seconds being usable.
00189             // The first cluster will appear at this point in the file, so it is
00190             // recorded in the segment's index for faster file reading.
00191             segment.index.insert(std::make_pair(tide::ids::Cluster,
00192                         segment.to_segment_offset(stream_.tellp())));
00193 
00194             // Set up a callback to get clouds from a grabber and write them to the
00195             // file.
00196             pcl::Grabber* interface(new pcl::OpenNIGrabber());
00197             boost::function<void (const pcl::PointCloud<pcl::PointXYZ>::ConstPtr&)> f(
00198                     boost::bind(&Recorder::Callback, this, _1));
00199             interface->registerCallback(f);
00200             // Start the first cluster
00201             cltr_start_ = bpt::microsec_clock::local_time();
00202             bpt::time_duration cltr_offset = cltr_start_ - seg_start_;
00203             cluster_.reset(new tide::FileCluster(
00204                         cltr_offset.total_microseconds() / 10000));
00205             cluster_->write(stream_);
00206             last_ = pcl::getTime();
00207             interface->start();
00208 
00209             std::cout << "Recording frames. Press any key to stop.\n";
00210             getchar();
00211 
00212             interface->stop();
00213             // Close the last open cluster
00214             if (cluster_)
00215             {
00216                 cluster_->finalise(stream_);
00217             }
00218 
00219             // Now that the data has been written, the last thing to do is to finalise
00220             // the segment.
00221             segment.finalise(stream_);
00222             // And finally, close the file.
00223             stream_.close();
00224 
00225             return 0;
00226         }
00227 
00228     private:
00229         std::string filename_;
00230         std::string title_;
00231         std::fstream stream_;
00232         tide::FileCluster::Ptr cluster_;
00233         bpt::ptime seg_start_;
00234         bpt::ptime cltr_start_;
00235         unsigned int count_;
00236         double last_;
00237 };
00238 
00239 
00240 class Player
00241 {
00242     public:
00243         Player(std::string const& filename)
00244             : filename_(filename), viewer_("PCL Video Player: " + filename)
00245         {
00246             //viewer_.setBackgroundColor(0, 0, 0);
00247             //viewer_.initCameraParameters();
00248         }
00249 
00250         int Run()
00251         {
00252             // Open the file and check for the EBML header. This confirms that the file
00253             // is an EBML file, and is a Tide document.
00254             std::ifstream stream(filename_, std::ios::in);
00255             tide::ids::ReadResult id = tide::ids::read(stream);
00256             if (id.first != tide::ids::EBML)
00257             {
00258                 std::cerr << "File does not begin with an EBML header.\n";
00259                 return 1;
00260             }
00261             tide::EBMLElement ebml_el;
00262             ebml_el.read(stream);
00263             if (ebml_el.doc_type() != tide::TideDocType)
00264             {
00265                 std::cerr << "Specified EBML file is not a Tide document.\n";
00266                 return 1;
00267             }
00268             if (ebml_el.read_version() > tide::TideEBMLVersion)
00269             {
00270                 std::cerr << "This Tide document requires read version " <<
00271                     ebml_el.read_version() << ".\n";
00272                 return 1;
00273             }
00274             if (ebml_el.doc_read_version() > tide::TideVersionMajor)
00275             {
00276                 std::cerr << "This Tide document requires doc read version " <<
00277                     ebml_el.read_version() << ".\n";
00278                 return 1;
00279             }
00280             std::cerr << "Found EBML header\n";
00281 
00282             // Open the file's segment. This will read some meta-data about the segment
00283             // and read (or build, if necessary) an index of the level 1 elements. With
00284             // this index, we will be able to quickly jump to important elements such
00285             // as the Tracks and the first Cluster.
00286             id = tide::ids::read(stream);
00287             if (id.first != tide::ids::Segment)
00288             {
00289                 std::cerr << "Segment element not found\n";
00290                 return 1;
00291             }
00292             tide::Segment segment;
00293             segment.read(stream);
00294             // The segment's date is stored as the number of nanoseconds since the
00295             // start of the millenium. Boost::Date_Time is invaluable here.
00296             bpt::ptime basis(boost::gregorian::date(2001, 1, 1));
00297             bpt::time_duration sd(bpt::microseconds(segment.info.date() / 1000));
00298             bpt::ptime seg_start(basis + sd);
00299 
00300             // The segment is now open and we can start reading its child elements. To
00301             // begin with, we get the tracks element (their may be more than one, if
00302             // the document was created by merging other documents) but generally only
00303             // one will exist).
00304             // We can guarantee that there is at least one in the index because
00305             // otherwise the call to segment.read() would have thrown an error.
00306             std::streampos tracks_pos(segment.index.find(tide::ids::Tracks)->second);
00307             stream.seekg(segment.to_stream_offset(tracks_pos));
00308             // To be sure, we can check it really is a Tracks element, but this is
00309             // usually not necessary.
00310             id = tide::ids::read(stream);
00311             if (id.first != tide::ids::Tracks)
00312             {
00313                 std::cerr << "Tracks element not at indicated position.\n";
00314                 return 1;
00315             }
00316             // Read the tracks
00317             tide::Tracks tracks;
00318             tracks.read(stream);
00319             // Now we can introspect the tracks available in the file.
00320             if (tracks.empty())
00321             {
00322                 std::cerr << "No tracks found.\n";
00323                 return 1;
00324             }
00325             // Let's check that the file contains the codec we expect for the first
00326             // track.
00327             if (tracks[1]->codec_id() != "pointcloud2")
00328             {
00329                 std::cerr << "Track #1 has wrong codec type " <<
00330                     tracks[1]->codec_id() << '\n';
00331                 return 1;
00332             }
00333 
00334             bpt::ptime pb_start(bpt::microsec_clock::local_time());
00335 
00336             // Now we can start reading the clusters. Get an iterator to the clusters
00337             // in the segment.
00338             // In this case, we are using a file-based cluster implementation, which
00339             // reads blocks from the file on demand. This is usually a better
00340             // option tham the memory-based cluster when the size of the stored
00341             // data is large.
00342             for (tide::Segment::FileBlockIterator block(segment.blocks_begin_file(stream));
00343                     block != segment.blocks_end_file(stream); ++block)
00344             {
00345                 bpt::time_duration blk_offset(bpt::microseconds((
00346                         (block.cluster()->timecode() + block->timecode()) *
00347                         segment.info.timecode_scale() / 1000)));
00348                 bpt::time_duration played_time(bpt::microsec_clock::local_time() -
00349                         pb_start);
00350                 // If the current playback time is ahead of this block, skip it
00351                 if (played_time > blk_offset)
00352                 {
00353                     std::cerr << "Skipping block at " << blk_offset <<
00354                         " because current playback time is " << played_time << '\n';
00355                     continue;
00356                 }
00357                 // Some blocks may actually contain multiple frames in a lace.
00358                 // In this case, we are reading blocks that do not use lacing,
00359                 // so there is only one frame per block. This is the general
00360                 // case; lacing is typically only used when the frame size is
00361                 // very small to reduce overhead.
00362                 tide::BlockElement::FramePtr frame_data(*block->begin());
00363                 // Copy the frame data into a serialised cloud structure
00364                 sensor_msgs::PointCloud2 blob;
00365                 blob.height = 480;
00366                 blob.width = 640;
00367                 sensor_msgs::PointField ptype;
00368                 ptype.name = "x";
00369                 ptype.offset = 0;
00370                 ptype.datatype = 7;
00371                 ptype.count = 1;
00372                 blob.fields.push_back(ptype);
00373                 ptype.name = "y";
00374                 ptype.offset = 4;
00375                 ptype.datatype = 7;
00376                 ptype.count = 1;
00377                 blob.fields.push_back(ptype);
00378                 ptype.name = "z";
00379                 ptype.offset = 8;
00380                 ptype.datatype = 7;
00381                 ptype.count = 1;
00382                 blob.fields.push_back(ptype);
00383                 blob.is_bigendian = false;
00384                 blob.point_step = 16;
00385                 blob.row_step = 10240;
00386                 blob.is_dense = false;
00387                 blob.data.assign(frame_data->begin(), frame_data->end());
00388                 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
00389                 pcl::fromROSMsg(blob, *cloud);
00390                 // Sleep until the block's display time. The played_time is
00391                 // updated to account for the time spent preparing the data.
00392                 played_time = bpt::microsec_clock::local_time() - pb_start;
00393                 bpt::time_duration sleep_time(blk_offset - played_time);
00394                 std::cerr << "Will sleep " << sleep_time << " until displaying block\n";
00395                 boost::this_thread::sleep(sleep_time);
00396                 viewer_.showCloud(cloud);
00397                 //viewer_.removePointCloud("1");
00398                 //viewer_.addPointCloud(cloud, "1");
00399                 //viewer_.spinOnce();
00400                 //if (viewer_.wasStopped())
00401                 //{
00402                     //break;
00403                 //}
00404             }
00405 
00406             return 0;
00407         }
00408 
00409     private:
00410         std::string filename_;
00411         //pcl::visualization::PCLVisualizer viewer_;
00412         pcl::visualization::CloudViewer viewer_;
00413 };
00414 
00415 
00416 int main(int argc, char** argv)
00417 {
00418     std::string filename;
00419     if (pcl::console::parse_argument(argc, argv, "-f", filename) < 0)
00420     {
00421         std::cerr << "Usage: " << argv[0] << " -f filename [-p] [-t title]\n";
00422         return 1;
00423     }
00424     std::string title("PCL 3D video");
00425     pcl::console::parse_argument(argc, argv, "-t", title);
00426     if (pcl::console::find_switch(argc, argv, "-p"))
00427     {
00428         Player player(filename);
00429         return player.Run();
00430     }
00431     else
00432     {
00433         Recorder recorder(filename, title);
00434         return recorder.Run();
00435     }
00436 }
00437 


pcl
Author(s): Open Perception
autogenerated on Mon Oct 6 2014 03:16:27