详解八叉树地图
个人博客:http://www.chenjianqu.com/原文链接:http://www.chenjianqu.com/show-102.html八叉树地图八叉树地图(OctoMap)就是一种灵活的、压缩的、又能随时更新的地图。八叉树示意图如下:一个大立方体不断地均匀分成八块,直到变成最小的方块为止。整个大方块可以看成是根节点,而最小的块可以看作是“叶子节...
个人博客:http://www.chenjianqu.com/
原文链接:http://www.chenjianqu.com/show-102.html
八叉树地图
八叉树地图(OctoMap)就是一种灵活的、压缩的、又能随时更新的地图。八叉树示意图如下:
一个大立方体不断地均匀分成八块,直到变成最小的方块为止。整个大方块可以看成是根节点,而最小的块可以看作是“叶子节点”。于是,在八叉树中,当我们由下一层节点往上走一层时,地图的体积就能扩大八倍。某个方块的所有子节点都被占据或都不被占据时,就没必要展开这个节点。
八叉树的节点存储了它是否被占据的信息。可以用0表示空白,1表示占据。更好的选择是用概率形式表达某节点是否被占据的事情。比方说,用一个浮点数 x∈[0,1]来表达。这个x一开始取 0.5。如果不断观测到它被占据,那么让这个值不断增加;反之,如果不断观测到它是空白,那就让它不断减小即可,这样可以动态地建模了地图中的障碍物信息。X不断减小或增加可能会超出[0,1]的范围,因此更好的方式是使用概率对数值来描述:y = logit(x) = log(x/(1-x)) ,反变换为 x = logit-1(y) = exp(y)/(exp(y)+1) 。存储 y 来表达节点是否被占据。当不断观测到“占据”时,让 y增加一个值;否则就让 y 减小一个值。当查询概率时,再用逆 logit 变换,将 y 转换至概率即可。
假设我们在RGB-D 图像中观测到某个像素带有深度 d,这说明了一件事:我们在深度值对应的空间点上观察到了一个占据数据,并且,从相机光心出发,到这个点的线段上,应该是没有物体的(否则会被遮挡)。利用这个信息,可以很好地对八叉树地图进行更新,并且能处理运动的结构。
代码实现
使用的库为Octomap,地址为:https://github.com/OctoMap/octomap ,包含 octomap 地图与 octovis(一个可视化程序),二者都是 cmake 工程,分别编译安装即可。
CMakeLists.txt
cmake_minimum_required(VERSION 2.6) project(octomaptest) set( CMAKE_BUILD_TYPE Release ) set( CMAKE_CXX_FLAGS "-std=c++11" ) # opencv find_package( OpenCV REQUIRED ) include_directories( ${OpenCV_INCLUDE_DIRS} ) # eigen include_directories( "/usr/include/eigen3/" ) # pcl find_package( PCL REQUIRED COMPONENT common io filters ) include_directories( ${PCL_INCLUDE_DIRS} ) add_definitions( ${PCL_DEFINITIONS} ) # octomap find_package( octomap REQUIRED ) include_directories( ${OCTOMAP_INCLUDE_DIRS} ) add_executable( octomaptest main.cpp ) target_link_libraries( octomaptest ${OpenCV_LIBS} ${PCL_LIBRARIES} ${OCTOMAP_LIBRARIES} ) install(TARGETS octomaptest RUNTIME DESTINATION bin)
main.cpp
#include <iostream> #include <fstream> #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 using namespace std; int main( int argc, char** argv ) { vector<cv::Mat> colorImgs, depthImgs; // 彩色图和深度图 vector<Eigen::Isometry3d> poses; // 相机位姿 ifstream fin("./data/pose.txt"); 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)%"pgm").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 = 325.5; double cy = 253.5; double fx = 518.0; double fy = 519.0; double depthScale = 1000.0; cout<<"正在将图像转换为 Octomap ..."<<endl; // octomap tree octomap::OcTree tree( 0.02 ); // 参数为分辨率 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库自带的简单店运 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表示没有测量到 if ( d >= 7000 ) continue; // 深度太大时不稳定,去掉 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; }
上述程序执行完了之后会生成八叉树地图octomap.bt,使用octovis即可查看地图,如下:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)