Ultralytics YOLO27:
Get Started

Guia de início rápido do ROS (Robot Operating System)#

Este guia mostra como integrar o Ultralytics YOLO com o ROS1 (rospy) ou o ROS2 (rclpy) para executar deteção de objetos e segmentação em tempo real em imagens RGB, imagens de profundidade e nuvens de pontos.

Vai diretamente para configurar o YOLO com ROS e, em seguida, trabalha com imagens RGB, imagens de profundidade ou nuvens de pontos.

O que é o ROS?#

O Sistema Operacional de Robôs (ROS) é um framework de código aberto amplamente utilizado na investigação e na indústria da robótica. O ROS fornece um conjunto de bibliotecas e ferramentas para ajudar os programadores a criar aplicações robóticas. O ROS foi concebido para funcionar com várias plataformas robóticas, o que o torna uma ferramenta flexível e poderosa para profissionais de robótica. Para uma breve introdução, vê o vídeo de três minutos Introdução ao ROS da Open Robotics.

Principais funcionalidades do ROS#

  1. Arquitetura modular: o ROS tem uma arquitetura modular que permite aos programadores criar sistemas complexos através da combinação de componentes menores e reutilizáveis, chamados nós. Cada nó executa normalmente uma função específica, e os nós comunicam entre si através de mensagens em tópicos ou serviços.

  2. Middleware de comunicação: o ROS oferece uma infraestrutura de comunicação robusta que permite a comunicação entre processos e a computação distribuída. Isto é conseguido através de um modelo de publicação-assinatura para fluxos de dados (tópicos) e de um modelo de pedido-resposta para chamadas de serviço.

  3. Abstração de hardware: o ROS fornece uma camada de abstração sobre o hardware, permitindo aos programadores escrever código independente do dispositivo. Assim, o mesmo código pode ser usado em diferentes configurações de hardware, facilitando a integração e a experimentação.

  4. Ferramentas e utilitários: o ROS inclui um conjunto abrangente de ferramentas e utilitários para visualização, depuração e simulação. Por exemplo, o RViz é usado para visualizar dados de sensores e informações sobre o estado do robô, enquanto o Gazebo oferece um poderoso ambiente de simulação para testar algoritmos e projetos de robôs.

  5. Ecossistema abrangente: o ecossistema ROS é vasto e está em constante crescimento, com inúmeros pacotes disponíveis para diferentes aplicações robóticas, incluindo navegação, manipulação, perceção e muito mais. A comunidade contribui ativamente para o desenvolvimento e a manutenção destes pacotes.

Evolução das versões do ROS

Desde o seu desenvolvimento em 2007, o ROS evoluiu através de várias versões, dividindo-se em ROS 1 e ROS 2. Os exemplos abaixo usam o ROS1 Noetic; os adaptadores compactos em Usar ROS2 mostram as interfaces rclpy correspondentes às versões atuais do ROS2.

ROS 1 vs. ROS 2#

Embora o ROS 1 tenha proporcionado uma base sólida para o desenvolvimento de robótica, o ROS 2 corrige suas limitações oferecendo:

  • Desempenho em tempo real: melhor suporte a sistemas de tempo real e comportamento determinístico.
  • Segurança: recursos de segurança aprimorados para uma operação segura e confiável em diversos ambientes.
  • Escalabilidade: melhor suporte a sistemas com vários robôs e implantações em larga escala.
  • Suporte multiplataforma: compatibilidade ampliada com diversos sistemas operacionais além do Linux, incluindo Windows e macOS.
  • Comunicação flexível: uso de DDS para uma comunicação entre processos mais flexível e eficiente.

Mensagens e tópicos do ROS#

No ROS, a comunicação entre nós ocorre por meio de mensagens e tópicos. Uma mensagem é uma estrutura de dados que define as informações trocadas entre nós, enquanto um tópico é um canal nomeado pelo qual as mensagens são enviadas e recebidas. Os nós podem publicar mensagens em um tópico ou assinar um tópico para receber mensagens, o que permite que se comuniquem entre si. Esse modelo de publicação e assinatura permite a comunicação assíncrona e o desacoplamento entre nós. Normalmente, cada sensor ou atuador em um sistema robótico publica dados em um tópico, que podem então ser consumidos por outros nós para processamento ou controle. Para este guia, vamos nos concentrar nas mensagens Image, Depth e PointCloud e nos tópicos de câmera.

