本文使用的方法不是从内部修改ORBSLAM2源码以获取稠密点云,而是先从ZED2 sdk获取以摄像头坐标系为描述的三维点云/作为点云地图的一个子集,然后融合IMU与ORB_SLAM2进行实时定位,通过点云滤波,点云融合建图。
 
以上是在室内实验的demo,由于是纯双目,没有深度传感器,在白墙和地板上有些失真,下次等移动平台到了我会去室外实验。
 
一、获取实时坐标和点云图
 
使用ORBSLAM2获取当前姿态,同时ZED2 利用其IMU数据对速度加速度积分得出另一个姿态,考虑到ORBSLAM2的响应及时性和IMU数据的漂移,当两者数据相差较大时停止建图,等待恢复正常,否则以ORB_SLAM2的姿态信息为准,同时手动添加损失量对IMU姿态信息进行校准。在某些情况下ORB_SLAM2可能会跟丢,此时通过IMU数据积分获取迪卡尔空间位移变化量,并回到之前的位置重新确定位置。

 1 if(!startTimer)
2 {
3   timeLast = ros::Time::now().toSec(); startTimer = 1;
4   ROS_ERROR("\noffect between two poseMsgs is too big, stop mapping...");
5   ROS_WARN("the offset from zed2Pose to orbPose2 is:\nx:%f y:%f z:%f \n-------------" ,carTF_zed2.pose.position.x - carTF_orb.pose.position.x ,carTF_zed2.pose.position.y - carTF_orb.pose.position.y ,carTF_zed2.pose.position.z - carTF_orb.pose.position.z);
6 }else if(timeNow = ros::Time::now().toSec() - timeLast > 10)
7 {
8   startTimer = 0;
9   timeLast = timeNow = 0;
10   ROS_WARN("Don't warry, it seems that something wrong happend, trying to fix it..."); x_bias = carTF_zed2.pose.position.x - carTF_orb.pose.position.x; y_bias = carTF_zed2.pose.position.y - carTF_orb.pose.position.y;
11   z_bias = carTF_zed2.pose.position.z - carTF_orb.pose.position.z;
12 }
这里我订阅ZED2 sdk输出的实时点云,有一点需要注意的是实时的点云和姿态信息必需要时间戳同步,不然融合出来的地图会发生很大的偏移和扭曲。使用ros message_filters管理消息同步,可以设置弱同步和强同步。

1 imu_sub = n.subscribe("/zed/zed_node/imu/data", 1, &MapBuild::imuCallback,this);
2 carTF_orb_sub = n.subscribe("/orb_slam2_stereo/pose", 1, &MapBuild::carTF_orb_Callback,this);
3 pointCloud_sub = new message_filters::Subscriber<sensor_msgs::PointCloud2> ( n, "/zed2/zed_node/point_cloud/cloud_registered", 1);
4 carTF_zed2_sub = new message_filters::Subscriber<geometry_msgs::PoseStamped> (n, "/zed2/zed_node/pose", 1);
5 sync_ = new message_filters::Synchronizer<sync_pol> (sync_pol(10), *pointCloud_sub, *carTF_zed2_sub); sync_->registerCallback(boost::bind(&MapBuild::buildMap_callback, this, _1, _2));
 
二、点云坐标系变换
 
我们当前的点云是相对摄像头坐标系的,但是建图就要将这些点转换到世界坐标系,点的坐标系变换我这里就不讲了,不懂的去看一下《机器人学导论》/题外话“英文的原汁原味”。要用到的工具当然是Eigen了。这里吐槽一下,Eigen和geometry_msgs::PoseStamped里有关四元数的写法顺序是颠倒的,大家注意一下。
 1 Quaterniond quaternion(carTF_zed2.pose.orientation.w, carTF_zed2.pose.orientation.x, carTF_zed2.pose.orientation.y, carTF_zed2.pose.orientation.z);
2 Matrix3d rotation_matrix; rotation_matrix=quaternion.toRotationMatrix();
3
4 // transform the cloud link to the "map" frame
5
6 Vector3d position_transform (carTF_zed2.pose.position.x - x_bias, carTF_zed2.pose.position.y - y_bias, carTF_zed2.pose.position.z - z_bias);
7
8 for (int i=0; i<cloud_xyz->width; i++)
9 {
10   Vector3d position_(cloud_xyz->at(i).x,cloud_xyz->at(i).y,cloud_xyz->at(i).z);
11   Vector3d position = rotation_matrix*position_ + position_transform;
12   cloud_xyz->at(i).x = position[0];
13   cloud_xyz->at(i).y = position[1];
14   cloud_xyz->at(i).z = position[2];
15 }
 
