1.gazebo 仿真环境创建
安装依赖:
(base) hjb@jl16k:~$ sudo apt install ros-$ROS_DISTRO-gazebo-ros-control
(base) hjb@jl16k:~$ sudo apt install ros-$ROS_DISTRO-effort-controllers
(base) hjb@jl16k:~$ sudo apt install ros-$ROS_DISTRO-joint-state-controller
(base) hjb@jl16k:~$ sudo apt install ros-$ROS_DISTRO-driver-base
(base) hjb@jl16k:~$ sudo apt install ros-$ROS_DISTRO-ackermann-msgs
(base) hjb@jl16k:~$ sudo apt install ros-$ROS_DISTRO-rtabmap-ros
(base) hjb@jl16k:~$ sudo apt install ros-$ROS_DISTRO-teb-local-planner
(base) hjb@jl16k:~$ sudo apt install tcl-dev tk-dev python3-tk
若在安装依赖的过程中出现以下错误:
通过输入以下代码尝试解决:
(base) hjb@jl16k:~$ sudo apt-get update
创建工作空间:
(base) hjb@jl16k:~$ mkdir -p catkin_gazebo_ws/src
下载git工程:
(base) hjb@jl16k:~/catkin_gazebo_ws$ cd src
(base) hjb@jl16k:~/catkin_gazebo_ws/src$ git clone https://github.com/soonuse/racecar.git
编译:
(base) hjb@jl16k:~/catkin_gazebo_ws/src$ cd ..
(base) hjb@jl16k:~/catkin_gazebo_ws$ catkin_make
如果编译过程中出现以下错误:
错误1:
解决办法:
(base) hjb@jl16k:~/catkin_gazebo_ws$ catkin_make -DPYTHON_EXECUTABLE=/usr/bin/python3
错误2:
解决办法:
找到自己的opencv安装路径,像我的就是 /usr/local/include/opencv4/opencv2
然后打开提示错误的这个文件,当然这个文件只可读,可以使用以下命令打开并修改:
(base) hjb@jl16k:/opt/ros/noetic/share/cv_bridge/cmake$ sudo gedit cv_bridgeConfig.cmake
大概在90多行,将箭头所指修改成上面找到的opencv安装路径,注意修改两个地方
修改完之后,重新编译:
(base) hjb@jl16k:~/catkin_gazebo_ws$ catkin_make -DPYTHON_EXECUTABLE=/usr/bin/python3
把工作区的setup.bash文件写入.bashrc文件中,先执行以下命令,然后得到路径/home/hjb/catkin_gazebo_ws/devel
打开新的终端:
(base) hjb@jl16k:~$ sudo gedit .bashrc
添加下图中的两行,路径就是上面所得到的路径并加上setup.bash文件名:
保存文件,为了使用配置,输入:
(base) hjb@jl16k:~$ . .bashrc
** 需要注意的是,这个仿真环境是从网上下载的,也就是git那一步。需要了解这个工作空间中各个文件的用处:**
像途中racecar_description里面存的是机器人模型(urdf)等,而racecar_gazebo里面是放与Gazebo有关的启动文件(launch)和世界文件(word)等。这个工作空间完全是可以自己创建的,其中图所示名字也是可以改变的,例如racecar_description完全可以改成myrobot_description,当然这是有能力自己创建的前提下。
2.gazebo的使用
初次使用gazebo的可以参考一下,里面有一些功能建的说明与使用:ROS入门(四)——Gazebo的基本使用 - 古月居
(1)简单创建一个地图模型
首先进入gazebo:
(base) hjb@jl16k:~/catkin_gazebo_ws$ gazebo
然后左上角点击Edit,选择Buiding Editor,进入下图界面
可以看到左窗口有walls(墙)等,点击wall简单创建一个房子后,点击左上角File,然后选择Save,建议第一次使用保存在主目录(home)下。如下图所示,我保存下来的是house文件,命名是随意的。打开house文件会发现有两个文件。
保存完之后,选择File Exit Building Editor,然后会有弹窗,选择Exit。就会回到原来的界面。如下图所示。
这个就属于world文件了,点击File,选择Save world,名字最好加上world(注意这个文件后缀记得加 .world,保存的时候不会默认是world文件的,需要自己加,图中我是保存错了)以区分这是一个world文件,然后也是先保存在主目录,以后熟练了可以根据需求保存。保存完之后目录下的文件如下图所示:
到此,关掉gazebo。那又该如何打开之前建好的模型呢?
重新打开gazebo,点击左窗口的Insert,然后看到我下图箭头所指,这个就是上面所建的模型,点击即可拉到界面。
当然值的注意的是,上面的例子是墙体模型,也有其他的模型,这需要慢慢去探索了。
(2)下载模型
一般来说,如果有现成的图形模型的话,那就大大节省了自己的时间,可以使用有的模型来做实验。
下载模型有两种反法,我用的是第二种方法,因为第一种方法可能是我的网不行下载的非常慢,虽然第二种方法我也下载了3个多小时。
方法1:注意的是.gazebo是隐藏的文件,可以在主目录(home)使用ctrl+H来显示出来,此外使用该方法下载的文件名为gazebo_models,需要将这个文件名改为models
(base) hjb@jl16k:~$ cd ~/.gazebo
(base) hjb@jl16k:~/.gazebo$ git clone https://github.com/osrf/gazebo_models
方法2:
(base) hjb@jl16k:~$ cd ~/.gazebo
(base) hjb@jl16k:~/.gazebo$ mkdir -p models
(base) hjb@jl16k:~/.gazebo$ cd models/
(base) hjb@jl16k:~/.gazebo/models$ wget http://file.ncnynl.com/ros/gazebo_models.txt
(base) hjb@jl16k:~/.gazebo/models$ wget -i gazebo_models.txt
(base) hjb@jl16k:~/.gazebo/models$ ls model.tar.g* | xargs -n1 tar xzvf
如果到下图这一步,应该算成功了吧。我也是刚入门不太懂,从文字上看下载了135M,应该不止这么小的。重复方法2倒数第二条命令还可以下载,我浅浅试了一下,不过我终止了,以后有需要再尝试一下吧。 最后解压一下就Ok了。
打开gazebo:
(base) hjb@jl16k:~/catkin_gazebo_ws$ gazebo
进入下图界面会发现,本地模型有了,随便拉一个进入 ,有显示就成功了。我拉了cafe这个模型进去。
(3)world模型的打开,使用launch文件
此前建了一个模型,有house的地图模型,以及world模型。
上面所示的打开是使用house模型,而建好的world场景又如何打开呢?这就得用launch文件打开了。
如下图所示,一般仿真环境都会有保存launch文件的功能包,像我这个环境存放launch文件的是racecar_gazebo,并且在这个功能包中有worlds文件,所创建的world文件应该拉到这个地方。所以将上面创建的house.world文件拉到worlds文件中,方便写launch文件。
写文件推荐使用vscode,用vscode打开catkin_gazebo_ws这个工作空间,然后在launch文件夹中创建一个launch文件,我创建的文件名为 display_house_world_in_gazebo.launch,这样创建可以知道这个launch文件是干嘛的,从意思上看我这个就是在gazebo中展示house.world。
代码如下,唯一要修改的就是 <arg name="world_name" value="$(find racecar_gazebo)/worlds/house_world_as.world"/>这一行了,自己world文件的路径如果不同根据自己作修改,$(find racecar_gazebo)就是找到racecar_gazebo这个功能包的路径,如果创建的功能包名字不一样修改一下就好了:
<?xml version="1.0"?>
<launch>
<!-- We resume the logic in empty_world.launch, changing only the name of the world to be launched -->
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="world_name" value="$(find racecar_gazebo)/worlds/house_world_as.world"/>
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="gui" value="true"/>
<arg name="headless" value="false"/>
<arg name="debug" value="false"/>
</include>
</launch>
然后执行一下命令即可看到打开的world文件:
(base) hjb@jl16k:~/catkin_gazebo_ws$ source devel/setup.bash
(base) hjb@jl16k:~/catkin_gazebo_ws$ roslaunch racecar_gazebo display_house_world_in_gazebo.launch
(4)world模型+小车模型的打开,使用launch文件
world+小车的打开也就是在world场景下增加了一辆小车,这就需要一个小车模型了。一般来说从网上下载的仿真环境都会有小车模型 。像下图中所示,我的小车模型就是在/catkin_gazebo_ws/src/racecar/racecar_description/urdf,其中我要使用的就是racecar_xacro。
同样需要在launch文件夹中创建一个launch文件,一般来说展示world场景和小车的launch文件较为复杂,我是通过复制所下载的环境中的一个launch文件修改得到的。
如下图所示,我所下载的仿真环境中小车+world展示的launch文件是racecar.launch,但是这个文件所指定的world文件并不是我想要的场景,因此负责该文件的内容创建一个launch文件,然后修改一下world文件的名字。
代码修改说明:
像我这个文件的话,其实以及很友好了,如果想要换world文件,只需要在<arg name="world_name" default="house_world_as" />这一行中修改default的值就可以了,像我的world文件名是house_world_as。注意这个地方不需要加文件名的后缀.world。当然在include的那部分中找world_name的路径也是需要注意的,如果和我的仿真环境不同需要自己改。我的launch文件名是display_house_world_and_car_in_gazebo.launch
<?xml version="1.0"?>
<launch>
<arg name="world_name" default="house_world_as" />
<arg name="gui" default="true" />
<arg name="run_camera" default="false"/>
<arg name="x_pos" default="-5.388334"/>
<arg name="y_pos" default="-4.094883"/>
<arg name="z_pos" default="0.0"/>
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="world_name" value="$(find racecar_gazebo)/worlds/$(arg world_name).world"/>
<arg name="gui" value="$(arg gui)"/>
</include>
<!-- urdf xml robot description loaded on the Parameter Server, converting the xacro into a proper urdf file-->
<param name="robot_description" command="$(find xacro)/xacro --inorder '$(find racecar_description)/urdf/racecar.xacro'" />
<!-- push robot_description to factory and spawn robot in gazebo -->
<node name="racecar_spawn" pkg="gazebo_ros" type="spawn_model" output="screen" args="-urdf -param robot_description -model racecar -x $(arg x_pos) -y $(arg y_pos) -z $(arg z_pos)" />
<!-- ros_control racecar launch file -->
<include file="$(find racecar_control)/launch/racecar_control.launch" ns="/"/>
<!-- Spawn the MUXs -->
<arg name="racecar_version" default="racecar-v2" />
<include file="$(find racecar)/launch/mux.launch" ns="vesc" />
<!-- Publish "better odom" topic that is normally generated by the particle filter -->
<node name="better_odom" pkg="topic_tools" type="relay"
args="/vesc/odom /pf/pose/odom" />
<!--Launch the simulation joystick control-->
<rosparam command="load" file="$(find racecar_gazebo)/config/keyboard_teleop.yaml" />
<node pkg="racecar_gazebo" type="keyboard_teleop.py" name="keyboard_teleop" />
</launch>
执行,会发现多了个小车:
(base) hjb@jl16k:~/catkin_gazebo_ws$ roslaunch racecar_gazebo display_house_world_and_car_in_gazebo.launch
(5)使用这个world模型+小车模型来建一下图(gmapping)
这一步需要gmapping功能包,这个功能包的下载可以网上找教程下载,下载好之后就可以不管了。launch文件还是创在仿真环境中的,在launch文件中指定gmapping的功能包名字会自动寻找并使用。
一般来说网上下载的仿真环境一般都会有gmapping的launch文件,如果没有也没有关系。启动gmapping建图需要创建两个launch文件,这些launch文件全都是放在图中上面所示的路径中,此后不在赘述。并且这两个文件的内容是不需要改就可以直接使用了,当然前提是有gmapping功能包。
slam_gmapping.launch(这个文件的作用是调用gmapping,如果调用的文件名不一样注意修改文件路径):
<launch>
<include file="$(find racecar_gazebo)/launch/gmapping.launch"/>
<!-- 启动rviz -->
<node pkg="rviz" type="rviz" name="rviz" args="-d $(find racecar_gazebo)/config/new_gmapping.rviz"/>
</launch>
gmapping.launch:
<launch>
<node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" output="screen">
<param name="base_frame" value="base_link"/> <!--机器人底盘坐标系基框架,附带在移动底盘的框架,原点-->
<param name="odom_frame" value="odom"/> <!--里程计坐标系里程计框架,附带在里程计的框架-->
<param name="map_frame" value="map"/> <!--地图坐标系地图框架,附带在地图上的框架-->
<param name="map_update_interval" value="0.01"/><!--地图更新速度,秒0.01-->
<param name="maxUrange" value="10.0"/><!--激光最大可用距离-->
<param name="maxRange" value="12.0"/><!--zuida juli-->
<param name="sigma" value="0.05"/>
<param name="kernelSize" value="3"/><!--moren:1-->
<param name="lstep" value="0.05"/>
<param name="astep" value="0.05"/>
<param name="iterations" value="5"/>
<param name="lsigma" value="0.075"/>
<param name="ogain" value="3.0"/>
<param name="lskip" value="0"/>
<param name="srr" value="0.1"/>
<param name="srt" value="0.2"/>
<param name="str" value="0.1"/>
<param name="stt" value="0.2"/>
<param name="minimumScore" value="0"/>
<param name="linearUpdate" value="0.05"/><!--线速度角速度在地图的更新-->
<param name="angularUpdate" value="0.0436"/>
<param name="temporalUpdate" value="-1"/><!--moren:-1-->
<param name="resampleThreshold" value="0.5"/>
<param name="particles" value="8"/><!--moren:30 gaicheng:8-->
<param name="xmin" value="-50.0"/>
<param name="ymin" value="-50.0"/>
<param name="xmax" value="50.0"/>
<param name="ymax" value="50.0"/>
<param name="delta" value="0.05"/>
<param name="llsamplerange" value="0.01"/>
<param name="llsamplestep" value="0.01"/>
<param name="lasamplerange" value="0.005"/>
<param name="lasamplestep" value="0.005"/>
<!--param name="transform_publish_period" value="0.01"/-->
</node>
</launch>
有了建图启动文件就可以建图了。
一般来说,我一旦使用ros,都会先roscore一下,建议也roscore一下:
(base) hjb@jl16k:~$ roscore
首先,先打开之前自己创建的场景,也就是world+小车的场景,打开一个新的终端,使用launch文件启动:
(base) hjb@jl16k:$ cd catkin_gazebo_ws
(base) hjb@jl16k:~/catkin_gazebo_ws$ source devel/setup.bash
(base) hjb@jl16k:~/catkin_gazebo_ws$ roslaunch racecar_gazebo display_house_world_and_car_in_gazebo.launch
有真实地图和机器人了,就可以控制机器人使用算法建图了,使用launch文件启动建图和键盘控制,这个键盘控制是我这个仿真环境写好的,至于其他仿真环境的话,那我只能说我无能为力:
打开新终端:
(base) hjb@jl16k:~$ cd catkin_gazebo_ws/
(base) hjb@jl16k:~/catkin_gazebo_ws$ source devel/setup.bash
(base) hjb@jl16k:~/catkin_gazebo_ws$ roslaunch racecar_gazebo slam_gmapping.launch
键盘控制移动扫描完整个地图完成建图,在建图想要移动必须点击下图所示的这个界面才能移动:
建好图的效果:
打开新终端,保存地图,可以根据自己需求改路径:
(base) hjb@jl16k:~$ rosrun map_server map_saver -f /home/hjb/catkin_gazebo_ws/src/racecar/racecar_gazebo/map/house
(6)使用上步所保存的图来导航
有了建图自然少不了导航,先说一下导航的前提条件,一般来说导航需要使用move_base功能包,这个包一般在navigation包中。而这个navigation包一般安装gmapping算法的时候会自带,如果没有需要自己下载这个navigation包,这个包网上教程参考下载即可。
到这一步,之前打开的终端可以都关了。实现导航需要两个launch文件,一个rivz配置文件,以及一个路径追踪的脚本。
racecar_house_and_car_world_navigation.launch :
有两处有根据自己修改:
<arg name="world_name" value="house_world_as"/> 的house_world_as要改成自己的文件名。
<node name="map_server" pkg="map_server" type="map_server" args="$(find racecar_gazebo)/map/house .yaml" />要house.yaml文件的路径要根据自己保存的修改,这个文件就是(5)保存地图的文件。
<?xml version="1.0"?>
<launch>
<!-- Launch the racecar -->
<include file="$(find racecar_gazebo)/launch/racecar.launch">
<arg name="world_name" value="house_world_as"/>
</include>
<!-- Launch the built-map -->
<node name="map_server" pkg="map_server" type="map_server" args="$(find racecar_gazebo)/map/house .yaml" />
<!--Launch the move base with time elastic band-->
<param name="/use_sim_time" value="true"/>
<node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
<rosparam file="$(find racecar_gazebo)/config/costmap_common_params.yaml" command="load" ns="global_costmap" />
<rosparam file="$(find racecar_gazebo)/config/costmap_common_params.yaml" command="load" ns="local_costmap" />
<rosparam file="$(find racecar_gazebo)/config/local_costmap_params.yaml" command="load" />
<rosparam file="$(find racecar_gazebo)/config/global_costmap_params.yaml" command="load" />
<rosparam file="$(find racecar_gazebo)/config/teb_local_planner_params.yaml" command="load" />
<param name="base_global_planner" value="global_planner/GlobalPlanner" />
<param name="planner_frequency" value="0.01" />
<param name="planner_patience" value="5.0" />
<!--param name="use_dijkstra" value="false" /-->
<param name="base_local_planner" value="teb_local_planner/TebLocalPlannerROS" />
<param name="controller_frequency" value="5.0" />
<param name="controller_patience" value="15.0" />
<param name="clearing_rotation_allowed" value="false" />
</node>
</launch>
racecar_rviz.launch :
<launch>
<!-- 启动rviz -->
<node pkg="rviz" type="rviz" name="rviz" args="-d $(find racecar_gazebo)/config/racecar_rviz.rviz"/>
</launch>
因为这个rviz配置是我所下载的这个仿真环境自带有的,如果使用的不是我这个仿真环境需要自己写一个rviz配置文件,并将上面的racecar_rviz.launch文件的路径改为这个rviz文件的路径。。
racecar_rviz.rviz :
Panels:
- Class: rviz/Displays
Help Height: 78
Name: Displays
Property Tree Widget:
Expanded:
- /Global Options1
- /Status1
- /LaserScan1
- /PoseArray1
Splitter Ratio: 0.5
Tree Height: 775
- Class: rviz/Selection
Name: Selection
- Class: rviz/Tool Properties
Expanded:
- /2D Pose Estimate1
- /2D Nav Goal1
- /Publish Point1
Name: Tool Properties
Splitter Ratio: 0.588679016
- Class: rviz/Views
Expanded:
- /Current View1
Name: Views
Splitter Ratio: 0.5
- Class: rviz/Time
Experimental: false
Name: Time
SyncMode: 0
SyncSource: LaserScan
Toolbars:
toolButtonStyle: 2
Visualization Manager:
Class: ""
Displays:
- Alpha: 0.5
Cell Size: 1
Class: rviz/Grid
Color: 160; 160; 164
Enabled: true
Line Style:
Line Width: 0.0299999993
Value: Lines
Name: Grid
Normal Cell Count: 0
Offset:
X: 0
Y: 0
Z: 0
Plane: XY
Plane Cell Count: 10
Reference Frame: <Fixed Frame>
Value: true
- Alpha: 0.699999988
Class: rviz/Map
Color Scheme: map
Draw Behind: false
Enabled: true
Name: Map
Topic: /map
Unreliable: false
Use Timestamp: false
Value: true
- Alpha: 1
Axes Length: 1
Axes Radius: 0.100000001
Class: rviz/Pose
Color: 255; 25; 0
Enabled: true
Head Length: 0.300000012
Head Radius: 0.100000001
Name: Pose
Shaft Length: 1
Shaft Radius: 0.0500000007
Shape: Arrow
Topic: /move_base_simple/goal
Unreliable: false
Value: true
- Angle Tolerance: 0.100000001
Class: rviz/Odometry
Covariance:
Orientation:
Alpha: 0.5
Color: 255; 255; 127
Color Style: Unique
Frame: Local
Offset: 1
Scale: 1
Value: true
Position:
Alpha: 0.300000012
Color: 204; 51; 204
Scale: 1
Value: true
Value: true
Enabled: true
Keep: 100
Name: Odometry
Position Tolerance: 0.100000001
Shape:
Alpha: 1
Axes Length: 1
Axes Radius: 0.100000001
Color: 255; 25; 0
Head Length: 0.300000012
Head Radius: 0.100000001
Shaft Length: 1
Shaft Radius: 0.0500000007
Value: Arrow
Topic: /vesc/odom
Unreliable: false
Value: true
- Alpha: 1
Autocompute Intensity Bounds: true
Autocompute Value Bounds:
Max Value: 10
Min Value: -10
Value: true
Axis: Z
Channel Name: intensity
Class: rviz/LaserScan
Color: 255; 255; 255
Color Transformer: Intensity
Decay Time: 0
Enabled: true
Invert Rainbow: false
Max Color: 255; 255; 255
Max Intensity: 0
Min Color: 0; 0; 0
Min Intensity: 0
Name: LaserScan
Position Transformer: XYZ
Queue Size: 10
Selectable: true
Size (Pixels): 3
Size (m): 0.100000001
Style: Flat Squares
Topic: /scan
Unreliable: false
Use Fixed Frame: true
Use rainbow: true
Value: true
- Alpha: 1
Buffer Length: 1
Class: rviz/Path
Color: 25; 255; 0
Enabled: true
Head Diameter: 0.300000012
Head Length: 0.200000003
Length: 0.300000012
Line Style: Lines
Line Width: 0.0299999993
Name: Path
Offset:
X: 0
Y: 0
Z: 0
Pose Color: 255; 85; 255
Pose Style: None
Radius: 0.0299999993
Shaft Diameter: 0.100000001
Shaft Length: 0.100000001
Topic: /move_base/TebLocalPlannerROS/global_plan
Unreliable: false
Value: true
- Alpha: 0.699999988
Class: rviz/Map
Color Scheme: map
Draw Behind: false
Enabled: true
Name: Map
Topic: /move_base/local_costmap/costmap
Unreliable: false
Use Timestamp: false
Value: true
- Alpha: 1
Axes Length: 1
Axes Radius: 0.100000001
Class: rviz/Pose
Color: 255; 25; 0
Enabled: true
Head Length: 0.300000012
Head Radius: 0.100000001
Name: Pose
Shaft Length: 1
Shaft Radius: 0.0500000007
Shape: Arrow
Topic: /move_base/current_goal
Unreliable: false
Value: true
- Alpha: 1
Arrow Length: 0.300000012
Axes Length: 0.300000012
Axes Radius: 0.00999999978
Class: rviz/PoseArray
Color: 255; 25; 0
Enabled: true
Head Length: 0.0700000003
Head Radius: 0.0299999993
Name: PoseArray
Shaft Length: 0.230000004
Shaft Radius: 0.00999999978
Shape: Arrow (Flat)
Topic: /move_base/TebLocalPlannerROS/teb_poses
Unreliable: false
Value: true
Enabled: true
Global Options:
Background Color: 48; 48; 48
Default Light: true
Fixed Frame: map
Frame Rate: 30
Name: root
Tools:
- Class: rviz/Interact
Hide Inactive Objects: true
- Class: rviz/MoveCamera
- Class: rviz/Select
- Class: rviz/FocusCamera
- Class: rviz/Measure
- Class: rviz/SetInitialPose
Topic: /initialpose
- Class: rviz/SetGoal
Topic: /move_base_simple/goal
- Class: rviz/PublishPoint
Single click: true
Topic: /clicked_point
Value: true
Views:
Current:
Class: rviz/Orbit
Distance: 4.41030741
Enable Stereo Rendering:
Stereo Eye Separation: 0.0599999987
Stereo Focal Distance: 1
Swap Stereo Eyes: false
Value: false
Focal Point:
X: -0.749346972
Y: -3.23726511
Z: 2.81896186
Focal Shape Fixed Size: true
Focal Shape Size: 0.0500000007
Invert Z Axis: false
Name: Current View
Near Clip Distance: 0.00999999978
Pitch: 1.13039768
Target Frame: <Fixed Frame>
Value: Orbit (rviz)
Yaw: 6.17858267
Saved: ~
Window Geometry:
Displays:
collapsed: false
Height: 1056
Hide Left Dock: false
Hide Right Dock: false
QMainWindow State: 000000ff00000000fd00000004000000000000016a00000396fc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006100fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000002800000396000000d700fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000010f00000396fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000002800000396000000ad00fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e100000197000000030000073f0000003efc0100000002fb0000000800540069006d006501000000000000073f0000030000fffffffb0000000800540069006d00650100000000000004500000000000000000000004ba0000039600000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000
Selection:
collapsed: false
Time:
collapsed: false
Tool Properties:
collapsed: false
Views:
collapsed: false
Width: 1855
X: 65
Y: 24
最后的脚本文件,用来让机器人根据规划好的路径移动到终点。
path_pursuit.py :
#!/usr/bin/env python
import rospy
from nav_msgs.msg import Path, Odometry
from ackermann_msgs.msg import AckermannDriveStamped
from geometry_msgs.msg import PoseStamped, PoseArray
import math
import numpy as np
from numpy import linalg as LA
from tf.transformations import euler_from_quaternion, quaternion_from_euler
import csv
import os
class following_path:
def __init__(self):
self.current_pose = rospy.Subscriber('/pf/pose/odom', Odometry, self.callback_read_current_position, queue_size=1)
self.Pose = []
self.path_pose = rospy.Subscriber('/move_base/TebLocalPlannerROS/global_plan', Path, self.callback_read_path, queue_size=1)
self.path_info = []
self.Goal = []
self.navigation_input = rospy.Publisher('/vesc/low_level/ackermann_cmd_mux/input/navigation', AckermannDriveStamped, queue_size=1)
self.reach_goal = False
self.MAX_VELOCITY = 0.5
self.MIN_VELOCITY = 0
self.max_angle = 1
self.steering_velocity = 1
self.jerk = 0.0
self.acceleration = 0.0
self.LOOKAHEAD_DISTANCE = 0.4
self.Low_Speed_Mode = False
def callback_read_path(self, data):
# Organize the pose message and only ask for (x,y) and orientation
# Read the Real time pose message and load them into path_info
self.path_info = []
path_array = data.poses
for path_pose in path_array:
path_x = path_pose.pose.position.x
path_y = path_pose.pose.position.y
path_qx = path_pose.pose.orientation.x
path_qy = path_pose.pose.orientation.y
path_qz = path_pose.pose.orientation.z
path_qw = path_pose.pose.orientation.w
path_quaternion = (path_qx, path_qy, path_qz, path_qw)
path_euler = euler_from_quaternion(path_quaternion)
path_yaw = path_euler[2]
self.path_info.append([float(path_x), float(path_y), float(path_yaw)])
self.Goal = list(self.path_info[-1]) # Set the last pose of the global path as goal location
def callback_read_current_position(self, data):
if self.reach_goal: # Stop updating the information.
self.path_info = []
self.Pose = []
ackermann_control = AckermannDriveStamped()
ackermann_control.drive.speed = 0.0
ackermann_control.drive.steering_angle = 0.0
ackermann_control.drive.steering_angle_velocity = 0.0
if not len(self.path_info) == 0:
# Read the path information to path_point list
path_points_x = [float(point[0]) for point in self.path_info]
path_points_y = [float(point[1]) for point in self.path_info]
path_points_w = [float(point[2]) for point in self.path_info]
# Read the current pose of the car from particle filter
x = data.pose.pose.position.x
y = data.pose.pose.position.y
qx = data.pose.pose.orientation.x
qy = data.pose.pose.orientation.y
qz = data.pose.pose.orientation.z
qw = data.pose.pose.orientation.w
# Convert the quaternion angle to eular angle
quaternion = (qx,qy,qz,qw)
euler = euler_from_quaternion(quaternion)
yaw = euler[2]
self.Pose = [float(x), float(y), float(yaw)]
if self.dist(self.Goal, self.Pose) < 1.0:
self.Low_Speed_Mode = True
if self.dist(self.Goal, self.Pose) < 0.3:
self.reach_goal = True
print('Goal Reached!')
else:
print('Low Speed Mode ON!')
else:
self.Low_Speed_Mode = False
# 2. Find the path point closest to the vehichle tat is >= 1 lookahead distance from vehicle's current location.
dist_array = np.zeros(len(path_points_x))
for i in range(len(path_points_x)):
dist_array[i] = self.dist((path_points_x[i], path_points_y[i]), (x,y))
goal = np.argmin(dist_array) # Assume the closet point as the goal point at first
goal_array = np.where((dist_array < (self.LOOKAHEAD_DISTANCE + 0.3)) & (dist_array > (self.LOOKAHEAD_DISTANCE - 0.3)))[0]
for id in goal_array:
v1 = [path_points_x[id] - x, path_points_y[id] - y]
v2 = [math.cos(yaw), math.sin(yaw)]
diff_angle = self.find_angle(v1,v2)
if abs(diff_angle) < np.pi/4: # Check if the one that is the cloest to the lookahead direction
goal = id
break
L = dist_array[goal]
# 3. Transform the goal point to vehicle coordinates.
glob_x = path_points_x[goal] - x
glob_y = path_points_y[goal] - y
goal_x_veh_coord = glob_x*np.cos(yaw) + glob_y*np.sin(yaw)
goal_y_veh_coord = glob_y*np.cos(yaw) - glob_x*np.sin(yaw)
# 4. Calculate the curvature = 1/r = 2x/l^2
# The curvature is transformed into steering wheel angle by the vehicle on board controller.
# Hint: You may need to flip to negative because for the VESC a right steering angle has a negative value.
diff_angle = path_points_w[goal] - yaw # Find the turning angle
r = L/(2*math.sin(diff_angle)) # Calculate the turning radius
angle = 2 * math.atan(0.4/r) # Find the wheel turning radius
angle = np.clip(angle, -self.max_angle, self.max_angle) # 0.4189 radians = 24 degrees because car can only turn 24 degrees max
angle = (0 if abs(angle) < 0.1 else angle)
VELOCITY = self.speed_control(angle, self.MIN_VELOCITY, self.MAX_VELOCITY)
# Write the Velocity and angle data into the ackermann message
ackermann_control = AckermannDriveStamped()
ackermann_control.drive.speed = VELOCITY
ackermann_control.drive.steering_angle = angle
ackermann_control.drive.steering_angle_velocity = self.steering_velocity
else:
ackermann_control = AckermannDriveStamped()
ackermann_control.drive.speed = 0.0
ackermann_control.drive.steering_angle = 0.0
ackermann_control.drive.steering_angle_velocity = 0.0
self.navigation_input.publish(ackermann_control)
# Computes the Euclidean distance between two 2D points p1 and p2
def dist(self, p1, p2):
try:
return np.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
except:
return 0.5
# Compute the angle between car direction and goal direction
def find_angle(self, v1, v2):
cos_ang = np.dot(v1, v2)
sin_ang = LA.norm(np.cross(v1, v2))
return np.arctan2(sin_ang, cos_ang)
# Control the speed of the car within the speed limit
def speed_control(self, angle, MIN_VELOCITY, MAX_VELOCITY):
# Assume the speed change linearly with respect to yaw angle
if self.Low_Speed_Mode:
Velocity = 0.5
else:
k = (MIN_VELOCITY - MAX_VELOCITY)/self.max_angle + 0.5
Velocity = k * abs(angle) + MAX_VELOCITY
return Velocity
if __name__ == "__main__":
rospy.init_node("pursuit_path")
following_path()
rospy.spin()
都完成后打开新终端,启动导航的launch文件:
(base) hjb@jl16k:~$ cd catkin_gazebo_ws/
(base) hjb@jl16k:~/catkin_gazebo_ws$ source devel/setup.bash
(base) hjb@jl16k:~/catkin_gazebo_ws$ roslaunch racecar_gazebo racecar_house_and_car_world_navigation.launch
再打开一个终端,启动rivz:
(base) hjb@jl16k:~$ cd catkin_gazebo_ws/
(base) hjb@jl16k:~/catkin_gazebo_ws$ source devel/setup.bash
(base) hjb@jl16k:~/catkin_gazebo_ws$ roslaunch racecar_gazebo racecar_rviz.launch
在rviz中点击下图箭头所指的2D Nav Goal,然后将箭头方向拉成像我一样的 ,当然其他方向也ok的。就会看到算法所得到的路径,绿色那条线。
再打开一个新终端:
(base) hjb@jl16k:~$ cd catkin_gazebo_ws/
(base) hjb@jl16k:~/catkin_gazebo_ws$ source devel/setup.bash
(base) hjb@jl16k:~/catkin_gazebo_ws$ rosrun racecar_gazebo path_pursuit.py
3.结束
一次完整的仿真。
版权归原作者 冷轩1112 所有, 如有侵权,请联系我们删除。