这里需要两个包,一个是octomap的包,一个是放了怎么做这种图的程序包。一定要把这两个包放在同一目录下,不要放在catkin工作空间,因为octomap这个包会使catkin工作空间catkin_make失败。
1.编译
安装依赖
sudo apt-get install libqt4-dev qt4-qmake libqglviewer-dev
 

git clone https://github.com/OctoMap/octomap

下面就是对这个包进行编译,就是'
mkdir build
cd build
cmake ..
make
只所以新建一个build文件夹,是因为要在里面cmake的话,会把编译过程中产生的缓存文件放在这个目录,如果直接cmake .,会不太好整理文件。就像我每次重新编译的时候,直接把build文件下的所以东西都删掉就可以了。
2.查看示例地图

bin/octovis octomap/share/data/geb079.bt

大概长这样



这是一层楼的扫描图。
3.把点云地图转换成octomap
git clone https://github.com/gaoxiang12/octomap_tutor
编译这个包,依然是
mkdir build
cd build
cmake ..
make
编译之后会在bin目录下生成几个可执行程序。这个例子里的程序对应的是pcd2octomap源代码在src/pcd2octomap.cpp里:
#include <iostream>
#include <assert.h>
//pcl
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
//octomap 
#include <octomap/octomap.h>
using namespace std;
int main( int argc, char** argv )
{
    if (argc != 3)
    {
        cout<<"Usage: pcd2octomap <input_file> <output_file>"<<endl;
        return -1;
    }
    string input_file = argv[1], output_file = argv[2];
    pcl::PointCloud<pcl::PointXYZRGBA> cloud;
    pcl::io::loadPCDFile<pcl::PointXYZRGBA> ( input_file, cloud );
    cout<<"point cloud loaded, piont size = "<<cloud.points.size()<<endl;
    //声明octomap变量
    cout<<"copy data into octomap..."<<endl;
    // 创建八叉树对象,参数为分辨率,这里设成了0.05
    octomap::OcTree tree( 0.05 );
    for (auto p:cloud.points)
    {
        // 将点云里的点插入到octomap中
        tree.updateNode( octomap::point3d(p.x, p.y, p.z), true );
    }
    // 更新octomap
    tree.updateInnerOccupancy();
    // 存储octomap
    tree.writeBinary( output_file );
    cout<<"done."<<endl;
    return 0;
}
这个代码也比较简单.用的时候可以直接调用
bin/pcd2octomap data/sample.pcd data/sample.bt
这个程序的作用是把data下的点云数据sample.pcd转换成octovis格式的数据(它有两种格式一个是bt,一个是ot,其它格式octovis识别不了),生成之后的文件是sample.bt,
比较结果
pcl_viewer data/sample.pcd
../octomap-devel/bin/octovis data/sample.bt
 
4.加入色彩信息
点云是有色彩信息的,octomap也有色彩信息,是用ColorOcTree类来存储色彩信息.用的程序是/bin/pcd2colorOctomap,源代码在src目录下。
#include <iostream>
#include <assert.h>
//pcl
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
//octomap 
#include <octomap/octomap.h>
#include <octomap/ColorOcTree.h>
using namespace std;
int main( int argc, char** argv )
{
    if (argc != 3)
    {
        cout<<"Usage: pcd2colorOctomap <input_file> <output_file>"<<endl;
        return -1;
    }
    string input_file = argv[1], output_file = argv[2];
    pcl::PointCloud<pcl::PointXYZRGBA> cloud;
    pcl::io::loadPCDFile<pcl::PointXYZRGBA> ( input_file, cloud );
    cout<<"point cloud loaded, piont size = "<<cloud.points.size()<<endl;
    //声明octomap变量
    cout<<"copy data into octomap..."<<endl;
    // 创建带颜色的八叉树对象,参数为分辨率,这里设成了0.05
    octomap::ColorOcTree tree( 0.05 );
    for (auto p:cloud.points)
    {
        // 将点云里的点插入到octomap中
        tree.updateNode( octomap::point3d(p.x, p.y, p.z), true );
    }
    // 设置颜色
    for (auto p:cloud.points)
    {
        tree.integrateNodeColor( p.x, p.y, p.z, p.r, p.g, p.b );
    }
    // 更新octomap
    tree.updateInnerOccupancy();
    // 存储octomap, 注意要存成.ot文件而非.bt文件
    tree.write( output_file );
    cout<<"done."<<endl;
    return 0;
}
这个程序是对之前的pcd2octomap做了一些修改,比如OcTree改成ColorOcTree,以及调用integrateNodeColor来混合颜色等。
bin/pcd2colorOctomap data/sample.pcd data/sample.ot
 ../octomap-devel/bin/octovis data/sample.ot 
效果如下
 
 

5.拼接与转换

这里做的是把五张RGBD图拼接成octomap,程序是joinMap,源代码如下:
 
#include <iostream>
#include <vector>
 
// octomap 
#include <octomap/octomap.h>
#include <octomap/ColorOcTree.h>
#include <octomap/math/Pose6D.h>
 
// opencv 用于图像数据读取与处理
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
 