三、点云滤波
 
 
可以看到,图中绿色的点云原本是墙面和窗帘,但是在边缘却有很多&amp;quot;飞点&amp;quot;
我们获取的原始点云有很多的噪声点并且密度太大,TX2吃不消,因此这里要对这些点云进行滤波,这里使用pcl的体素滤波和直通滤波。

 1 // Perform the actual filtering
2 // VoxelGrid(decrease the memory occupation) & PassThrough(delete some incorrect points)
3
4 pcl::PCLPointCloud2* cloud2 = new pcl::PCLPointCloud2;
5 pcl::PCLPointCloud2ConstPtr cloudPtr(cloud2);
6 pcl_conversions::toPCL(*cloud, *cloud2);
7
8 // VoxelGrid pcl::PCLPointCloud2* cloud_filtered_1 = new pcl::PCLPointCloud2;
9 pcl::PCLPointCloud2ConstPtr cloud_filter_1_Ptr(cloud_filtered_1);
10 pcl::VoxelGrid<pcl::PCLPointCloud2> filter_1;
11 filter_1.setInputCloud (cloudPtr);
12 filter_1.setLeafSize (0.03, 0.03, 0.03); filter_1.filter(*cloud_filtered_1);
13 // PassThrough pcl::PCLPointCloud2* cloud_filtered_2 = new pcl::PCLPointCloud2; pcl::PCLPointCloud2ConstPtr cloud_filter_2_Ptr(cloud_filtered_2); pcl::PassThrough<pcl::PCLPointCloud2> filter_2; filter_2.setInputCloud (cloud_filter_1_Ptr); filter_2.setFilterFieldName ("y"); filter_2.setFilterLimits (-1.2, 1.2); // filter_2.setFilterLimitsNegative (true); filter_2.filter(*cloud_filtered_2); pcl::PCLPointCloud2 cloud_filtered_3; filter_2.setInputCloud (cloud_filter_2_Ptr); filter_2.setFilterFieldName ("z"); filter_2.setFilterLimits (-2,2);// filter_2.setFilterLimitsNegative (true); filter_2.filter(cloud_filtered_3);
四、点云融合

1 // fused the current cloud to the fused cloud
2 *cloud_xyzFused += *cloud_xyz; pcl::toROSMsg(*cloud_xyzFused, mPointcloudFusedMsg);
3 mPointcloudFusedMsg.header.frame_id = "map"; pointCloudFused_pub.publish(mPointcloudFusedMsg);
 
五、去除重复的点云
 
实验过程中不可避免的会回到某个之前来过的姿态,这个时候不能任由重复的点云在我的地图上大行其道,因此需要实时判断当前的点云是否已经添加到地图中去了,使用pcl::registration::CorrespondenceEstimation判断当前点云和地图有多少重复的点,该数目与点云总体数目之比如果大于某个阈值,则丢弃该点云。
 1 pcl::registration::CorrespondenceEstimation<pcl::PointXYZRGB, pcl::PointXYZRGB> est; cloud_xyzFusedPtr = cloud_xyzFused->makeShared();
2 cloud_xyzPtr = cloud_xyz->makeShared();
3 est.setInputSource (cloud_xyzPtr);
4 est.setInputTarget (cloud_xyzFusedPtr);
5 pcl::Correspondences all_correspondences;
6 // Determine all reciprocal correspondences
7
8 est.determineReciprocalCorrespondences (all_correspondences);
9 // filter the reciprocal points cloud
10
11 if(1.0*all_correspondences.size()/cloud_xyz->width < 0.9) {
12   // fused the current cloud to the fused cloud
13   *cloud_xyzFused += *cloud_xyz; pcl::toROSMsg(*cloud_xyzFused, mPointcloudFusedMsg);
14   mPointcloudFusedMsg.header.frame_id = "map";
15   pointCloudFused_pub.publish(mPointcloudFusedMsg);
16 }
我们下期再见
./下期就有小车实际的实验了
 

