ROS卡车手推车。 第6部分。带轮编码器的里程表,房间图,激光雷达

系列文章:
8.我们从电话控制ROS控制,GPS节点
7.机器人本地化:映射,AMCL,房间地图上的参考点
6.带轮编码器的里程表,房间地图,激光雷达
5.我们在rviz和凉亭中工作:xacro,新传感器。
4.使用rviz和gazebo编辑器创建机器人仿真。
3.加速,更换相机,固定步态
2.软件
1.铁

上一次,我们将项目设计为单独的xacro模块,并添加了虚拟摄像机和imu(陀螺仪)。

在本文中,我们将使用安装在轮轴上的光学编码器的里程表进行工作,加载房间地图并将其骑在真正的机器人手推车上。

里程表和tf


在资源上已经很好地描述了什么是odometry和tf以及它们通常如何在ROS中实现,因此我们可以参考理论中的相关文章,例如。
从理论基础开始,我们将与实践合作。

让我们从通过VNC连接到手推车机器人开始。

进入rosbots_driver文件夹并创建一个文件节点。 该文件将生成里程表,并从光学编码器接收里程表,然后再将其发送到arduino uno,再发送到树莓派。

cd /home/pi/rosbots_catkin_ws/src/rosbots_driver/scripts/rosbots_driver touch diff-tf.py 

我们将代码放入文件中:

