SLAM从入门到精通(构建自己的slam包)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        我们学习了很多的开源包,比如hector、gmapping。但其实我们也可以自己编写一个slam包。这么做最大的好处,主要还是可以帮助自己更好地去了解slam、掌握slam以及用好slam。就像学习rtos一样,使用好别人提供的api是一回事,自己会写rtos又是另外一回事。一旦我们自己会写rtos之后,那么其他所有的实时操作系统都是很容易掌握的。slam也是一样。

        前面我们也知道,怎么构建一个slam包了?一般来说,它就是画图-》定位-》画图循环迭代的过程。今天可以做的简单一点。直接从cmd_vel-》laser-》画图,虽然内容简单了一点,但是效果出来的时候,还是很有成就感的。另外本文代码参考了现有的ros书籍,再次表示感谢。

1、编写slam_tfpub文件

        代码的主要功能就是接收到cmd_vel消息之后,将自己的tf信息发送出去。头文件slam_tfpub.h如下所示,

#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <tf/transform_broadcaster.h>
#include <tf/tf.h>#define pi 3.1415926class TfMove
{public:TfMove(ros::NodeHandle& nh, ros::Rate& r);void VelCallback(const geometry_msgs::TwistPtr& vel);void init_sub();private:ros::NodeHandle& nh_;ros::Subscriber sub_;tf::TransformBroadcaster tfbrd_;ros::Rate rate;double x,y,z,roll,pit,yaw;
};

        而源文件slam_tfpub.cpp如下所示,

#include "slam_tfpub.h"TfMove::TfMove(ros::NodeHandle& nh, ros::Rate& r):nh_(nh), rate(r)
{x = 0;y = 0;z = 0;roll = 0;pit = 0;yaw = 0;init_sub();
}void TfMove::VelCallback(const geometry_msgs::TwistPtr& vel)
{x += vel->linear.x;y += vel->linear.y;z += vel->linear.z;roll += vel->angular.x/pi * 180;pit += vel->angular.y/pi * 180;yaw += vel->angular.z/pi * 180;tf::Transform trans;trans.setOrigin(tf::Vector3(x,y,z));tf::Quaternion q;q.setRPY(roll, pit, yaw);trans.setRotation(q);tfbrd_.sendTransform(tf::StampedTransform(trans, ros::Time::now(), "map", "base_link"));rate.sleep();
}void TfMove::init_sub()
{sub_ = nh_.subscribe("cmd_vel", 1, &TfMove::VelCallback, this);ros::spin();
}int main(int argc, char* argv[])
{ros::init(argc, argv, "myslam_tfpub");ros::NodeHandle nh;ros::Rate rate(10);TfMove tfmove(nh, rate);return 0;}

2、编写slam_laser文件

        slam_laser主要是模拟lidar的传感器数据。它的头文件slam_laser.h是这样的,

#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>class LaserScanPub
{public:LaserScanPub(ros::NodeHandle& nh, double minAngle, double maxAngle, double scanTime,double minRange, double maxRange, double scanNums);~LaserScanPub();void scanpub_init();void laserdata_init();private:ros::NodeHandle nh_;ros::Publisher scanpub_;sensor_msgs::LaserScan laserdata_;double minAngle;double maxAngle;double minRange;double maxRange;double scanTime;double scanNums;
};

        而源文件slam_laser.cpp是这样的,

#include "slam_laser.h"LaserScanPub::LaserScanPub(ros::NodeHandle& nh, double min_angle, double maxAngle,double scanTime, double minRange, double maxRange, double scanNums):nh_(nh),minAngle(minAngle), maxAngle(maxAngle),minRange(minRange),maxRange(maxRange), scanNums(scanNums), scanTime(scanTime)
{scanpub_init();
}LaserScanPub::~LaserScanPub()
{
}void LaserScanPub::laserdata_init()
{ros::Time scantime = ros::Time::now();laserdata_.header.stamp = scantime;laserdata_.header.frame_id = "base_link";laserdata_.range_min = minRange;laserdata_.range_max = maxRange;laserdata_.scan_time = scanTime;laserdata_.angle_increment = (maxAngle - minAngle)/scanNums; // angle resolutionlaserdata_.time_increment = scanTime/scanNums; // time resolutionlaserdata_.ranges.resize(scanNums);laserdata_.intensities.resize(scanNums);for(int i = 0; i < scanNums; i++){laserdata_.ranges[i] = 5;laserdata_.intensities[i] = 100;}
}void LaserScanPub::scanpub_init()
{scanpub_ = nh_.advertise<sensor_msgs::LaserScan>("scan", 100);ros::Rate rate(10);while(nh_.ok()){laserdata_init();scanpub_.publish(laserdata_);rate.sleep();}
}int main(int argc, char* argv[])
{ros::init(argc, argv, "myslam_laser");ros::NodeHandle nh;LaserScanPub scanpub(nh, 0, 1.57, 0.01, 0, 10, 100);return 0;
}

