OctoMap库 简单解析

OctoMap 八叉树地图库简单解析

Posted by YuCong on July 13, 2022

OctoMap 八叉树地图库


Created 2022.07.11 by Cong Yu; Last modified: 2022.08.19-V2.5.7 -> 2022.08.29-V2.9.0

Contact: windmillyucong@163.com

Copyleft! 2022 Cong Yu. Some rights reserved.


OctoMap

基本概念

  • 八叉树结构
  • 叶子节点的分辨率
  • 每个node都有一个数据描述是否被占据
    • 最简单的情况下,0空,1占据,但是没有意义
    • 通常使用0-1之间的浮点数表示被占据的概率
  • 好处
    • 当某个节点的子节点都“占据”或者“不占据”时,且概率都相等时,可以剪枝
    • 非常节省存储空间
      • 实测 8M的pcd 转化为 0.01分辨率的octmap只有100多k
    • 一个node本身表征了一定的占据空间,非常适合于做碰撞检测
    • 树结构可以完成非常快速的检索与搜寻
    • 对比pcd,可以描述运动信息(擦除机制)
  • 缺点
    • 信息损失
    • 树结构需要维护

八叉树的更新

0. 库的编译与安装

1
sudo apt-get install libqglviewer-dev-qt5

octovis

1
sudo apt-get install octovis

也可以自己编译安装octovis, 源码 https://github.com/OctoMap/octomap/tree/devel/octovis

octomap

1
2
3
4
5
6
7
8
9
git clone https://github.com/Octomap/octomap.git

cd octomap
git checkout v1.9.0
mkdir build
cd build
camke ..
make -j4
sudo make install

1. Data Structures

细节详见 https://raw.githubusercontent.com/YuYuCong/BlogImg/develop/post_octmap/OcTree.drawio.png

img

1.0 类间基本关系

uml_overview

注:UML类图

  • 简单理解分为两类:
    • tree结构类
    • tree里面的数据结构类
  • AbstractOctree: 万物起始。在这个类中定义了一些八叉树应该有的纯虚方法,包括但不限于:
    • 设置与读取八叉树分辨率
    • 获取八叉树的type string
    • prune/expand/clear 等等方法
  • OcTreeBase: 这个类在继承了AbstractOcTree的基础上,完整实现了octree的数据结构,包括八叉树的search\光线投射\更新node等等。
  • OccupancyOcTreeBase: 在继承了OcTreeBase的基础上,引入了logOdds来表示node的占据概率。新增了insertPointcloud方法来一次性插入点云。
  • OcTree/ColorOcTree:继承OccupancyOcTreeBase,是OcTree的具体实现,基本上没有新加什么东西,ColorOcTree给Node增加了颜色方法。

1.1 Tree类

1.1.1 AbstractOcTree

Inheritance graph

  • file
    • octomap/octomap/include/octomap/AbstractOcTree.h
    • octomap/octomap/src/AbstractOcTree.cpp
方法
  • 纯虚方法

    • 读取八叉树分辨率

    • 获取八叉树的type string

    • prune/expand/clear 等等方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    AbstractOcTree();
    virtual ~AbstractOcTree() {};

    /// returns actual class name as string for identification
    virtual std::string getTreeType() const = 0;



    virtual double getResolution() const = 0;
    virtual void setResolution(double res) = 0;
    virtual size_t size() const = 0;
    virtual size_t memoryUsage() const = 0;
    virtual size_t memoryUsageNode() const = 0;
    virtual void getMetricMin(double& x, double& y, double& z) = 0;
    virtual void getMetricMin(double& x, double& y, double& z) const = 0;
    virtual void getMetricMax(double& x, double& y, double& z) = 0;
    virtual void getMetricMax(double& x, double& y, double& z) const = 0;
    virtual void getMetricSize(double& x, double& y, double& z) = 0;

    virtual void prune() = 0;
    virtual void expand() = 0;
    virtual void clear() = 0;

