رؤية YOLO لعام 2026:

دليل البدء السريع لـ ROS (نظام تشغيل الروبوت)#

يوضح لك هذا الدليل كيفية دمج Ultralytics YOLO مع ROS1 (rospy) أو ROS2 (rclpy) لتشغيل object detection وsegmentation في الوقت الفعلي على صور RGB، وصور العمق، والسحب النقطية.

انتقل إلى setting up YOLO with ROS، ثم اعمل مع RGB images، أو depth images، أو point clouds.

ROS Introduction (captioned) from Open Robotics on Vimeo.

ما هو ROS؟#

يُعد Robot Operating System (ROS) إطار عمل مفتوح المصدر يُستخدم على نطاق واسع في أبحاث وصناعة الروبوتات. توفر ROS مجموعة من libraries and tools لمساعدة المطورين على إنشاء تطبيقات الروبوتات. تم تصميم ROS للعمل مع مختلف robotic platforms، مما يجعلها أداة مرنة وقوية لمتخصصي الروبوتات.

الميزات الرئيسية لـ ROS#

  1. البنية النمطية (Modular Architecture): تمتلك ROS بنية نمطية تتيح للمطورين بناء أنظمة معقدة من خلال الجمع بين مكونات اصغر قابلة لإعادة الاستخدام تسمى nodes. عادةً ما يؤدي كل عقدة وظيفة محددة، وتتواصل العقد مع بعضها البعض باستخدام الرسائل عبر topics أو services.

  2. برمجيات التواصل الوسيطة (Communication Middleware): يوفر ROS بنية تحتية قوية للاتصالات تدعم التواصل بين العمليات والحوسبة الموزعة. يتم تحقيق ذلك من خلال نموذج النشر والاشتراك لتدفقات البيانات (topics) ونموذج الطلب والرد لاستدعاءات الخدمات (services).

  3. تجريد الأجهزة (Hardware Abstraction): يوفر ROS طبقة تجريد فوق الأجهزة، مما يتيح للمطورين كتابة كود مستقل عن نوع الجهاز. يسمح هذا باستخدام نفس الكود مع إعدادات أجهزة مختلفة، مما يسهل عملية التكامل والتجربة.

  4. الأدوات والمرافق: يأتي ROS مع مجموعة غنية من الأدوات والمرافق للتصور والتصحيح والمحاكاة. على سبيل المثال، يُستخدم RViz لتصور بيانات المستشعرات ومعلومات حالة الروبوت، بينما يوفر Gazebo بيئة محاكاة قوية لاختبار الخوارزميات وتصميمات الروبوتات.

  5. النظام البيئي الموسع: نظام ROS البيئي واسع وينمو باستمرار، مع توفر العديد من الحزم لتطبيقات روبوتية مختلفة، بما في ذلك الملاحة، والتحكم، والإدراك، والمزيد. يساهم المجتمع بفعالية في تطوير وصيانة هذه الحزم.

تطور إصدارات ROS

منذ تطويرها في عام 2007، تطورت ROS عبر multiple versions، وانقسمت إلى ROS 1 و ROS 2. تستخدم الأمثلة الحالية أدناه ROS1 Noetic؛ بينما تظهر المحولات المدمجة في Using ROS2 واجهات rclpy المقابلة لإصدارات ROS2 الحالية.

ROS 1 مقابل ROS 2#

بينما قدم ROS 1 أساساً متيناً لتطوير الروبوتات، يعالج ROS 2 أوجه قصوره من خلال توفير:

  • الأداء في الوقت الفعلي (Real-time Performance): دعم محسن للأنظمة في الوقت الفعلي والسلوك الحتمي.
  • الأمان: ميزات أمان معززة للتشغيل الآمن والموثوق في بيئات مختلفة.
  • القابلية للتوسع: دعم أفضل لأنظمة الروبوتات المتعددة والنشر على نطاق واسع.
  • دعم عبر المنصات: توافق موسع مع أنظمة تشغيل مختلفة بخلاف Linux، بما في ذلك Windows و macOS.
  • اتصالات مرنة: استخدام DDS لاتصالات أكثر مرونة وكفاءة بين العمليات.

رسائل ومواضيع ROS#