Configurar o Ultralytics YOLO com o ROS#

Os exemplos de ROS1 foram testados usando este ambiente ROS, um fork do repositório ROSbot ROS. O mesmo processamento com YOLO e NumPy se aplica ao ROS2; somente o ciclo de vida do nó e a conversão de mensagens são diferentes.

Husarion ROSbot 2 PRO autonomous robot platform

Instalação de dependências#

Além do ambiente ROS, você precisará instalar as seguintes dependências:

  • Pacote ROS NumPy: necessário para converter rapidamente mensagens ROS Image em arrays NumPy.

    pip install ros_numpy
  • Pacote Ultralytics:

    pip install ultralytics

Usando o ROS2#

O ROS2 substitui rospy por rclpy e a conversão de imagens de ros_numpy por cv_bridge. O nó a seguir é o equivalente completo em ROS2 do fluxo de detecção RGB abaixo; instancie os modelos uma única vez e reutilize-os em todos os 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()

Para imagens de profundidade, reutilize o código de processamento de profundidade abaixo e substitua somente a obtenção e a conversão das mensagens:

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")
    # Aplique a máscara NumPy e o cálculo de distância do exemplo de profundidade abaixo.

Para nuvens de pontos, o ROS2 fornece sensor_msgs_py.point_cloud2; converta a nuvem organizada uma única vez e reutilize a segmentação NumPy e o mapeamento 3D abaixo:

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)

Usar o Ultralytics com o ROS sensor_msgs/Image#

O tipo de mensagem sensor_msgs/Image é comumente usado no ROS para representar dados de imagem. Ele contém campos para codificação, altura, largura e dados de pixel, o que o torna adequado para transmitir imagens capturadas por câmeras ou outros sensores. As mensagens Image são amplamente usadas em aplicações robóticas para tarefas como percepção visual, detecção de objetos e navegação.

Detection and Segmentation in ROS Gazebo

Uso passo a passo de imagens#

O trecho de código a seguir demonstra como usar o pacote Ultralytics YOLO com o ROS. Neste exemplo, assinamos um tópico de câmera, processamos a imagem recebida usando YOLO e publicamos os objetos detectados em novos tópicos de detecção e segmentação.

Primeiro, importe as bibliotecas necessárias e instancie dois modelos: um para segmentação e outro para detecção. Inicialize um nó ROS (com o nome ultralytics) para permitir a comunicação com o mestre do ROS. Para garantir uma conexão estável, incluímos uma breve pausa, dando ao nó tempo suficiente para estabelecer a conexão antes de prosseguir.

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)

Inicialize dois tópicos ROS: um para detecção e outro para segmentação. Esses tópicos serão usados para publicar as imagens anotadas, tornando-as acessíveis para processamento posterior. A comunicação entre nós é facilitada pelo uso de mensagens 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)

Por fim, crie um assinante que escute as mensagens no tópico /camera/color/image_raw e chame uma função de callback para cada nova mensagem. Essa função de callback recebe mensagens do tipo sensor_msgs/Image, converte-as em um array NumPy usando ros_numpy, processa as imagens com os modelos YOLO instanciados anteriormente, anota as imagens e, em seguida, publica-as de volta nos tópicos correspondentes: /ultralytics/detection/image para detecção e /ultralytics/segmentation/image para segmentação.

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()
Código completo
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()
Depuração

Depurar nós do ROS (Sistema Operacional de Robôs) pode ser um desafio devido à natureza distribuída do sistema. Várias ferramentas podem ajudar nesse processo:

  1. rostopic echo <TOPIC-NAME> : este comando permite visualizar as mensagens publicadas em um tópico específico, ajudando você a inspecionar o fluxo de dados.
  2. rostopic list: use este comando para listar todos os tópicos disponíveis no sistema ROS e ter uma visão geral dos fluxos de dados ativos.
  3. rqt_graph: esta ferramenta de visualização exibe o grafo de comunicação entre nós, mostrando como os nós estão interconectados e como interagem.
  4. Para visualizações mais complexas, como representações 3D, você pode usar o RViz. O RViz (visualização do ROS) é uma poderosa ferramenta de visualização 3D para o ROS. Ela permite visualizar em tempo real o estado do robô e o ambiente ao redor. Com o RViz, você pode ver dados de sensores (por exemplo, sensor_msgs/Image), estados do modelo do robô e diversos outros tipos de informações, facilitando a depuração e a compreensão do comportamento do sistema robótico.