关于文件读写的方法
  • write
  • read
  • readData 纯虚方法
  • writeData 纯虚方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    /// Write file header and complete tree to file (serialization)
    bool write(const std::string& filename) const;   

    /**
     * Read the file header, create the appropriate class and deserialize.
     * This creates a new octree which you need to delete yourself. If you
     * expect or requre a specific kind of octree, use dynamic_cast afterwards:
     * @code
     * AbstractOcTree* tree = AbstractOcTree::read("filename.ot");
     * OcTree* octree = dynamic_cast<OcTree*>(tree);
     *
     * @endcode
     */
    static AbstractOcTree* read(const std::string& filename);

    /**
     * Read all nodes from the input stream (without file header),
     * for this the tree needs to be already created.
     * For general file IO, you
     * should probably use AbstractOcTree::read() instead.
     */
    virtual std::istream& readData(std::istream &s) = 0;

    /// Write complete state of tree to stream (without file header) unmodified.
    /// Pruning the tree first produces smaller files (lossless compression)
    virtual std::ostream& writeData(std::ostream &s) const = 0;
  • write() 的实现
    • 实现了文件头的书写格式,然后调用纯虚函数writeData()写实际数据
1
2
3
4
5
6
7
8
9
10
11
  bool AbstractOcTree::write(std::ostream &s) const{
    s << fileHeader <<"\n# (feel free to add / change comments, but leave the first line as it is!)\n#\n";
    s << "id " << getTreeType() << std::endl;
    s << "size "<< size() << std::endl;
    s << "res " << getResolution() << std::endl;
    s << "data" << std::endl;

    // write the actual data:
    writeData(s);
    return true;
  }
  • read()的实现
    • 实现了流的check,文件头部格式的读取校验,创建Tree,然后调用纯虚函数readData()读取具体数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  AbstractOcTree* AbstractOcTree::read(std::istream &s){

    // check if first line valid:
    std::string line;
    std::getline(s, line);
    if (line.compare(0,fileHeader.length(), fileHeader) !=0){
      OCTOMAP_ERROR_STR("First line of OcTree file header does not start with \""<< fileHeader);
      return NULL;
    }

    std::string id;
    unsigned size;
    double res;
    if (!AbstractOcTree::readHeader(s, id, size, res))
      return NULL;


    // otherwise: values are valid, stream is now at binary data!
    OCTOMAP_DEBUG_STR("Reading octree type "<< id);

    AbstractOcTree* tree = createTree(id, res);

    if (tree){
      if (size > 0)
        tree->readData(s);

      OCTOMAP_DEBUG_STR("Done ("<< tree->size() << " nodes)");
    }

    return tree;
  }
特别提醒
  • createTree的时候会检查classIDMapping的表,对于自定义的tree类型需要用户自行实现注册
  • createTree的实现:
1
2
3
4
5
6
7
8
9
10
11
12
  AbstractOcTree* AbstractOcTree::createTree(const std::string class_name, double res){
    std::map<std::string, AbstractOcTree*>::iterator it = classIDMapping().find(class_name);
    if (it == classIDMapping().end()){
      OCTOMAP_ERROR("Could not create octree of type %s, not in store in classIDMapping\n", class_name.c_str());
      return NULL;
    } else {
      AbstractOcTree* tree = it->second->create();

      tree->setResolution(res);
      return tree;
    }
  }

此外,用户还需要实现为自定义的类继承create()方法

类型表
  • std::map<string, AbstractOcTree*>
  • 类型注册函数: registerTreeType()
1
2
3
4
5
6
7
  private:
    /// create private store, Construct on first use
    static std::map<std::string, AbstractOcTree*>& classIDMapping();

  protected:
    static void registerTreeType(AbstractOcTree* tree);

