声明:本文章为导航栈的第二部分,小伙伴可以先查阅同标题的第一部分,也可以单独看这一部分
带路谁说了算?
当在rviz上面发布一个坐标点后,会看见一条绿色的线。我需要搞清楚,这个线谁设计的。查资料知道是全局规划器实现的,而dwa、teb的作用是在局部参考这个绿线然后做出反应。上一段提到的路径跟随权重就是参考全局路径的权重。当前使用的是DWA,这家伙不会自己画一条路,相对依赖全局规划。这边就出现了权衡:皮卡DWA会越野,算力小,但是不那么智能;小轿车teb可以实现精细操作,自己临时找路,操作空间特别大,但是调参困难,算力大。需要看实际情况了。
全局规划三剑客
我们跑图,避障肯定是最重要的。ROS官方有三个,分别是:dijkstra、quadratic
、grid_path。我先尝试了二次代价quadratic。这个东西就是优先避障的。但是这个东西太难调了,50cm窄通道感觉有难度,而且需要联合代价地图一起调,多次尝试最后放弃。换dijkstra试了一下,这个算法也是皮卡,暴力又粗犷,它会寻找代价最小且路程短的一条路,怎么理解?如果路上有0代价的路,它一定会贴过去,表现为跑直线,路径容易贴着膨胀半径。如果没有0代价的路,就选代价最小且路程最短的路。那么这个情况下,我必然得利用这个“特性”:调节全局膨胀半径,缩小通道中间空白区域宽度,迫使全局规划的路径贴近通道中间。
图片里蓝色是死区,红色是膨胀半径。膨胀半径的代价随着靠近障碍物而变大,所以规划器会倾向于选择膨胀半径的边缘,两边膨胀半径同时作用,逼迫全局规划器跑中间。下面是具体的配置:
之前有提到动态障碍物延迟问题,解决方法是让代价地图带有清除功能。
这边可以留意到,全局和局部的代价地图是不一样的风格,全局范围大,但是代价梯度小,代价变化慢;局部范围大,但是代价变化梯度大。这样的设计是因为,在具体的位置,规划器会同时考虑全局和局部,全局范围大,梯度小不至于卡死,局部范围小梯度大则是强调避障。
关于common_costmap
默认的配置会加载这个,代价地图的膨胀半径也写在这里。这样确实是在设置全局变量,但是如果你想精细调整代价地图,我还是建议你别加载这个。它的出发点是好的,但是容易出现参数打架白设置。关键是它不会报错告诉你,而是覆盖,或者干脆用默认参数。Yaml文件设计时是这样的,对缩进敏感,对空参数敏感,对冗余参数设置不敏感。
算力瓶颈
机器人是工控机或者树莓派,在算力上是有局限,它不会直接和你说,但是会告诉你
[warn]:代价地图更新频率跟不上预设频率。或者底盘控制频率跟不上预设频率。
如果只是导航一个任务,而且导航质量不受影响,那确实问题不大,需要留意机器人是否出现一卡一卡,如果出现了,那三个原因:规划频率低了,可能是规划器的参数设置不合理。计算太复杂,经常找不到数学上的解;还有就是控制频率过低;以及算力局限。如果是第一个问题,那比较麻烦。先排查后面两个:缓解算力,先不换局部规划器,而是在局部代价地图里面:
rolling_window: true
width: 2.2
height: 2.2
resolution: 0.025
第一个是局部代价地图跟随窗口,一般是打开,这样机器人会不断实时探测周围障碍物。下面的长和宽就是探测范围,如果太大了会比较吃算力,这边临时调小来。下面的resolution是地图分辨率(米),和gmapping的分辨率不一样,后者是建图时固定的,前者是动态控制的。
同时,你可以在amcl配置里面:
减少你的激光采样粒子数。
刚才提到的那些非常吃算力,暂时调小可以检查问题所在。
如果你想更精细一点查看cpu使用情况,我们也有专门的办法:
sudo apt install htop#安装工具
htop#启动cpu占用可视化功能
这个还可以展开,把窗口拉大看具体的。图片里面显示了8个核心的cpu占用情况(rk3588就是4大核,4小核),下面的列表写了具体的进程占用情况。图里可以看到。这边摄像头占用太多,可以降低帧率,不过这也是ros1的固有缺陷了。
如果你觉得某个进程特别重要,可以做好绑定,让某个进程专用某个核。
你必须有主心骨
如果真的是第一个原因导致卡顿(比如说导航占用20%左右那就算正常了),那么需要检查规划器,局部规划器的本质是,在你设计的参数约束下,数学计算一个最优解。然后按照那条路跑起来。那么如果你想顺利算出来,你肯定必须满足两个条件:
- 数学上有解
- 最好不要有多重根
在设计上的体现是,第一,你不要既要又要。最好不要同时最安全、路径最短、目标导向、严谨跟全局路线……这些都是好考虑,但是在这么多条件下经常无路可走,就像世界上没有完美的人一样。第二:你必须要有倾向!如果各个参数权重都接近的话,算出好多条好路,你跑哪个?如果这两个没有做好,车必然犹豫不决,选择困难症。
DWA偏好示例,这边是避障是主心骨。实际测试效果非常好。
好心办坏事的move_base
有时候机器人犹豫是正常的,具体是哪种犹豫你得看清楚,有时候小车在慢慢挪动,调整位姿,那是在尝试符合你提的所有要求。但是在move_base的眼里,它觉得小车这样肯定是遇到困难了,卡住了,发疯了,于是它会强制让车停下来。但是我们的场景是安全的,它打断了正常的调整。关键是move_base两个参数:
这里我是故意设置为0,就是为了让这个东西永不触发。第一个的意思是,挪动多长时间打断,第二个是,挪动幅度小于多少打断。
如果没有imu
文章最开始提到过,车在偏移,小角度旋转时容易位置飘,按照之前提到的强激光配置,在固定障碍物下已经非常强悍,但是如果出现随机障碍物那就暴毙了,激光被干扰,里程计误差积累,那确实没办法的结果。如果你有imu,可以让amcl在定位时参考它的数据。Imu是姿态传感器,里程计直线是准的,主要是角度会飘,imu天生处理这个问题。如果你没有,还记得局本代价地图的窗口吗,调大它,让它看到固定障碍物,缓解不适。当然,治标不治本,只能坚持一段时间,后续必飘。
参数到底写哪里了
有时候你改动参数会发现没变化,或者有莫名其妙的报错。可能的原因特别多,但是排查的顺序我建议你先看一下参数生效了没:比如它说机器人找不到路,是因为规划器的参数没写进去,而不是电机坏了。速度话题的数据一直是0,不是下位机坏了,是速度配置是空的。具体的检查方式是:
grep -n "rosparam" your.launch
rosparam list | grep costmap
我们写/move_base/local_costmap/width是可以的,这个是加载路径,你可以定。但是,既然加载路径已经包括了local_costmap,那你的yaml文件必须删掉表头了,不然就会变成/move_base/local_costmap/local_costmap/width。Move_base看不懂,就不理了,用默认参数,还不告诉你。
ROS1传参数有四种方法,优先级混乱,较为明确的是,launch下param>load>终端。传参混乱是ROS1固有问题,只能留心眼,无法根本解决
最终配置
按照上面描述的配置下来,小车在迷宫里面非常丝滑,遇到狭窄处会自己小心调整姿态,在有动态障碍物的地方可以正常通过。
具体大致配置是:
Amcl:麦克纳姆轮;粒子数1000-2000,激光为主;里程计平移置信度高,旋转低;开启全向运动,不要求移动时车头朝向目标;激光忽略低于5cm的障碍物;
DWA:避障权重高,跟随,目标点偏低;仿真时间1.2s。
全局代价地图:膨胀宽,代价下降速率低
局部代价地图:膨胀窄,代价下降速率快
总结
导航工作流大致是:
- 观察场地特征,有无动态障碍物、杂物、垃圾、光照变化等
- 清除动态障碍物,得到临时理想场地
- 检查机器人传感器情况:激光雷达范围和角度、imu、里程计、深度相机等。确定运动逻辑:差数轮、全向轮、麦克纳姆轮
- 根据上一轮雷达实际情况,合理建图
- 根据第三步选择合适的amcl配置:优先使用强传感器定位,弱传感器辅助,同时参考算力,环境变化等限制,多传感器配合;根据运动逻辑合理调节里程计各数据置信度;根据环境杂物情况调整传感器阈值,防止误判
- 根据工作环境确定规划器:行人,猫猫狗狗多,就用teb更灵活;环境相对固定,DWA皮实不出错
- 排错流程:先软件,实在不行再硬件,话题是不是写错了,参数是不是没生效,是不是不合理。算力分配是不是不够精明
- 迭代
没有完美的方案,只有工程上的权衡,稳定能跑永远比高级算法重要的多。