• 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如下所示,

    1. #include
    2. #include
    3. #include
    4. #include
    5. #define pi 3.1415926
    6. class TfMove
    7. {
    8. public:
    9. TfMove(ros::NodeHandle& nh, ros::Rate& r);
    10. void VelCallback(const geometry_msgs::TwistPtr& vel);
    11. void init_sub();
    12. private:
    13. ros::NodeHandle& nh_;
    14. ros::Subscriber sub_;
    15. tf::TransformBroadcaster tfbrd_;
    16. ros::Rate rate;
    17. double x,y,z,roll,pit,yaw;
    18. };

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

    1. #include "slam_tfpub.h"
    2. TfMove::TfMove(ros::NodeHandle& nh, ros::Rate& r):nh_(nh), rate(r)
    3. {
    4. x = 0;
    5. y = 0;
    6. z = 0;
    7. roll = 0;
    8. pit = 0;
    9. yaw = 0;
    10. init_sub();
    11. }
    12. void TfMove::VelCallback(const geometry_msgs::TwistPtr& vel)
    13. {
    14. x += vel->linear.x;
    15. y += vel->linear.y;
    16. z += vel->linear.z;
    17. roll += vel->angular.x/pi * 180;
    18. pit += vel->angular.y/pi * 180;
    19. yaw += vel->angular.z/pi * 180;
    20. tf::Transform trans;
    21. trans.setOrigin(tf::Vector3(x,y,z));
    22. tf::Quaternion q;
    23. q.setRPY(roll, pit, yaw);
    24. trans.setRotation(q);
    25. tfbrd_.sendTransform(tf::StampedTransform(trans, ros::Time::now(), "map", "base_link"));
    26. rate.sleep();
    27. }
    28. void TfMove::init_sub()
    29. {
    30. sub_ = nh_.subscribe("cmd_vel", 1, &TfMove::VelCallback, this);
    31. ros::spin();
    32. }
    33. int main(int argc, char* argv[])
    34. {
    35. ros::init(argc, argv, "myslam_tfpub");
    36. ros::NodeHandle nh;
    37. ros::Rate rate(10);
    38. TfMove tfmove(nh, rate);
    39. return 0;
    40. }

    2、编写slam_laser文件

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

    1. #include
    2. #include
    3. class LaserScanPub
    4. {
    5. public:
    6. LaserScanPub(ros::NodeHandle& nh,
    7. double minAngle, double maxAngle, double scanTime,
    8. double minRange, double maxRange, double scanNums);
    9. ~LaserScanPub();
    10. void scanpub_init();
    11. void laserdata_init();
    12. private:
    13. ros::NodeHandle nh_;
    14. ros::Publisher scanpub_;
    15. sensor_msgs::LaserScan laserdata_;
    16. double minAngle;
    17. double maxAngle;
    18. double minRange;
    19. double maxRange;
    20. double scanTime;
    21. double scanNums;
    22. };

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

    1. #include "slam_laser.h"
    2. LaserScanPub::LaserScanPub(ros::NodeHandle& nh, double min_angle, double maxAngle,
    3. double scanTime, double minRange, double maxRange, double scanNums)
    4. :nh_(nh),minAngle(minAngle), maxAngle(maxAngle),minRange(minRange),
    5. maxRange(maxRange), scanNums(scanNums), scanTime(scanTime)
    6. {
    7. scanpub_init();
    8. }
    9. LaserScanPub::~LaserScanPub()
    10. {
    11. }
    12. void LaserScanPub::laserdata_init()
    13. {
    14. ros::Time scantime = ros::Time::now();
    15. laserdata_.header.stamp = scantime;
    16. laserdata_.header.frame_id = "base_link";
    17. laserdata_.range_min = minRange;
    18. laserdata_.range_max = maxRange;
    19. laserdata_.scan_time = scanTime;
    20. laserdata_.angle_increment = (maxAngle - minAngle)/scanNums; // angle resolution
    21. laserdata_.time_increment = scanTime/scanNums; // time resolution
    22. laserdata_.ranges.resize(scanNums);
    23. laserdata_.intensities.resize(scanNums);
    24. for(int i = 0; i < scanNums; i++)
    25. {
    26. laserdata_.ranges[i] = 5;
    27. laserdata_.intensities[i] = 100;
    28. }
    29. }
    30. void LaserScanPub::scanpub_init()
    31. {
    32. scanpub_ = nh_.advertise<sensor_msgs::LaserScan>("scan", 100);
    33. ros::Rate rate(10);
    34. while(nh_.ok())
    35. {
    36. laserdata_init();
    37. scanpub_.publish(laserdata_);
    38. rate.sleep();
    39. }
    40. }
    41. int main(int argc, char* argv[])
    42. {
    43. ros::init(argc, argv, "myslam_laser");
    44. ros::NodeHandle nh;
    45. LaserScanPub scanpub(nh, 0, 1.57, 0.01, 0, 10, 100);
    46. return 0;
    47. }

    3、编写book_myslam文件

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. using namespace std;
    13. struct MapPoint
    14. {
    15. int x;
    16. int y;
    17. MapPoint()
    18. {
    19. x = 0;
    20. y = 0;
    21. }
    22. MapPoint(int x0, int y0)
    23. {
    24. x = x0;
    25. y = y0;
    26. }
    27. };
    28. class MySlam
    29. {
    30. public:
    31. MySlam(ros::NodeHandle& nh, double mapreso, double mposx, double mposy,
    32. double mposz, double morientx, double morienty, double orientz,
    33. double morientw, int mwidth, int mheight);
    34. ~MySlam();
    35. void mappub_init();
    36. void lasersub_init();
    37. void lasercallback(const sensor_msgs::LaserScanConstPtr& laserdata);
    38. void mapdata_init();
    39. vector bresenham(int x0, int y0, int x1, int y1);
    40. private:
    41. ros::NodeHandle nh_;
    42. ros::Subscriber lasersub_;
    43. ros::Publisher mappub_;
    44. tf::TransformListener tflistener_;
    45. nav_msgs::OccupancyGrid mapdata_;
    46. double mapreso;
    47. double mposx;
    48. double mposy;
    49. double mposz;
    50. double morientx;
    51. double morienty;
    52. double morientz;
    53. double morientw;
    54. int mwidth;
    55. int mheight;
    56. vector endpoints;
    57. MapPoint endpoint;
    58. vector mappoints;
    59. tf::StampedTransform base2map;
    60. tf::Quaternion quat;
    61. double theta;
    62. tf::Vector3 trans_base2map;
    63. double tx, ty;
    64. int basex0, basey0;
    65. double basex, basey;
    66. double mapx, mapy;
    67. double beamsAngle;
    68. int mapxn, mapyn;
    69. int laserNum;
    70. int nx,ny;
    71. int idx;
    72. ofstream fopen;
    73. int scan_count;
    74. int scan_reso;
    75. boost::mutex map_mutex;
    76. };

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

    1. #include "book_myslam.h"
    2. MySlam::MySlam(ros::NodeHandle& nh, double mapreso, double mposx, double mposy,
    3. double mposz, double morientx, double morienty, double morientz,
    4. double morientw, int mwidth, int mheight):nh_(nh), mapreso(mapreso),
    5. mposx(mposx), mposy(mposy), mposz(mposz), morientx(morientx),
    6. morienty(morienty), morientz(morientz), morientw(morientw),
    7. mwidth(mwidth), mheight(mheight)
    8. {
    9. mapdata_init();
    10. mappub_init();
    11. lasersub_init();
    12. }
    13. MySlam::~MySlam()
    14. {
    15. }
    16. void MySlam::lasercallback(const sensor_msgs::LaserScanConstPtr& laserdata)
    17. {
    18. if(scan_count % scan_reso == 0)
    19. {
    20. try {
    21. tflistener_.waitForTransform("map", "base_link", ros::Time(0), ros::Duration(3.0));
    22. tflistener_.lookupTransform("map", "base_link", ros::Time(0), base2map);
    23. }
    24. catch(tf::TransformException& ex)
    25. {
    26. ROS_INFO("%s", ex.what());
    27. ros::Duration(1.0).sleep();
    28. }
    29. boost::mutex::scoped_lock map_lock(map_mutex);
    30. quat = base2map.getRotation();
    31. theta = quat.getAngle();
    32. trans_base2map = base2map.getOrigin();
    33. tx = trans_base2map.getX();
    34. ty = trans_base2map.getY();
    35. basex0 = int(tx/mapreso);
    36. basey0 = int(ty/mapreso);
    37. laserNum = laserdata->ranges.size();
    38. fopen.open("data.txt", ios::app);
    39. if(fopen.is_open())
    40. {
    41. cout << "open file successful!" << endl;
    42. }
    43. else
    44. {
    45. cout << "open file fail" << endl;
    46. }
    47. for(int i = 0; i < laserNum; i++)
    48. {
    49. beamsAngle = laserdata->angle_min + i * laserdata->angle_increment;
    50. basex = laserdata->ranges[i] * cos(beamsAngle);
    51. basey = laserdata->ranges[i] * sin(beamsAngle);
    52. mapx = basex * cos(theta) + basey * sin(theta) + tx;
    53. mapy = basey * cos(theta) - basex * sin(theta) + ty;
    54. nx = int(mapx/mapreso);
    55. ny = int(mapy/mapreso);
    56. mapxn = nx + 1;
    57. mapyn = ny + 1;
    58. endpoint.x = mapxn;
    59. endpoint.y = mapyn;
    60. fopen << endpoint.x << " " << endpoint.y << std::endl;
    61. endpoints.push_back(endpoint);
    62. }
    63. fopen.close();
    64. for(vector<MapPoint>::iterator iter = endpoints.begin(); iter != endpoints.end(); iter++)
    65. {
    66. mappoints = MySlam::bresenham(basex0, basey0, (*iter).x, (*iter).y);
    67. cout << "scan numbers: " << endpoints.size() << endl;
    68. cout << "bresenham point nums are: " << mappoints.size() << endl;
    69. cout << "x0, y0 is " << basex0 << " " << basey0 << std::endl;
    70. cout << "angle is " << theta << std::endl;
    71. for(vector<MapPoint>::iterator iter1 = mappoints.begin(); iter1 != mappoints.end(); iter1 ++)
    72. {
    73. idx = mwidth * (*iter1).y + (*iter1).x;
    74. cout << "idx is " << (*iter1).x << " " << (*iter1).y << std::endl;
    75. mapdata_.data[idx] = 0;
    76. }
    77. mappoints.clear();
    78. }
    79. endpoints.clear();
    80. mappub_.publish(mapdata_);
    81. }
    82. scan_count ++;
    83. }
    84. vector<MapPoint> MySlam::bresenham(int x0, int y0, int x1, int y1)
    85. {
    86. vector<MapPoint> pp;
    87. MapPoint p;
    88. int dx, dy, h, a, b, x, y, flag, t;
    89. dx = abs(x1-x0);
    90. dy = abs(y1-y0);
    91. if(x1 > x0)
    92. a = 1;
    93. else
    94. a = -1;
    95. if(y1 > y0)
    96. b = 1;
    97. else
    98. b = -1;
    99. x = x0;
    100. y = y0;
    101. if(dx >= dy)
    102. {
    103. flag = 0;
    104. }
    105. else
    106. {
    107. t = dx;
    108. dx = dy;
    109. dy = t;
    110. flag = 1;
    111. }
    112. h = 2 * dy - dx;
    113. for(int i = 1; i <= dx; ++i)
    114. {
    115. p.x = x, p.y = y;
    116. pp.push_back(p);
    117. if(h >= 0)
    118. {
    119. if(flag == 0)
    120. y = y+b;
    121. else
    122. x = x+a;
    123. h =h - 2*dx;
    124. }
    125. if(flag ==0)
    126. x = x+a;
    127. else
    128. y = y+b;
    129. h = h + 2*dy;
    130. }
    131. return pp;
    132. }
    133. void MySlam::mappub_init()
    134. {
    135. mappub_ = nh_.advertise<nav_msgs::OccupancyGrid>("map", 100);
    136. }
    137. void MySlam::lasersub_init()
    138. {
    139. lasersub_ = nh_.subscribe("scan", 1, &MySlam::lasercallback, this);
    140. }
    141. void MySlam::mapdata_init()
    142. {
    143. scan_count = 0;
    144. scan_reso = 1;
    145. ros::Time currtime = ros::Time::now();
    146. mapdata_.header.stamp = currtime;
    147. mapdata_.header.frame_id = "map";
    148. mapdata_.info.resolution = mapreso;
    149. mapdata_.info.width = mwidth;
    150. mapdata_.info.height = mheight;
    151. mapdata_.info.origin.position.x = mposx;
    152. mapdata_.info.origin.position.y = mposy;
    153. mapdata_.info.origin.position.z = mposz;
    154. mapdata_.info.origin.orientation.x = morientx;
    155. mapdata_.info.origin.orientation.y = morienty;
    156. mapdata_.info.origin.orientation.z = morientz;
    157. mapdata_.info.origin.orientation.w = morientw;
    158. int datasize = mwidth * mheight;
    159. mapdata_.data.resize(datasize);
    160. for(int i = 0; i < datasize; i++)
    161. {
    162. mapdata_.data[i] = -1;
    163. }
    164. }
    165. int main(int argc, char* argv[])
    166. {
    167. int debug_flag = 0;
    168. //while(debug_flag == 0) sleep(10);
    169. ros::init(argc, argv, "MySlam");
    170. ros::NodeHandle nh;
    171. double mapreso = 0.05;
    172. double mposx = 0;
    173. double mposy = 0;
    174. double mposz = 0;
    175. double morientx = 0;
    176. double morienty = 0;
    177. double morientz= 0;
    178. double morientw= 1;
    179. int mwidth = 300;
    180. int mheight = 300;
    181. MySlam myslam(nh, mapreso, mposx, mposy, mposz, morientx, morienty, morientz, morientw, mwidth, mheight);
    182. ros::spin();
    183. return 0;
    184. }

    4、准备编译脚本

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

    1. add_executable(slam_tfpub src/slam_tfpub.cpp)
    2. target_link_libraries(slam_tfpub ${catkin_LIBRARIES})
    3. add_dependencies(slam_tfpub beginner_tutorials_generate_messages_cpp)
    4. add_executable(slam_laser src/slam_laser.cpp)
    5. target_link_libraries(slam_laser ${catkin_LIBRARIES})
    6. add_dependencies(slam_laser beginner_tutorials_generate_messages_cpp)
    7. add_definitions("-Wall -g")
    8. add_executable(book_myslam src/book_myslam.cpp)
    9. target_link_libraries(book_myslam ${catkin_LIBRARIES})
    10. add_dependencies(book_myslam beginner_tutorials_generate_messages_cpp)

    5、编译

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

    6、构建launch文件

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

    1. <launch>
    2. <node pkg="beginner_tutorials" type="slam_tfpub" name="tf_pub"/>
    3. <node pkg="beginner_tutorials" type="slam_laser" name="laser_pub"/>
    4. <node pkg="beginner_tutorials" type="book_myslam" name="myslam"/>
    5. </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上面看到这样的建图效果了,

  • 相关阅读:
    已经有 MESI 协议,为什么还需要 volatile 关键字?
    记一次opencv安装过程
    leetcode347 前 K 个高频元素
    【计算机视觉 | 目标检测】目标检测常用数据集及其介绍(十六)
    Flutter开发实战之Google Play 最佳应用程序开发者分享Flutter经验与技巧
    Redis渐进式rehash小疑问
    游戏开发丨基于Tkinter的五子棋小游戏
    Java使用javah命令:‘javah‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。
    【指针内功修炼】字符指针 + 指针数组 + 数组指针 + 指针参数(一)
    【C++】STL——list模拟实现
  • 原文地址:https://blog.csdn.net/feixiaoxing/article/details/133938333