1
2
3
4
5
6
7
8
9
10
11
  std::map<std::string, AbstractOcTree*>& AbstractOcTree::classIDMapping(){
    // we will "leak" the memory of the map and all trees until program exits,
    // but this ensures all static objects are there as long as needed
    // http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.15
    static std::map<std::string, AbstractOcTree*>* map = new std::map<std::string, AbstractOcTree*>();
    return *map;
  }

  void AbstractOcTree::registerTreeType(AbstractOcTree* tree){
    classIDMapping()[tree->getTreeType()] = tree;
  }

1.1.2 OcTreeBaseImpl

Inheritance graph

  • INTERFACE 是 AbstractOcTree 或者 AbstractOccupancyOcTree

    1
    2
    3
    4
    
    /** 
     * \tparam INTERFACE Interface to be derived from, should be either
     *    AbstractOcTree or AbstractOccupancyOcTree
     */
    
  • file

    • octomap/include/octomap/OcTreeBaseImpl.h
    • octomap/include/octomap/OcTreeBaseImpl.hxx
  • 同样是虚基类
    1
    2
    
      OcTreeBaseImpl(double resolution);
      virtual ~OcTreeBaseImpl();
    
  • 大小有限制
    • 最多16层
    • 分辨率最小为0.01
1
2
3
4
  /* This tree implementation currently has a maximum depth of 16
   * nodes. For this reason, coordinates values have to be, e.g.,
   * below +/- 327.68 meters (2^15) at a maximum resolution of 0.01m.
   */
getTreeDepth()
  • 获取树的深度
1
2
3
    NODE * octomap::OcTreeBaseImpl< NODE, I >::search	(	const point3d & 	value,
    unsigned int 	depth = 0 
    )		const
  • Search node at specified depth given a 3d point (depth=0: search full tree depth) You need to check if the returned node is NULL, since it can be in unknown space.
  • Returns: pointer to node if found, NULL otherwise.
  • 搜索某位置的子节点
getMetricMin()

获取最小值。todo(congyu)是有效node边界的最左边?还是tree中数据的最小值?

getMetricMax()

获取最大值

getMetricSize()

= getMetricMax() - getMetricMin()

getRoot()
prune()
expand()
关于文件读写的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    // file IO

    /**
     * Read all nodes from the input stream (without file header),
     * for this the tree needs to be already created.
     * For general file IO, you
     * should probably use AbstractOcTree::read() instead.
     */
    std::istream& readData(std::istream &s);

    /// Write complete state of tree to stream (without file header) unmodified.
    /// Pruning the tree first produces smaller files (lossless compression)
    std::ostream& writeData(std::ostream &s) const;

  • readData() 和 writeData() 里面分别调用 readNodesRecurs() 和 writeNodesRecurs()
