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

news2024/11/18 14:40:41

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱: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.1415926

class 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 resolution
	laserdata_.time_increment = scanTime/scanNums; // time resolution
	laserdata_.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.coloradmin.cn/o/1112905.html

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

相关文章

【会议征稿通知】第三届大数据经济与数字化管理国际学术会议(BDEDM 2024)

2024 3rd International Conference on Big Data Economy and Digital Management 第三届大数据经济与数字化管理国际学术会议&#xff08;BDEDM 2024&#xff09; 第三届大数据经济与数字化管理国际学术会议&#xff08;BDEDM 2024&#xff09;将于2024年1月12-14日于宁波召…

分布式Trace:横跨几十个分布式组件的慢请求要如何排查?

目录 前言 一、问题的出现&#xff1f; 二、一体化架构中的慢请求排查如何做 三、分布式 Trace原理 四、如何来做分布式 Trace 前言 在分布式服务架构下&#xff0c;一个 Web 请求从网关流入&#xff0c;有可能会调用多个服务对请求进行处理&#xff0c;拿到最终结果。这个…

AGI热门方向:国内前五!AI智能体TARS-RPA-Agent落地,实在智能打造人手一个智能助理

早在 1950 年代&#xff0c;Alan Turing 就将「智能」的概念扩展到了人工实体&#xff0c;并提出了著名的图灵测试。这些人工智能实体通常被称为 —— 代理&#xff08;Agent&#xff09;。 代理这一概念起源于哲学&#xff0c;描述了一种拥有欲望、信念、意图以及采取行动能力…

SystemVerilog Assertions应用指南 Chapter1.33 在蕴含中使用 if/else

SVA允许在使用蕴含的属性的后续算子中使用“if/else”语句。 属性 p_if_else检査如果信号“ start”的下降沿被检测到,就是个有效开始,接着一个时钟周期后,信号“a”或者信号“b”为高。在现行算子成功匹配时,后续算子有两个可能的路径。 1.如果信号“a”为高,…

javaEE - 2(11000字详解多线程)

一&#xff1a;多线程带来的的风险-线程安全 线程安全的概念&#xff1a;如果多线程环境下代码运行的结果是符合我们预期的&#xff0c;即在单线程环境应该的结果&#xff0c;则说这个程序是线程安全的。 当多个线程同时访问共享资源时&#xff0c;就会产生线程安全的风险&am…

PHP的学习入门建议

学习入门PHP的步骤如下&#xff1a; 确定学习PHP的目的和需求&#xff0c;例如是为了开发网站还是为了与数据库交互等。学习PHP的基础语法和程序结构&#xff0c;包括变量、数据类型、循环、条件等。学习PHP的面向对象编程&#xff08;OOP&#xff09;概念和技术。学习与MySQL…

1811_spacemacs从v.0.200.13升级到v.0.200.14的几点变化感受

全部学习汇总&#xff1a; GreyZhang/editors_skills: Summary for some common editor skills I used. (github.com) 安装了全新的spacemacs的配置&#xff0c;查看了一下版本是v.0.200.14。在此之前&#xff0c;我使用的版本是v.0.200.13。现在还没有在这个配置上完成我所有的…

win32汇编-使用子程序

当程序中相同功能的一段代码用得比较频繁时&#xff0c;可以将它分离出来写成一个子程序&#xff0c;在主程序中用call指令来调用它。这样可以不用重复写相同的代码&#xff0c; 仅仅用call指令就可以完成多次同样的工作了。Win 32汇编中的子程序也采用堆栈来传递参数&#xff…

【小黑嵌入式系统第四课】嵌入式系统硬件平台(二)——I/O设备、通信设备(UARTUSB蓝牙)、其他(电源时钟复位中断)

上一课&#xff1a; 【小黑嵌入式系统第三课】嵌入式系统硬件平台&#xff08;一&#xff09;——概述、总线、存储设备&#xff08;RAM&ROM&FLASH) 文章目录 一、I/O设备1. 定时器/计数器2. ADC和DAC3. 人机接口设备3.1 键盘3.2 LCD显示器3.3 触摸屏 二、通信设备1. 通…