Publicar classes detectadas com std_msgs/String#

As mensagens padrão do ROS também incluem mensagens std_msgs/String. Em muitas aplicações, não é necessário republicar a imagem anotada inteira; basta publicar as classes presentes no campo de visão do robô. O exemplo a seguir demonstra como usar mensagens std_msgs/String para republicar as classes detectadas no tópico /ultralytics/detection/classes. Essas mensagens são mais leves e fornecem informações essenciais, o que as torna valiosas para diversas aplicações.

Exemplo de caso de uso#

Considere um robô de armazém equipado com uma câmera e um modelo de detecção de objetos. Em vez de enviar imagens anotadas grandes pela rede, o robô pode publicar uma lista de classes detectadas como mensagens std_msgs/String. Por exemplo, quando o robô detecta objetos como "caixa", "palete" e "empilhadeira", ele publica essas classes no tópico /ultralytics/detection/classes. Um sistema central de monitoramento pode usar essas informações para acompanhar o inventário em tempo real, otimizar o planejamento da rota do robô para evitar obstáculos ou acionar ações específicas, como pegar uma caixa detectada. Essa abordagem reduz a largura de banda necessária para a comunicação e prioriza a transmissão de dados essenciais.

Uso passo a passo de String#

Este exemplo demonstra como usar o pacote Ultralytics YOLO com o ROS. Neste exemplo, assinamos um tópico de câmera, processamos a imagem recebida usando YOLO e publicamos os objetos detectados no novo tópico /ultralytics/detection/classes usando mensagens std_msgs/String. O pacote ros_numpy é usado para converter a mensagem ROS Image em um array NumPy para processamento com 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()

Usar o Ultralytics com imagens de profundidade do ROS#

Além de imagens RGB, o ROS oferece suporte a imagens de profundidade, que fornecem informações sobre a distância dos objetos em relação à câmera. Imagens de profundidade são essenciais para aplicações robóticas como desvio de obstáculos, mapeamento 3D e localização.

Uma imagem de profundidade é uma imagem em que cada pixel representa a distância entre a câmera e um objeto. Ao contrário das imagens RGB, que capturam cores, as imagens de profundidade capturam informações espaciais, permitindo que os robôs percebam a estrutura 3D do ambiente.

Obtenção de imagens de profundidade

Imagens de profundidade podem ser obtidas usando diversos sensores:

  1. Câmeras estéreo: use duas câmeras para calcular a profundidade com base na disparidade entre as imagens.
  2. Câmeras de tempo de voo (ToF): medem o tempo que a luz leva para retornar de um objeto.
  3. Sensores de luz estruturada: projetam um padrão e medem sua deformação nas superfícies.

Usar YOLO com imagens de profundidade#

No ROS, as imagens de profundidade são representadas pelo tipo de mensagem sensor_msgs/Image, que inclui campos para codificação, altura, largura e dados de pixel. O campo de codificação das imagens de profundidade costuma usar um formato como "16UC1", que indica um inteiro sem sinal de 16 bits por pixel, em que cada valor representa a distância até o objeto. As imagens de profundidade são comumente usadas em conjunto com imagens RGB para oferecer uma visão mais completa do ambiente.

Com YOLO, é possível extrair e combinar informações de imagens RGB e de profundidade. Por exemplo, YOLO pode detectar objetos em uma imagem RGB, e essa detecção pode ser usada para localizar as regiões correspondentes na imagem de profundidade. Isso permite extrair informações precisas de profundidade dos objetos detectados, melhorando a capacidade do robô de compreender o ambiente em três dimensões.

Câmeras RGB-D

Ao trabalhar com imagens de profundidade, é essencial garantir que as imagens RGB e de profundidade estejam alinhadas corretamente. Câmeras RGB-D, como a série Intel RealSense, fornecem imagens RGB e de profundidade sincronizadas, facilitando a combinação de informações das duas fontes. Se você usar câmeras RGB e de profundidade separadas, é fundamental calibrá-las para garantir o alinhamento preciso.

Uso passo a passo de imagens de profundidade#

Neste exemplo, usamos YOLO para segmentar uma imagem e aplicamos a máscara extraída para segmentar o objeto na imagem de profundidade. Isso permite determinar a distância de cada pixel do objeto de interesse em relação ao centro focal da câmera. Com essas informações de distância, podemos calcular a distância entre a câmera e o objeto específico na cena. Comece importando as bibliotecas necessárias, criando um nó ROS e instanciando um modelo de segmentação e um tópico 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)

