ROS探索总结(十三)——导航与定位框架

  • 内容
  • 评论
  • 相关

        导航与定位是机器人研究中的重要部分。
        一般机器人在陌生的环境下需要使用激光传感器(或者深度传感器转换成激光数据),先进行地图建模,然后在根据建立的地图进行导航、定位。在ROS中也有很多完善的包可以直接使用。
        在ROS中,进行导航需要使用到的三个包是:
      (1) move_base:根据参照的消息进行路径规划,使移动机器人到达指定的位置;
      (2) gmapping:根据激光数据(或者深度数据模拟的激光数据)建立地图;
      (3) amcl:根据已经有的地图进行定位。

       参考链接:http://www.ros.org/wiki/navigation/Tutorials/RobotSetup

                         http://www.ros.org/wiki/navigation/Tutorials

       整体导航包的格局如下图所示:


        其中白色框内的是ROS已经为我们准备好的必须使用的组件,灰色框内的是ROS中可选的组件,蓝色的是用户需要提供的机器人平台上的组件

1、sensor transforms

        其中涉及到使用tf进行传感器坐标的转换,因为我们使用的机器人的控制中心不一定是在传感器上,所以要把传感器的数据转换成在控制中心上的坐标信息。如下图所示,传感器获取的数据是在base_laser的坐标系统中的,但是我们控制的时候是以base_link为中心,所以要根据两者的位置关心进行坐标的变换。


        变换的过程不需要我们自己处理,只需要将base_laser和base_link两者之间的位置关系告诉tf,就可以自动转换了。具体的实现可以参见:http://blog.csdn.net/hcx25909/article/details/9255001


2、sensor sources

        这里是机器人导航传感器数据输入,一般只有两种:
      (1) 激光传感器数据
      (2) 点云数据
      具体实现见:
http://www.ros.org/wiki/navigation/Tutorials/RobotSetup/Sensors

3、odometry source

        机器人的导航需要输入里程计的数据(tf,nav_msgs/Odometry_message),具体实现见:
       
http://www.ros.org/wiki/navigation/Tutorials/RobotSetup/Odom

4、base controller

        在导航过程中,该部分负责将之前得出来的数据转封装成具体的线速度和转向角度信息(Twist),并且发布给硬件平台。

5、map_server

       在导航过程中,地图并不是必须的,此时相当于是在一个无限大的空地上进行导航,并没有任何障碍物。但是考虑到实际情况,在我们使用导航的过程中还是要一个地图的。
       具体实现见:
http://www.ros.org/wiki/slam_gmapping/Tutorials/MappingFromLoggedData
       在ROS的导航中,costmap_2d这个包主要负责根据传感器的信息建立和更新二维或三维的地图。ROS的地图(costmap)采用网格(grid)的形式,每个网格的值从0~255,分为三种状态:占用(有障碍物)、无用(空闲的)、未知。具体状态和值的对应关系如下:

       

        上图共分为五个部分:(下面的红色框图是机器人的轮廓,旁边的黑框是上图的映射位置)
      (1) Lethal(致命的):机器人的中心与该网格的中心重合,此时机器人必然与障碍物冲突。
      (2) Inscribed(内切):网格的外切圆与机器人的轮廓内切,此时机器人也必然与障碍物冲突。
      (3) Possibly circumscribed(外切):网格的外切圆与机器人的轮廓外切,此时机器人相当于靠在障碍物附近,所以不一定冲突。
      (4) Freespace(自由空间):没有障碍物的空间。
      (5) Unknown(未知):未知的空间。
       具体可见:
http://www.ros.org/wiki/costmap_2d


原创文章,转载请注明: 转载自古月居

本文链接地址: ROS探索总结(十三)——导航与定位框架

微信 OR 支付宝 扫描二维码
为本文作者 打个赏
pay_weixinpay_weixin

评论