1
2
3
4
5
6
    /// recursive call of readData()
    std::istream& readNodesRecurs(NODE*, std::istream &s);
    
    /// recursive call of writeData()
    std::ostream& writeNodesRecurs(const NODE*, std::ostream &s) const;
    
  • readNodesRecurs() 和 writeNodesRecurs() 的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
  
  template <class NODE,class I>
  std::ostream& OcTreeBaseImpl<NODE,I>::writeData(std::ostream &s) const{
    if (root)
      writeNodesRecurs(root, s);

    return s;
  }
    
  template <class NODE,class I>
  std::ostream& OcTreeBaseImpl<NODE,I>::writeNodesRecurs(const NODE* node, std::ostream &s) const{
    node->writeData(s);
    
    // 1 bit for each children; 0: empty, 1: allocated
    std::bitset<8> children;
    for (unsigned int i=0; i<8; i++) {
      if (nodeChildExists(node, i))
        children[i] = 1;
      else
        children[i] = 0;
    }

    char children_char = (char) children.to_ulong();
    s.write((char*)&children_char, sizeof(char));

//     std::cout << "wrote: " << value << " "
//               << children.to_string<char,std::char_traits<char>,std::allocator<char> >() 
//               << std::endl;

    // recursively write children
    for (unsigned int i=0; i<8; i++) {
      if (children[i] == 1) {
        this->writeNodesRecurs(getNodeChild(node, i), s);
      }
    }
    
    return s;
  }


  template <class NODE,class I>
  std::istream& OcTreeBaseImpl<NODE,I>::readData(std::istream &s) {

    if (!s.good()){
      OCTOMAP_WARNING_STR(__FILE__ << ":" << __LINE__ << "Warning: Input filestream not \"good\"");
    }

    this->tree_size = 0;
    size_changed = true;

    // tree needs to be newly created or cleared externally
    if (root) {
      OCTOMAP_ERROR_STR("Trying to read into an existing tree.");
      return s;
    }

    root = new NODE();
    readNodesRecurs(root, s);
    
    tree_size = calcNumNodes();  // compute number of nodes
    return s;
  }
  

  template <class NODE,class I>
  std::istream& OcTreeBaseImpl<NODE,I>::readNodesRecurs(NODE* node, std::istream &s) {
    
    node->readData(s);
    
    char children_char;
    s.read((char*)&children_char, sizeof(char));
    std::bitset<8> children ((unsigned long long) children_char);

    //std::cout << "read: " << node->getValue() << " "
    //            << children.to_string<char,std::char_traits<char>,std::allocator<char> >()
    //            << std::endl;

    for (unsigned int i=0; i<8; i++) {
      if (children[i] == 1){
        NODE* newNode = createNodeChild(node, i);
        readNodesRecurs(newNode, s);
      }
    }
    
    return s;
  }

关于光追(Raytracing)的方法
computeRayKeys()
1
2
3
4
5
6
7
8
9
10
11
12
13
   /**
    * Traces a ray from origin to end (excluding), returning an
    * OcTreeKey of all nodes traversed by the beam. You still need to check
    * if a node at that coordinate exists (e.g. with search()).
    *
    * @param origin start coordinate of ray
    * @param end end coordinate of ray
    * @param ray KeyRay structure that holds the keys of all nodes traversed by the ray, excluding "end"
    * @return Success of operation. Returning false usually means that one of the coordinates is out of the OcTree's range
    */
    bool computeRayKeys(const point3d& origin, const point3d& end, KeyRay& ray) const;


  • 更推荐使用computeRay(),该函数更快
  • 为什么更快,看一看具体实现
computeRay()
1
2
3
4
5
6
7
8
9
10
11
12
   /**
    * Traces a ray from origin to end (excluding), returning the
    * coordinates of all nodes traversed by the beam. You still need to check
    * if a node at that coordinate exists (e.g. with search()).
    * @note: use the faster computeRayKeys method if possible.
    * 
    * @param origin start coordinate of ray
    * @param end end coordinate of ray
    * @param ray KeyRay structure that holds the keys of all nodes traversed by the ray, excluding "end"
    * @return Success of operation. Returning false usually means that one of the coordinates is out of the OcTree's range
    */
    bool computeRay(const point3d& origin, const point3d& end, std::vector<point3d>& ray);

补:KeyRay

  • KeyRay类:光线的表达

  • 实现:是一个vector的OcTreeKey

    1
    2
    
      private:
        std::vector<OcTreeKey> ray;
    

1.1.3 OcTreeBase

Inheritance graph

  • file
    • octomap/include/octomap/OcTreeBase.h
  • 实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      template <class NODE>
      class OcTreeBase : public OcTreeBaseImpl<NODE,AbstractOcTree> {
      public:
        OcTreeBase<NODE>(double res) : OcTreeBaseImpl<NODE,AbstractOcTree>(res) {};
      
        /// virtual constructor: creates a new object of same type
        /// (Covariant return type requires an up-to-date compiler)
        OcTreeBase<NODE>* create() const {return new OcTreeBase<NODE>(this->resolution); }
        std::string getTreeType() const {return "OcTreeBase";}
      };
    
  • 简单包装了一下OcTreeBaseImpl

  • 所有的具体方法都在上文的虚类OcTreeBaseImpl里面

  • 具体的tree类继承自这个类,用户定义的tree类也可以继承于OcTreeBase, eg:

    1
    2
    3
    
    class MyOcTree : public octomap::OcTreeBase<MyOcTreeNode> {
        // balabala...
    };
    