3、编写book_myslam文件

        前面准备好了tf和laser,接下来就是最终要的制图工作的。它的基本原理就是在laser触发回调的时候,利用tf信息,计算出lidar坐标在地图上的实际位置。中间绘制的方法使用到了bresenham算法,这个前面也提及过。book_myslam.h头文件是这样的,

#include <ros/ros.h>
#include <nav_msgs/OccupancyGrid.h>
#include <nav_msgs/MapMetaData.h>
#include <sensor_msgs/LaserScan.h>
#include <tf/transform_listener.h>#include <tf/tf.h>
#include <vector>
#include <fstream>
#include <math.h>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>using namespace std;struct MapPoint
{int x;int y;MapPoint(){x = 0;y = 0;}MapPoint(int x0, int y0){x = x0;y = y0;}
};class MySlam
{public:MySlam(ros::NodeHandle& nh, double mapreso, double mposx, double mposy,double mposz, double morientx, double morienty, double orientz,double morientw, int mwidth, int mheight);~MySlam();void mappub_init();void lasersub_init();void lasercallback(const sensor_msgs::LaserScanConstPtr& laserdata);void mapdata_init();vector<MapPoint> bresenham(int x0, int y0, int x1, int y1);private:ros::NodeHandle nh_;ros::Subscriber lasersub_;ros::Publisher mappub_;tf::TransformListener tflistener_;nav_msgs::OccupancyGrid mapdata_;double mapreso;double mposx;double mposy;double mposz;double morientx;double morienty;double morientz;double morientw;int mwidth;int mheight;vector<MapPoint> endpoints;MapPoint endpoint;vector<MapPoint> mappoints;tf::StampedTransform base2map;tf::Quaternion quat;double theta;tf::Vector3 trans_base2map;double tx, ty;int basex0, basey0;double basex, basey;double mapx, mapy;double beamsAngle;int mapxn, mapyn;int laserNum;int nx,ny;int idx;ofstream fopen;int scan_count;int scan_reso;boost::mutex map_mutex;	
};

        它的实现文件book_myslam.cpp是这样的,