Em seguida, defina uma função de callback que processe a mensagem recebida da imagem de profundidade. A função aguarda as mensagens da imagem de profundidade e da imagem RGB, converte-as em arrays NumPy e aplica o modelo de segmentação à imagem RGB. Depois, extrai a máscara de segmentação de cada objeto detectado e calcula a distância média do objeto em relação à câmera usando a imagem de profundidade. A maioria dos sensores tem uma distância máxima, conhecida como distância de corte, além da qual os valores são representados como inf (np.inf). Antes do processamento, é importante filtrar esses valores nulos e atribuir a eles o valor 0. Por fim, publica os objetos detectados e suas distâncias médias no tópico /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()
Código completo
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()

Usar o Ultralytics com o ROS sensor_msgs/PointCloud2#

Detection and Segmentation in ROS Gazebo

O tipo de mensagem sensor_msgs/PointCloud2 é uma estrutura de dados usada no ROS para representar dados de nuvens de pontos 3D. Esse tipo de mensagem é essencial para aplicações robóticas e permite tarefas como mapeamento 3D, reconhecimento de objetos e localização.

Uma nuvem de pontos é um conjunto de pontos de dados definidos em um sistema de coordenadas tridimensional. Esses pontos representam a superfície externa de um objeto ou de uma cena, capturada por tecnologias de digitalização 3D. Cada ponto da nuvem tem coordenadas X, Y e Z, que correspondem à sua posição no espaço, e também pode incluir informações adicionais, como cor e intensidade.

Referencial

Ao trabalhar com sensor_msgs/PointCloud2, é essencial considerar o referencial do sensor que adquiriu os dados da nuvem de pontos. Inicialmente, a nuvem de pontos é capturada no referencial do sensor. Você pode determinar esse referencial escutando o tópico /tf_static. No entanto, dependendo dos requisitos específicos da sua aplicação, talvez seja necessário converter a nuvem de pontos para outro referencial. Essa transformação pode ser feita usando o pacote tf2_ros, que fornece ferramentas para gerenciar referenciais de coordenadas e transformar dados entre eles.

Obtenção de nuvens de pontos

As nuvens de pontos podem ser obtidas usando diversos sensores:

  1. LIDAR (detecção e medição de distância por luz): usa pulsos de laser para medir distâncias até objetos e criar mapas 3D de alta precisão.
  2. Câmeras de profundidade: capturam informações de profundidade para cada pixel, permitindo a reconstrução 3D da cena.
  3. Câmeras estéreo: usam duas ou mais câmeras para obter informações de profundidade por triangulação.
  4. Scanners de luz estruturada: projetam um padrão conhecido sobre uma superfície e medem sua deformação para calcular a profundidade.

Usar YOLO com nuvens de pontos#

Para integrar YOLO a mensagens do tipo sensor_msgs/PointCloud2, podemos usar um método semelhante ao empregado para mapas de profundidade. Aproveitando as informações de cor incorporadas à nuvem de pontos, podemos extrair uma imagem 2D, segmentá-la usando YOLO e, em seguida, aplicar a máscara resultante aos pontos tridimensionais para isolar o objeto 3D de interesse.

Para lidar com nuvens de pontos, recomendamos o uso do Open3D (pip install open3d), uma biblioteca Python fácil de usar. O Open3D oferece ferramentas robustas para gerenciar estruturas de dados de nuvens de pontos, visualizá-las e executar operações complexas sem dificuldades. Essa biblioteca pode simplificar significativamente o processo e ampliar nossa capacidade de manipular e analisar nuvens de pontos em conjunto com a segmentação baseada em YOLO.

Uso passo a passo de nuvens de pontos#

Importe as bibliotecas necessárias e instancie o modelo YOLO para segmentação.

import time

import rospy

from ultralytics import YOLO

rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolo26m-seg.pt")

Crie uma função pointcloud2_to_array que transforme uma mensagem sensor_msgs/PointCloud2 em dois arrays NumPy. As mensagens sensor_msgs/PointCloud2 contêm pontos n com base em width e height da imagem adquirida. Por exemplo, uma imagem 480 x 640 terá pontos 307,200. Cada ponto inclui três coordenadas espaciais (xyz) e a cor correspondente no formato RGB. Esses elementos podem ser considerados dois canais de informação separados.