1.1.4 AbstractOccupancyOcTree

Inheritance graph

  • file

    • octomap/src/AbstractOccupancyOcTree.cpp
    • octomap/include/octomap/AbstractOccupancyOcTree.h
  • Interface class for all octree types that store occupancy. 是所有存储占用信息的八叉树的虚基类

  • 有许多logodds相关的接口

  • 构造函数里面有设置默认的概率更新参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      AbstractOccupancyOcTree::AbstractOccupancyOcTree(){
        // some sane default values:
        setOccupancyThres(0.5);   // = 0.0 in logodds
        setProbHit(0.7);          // = 0.85 in logodds
        setProbMiss(0.4);         // = -0.4 in logodds
      
        setClampingThresMin(0.1192); // = -2 in log odds
        setClampingThresMax(0.971); // = 3.5 in log odds
      }
    
  • 实现了writeBinary() readBinary() 等文件IO方法

文件IO
readBinary()
  • 从.bt二进制文件读取octree
writeBinary
  • 保存为.bt文件
readBinaryData()
writeBinaryData()

// todo(congyu)

isNodeOccupied()
1
2
3
4
/// queries whether a node is occupied according to the tree's parameter for "occupancy"
inline bool isNodeOccupied(const OcTreeNode* occupancyNode) const{
  return (occupancyNode->getLogOdds() >= this->occ_prob_thres_log);
}
updateNode()
1
2
3
4
5
    virtual OcTreeNode* octomap::AbstractOccupancyOcTree::updateNode	(	const point3d & 	value,
    bool 	occupied,
    bool 	lazy_eval = false 
    )		
    pure virtual

参数:

parameters  
value 3d coordinate of the NODE that is to be updated 3d坐标,endpoint
occupied true if the node was measured occupied, else false true表示占据,false表示free
lazy_eval whether update of inner nodes is omitted after the update (default: false). This speeds up the insertion, but you need to call updateInnerOccupancy() when done.
1
2
3
4
5
6
7
template <class NODE>
NODE* OccupancyOcTreeBase<NODE>::updateNode(const point3d& value, bool occupied, bool lazy_eval) {
	OcTreeKey key;
    if (!this->coordToKeyChecked(value, key))
	    return NULL;
	return updateNode(key, occupied, lazy_eval);
}
概率更新相关参数设置
  • setProbHit()

    • sets the probability for a “hit” (will be converted to logodds) - sensor model
  • setProMiss()

    • sets the probability for a “miss” (will be converted to logodds) - sensor model
  • setClampingThresMax()

    • sets the maximum threshold for occupancy clamping (sensor model)
  • setClampingThresMin()

    • sets the minimum threshold for occupancy clamping (sensor model)
  • setOccupancyThres()

    • sets the threshold for occupancy (sensor model) 设置occupancy参数

默认参数:

1
2
3
4
5
6
setOccupancyThres(0.5);   // = 0.0 in logodds  
setProbHit(0.7);          // = 0.85 in logodds  
setProbMiss(0.4);         // = -0.4 in logodds  
  
setClampingThresMin(0.1192); // = -2 in log odds  
setClampingThresMax(0.971); // = 3.5 in log odds