#include "book_myslam.h"MySlam::MySlam(ros::NodeHandle& nh, double mapreso, double mposx, double mposy,double mposz, double morientx, double morienty, double morientz,double morientw, int mwidth, int mheight):nh_(nh), mapreso(mapreso),mposx(mposx), mposy(mposy), mposz(mposz), morientx(morientx),morienty(morienty), morientz(morientz), morientw(morientw),mwidth(mwidth), mheight(mheight)
{mapdata_init();mappub_init();lasersub_init();
}MySlam::~MySlam()
{
}void MySlam::lasercallback(const sensor_msgs::LaserScanConstPtr& laserdata)
{if(scan_count % scan_reso == 0){try {tflistener_.waitForTransform("map", "base_link", ros::Time(0), ros::Duration(3.0));tflistener_.lookupTransform("map", "base_link", ros::Time(0), base2map);}catch(tf::TransformException& ex){ROS_INFO("%s", ex.what());ros::Duration(1.0).sleep();}boost::mutex::scoped_lock map_lock(map_mutex);quat = base2map.getRotation();theta = quat.getAngle();trans_base2map = base2map.getOrigin();tx = trans_base2map.getX();ty = trans_base2map.getY();basex0 = int(tx/mapreso);basey0 = int(ty/mapreso);laserNum = laserdata->ranges.size();fopen.open("data.txt", ios::app);if(fopen.is_open()){cout << "open file successful!" << endl;}else{cout << "open file fail" << endl;}for(int i = 0; i < laserNum; i++){beamsAngle = laserdata->angle_min + i * laserdata->angle_increment;basex = laserdata->ranges[i] * cos(beamsAngle);basey = laserdata->ranges[i] * sin(beamsAngle);mapx = basex * cos(theta) + basey * sin(theta) + tx;mapy = basey * cos(theta) - basex * sin(theta) + ty;nx = int(mapx/mapreso);ny = int(mapy/mapreso);mapxn = nx + 1;mapyn = ny + 1;endpoint.x = mapxn;endpoint.y = mapyn;fopen << endpoint.x << " " << endpoint.y << std::endl;endpoints.push_back(endpoint);}fopen.close();for(vector<MapPoint>::iterator iter = endpoints.begin(); iter != endpoints.end(); iter++){mappoints = MySlam::bresenham(basex0, basey0, (*iter).x, (*iter).y);cout << "scan numbers: " << endpoints.size() << endl;cout << "bresenham point nums are: " << mappoints.size() << endl;cout << "x0, y0 is " << basex0 << " " << basey0 << std::endl;cout << "angle is " << theta << std::endl;for(vector<MapPoint>::iterator iter1 = mappoints.begin(); iter1 != mappoints.end(); iter1 ++){idx = mwidth * (*iter1).y + (*iter1).x;cout << "idx is " << (*iter1).x << " " << (*iter1).y << std::endl;mapdata_.data[idx] = 0;}mappoints.clear();}endpoints.clear();mappub_.publish(mapdata_);}scan_count ++;
}vector<MapPoint> MySlam::bresenham(int x0, int y0, int x1, int y1)
{vector<MapPoint> pp;MapPoint p;int dx, dy, h, a, b, x, y, flag, t;dx = abs(x1-x0);dy = abs(y1-y0);if(x1 > x0) a = 1; else a = -1;if(y1 > y0) b = 1; else b = -1;x = x0;y = y0;if(dx >= dy){flag = 0;}else{t = dx;dx = dy;dy = t;flag = 1;}h = 2 * dy - dx;for(int i = 1; i <= dx; ++i){p.x = x, p.y = y;pp.push_back(p);if(h >= 0){if(flag == 0) y = y+b;else x = x+a;h =h - 2*dx;}if(flag ==0) x = x+a;else y = y+b;h = h + 2*dy;}return pp;
}void MySlam::mappub_init()
{mappub_ = nh_.advertise<nav_msgs::OccupancyGrid>("map", 100);
}void MySlam::lasersub_init()
{lasersub_ = nh_.subscribe("scan", 1, &MySlam::lasercallback, this);
}void MySlam::mapdata_init()
{scan_count = 0;scan_reso = 1;ros::Time currtime = ros::Time::now();mapdata_.header.stamp = currtime;mapdata_.header.frame_id = "map";mapdata_.info.resolution = mapreso;mapdata_.info.width = mwidth;mapdata_.info.height = mheight;mapdata_.info.origin.position.x = mposx;mapdata_.info.origin.position.y = mposy;mapdata_.info.origin.position.z = mposz;mapdata_.info.origin.orientation.x = morientx;mapdata_.info.origin.orientation.y = morienty;mapdata_.info.origin.orientation.z = morientz;mapdata_.info.origin.orientation.w = morientw;int datasize = mwidth * mheight;mapdata_.data.resize(datasize);for(int i = 0;  i < datasize; i++){mapdata_.data[i] = -1;}
}int main(int argc, char* argv[])
{int debug_flag = 0;//while(debug_flag == 0) sleep(10);ros::init(argc, argv, "MySlam");ros::NodeHandle nh;double mapreso = 0.05;double mposx = 0;double mposy = 0;double mposz = 0;double morientx = 0;double morienty = 0;double morientz= 0;double morientw= 1;int mwidth = 300;int mheight = 300;MySlam myslam(nh, mapreso, mposx, mposy, mposz, morientx, morienty, morientz, morientw, mwidth, mheight);ros::spin();return 0;
}

4、准备编译脚本

        上面3个文件其实就是3个程序,所以我们在CMakeLists.txt里面做好三个程序的编译脚本就可以了。需要调试的话,可以添加上-Wall -g选项。