diff_tf.py
 #!/usr/bin/env python """ diff_tf.py - follows the output of a wheel encoder and creates tf and odometry messages. some code borrowed from the arbotix diff_controller script A good reference: http://rossum.sourceforge.net/papers/DiffSteer/ Copyright (C) 2012 Jon Stephan. """ import rospy #import roslib #roslib.load_manifest('differential_drive') from math import sin, cos, pi from geometry_msgs.msg import Quaternion from geometry_msgs.msg import Twist from geometry_msgs.msg import Vector3 from nav_msgs.msg import Odometry import tf from tf.broadcaster import TransformBroadcaster from std_msgs.msg import Int16, Int32, Int64, UInt32 ############################################################################# class DiffTf: ############################################################################# ############################################################################# def __init__(self): ############################################################################# rospy.init_node("diff_tf") self.nodename = rospy.get_name() rospy.loginfo("-I- %s started" % self.nodename) #### parameters ####### #Wheel radius : 0.0325 # wheel circum = 2* 3.14 * 0.0325 = 0.2041 # One rotation encoder ticks : 8 ticks # For 1 meter: 8 * ( 1 / 0.2041) = 39 ticks self.rate = rospy.get_param('~rate',10.0) # the rate at which to publish the transform self.ticks_meter = float(rospy.get_param('ticks_meter', 190)) # The number of wheel encoder ticks per meter of travel self.base_width = float(rospy.get_param('~base_width', 0.11)) # The wheel base width in meters self.base_frame_id = rospy.get_param('~base_frame_id','base_link') # basefootprint /the name of the base frame of the robot self.odom_frame_id = rospy.get_param('~odom_frame_id', 'odom') # the name of the odometry reference frame self.encoder_min = rospy.get_param('encoder_min', -2147483648) self.encoder_max = rospy.get_param('encoder_max', 2147483648) self.encoder_low_wrap = rospy.get_param('wheel_low_wrap', (self.encoder_max - self.encoder_min) * 0.3 + self.encoder_min ) self.encoder_high_wrap = rospy.get_param('wheel_high_wrap', (self.encoder_max - self.encoder_min) * 0.7 + self.encoder_min ) self.t_delta = rospy.Duration(1.0/self.rate) self.t_next = rospy.Time.now() + self.t_delta # internal data self.enc_left = None # wheel encoder readings self.enc_right = None self.left = 0 # actual values coming back from robot self.right = 0 self.lmult = 0 self.rmult = 0 self.prev_lencoder = 0 self.prev_rencoder = 0 self.x = 0 # position in xy plane self.y = 0 self.th = 0 self.dx = 0 # speeds in x/rotation self.dr = 0 self.yaw = 0.01 self.pitch = 0.01 self.roll = 0.01 self.then = rospy.Time.now() self.quaternion_1 = Quaternion() # subscriptions rospy.Subscriber("wheel_ticks_left", UInt32, self.lwheelCallback) rospy.Subscriber("wheel_ticks_right", UInt32, self.rwheelCallback) #rospy.Subscriber("imu_data", Vector3, self.imu_value_update) self.odomPub = rospy.Publisher("odom", Odometry,queue_size=10) self.odomBroadcaster = TransformBroadcaster() ############################################################################# def spin(self): ############################################################################# r = rospy.Rate(self.rate) while not rospy.is_shutdown(): self.update() r.sleep() ############################################################################# def update(self): ############################################################################# now = rospy.Time.now() if now > self.t_next: elapsed = now - self.then self.then = now elapsed = elapsed.to_sec() # calculate odometry if self.enc_left == None: d_left = 0 d_right = 0 else: d_left = (self.left - self.enc_left) / self.ticks_meter d_right = (self.right - self.enc_right) / self.ticks_meter self.enc_left = self.left self.enc_right = self.right # distance traveled is the average of the two wheels d = ( d_left + d_right ) / 2 # this approximation works (in radians) for small angles th = ( d_right - d_left ) / self.base_width # calculate velocities self.dx = d / elapsed self.dr = th / elapsed if (d != 0): # calculate distance traveled in x and y x = cos( th ) * d y = -sin( th ) * d # calculate the final position of the robot self.x = self.x + ( cos( self.th ) * x - sin( self.th ) * y ) self.y = self.y + ( sin( self.th ) * x + cos( self.th ) * y ) if( th != 0): self.th = self.th + th # publish the odom information quaternion = Quaternion() quaternion.x = 0.0 quaternion.y = 0.0 quaternion.z = sin( self.th / 2 ) quaternion.w = cos( self.th / 2 ) ''' try: quaternion.z = self.quaternion_1[2] quaternion.w = self.quaternion_1[3] except: quaternion.z = sin( self.th / 2 ) quaternion.w = cos( self.th / 2 ) pass ''' self.odomBroadcaster.sendTransform( (self.x, self.y, 0), (quaternion.x, quaternion.y, quaternion.z, quaternion.w), rospy.Time.now(), self.base_frame_id, self.odom_frame_id ) odom = Odometry() odom.header.stamp = now odom.header.frame_id = self.odom_frame_id odom.pose.pose.position.x = self.x odom.pose.pose.position.y = self.y odom.pose.pose.position.z = 0 odom.pose.pose.orientation = quaternion odom.child_frame_id = self.base_frame_id odom.twist.twist.linear.x = self.dx odom.twist.twist.linear.y = 0 odom.twist.twist.angular.z = self.dr self.odomPub.publish(odom) def imu_value_update(self, imu_data): orient = Vector3() orient = imu_data self.yaw = orient.x self.pitch = orient.y self.roll = orient.z try: self.quaternion_1 = tf.transformations.quaternion_from_euler(self.yaw, self.pitch, self.roll) #print self.quaternion_1[0] #print self.quaternion_1[1] #print self.quaternion_1[2] #print self.quaternion_1[3] except: rospy.logwarn("Unable to get quaternion values") pass ############################################################################# def lwheelCallback(self, msg): ############################################################################# enc = msg.data if (enc < self.encoder_low_wrap and self.prev_lencoder > self.encoder_high_wrap): self.lmult = self.lmult + 1 if (enc > self.encoder_high_wrap and self.prev_lencoder < self.encoder_low_wrap): self.lmult = self.lmult - 1 self.left = 1.0 * (enc + self.lmult * (self.encoder_max - self.encoder_min)) self.prev_lencoder = enc ############################################################################# def rwheelCallback(self, msg): ############################################################################# enc = msg.data if(enc < self.encoder_low_wrap and self.prev_rencoder > self.encoder_high_wrap): self.rmult = self.rmult + 1 if(enc > self.encoder_high_wrap and self.prev_rencoder < self.encoder_low_wrap): self.rmult = self.rmult - 1 self.right = 1.0 * (enc + self.rmult * (self.encoder_max - self.encoder_min)) self.prev_rencoder = enc ############################################################################# ############################################################################# if __name__ == '__main__': """ main """ diffTf = DiffTf() diffTf.spin() 