具体的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
     //-- parameters for occupancy and sensor model:
     
    /// sets the threshold for occupancy (sensor model)
    void setOccupancyThres(double prob){occ_prob_thres_log = logodds(prob); }
    /// sets the probability for a "hit" (will be converted to logodds) - sensor model
    void setProbHit(double prob){prob_hit_log = logodds(prob); assert(prob_hit_log >= 0.0);}
    /// sets the probability for a "miss" (will be converted to logodds) - sensor model
    void setProbMiss(double prob){prob_miss_log = logodds(prob); assert(prob_miss_log <= 0.0);}
    /// sets the minimum threshold for occupancy clamping (sensor model)
    void setClampingThresMin(double thresProb){clamping_thres_min = logodds(thresProb); }
    /// sets the maximum threshold for occupancy clamping (sensor model)
    void setClampingThresMax(double thresProb){clamping_thres_max = logodds(thresProb); }

    /// @return threshold (probability) for occupancy - sensor model
    double getOccupancyThres() const {return probability(occ_prob_thres_log); }
    /// @return threshold (logodds) for occupancy - sensor model
    float getOccupancyThresLog() const {return occ_prob_thres_log; }

    /// @return probability for a "hit" in the sensor model (probability)
    double getProbHit() const {return probability(prob_hit_log); }
    /// @return probability for a "hit" in the sensor model (logodds)
    float getProbHitLog() const {return prob_hit_log; }
    /// @return probability for a "miss"  in the sensor model (probability)
    double getProbMiss() const {return probability(prob_miss_log); }
    /// @return probability for a "miss"  in the sensor model (logodds)
    float getProbMissLog() const {return prob_miss_log; }

    /// @return minimum threshold for occupancy clamping in the sensor model (probability)
    double getClampingThresMin() const {return probability(clamping_thres_min); }
    /// @return minimum threshold for occupancy clamping in the sensor model (logodds)
    float getClampingThresMinLog() const {return clamping_thres_min; }
    /// @return maximum threshold for occupancy clamping in the sensor model (probability)
    double getClampingThresMax() const {return probability(clamping_thres_max); }
    /// @return maximum threshold for occupancy clamping in the sensor model (logodds)
    float getClampingThresMaxLog() const {return clamping_thres_max; }



1.1.5 OccupancyOcTreeBase

Inheritance graph

  • file
    • octomap/include/octomap/OccupancyOcTreeBase.h
    • octomap/include/octomap/OccupancyOcTreeBase.hxx
  • 比较重要!!!!!
  • Base implementation for Occupancy Octrees (e.g. for mapping). 是八叉占据地图的虚基类
  • 实现了很多重要函数
insertPointCloud()
  • 点云插入函数 Integrate a Pointcloud (in global reference frame), parallelized with OpenMP.

    1
    2
    3
    4
    5
    6
    7
    
    template<class NODE >
    void octomap::OccupancyOcTreeBase< NODE >::insertPointCloud	(	const Pointcloud & 	scan,
    const octomap::point3d & 	sensor_origin,
    double 	maxrange = -1.,
    bool 	lazy_eval = false,
    bool 	discretize = false 
    )
    
insertPointCloudRays()
setNodeValue()
updateNode()
toMaxLikelihood()
insertRay()
castRay()
1
2
3
4
5
6
    virtual bool octomap::OccupancyOcTreeBase< OcTreeNode >::castRay(const point3d & origin,
                                                                   const point3d & direction,
                                                                   point3d & end,
                                                                   bool ignoreUnknownCells = false,
                                                                   double maxRange = -1.0 
                                                                   ) const
  • 计算光线

    • 参数

      • origin: 光束起点,是世界坐标系下sensor(可以是RGBD传感器、也可以是三维激光雷达)的位置
      • direction: 光束的方向向量
        • 不需要归一化,castRay函数在内部会为我们完成这件事
      • end: 返回值,光线击中node的中心位置坐标
    • 返回: true if an occupied cell was hit, false if the maximum range or octree bounds are reached, or if an unknown node was hit.

    • 函数的参数origin(光束起点)和参数end(传感器末端击中点)都是世界坐标系下的表达!

    • 支持并行,可以使用openmp加速

getRayIntersection()
  • Retrieves the entry point of a ray into a voxel.

    This is the closest intersection point of the ray originating from origin and a plane of the axis aligned cube.

    返回光线在体素外壳的进入点的坐标