软件篇-03-基于ORB_SLAM2手写SLAM稠密地图构建实现的更多相关文章

  1. 放弃antd table,基于React手写一个虚拟滚动的表格

    缘起 标题有点夸张,并不是完全放弃antd-table,毕竟在react的生态圈里,对国人来说,比较好用的PC端组件库,也就antd了.即便经历了2018年圣诞彩蛋事件,antd的使用者也不仅不减,反 ...

  2. [年薪60W分水岭]基于Netty手写Apache Dubbo(带注册中心和注解)

    阅读这篇文章之前,建议先阅读和这篇文章关联的内容. 1. 详细剖析分布式微服务架构下网络通信的底层实现原理(图解) 2. (年薪60W的技巧)工作了5年,你真的理解Netty以及为什么要用吗?(深度干 ...

  3. Tensorflow之基于MNIST手写识别的入门介绍

    Tensorflow是当下AI热潮下,最为受欢迎的开源框架.无论是从Github上的fork数量还是star数量,还是从支持的语音,开发资料,社区活跃度等多方面,他当之为superstar. 在前面介 ...

  4. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

  5. 基于vue手写tree插件那点事

    目录 iview提供的控件 手写控件 手写控件扩展 手写控件总结 # 加入战队 微信公众号 主题 Tree树形控件在前端开发中必不可少,对于数据的展示现在网站大都采取树形展示.因为大数据全部展示出来对 ...

  6. 软件篇-05-融合ORB_SLAM2和IMU闭环控制SLAM底盘运动轨迹

      前面我们已经得到了当前底盘在世界坐标系中的位姿,这个位姿是通过融合ORB_SLAM2位姿和IMU积分得到的,在当前位姿已知的case下,给SLAM小车设置一个goal,我这里是通过上位机设置,然后 ...

  7. 基于netty手写RPC框架

    代码目录结构 rpc-common存放公共类 rpc-interface为rpc调用方需要调用的接口 rpc-register提供服务的注册与发现 rpc-client为rpc调用方底层实现 rpc- ...

  8. 基于Vue手写一个下拉刷新

    当然不乏有很多下拉刷新的插件可以直接使用,但是自定义程度不强,大部分都只能改改文字,很难满足设计师的创意,譬如淘宝和京东首页那种效果,就需要自己花心思倒腾了,最近刚好有这种需求,做完了稍微总结一下,具 ...

  9. 基于springJDBC手写ORM框架

    一.添加MySQLjar包依赖 二.结构 三.文件内容 (一).bean包 1.ColumnInfo.java 2.javaFiledInfo.java 3.TableInfo.java 4.Conf ...

随机推荐

  1. hexo 报错 use_date_for_updated is deprecated...

    hexo 报错 use_date_for_updated is deprecated... WARN Deprecated config detected: "use_date_for_up ...

  2. 使用dlopen加载动态库

    目录 概述 接口 C CMakeLists.txt src/main.c src/add.c ./dlopen_test C++ CMakeLists.txt src/main.cpp src/add ...

  3. PHP代码审计_用==与===的区别

    目录 背景介绍 如何审计 绕过案例1 绕过案例2 背景介绍 比较==与===的差别 == 是等于符号,=== 是恒等于符号,两个符号的功能都是用来比较两个变量是否相等的,只不过两个符号的比较维度不一样 ...

  4. 184. 部门工资最高的员工 + join + in

    184. 部门工资最高的员工 LeetCode_MySql_184 题目描述 题解分析 1.首先需要使用group by找出工资最高的值 2. 然后考虑到最高工资的可能有多位,所以使用in语句找到所有 ...

  5. 未来直播 “神器”,像素级视频分割是如何实现的 | CVPR 冠军技术解读

    被誉为计算机视觉领域 "奥斯卡" 的 CVPR 刚刚落下帷幕,2021 年首届 "新内容 新交互" 全球视频云创新挑战赛正火热进行中,这两场大赛都不约而同地将关 ...

  6. PAT (Advanced Level) Practice 1019 General Palindromic Number (20 分) 凌宸1642

    PAT (Advanced Level) Practice 1019 General Palindromic Number (20 分) 凌宸1642 题目描述: A number that will ...

  7. 全网最详细的Linux命令系列-cat命令

    cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. 命令格式: cat [选项] [文件].. ...

  8. shell字符串处理总结

    1. 字符串切片 1.1 基于偏移量取字符串 返回字符串 string 的长度 ${#string} 示例 [root@centos8 script]#str=" I Love Python ...

  9. D. 【例题4】字符串环

    解析 字符串的操作,可以用函数解决这个问题 s 2. f i n d ( s 1. s u b s t r ( i , j ) ) s2.find~(s1.substr~(i,~j)) s2.find ...

  10. 201871030116-李小龙 实验一 软件工程准备——Blog

    项目 内容 课程班级博客链接 https://edu.cnblogs.com/campus/xbsf/2018CST 这个作业要求链接 https://www.cnblogs.com/nwnu-dai ...