عربة شاحنة ROS. الجزء 6. المسافات مع ترميز عجلة ، خريطة غرفة ، ليدار

المشاركات في السلسلة:
8. نحن نتحكم من الهاتف ROS Control ، GPS العقدة
7. توطين الروبوت: gmapping ، AMCL ، النقاط المرجعية على خريطة الغرفة
6. المسافات مع ترميز عجلة ، خريطة الغرفة ، ليدار
5. نحن نعمل في rviz وشرفة المراقبة: xacro ، أجهزة استشعار جديدة.
4. قم بإنشاء محاكاة روبوت باستخدام محرري rviz و gazebo.
3. تسريع ، تغيير الكاميرا ، إصلاح المشية
2. البرمجيات
1. الحديد

آخر مرة ، قمنا بتصميم المشروع كوحدات منفصلة xacro ، وأضاف كاميرا فيديو افتراضية و imu (جيروسكوب).

في هذا المنشور ، سنعمل مع قياس المسافات من أجهزة التشفير البصرية المثبتة على أعمدة العجلات ، وتحميل خريطة الغرفة وركوبها على عربة روبوت حقيقية.

المسافات و tf


ما هو قياس المسافات و tf وكيف يتم تنفيذها عادة في ROS تم وصفه بالفعل جيدًا على المورد ، لذلك نشير إلى المقالات ذات الصلة في جزء النظرية ، على سبيل المثال ، هنا .
بعد أن بدأت من القاعدة النظرية ، سنعمل مع الممارسة.

لنبدأ بالعمل على روبوت عربة عبر الاتصال به عبر VNC.

انتقل إلى مجلد rosbots_driver وقم بإنشاء عقدة ملف. سيقوم هذا الملف بإنشاء قياس المسافات واستلامه من أجهزة التشفير البصرية ، والتي بدورها ترسله إلى arduino uno ثم إلى التوت pi.

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


الآن على الروبوت ، قم بتشغيل العقد الثانية - برنامج التشغيل و diff-tf:
المحطة الأولى:

 python diff_tf.py 

2:

 rosrun rosbots_driver part2_cmr.py 

في المحطة الثالثة ، سوف نتحقق من وجود موضوعات odom و tf جديدة:



دعنا نرى مع الأمر rostopic odom odom ما يتم نشره في الموضوع (وما إذا كان قد تم نشره على الإطلاق).
سيكون الإخراج تقريبا على النحو التالي:



الآن ، وبدون إغلاق العقد قيد التشغيل على الروبوت ، سنطلق جهاز الكمبيوتر للتحكم باستخدام البيئات الرسومية rviz و gazebo.

* صورة (جهاز VMWare الظاهري مع Ubuntu 16.04 + ROS Kinetic) ، والذي تم عرضه مسبقًا للتنزيل ، يحتوي على كل ما تحتاجه.

على كمبيوتر التحكم (المشار إليه فيما يلي باسم "الكمبيوتر") ، قم بتشغيل النموذج في rviz:

 roslaunch rosbots_description rviz.launch 

سيتم تحميل نموذج الروبوت الذي تم تحميله والذي تم تشغيله في المشاركات السابقة:



إضافة شاشتين إلى rviz بالنقر فوق إضافة. العرض مع قياس المسافات والعرض مع tf ، ضع علامة في المربعات لتصورهم.

في النافذة التي يظهر فيها نموذج الروبوت ، ستظهر رسومات مميزة:


* لجعلها أكثر وضوحًا ، يمكنك إيقاف تشغيل عرض Robotmodel.

نحن نتحكم في الروبوت من لوحة مفاتيح الكمبيوتر ونرى كيف يتغير تصوّر tf و odometry.

بدون إغلاق rviz في الطرف الثاني ، سنبدأ التحكم من لوحة المفاتيح:

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

عند التحكم في الروبوت ، سترى في النافذة مع التصور: سهم أحمر (تصوّر لموضوع odom) ، خطوط متجهة (موضوع tf).

إذا أظهر السهم الأحمر لموضوع odom اتجاه حركة الروبوت ، فإن خطوط المتجهات tf توضح كيف توجد العناصر الفردية للروبوت في الفضاء:

الفيديو


الآن ، للمضي قدما ، تحتاج إلى "ضبط" القياس.
للقيام بذلك ، أغلق محرر rviz وابدأ تشغيله مرة أخرى ، فقط دون تصور النموذج باستخدام الأمر:

 rosrun rviz rviz 

يعد ذلك ضروريًا بحيث تبقى فقط base_link و odom من متجهات موضوع tf:



في رفيز ، خلية واحدة هي 1 متر. لذلك ، في الواقع ، يجب أن يمر الروبوت أيضًا على مسافة متر واحد حتى تكون البيانات قابلة للمقارنة.

سنتجاوز مترًا واحدًا على الروبوت ، ونتحكم فيه من لوحة المفاتيح. في rviz ، يتوجب على الروبوت قيادة خلية واحدة - متر واحد.