1
2
3
4
5
6
    bool octomap::OccupancyOcTreeBase< NODE >::getRayIntersection	(	const point3d & 	origin,
    const point3d & 	direction,
    const point3d & 	center,
    point3d & 	intersection,
    double 	delta = 0.0 
    )		const
Parameters    
[in] origin Starting point of ray
[in] direction A vector pointing in the direction of the raycast. Does not need to be normalized.
[in] center The center of the voxel where the ray terminated. This is the output of castRay.
[out] intersection The entry point of the ray into the voxel, on the voxel surface.
[in] delta A small increment to avoid ambiguity of beeing exactly on a voxel surface. A positive value will get the point out of the hit voxel, while a negative valuewill get it inside.
getNormals()
  • 没懂 todo(congyu)
updateInnerOccupancy()
  • 更新八叉树的父节点的数值,避免重复计算
概率更新相关的方法
1
2
3
4
5
6
7
8
9
10
11
    /// integrate a "hit" measurement according to the tree's sensor model
    virtual void integrateHit(NODE* occupancyNode) const;
    /// integrate a "miss" measurement according to the tree's sensor model
    virtual void integrateMiss(NODE* occupancyNode) const;
    /// update logodds value of node by adding to the current value.
    virtual void updateNodeLogOdds(NODE* occupancyNode, const float& update) const;

    /// converts the node to the maximum likelihood value according to the tree's parameter for "occupancy"
    virtual void nodeToMaxLikelihood(NODE* occupancyNode) const;
    /// converts the node to the maximum likelihood value according to the tree's parameter for "occupancy"
    virtual void nodeToMaxLikelihood(NODE& occupancyNode) const;

1.1.6 OcTree

  • file
    • octomap/src/OcTree.cpp
    • octomap/include/octomap/OcTree.h

img

  • OcTree 作为最顶层的类提供操作所有基本数据结构的方法

  • 补充说明
    • 在目前的PC机上,github提供的源码实现是通过OpenMP并行计算完成的
    • 而Octomap建图还经常使用在无人机上,机载处理器无法像在PC机上实现并行计算,因此ETH ASL实验室的工程人员专门为此开发了适合于无人机应用的Octomap建图程序包volumetric_mapping
  • 是OccupancyOcTreeBase的一种具体实现
1
2
3
4
5
6
7
8
  /**
   * octomap main map data structure, stores 3D occupancy grid map in an OcTree.
   * Basic functionality is implemented in OcTreeBase.
   *
   */
  class OcTree : public OccupancyOcTreeBase <OcTreeNode> {
      
  };
  • OcTree里面啥也没有,是简单继承了OccupancyOcTreeBase, 核心代码实现都在 octomap::OccupancyOcTreeBase、octomap::OcTreeBaseImpl两个类里面

  • 构造

    c++ octomap::OcTree tree(1.0); // create empty tree with resolution 1.0

    • Node的边界是左闭右开
    • tree(1.0) 意味着 [0.0, 1.0) 为一个node, [1.0, 2.0) 为一个node

1.1.7 ColorOcTreeNode

todo(congyu)

integrateNodeColor()
  • 设置颜色
1
    tree.integrateNodeColor(x, y, z, r, g, b);

1.1.8 CountingOcTree

Inheritance graph

  • file
    • octomap/src/CountingOcTree.cpp
    • octomap/include/octomap/CountingOcTree.h

注:

现在的源码里面是不支持counting_tree的IO,没有实现counting_tree的create() 和 getTreeType()方法

1.2 Node类

1.2.1 AbstractOcTreeNode

Inheritance graph

  • 所有Node的基类
  • file
    • octomap/include/octomap/OcTreeDataNode.h

其实是空的:

1
2
3
  class AbstractOcTreeNode {

  };

1.2.2 OcTreeDataNode

