Example: rgbd_dataset2rawlog

C++ example source code:

/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

/* ------------------------------------------------------
                  rgbd2rawlog
    A small tool to translate RGB+D datasets from TUM:
    http://cvpr.in.tum.de/data/datasets/rgbd-dataset/

    into MRPT rawlog binary format, ready to be parsed
    by RawLogViewer or user programs.

Usage:
      rgbd_dataset2rawlog  [PATH_TO_RGBD_DATASET] [OUTPUT_NAME]

Where expected input files are:
    - PATH_TO_RGBD_DATASET/accelerometer.txt
    - PATH_TO_RGBD_DATASET/depth.txt
    - PATH_TO_RGBD_DATASET/rgb.txt
    - PATH_TO_RGBD_DATASET/rgb/... (Images, 3x8u)
    - PATH_TO_RGBD_DATASET/depth/...  (Images, 1x16u)

Output files:
    - OUTPUT_NAME.rawlog: The output rawlog file.
    - OUTPUT_NAME_Images/...: External RGB images.

   ------------------------------------------------------ */

#include <mrpt/io/CFileGZOutputStream.h>
#include <mrpt/io/CTextFileLinesParser.h>
#include <mrpt/obs/CObservation3DRangeScan.h>
#include <mrpt/obs/CObservationIMU.h>
#include <mrpt/serialization/CArchive.h>
#include <mrpt/system/filesystem.h>

#include <iostream>

using namespace std;
using namespace mrpt;
using namespace mrpt::obs;
using namespace mrpt::serialization;

const double KINECT_FPS = 30.0;

// Based on:
// http://stackoverflow.com/questions/218488/finding-best-matching-key-for-a-given-key-in-a-sorted-stl-container
template <class CONTAINER>
typename CONTAINER::const_iterator find_closest(
    const CONTAINER& data, const typename CONTAINER::key_type& searchkey)
{
    typename CONTAINER::const_iterator upper = data.lower_bound(searchkey);
    if (upper == data.begin() || upper->first == searchkey) return upper;
    typename CONTAINER::const_iterator lower = upper;
    --lower;
    if (upper == data.end() ||
        (searchkey - lower->first) < (upper->first - searchkey))
        return lower;
    return upper;
}