إذا كان الروبوت يسافر لفترة أطول مما ينبغي في 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 ، حيث يكون كل بكسل إما:

  • مساحة بيضاء خالية ؛
  • أسود - الفضاء يشغلها عقبة؛
  • الرمادي - لم يتم استكشاف الفضاء بعد.

ملف .yam الثاني هو ملف بإعدادات الخريطة ، حيث تتم الإشارة إلى أبعادها ، وشغل البكسل بأنواع مختلفة (المذكورة أعلاه) ، ومعلمات أخرى.

قم بتشغيل العقدة على الكمبيوتر الذي سينشر البطاقة:

 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 ، أضف عرض الخريطة.

في rviz ، تبين أن الروبوت كبير بشكل غير متناسب ، ويقع خارج الخريطة:



لإصلاح ذلك ، تحتاج إلى تشغيل خريطة حيث سيكون حجم الخلية 1 متر. أعد تشغيل البطاقة باستخدام المعلمة 1 في النهاية:

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

يمكنك الآن ركوب الخريطة في rviz ، والتحكم في الروبوت من لوحة المفاتيح:

الفيديو


لذا ، ما تم تحقيقه :

  • تلقي بيانات قياس المسافات من أجهزة ترميز العجلة الضوئية للروبوت وإرسالها إلى موضوعات للعرض في rviz ؛
  • تكوين القياس الآلي للروبوت بحيث يتطابق مع المسافة المقطوعة مباشرة تقريبًا ؛
  • تحميل وعرض خريطة الغرفة.

ومع ذلك ، على الرغم من حقيقة أن الخريطة معروضة ويمكن للروبوت أن يركب عليها بقياس "ضبطها" ، في الواقع أن الروبوت أعمى. لا يرى أي عقبات وسوف يتعثر عليها. الثاني ناقص هو خريطة غرفة افتراضية محملة في rviz يسمح لك ركوب بنفسك في جميع الاتجاهات ، حتى في تلك التي تظهر بوضوح العقبات.

كيفية جعل الروبوت "رؤية" العقبات في الواقع وعمليا؟

مع بيئة افتراضية أبسط. كل شيء هنا يعتمد على محرر شرفات المراقبة. وفي الوظائف السابقة ذكر هذا.

الأمر أكثر تعقيدًا مع الواقع. نحتاج إلى عنصر (مستشعر) يشير إلى العقبات ويبلغ النظام بذلك.

خيار واحد هو وضع lidar على الروبوت.

ليدار RPlidar A1


سوف نستخدم حل الميزانية المعقولة ونضع اللد على الروبوت. ربما يكون هذا الحل أكثر تكلفة من استخدام نفس Kinect ، ولكن كما أثبتت الممارسة ، فهو أكثر فعالية من حيث السرعة والدقة وسهولة التركيب (أقل تعقيدًا). بالإضافة إلى ذلك ، من الأسهل بدء العمل مع lidar ، مثل لا يلزم التفكير في كيفية تشغيله وتوصيله بالمشروع (https://habr.com/en/company/tod/blog/210252/).

سنحتاج إلى حزمة ros للعمل مع lidar - wiki.ros.org/rplidar .
بمساعدة lidar ، سنبني خريطة للغرفة ، ونستخدمها أيضًا في الملاحة.

كيفية تثبيت rplidar في ROS لديه الكثير من المقالات ، على سبيل المثال هنا .

سوف نستخدم معرفة كبار السن من الرجال ذوي الشعر الرمادي وتثبيت حزم مع lidar في النظام على الروبوت :

 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 

نلقي نظرة. الذي يعرض ليدار في رفيز.

للقيام بذلك ، قم بتشغيله على الروبوت:

 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 

دعونا تشغيله.

على الروبوت:

1st محطة: roslaunch rplidar_ros rplidar.launch
2nd: rosrun rosbots_driver part2_cmr.py

على الكمبيوتر:

المحطة الأولى: roslaunch my_hector_mapping hector.launch
2nd: 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 

بناء الخريطة المثالية مع ليدار الميزانية هو خرافة. لذلك ، سوف نساعد lidar في Photoshop. سنقوم بإزالة القطع الأثرية السوداء من الخريطة ، حيث لا توجد أي عقبات ، ونحاذي الجدران مع الخطوط السوداء:



لا تنس حفظ الخريطة بتنسيق .pgm.

نكرر الآن على الكمبيوتر الأوامر التي كانت في بداية المنشور ، ولكن باستخدام خريطة جديدة:
المحطة الأولى: rosrun map_server maserver /home/pi/catkin_ws/src/rosbots_description/maps/map-1.pgm 0.05
2nd: roslaunch rosbots_description rviz.launch

النتيجة في rviz:



تم تحميل الخريطة الجديدة ، مثل طراز الروبوت ، ولكن الروبوت خارج الخريطة.

سنتحدث عن هذا لاحقًا ، ولكن الآن ، دعونا نلخص:

  • اتقان lidar RP-lidar A1
  • بناء خريطة غرفة باستخدام lidar ، تعديله وتحميله في محرر بصري rviz.

ملفات للتحميل: خريطة الغرفة .

Source: https://habr.com/ru/post/ar471028/


All Articles