دليل البدء السريع مع 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. تمثل العقدة التالية النظير الكامل لتدفق اكتشاف RGB أدناه في ROS2؛ أنشئ النماذج مرة واحدة وأعد استخدامها عبر عمليات الاستدعاء.
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")
# Apply the NumPy mask and distance calculation from the depth example below.بالنسبة إلى السحب النقطية، يوفر 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)
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)
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)
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)
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) 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])يوفّر هذا النهج تصورًا ثلاثي الأبعاد للكائنات المُقسَّمة، وهو مفيد لمهام مثل التنقل والمناولة في تطبيقات الروبوتات.
- حوِّل رسائل