في ROS، يتم تسهيل الاتصال بين العقد من خلال messages وtopics. الرسالة هي هيكل بيانات يحدد المعلومات المتبادلة بين العقد، بينما الموضوع (Topic) هو قناة مسماة تُرسل وتُستقبل من خلالها الرسائل. يمكن للعقد نشر الرسائل في موضوع أو الاشتراك في الرسائل من موضوع، مما يمكنها من التواصل مع بعضها البعض. يتيح نموذج النشر والاشتراك هذا إجراء الاتصال غير المتزامن وفصل العقد. عادةً ما ينشر كل مستشعر أو محرك في نظام الروبوت بيانات إلى موضوع، والتي يمكن بعد ذلك استهلاكها بواسطة العقد الأخرى لمعالجة البيانات أو التحكم فيها. لغرض هذا الدليل، سنركز على رسائل الصور والعمق والسحب النقطية ومواضيع الكاميرا.

إعداد Ultralytics YOLO مع ROS#

تم اختبار أمثلة ROS1 باستخدام this ROS environment، وهو تفرع من مستودع ROSbot ROS repository. ينطبق نفس معالجة YOLO و NumPy في ROS2؛ ويختلف فقط دورة حياة العقدة وتحويل الرسائل.

Husarion ROSbot 2 PRO autonomous robot platform

تثبيت التبعيات#

بصرف النظر عن بيئة ROS، ستحتاج إلى تثبيت التبعيات التالية:

  • ROS NumPy package: هذا مطلوب للتحويل السريع بين رسائل صور ROS ومصفوفات NumPy.

    pip install ros_numpy
  • حزمة Ultralytics:

    pip install ultralytics

استخدام ROS2#

يستبدل ROS2 rospy بـ rclpy وتحويل صور ros_numpy بـ cv_bridge. العقدة التالية هي المكمل الكامل لـ ROS2 لتدفق اكتشاف RGB أدناه؛ قم بتنفيذ النماذج مرة واحدة وإعادة استخدامها عبر ردود النداء (callbacks).

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 لتمثيل بيانات الصور. وهو يحتوي على حقول للترميز، والارتفاع، والعرض، وبيانات البكسل، مما يجعله مناسباً لنقل الصور الملتقطة بواسطة الكاميرات أو المستشعرات الأخرى. تُستخدم رسائل الصور على نطاق واسع في تطبيقات الروبوتات لمهام مثل الإدراك البصري، واكتشاف الكائنات، والتنقل.

Detection and Segmentation in ROS Gazebo

الاستخدام خطوة بخطوة للصور#

يوضح مقتطف الكود التالي كيفية استخدام حزمة Ultralytics YOLO مع ROS. في هذا المثال، نشترك في موضوع كاميرا، ونعالج الصورة الواردة باستخدام YOLO، وننشر الكائنات المكتشفة إلى مواضيع جديدة من أجل detection وsegmentation.

أولاً، قم استيراد المكتبات اللازمة وإنشاء نموذجين: أحدهما لـ segmentation والآخر لـ detection. قم بتهيئة عقدة 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: أحدهما لـ detection والآخر لـ segmentation. سيتم استخدام هذه المواضيع لنشر الصور المشروحة، مما يجعلها في متناول المعالجة الإضافية. يتم تسهيل الاتصال بين العقد باستخدام رسائل 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 ويستدعي دالة رد نداء (callback) لكل رسالة جديدة. تستقبل دالة رد النداء هذه رسائل من النوع 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()
تصحيح الأخطاء (Debugging)

قد يكون تصحيح أخطاء عقد ROS (نظام تشغيل الروبوت) أمراً صعباً نظراً للطبيعة الموزعة للنظام. يمكن للعديد من الأدوات المساعدة في هذه العملية:

  1. rostopic echo <TOPIC-NAME> : يتيح لك هذا الأمر عرض الرسائل المنشورة في موضوع معين، مما يساعدك على فحص تدفق البيانات.
  2. rostopic list: استخدم هذا الأمر لسرد كافة المواضيع المتاحة في نظام ROS، مما يمنحك نظرة عامة على تدفقات البيانات النشطة.
  3. rqt_graph: تعرض أداة التصور هذه رسم البياني للاتصال بين العقد، مما يوفر رؤى حول كيفية ترابط العقد وكيفية تفاعلها.
  4. للحصول على تصورات أكثر تعقيداً، مثل التمثيلات ثلاثية الأبعاد، يمكنك استخدام RViz. RViz (تصور ROS) هي أداة تصور ثلاثية الأبعاد قوية لـ ROS. وهي تتيح لك تصور حالة روبوتك وبيئته في الوقت الفعلي. باستخدام RViz، يمكنك عرض بيانات المستشعرات (على سبيل المثال، sensor_msgs/Image)، وحالات نموذج الروبوت، وأنواع مختلفة أخرى من المعلومات، مما يسهل تصحيح أخطاء نظام الروبوت الخاص بك وفهم سلوكه.

نشر الفئات المكتشفة باستخدام std_msgs/String#

