【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
我们学习了很多的开源包,比如hector、gmapping。但其实我们也可以自己编写一个slam包。这么做最大的好处,主要还是可以帮助自己更好地去了解slam、掌握slam以及用好slam。就像学习rtos一样,使用好别人提供的api是一回事,自己会写rtos又是另外一回事。一旦我们自己会写rtos之后,那么其他所有的实时操作系统都是很容易掌握的。slam也是一样。
前面我们也知道,怎么构建一个slam包了?一般来说,它就是画图-》定位-》画图循环迭代的过程。今天可以做的简单一点。直接从cmd_vel-》laser-》画图,虽然内容简单了一点,但是效果出来的时候,还是很有成就感的。另外本文代码参考了现有的ros书籍,再次表示感谢。
代码的主要功能就是接收到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;
-
- }
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<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;
- }
-
前面准备好了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<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;
- }
-
上面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)
编译就很简单了,直接输入catkin_make即可。
因为启动的程序比较多,这里可以编写一个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>
实验步骤稍微复杂一点,主要分成四步。第一,打开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上面看到这样的建图效果了,