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上面看到这样的建图效果了,
相关文章:

SLAM从入门到精通(构建自己的slam包)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 我们学习了很多的开源包,比如hector、gmapping。但其实我们也可以自己编写一个slam包。这么做最大的好处,主要还是可以帮助…...

全球二氧化碳排放数据1deg产品(ODIAC)数据
简介 全球二氧化碳排放数据1deg产品(ODIAC)是一个空间分辨率为1deg*1deg的全球化石燃料燃烧产生的二氧化碳空间分布产品。它率先将基于空间的夜间灯光数据与单个发电厂的排放/位置相结合来估计化石燃料二氧化碳的排放。该产品被国际研究界广泛用于各种研究应用(例如…...

Element-UI 日期选择器--禁用未来日期
在做项目的时候经常会遇到一些报表需要填写日期,一般是填写当日及当日以前,这时候我们的日期选择器就需要进行一些限制,比如: 这样之后,就不会误填写到明天啦,下面让我们看一下代码实现 html页面代码 这里…...

终端常用脚本命令
Mac编写shell脚本文件 Rvm切换Ruby Mac系统指定更新 Mac应用安装:允许任何来源 Mac终端常用命令与Vim常用命令 Mac退出VIM模式 git协议实现管理(三个步骤) GIT 常用命令 .gitignore git工具常用操作指令 prettier前端本地格式化工具 SourceTree撤销Commit提交 pod i…...

百度翻译很方便,几点注意事项
前几天修改资源,就想翻译一些字串。用了一下百度,还是很方便的。 昨天开通了开发者账号,试了一下批量翻译。也发现了一些问题: 有的语言不支持,如ben/tr/jav好像没有区分地区。也可能是我还不熟悉。使用太多会欠费。比…...

阿里云安装 redis
1、在opt目录下面安装redis https://download.redis.io/redis-stable.tar.gz redis的最新稳定版本。更多版本可见 redis cd /opt wget https://download.redis.io/redis-stable.tar.gz2、解压tar包,会生成redis-stable文件夹 tar -xzvf redis-stable.tar.gz3、安装…...

解释什么是异步非阻塞?
在IO和网络编程中,我们经常看到几个概念:同步、异步、阻塞、非阻塞。 同步和异步 同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发…...

1024程序节特辑:一文读懂小程序支付流程
小程序支付流程 概述前置准备登录流程调用wx.login()向微信服务器发送请求 支付流程调用wx.requestPayment()部分后台处理逻辑支付功能要求 支付流程面试题 主页传送门:📀 传送 概述 小程序支付是由微信支付推出的一种便捷支付方式,通过扫码…...

C- 使用原子变量实现信号量
信号量 信号量(Semaphore)是并发编程中的一个核心同步原语,它在多进程和多线程环境下被设计用来协调不同的执行单元,确保它们在对共享资源的访问上达到同步和互斥。信号量内部维护一个计数器,该计数器的初始值可以被视…...

Pytorch与Onnx的转换与推理
Open Neural Network Exchange(ONNX,开放神经网络交换)格式,是一个用于表示深度学习模型的标准,可使模型在不同框架之间进行转移。 一、pytorch模型保存/加载 有两种方式可用于保存/加载pytorch模型 1)文件…...

Linux权限详解
文章目录 1. shell命令及运行原理2. Linux权限的概念(1)用户种类(2)切换用户(3)命令提权 3. Linux权限管理(1)文件访问者的分类(人)(2)…...

基于react18+arco+zustand通用后台管理系统React18Admin
React-Arco-Admin轻量级后台管理系统解决方案 基于vite4构建react18后台项目ReactAdmin。使用了reactarco-designzustandbizcharts等技术架构非凡后台管理框架。支持 dark/light主题、i18n国际化、动态路由鉴权、3种经典布局、tabs路由标签 等功能。 技术框架 编辑器ÿ…...

BAT031:按列表名单将路径a下的文件夹批量剪切到路径b
引言:编写批处理程序,实现按列表名单将路径a下的文件夹批量剪切到路径b。 一、新建Windows批处理文件 参考博客: CSDNhttps://mp.csdn.net/mp_blog/creation/editor/132137544 二、写入批处理代码 1.右键新建的批处理文件,点击…...

随机专享记录第一话 -- RustDesk的自我搭建和使用
1.介绍 RustDesk是继TeamView、向日葵等远程桌面软件后的新起之秀,最主要的是开源的可自己搭建中继服务。相比于公共服务器,连接一次等待的时间要多久,用过TeamView的都知道,而且还是免费的,不像某些远程搞各种个人证书,各种登录设备限制! 先看看软件图,这是待连接界…...

【数据库】拼接字段 使用别名
拼接字段 使用别名 e . g . e.g. e.g. Vendors 表包含供应商名和电话信息,name 和 mobile;需要输出这两个属性的值的组合作为供应商的基本信息组合。 SELECT concat(name, _, mobile) FROM Vendors; -- 语句通过 MySQL 环境下测试,其他 DBMS…...

