دليل بدء التشغيل السريع لنظام تشغيل الروبوتات (ROS)
مقدمة ROS (مصحوبة بتعليق) من Open Robotics على Vimeo.
ما هو ROS؟
نظام تشغيل الروبوتات (ROS ) هو إطار عمل مفتوح المصدر يُستخدم على نطاق واسع في أبحاث الروبوتات والصناعة. يوفر ROS مجموعة من المكتبات والأدوات لمساعدة المطورين على إنشاء تطبيقات الروبوتات. تم تصميم ROS للعمل مع مختلف المنصات الروبوتية، مما يجعله أداة مرنة وقوية لمطوري الروبوتات.
الميزات الرئيسية لـ ROS
-
بنية معيارية: يحتوي ROS على بنية معيارية تسمح للمطورين ببناء أنظمة معقدة من خلال الجمع بين مكونات أصغر قابلة لإعادة الاستخدام تسمى العقد. تؤدي كل عقدة عادةً وظيفة محددة، وتتواصل العقد مع بعضها البعض باستخدام الرسائل عبر المواضيع أو الخدمات.
-
البرمجيات الوسيطة للاتصالات: يوفر ROS بنية تحتية قوية للاتصالات تدعم الاتصال بين العمليات والحوسبة الموزعة. ويتم تحقيق ذلك من خلال نموذج النشر والاشتراك لتدفقات البيانات (المواضيع) ونموذج الطلب والرد لاستدعاءات الخدمة.
-
تجريد الأجهزة: يوفر ROS طبقة من التجريد على الأجهزة، مما يمكّن المطورين من كتابة شيفرة برمجية لا تعتمد على الجهاز. يسمح ذلك باستخدام نفس الكود البرمجي مع إعدادات أجهزة مختلفة، مما يسهل التكامل والتجريب.
-
الأدوات والأدوات المساعدة: يأتي ROS مع مجموعة غنية من الأدوات والأدوات المساعدة للتصور والتصحيح والمحاكاة. على سبيل المثال، تُستخدم أداة RViz لتصور بيانات المستشعرات ومعلومات حالة الروبوت، بينما توفر أداة Gazebo بيئة محاكاة قوية لاختبار الخوارزميات وتصميمات الروبوت.
-
نظام بيئي واسع النطاق: نظام ROS البيئي واسع ومتنامي باستمرار، مع وجود العديد من الحزم المتاحة للتطبيقات الروبوتية المختلفة، بما في ذلك الملاحة والمعالجة والإدراك وغيرها. يساهم المجتمع بنشاط في تطوير هذه الحزم وصيانتها.
تطور إصدارات ROS
منذ تطويره في عام 2007، تطورت ROS من خلال إصدارات متعددة، كل منها يقدم ميزات وتحسينات جديدة لتلبية الاحتياجات المتزايدة لمجتمع الروبوتات. يمكن تصنيف تطوير ROS إلى سلسلتين رئيسيتين: ROS 1 و ROS 2. يركز هذا الدليل على إصدار الدعم طويل الأمد (LTS) من ROS 1، المعروف باسم ROS Noetic Ninjemys، وينبغي أن يعمل الكود أيضاً مع الإصدارات السابقة.
ROS 1 مقابل ROS 2
بينما وفرت ROS 1 أساساً متيناً لتطوير الروبوتات، فإن ROS 2 تعالج أوجه القصور فيها من خلال تقديم
- الأداء في الوقت الحقيقي: دعم محسّن لأنظمة الوقت الحقيقي والسلوك الحتمي.
- الأمان: ميزات أمان محسّنة للتشغيل الآمن والموثوق في بيئات مختلفة.
- قابلية التوسع: دعم أفضل للأنظمة متعددة الروبوتات وعمليات النشر واسعة النطاق.
- دعم متعدد المنصات: توافق موسع مع مختلف أنظمة التشغيل بخلاف لينكس، بما في ذلك ويندوز وماك أو إس.
- اتصال مرن: استخدام DDS لتواصل أكثر مرونة وفعالية بين العمليات.
رسائل ROS ومواضيعها
في ROS، يتم تسهيل الاتصال بين العقد من خلال الرسائل والمواضيع. الرسالة هي بنية بيانات تحدد المعلومات التي يتم تبادلها بين العقد، في حين أن الموضوع هو قناة مسماة يتم إرسال واستقبال الرسائل عبرها. يمكن للعقد نشر رسائل إلى موضوع أو الاشتراك في رسائل من موضوع، مما يتيح لها التواصل مع بعضها البعض. يسمح نموذج النشر والاشتراك هذا بالاتصال غير المتزامن والفصل بين العقد. يقوم كل مستشعر أو مشغل في النظام الروبوتي عادةً بنشر البيانات إلى موضوع، والتي يمكن أن تستهلكها العقد الأخرى للمعالجة أو التحكم. لغرض هذا الدليل، سنركز على رسائل الصور والعمق والنقاط السحابية وموضوعات الكاميرا.
إعداد Ultralytics YOLO مع ROS
تم اختبار هذا الدليل باستخدام بيئة ROS هذه، وهي شوكة من مستودع ROSbot ROS. تتضمن هذه البيئة الحزمة Ultralytics YOLO ، وحاوية Docker لسهولة الإعداد، وحزم ROS الشاملة، وعوالم Gazebo للاختبار السريع. وهي مصممة للعمل مع Husarion ROSbot 2 PRO. ستعمل أمثلة التعليمات البرمجية المقدمة في أي بيئة ROS Noetic/Melodic، بما في ذلك المحاكاة والعالم الحقيقي.
تثبيت التبعيات
بصرف النظر عن بيئة ROS، ستحتاج إلى تثبيت التبعيات التالية:
-
حزمة ROS Numpy: هذا مطلوب للتحويل السريع بين رسائل صور ROS ومصفوفات numpy.
-
Ultralytics الحزمة:
استخدم Ultralytics مع ROS sensor_msgs/Image
إن sensor_msgs/Image
نوع الرسالة تُستخدم عادةً في ROS لتمثيل بيانات الصور. وهي تحتوي على حقول للترميز والارتفاع والعرض وبيانات البكسل، مما يجعلها مناسبة لنقل الصور الملتقطة بواسطة الكاميرات أو أجهزة الاستشعار الأخرى. تُستخدم رسائل الصور على نطاق واسع في التطبيقات الروبوتية لمهام مثل الإدراك البصري, اكتشاف الأجساموالملاحة
استخدام الصورة خطوة بخطوة
يوضح مقتطف الكود التالي كيفية استخدام الحزمة Ultralytics YOLO مع ROS. في هذا المثال، نشترك في موضوع الكاميرا، ونعالج الصورة الواردة باستخدام YOLO ، وننشر الكائنات المكتشفة إلى مواضيع جديدة للكشف والتجزئة.
أولاً، استورد المكتبات اللازمة وأنشئ نموذجين: أحدهما ل التجزئة وواحد ل الكشف. تهيئة عقدة ROS (بالاسم ultralytics
) لتمكين الاتصال مع ROS الرئيسي. لضمان اتصال مستقر، نقوم بتضمين وقفة قصيرة لإعطاء العقدة وقتًا كافيًا لتأسيس الاتصال قبل المتابعة.
import time
import rospy
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
segmentation_model = YOLO("yolov8m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)
تهيئة موضوعي ROS: واحد لـ الكشف وواحد ل التجزئة. سيتم استخدام هذه المواضيع لنشر الصور المشروحة، مما يجعلها متاحة لمزيد من المعالجة. يتم تسهيل الاتصال بين العقد باستخدام sensor_msgs/Image
الرسائل.
from sensor_msgs.msg import Image
det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
seg_image_pub = rospy.Publisher("/ultralytics/segmentation/image", Image, queue_size=5)
أخيرًا، أنشئ مشتركًا يستمع إلى الرسائل على /camera/color/image_raw
موضوع ويستدعي دالة رد نداء لكل رسالة جديدة. تستقبل دالة رد النداء هذه رسائل من النوع sensor_msgs/Image
ويحولها إلى مصفوفة رقمية باستخدام ros_numpy
يعالج الصور باستخدام نماذج YOLO التي تم إنشاؤها مسبقًا، ويضع تعليقات توضيحية على الصور، ثم ينشرها مرة أخرى إلى الموضوعات المعنية: /ultralytics/detection/image
للكشف و /ultralytics/segmentation/image
للتجزئة.
import ros_numpy
def callback(data):
"""Callback function to process image and publish annotated images."""
array = ros_numpy.numpify(data)
if det_image_pub.get_num_connections():
det_result = detection_model(array)
det_annotated = det_result[0].plot(show=False)
det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))
if seg_image_pub.get_num_connections():
seg_result = segmentation_model(array)
seg_annotated = seg_result[0].plot(show=False)
seg_image_pub.publish(ros_numpy.msgify(Image, seg_annotated, encoding="rgb8"))
rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
rospy.spin()
الرمز الكامل
import time
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
segmentation_model = YOLO("yolov8m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)
det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
seg_image_pub = rospy.Publisher("/ultralytics/segmentation/image", Image, queue_size=5)
def callback(data):
"""Callback function to process image and publish annotated images."""
array = ros_numpy.numpify(data)
if det_image_pub.get_num_connections():
det_result = detection_model(array)
det_annotated = det_result[0].plot(show=False)
det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))
if seg_image_pub.get_num_connections():
seg_result = segmentation_model(array)
seg_annotated = seg_result[0].plot(show=False)
seg_image_pub.publish(ros_numpy.msgify(Image, seg_annotated, encoding="rgb8"))
rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
rospy.spin()
تصحيح الأخطاء
قد يكون تصحيح أخطاء عُقَد نظام تشغيل الروبوت (ROS) صعباً بسبب طبيعة النظام الموزعة. يمكن للعديد من الأدوات المساعدة في هذه العملية:
rostopic echo <TOPIC-NAME>
: يتيح لك هذا الأمر عرض الرسائل المنشورة على موضوع معين، مما يساعدك على فحص تدفق البيانات.rostopic list
: استخدم هذا الأمر لسرد جميع المواضيع المتاحة في نظام ROS، مما يمنحك نظرة عامة على تدفقات البيانات النشطة.rqt_graph
: تعرض أداة التصور هذه الرسم البياني للاتصالات بين العقد، مما يوفر نظرة ثاقبة حول كيفية ترابط العقد وكيفية تفاعلها.- للحصول على تصورات أكثر تعقيدًا، مثل التمثيلات ثلاثية الأبعاد، يمكنك استخدام ريفيز. RViz (تصور ROS) هو أداة تصور ثلاثية الأبعاد قوية لـ ROS. تسمح لك بتصور حالة الروبوت الخاص بك وبيئته في الوقت الحقيقي. باستخدام RViz، يمكنك عرض بيانات المستشعر (على سبيل المثال
sensors_msgs/Image
)، وحالات نموذج الروبوت، وأنواع أخرى مختلفة من المعلومات، مما يسهل تصحيح الأخطاء وفهم سلوك نظامك الآلي.
نشر الفئات المكتشفة مع std_msgs/String
تتضمن رسائل ROS القياسية أيضًا std_msgs/String
الرسائل. في العديد من التطبيقات، ليس من الضروري إعادة نشر الصورة المشروحة بالكامل؛ بدلاً من ذلك، لا يلزم سوى الفئات الموجودة في عرض الروبوت. يوضح المثال التالي كيفية استخدام std_msgs/String
الرسائل لإعادة نشر الأصناف المكتشفة على /ultralytics/detection/classes
الموضوع. هذه الرسائل أخف وزناً وتوفر معلومات أساسية، مما يجعلها ذات قيمة لمختلف التطبيقات.
مثال لحالة الاستخدام
ضع في اعتبارك روبوت مستودع مجهز بكاميرا وجسم نموذج الكشف. وبدلاً من إرسال صور كبيرة مشروحة عبر الشبكة، يمكن للروبوت نشر قائمة بالفئات المكتشفة على أنها std_msgs/String
الرسائل. على سبيل المثال، عندما يكتشف الروبوت أجسامًا مثل "صندوق" و"منصة نقالة" و"رافعة شوكية"، فإنه ينشر هذه الفئات إلى /ultralytics/detection/classes
الموضوع. يمكن بعد ذلك استخدام هذه المعلومات من قبل نظام مراقبة مركزي لتتبع المخزون في الوقت الحقيقي، أو تحسين تخطيط مسار الروبوت لتجنب العوائق، أو تشغيل إجراءات محددة مثل التقاط صندوق تم اكتشافه. يقلل هذا النهج من عرض النطاق الترددي المطلوب للاتصال ويركز على نقل البيانات الهامة.
استخدام السلسلة خطوة بخطوة
يوضح هذا المثال كيفية استخدام الحزمة Ultralytics YOLO مع ROS. في هذا المثال، نشترك في موضوع الكاميرا، ونعالج الصورة الواردة باستخدام YOLO ، وننشر الكائنات المكتشفة إلى موضوع جديد /ultralytics/detection/classes
باستخدام std_msgs/String
الرسائل. إن ros_numpy
تُستخدم الحزمة لتحويل رسالة صورة ROS إلى مصفوفة numpy للمعالجة باستخدام YOLO.
import time
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import String
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
rospy.init_node("ultralytics")
time.sleep(1)
classes_pub = rospy.Publisher("/ultralytics/detection/classes", String, queue_size=5)
def callback(data):
"""Callback function to process image and publish detected classes."""
array = ros_numpy.numpify(data)
if classes_pub.get_num_connections():
det_result = detection_model(array)
classes = det_result[0].boxes.cls.cpu().numpy().astype(int)
names = [det_result[0].names[i] for i in classes]
classes_pub.publish(String(data=str(names)))
rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
rospy.spin()
استخدام Ultralytics مع صور العمق ROS
بالإضافة إلى صور RGB، يدعم ROS صور العمق التي توفر معلومات حول مسافة الأجسام من الكاميرا. تُعد صور العمق ضرورية للتطبيقات الروبوتية مثل تجنب العوائق ورسم الخرائط ثلاثية الأبعاد وتحديد المواقع.
صورة العمق هي صورة يمثل كل بكسل فيها المسافة من الكاميرا إلى جسم ما. على عكس صور RGB التي تلتقط الألوان، تلتقط صور العمق المعلومات المكانية، مما يمكّن الروبوتات من إدراك البنية ثلاثية الأبعاد لبيئتها.
الحصول على صور العمق
يمكن الحصول على صور العمق باستخدام أجهزة استشعار مختلفة:
- كاميرات مجسمة: استخدم كاميرتين لحساب العمق بناءً على تباين الصور.
- كاميرات زمن التحليق (ToF): قياس الوقت الذي يستغرقه الضوء للعودة من جسم ما.
- مستشعرات الضوء المهيكل: إسقاط نمط وقياس تشوهه على الأسطح.
استخدام YOLO مع صور العمق
في ROS، يتم تمثيل صور العمق في ROS بواسطة sensor_msgs/Image
نوع الرسالة، والذي يتضمن حقولاً للترميز والارتفاع والعرض وبيانات البكسل. غالبًا ما يستخدم حقل الترميز لصور العمق تنسيقًا مثل "16UC1"، مما يشير إلى عدد صحيح غير موقع 16 بت لكل بكسل، حيث تمثل كل قيمة المسافة إلى الكائن. تُستخدم صور العمق عادةً مع صور RGB لتوفير رؤية أكثر شمولاً للبيئة.
باستخدام YOLO ، من الممكن استخراج المعلومات ودمجها من كل من صور RGB وصور العمق. على سبيل المثال، يمكن للروبوت YOLO اكتشاف الأجسام داخل صورة RGB، ويمكن استخدام هذا الاكتشاف لتحديد المناطق المقابلة في صورة العمق. يسمح ذلك باستخراج معلومات العمق الدقيقة للأجسام المكتشفة، مما يعزز قدرة الروبوت على فهم بيئته في ثلاثة أبعاد.
كاميرات RGB-D
عند العمل مع صور العمق، من الضروري التأكد من محاذاة صور RGB وصور العمق بشكل صحيح. توفر كاميرات RGB-D، مثل سلسلة Intel RealSense، صور RGB وصور عمق متزامنة، مما يسهل دمج المعلومات من كلا المصدرين. إذا كنت تستخدم كاميرات RGB وكاميرات عمق منفصلة، فمن الضروري معايرتها لضمان المحاذاة الدقيقة.
استخدام العمق خطوة بخطوة
في هذا المثال، نستخدم YOLO لتجزئة الصورة وتطبيق القناع المستخرج لتجزئة الكائن في صورة العمق. يتيح لنا ذلك تحديد مسافة كل بكسل من الجسم محل الاهتمام من المركز البؤري للكاميرا. من خلال الحصول على معلومات المسافة هذه، يمكننا حساب المسافة بين الكاميرا والجسم المحدد في المشهد. ابدأ باستيراد المكتبات اللازمة، وإنشاء عقدة ROS، وإنشاء نموذج تجزئة وموضوع ROS.
import time
import rospy
from std_msgs.msg import String
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)
بعد ذلك، حدد دالة رد نداء تعالج رسالة صورة العمق الواردة. تنتظر الدالة رسالتي صورة العمق وصورة RGB، وتحولهما إلى مصفوفات numpy، وتطبق نموذج التجزئة على صورة RGB. ثم تستخرج قناع التجزئة لكل جسم مكتشف وتحسب متوسط المسافة بين الجسم والكاميرا باستخدام صورة العمق. معظم أجهزة الاستشعار لديها مسافة قصوى، تُعرف باسم مسافة المقطع، والتي يتم تمثيل القيم التي تتجاوزها على أنها inf (np.inf
). قبل المعالجة، من المهم تصفية هذه القيم الفارغة وتعيين قيمة 0
. أخيرًا، ينشر الأجسام المكتشفة مع متوسط مسافاتها إلى /ultralytics/detection/distance
الموضوع.
import numpy as np
import ros_numpy
from sensor_msgs.msg import Image
def callback(data):
"""Callback function to process depth image and RGB image."""
image = rospy.wait_for_message("/camera/color/image_raw", Image)
image = ros_numpy.numpify(image)
depth = ros_numpy.numpify(data)
result = segmentation_model(image)
for index, cls in enumerate(result[0].boxes.cls):
class_index = int(cls.cpu().numpy())
name = result[0].names[class_index]
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
obj = depth[mask == 1]
obj = obj[~np.isnan(obj)]
avg_distance = np.mean(obj) if len(obj) else np.inf
classes_pub.publish(String(data=str(all_objects)))
rospy.Subscriber("/camera/depth/image_raw", Image, callback)
while True:
rospy.spin()
الرمز الكامل
import time
import numpy as np
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import String
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)
def callback(data):
"""Callback function to process depth image and RGB image."""
image = rospy.wait_for_message("/camera/color/image_raw", Image)
image = ros_numpy.numpify(image)
depth = ros_numpy.numpify(data)
result = segmentation_model(image)
for index, cls in enumerate(result[0].boxes.cls):
class_index = int(cls.cpu().numpy())
name = result[0].names[class_index]
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
obj = depth[mask == 1]
obj = obj[~np.isnan(obj)]
avg_distance = np.mean(obj) if len(obj) else np.inf
classes_pub.publish(String(data=str(all_objects)))
rospy.Subscriber("/camera/depth/image_raw", Image, callback)
while True:
rospy.spin()
استخدم Ultralytics مع ROS sensor_msgs/PointCloud2
إن sensor_msgs/PointCloud2
نوع الرسالة هي بنية بيانات مستخدمة في ROS لتمثيل بيانات سحابة النقاط ثلاثية الأبعاد. يعد هذا النوع من الرسائل جزءًا لا يتجزأ من التطبيقات الروبوتية، مما يتيح مهام مثل رسم الخرائط ثلاثية الأبعاد والتعرف على الأجسام وتحديد المواقع.
السحابة النقطية هي مجموعة من نقاط البيانات المحددة ضمن نظام إحداثيات ثلاثي الأبعاد. تمثل نقاط البيانات هذه السطح الخارجي لجسم ما أو مشهد ما، تم التقاطه عبر تقنيات المسح ثلاثي الأبعاد. تحتوي كل نقطة في السحابة على X
, Y
و Z
التي تتوافق مع موضعها في الفضاء، وقد تتضمن أيضًا معلومات إضافية مثل اللون والشدة.
الإطار المرجعي
عند العمل مع sensor_msgs/PointCloud2
، من الضروري مراعاة الإطار المرجعي للمستشعر الذي تم الحصول منه على بيانات السحابة النقطية. يتم التقاط السحابة النقطية في البداية في الإطار المرجعي للمستشعر. يمكنك تحديد هذا الإطار المرجعي من خلال الاستماع إلى /tf_static
الموضوع. ومع ذلك، بناءً على متطلبات تطبيقك المحددة، قد تحتاج إلى تحويل سحابة النقاط إلى إطار مرجعي آخر. يمكن تحقيق هذا التحويل باستخدام tf2_ros
التي توفر أدوات لإدارة أطر الإحداثيات وتحويل البيانات بينها.
الحصول على السحب النقطية
يمكن الحصول على السحب النقطية باستخدام أجهزة استشعار مختلفة:
- ليدار (الكشف عن الضوء وتحديد المدى): يستخدم نبضات الليزر لقياس المسافات إلى الأجسام وإنشاء خرائط ثلاثية الأبعاد عالية الدقة.
- كاميرات العمق: التقاط معلومات العمق لكل بكسل، مما يسمح بإعادة بناء المشهد ثلاثي الأبعاد.
- الكاميرات المجسمة: استخدام كاميرتين أو أكثر للحصول على معلومات العمق من خلال التثليث.
- الماسحات الضوئية المهيكلة: إسقاط نمط معروف على سطح ما وقياس التشوه لحساب العمق.
استخدام YOLO مع السحب النقطية
لدمج YOLO مع sensor_msgs/PointCloud2
نوع الرسائل، يمكننا استخدام طريقة مشابهة لتلك المستخدمة في خرائط العمق. من خلال الاستفادة من المعلومات الملونة المضمنة في سحابة النقاط، يمكننا استخراج صورة ثنائية الأبعاد، وإجراء تجزئة على هذه الصورة باستخدام YOLO ، ثم تطبيق القناع الناتج على النقاط ثلاثية الأبعاد لعزل الجسم ثلاثي الأبعاد محل الاهتمام.
للتعامل مع السحب النقطية، نوصي باستخدام Open3D (pip install open3d
)، وهي مكتبة Python سهلة الاستخدام. توفر Open3D أدوات قوية لإدارة هياكل البيانات السحابية النقطية وتصورها وتنفيذ العمليات المعقدة بسلاسة. يمكن لهذه المكتبة أن تبسط العملية بشكل كبير وتعزز قدرتنا على معالجة وتحليل السحب النقطية بالاقتران مع التجزئة القائمة على YOLO.
استخدام السحب النقطية خطوة بخطوة
استورد المكتبات اللازمة وقم بإنشاء النموذج YOLO للتجزئة.
import time
import rospy
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
إنشاء دالة pointcloud2_to_array
الذي يحوِّل sensor_msgs/PointCloud2
رسالة في مصفوفتي نومي. إن sensor_msgs/PointCloud2
تحتوي الرسائل على n
النقاط بناءً على width
و height
للصورة التي تم الحصول عليها. على سبيل المثال 480 x 640
ستحتوي الصورة على 307,200
نقاط. تتضمن كل نقطة ثلاثة إحداثيات مكانية (xyz
) واللون المقابل في RGB
التنسيق. يمكن اعتبارهما قناتين منفصلتين للمعلومات.
تقوم الدالة بإرجاع xyz
الإحداثيات و RGB
بتنسيق دقة الكاميرا الأصلية (width x height
). معظم أجهزة الاستشعار لها مسافة قصوى، تُعرف بمسافة المقطع، والتي يتم تمثيل القيم التي تتجاوزها على أنها inf (np.inf
). قبل المعالجة، من المهم تصفية هذه القيم الفارغة وتعيين قيمة 0
.
import numpy as np
import ros_numpy
def pointcloud2_to_array(pointcloud2: PointCloud2) -> tuple:
"""
Convert a ROS PointCloud2 message to a numpy array.
Args:
pointcloud2 (PointCloud2): the PointCloud2 message
Returns:
(tuple): tuple containing (xyz, rgb)
"""
pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
nan_rows = np.isnan(xyz).all(axis=2)
xyz[nan_rows] = [0, 0, 0]
rgb[nan_rows] = [0, 0, 0]
return xyz, rgb
بعد ذلك، اشترك في /camera/depth/points
موضوع لتلقي الرسالة السحابية النقطية وتحويل sensor_msgs/PointCloud2
إلى مصفوفات numpy التي تحتوي على إحداثيات XYZ وقيم RGB (باستخدام مصفوفات numpy) pointcloud2_to_array
الدالة). معالجة صورة RGB باستخدام النموذج YOLO لاستخراج الأجسام المجزأة. لكل كائن تم اكتشافه، استخرج قناع التجزئة وطبقه على كل من صورة RGB والإحداثيات XYZ لعزل الكائن في الفضاء ثلاثي الأبعاد.
تُعد معالجة القناع مباشرة لأنها تتكون من قيم ثنائية، مع 1
تشير إلى وجود الجسم و 0
تشير إلى الغياب. لتطبيق القناع، ما عليك سوى ضرب القنوات الأصلية في القناع. تعزل هذه العملية بفعالية الكائن محل الاهتمام داخل الصورة. أخيرًا، قم بإنشاء كائن سحابة نقطية Open3D وتصور الكائن المجزأ في الفضاء ثلاثي الأبعاد مع الألوان المرتبطة به.
import sys
import open3d as o3d
ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)
if not len(result[0].boxes.cls):
print("No objects detected")
sys.exit()
classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
mask_expanded = np.stack([mask, mask, mask], axis=2)
obj_rgb = rgb * mask_expanded
obj_xyz = xyz * mask_expanded
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
o3d.visualization.draw_geometries([pcd])
الرمز الكامل
import sys
import time
import numpy as np
import open3d as o3d
import ros_numpy
import rospy
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
def pointcloud2_to_array(pointcloud2: PointCloud2) -> tuple:
"""
Convert a ROS PointCloud2 message to a numpy array.
Args:
pointcloud2 (PointCloud2): the PointCloud2 message
Returns:
(tuple): tuple containing (xyz, rgb)
"""
pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
nan_rows = np.isnan(xyz).all(axis=2)
xyz[nan_rows] = [0, 0, 0]
rgb[nan_rows] = [0, 0, 0]
return xyz, rgb
ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)
if not len(result[0].boxes.cls):
print("No objects detected")
sys.exit()
classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
mask_expanded = np.stack([mask, mask, mask], axis=2)
obj_rgb = rgb * mask_expanded
obj_xyz = xyz * mask_expanded
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
o3d.visualization.draw_geometries([pcd])
الأسئلة الشائعة
ما هو نظام تشغيل الروبوت (ROS)؟
نظام تشغيل الروبوتات (ROS ) هو إطار عمل مفتوح المصدر شائع الاستخدام في مجال الروبوتات لمساعدة المطورين على إنشاء تطبيقات روبوتات قوية. وهو يوفر مجموعة من المكتبات والأدوات لبناء الأنظمة الروبوتية والتفاعل معها، مما يتيح تطوير تطبيقات معقدة بشكل أسهل. يدعم ROS التواصل بين العقد باستخدام الرسائل عبر المواضيع أو الخدمات.
كيف يمكنني دمج Ultralytics YOLO مع ROS للكشف عن الأجسام في الوقت الحقيقي؟
يتضمن دمج Ultralytics YOLO مع ROS إعداد بيئة ROS واستخدام YOLO لمعالجة بيانات المستشعر. ابدأ بتثبيت التبعيات المطلوبة مثل ros_numpy
و Ultralytics YOLO :
بعد ذلك، أنشئ عقدة ROS واشترك في موضوع صورة لمعالجة البيانات الواردة. إليك مثال بسيط:
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
rospy.init_node("ultralytics")
det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
def callback(data):
array = ros_numpy.numpify(data)
det_result = detection_model(array)
det_annotated = det_result[0].plot(show=False)
det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))
rospy.Subscriber("/camera/color/image_raw", Image, callback)
rospy.spin()
ما هي موضوعات ROS وكيف يتم استخدامها في Ultralytics YOLO ؟
تعمل مواضيع ROS على تسهيل الاتصال بين العُقد في شبكة ROS باستخدام نموذج النشر والاشتراك. الموضوع هو قناة مسماة تستخدمها العقد لإرسال واستقبال الرسائل بشكل غير متزامن. في سياق Ultralytics YOLO ، يمكنك جعل عقدة ما تشترك في موضوع صورة، ومعالجة الصور باستخدام YOLO لمهام مثل الكشف أو التجزئة، ونشر النتائج إلى مواضيع جديدة.
على سبيل المثال، الاشتراك في موضوع كاميرا ومعالجة الصورة الواردة للكشف عنها:
لماذا استخدام صور العمق مع Ultralytics YOLO في ROS؟
صور العمق في ROS، ممثلة بـ sensor_msgs/Image
توفير مسافة الأجسام من الكاميرا، وهو أمر ضروري لمهام مثل تجنب العوائق ورسم الخرائط ثلاثية الأبعاد وتحديد المواقع. بواسطة باستخدام معلومات العمق إلى جانب صور RGB، يمكن للروبوتات فهم بيئتها ثلاثية الأبعاد بشكل أفضل.
باستخدام YOLO ، يمكنك استخراج أقنعة التجزئة من صور RGB وتطبيق هذه الأقنعة على صور العمق للحصول على معلومات دقيقة ثلاثية الأبعاد عن الأجسام، مما يحسن قدرة الروبوت على التنقل والتفاعل مع محيطه.
كيف يمكنني تصور السحب النقطية ثلاثية الأبعاد باستخدام YOLO في ROS؟
لتصور السحب النقطية ثلاثية الأبعاد في ROS باستخدام YOLO:
- التحويل
sensor_msgs/PointCloud2
رسائل إلى مصفوفات numpy. - استخدم YOLO لتجزئة صور RGB.
- تطبيق قناع التجزئة على السحابة النقطية.
إليك مثال باستخدام Open3D للتصور البصري:
import sys
import open3d as o3d
import ros_numpy
import rospy
from sensor_msgs.msg import PointCloud2
from ultralytics import YOLO
rospy.init_node("ultralytics")
segmentation_model = YOLO("yolov8m-seg.pt")
def pointcloud2_to_array(pointcloud2):
pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
return xyz, rgb
ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)
if not len(result[0].boxes.cls):
print("No objects detected")
sys.exit()
classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
mask_expanded = np.stack([mask, mask, mask], axis=2)
obj_rgb = rgb * mask_expanded
obj_xyz = xyz * mask_expanded
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((-1, 3)))
pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((-1, 3)) / 255)
o3d.visualization.draw_geometries([pcd])
يوفر هذا النهج تصوراً ثلاثي الأبعاد للأجسام المجزأة، وهو مفيد لمهام مثل الملاحة والمعالجة.