保存文件并使其可执行:
CTRL+X
chmod +x diff-tf.py


现在在机器人上,运行第二个节点-driver和diff-tf:
第一航站楼:

 python diff_tf.py 

第二名:

 rosrun rosbots_driver part2_cmr.py 

在第三个终端中,我们将验证是否有新的odom和tf主题:



让我们用rostopic echo odom命令查看该主题中发布的内容(以及是否发布)。
输出将大致如下:



现在,无需关闭机器人上的运行节点,我们将使用图形环境rviz和gazebo启动控制计算机。

*以前提供下载的映像(带有Ubuntu 16.04 + ROS Kinetic的VMWare虚拟机)包含您需要的所有内容。

在控制计算机(以下称为“计算机”)上,在rviz中运行模型:

 roslaunch rosbots_description rviz.launch 

在之前的文章中使用过的机器人模型将加载:



通过单击添加将两个显示添加到rviz。 带有里程表的显示器和带有tf的显示器,选中复选框以使其可视化。

在描述机器人模型的窗口中,将显示特征图形:


*为了使其更清晰可见,您可以关闭Robotmodel显示。

我们通过计算机键盘控制机器人,并查看tf和里程表的可视化如何变化。

在不关闭第二个终端的rviz的情况下,我们将通过键盘启动控制:

 rosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/part2_cmr/cmd_vel 

当控制机器人时,带有可视化的窗口将显示:红色箭头(odom主题的可视化),矢量线(tf主题)。

如果odom主题的红色箭头显示了机器人的运动方向,则矢量线tf显示了机器人的各个元素在空间中的位置:

影片


现在,继续前进,您需要“调整”里程表。
为此,请关闭rviz编辑器,然后再次启动它,仅在不使用命令显示模型的情况下:

 rosrun rviz rviz 

这是必要的,以便tf主题的向量中仅保留base_link和odom:



在rviz中,一个像元等于1米。 因此,实际上,机器人也必须经过1米才能使数据具有可比性。

我们将在机器人上通过1米,并通过键盘对其进行控制。 在rviz中,机器人还必须驱动1米-一个单元。

如果机器人行进的距离比在rviz中移动的时间长,或者反之亦然(比实际距离短),那么您需要编辑先前创建的diff_tf.py文件,即以下代码块:

diff_tf.py
  #### parameters ####### #Wheel radius : 0.0325 # wheel circum = 2* 3.14 * 0.0325 = 0.2041 # One rotation encoder ticks : 8 ticks # For 1 meter: 8 * ( 1 / 0.2041) = 39 ticks self.rate = rospy.get_param('~rate',10.0) # the rate at which to publish the transform self.ticks_meter = float(rospy.get_param('ticks_meter', 190)) # The number of wheel encoder ticks per meter of travel self.base_width = float(rospy.get_param('~base_width', 0.11)) # The wheel base width in meters 


地图


要去某个地方,您需要一张地图。 为了我们的机器人的目的-我们需要一个房间地图。
让我们和她一起工作。

为了将地图上载到rviz,您需要在计算机上(而不是在机器人上)在项目(rosbots_description)中创建一个地图文件夹,并将其中的两个文件放入地图中:map.pgm和map.yaml。
*实际上,一个文件夹中可以有多个地图文件,但是您只能将一个地图文件上载到向导。

ROS中的地图由两个文件组成,其中一个是PGM图像,其中每个像素为:

  • 白色-空间是自由的;
  • 黑色-障碍物占据空间;
  • 灰色-尚未探索空间。

第二个.yaml文件是具有地图设置的文件,其中指示了地图的尺寸​​,不同类型的像素占用率(上面列出)以及其他参数。

在将发布卡的计算机上运行节点:

 rosrun map_server map_server /home/pi/catkin_ws/src/rosbots_description/maps/rail_lab.pgm 0.05 

在相邻终端中,在rviz中运行模型:

 roslaunch rosbots_description rviz.launch 

在rviz中,添加​​一个Map显示。

