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 
#include 
#include 
#include 

#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 
#include 

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("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 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 

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 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 endpoints;
		MapPoint endpoint;
		vector 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::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::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 MySlam::bresenham(int x0, int y0, int x1, int y1)
{
	vector 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("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目录下面。


	
	
	

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包)_第1张图片

你可能感兴趣的:(SLAM从入门到精通,机器人)