add_executable(slam_tfpub src/slam_tfpub.cpp)
target_link_libraries(slam_tfpub ${catkin_LIBRARIES})
add_dependencies(slam_tfpub beginner_tutorials_generate_messages_cpp)add_executable(slam_laser src/slam_laser.cpp)
target_link_libraries(slam_laser ${catkin_LIBRARIES})
add_dependencies(slam_laser beginner_tutorials_generate_messages_cpp)add_definitions("-Wall -g")add_executable(book_myslam src/book_myslam.cpp)
target_link_libraries(book_myslam ${catkin_LIBRARIES})
add_dependencies(book_myslam beginner_tutorials_generate_messages_cpp)

5、编译

        编译就很简单了,直接输入catkin_make即可。

6、构建launch文件

        因为启动的程序比较多,这里可以编写一个myslam.launch文件,使用起来方便一点。脚本文件注意放在launch目录下面。

<launch><node pkg="beginner_tutorials" type="slam_tfpub" name="tf_pub"/><node pkg="beginner_tutorials" type="slam_laser" name="laser_pub"/><node pkg="beginner_tutorials" type="book_myslam" name="myslam"/>
</launch>

7、实验步骤

        实验步骤稍微复杂一点,主要分成四步。第一,打开roscore;第二,用rostopic发送cmd_vel信息,

rostopic pub -r 10 /cmd_vel geometry_msgs/Twist '[0.003, 0.0, 0.0]' '[0.0, 0.0, 0.0]'

        第三,启动myslam.launch文件,

roslaunch beginner_tutorials myslam.launch

        第四,就是输入rosrun rviz rviz命令,创建map,选中map之后进一步查看建图效果。这四个步骤需要严格按顺序执行,不然缺少了某个步骤,很有可能程序会发生闪退,主要是book_myslam这个程序。这样,不出意外的话,我们就可以在rviz上面看到这样的建图效果了,

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/111423.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

抽象轻松的java