A função retorna as coordenadas xyz e os valores RGB no formato da resolução original da câmera (width x height). A maioria dos sensores tem uma distância máxima, conhecida como distância de corte, além da qual os valores são representados como inf (np.inf). Antes do processamento, é importante filtrar esses valores nulos e atribuir a eles o valor 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

Em seguida, assine o tópico /camera/depth/points para receber a mensagem da nuvem de pontos e converta a mensagem sensor_msgs/PointCloud2 em arrays NumPy com as coordenadas XYZ e os valores RGB (usando a função pointcloud2_to_array). Processe a imagem RGB com o modelo YOLO para extrair os objetos segmentados. Para cada objeto detectado, extraia a máscara de segmentação e aplique-a à imagem RGB e às coordenadas XYZ para isolar o objeto no espaço 3D.

O processamento da máscara é simples, pois ela consiste em valores binários: 1 indica a presença do objeto e 0 indica sua ausência. Para aplicar a máscara, basta multiplicar os canais originais pela máscara. Essa operação isola efetivamente o objeto de interesse na imagem. Por fim, crie um objeto de nuvem de pontos Open3D e visualize o objeto segmentado no espaço 3D com as cores correspondentes.

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)  # máscaras na resolução original da imagem

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])
Código completo
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)  # máscaras na resolução original da imagem

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

Conclusão#

Com o Ultralytics YOLO integrado ao ROS, seu robô pode executar detecção de objetos e segmentação em imagens RGB, imagens de profundidade e nuvens de pontos, transformando fluxos brutos de sensores em percepção acionável. A partir daqui, explore o modo Predict para ver mais opções de inferência ou siga as etapas de um projeto de visão computacional para levar sua aplicação robótica do protótipo à produção.

Perguntas frequentes#

  • O Sistema Operacional de Robôs (ROS) é uma estrutura de código aberto comumente usada em robótica para ajudar desenvolvedores a criar aplicações robóticas robustas. Ele fornece um conjunto de bibliotecas e ferramentas para criar sistemas robóticos e interagir com eles, facilitando o desenvolvimento de aplicações complexas. O ROS oferece suporte à comunicação entre nós por meio de mensagens enviadas por tópicos ou serviços.

  • A integração do Ultralytics YOLO ao ROS envolve configurar um ambiente ROS e usar YOLO para processar dados de sensores. Comece instalando as dependências necessárias, como ros_numpy, e o Ultralytics YOLO:

    pip install ros_numpy ultralytics

    Em seguida, crie um nó ROS e assine um tópico de imagem para processar os dados recebidos para detecção de objetos. Veja um exemplo mínimo:

    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()
  • Os tópicos ROS facilitam a comunicação entre nós em uma rede ROS usando um modelo de publicação e assinatura. Um tópico é um canal nomeado que os nós usam para enviar e receber mensagens de forma assíncrona. No contexto do Ultralytics YOLO, você pode configurar um nó para assinar um tópico de imagem, processar as imagens com YOLO para tarefas como detecção ou segmentação e publicar os resultados em novos tópicos.

    Por exemplo, assine um tópico de câmera e processe a imagem recebida para detecção:

    rospy.Subscriber("/camera/color/image_raw", Image, callback)
  • As imagens de profundidade no ROS, representadas por sensor_msgs/Image, fornecem a distância dos objetos em relação à câmera, algo essencial para tarefas como desvio de obstáculos, mapeamento 3D e localização. Ao usar informações de profundidade junto com imagens RGB, os robôs podem compreender melhor o ambiente 3D.

    Com YOLO, você pode extrair máscaras de segmentação de imagens RGB e aplicá-las às imagens de profundidade para obter informações 3D precisas sobre os objetos, melhorando a capacidade do robô de navegar e interagir com o ambiente.

  • Para visualizar nuvens de pontos 3D no ROS com YOLO:

    1. Converta as mensagens sensor_msgs/PointCloud2 em arrays NumPy.
    2. Use YOLO para segmentar imagens RGB.
    3. Aplique a máscara de segmentação à nuvem de pontos.

    Veja um exemplo de visualização usando 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)  # máscaras na resolução original da imagem
    
    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])

    Essa abordagem oferece uma visualização 3D dos objetos segmentados, útil para tarefas como navegação e manipulação em aplicações robóticas.

Comentários