在rviz中,该机器人的体积过大,并且位于地图之外:



要解决此问题,您需要运行一个单元格大小为1米的地图。 最后用参数1重新启动卡:

 rosrun map_server map_server /home/pi/catkin_ws/src/rosbots_description/maps/rail_lab.pgm 1 

现在,您可以在rviz中骑行地图,通过键盘控制机器人:

影片


因此,实现了什么

  • 从机器人的光学轮编码器接收里程数据,并将其发送到主题以在rviz中显示;
  • 配置机器人里程计以匹配实际和实际行驶的距离;
  • 加载并显示房间地图。

但是,尽管显示了地图并且机器人可以通过“已调整”的里程表骑行,但实际上机器人是盲人的。 他看不到任何障碍,会跌跌撞撞。 第二个缺点是rviz中加载的虚拟房间地图使您可以在各个方向上自行骑行,即使在障碍物清晰可见的地方。

如何使机器人在现实中和虚拟环境中“看到”障碍?

使用虚拟环境更简单。 此处的所有内容均基于凉亭模拟器编辑器。 在以前的帖子中提到了这一点。

现实变得更加复杂。 我们需要一个指示障碍物的元素(传感器)并将其报告给系统。

一种选择是将激光雷达放在机器人上。

激光雷达RPlidar A1