21条评论
  1. Gravatar 头像

    啤酒花生 回复

    古月大大:
    我在用gmapping做gazebo仿真,打开rviz时候,Fixed Frame :map. 但是Map的反馈是No map received,rviz中是黑色一片和小车的一个白点,请问这个您有什么建议吗?因为我用的是您之前的教程代码,不知道问题处在哪儿。

    • 古月

      古月 回复

      @啤酒花生 可能是没启动gmapping节点,用tf工具看下中间是不是有哪个tf断掉了,或者终端中有没有错误提示

  2. Gravatar 头像

    胡翔 回复

    胡大神您好,有个问题请教下。map_server里的分辨率设置为0.05,利用导航包可以快速准确的到达目标位置;但是我将分辨率改为更大后,如0.25,问题出现了,更改后再调用导航包老是出现到达不了目标位置,在目标位置不停地打转。您能帮忙分析下这是什么造成的么?万分感谢!

    • 古月

      古月 回复

      @胡翔 应该是允许误差小于地图分辨率了,修改一下配置文件中的允许误差

      • Gravatar 头像

        胡翔 回复

        @古月 试过了,改大了允许误差还是有问题,老是在目标位置附近打转,到达不了目标位置

        • 古月

          古月 回复

          @胡翔 看下还有没有其他关联参数,都尝试修改一下。move_base能够实现基础功能,但是效果没办法尽善尽美,只能通过调试参数来优化。要进一步完善,需要自己做代码级的优化和修改。

  3. Gravatar 头像

    小弭 回复

    古月大大您好!

    昨天在您這看見可以透過訂閱/move_base/NavFnROS/plan 取得

    我擷取的資料型態是data.poses 但是他的資料是一整個矩陣資料

    可不可以只取得 其中的xy

    像是data.poses.pose.position.x 這樣子的感覺

    • Gravatar 头像

      小弭 回复

      @小弭 修正 是取得 透過導航包 規劃出來的 路徑資料

      • 古月

        古月 回复

        @小弭 可以的,通过data.poses[0].pose.position.x这样的形式是可以读取其中的x、y的

        • Gravatar 头像

          小弭 回复

          @古月 原來是這樣啊! 感謝古月大大 真是博學多聞呢,已經順利取出!

  4. Gravatar 头像

    张某人 回复

    古大,我在跟着slam_gmapping里生成地图的教程走的时候,遇到了两个问题。
    1:如果我将use_sim_time设为true,用rosbag记录的时候会提示我“/use_sim_time set to true and no clock published. Still waiting for valid time...”,然后就无法记录。你知道是什么原因吗?谷歌之后有人说将use_sim_time 设为false,我试过之后又出现了第2个问题。
    2:将use_sim_time设为false之后,bag文件可以生成也可以回放,但是在回放数据的时候slam_gmapping节点一直提示“TF_OLD_DATA ignoring data from past for frame laser at time 1.52415e+09 according to authority unknown_publisher”,最后也无法生成地图,我在tf中用的时间戳都是ros::Time::now(),你知道这是什么问题吗?
    这两个问题我都谷歌过了,但还是没发现问题在哪里,求古大指教

    • Gravatar 头像

      张某人 回复

      @张某人 好吧,问题解决了,还是自己的问题。首先在用rosbag记录的时候需要将use_sim_time设置为false才能记录,记录完成之后需要再将use_sim_time设置为true然后再运行slam_gmapping才能完成mapper的初始化,然后按照步骤就能生成地图了。而且我在记录bag的时候还犯了一个严重的错误,就是把雷达发布的话题写错了(把scan给记成了laser_scan,==|||),还是查看了一下bag文件才发现的。
      不过我还是有个问题想请教一下古大,这个use_sim_time到底是什么意思?网上解释说是设置为true是仿真时间,false是wall time,但我还是不太能理解两者的区别,也不清楚到底什么时候该设为true,什么时候该设为false,古大能帮忙解答我的这些疑惑吗?先行谢过了

    • 古月

      古月 回复

      @张某人 use_sim_time是配置是否使用仿真时间,后一个错误是说TF数据的时间戳相差太大

  5. Gravatar 头像

    ccc 回复

    古老师,您好,我将 ROS BY EXAMPLES书上的nav_square.py机器人走正方形路线的程序修改了。具体的修改思路是:机器人每行走1m停下来,逆时针旋转90度,采集待检测的点信息,然后再逆时针旋转360度至前进方向。一共执行上面的过程6次,最后将上面的整个步骤循环四次,这样走出的轨迹应该是一个边长为6m的正方形轨迹,但是实际上机器人运行到正方行第二条边上时,旋转方向已经不对了,请问这个是怎么回事?是由于只用里程计的效果吗?
    下面是我的程序
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import rospy
    from geometry_msgs.msg import Twist, Point, Quaternion
    import tf
    from rbx1_nav.transform_utils import quat_to_angle, normalize_angle
    from math import radians, copysign, sqrt, pow, pi

    class RunningCar():
    def __init__(self):
    # Give the node a name
    rospy.init_node('nav_square', anonymous=False)

    # Set rospy to execute a shutdown function when terminating the script
    rospy.on_shutdown(self.shutdown)

    # How fast will we check the odometry values?
    rate = 20

    # Set the equivalent ROS rate variable
    r = rospy.Rate(rate)

    # Set the parameters for the target square
    #goal_distance = rospy.get_param("~goal_distance", 2.0) # meters
    goal_distance1 = rospy.get_param("~goal_distance1", 1.0)
    goal_angle1 = rospy.get_param("~goal_angle1", radians(0))
    goal_angle2 = rospy.get_param("~goal_angle2", radians(90))
    goal_angle3 = rospy.get_param("~goal_angle3", radians(360))
    goal_angle = rospy.get_param("~goal_angle", radians(270))
    goal_angle4 = rospy.get_param("~goal_angle4", radians(0))
    goal_angle5 = rospy.get_param("~goal_angle5", radians(270))
    goal_angle6 = rospy.get_param("~goal_angle6", radians(180))
    linear_speed = rospy.get_param("~linear_speed", 0.2) # meters per second
    angular_speed = rospy.get_param("~angular_speed", 0.7) # radians per second
    angular_tolerance = rospy.get_param("~angular_tolerance", radians(2)) # degrees to radians

    # Publisher to control the robot's speed
    self.cmd_vel = rospy.Publisher('/cmd_vel', Twist)

    # The base frame is base_footprint for the TurtleBot but base_link for Pi Robot
    self.base_frame = rospy.get_param('~base_frame', '/base_link')

    # The odom frame is usually just /odom
    self.odom_frame = rospy.get_param('~odom_frame', '/odom')

    # Initialize the tf listener
    self.tf_listener = tf.TransformListener()

    # Give tf some time to fill its buffer
    rospy.sleep(2)

    # Set the odom frame
    self.odom_frame = '/odom'

    # Find out if the robot uses /base_link or /base_footprint
    try:
    self.tf_listener.waitForTransform(self.odom_frame, '/base_footprint', rospy.Time(), rospy.Duration(1.0))
    self.base_frame = '/base_footprint'
    except (tf.Exception, tf.ConnectivityException, tf.LookupException):
    try:
    self.tf_listener.waitForTransform(self.odom_frame, '/base_link', rospy.Time(), rospy.Duration(1.0))
    self.base_frame = '/base_link'
    except (tf.Exception, tf.ConnectivityException, tf.LookupException):
    rospy.loginfo("Cannot find transform between /odom and /base_link or /base_footprint")
    rospy.signal_shutdown("tf Exception")

    # Initialize the position variable as a Point type
    position = Point()
    # Set the movement command to a rotation
    move_cmd = Twist()
    move_cmd.angular.z = angular_speed

    # Track the last angle measured
    last_angle = rotation = 0

    # Track how far we have turned
    turn_angle = 0

    # Begin the rotation
    while abs(turn_angle + angular_tolerance) < abs(goal_angle1) and not rospy.is_shutdown():
    # Publish the Twist message and sleep 1 cycle
    self.cmd_vel.publish(move_cmd)

    r.sleep()

    # Get the current rotation
    (position, rotation) = self.get_odom()

    # Compute the amount of rotation since the last lopp
    delta_angle = normalize_angle(rotation - last_angle)

    turn_angle += delta_angle
    last_angle = rotation

    move_cmd = Twist()
    self.cmd_vel.publish(move_cmd)
    rospy.sleep(1.0)

    for n in range(4):
    # 第一段走2m的程序
    i = 1
    while i < 3:
    move_cmd = Twist()
    move_cmd.linear.x = linear_speed
    (position, rotation) = self.get_odom()
    x_start = position.x
    y_start = position.y
    distance1 = 0
    while distance1 < goal_distance1 and not rospy.is_shutdown():
    self.cmd_vel.publish(move_cmd)
    r.sleep()
    (position, rotation) = self.get_odom()
    distance1 = sqrt(pow((position.x - x_start), 2) +
    pow((position.y - y_start), 2))
    move_cmd = Twist()
    self.cmd_vel.publish(move_cmd)
    rospy.sleep(2.0)
    # 检测数据程序时间设为2s
    move_cmd.angular.z = angular_speed
    last_angle = rotation = 0
    turn_angle = 0
    while abs(turn_angle + angular_tolerance) < abs(goal_angle2) and not rospy.is_shutdown():
    self.cmd_vel.publish(move_cmd)
    r.sleep()
    (position, rotation) = self.get_odom()
    delta_angle = normalize_angle(rotation - last_angle)
    turn_angle += delta_angle
    last_angle = rotation
    move_cmd = Twist()
    self.cmd_vel.publish(move_cmd)
    rospy.sleep(2.0)
    # 方向复位时间
    move_cmd.angular.z = angular_speed
    last_angle = rotation = 0
    turn_angle = 0
    while abs(turn_angle + angular_tolerance) < abs(goal_angle3) and not rospy.is_shutdown():
    self.cmd_vel.publish(move_cmd)
    r.sleep()
    (position, rotation) = self.get_odom()
    delta_angle = normalize_angle(rotation - last_angle)
    turn_angle += delta_angle
    last_angle = rotation
    move_cmd = Twist()
    self.cmd_vel.publish(move_cmd)
    rospy.sleep(1.0)

    i += 1

    move_cmd = Twist()
    self.cmd_vel.publish(move_cmd)
    rospy.sleep(1.0)
    # 到第一个角点处转向的程序
    move_cmd.angular.z = angular_speed
    last_angle = rotation = 0
    turn_angle = 0
    while abs(turn_angle + angular_tolerance) < abs(goal_angle) and not rospy.is_shutdown():
    self.cmd_vel.publish(move_cmd)
    r.sleep()
    (position, rotation) = self.get_odom()
    delta_angle = normalize_angle(rotation - last_angle)
    turn_angle += delta_angle
    last_angle = rotation
    move_cmd = Twist()
    self.cmd_vel.publish(move_cmd)
    rospy.sleep(1.0)

    # Stop the robot when we are done
    self.cmd_vel.publish(Twist())

    def get_odom(self):
    # Get the current transform between the odom and base frames
    try:
    (trans, rot) = self.tf_listener.lookupTransform(self.odom_frame, self.base_frame, rospy.Time(0))
    except (tf.Exception, tf.ConnectivityException, tf.LookupException):
    rospy.loginfo("TF Exception")
    return

    return (Point(*trans), quat_to_angle(Quaternion(*rot)))

    def shutdown(self):
    # Always stop the robot when shutting down the node
    rospy.loginfo("Stopping the robot...")
    self.cmd_vel.publish(Twist())
    rospy.sleep(1)

    if __name__ == '__main__':
    try:
    RunningCar()
    except rospy.ROSInterruptException:
    rospy.loginfo("Navigation terminated.")

    • 古月

      古月 回复

      @ccc 里程计会产生累计误差,尤其是旋转,误差很大

      • Gravatar 头像

        ccc 回复

        @古月 之前我以为是我的程序逻辑问题,现在确定是rviz中只利用self.get_odom()来获得位置和方向不准确引起的。那请问胡老师我可以在rviz中添加IMU传感器,来获得机器人比较准确的位置和方向吗?我看了相关论文,一般利用里程计+IMU来获得机器人的位置和方向。谢谢老师!

        • 古月

          古月 回复

          @ccc 这是一种提高精度的方法,rviz主要是做显示,如果要添加IMU+odom,需要在gazebo仿真或使用真实机器人,只用rviz不行。

          • Gravatar 头像

            ccc 回复

            @古月 我上午在gazebo中的model没有找到IMU传感器,只是找到了laser和立体摄像头的模型,请问gazebo中能添加IMU传感器吗?

            • 古月

              古月 回复

              @ccc 可以的,参考这里:http://gazebosim.org/tutorials?tut=ros_gzplugins

  6. Gravatar 头像

    海漩涡 回复

    月哥, 我想修改cost map为一个长方形,机器人位于某”宽“(对应的是长)边中间,机器人的前面是一大片costmap面积,当机器人旋转时costmap随之旋转。 请问实现这样的功能有什么问题吗?

    我的目的是想减少costmap的面积,以提高性能。

发表评论

电子邮件地址不会被公开。 必填项已用*标注