void rgbd2rawlog(const string& src_path, const string& out_name)
{
    const string in_fil_acc = src_path + string("/accelerometer.txt");
    const string in_fil_depth =
        mrpt::system::fileExists(src_path + string("/ir.txt"))
        ? src_path + string("/ir.txt")
        : src_path + string("/depth.txt");
    const string in_fil_rgb = src_path + string("/rgb.txt");

    // ASSERT_FILE_EXISTS_(in_fil_acc)
    // ASSERT_FILE_EXISTS_(in_fil_depth)
    // ASSERT_FILE_EXISTS_(in_fil_rgb)

    // make a list with RGB & DEPTH files ------------------------
    map<double, string> list_rgb, list_depth;
    map<double, vector<double>> list_acc;
    std::istringstream line;
    if (mrpt::system::fileExists(in_fil_rgb))
    {
        mrpt::io::CTextFileLinesParser fparser(in_fil_rgb);
        while (fparser.getNextLine(line))
        {
            double tim;
            string f;
            if (line >> tim >> f) list_rgb[tim] = f;
        }
    }
    if (mrpt::system::fileExists(in_fil_depth))
    {
        mrpt::io::CTextFileLinesParser fparser(in_fil_depth);
        while (fparser.getNextLine(line))
        {
            double tim;
            string f;
            if (line >> tim >> f) list_depth[tim] = f;
        }
    }
    if (mrpt::system::fileExists(in_fil_acc))
    {
        mrpt::io::CTextFileLinesParser fparser(in_fil_acc);
        while (fparser.getNextLine(line))
        {
            double tim;
            double ax, ay, az;
            if (line >> tim >> ax >> ay >> az)
            {
                vector<double>& v = list_acc[tim];
                v.resize(3);
                v[0] = ax;
                v[1] = ay;
                v[2] = az;
            }
        }
    }
    cout << "Parsed: " << list_depth.size() << " / " << list_rgb.size() << " / "
         << list_acc.size() << " depth/rgb/acc entries.\n";

    const bool only_ir = list_depth.size() > 10 && list_rgb.empty();
    const bool only_rgb = list_depth.empty() && list_rgb.size() > 10;

    // Create output directory for images ------------------------------
    const string out_img_dir = out_name + string("_Images");

    cout << "Creating images directory: " << out_img_dir << endl;
    mrpt::system::createDirectory(out_img_dir);

    // Create rawlog file ----------------------------------------------
    const string out_rawlog_fil = out_name + string(".rawlog");
    cout << "Creating rawlog: " << out_rawlog_fil << endl;
    mrpt::io::CFileGZOutputStream f_out(out_rawlog_fil);

    // Fill out the common field to all entries:
    CObservation3DRangeScan obs;
    obs.sensorLabel = "KINECT";
    obs.range_is_depth = true;  // Kinect style: ranges are actually depth
    // values, not Euclidean distances.

    // Range & RGB images are already registered and warped to remove
    // distortion:
    const double FOCAL = 525.0;
    obs.cameraParams.nrows = 480;
    obs.cameraParams.ncols = 640;
    obs.cameraParams.fx(FOCAL);
    obs.cameraParams.fy(FOCAL);
    obs.cameraParams.cx(319.5);
    obs.cameraParams.cy(239.5);
    obs.cameraParamsIntensity = obs.cameraParams;
    obs.relativePoseIntensityWRTDepth = mrpt::poses::CPose3D(
        0, 0, 0, mrpt::DEG2RAD(-90), 0,
        mrpt::DEG2RAD(-90));  // No translation between rgb & range
    // cameras, and rotation only due to XYZ
    // axes conventions.

    CObservationIMU obs_imu;
    obs_imu.sensorLabel = "KINECT_ACC";
    obs_imu.dataIsPresent[IMU_X_ACC] = true;
    obs_imu.dataIsPresent[IMU_Y_ACC] = true;
    obs_imu.dataIsPresent[IMU_Z_ACC] = true;

    // Go thru the data:
    unsigned int counter = 0;
    if (!only_ir && !only_rgb)
    {
        for (map<double, string>::const_iterator it_list_rgb = list_rgb.begin();
             it_list_rgb != list_rgb.end(); ++it_list_rgb)
        {
            cout << "Processing image " << counter << "\r";
            cout.flush();
            counter++;

            // This is not the most efficient solution in the world, but...come
            // on! it's just a rawlog converter tool!
            map<double, string>::const_iterator it_list_depth =
                find_closest(list_depth, it_list_rgb->first);

            const double At =
                std::abs(it_list_rgb->first - it_list_depth->first);
            if (At > (1. / KINECT_FPS) * .5)
            {
                cout << "\nWarning: Discarding observation for too separated "
                        "RGB/D timestamps: "
                     << At * 1e3 << " ms\n";
            }
            else
            {
                // OK, we accept this RGB-DEPTH pair:
                const double avrg_time =
                    .5 * (it_list_rgb->first + it_list_depth->first);
                obs.timestamp = mrpt::Clock::fromDouble(avrg_time);

                // RGB img:
                obs.hasIntensityImage = true;
                obs.intensityImage.loadFromFile(
                    src_path + string("/") + it_list_rgb->second);
                const string sRGBfile =
                    mrpt::format("%.06f_rgb.png", avrg_time);
                obs.intensityImage.saveToFile(
                    out_img_dir + string("/") + sRGBfile);
                obs.intensityImage.setExternalStorage(sRGBfile);

                // Depth:
                obs.hasRangeImage = true;
                obs.rangeImage_forceResetExternalStorage();
                mrpt::img::CImage depth_img;
                if (!depth_img.loadFromFile(
                        src_path + string("/") + it_list_depth->second))
                    throw std::runtime_error(
                        string("Error loading depth image!: ") +
                        it_list_depth->second);

                const unsigned int w = depth_img.getWidth();
                const unsigned int h = depth_img.getHeight();
                obs.rangeImage_setSize(h, w);

                for (unsigned int row = 0; row < h; row++)
                {
                    const uint16_t* ptr = depth_img.ptrLine<uint16_t>(row);
                    for (unsigned int col = 0; col < w; col++)
                        obs.rangeImage(row, col) = (*ptr++) * (1.f / 5000);
                }

                const string sDepthfile =
                    mrpt::format("%.06f_depth.bin", avrg_time);
                obs.rangeImage_convertToExternalStorage(
                    sDepthfile, out_img_dir + string("/"));

                // save:
                archiveFrom(f_out) << obs;

                // Search for acc data:
                map<double, vector<double>>::const_iterator it_list_acc =
                    find_closest(list_acc, avrg_time);
                const double At_acc =
                    std::abs(it_list_rgb->first - it_list_acc->first);
                if (At_acc < 1e-2)
                {
                    obs_imu.timestamp = mrpt::Clock::fromDouble(avrg_time);

                    obs_imu.rawMeasurements[IMU_X_ACC] = it_list_acc->second[0];
                    obs_imu.rawMeasurements[IMU_Y_ACC] = it_list_acc->second[1];
                    obs_imu.rawMeasurements[IMU_Z_ACC] = it_list_acc->second[2];

                    archiveFrom(f_out) << obs_imu;
                }
            }
        }
    }
    else if (only_ir)
    {
        for (map<double, string>::const_iterator it_list_depth =
                 list_depth.begin();
             it_list_depth != list_depth.end(); ++it_list_depth)
        {
            cout << "Processing image " << counter << "\r";
            cout.flush();
            counter++;

            const double avrg_time = it_list_depth->first;
            obs.timestamp = mrpt::Clock::fromDouble(it_list_depth->first);

            // Depth:
            obs.hasRangeImage = true;
            obs.rangeImage_forceResetExternalStorage();
            mrpt::img::CImage depth_img;
            if (!depth_img.loadFromFile(
                    src_path + string("/") + it_list_depth->second))
                throw std::runtime_error(
                    string("Error loading depth image!: ") +
                    it_list_depth->second);

            const unsigned int w = depth_img.getWidth();
            const unsigned int h = depth_img.getHeight();
            obs.rangeImage_setSize(h, w);

            for (unsigned int row = 0; row < h; row++)
            {
                const uint16_t* ptr = depth_img.ptrLine<uint16_t>(row);
                for (unsigned int col = 0; col < w; col++)
                    obs.rangeImage(row, col) = (*ptr++) * (1.f / 5000);
            }

            const string sDepthfile =
                mrpt::format("%.06f_depth.bin", avrg_time);
            obs.rangeImage_convertToExternalStorage(
                sDepthfile, out_img_dir + string("/"));

            // save:
            archiveFrom(f_out) << obs;
        }
    }
    else if (only_rgb)
    {
        for (map<double, string>::const_iterator it_list_rgb = list_rgb.begin();
             it_list_rgb != list_rgb.end(); ++it_list_rgb)
        {
            cout << "Processing image " << counter << "\r";
            cout.flush();
            counter++;

            const double avrg_time = it_list_rgb->first;
            obs.timestamp = mrpt::Clock::fromDouble(it_list_rgb->first);

            // RGB img:
            obs.hasIntensityImage = true;
            obs.intensityImage.loadFromFile(
                src_path + string("/") + it_list_rgb->second);
            const string sRGBfile = mrpt::format("%.06f_rgb.png", avrg_time);
            obs.intensityImage.saveToFile(out_img_dir + string("/") + sRGBfile);
            obs.intensityImage.setExternalStorage(sRGBfile);

            // save:
            archiveFrom(f_out) << obs;
        }
    }

    cout << "\nAll done!\n";
}

// ------------------------------------------------------
//                      MAIN
// ------------------------------------------------------
int main(int argc, char** argv)
{
    try
    {
        if (argc != 3)
        {
            cerr << "Usage: " << argv[0]
                 << " [PATH_TO_RGBD_DATASET] [OUTPUT_NAME]\n";
            return 1;
        }

        rgbd2rawlog(argv[1], argv[2]);

        return 0;
    }
    catch (const std::exception& e)
    {
        std::cerr << "MRPT error: " << mrpt::exception_to_str(e) << std::endl;
        return -1;
    }
}