تتضمن رسائل ROS القياسية أيضاً رسائل std_msgs/String. في العديد من التطبيقات، ليس من الضروري إعادة نشر الصورة المُشرحة بأكملها؛ وبلاً من ذلك، يلزم فقط الفئات الموجودة في مجال رؤية الروبوت. يوضح المثال التالي كيفية استخدام رسائل std_msgs/String لإعادة نشر الفئات المكتشفة على موضوع /ultralytics/detection/classes. هذه الرسائل تكون أكثر خفة وتوفر معلومات أساسية، مما يجعلها قيّمة لمختلف التطبيقات.

مثال على حالة الاستخدام#

ضع في اعتبارك روبوت مستودع مزود بكاميرا وdetection model. بدلاً من إرسال صور مشروحة كبيرة عبر الشبكة، يمكن للروبوت نشر قائمة بالفئات المكتشفة كرسائل 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("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 depth images، والتي توفر معلومات حول مسافة الكائنات من الكاميرا. تُعد صور العمق بالغة الأهمية لتطبيقات الروبوتات مثل تجنب العقبات، ورسم الخرائط ثلاثية الأبعاد، وتحديد المواقع.

صورة العمق هي صورة يمثل فيها كل بكسل المسافة من الكاميرا إلى كائن ما. على عكس صور RGB التي تلتقط اللون، تلتقط صور العمق معلومات مكانية، مما يمكن الروبوتات من إدراك البنية ثلاثية الأبعاد لبيئتها.

الحصول على صور العمق

يمكن الحصول على صور العمق باستخدام مستشعرات مختلفة:

  1. Stereo Cameras: استخدام كاميرتين لحساب العمق بناءً على التباين بين الصور.
  2. Time-of-Flight (ToF) Cameras: قياس الوقت الذي يستغرقه الضوء للعودة من الكائن.
  3. Structured Light Sensors: إسقاط نمط وقياس تشوهه على الأسطح.

استخدام YOLO مع صور العمق#

في 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("yolo26m-seg.pt")

classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)

بعد ذلك، قم بتعريف دالة رد نداء (callback) تعالج رسالة صورة العمق الواردة. تنتظر الدالة رسائل صورة العمق وصورة RGB، وتحولها إلى مصفوفات NumPy، وتطبق نموذج التجزئة (segmentation) على صورة RGB. ثم تستخرج قناع التجزئة لكل كائن مكتشف وتحسب متوسط مسافة الكائن عن الكاميرا باستخدام صورة العمق. تمتلك معظم المستشعرات مسافة قصوى، تُعرف بمسافة الاقتطاع (clip distance)، والتي تتجاوزها القيم الممثلة بـ 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#

Detection and Segmentation in ROS Gazebo

يُعد نوع الرسالة sensor_msgs/PointCloud2 هيكل بيانات مُستخدماً في ROS لتمثيل بيانات سحابة النقاط ثلاثية الأبعاد. يُعتبر نوع الرسالة هذا أساسياً لتطبيقات الروبوتات، حيث يتيح تنفيذ مهام مثل التخطيط ثلاثي الأبعاد، والتعرف على الكائنات، وتحديد الموقع.

السحاب النقطي هو مجموعة من نقاط البيانات المحددة داخل نظام إحداثيات ثلاثي الأبعاد. تمثل نقاط البيانات هذه السطح الخارجي لكائن أو مشهد، يتم التقاطه عبر تقنيات المسح ثلاثي الأبعاد. تحتوي كل نقطة في السحاب على إحداثيات X، وY، وZ، والتي تتوافق مع موقعها في الفضاء، وقد تتضمن أيضاً معلومات إضافية مثل اللون والشدة.

إطار مرجعي

عند العمل مع sensor_msgs/PointCloud2، من الضروري مراعاة إطار مرجعي للمستشعر الذي تم الحصول على بيانات السحاب النقطي منه. يتم التقاط السحاب النقطي في البداية في الإطار المرجعي للمستشعر. يمكنك تحديد هذا الإطار المرجعي عن طريق الاستماع إلى الموضوع /tf_static. ومع ذلك، اعتماداً على متطلبات تطبيقك المحددة، قد تحتاج إلى تحويل السحاب النقطي إلى إطار مرجعي آخر. يمكن تحقيق هذا التحويل باستخدام حزمة tf2_ros، والتي توفر أدوات لإدارة إطارات الإحداثيات وتحويل البيانات بينها.

الحصول على سحب النقاط

