دليل البدء السريع بـ ROS (نظام تشغيل الروبوتات)#
يوضح هذا الدليل كيفية دمج Ultralytics YOLO مع ROS1 (rospy) أو ROS2 (rclpy) لإجراء اكتشاف الكائنات والتجزئة في الوقت الفعلي على صور RGB وصور العمق والسحب النقطية.
انتقل إلى إعداد YOLO مع ROS، ثم تعامل مع صور RGB، أو صور العمق، أو السحب النقطية.
ما ROS؟#
يُعد نظام تشغيل الروبوتات (ROS) إطار عمل مفتوح المصدر يُستخدم على نطاق واسع في أبحاث الروبوتات وصناعتها. يوفّر ROS مجموعة من المكتبات والأدوات لمساعدة المطورين على إنشاء تطبيقات الروبوتات. وصُمّم ROS للعمل مع منصات روبوتية متنوعة، ما يجعله أداة مرنة وقوية لمطوري الروبوتات. ولمقدمة موجزة، شاهد فيديو مقدمة إلى ROS من Open Robotics، ومدته ثلاث دقائق.
الميزات الرئيسية لـ ROS#
-
بنية معيارية: يتمتع ROS ببنية معيارية تتيح للمطورين إنشاء أنظمة معقدة من خلال الجمع بين مكونات أصغر قابلة لإعادة الاستخدام تُسمى العُقد. تؤدي كل عقدة عادةً وظيفة محددة، وتتواصل العُقد فيما بينها باستخدام الرسائل عبر المواضيع أو الخدمات.
-
برمجيات وسيطة للاتصالات: يوفّر ROS بنية تحتية موثوقة للاتصالات تدعم التواصل بين العمليات والحوسبة الموزعة. ويتحقق ذلك من خلال نموذج النشر والاشتراك لتدفقات البيانات (المواضيع)، ونموذج الطلب والرد لاستدعاءات الخدمات.
-
تجريد العتاد: يوفّر ROS طبقة تجريد فوق العتاد، ما يمكّن المطورين من كتابة شيفرة لا تعتمد على جهاز بعينه. ويسمح ذلك باستخدام الشيفرة نفسها مع إعدادات عتاد مختلفة، ما يسهّل التكامل والتجريب.
-
الأدوات والمرافق: يأتي ROS مزودًا بمجموعة غنية من الأدوات والمرافق للتصور وتصحيح الأخطاء والمحاكاة. فعلى سبيل المثال، يُستخدم RViz لتصور بيانات المستشعرات ومعلومات حالة الروبوت، بينما توفّر Gazebo بيئة محاكاة قوية لاختبار الخوارزميات وتصميمات الروبوتات.
-
منظومة متكاملة واسعة: منظومة ROS واسعة ومتنامية باستمرار، وتضم حزمًا عديدة متاحة لتطبيقات روبوتية متنوعة، منها الملاحة والمناولة والإدراك وغيرها. ويسهم المجتمع بفاعلية في تطوير هذه الحزم وصيانتها.
تطور إصدارات ROS
منذ تطويره في عام 2007، مرّ ROS عبر إصدارات متعددة، وانقسم إلى ROS 1 وROS 2. تستخدم الأمثلة الحالية أدناه ROS1 Noetic؛ وتعرض المحولات الموجزة في استخدام ROS2 واجهات rclpy المقابلة لإصدارات ROS2 الحالية.
ROS 1 مقابل ROS 2#
مع أن ROS 1 وفّر أساسًا متينًا لتطوير الروبوتات، يعالج ROS 2 أوجه قصوره من خلال توفير:
- الأداء في الوقت الفعلي: دعم محسّن للأنظمة الآنية والسلوك الحتمي.
- الأمان: ميزات أمان محسّنة لضمان التشغيل الآمن والموثوق في بيئات مختلفة.
- قابلية التوسع: دعم أفضل للأنظمة متعددة الروبوتات وعمليات النشر واسعة النطاق.
- الدعم عبر الأنظمة الأساسية: توافق موسّع مع أنظمة تشغيل مختلفة غير Linux، بما فيها Windows وmacOS.
- اتصالات مرنة: استخدام DDS لاتصالات أكثر مرونة وكفاءة بين العمليات.
رسائل ROS وموضوعاته#
في ROS، يُيسَّر الاتصال بين العُقد من خلال الرسائل والموضوعات. الرسالة بنية بيانات تحدد المعلومات المتبادلة بين العُقد، بينما الموضوع قناة ذات اسم تُرسل الرسائل عبرها وتُستقبل. يمكن للعُقد نشر الرسائل إلى موضوع أو الاشتراك لتلقي الرسائل من موضوع، ما يتيح لها التواصل بعضها مع بعض. يتيح نموذج النشر والاشتراك هذا الاتصال غير المتزامن وفصل العُقد بعضها عن بعض. ينشر كل مستشعر أو مشغّل في النظام الروبوتي بياناته عادةً إلى موضوع، ويمكن للعُقد الأخرى استهلاك هذه البيانات لمعالجتها أو للتحكم. ولأغراض هذا الدليل، سنركز على رسائل Image وDepth وPointCloud وموضوعات الكاميرا.
إعداد Ultralytics YOLO مع ROS#
اختُبرت أمثلة ROS1 باستخدام بيئة ROS هذه، وهي تفرع من مستودع ROSbot ROS. تنطبق معالجة YOLO وNumPy نفسها في ROS2؛ ويقتصر الاختلاف على دورة حياة العقدة وتحويل الرسائل.
تثبيت التبعيات#
إلى جانب بيئة ROS، ستحتاج إلى تثبيت التبعيات التالية:
-
حزمة ROS NumPy: يلزم استخدامها للتحويل السريع بين رسائل ROS Image ومصفوفات NumPy.
pip install ros_numpy -
حزمة Ultralytics:
pip install ultralytics
استخدام ROS2#
يستبدل ROS2 rospy بـ rclpy وتحويل الصور من ros_numpy بـ cv_bridge. تمثل العقدة التالية النظير الكامل في ROS2 لمسار كشف RGB أدناه؛ أنشئ النماذج مرة واحدة وأعد استخدامها عبر دوال الاستدعاء الراجعة.
import cv_bridge
import rclpy
from rclpy.node import Node
from rclpy.qos import qos_profile_sensor_data
from sensor_msgs.msg import Image
from ultralytics import YOLO
class UltralyticsNode(Node):
"""Run YOLO detection on ROS2 image messages."""
def __init__(self):
"""Initialize the ROS2 node, model, and image interfaces."""
super().__init__("ultralytics")
self.bridge = cv_bridge.CvBridge()
self.model = YOLO("yolo26m.pt")
self.publisher = self.create_publisher(Image, "/ultralytics/detection/image", 5)
self.create_subscription(Image, "/camera/color/image_raw", self.callback, qos_profile_sensor_data)
def callback(self, message):
"""Publish the annotated camera frame."""
image = self.bridge.imgmsg_to_cv2(message, desired_encoding="bgr8")
annotated = self.model(image)[0].plot(show=False)
self.publisher.publish(self.bridge.cv2_to_imgmsg(annotated, encoding="bgr8"))
def main(args=None):
"""Start the ROS2 node."""
rclpy.init(args=args)
node = UltralyticsNode()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
main()بالنسبة إلى صور العمق، أعد استخدام شفرة معالجة العمق أدناه، واستبدل فقط الحصول على الرسائل وتحويلها:
self.create_subscription(Image, "/camera/color/image_raw", self.rgb_callback, qos_profile_sensor_data)
self.create_subscription(Image, "/camera/depth/image_raw", self.depth_callback, qos_profile_sensor_data)
def rgb_callback(self, message):
self.rgb_image = self.bridge.imgmsg_to_cv2(message, desired_encoding="bgr8")
def depth_callback(self, message):
depth_image = self.bridge.imgmsg_to_cv2(message, desired_encoding="passthrough")
# طبّق قناع NumPy وحساب المسافة الواردين في مثال العمق أدناه.بالنسبة إلى السحب النقطية، يوفر ROS2 sensor_msgs_py.point_cloud2؛ حوّل السحابة المنظّمة مرة واحدة، ثم أعد استخدام تجزئة NumPy وربط الإحداثيات ثلاثية الأبعاد أدناه:
from sensor_msgs_py import point_cloud2
points = point_cloud2.read_points_numpy(message, field_names=("x", "y", "z", "rgb"))
points = points.reshape(message.height, message.width, 4)استخدام Ultralytics مع ROS sensor_msgs/Image#
يُستخدم نوع الرسالة sensor_msgs/Image عادةً في ROS لتمثيل بيانات الصور. ويتضمن حقولًا للترميز والارتفاع والعرض وبيانات البكسل، ما يجعله مناسبًا لنقل الصور التي تلتقطها الكاميرات أو المستشعرات الأخرى. تُستخدم رسائل Image على نطاق واسع في التطبيقات الروبوتية لمهام مثل الإدراك البصري وكشف الأجسام والملاحة.
الاستخدام خطوة بخطوة للصور#
يوضح مقتطف الشفرة التالي كيفية استخدام حزمة Ultralytics YOLO مع ROS. في هذا المثال، نشترك في موضوع كاميرا، ونعالج الصورة الواردة باستخدام YOLO، وننشر الأجسام المكتشفة إلى موضوعات جديدة من أجل الكشف والتجزئة.
أولًا، استورد المكتبات اللازمة وأنشئ نموذجين: أحدهما لـالتجزئة والآخر لـالكشف. هيّئ عقدة ROS (بالاسم ultralytics) لتمكين الاتصال مع ROS master. لضمان استقرار الاتصال، نضيف فترة انتظار قصيرة تمنح العقدة وقتًا كافيًا لإنشاء الاتصال قبل المتابعة.
import time
import rospy
from ultralytics import YOLO
detection_model = YOLO("yolo26m.pt")
segmentation_model = YOLO("yolo26m-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، وتحولها إلى مصفوفة NumPy باستخدام 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("yolo26m.pt")
segmentation_model = YOLO("yolo26m-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. يُعد RViz (تصور ROS) أداة قوية للتصور ثلاثي الأبعاد في ROS. فهو يتيح لك تصور حالة الروبوت وبيئته في الوقت الفعلي. باستخدام RViz، يمكنك عرض بيانات المستشعر (مثل
sensor_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 Image إلى مصفوفة 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("yolo26m.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، تُمثَّل صور العمق بنوع الرسالة sensor_msgs/Image، الذي يتضمن حقولًا للترميز والارتفاع والعرض وبيانات البكسل. وغالبًا ما يستخدم حقل ترميز صور العمق تنسيقًا مثل "16UC1"، ما يشير إلى عدد صحيح غير موقّع من 16 بت لكل بكسل، حيث تمثل كل قيمة المسافة إلى الجسم. وتُستخدم صور العمق عادةً مع صور RGB لتوفير رؤية أشمل للبيئة.
يمكن باستخدام YOLO استخراج المعلومات من صور RGB وصور العمق ودمجها. فعلى سبيل المثال، يستطيع YOLO كشف الأجسام في صورة RGB، ثم استخدام هذا الكشف لتحديد المناطق المقابلة في صورة العمق. ويسمح ذلك باستخراج معلومات دقيقة عن عمق الأجسام المكتشفة، ما يعزز قدرة الروبوت على فهم بيئته بأبعادها الثلاثة.
عند العمل مع صور العمق، من الضروري التأكد من محاذاة صور 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("yolo26m-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, retina_masks=True) # masks at the original image resolution
all_objects = []
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
all_objects.append(f"{name}: {avg_distance:.2f}m")
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("yolo26m-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, retina_masks=True) # masks at the original image resolution
all_objects = []
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
all_objects.append(f"{name}: {avg_distance:.2f}m")
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، التي توفر أدوات لإدارة إطارات الإحداثيات وتحويل البيانات بينها.
يمكن الحصول على السحب النقطية باستخدام مستشعرات مختلفة:
- LIDAR (الكشف وتحديد المدى بالضوء): يستخدم نبضات الليزر لقياس المسافات إلى الأجسام وإنشاء خرائط ثلاثية الأبعاد عالية الدقة.
- كاميرات العمق: تلتقط معلومات العمق لكل بكسل، ما يتيح إعادة بناء المشهد ثلاثي الأبعاد.
- كاميرات الاستريو: تستخدم كاميرتين أو أكثر للحصول على معلومات العمق من خلال التثليث.
- ماسحات الضوء المنظّم: تسقط نمطًا معروفًا على سطح وتقيس تشوهه لحساب العمق.
استخدام 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("yolo26m-seg.pt")أنشئ دالة pointcloud2_to_array تحوّل رسالة sensor_msgs/PointCloud2 إلى مصفوفتَي NumPy. تحتوي رسائل 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 (باستخدام الدالة 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, retina_masks=True) # الأقنعة بدقة الصورة الأصلية
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 sensor_msgs.msg import PointCloud2
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-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, retina_masks=True) # الأقنعة بدقة الصورة الأصلية
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])
الخلاصة#
بدمج Ultralytics YOLO في ROS، يستطيع الروبوت تنفيذ كشف الأجسام والتجزئة على صور RGB وصور العمق والسحب النقطية، محولًا تدفقات بيانات المستشعر الخام إلى إدراك قابل للتنفيذ. ومن هنا، استكشف وضع التنبؤ لمزيد من خيارات الاستدلال، أو اتبع خطوات مشروع رؤية حاسوبية لنقل تطبيقك الروبوتي من النموذج الأولي إلى مرحلة الإنتاج.
الأسئلة الشائعة#
نظام تشغيل الروبوت (ROS) إطار عمل مفتوح المصدر يُستخدم عادةً في مجال الروبوتات لمساعدة المطورين على إنشاء تطبيقات روبوتية متينة. وهو يوفر مجموعة من المكتبات والأدوات لبناء الأنظمة الروبوتية والتفاعل معها، ما يسهّل تطوير التطبيقات المعقدة. يدعم ROS الاتصال بين العُقد باستخدام الرسائل عبر الموضوعات أو الخدمات.
يتطلب دمج Ultralytics YOLO مع ROS إعداد بيئة ROS واستخدام YOLO لمعالجة بيانات المستشعرات. ابدأ بتثبيت التبعيات المطلوبة مثل
ros_numpyوUltralytics YOLO:pip install ros_numpy ultralyticsبعد ذلك، أنشئ عقدة ROS واشترك في موضوع صور لمعالجة البيانات الواردة من أجل كشف الأجسام. إليك مثالًا موجزًا:
import ros_numpy import rospy from sensor_msgs.msg import Image from ultralytics import YOLO detection_model = YOLO("yolo26m.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 الاتصال بين العُقد في شبكة ROS باستخدام نموذج النشر والاشتراك. الموضوع قناة ذات اسم تستخدمها العُقد لإرسال الرسائل واستقبالها بصورة غير متزامنة. وفي سياق Ultralytics YOLO، يمكنك جعل عقدة تشترك في موضوع صور، وتعالج الصور باستخدام YOLO لمهام مثل الكشف أو التجزئة، وتنشر النتائج إلى موضوعات جديدة.
على سبيل المثال، اشترك في موضوع كاميرا وعالج الصورة الواردة للكشف:
rospy.Subscriber("/camera/color/image_raw", Image, callback)توفر صور العمق في ROS، والممثلة بـ
sensor_msgs/Image، مسافات الأجسام عن الكاميرا، وهي معلومات أساسية لمهام مثل تجنب العوائق والرسم ثلاثي الأبعاد للخرائط وتحديد الموقع. وبـاستخدام معلومات العمق إلى جانب صور RGB، تستطيع الروبوتات فهم بيئتها ثلاثية الأبعاد على نحو أفضل.باستخدام YOLO، يمكنك استخراج أقنعة التجزئة من صور RGB وتطبيق هذه الأقنعة على صور العمق للحصول على معلومات دقيقة عن الأجسام ثلاثية الأبعاد، ما يحسن قدرة الروبوت على التنقل والتفاعل مع محيطه.
لتصور السحب النقطية ثلاثية الأبعاد في ROS باستخدام YOLO:
- حوّل رسائل
sensor_msgs/PointCloud2إلى مصفوفات NumPy. - استخدم YOLO لتجزئة صور RGB.
- طبّق قناع التجزئة على السحابة النقطية.
إليك مثالًا يستخدم Open3D للتصور:
import sys import numpy as np 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("yolo26m-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, retina_masks=True) # الأقنعة بدقة الصورة الأصلية 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])يوفر هذا النهج تصورًا ثلاثي الأبعاد للأجسام المجزأة، وهو مفيد لمهام مثل الملاحة والتلاعب في التطبيقات الروبوتية.
- حوّل رسائل