视觉十四讲:第十二讲_八叉树地图

1.简介

把三维空间建模为许多小方块,如果把每一个小方块的每一个面平均切为两片,那么这个小方块就会变为同样大小的八个小方块,这个步骤不断重复,直到最后的方块大小达到建模的最高精度,这个过程,就是一颗八叉树。

上图显示了大立方体不断均分为八块,知道变成最小的方块为止,整个大方块可以看作根节点,最小的方块可以看作叶子节点。
在八叉树中,当某个方块的一个子节点都被占据或者不被占据时,就没必要展开这个节点,例如一开始地图为空白,那么只需要一个根节点,不需要完整的树,当向地图中添加信息时,由于实际的物体经常连在一起,空白的地方也经常连在一起,所以大多数八叉树节点不需要展开到叶子层面,这就大大减小了存储空间。
对于占据和空白来说,我们选择用概率的形式表达这件事情,设 \(y\in R\) 为概率对数值,x为0到1之间的概率:


可以看到,当y从负无穷变到正无穷时,x相应地从0变到了1。当y取0的时候,x取到了0.5。因此,我们不妨设y来表达节点是否被占据,当不断观测到占据时,让y增加一个值,否则就减小一个值。用数学形式来说,设某节点为n,观测数据为z。那么从开始到t时刻某点的概率对数值为 \(L(n|z_{1:t})\),那么t+1时刻为:

有了对数概率,我们就可以根据RGB-D数据,更新整个八叉树地图了,假设我们在RGB-D图像中观测到了某个像素带有深度d,这说明一件事:我们在深度值对应的空间点上观测到了一个占据数据,并且,从相机光心出发,到这个点的线段上,是没有物体的,利用这个信息,就可以很好的对八叉树地图进行更新。

#include <iostream>
#include <fstream>

using namespace std;

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <octomap/octomap.h>    // for octomap 

#include <Eigen/Geometry>
#include <boost/format.hpp>  // for formating strings

int main(int argc, char **argv) {
    vector<cv::Mat> colorImgs, depthImgs;    // 彩色图和深度图
    vector<Eigen::Isometry3d> poses;         // 相机位姿

    ifstream fin("./data/pose.txt");
    if (!fin) {
        cerr << "cannot find pose file" << endl;
        return 1;
    }

    for (int i = 0; i < 5; i++) {
        boost::format fmt("./data/%s/%d.%s"); //图像文件格式
        colorImgs.push_back(cv::imread((fmt % "color" % (i + 1) % "png").str()));
        depthImgs.push_back(cv::imread((fmt % "depth" % (i + 1) % "png").str(), -1)); // 使用-1读取原始图像

        double data[7] = {0};
        for (int i = 0; i < 7; i++) {
            fin >> data[i];
        }
        Eigen::Quaterniond q(data[6], data[3], data[4], data[5]);
        Eigen::Isometry3d T(q);
        T.pretranslate(Eigen::Vector3d(data[0], data[1], data[2]));
        poses.push_back(T);
    }

    // 计算点云并拼接
    // 相机内参 
    double cx = 319.5;
    double cy = 239.5;
    double fx = 481.2;
    double fy = -480.0;
    double depthScale = 5000.0;

    cout << "正在将图像转换为 Octomap ..." << endl;

    // octomap tree 
    octomap::OcTree tree(0.01); // 参数为分辨率

    for (int i = 0; i < 5; i++) {
        cout << "转换图像中: " << i + 1 << endl;
        cv::Mat color = colorImgs[i];
        cv::Mat depth = depthImgs[i];
        Eigen::Isometry3d T = poses[i];

        octomap::Pointcloud cloud;  // the point cloud in octomap 

        for (int v = 0; v < color.rows; v++)
            for (int u = 0; u < color.cols; u++) {
                unsigned int d = depth.ptr<unsigned short>(v)[u]; // 深度值
                if (d == 0) continue; // 为0表示没有测量到
                Eigen::Vector3d point;
                point[2] = double(d) / depthScale;
                point[0] = (u - cx) * point[2] / fx;
                point[1] = (v - cy) * point[2] / fy;
                Eigen::Vector3d pointWorld = T * point;
                // 将世界坐标系的点放入点云
                cloud.push_back(pointWorld[0], pointWorld[1], pointWorld[2]);
            }

        // 将点云存入八叉树地图,给定原点,这样可以计算投射线
        tree.insertPointCloud(cloud, octomap::point3d(T(0, 3), T(1, 3), T(2, 3)));
    }

    // 更新中间节点的占据信息并写入磁盘
    tree.updateInnerOccupancy();
    cout << "saving octomap ... " << endl;
    tree.writeBinary("octomap.bt");
    return 0;
}

cmake_minimum_required(VERSION 2.8)

set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_FLAGS "-std=c++11 -O2")

# opencv 
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

# eigen 
include_directories("/usr/include/eigen3/")

# pcl 
find_package(PCL REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
add_definitions(${PCL_DEFINITIONS})

# octomap 
find_package(octomap REQUIRED)
include_directories(${OCTOMAP_INCLUDE_DIRS})

add_executable(octomap_mapping octomap_mapping.cpp)
target_link_libraries(octomap_mapping ${OpenCV_LIBS} ${PCL_LIBRARIES} ${OCTOMAP_LIBRARIES})

posted @ 2020-09-24 10:50  penuel  阅读(898)  评论(0编辑  收藏  举报