Golang设计22种模式
什么是设计模式 设计模式是面向对象软件的设计经验,是通常设计问题的解决方案。每一种设计模式系统的命名、解释和评价了面向对象中一个重要的和重复出现的设计。 设计模式的分类 创建模式 - 用来帮助我们创建对象的 工厂模式 (Factory Pattern)抽象工厂模式 (Abstract F…...

MMKV(3)
使用时遇到的问题 在项目的构建配置文件(如 Gradle 或 Maven)中添加相应的依赖项。 MMKV 是一个键值存储库,它存储的是原始的字节数组数据。需要存储和检索复杂的对象或数据结构,需要自行进行序列化和反序列化操作。可以使用任何…...

vivado报错警告之[Vivado 12-1017] Problems encountered:
文章目录 方法一方法二方法三(作者最终解决) 我们对vivado 的程序进行综合(Run Synthesis)时,可能会出现[Vivado 12-1017] Problems encountered: 1. Failed to delete one or more files in run directory的一个警告信息,导致我们…...

基于springboot汽车租赁系统
功能如下图所示 摘要 Spring Boot汽车租赁系统的设计旨在满足不断增长的租车市场需求,并通过简化开发和部署流程来提供方便的租车解决方案。系统采用了现代化的架构,主要基于以下技术栈: Spring Boot:作为后端的核心框架ÿ…...

C++禁用赋值操作符
1.禁用赋值操作符 在C中,void operator(const ClassName&) delete; 是一种特殊的语法,用于明确地禁止赋值操作符(assignment operator)的默认实现或自定义实现。 这通常用于防止类的实例被意外赋值。通过明确地删除赋值操作…...

小程序的数据驱动和vue的双向绑定有何异同?
小程序的数据驱动和Vue的双向绑定有以下异同之处: 异同点: 数据驱动:小程序的数据驱动是指通过编写数据绑定的代码,将数据与视图进行关联,当数据发生变化时,视图会自动更新。而Vue的双向绑定则是一种特殊的…...

Nvm管理NodeJs版本
文章目录 Nvm管理NodeJs版本一、前言1.简介2.环境 二、正文1.卸载NodeJs2.安装Nvm3.配置国内镜像4.Nvm使用5.其它1)报错12)报错2 Nvm管理NodeJs版本 一、前言 1.简介 Node Version Manager(nvm)可通过命令行快速安装和使用不同…...

阿里云国际站服务器开放端口详解!!
在互联网技术发展的今天,服务器扮演着至关重要的角色。作为云服务供给商,阿里云服务器供给了安稳、高效的服务,而敞开端口则是阿里云服务器功能的重要体现。本文将详细解读阿里云服务器敞开端口的意义、实现办法以及其带来的优点。 一、阿里云…...

【自动化测试入门】用Airtest - Selenium对Firefox进行自动化测试(0基础也能学会)
1. 前言 本文将详细介绍如何使用AirtestIDE驱动Firefox测试,以及脱离AirtestIDE怎么驱动Firefox(VScode为例)。看完本文零基础小白也能学会Firefox浏览器自动化测试!!! 2. 如何使用AirtestIDE驱动Firefox…...

Python 爬虫入门:常见工具介绍
接着我的上一篇文章《网页爬虫完全指南》,这篇文章将涵盖几乎所有的 Python 网页爬取工具。我们从最基本的开始讲起,逐步涉及到当前最前沿的技术,并且对它们的利弊进行分析。 当然,我们不能全面地介绍每个工具,但这篇…...

uniGUI文件操作
一.文件上传TUniFileUploadButton TUniFileUploadButton主要属性: Filter: 文件类型过滤,有图片image/* audio/* video/*三种过滤 MaxAllowedSize: 设置文件最大上传尺寸; Message:标题以及消息文本,可翻译成中文…...

Python多进程之分享(multiprocessing包)
threading和multiprocessing (可以阅读Python多线程与同步) multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的…...

【试题028】C语言关于逻辑与的短路例题
1.题目:设inta1,b;,执行b0&&(a);后,变量a的值是? 2.代码解析: #include <stdio.h> int main() {//设inta1,b;执行b0&&(a);后,变量a的值是?int a 1, b;printf("表达式的值是…...

TSINGSEE烟火识别算法的技术原理是什么?如何应用在视频监控中?
AI烟火识别算法是基于深度学习技术的一种视觉识别算法,主要用于在视频监控场景中自动检测和识别烟雾、火焰的行为。该技术基于深度学习神经网络技术,可以动态识别烟雾和火焰从有到无、从小到大、从大到小、从小烟到浓烟的状态转换过程。 1、技术原理 1…...

优雅而高效的JavaScript——?? 运算符、?. 运算符和 ?. .运算符
🥴博主:小猫娃来啦 🥴文章核心:优雅而高效的JavaScript——?? 运算符、?. 运算符和 ?. 运算符 文章目录 引言空值处理的挑战解决方案1:?? 运算符基本用法与 || 运算符的区别实际应用场景举例 解决方案2ÿ…...