// 使用Eigen的Geometry模块处理3d运动
#include <Eigen/Core>
#include <Eigen/Geometry> 
 
// pcl
#include <pcl/common/transforms.h>
#include <pcl/point_types.h>
 
// boost.format 字符串处理
#include <boost/format.hpp>
 
using namespace std;
 
// 全局变量:相机矩阵
// 更好的写法是存到参数文件中,但为方便起见我就直接这样做了
float camera_scale  = 1000;
float camera_cx     = 325.5;
float camera_cy     = 253.5;
float camera_fx     = 518.0;
float camera_fy     = 519.0;
 
int main( int argc, char** argv )
{
    // 读关键帧编号
    ifstream fin( "./data/keyframe.txt" );
    vector<int> keyframes;
    vector< Eigen::Isometry3d > poses;
    // 把文件 ./data/keyframe.txt 里的数据读取到vector中
    while( fin.peek() != EOF )
    {
        int index_keyframe;
        fin>>index_keyframe;
        if (fin.fail()) break;
        keyframes.push_back( index_keyframe );
    }
    fin.close();
 
    cout<<"load total "<<keyframes.size()<<" keyframes. "<<endl;
 
    // 读关键帧姿态
    // 我的代码中使用了Eigen来存储姿态,类似的,也可以用octomath::Pose6D来做这件事
    fin.open( "./data/trajectory.txt" );
    while( fin.peek() != EOF )
    {
        int index_keyframe;
        float data[7]; // 三个位置加一个姿态四元数x,y,z, w,ux,uy,uz
        fin>>index_keyframe;
        for ( int i=0; i<7; i++ )
        {
            fin>>data[i];
            cout<<data[i]<<" ";
        }
        cout<<endl;
        if (fin.fail()) break;
        // 注意这里的顺序。g2o文件四元数按 qx, qy, qz, qw来存
        // 但Eigen初始化按照qw, qx, qy, qz来做
        Eigen::Quaterniond q( data[6], data[3], data[4], data[5] );
        Eigen::Isometry3d t(q);
        t(0,3) = data[0]; t(1,3) = data[1]; t(2,3) = data[2];
        poses.push_back( t );
    }
    fin.close();
 
    // 拼合全局地图
    octomap::ColorOcTree tree( 0.05 ); //全局map
 
    // 注意我们的做法是先把图像转换至pcl的点云,进行姿态变换,最后存储成octomap
    // 因为octomap的颜色信息不是特别方便处理,所以采用了这种迂回的方式
    // 所以,如果不考虑颜色,那不必转成pcl点云,而可以直接使用octomap::Pointcloud结构
 
    for ( size_t i=0; i<keyframes.size(); i++ )
    {
        pcl::PointCloud<pcl::PointXYZRGBA> cloud; 
        cout<<"converting "<<i<<"th keyframe ..." <<endl;
        int k = keyframes[i];
        Eigen::Isometry3d& pose = poses[i];
 
        // 生成第k帧的点云,拼接至全局octomap上
        boost::format fmt ("./data/rgb_index/%d.ppm" );
        cv::Mat rgb = cv::imread( (fmt % k).str().c_str() );
        fmt = boost::format("./data/dep_index/%d.pgm" );
        cv::Mat depth = cv::imread( (fmt % k).str().c_str(), -1 );
 
        // 从rgb, depth生成点云,运算方法见《一起做》第二讲
        // 第一次遍历用于生成空间点云
        for ( int m=0; m<depth.rows; m++ )
            for ( int n=0; n<depth.cols; n++ )
            {
                ushort d = depth.ptr<ushort> (m) [n];
                if (d == 0)
                    continue;
                float z = float(d) / camera_scale;
                float x = (n - camera_cx) * z / camera_fx;
                float y = (m - camera_cy) * z / camera_fy;
                pcl::PointXYZRGBA p;
                p.x = x; p.y = y; p.z = z;
 
                uchar* rgbdata = &rgb.ptr<uchar>(m)[n*3];
                uchar b = rgbdata[0];
                uchar g = rgbdata[1];
                uchar r = rgbdata[2];
 
                p.r = r; p.g = g; p.b = b;
                cloud.points.push_back( p ); 
            }
        // 将cloud旋转之后插入全局地图
        pcl::PointCloud<pcl::PointXYZRGBA>::Ptr temp( new pcl::PointCloud<pcl::PointXYZRGBA>() );
        pcl::transformPointCloud( cloud, *temp, pose.matrix() );
 
        octomap::Pointcloud cloud_octo;
        for (auto p:temp->points)
            cloud_octo.push_back( p.x, p.y, p.z );
 
        tree.insertPointCloud( cloud_octo, 
                octomap::point3d( pose(0,3), pose(1,3), pose(2,3) ) );
 
        for (auto p:temp->points)
            tree.integrateNodeColor( p.x, p.y, p.z, p.r, p.g, p.b );
    }
 
    tree.updateInnerOccupancy();
    tree.write( "./data/map.ot" );
 
    cout<<"done."<<endl;
 
    return 0;
 
}
这个程序是把RGBD图先转换成点云,再放到octotree,这样做出来的图是有颜色的。