我们将使用负担得起的预算解决方案,并将激光雷达安装在机器人上。 也许该解决方案将比使用相同的Kinect更为昂贵,但是,如实践所示,它在速度,准确性和易于安装方面(较少麻烦)更为有效。 此外,使用激光雷达更容易,因为 无需思考如何为其供电以及将其连接到项目(https://habr.com/en/company/tod/blog/210252/)。

我们将需要ros软件包才能与激光雷达-wiki.ros.org/rplidar一起使用
在激光雷达的帮助下,我们将绘制房间地图,并将其用于导航。

如何在ROS中安装rplidar有很多文章,例如在此处

我们将使用白发苍苍的老人的知识,并在机器人的系统中安装带有激光雷达的软件包:

 cd /home/pi/rosbots_catkin_ws/src git clone https://github.com/robopeak/rplidar_ros.git cd .. catkin_make 

计算机上,安装用于处理卡的软件包:

 cd /home/pi/rosbots_catkin_ws/src git clone https://github.com/tu-darmstadt-ros-pkg/hector_slam </code> cd .. catkin_make 

在机器人上运行包装,并检查激光雷达是否正常工作:

 sudo chmod a+rw /dev/ttyUSB0 roslaunch rplidar_ros rplidar.launch 

*第一个命令可访问连接激光雷达的USB端口。

如果一切顺利,那么它将向控制台输出行:

 [ INFO] [1570900184.874891236]: RPLIDAR running on ROS package rplidar_ros. SDK Version:1.9.0 RPLIDAR S/N: ---------------- [ INFO] [1570900187.397858270]: Firmware Ver: 1.24 [ INFO] [1570900187.398081809]: Hardware Rev: 5 [ INFO] [1570900187.401749476]: RPLidar health status : 0 [ INFO] [1570900188.014285166]: current scan mode: Express, max_distance: 12.0 m, Point number: 4.0K , angle_compensate: 1 

在这里,我们立即配置一个小激光雷达,因为 官方网站说它(激光雷达)可以更好地工作。

当扫描不是默认发布的4.0K点而是8.0K点时,我们需要实现输出。 此选项将略微提高扫描质量。

为此,我们将在rplidar软件包中设置另一个参数-扫描模式:

 cd /rosbots_catkin_ws/src/rplidar_ros/launch nano nano rplidar.launch 

然后

 <param name="angle_compensate" type="bool" value="true"/> 
添加行:

 <param name="scan_mode" type="string" value="Boost"/> 

第二行需要在此处修复:

 <param name="frame_id" type="string" value="laser"/> 

用base_link替换激光值。

*现在,如果使用roslaunch rplidar_ros rplidar.launch命令重新启动节点,则输出将不同:

 [ INFO] [1570900188.014285166]: current scan mode: Boost, max_distance: 12.0 m, Point number: 8.0K , angle_compensate: 1 

看一下 在rviz中显示激光雷达。

为此,请在机器人上运行:

 roslaunch rplidar_ros rplidar.launch 

在计算机上:

 roslaunch rosbots_description rviz.launch 

在rviz中,添加​​LaserScan显示并选择扫描主题。 进一步可以看到消息属于该主题:



在具有可视化机器人的窗口中,该机器人竟然是一个巨型机器人。 借助其大小,我们稍后会解决。 现在让我们建立一个房间地图。

为此,请创建一个带有节点的包:

 catkin_create_pkg my_hector_mapping rospy cd my_hector_mapping mkdir launch cd launch nano hector.launch 

hector.launch
 <?xml version="1.0"?> <launch> <node pkg="tf" type="static_transform_publisher" name="laser_link" args="0.0 0.0 0.0 0.0 0.0 0.0 /base_link /laser 50" /> <node pkg="hector_mapping" type="hector_mapping" name="hector_mapping" output="screen"> <!-- Frame names --> <param name="map_frame" value="map" /> <param name="odom_frame" value="base_link" /> <!-- Map size / start point --> <param name="map_resolution" value="0.050"/> <param name="map_size" value="1024"/> <param name="map_start_x" value="0.5"/> //  <param name="map_start_y" value="0.5" /> <param name="map_multi_res_levels" value="2" /> <!-- Map update parameters --> <param name="update_factor_free" value="0.4"/> <param name="update_factor_occupied" value="0.9" /> <param name="map_update_distance_thresh" value="0.4"/> <param name="map_update_angle_thresh" value="0.06" /> <param name="laser_z_min_value" value="-1.0" /> <param name="laser_z_max_value" value="1.0" /> <!-- Advertising config --> <param name="advertise_map_service" value="true"/> <param name="scan_subscriber_queue_size" value="5"/> <param name="scan_topic" value="scan"/> </node> </launch> 


 cd ~/rosbots_catkin_ws catkin_make 

让我们运行它。

在机器人上:

第一终端: roslaunch rplidar_ros rplidar.launch
第二名: rosrun rosbots_driver part2_cmr.py

在计算机上:

第一终端: roslaunch my_hector_mapping hector.launch
第二名: roslaunch rosbots_description rviz.launch
第三名: rosrun teleop_twist_keyboard teleop_twist_keyboard.py /cmd_vel:=/part2_cmr/cmd_vel

在显示中,您需要添加地图,然后在“固定框架”中选择base_link。 然后,您可以实时观察激光雷达如何“照亮”其周围的空间:



在当前步骤中,为了构建地图,您需要绕着房间骑行,以不同角度“停下来”,以便激光雷达在地图上对其进行标记。

因此,推荐教科书。 但是我们的建议是拿起机器人并随身携带,将其握在您面前。 因此,从某种意义上讲,您不必分神,并且可以在没有视觉接触的情况下观察机器人在下一房间的行驶位置,从而加快了地图的绘制速度。

此外,在旅途中绕轴旋转机器人时,激光雷达会在实际上没有障碍物的那些地方留下特征性的黑色假象:



构建地图后,使用以下命令将其保存:

 rosrun map_server map_saver -f map-1 

用预算激光雷达构建完美的地图是一个神话。 因此,我们将帮助Photoshop中的激光雷达。 我们将从地图上移除没有任何障碍物的黑色人工痕迹,然后将墙壁与黑色线条对齐:



不要忘记将地图保存为.pgm格式。

现在,我们在计算机上重复发布开头的命令,但带有新的地图:
第一终端: rosrun map_server maserver /home/pi/catkin_ws/src/rosbots_description/maps/map-1.pgm 0.05
第二名: roslaunch rosbots_description rviz.launch

结果为rviz:



加载了新地图,就像上面的机器人模型一样,但是机器人在地图外部。

我们稍后再讨论,但是现在,让我们总结一下:

  • 掌握激光雷达RP-lidar A1
  • 使用激光雷达构建房间地图,对其进行调整并将其加载到rviz可视编辑器中。

要下载的文件: 房间地图

Source: https://habr.com/ru/post/zh-CN471028/


All Articles