يمكن الحصول على سحب النقاط باستخدام مستشعرات مختلفة:

  1. LIDAR (الكشف عن الضوء والمدى): يستخدم نبضات الليزر لقياس المسافات إلى الكائنات وإنشاء خرائط ثلاثية الأبعاد عالية precision.
  2. كاميرات العمق: تلتقط معلومات العمق لكل بكسل، مما يسمح بإعادة بناء المشهد ثلاثي الأبعاد.
  3. كاميرات ستيريو: تستخدم كاميرتين أو أكثر للحصول على معلومات العمق من خلال التثليث.
  4. ماسحات الضوء المهيكل: تُسقط نمطاً معروفاً على سطح ما وتقيس التشويه لحساب العمق.

استخدام 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). تمتلك معظم المستشعرات مسافة قصوى، تُعرف بمسافة الاقتطاع (clip distance)، والتي تتجاوزها القيم الممثلة بـ 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])

Point Cloud Segmentation with Ultralytics

الخلاصة#

مع دمج Ultralytics YOLO في ROS، يمكن لروبوتك تشغيل object detection وsegmentation عبر صور RGB، وصور العمق، والسحب النقطية، وتحويل تدفقات المستشعرات الخام إلى إدراك قابل للتنفيذ. من هنا، استكشف Predict mode لمزيد من خيارات الاستدلال، أو اتبع steps of a computer vision project لنقل تطبيق الروبوتات الخاص بك من النموذج الأولي إلى الإنتاج.

الأسئلة الشائعة#

ما هو نظام تشغيل الروبوت (ROS)؟#

يُعد Robot Operating System (ROS) إطار عمل مفتوح المصدر يُستخدم بشكل شايع في الروبوتات لمساعدة المطورين على إنشاء تطبيقات روبوت قوية. وهو يوفر مجموعة من libraries and tools لبناء أنظمة الروبوتات والربط بينها، مما يتيح تطوير تطبيقات معقدة بسهولة أكبر. تدعم ROS الاتصال بين العقد باستخدام الرسائل عبر topics أو services.

كيف يمكنني دمج Ultralytics YOLO مع ROS لاكتشاف الكائنات في الوقت الفعلي؟#

يتضمن دمج Ultralytics YOLO مع ROS إعداد بيئة ROS واستخدام YOLO لمعالجة بيانات المستشعرات. ابدأ بتثبيت التبعيات المطلوبة مثل ros_numpy و Ultralytics YOLO:

pip install ros_numpy ultralytics

بعد ذلك، قم بإنشاء عقدة ROS والاشتراك في موضوع صورة لمعالجة البيانات الواردة لـ object detection. إليك مثال مبسط:

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 وكيف تُستخدم في Ultralytics YOLO؟#

تسهل مواضيع ROS الاتصال بين العقد في شبكة ROS باستخدام نموذج النشر والاشتراك. الموضوع هو قناة مسماة تستخدمها العقد لإرسال واستقبال الرسائل بشكل غير متزامن. في سياق Ultralytics YOLO، يمكنك جعل العقدة تشترك في موضوع صورة، ومعالجة الصور باستخدام YOLO لمهام مثل detection أو segmentation، ونشر النتائج إلى مواضيع جديدة.

على سبيل المثال، اشترك في موضوع كاميرا وعالج الصورة الواردة للاكتشاف:

rospy.Subscriber("/camera/color/image_raw", Image, callback)

لماذا نستخدم صور العمق مع Ultralytics YOLO في ROS؟#

توفر صور العمق في ROS، والتي يمثلها sensor_msgs/Image، مسافة الكائنات من الكاميرا، وهي بالغة الأهمية لمهام مثل تجنب العقبات، ورسم الخرائط ثلاثية الأبعاد، وتحديد المواقع. من خلال using depth information جنباً إلى جنب مع صور RGB، يمكن للروبوتات فهم بيئتها ثلاثية الأبعاد بشكل أفضل.

باستخدام YOLO، يمكنك استخراج segmentation masks من صور RGB وتطبيق هذه الأقنعة على صور العمق للحصول على معلومات دقيقة للكائنات ثلاثية الأبعاد، مما يحسن قدرة الروبوت على التنقل والتفاعل مع محيطه.

كيف يمكنني تصور سحب النقاط ثلاثية الأبعاد مع YOLO في ROS؟#

لتصور سحب النقاط ثلاثية الأبعاد في ROS مع YOLO:

  1. تحويل رسائل sensor_msgs/PointCloud2 إلى مصفوفات NumPy.
  2. استخدم YOLO لتجزئة صور RGB.
  3. طبق قناع التجزئة على سحابة النقاط.

إليك مثال باستخدام 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])

يوفر هذا النهج تصويراً ثلاثي الأبعاد للكائنات المجزأة، وهو مفيد لمهام مثل الملاحة والتعامل في robotics applications.

التعليقات