先看问题代码和运行结果 public static void main(String[] args) {Scanner scanner new Scanner(System.in);System.out.println("输入一串数字");int i scanner.nextInt();System.out.println("i&#xff1a;" i);int z scanner.nextInt();System.ou…

宏(预编译)详解

目录 一、程序的编译环境 二、运行环境 三、预编译详解 3.1预定义符号 3.2.1 #define 定义标识符 3.2.2 #define 定义宏 3.2.3#define替换规则 3.2.4 #和## 2)##的作用&#xff1a; 3.2.5宏和函数的对比 3.2.6宏的命名约定和#undef指令 一、命名约定&#xff1a; …

学信息系统项目管理师第4版系列32_信息技术发展

1. 大型信息系统 1.1. 大型信息系统是指以信息技术和通信技术为支撑&#xff0c;规模庞大&#xff0c;分布广阔&#xff0c;采用多级 网络结构&#xff0c;跨越多个安全域&#xff1b;处理海量的&#xff0c;复杂且形式多样的数据&#xff0c;提供多种类型应用 的大系统 1.1.…

【常用图像增强技术,Python-opencv】

文章目录 常用图像增强技术调整大小灰度变换标准化随机旋转中心剪切随机裁剪高斯模糊亮度、对比度和饱和度调节水平翻转垂直翻转高斯噪声随机块中心区域 常用图像增强技术 图像增强技术是常用于数据增强的方法&#xff0c;可以帮助增加数据集中图像的多样性&#xff0c;提高深…

论文阅读:Point-to-Voxel Knowledge Distillation for LiDAR Semantic Segmentation

来源&#xff1a;CVPR 2022 链接&#xff1a;https://arxiv.org/pdf/2206.02099.pdf 0、Abstract 本文解决了将知识从大型教师模型提取到小型学生网络以进行 LiDAR 语义分割的问题。由于点云的固有挑战&#xff0c;即稀疏性、随机性和密度变化&#xff0c;直接采用以前的蒸馏…

Mapping 设计指南

Mapping 设计指南 目录概述需求&#xff1a; 设计思路实现思路分析1、properties2.fields 3.search_analyzer4.2、format1、enabled2、doc_values 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0…

Vue3 + Nodejs 实战 ,文件上传项目--大文件分片上传+断点续传

目录 1.大文件上传的场景 2.前端实现 2.1 对文件进行分片 2.2 生成hash值&#xff08;唯一标识&#xff09; 2.3 发送上传文件请求 3.后端实现 3.1 接收分片数据临时存储 3.2 合并分片 4.完成段点续传 4.1修改后端 4.2 修改前端 5.测试 博客主页&#xff1a;専心_前端…

【Python】文件操作

一、文件的编码 思考:计算机只能识别:0和1&#xff0c;那么我们丰富的文本文件是如何被计算机识别&#xff0c;并存储在硬盘中呢? 答案:使用编码技术( 密码本)将内容翻译成0和1存入 编码技术即:翻译的规则&#xff0c;记录了如何将内容翻译成二进制&#xff0c;以及如何将二…

人人开源前后端分离开源项目启动流程(超详细)

renren-security是一个轻量级的&#xff0c;前后端分离的Java快速开发平台&#xff0c;能快速开发项目并交付【接私活利器】采用SpringBoot、Shiro、MyBatis-Plus、Vue3、TypeScript、Element Plus、Vue Router、Pinia、Axios、Vite框架&#xff0c;开发的一套权限系统&#xf…

【计算机网络笔记】OSI参考模型基本概念

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

(H5轮播)vue一个轮播里显示多个内容/一屏展示两个半内容

效果图 : html: <div class"content"><van-swipeclass"my-swipe com-long-swipe-indicator":autoplay"2500"indicator-color"#00C4FF"><van-swipe-itemclass"flex-row-wrap"v-for"(items, index) in M…

【Git】升级MacOS系统,git命令无法使用

终端执行git命令报错 xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun安装这个东东&#xff0c;&#xff1f;需要42小时 最终解决&#xff1a; 下载安装 https…

微信小程序开发的OA会议之会议个人中心的页面搭建及模板,自定义组件的学习

目录 一.自定义组件及会议效果编写 效果显示 二.个人中心布局 编写结果 ​编辑 一.自定义组件及会议效果编写 在页面中创建一个以components命名的项目来存放组件 再在components文件夹中创建一个组件&#xff0c;名为 :tabs &#xff0c;创建操作如图所示 刚刚创建好会报…

山海鲸可视化B/S架构应用

一、什么是B/S架构 BS架构&#xff08;Browser-Server架构&#xff09;是一种常见的软件架构模式&#xff0c;其中系统的核心业务逻辑和数据处理都发生在服务器端&#xff08;Server&#xff09;&#xff0c;而客户端&#xff08;Browser&#xff09;主要负责显示和用户交互。…

客户端post请求,服务器收到{}数据解决方法

当我们发起登录请求时&#xff0c;后台接收到的为{}数据 原因&#xff1a;传送过去的对象格式不对 解决方案&#xff1a; 引入qs npm install qs 在data中格式化数据 const res await axios({url:http://127.0.0.1:3000/post,method:post,data:Qs.stringify({username:te…

阿里云服务器x86计算架构ECS规格大全

阿里云企业级服务器基于X86架构的实例规格&#xff0c;每一个vCPU都对应一个处理器核心的超线程&#xff0c;基于ARM架构的实例规格&#xff0c;每一个vCPU都对应一个处理器的物理核心&#xff0c;具有性能稳定且资源独享的特点。阿里云服务器网aliyunfuwuqi.com分享阿里云企业…

【高等数学】导数与微分

文章目录 1、导数的概念1.1、引例1.1.1、变速直线运动瞬时速度1.1.2、曲线的切线 1.2、导数的定义1.3、证明常用导数1.4、导数的几何意义1.5、可导与连续的关系 2、函数的求导法则2.1、函数的和、差、积、商的求导法则2.2、反函数的求导法则2.3、复合函数的求导法则2.4、基本初…

github: kex_exchange_identification: Connection closed by remote host

问题描述 (base) ➜ test git:(dev) git pull kex_exchange_identification: Connection closed by remote host Connection closed by 192.30.255.113 port 22 致命错误&#xff1a;无法读取远程仓库。解决方案 参照下边文档 https://docs.github.com/en/authentication/tr…

基于SSM的工资管理系统

基于SSM的工资管理系统 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 管理员界面 通知公告 考勤管理 工资管理 请假管理 摘要 基于SSM&#xff08;Spring、S…

【每日一题】根据规则将箱子分类

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;分类讨论 其他语言cpython3 写在最后 Tag 【分类讨论】【2023-10-20】 题目来源 2525. 根据规则将箱子分类 题目解读 题目意思明确&#xff0c;根据条件判断箱子的类别。 解题思路 方法一&#xff1a;分类讨论 根据…