赞
踩
安装依赖:
- (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,当然这是有能力自己创建的前提下。
初次使用gazebo的可以参考一下,里面有一些功能建的说明与使用:ROS入门(四)——Gazebo的基本使用 - 古月居
首先进入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,然后看到我下图箭头所指,这个就是上面所建的模型,点击即可拉到界面。
当然值的注意的是,上面的例子是墙体模型,也有其他的模型,这需要慢慢去探索了。
一般来说,如果有现成的图形模型的话,那就大大节省了自己的时间,可以使用有的模型来做实验。
下载模型有两种反法,我用的是第二种方法,因为第一种方法可能是我的网不行下载的非常慢,虽然第二种方法我也下载了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这个模型进去。
此前建了一个模型,有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
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
这一步需要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
-
有了建图自然少不了导航,先说一下导航的前提条件,一般来说导航需要使用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
一次完整的仿真。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。