Guia de início rápido do ROS (Robot Operating System)#
Este guia mostra como integrar o Ultralytics YOLO com o ROS1 (rospy) ou ROS2 (rclpy) para executar detecção de objetos e segmentação em tempo real em imagens RGB, imagens de profundidade e nuvens de pontos.
Pule para a configuração do YOLO com o ROS e, em seguida, trabalhe com imagens RGB, imagens de profundidade ou nuvens de pontos.
ROS Introduction (captioned) from Open Robotics on Vimeo.
O que é o ROS?#
O Robot Operating System (ROS) é uma estrutura de código aberto amplamente utilizada na pesquisa e na indústria de robótica. O ROS fornece uma coleção de bibliotecas e ferramentas para ajudar desenvolvedores a criar aplicativos para robôs. O ROS foi criado para funcionar com várias plataformas robóticas, tornando-se uma ferramenta flexível e poderosa para roboticistas.
Principais recursos do ROS#
-
Arquitetura Modular: O ROS possui uma arquitetura modular, permitindo que desenvolvedores criem sistemas complexos combinando componentes menores e reutilizáveis chamados nós. Cada nó normalmente executa uma função específica, e os nós se comunicam entre si usando mensagens por meio de tópicos ou serviços.
-
Middleware de Comunicação: O ROS oferece uma infraestrutura de comunicação robusta que suporta comunicação entre processos e computação distribuída. Isso é alcançado através de um modelo de publicação-assinatura para fluxos de dados (tópicos) e um modelo de solicitação-resposta para chamadas de serviço.
-
Abstração de Hardware: O ROS fornece uma camada de abstração sobre o hardware, permitindo que desenvolvedores escrevam código independente de dispositivo. Isso permite que o mesmo código seja usado com diferentes configurações de hardware, facilitando a integração e a experimentação.
-
Ferramentas e Utilitários: O ROS vem com um rico conjunto 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 de estado do robô, enquanto o Gazebo oferece um ambiente de simulação poderoso para testar algoritmos e designs de robôs.
-
Ecossistema Extenso: 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, percepção e muito mais. A comunidade contribui ativamente para o desenvolvimento e a manutenção desses pacotes.
Evolução das versões do ROS
Desde o seu desenvolvimento em 2007, o ROS evoluiu por várias versões, divididas em ROS 1 e ROS 2. Os exemplos existentes abaixo usam o ROS1 Noetic; os adaptadores compactos em Usando o ROS2 mostram as interfaces rclpy correspondentes para as versões atuais do ROS2.
ROS 1 vs. ROS 2#
Embora o ROS 1 tenha fornecido uma base sólida para o desenvolvimento robótico, o ROS 2 aborda suas limitações oferecendo:
- Desempenho em Tempo Real: Suporte aprimorado para sistemas em 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 para sistemas multi-robôs e implementações em larga escala.
- Suporte Multiplataforma: Compatibilidade expandida com vários 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 é facilitada por meio de mensagens e tópicos. Uma mensagem é uma estrutura de dados que define as informações trocadas entre os 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 mensagens de um tópico, permitindo que se comuniquem entre si. Esse modelo de publicação-inscrição permite comunicação assíncrona e desacoplamento entre os nós. Cada sensor ou atuador em um sistema robótico normalmente publica dados em um tópico, que pode então ser consumido por outros nós para processamento ou controle. Para os fins deste guia, focaremos em mensagens de Imagem (Image), Profundidade (Depth) e Nuvem de Pontos (PointCloud) e tópicos de câmera.
Configurando o Ultralytics YOLO com ROS#
Os exemplos do ROS1 foram testados usando este ambiente ROS, uma bifurcação (fork) do repositório ROSbot ROS. O mesmo processamento do YOLO e do NumPy se aplica no ROS2; apenas o ciclo de vida do nó e a conversão de mensagens diferem.
Instalação de Dependências#
Além do ambiente ROS, você precisará instalar as seguintes dependências:
-
Pacote NumPy para ROS: Isso é necessário para a conversão rápida entre mensagens de imagem do ROS e matrizes do NumPy.
pip install ros_numpy -
Pacote Ultralytics:
pip install ultralytics
Usando ROS2#
O ROS2 substitui rospy por rclpy e a conversão de imagem de ros_numpy por cv_bridge. O nó a seguir é o equivalente completo no ROS2 para o fluxo de detecção RGB abaixo; instancie os modelos uma vez e reutilize-os em 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 apenas a aquisição e conversão de 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")
# Apply the NumPy mask and distance calculation from the depth example below.Para nuvens de pontos, o ROS2 fornece sensor_msgs_py.point_cloud2; converta a nuvem organizada uma vez e, em seguida, reutilize a segmentação do 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)Use 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, tornando-o adequado para transmitir imagens capturadas por câmeras ou outros sensores. As mensagens de imagem são amplamente utilizadas em aplicações robóticas para tarefas como percepção visual, detecção de objetos e navegação.
Uso passo a passo de Imagem#
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 o YOLO e publicamos os objetos detectados em novos tópicos para detecção e segmentação.
Primeiro, importe as bibliotecas necessárias e instancie dois modelos: um para segmentação e um para detecção. Inicialize um nó ROS (com o nome ultralytics) para permitir a comunicação com o mestre 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 do ROS: um para detecção e um 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 os nós é facilitada usando 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 (subscriber) que escute 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 uma matriz NumPy usando ros_numpy, processa as imagens com os modelos YOLO instanciados anteriormente, anota as imagens e, em seguida, publica-as de volta nos respectivos tópicos: /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 (Robot Operating System) pode ser um desafio devido à natureza distribuída do sistema. Várias ferramentas podem ajudar nesse processo:
rostopic echo <TOPIC-NAME>: Este comando permite visualizar mensagens publicadas em um tópico específico, ajudando você a inspecionar o fluxo de dados.rostopic list: Use este comando para listar todos os tópicos disponíveis no sistema ROS, obtendo uma visão geral dos fluxos de dados ativos.rqt_graph: Esta ferramenta de visualização exibe o gráfico de comunicação entre os nós, fornecendo insights sobre como os nós estão interconectados e como elesinteragem.- Para visualizações mais complexas, como representações 3D, você pode usar o RViz. O RViz (ROS Visualization) é uma ferramenta poderosa de visualização 3D para o ROS. Ele permite visualizar o estado do seu robô e do ambiente dele em tempo real. Com o RViz, você pode ver dados de sensores (por exemplo,
sensor_msgs/Image), estados de modelos de robôs e vários outros tipos de informações, facilitando a depuração e a compreensão do comportamento do seu sistema robótico.
Publique 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 toda a imagem anotada; em vez disso, apenas as classes presentes no campo de visão do robô são necessárias. O exemplo a seguir demonstra como usar as mensagens std_msgs/String para republicar as classes detetadas no tópico /ultralytics/detection/classes. Estas mensagens são mais leves e fornecem informações essenciais, tornando-as valiosas para várias aplicações.
Caso de uso de exemplo#
Considere um robô de armazém equipado com uma câmera e um modelo de detecção de objetos. Em vez de enviar grandes imagens anotadas pela rede, o robô pode publicar uma lista de classes detectadas como mensagens std_msgs/String. Por exemplo, quando o robô detecta objetos como "box", "pallet" e "forklift", ele publica essas classes no tópico /ultralytics/detection/classes. Essas informações podem ser usadas por um sistema de monitoramento central para rastrear o inventário em tempo real, otimizar o planejamento de rotas 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 foca na transmissão de dados críticos.
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 o 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 de Imagem do ROS em uma matriz NumPy para processamento com o 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()Use 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. As imagens de profundidade são cruciais para aplicações robóticas, como desvio de obstáculos, mapeamento 3D e localização.
Uma imagem de profundidade é uma imagem onde cada pixel representa a distância da câmera a um objeto. Diferente das imagens RGB que capturam cores, as imagens de profundidade capturam informações espaciais, permitindo que os robôs percebam a estrutura 3D de seu ambiente.
Imagens de profundidade podem ser obtidas usando vários sensores:
- Câmeras Estéreo: usam duas câmeras para calcular a profundidade com base na disparidade de imagem.
- Câmeras de Tempo de Voo (ToF): medem o tempo que a luz leva para retornar de um objeto.
- Sensores de Luz Estruturada: projetam um padrão e medem sua deformação em superfícies.
Usando 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 pixels. O campo de codificação para imagens de profundidade geralmente usa um formato como "16UC1", indicando um inteiro sem sinal de 16 bits por pixel, onde cada valor representa a distância até o objeto. As imagens de profundidade são comumente usadas em conjunto com imagens RGB para fornecer uma visão mais abrangente do ambiente.
Usando o YOLO, é possível extrair e combinar informações de imagens RGB e de profundidade. Por exemplo, o YOLO pode detectar objetos dentro de uma imagem RGB, e essa detecção pode ser usada para identificar regiões correspondentes na imagem de profundidade. Isso permite a extração de informações de profundidade precisas para objetos detectados, aumentando a capacidade do robô de entender seu ambiente em três dimensões.
Ao trabalhar com imagens de profundidade, é essencial garantir que as imagens RGB e de profundidade estejam alinhadas corretamente. As câmeras RGB-D, como a série Intel RealSense, fornecem imagens RGB e de profundidade sincronizadas, facilitando a combinação de informações de ambas as fontes. Se você estiver usando câmeras RGB e de profundidade separadas, é crucial calibrá-las para garantir um alinhamento preciso.
Uso passo a passo de Profundidade#
Neste exemplo, usamos o YOLO para segmentar uma imagem e aplicar a máscara extraída para segmentar o objeto na imagem de profundidade. Isso nos permite determinar a distância de cada pixel do objeto de interesse a partir do centro focal da câmera. Ao obter essa informação 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 de imagem de profundidade recebida. A função aguarda pelas mensagens de imagem de profundidade e de imagem RGB, converte-as em matrizes NumPy e aplica o modelo de segmentação à imagem RGB. Em seguida, ela extrai a máscara de segmentação para cada objeto detectado e calcula a distância média do objeto à câmera usando a imagem de profundidade. A maioria dos sensores tem uma distância máxima, conhecida como distância de corte (clip distance), além da qual os valores são representados como inf (np.inf). Antes de processar, é importante filtrar esses valores nulos e atribuir a eles um valor 0. Por fim, ela publica os objetos detectados juntamente com 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)
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)
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()Use o Ultralytics com o ROS sensor_msgs/PointCloud2#
O tipo de mensagem sensor_msgs/PointCloud2 é uma estrutura de dados usada no ROS para representar dados de nuvem de pontos 3D. Este tipo de mensagem é fundamental para aplicações robóticas, permitindo tarefas como mapeamento 3D, reconhecimento de objetos e localização.
Uma nuvem de pontos é uma coleção de pontos de dados definidos dentro de um sistema de coordenadas tridimensional. Esses pontos de dados representam a superfície externa de um objeto ou de uma cena, capturados por meio de tecnologias de escaneamento 3D. Cada ponto na nuvem possui 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.
Ao trabalhar com sensor_msgs/PointCloud2, é essencial considerar o referencial (reference frame) do sensor a partir do qual os dados da nuvem de pontos foram adquiridos. A nuvem de pontos é inicialmente capturada no referencial do sensor. Você pode determinar esse referencial ouvindo o tópico /tf_static. No entanto, dependendo dos requisitos específicos da sua aplicação, pode ser necessário converter a nuvem de pontos em outro referencial. Essa transformação pode ser alcançada usando o pacote tf2_ros, que fornece ferramentas para gerenciar referenciais de coordenadas e transformar dados entre eles.
Nuvens de Pontos podem ser obtidas usando vários sensores:
- LIDAR (Light Detection and Ranging): Usa pulsos de laser para medir distâncias até objetos e criar mapas 3D de alta precisão.
- Câmeras de Profundidade: Capturam informações de profundidade para cada pixel, permitindo a reconstrução 3D da cena.
- Câmeras Estéreo: Utilizam duas ou mais câmeras para obter informações de profundidade através de triangulação.
- Escâneres de Luz Estruturada: Projetam um padrão conhecido sobre uma superfície e medem a deformação para calcular a profundidade.
Usando o YOLO com Nuvens de Pontos#
Para integrar o YOLO com mensagens do tipo sensor_msgs/PointCloud2, podemos empregar um método semelhante ao usado para mapas de profundidade. Ao aproveitar as informações de cores incorporadas na nuvem de pontos, podemos extrair uma imagem 2D, realizar a segmentação nessa imagem usando o 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 fornece ferramentas robustas para gerenciar estruturas de dados de nuvem de pontos, visualizá-las e executar operações complexas sem problemas. Essa biblioteca pode simplificar significativamente o processo e melhorar nossa capacidade de manipular e analisar nuvens de pontos em conjunto com a segmentação baseada no 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 transforma uma mensagem sensor_msgs/PointCloud2 em duas matrizes NumPy. As mensagens sensor_msgs/PointCloud2 contêm pontos n com base na 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. Eles podem ser considerados como 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 (clip distance), além da qual os valores são representados como inf (np.inf). Antes de processar, é importante filtrar esses valores nulos e atribuir a eles um 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, rgbEm seguida, assine o tópico /camera/depth/points para receber a mensagem de nuvem de pontos e converta a mensagem sensor_msgs/PointCloud2 em matrizes NumPy contendo as coordenadas XYZ e os valores RGB (usando a função pointcloud2_to_array). Processe a imagem RGB usando o modelo YOLO para extrair objetos segmentados. Para cada objeto detectado, extraia a máscara de segmentação e aplique-a tanto à imagem RGB quanto às coordenadas XYZ para isolar o objeto no espaço 3D.
O processamento da máscara é direto, pois ela consiste em valores binários, com 1 indicando a presença do objeto e 0 indicando a ausência. Para aplicar a máscara, basta multiplicar os canais originais pela máscara. Esta operação efetivamente isola o objeto de interesse dentro da imagem. Por fim, crie um objeto de nuvem de pontos do Open3D e visualize o objeto segmentado no espaço 3D com as cores associadas.
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])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)
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])
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 de sensores brutos em percepção acionável. A partir daqui, explore o modo Predict para mais opções de inferência ou siga os passos de um projeto de visão computacional para levar seu aplicativo de robótica do protótipo à produção.
FAQ#
O que é o Robot Operating System (ROS)?#
O Robot Operating System (ROS) é uma estrutura de código aberto comumente usada na robótica para ajudar desenvolvedores a criar aplicativos robóticos robustos. Ele fornece uma coleção de bibliotecas e ferramentas para construir e interagir com sistemas robóticos, permitindo um desenvolvimento mais fácil de aplicações complexas. O ROS oferece suporte à comunicação entre nós usando mensagens por meio de tópicos ou serviços.
Como integrar o Ultralytics YOLO com o ROS para detecção de objetos em tempo real?#
A integração do Ultralytics YOLO com o ROS envolve a configuração de um ambiente ROS e o uso do YOLO para processar dados de sensores. Comece instalando as dependências necessárias como ros_numpy e o Ultralytics YOLO:
pip install ros_numpy ultralyticsEm seguida, crie um nó ROS e assine um tópico de imagem para processar os dados recebidos para detecção de objetos. Aqui está 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()O que são tópicos ROS e como eles são usados no Ultralytics YOLO?#
Os tópicos do ROS facilitam a comunicação entre os nós em uma rede ROS usando um modelo de publicação-inscrição. 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 fazer com que um nó assine um tópico de imagem, processe as imagens usando o YOLO para tarefas como detecção ou segmentação e publique 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)Por que usar imagens de profundidade com o Ultralytics YOLO no ROS?#
As imagens de profundidade no ROS, representadas por sensor_msgs/Image, fornecem a distância dos objetos em relação à câmera, sendo cruciais para tarefas como desvio de obstáculos, mapeamento 3D e localização. Usando informações de profundidade junto com imagens RGB, os robôs podem entender melhor seu ambiente 3D.
Com o YOLO, você pode extrair máscaras de segmentação de imagens RGB e aplicar essas máscaras em imagens de profundidade para obter informações precisas de objetos 3D, melhorando a capacidade do robô de navegar e interagir com seu entorno.
Como posso visualizar nuvens de pontos 3D com YOLO no ROS?#
Para visualizar nuvens de pontos 3D no ROS com YOLO:
- Converta mensagens
sensor_msgs/PointCloud2em matrizes NumPy. - Use o YOLO para segmentar imagens RGB.
- Aplique a máscara de segmentação à nuvem de pontos.
Aqui está um exemplo usando o Open3D para visualização:
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])Esta abordagem fornece uma visualização 3D de objetos segmentados, útil para tarefas como navegação e manipulação em aplicações de robótica.