小张实习记录 ROS学习之路
特别鸣谢:Unity-Drive Platform Group & Other group & Github_DLv
小车使用设备清单:rslidar_16线;xsens_imu;底层STM32F103读编码器;超声波;usb_cam
Authors:Kin_Zhang & All members in Unity-Drive
阅读前提:自己学习完 ROS_21讲
0 ROS_wiki
1 bilibli 古月居 :av59458869
2 bilibli ROS机器人开发零基础入门 :av50376766 & av50377985 -> 这个系列有两个
3 MOOC 机器人操作系统入门
其余ubuntu与插件便携系列在另一个博客里记录:ROS路程上的软件包及便携系列
多说一句:其实记录这个距离我知道真正学ROS前后没有1个月吧,做的基本都是实用性,过程告诉我,大家!有时间!一定要!多看源码!血泪教训啊,找一个对不上的 frame_id 找了一下午
- ✔20211125更新:原理性更新,好像知道为什么DWA走蛇皮了,因为!heading权重!详情见 DWA论文阅读及代码分析:https://www.cnblogs.com/kin-zhang/p/15211490.html 然后分析我写到第七步那里 目录跳转吧;最近看有没有时间 在仿真里复现一下并解决一下这个问题除了调权重还有一些解决办法 /狗头。
- ✔20190920更新:小张做完啦!成功跑完了navigation的所有操作,虽然都是用的包,但是用包的过程让我对ROS的整体框架也有了认识,(还有许多种error的情况啥的);剩下就每天更新一下回忆一下整个过程和看一下概率机器人那本书(需要的话留下邮箱在评论区,我尽量都发到),原理得弄一弄;剩余8天回校
- ✔ 20190927更新:今天基本把所有的东西做完了,把超声波转成laser信息加入障碍层(其实range_sonar_layer一直是一个范围层 从来不是障碍层!)这是看作者的issue和别人讨论的时候看到的,ps源码里也有 只对最大和最小进行判断
- ✔ 20190928更新:今天试着把速度提上去了,发现Move_base里的最大速度竟然是由加速度限制的(详情见第七步);走S形还是没有解决,但是速度上来了会有点弥补效果,后面有时间上传一下导航的效果视频。今天也算是走前更新完毕了,后面回校会尝试用一下gazebo继续小白的学习。(后面想做做视觉3维的导航)
- ✔ 20190929更新:回校了,这贴算是更新全部完成了,看了一下该有的示意图都有了。
小车3D图示意:(有意购买车身设备等请点击了解:Unity-Drive)
- ✔ 20211103更新:gmapping崩溃原因,及dwa navigation为啥会蛇皮现象原因,但是都没再能实际调整了,见各个部分详情
第一步:第一个node 第一个订阅
其实前人做了超多东西,底层32和ROS的通信串口什么的一应具全,全部写好了,由于某些原因这部分代码不能完全展示,但是大概就是打开它能看到以下信息:
主程序里需要订阅joy消息
/*Review */
ros::Subscriber joy_botton = nh.subscribe("/joy",10,JoyHandler);
之所以是本人的第一个Node是当时想用joy做一个按键切换自动导航还是手动驾驶。这个当时没想着记录大概点几个点:(这里的教训就是… 初次依赖那块和joy的消息格式不太清楚,特别是我一开始写的一直都是joystick->button(0)… 是后面才知道原来是这个格式
建议大家在学习过程中多多查看ROS_wiki和其包的源码
/*Review add joy.h */
#include <sensor_msgs/Joy.h> //务必!包含头文件啊
/*Review */
void JoyHandler(const sensor_msgs::JoyConstPtr &joystick )
{
man_stop_button = joystick->buttons.at(2);
auto_drive_button = joystick->buttons.at(0);
}
第二步:odom里程计信息
这里是拿嵌入式那边STM32串口读出来的p->data STM32发布的是/littlebot/encoder所以订阅的就是这个,稍后会添加图片给大家看一下消息内容示意:提示根据自己的消息类型查看
这个ROS_WIKI也有教程,成员也是根据odometry_tutorial的模式来写的
发布关于move_base支持的传感器的各种教程->Github链接
rostopic echo /littlebot/encoder
#include <iostream>
#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <nav_msgs/Odometry.h>
#include <std_msgs/Float32MultiArray.h>
using namespace std;
#define PI 3.141592653
double x = 0.0;
double y = 0.0;
double th = 0.0; double vx = 0.0;
double vy = 0.0;
double vth = 0.0; float dt = 0.05;
double l = 0;
double r = 0;
double dl = 0;
double dr = 0;
void Encoder_Handler(const::std_msgs::Float32MultiArrayConstPtr p) //订阅编码器的消息,转换为里程消息
{
double left_pulse = p->data[1];
double right_pulse = p->data[3];
l = l + left_pulse;
r = r + right_pulse;
double dleft = left_pulse*PI*0.172/90; //计算左轮一周期内的运动路程,一圈为90个脉冲值
double dright = right_pulse*PI*0.172/90; //计算右轮一周期内的运动路程
dl = dl + dleft;
dr = dr + dright;
vx = (dleft+dright)/dt/2;
vy = 0;
vth = (dright-dleft)/dt/2/0.177;
x += vx * cos(th+vth/2) * dt;
y += vx * sin(th+vth/2) * dt;
th += vth * dt;
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "odometry_initial"); ros::NodeHandle n;
ros::Publisher odom_pub = n.advertise<nav_msgs::Odometry>("littlebot/odom", 50);
ros::Subscriber encoder_sub = n.subscribe("/littlebot/encoder", 50, Encoder_Handler);
ros::Time current_time, last_time; ros::Rate r(10);
while(n.ok())
{
current_time = ros::Time::now(); ros::spinOnce(); //since all odometry is 6DOF we'll need a quaternion created from yaw
geometry_msgs::Quaternion odom_quat = tf::createQuaternionMsgFromYaw(th); //next, we'll publish the odometry message over ROS
nav_msgs::Odometry odom;
odom.header.stamp = current_time;
odom.header.frame_id = "odom";
odom.child_frame_id = "base_link"; //set the position
odom.pose.pose.position.x = x;
odom.pose.pose.position.y = y;
odom.pose.pose.position.z = 0.0;
odom.pose.pose.orientation = odom_quat; //set the velocity
odom.twist.twist.linear.x = vx;
odom.twist.twist.linear.y = vy;
odom.twist.twist.angular.z = vth;
// SymmetricMatrix measNoiseOdom_Cov(6); measNoiseOdom_Cov = 0;
odom.pose.covariance[0] = pow(10,-2); // = 0.01221 meters / sec
odom.pose.covariance[7] = pow(10,-2); // = 0.01221 meters / sec
odom.pose.covariance[14] = pow(10,6); // = 0.01221 meters / sec
odom.pose.covariance[21] = pow(10,6); // = 0.41 degrees / sec
odom.pose.covariance[28] = pow(10,6); // = 0.41 degrees / sec
odom.pose.covariance[35] = pow(10,6);//0.2;// pow(0.1,2) = 0.41 degrees / sec //publish the message
odom_pub.publish(odom); last_time = current_time;
r.sleep();
}
}
这样我们就得到了自己的odom信息,注意头文件的包含要在Package.xml和cmake文件中添加依赖等哦,不了解的请查看阅读前提处的课程
第三步:robot_pose_ekf
这个坑巨大!首先是!imu的frame_id一定得看清楚了,一定要出现红框的两个连接才是robot_pose_ekf起了作用就是数据链是连着的。
以下是配置的launch文件,
<remap from="odom" to="/littlebot/odom" />
<remap from="imu_data" to="/imu/data" />
这里的remap就是把robot_pose_ekf的输入odom映射到/littlebot/odom上,也就是ekf融合的信息topic分别来自/littlebot/odom和/imu/data,也就是上图画出红圈圈的两个节点,(这里注意每个的frame_id一定要对应哦!imu在源码中是imu的id。错误信息:-> Could not transform imu message from %s to %s"
<node pkg="robot_pose_ekf" type="robot_pose_ekf" name="robot_pose_ekf">
<param name="output_frame" value="odom"/>
<param name="base_footprint_frame" value="base_link"/>
<param name="freq" value="30.0"/>
<param name="sensor_timeout" value="1.0"/>
<param name="odom_used" value="true"/>
<param name="imu_used" value="true"/>
<param name="vo_used" value="false"/>
<param name="gps_used" value="false"/>
<param name="debug" value="false"/>
<param name="self_diagnose" value="false"/>
<remap from="odom" to="/littlebot/odom" />
<remap from="imu_data" to="/imu/data" />
<!--_data _data-->
</node>
以上配置完成后launch一下,打开rqt_graph查看节点是否都已完成连接传输即可进行下一步。 (到这里完成的话 后面十分轻松,非常迅速)
第四步:tf_tree
如果上面的robot_pose_ekf没什么问题的话,后面就可以直接生成 -> tf 的变化图,本人最后不带map的图,生成这条线了才能 继续往下建图哦!特别是odom->base_link的
第五步:hector_mapping & karto_mapping
我也不知道gmapping为啥坑死在那个地方,
最新更新,因为我在github里面也提了一个issue,直到2021年有另一个小伙伴也遇到了这个问题,直到前段时间另一位大佬,找到了问题,对于gmapping来说如果laser_scan信息量太大会崩溃的。详情见此issue:https://github.com/ros-perception/slam_gmapping/issues/79
所以直接按着博客里的一个教程用的hector_mapping建图的,建图过程中请查看好自己的tf_tree
这个建图过程告诉我一件事就是:多了解某个领域的东西,多多尝试! 走到hector_mapping的时候!想做个回环来闭环都不行,大概给大家看一下hector的建图效果,在我们办公室外的走廊走的一幅图… 左边是做了robot_pose_ekf的融合,右边直接拿的encoder去跑的(就是从一个起点跑过去 转一转然后原路跑回来,但是后面再大的时候效果就更差了)
效果更差图:
后续我大概贴一下launch文件,主要是注意激光雷达的frame_id变为laser或是mapping那边的scan信息 输入与激光雷达的输出对应,请经常用rqt_graph来检查node之间的连接,查看是否达到效果;
听师兄说,karto建图之所以更好是内部有激光雷达的闭环操作,具体操作呢… 我也没去看但是,确实建图效果十分ok,建图结果如下:
最重要karto超级容易设置,只要tf,scan信息都ok 记下就可以launch成功,… 我不知道为啥我的gmapping至今没解决那个问题
karto设置如下:
<node pkg="slam_karto" type="slam_karto" name="slam_karto" output="screen" launch-prefix="gnome-terminal -e">
<remap from="scan" to="scan"/>
<param name="odom_frame" value="odom"/>
<param name="map_update_interval" value="25"/>
<param name="resolution" value="0.025"/>
</node>
第六步:amcl
这个没怎么设置,就跑通了,主要是发现amcl在运动过程中的定位效果通常更好。
<launch>
<!-- Arguments -->
<arg name="scan_topic" default="scan"/>
<arg name="initial_pose_x" default="0.0"/>
<arg name="initial_pose_y" default="0.0"/>
<arg name="initial_pose_a" default="0.0"/>
<!-- AMCL -->
<node pkg="amcl" type="amcl" name="amcl"> <param name="min_particles" value="500"/>
<param name="max_particles" value="3000"/>
<param name="kld_err" value="0.02"/>
<param name="update_min_d" value="0.20"/>
<param name="update_min_a" value="0.20"/>
<param name="resample_interval" value="1"/>
<param name="transform_tolerance" value="0.5"/>
<param name="recovery_alpha_slow" value="0.00"/>
<param name="recovery_alpha_fast" value="0.00"/>
<param name="initial_pose_x" value="$(arg initial_pose_x)"/>
<param name="initial_pose_y" value="$(arg initial_pose_y)"/>
<param name="initial_pose_a" value="$(arg initial_pose_a)"/>
<param name="gui_publish_rate" value="50.0"/> <remap from="scan" to="$(arg scan_topic)"/>
<param name="laser_max_range" value="3.5"/>
<param name="laser_max_beams" value="180"/>
<param name="laser_z_hit" value="0.5"/>
<param name="laser_z_short" value="0.05"/>
<param name="laser_z_max" value="0.05"/>
<param name="laser_z_rand" value="0.5"/>
<param name="laser_sigma_hit" value="0.2"/>
<param name="laser_lambda_short" value="0.1"/>
<param name="laser_likelihood_max_dist" value="2.0"/>
<param name="laser_model_type" value="likelihood_field"/> <param name="odom_model_type" value="diff"/>
<param name="odom_alpha1" value="0.1"/>
<param name="odom_alpha2" value="0.1"/>
<param name="odom_alpha3" value="0.1"/>
<param name="odom_alpha4" value="0.1"/>
<param name="odom_frame_id" value="odom"/>
<param name="base_frame_id" value="base_footprint"/> </node>
</launch>
整个amcl和move_base开启后的rqt_graph图:
第七步:navigation
主要使用的是move_base里的一系列的东西,
发现一个非常好的 解释move_base里一系列参数的东西的网址:
这里有个严重的问题就是小车的转向速度一直都有,但是明明global_plan路径规划的线很直,但是local_plan总是在转弯…(也不是什么大转弯 但是cmd_vel中有赋值,这个问题待解决,并没有看到有效方案)大概速度变换图我用PlotJuggler看了一下,如下:
右边两幅是路径我是走过去,又导航回来的路径,大致应该重合但是没有,所以amcl的定位效果还是很不错的。
左上角是速度变换图,linear是被我处理了 以免刹车太急,(取个平均处理法)
第二个问题是:最大速度由加速度决定(可能是速度切换太快所以加速度一下能加上去,修改地方为:dwa_local_planner_params.yaml
# The velocity when robot is moving in a straight line
max_trans_vel: 1.5
min_trans_vel: 0.5 #0.65
max_rot_vel: 2
min_rot_vel: 0.2
acc_lim_x: 8.5 #4 !!这里 这个我的得加到8.5就能得到1.5m/s的速度
acc_lim_y: 0.0
acc_lim_theta: 2.0
这么一看我好像知道我曾经用ROS DWA那边有啥问题了:
-
刷新路径dt太快了?走一下发现下一步的轨迹需要左移,然后右移,然后左移 emmm 这样就S形了?
这一点可能但不是最终问题原因
-
或者是把障碍物的cost因子调低一点?好像是看到了就躲一下,进了再躲一下;
这一点是XM提醒的,我提出当时我有个问题一直没解决,然而我分析的原因是这个,XM说:那就算你调低了 也只是暂时的 你走S是因为你朝向了那边 进了障碍物自然要躲除非不躲,那就不会,但是你不可能把障碍物的因子调到0的。我后面思考 觉得十分有道理!
-
最近总结的时候又再搜DWA的缺点,发现了
可能还有一个原因是heading因子cost设的太高了!![我觉得肯定是这条了!分析见后面]参考于:动态窗口法的理解和一些细节_Azahaxia的博客-程序员宝宝_动态窗口法缺点 - 程序员宝宝 (cxybb.com)
![](https://i-blog.csdnimg.cn/blog_migrate/f91d026a336f6da9d6b6477a36349dd5.png)
仔细看我走的路径中是刚好从左下角走到右上区,所以小车总是想要往右跑,跑到了墙壁处就开始躲避一下障碍物,然后又朝着目标跑,又撞墙又躲,emm 所以这个heading我感觉不应该以最终目标点为heading,而应该以全局规划的线heading?
READ ME
/**
******************************************************************************
* @file READ ME
* @author Kin.Zhang <kin_eng@163.com>
* @version V1.0.0
* @date 2019-09-06
******************************************************************************
* @attention
*
* Copyright (C) 2019 UDI Platform Group. All rights reserverd.
******************************************************************************
*/
#P.S.: already dialout chmod,source etc
#control
roslaunch ros_littlebot simple.launch
include:
littlebot simple control & xsen_imu & tf->imu
#odom
roslaunch lb_navigation lb_ekf_odom.launch
encoder odom & ekf_odom
#MAKE A MAP!
#laser 激光雷达 & pointcloud_to_laser(for karto to make a map)
roslaunch lb_navigation lb_kartomapping.launch
#laser & pointcloud_to_laser(for hector to make a map)
roslaunch lb_navigation lb_hectormapping.launch
#NAVIGATION!
#amcl location
roslaunch lb_navigation amcl.launch
#move_base
roslaunch lb_navigation move_base.launch
#ATTENTION:
save map:
cd kintest_ws/src/lb_navigation/maps/
rosrun map_server map_saver -f mapxxrecord bag:
cd ~
rosbag record -a
补充:将超声波加入costmap中
首先确认你的超声波的最小距离与最大距离,然后,根据navigation的tutorial教程将超声波转为laser信息,(我是这么干的… 可能有更好的方法,然后将我的代码贴在这里:)注意得根据自己的超声波来修改一些信息哦:,修改信息如下:
scan.angle_min = -0.08;//!!!这里改超声波扫描角度范围
scan.angle_max = 0.08;
scan.range_min = 0.2; //!!!这里改最小距离
scan.range_max = 4.0; //!!!这里改最大距离
总.cpp代码如下:
/**
******************************************************************************
* @file ultrasonic2laser
* @author Kin.Zhang <kin_eng@163.com>
* @version V1.0.0
* @date 2019-09-20
******************************************************************************
* @attention
*
* Copyright (C) 2019 UDI Platform Group. All rights reserverd.
******************************************************************************
*/
#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>
#include <std_msgs/UInt16MultiArray.h>
double ul_Forward = 0.0;
double ul_Right = 0.0;
double ul_Left = 0.0;
double ul_Behind = 0.0;void ultrasonicCallback(const::std_msgs::UInt16MultiArrayConstPtr p)
{
ul_Forward = p->data[0]/1000.0;
ul_Right = p->data[1]/1000.0;
ul_Left = p->data[3]/1000.0;
ul_Behind = p->data[2]/1000.0;
}
sensor_msgs::LaserScan pub_laser(double ultrasonic, int Dir)
{
sensor_msgs::LaserScan scan;
unsigned int num_readings = 300;
double laser_frequency = 100;
double ranges[num_readings];
double intensities[num_readings]; //generate some fake data for our laser scan
for(unsigned int i = 0; i < num_readings; ++i)
{
ranges[i] = ultrasonic;
intensities[i] = 0;
}
ros::Time scan_time = ros::Time::now(); //populate the LaserScan message
scan.header.stamp = scan_time; if (Dir == 1) scan.header.frame_id = "/ultrasonic_Forward";
else if(Dir == 2) scan.header.frame_id = "/ultrasonic_Right";
else if(Dir == 3) scan.header.frame_id = "/ultrasonic_Left";
scan.angle_min = -0.08;//!!!这里改超声波扫描角度范围
scan.angle_max = 0.08;
scan.angle_increment = (scan.angle_max-scan.angle_min) *1.0 / num_readings;
scan.time_increment = (1 / laser_frequency) / (num_readings);
scan.range_min = 0.2; //!!!这里改最小距离
scan.scan_time = (1 / laser_frequency);
scan.range_max = 4.0; //!!!这里改最大距离
scan.ranges.resize(num_readings);
for(unsigned int i = 0; i < num_readings; ++i)
{
scan.ranges[i] = ranges[i];
}
return scan;}
int main(int argc, char** argv)
{
ros::init(argc, argv, "ultrasonic2laser"); ros::NodeHandle n,n2;
ros::Publisher scan_pubF = n.advertise<sensor_msgs::LaserScan>("ultrasonic_laserF", 50);
ros::Publisher scan_pubR = n.advertise<sensor_msgs::LaserScan>("ultrasonic_laserR", 50);
ros::Publisher scan_pubL = n.advertise<sensor_msgs::LaserScan>("ultrasonic_laserL", 50);
ros::Subscriber ultrasonic_sub=n2.subscribe("/littlebot/ultrasonic", 50, ultrasonicCallback);
ros::Rate r(1.0);
while(n.ok())
{
ros::spinOnce(); // check for incoming messages
scan_pubF.publish(pub_laser(ul_Forward,1));
scan_pubR.publish(pub_laser(ul_Right,2));
scan_pubL.publish(pub_laser(ul_Left,3));
r.sleep();
}
}
插入加入超声波的显示图:
灰色是laser信息,彩色是costmap信息
但是由于我自己的超声波是串口输出有时候传输速度跟不上没办法刷新的很快,这个超声波转为laser后可以弥补激光雷达在玻璃处丢失定位的一些弊端,大家可以尝试把120°夹角的三个超声波转为一个激光雷达的360°,这样能和激光雷达共同工作。
以上,前人做的功课很多,才让小张能快速上手,但愿把这段时间的一些问题和找bug的都记录了吧。欢迎大家有问题多交流