C#,数值计算——分类与推理Phylagglom的计算方法与源程序

1 文本格式 using System; using System.Collections.Generic; namespace Legalsoft.Truffer { public abstract class Phylagglom { public int n { get; set; } public int root { get; set; } public int fsroot { get; set; } p…

idea启动vue项目:Error:0308010C:digital envelope routines::unsupported

此问题是因为Node.js的版本原因&#xff0c;此处安装的Node.js是最新长期维护版: 18.16.0 (includes npm 9.5.1) 有两种解决办法&#xff1a; #1、方法一 重新安装低版本的node.js#2、方法二 在package.json文件中进行配置【此种方法较简单】介绍一下第二种方法&#xff1a; …

《动手学深度学习 Pytorch版》 9.4 双向循环神经网络

之前的序列学习中假设的目标是在给定观测的情况下对下一个输出进行建模&#xff0c;然而也存在需要后文预测前文的情况。 9.4.1 隐马尔可夫模型中的动态规划 数学推导太复杂了&#xff0c;略。 9.4.2 双向模型 双向循环神经网络&#xff08;bidirectional RNNs&#xff09;…

《向量数据库指南》——向量数据库是小题大作的方案?

假设大语言模型需要 10 秒钟才能生成一条结果,即需要存储的单条新记忆。那么我们获得 10 万条记忆的时间周期将为:100000 x 10 秒 = 1000000 秒——约等于 11.57 天。而即使我们用最简单的暴力算法(Numpy 的点查询),整个过程也只需要几秒钟时间,完全不值得进行优化!也就…

《Helm包管理工具篇:Helm工具概述和安装》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;企业级K8s集群运维实战 一、Helm概述 Helm 是Kubernetes 的一个包管理工具&#xff0c;类似于Linux下的包管理工具如yum、apt等。可以方便的将之…

【试题025】C语言宏定义和表达式

题目&#xff1a; 若有宏定义: #define MOD(x,y) x%y 则执行以下语句后的输出结果是 ? int a13,b94: printf("%d\n",MOD(b,(a4)); 代码分析&#xff1a; #include <stdio.h> #define MOD(x,y) x%y //x和y两个形式参数进行模运算 int main() {/* 若有宏定义…

uniGUI 快速定制手机端输入界面布局

咱还是直奔主题&#xff0c;如何快速制作输入界面呢&#xff1f;如下图&#xff1a; 第一步&#xff0c;放置一个UnimFieldContainer&#xff0c;设置属性&#xff1a; AlignmentControluniAlignmentClient&#xff0c;让客户端处理对齐&#xff1b; LayoutConfig.Padding10,…

MySQL数据库——视图-介绍及基本语法(创建、查询、修改、删除、演示示例)

目录 介绍 语法 创建 查询 修改 删除 演示示例 介绍 视图&#xff08;View&#xff09;是一种虚拟存在的表。视图中的数据并不在数据库中实际存在&#xff0c;行和列数据来自定义视图的查询中使用的表&#xff08;称为基表&#xff09;&#xff0c;并且是在使用视图时动…

优雅而高效的JavaScript——?? 运算符、?. 运算符和 ?. .运算符

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

fastadmin如何让后台的日期显示成年月日格式

fastadmin的后台时间戳字段如何显示成年月日的日期格式&#xff0c;网上有很多同仁也在问这个问题&#xff0c;下面我把我这摸索到的方法给大家分享一下&#xff1a; 解决方法&#xff1a; 找到public\asset\js\backend\控制器.js 增加formatter: Table.api.formatter.datetim…

基于stm32f103c8t6的串口中断蓝牙通讯

这一篇文章与 上一篇文章相基于 stm32f103c8t6的串口非中断蓝牙通讯上一篇文章相http://t.csdnimg.cn/7j0Ec 相比&#xff0c;硬件部分是相同的。在原有的旧初上&#xff0c;要在stm32cube加入中断&#xff0c;同时代码中也要引入中断函数以及中断回调函数。到后面我谁说说我…