Inheritance graph

  • file
    • octomap/include/octomap/OcTreeDataNode.h
  • Basic node in the OcTree that can hold arbitrary data of type T in value. This is the base class for nodes used in an OcTree. The used implementation for occupancy mapping is in OcTreeNode.
  • \tparam T: data to be stored in the node (e.g. a float for probabilities)
  • Note: If you derive a class (directly or indirectly) from OcTreeDataNode, you have to implement (at least) the following functions to avoid slicing errors and memory-related bugs: createChild(), getChild(), getChild() const, expandNode(). See ColorOcTreeNode in ColorOcTree.h for an example.
  • 由该类可继承出用户自已的Node类,要求必须至少实现以下接口: createChild(), getChild(), getChild() const, expandNode().

1.2.3 OcTreeNode

Inheritance graph

  • file
    • octomap/include/octomap/OcTreeNode.h
    • octomap/src/OcTreeNode.cpp
  • Nodes to be used in OcTree. They represent 3d occupancy grid cells. “value” stores their log-odds occupancy.
  • OcTreeNode 存储的是log-odd数值,并不是概率值
  • Node的边界是左闭右开
getOccupancy()
  • 返回occupancy probability
getLogOdds()
  • 返回 log odds representation of occupancy probability of node
addValue()
  • adds p to the node’s logOdds value (with no boundary / threshold checking!)

1.2.4 ColorOcTreeNode

Inheritance graph

  • file
    • octomap/include/octomap/ColorOcTree.h
    • octomap/src/ColorOcTree.cpp

1.2.5 CountingOcTreeNode

Inheritance graph

  • file
    • octomap/src/CountingOcTree.cpp
    • octomap/include/octomap/CountingOcTree.h
  • 一个简单的计数tree
  • 父节点的value等于子节点的value的合

1.3 OcTreeKey

  • is a container class for internal key addressing.
  • The keys count the number of cells (voxels) from the origin as discrete address of a voxel.
  • 八叉树节点的关键字查询
  • 其实就是离散三维点与node元之间的转换
  • todo(congyu) 详细接口

1.4 OctoTreeParam

参数类,比较简单

todo(congyu)

1.5 iterator

迭代器

1
2
3
4
5
6
7
8
9
  octomap::OcTree* tree = new octomap::OcTree(1.0);  

  unsigned int maxDepth = tree->getTreeDepth();
  std::vector<octomap::OcTreeNode*> collapsed_occ_nodes;
  for (octomap::OcTree::iterator it = tree->begin(); it != tree->end(); ++it) {
    if (tree->isNodeOccupied(*it) && it.getDepth() < maxDepth) {
      collapsed_occ_nodes.push_back(&(*it));
    }
  }
getDepth()
  • 返回深度
getCoordinate()
  • return the center coordinate of the current node 返回node的中心位置坐标

1.6 ScanGraph

  • A ScanGraph is a collection of ScanNodes, connected by ScanEdges. Each ScanNode contains a 3D scan performed from a pose.
readPlainASCII()
  • 读取 InputFile.log

  • 文件格式:

1
2
3
4
5
6
      std::cerr << "The log file needs to be in the format of:\n"
          << "NODE x y z roll pitch yaw\n"
          << "x y z\nx y z\n...\n"
          << "NODE x y z roll pitch yaw\n"
          << "x y z\n...\n\n"
          << "Lines starting with '#' or empty lines are ignored.\n\n";
writeBinary()
  • 写入 OutputFile.graph

1.7 ScanNode

todo(congyu)

1.8 ScanEdge

todo(congyu)

1.9 文件格式

example: http://docs.ros.org/en/melodic/api/octomap/html/convert__octree_8cpp_source.html

.bt

the occupied voxels of a binary OctoMap file.

.bt.wrl

VRML2.0 file.

.ot

OctoMap octree file formats. 最新的文件格式

.graph

scan graph file (point clouds with poses).

.log

a plain text log file.

Examples and Tests


Contact

Feel free to contact me windmillyucong@163.com anytime for anything.

License

Creative Commons BY-SA 4.0

CC0