Guia de início rápido do ROS (Sistema Operacional de Robôs)#
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.
Vá diretamente para configurar o YOLO com ROS e, em seguida, trabalhe com imagens RGB, imagens de profundidade ou nuvens de pontos.
O que é o ROS?#
O Sistema Operacional de Robôs (ROS) é uma estrutura de código aberto amplamente utilizada na investigação e na indústria da robótica. O ROS disponibiliza 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, assista ao vídeo de três minutos Introdução ao ROS da Open Robotics.
Principais funcionalidades do ROS#
-
Arquitetura modular: o ROS tem uma arquitetura modular, permitindo que os programadores 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 comunicam entre si usando mensagens através de tópicos ou serviços.
-
Middleware de comunicação: o ROS oferece uma infraestrutura de comunicação robusta que suporta 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 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 os programadores escrevam código independente do dispositivo. Assim, o mesmo código pode ser utilizado com diferentes configurações de hardware, facilitando a integração e a experimentação.
-
Ferramentas e utilitários: o ROS inclui um amplo conjunto de ferramentas e utilitários para visualização, depuração e simulação. Por exemplo, o RViz é utilizado para visualizar dados dos sensores e informações sobre o estado do robô, enquanto o Gazebo fornece um poderoso ambiente de simulação para testar algoritmos e projetos de robôs.
-
Ecossistema abrangente: o ecossistema do ROS é vasto e continua a crescer, 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 desses pacotes.
Evolução das versões do ROS
Desde o seu desenvolvimento em 2007, o ROS evoluiu através de várias versões, divididas entre ROS 1 e ROS 2. Os exemplos existentes abaixo utilizam o ROS1 Noetic; os adaptadores compactos em Usar o ROS2 mostram as interfaces rclpy correspondentes às 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 resolve as suas limitações ao oferecer:
- Desempenho em tempo real: suporte melhorado para sistemas em tempo real e comportamento determinístico.
- Segurança: funcionalidades de segurança reforçadas para uma operação segura e fiável em vários ambientes.
- Escalabilidade: melhor suporte para sistemas com vários robôs e implementações em grande escala.
- Suporte multiplataforma: compatibilidade ampliada com vários sistemas operacionais além do Linux, incluindo Windows e macOS.
- Comunicação flexível: utilização do 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 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 através do qual as mensagens são enviadas e recebidas. Os nós podem publicar mensagens num tópico ou subscrever mensagens de um tópico, permitindo a comunicação entre si. Este modelo de publicação-assinatura permite a comunicação assíncrona e o desacoplamento entre nós. Cada sensor ou atuador de um sistema robótico normalmente publica dados num tópico, que pode então ser consumido por outros nós para processamento ou controlo. Para os fins deste guia, vamos concentrar-nos nas mensagens Image, Depth e PointCloud e nos tópicos de câmara.
Configurar o Ultralytics YOLO com ROS#
Os exemplos de ROS1 foram testados utilizando este ambiente ROS, uma bifurcação do repositório ROS do ROSbot. O mesmo processamento com YOLO e NumPy aplica-se ao ROS2; apenas o ciclo de vida do nó e a conversão de mensagens diferem.
Instalação das dependências#
Além do ambiente ROS, terás de instalar as seguintes dependências:
-
Pacote ROS NumPy: é necessário para a conversão rápida entre mensagens Image do ROS e matrizes NumPy.
pip install ros_numpy -
Pacote Ultralytics:
pip install ultralytics
Usar o ROS2#
O ROS2 substitui rospy por rclpy e a conversão de imagens ros_numpy por cv_bridge. O nó seguinte é o equivalente completo em ROS2 do fluxo de deteção RGB abaixo; instancia os modelos uma vez e reutiliza-os em diferentes 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, reutiliza o código de processamento de profundidade abaixo e substitui apenas a aquisiçã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")
# Apply the NumPy mask and distance calculation from the depth example below.Para nuvens de pontos, o ROS2 fornece sensor_msgs_py.point_cloud2; converte a nuvem organizada uma vez e, em seguida, reutiliza 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 ROS sensor_msgs/Image#
O tipo de mensagem sensor_msgs/Image é normalmente utilizado no ROS para representar dados de imagem. Contém campos para a codificação, a altura, a largura e os dados dos píxeis, tornando-o adequado para transmitir imagens capturadas por câmaras ou outros sensores. As mensagens Image são amplamente utilizadas em aplicações robóticas para tarefas como perceção visual, deteção de objetos e navegação.
Utilização de Image passo a passo#
O seguinte trecho de código demonstra como utilizar o pacote Ultralytics YOLO com ROS. Neste exemplo, subscrevemos um tópico de câmara, processamos a imagem recebida com YOLO e publicamos os objetos detetados em novos tópicos para deteção e segmentação.
Primeiro, importa as bibliotecas necessárias e instancia dois modelos: um para segmentação e outro para deteção. Inicializa um nó ROS (com o nome ultralytics) para permitir a comunicação com o mestre ROS. Para garantir uma ligação estável, incluímos uma breve pausa, dando ao nó tempo suficiente para estabelecer a ligaçã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)Inicializa dois tópicos ROS: um para deteção e outro para segmentação. Estes tópicos serão utilizados para publicar as imagens anotadas, tornando-as acessíveis para processamento adicional. A comunicação entre nós é facilitada através 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, cria um subscritor que escuta mensagens no tópico /camera/color/image_raw e chama uma função de callback para cada nova mensagem. Esta função de callback recebe mensagens do tipo sensor_msgs/Image, converte-as numa matriz NumPy utilizando ros_numpy, processa as imagens com os modelos YOLO instanciados anteriormente, anota as imagens e publica-as novamente nos respetivos tópicos: /ultralytics/detection/image para deteçã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
A depuração de nós ROS (Sistema Operacional de Robôs) pode ser um desafio devido à natureza distribuída do sistema. Várias ferramentas podem ajudar neste processo:
rostopic echo <TOPIC-NAME>: este comando permite visualizar as mensagens publicadas num tópico específico, ajudando-te a inspecionar o fluxo de dados.rostopic list: utiliza 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 apresenta o grafo de comunicação entre nós, fornecendo informações sobre como os nós estão interligados e como interagem.- Para visualizações mais complexas, como representações 3D, podes utilizar o RViz. O RViz (Visualização do ROS) é uma poderosa ferramenta de visualização 3D para o ROS. Permite visualizar o estado do robô e do ambiente em tempo real. Com o RViz, podes ver dados dos sensores (por exemplo,
sensor_msgs/Image), estados do modelo do robô e vários outros tipos de informação, facilitando a depuração e a compreensão do comportamento do sistema robótico.
Publicar classes detetadas com std_msgs/String#
As mensagens ROS padrão também incluem mensagens std_msgs/String. Em muitas aplicações, não é necessário republicar a imagem anotada completa; em vez disso, são necessárias apenas as classes presentes no campo de visão do robô. O exemplo seguinte demonstra como utilizar 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 úteis para várias aplicações.
Exemplo de caso de utilização#
Considera um robô de armazém equipado com uma câmara e um modelo de deteção de objetos. Em vez de enviar imagens anotadas grandes pela rede, o robô pode publicar uma lista de classes detetadas como mensagens std_msgs/String. Por exemplo, quando o robô deteta objetos como "caixa", "palete" e "empilhador", publica estas classes no tópico /ultralytics/detection/classes. Estas informações podem então ser utilizadas por um sistema central de monitorização para acompanhar o inventário em tempo real, otimizar o planeamento da trajetória do robô para evitar obstáculos ou desencadear ações específicas, como recolher uma caixa detetada. Esta abordagem reduz a largura de banda necessária para a comunicação e concentra-se na transmissão de dados críticos.
Utilização de String passo a passo#
Este exemplo demonstra como utilizar o pacote Ultralytics YOLO com ROS. Neste exemplo, subscrevemos um tópico de câmara, processamos a imagem recebida com YOLO e publicamos os objetos detetados no novo tópico /ultralytics/detection/classes utilizando mensagens std_msgs/String. O pacote ros_numpy é utilizado para converter a mensagem Image do ROS numa matriz 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 no ROS#
Além das imagens RGB, o ROS suporta imagens de profundidade, que fornecem informações sobre a distância dos objetos em relação à câmara. As imagens de profundidade são essenciais para aplicações robóticas como evitar obstáculos, mapeamento 3D e localização.
Uma imagem de profundidade é uma imagem em que cada píxel representa a distância entre a câmara 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.
As imagens de profundidade podem ser obtidas utilizando vários sensores:
- Câmaras estéreo: utilizam duas câmaras para calcular a profundidade com base na disparidade da imagem.
- Câmaras de tempo de voo (ToF): medem o tempo que a luz demora a regressar de um objeto.
- Sensores de luz estruturada: projetam um padrão e medem a 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 a codificação, a altura, a largura e os dados dos píxeis. O campo de codificação das imagens de profundidade utiliza frequentemente um formato como "16UC1", que indica um inteiro sem sinal de 16 bits por píxel, em que cada valor representa a distância até ao objeto. As imagens de profundidade são normalmente utilizadas em conjunto com imagens RGB para fornecer uma visão mais abrangente do ambiente.
Com YOLO, é possível extrair e combinar informações de imagens RGB e de profundidade. Por exemplo, YOLO pode detetar objetos numa imagem RGB, e esta deteção pode ser utilizada para identificar as regiões correspondentes na imagem de profundidade. Isto permite extrair informações precisas de profundidade dos objetos detetados, melhorando a capacidade do robô de compreender o ambiente em três dimensões.
Ao trabalhar com imagens de profundidade, é essencial garantir que as imagens RGB e de profundidade estão corretamente alinhadas. As câmaras 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 utilizares câmaras RGB e de profundidade separadas, é crucial calibrá-las para garantir um alinhamento preciso.
Utilização de profundidade passo a passo#
Neste exemplo, utilizamos YOLO para segmentar uma imagem e aplicamos a máscara extraída para segmentar o objeto na imagem de profundidade. Isto permite determinar a distância de cada píxel do objeto de interesse em relação ao centro focal da câmara. Ao obter estas informações de distância, podemos calcular a distância entre a câmara e o objeto específico na cena. Começa por importar as bibliotecas necessárias, criar um nó ROS e instanciar 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, define uma função de callback que processa a mensagem de imagem de profundidade recebida. A função aguarda as mensagens da imagem de profundidade e da imagem RGB, converte-as em matrizes NumPy e aplica o modelo de segmentação à imagem RGB. Depois, extrai a máscara de segmentação de cada objeto detetado e calcula a distância média do objeto à câmara utilizando a imagem de profundidade. A maioria dos sensores tem uma distância máxima, conhecida como distância de recorte, além da qual os valores são representados como inf (np.inf). Antes do processamento, é importante filtrar estes valores nulos e atribuir-lhes o valor 0. Por fim, publica os objetos detetados, juntamente com as respetivas 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()Usar o Ultralytics com ROS sensor_msgs/PointCloud2#
O tipo de mensagem sensor_msgs/PointCloud2 é uma estrutura de dados utilizada no ROS para representar dados de nuvens de pontos 3D. Este tipo de mensagem é essencial 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 num sistema de coordenadas tridimensional. Estes pontos representam a superfície externa de um objeto ou de uma cena, capturada através de tecnologias de digitalização 3D. Cada ponto da nuvem tem coordenadas X, Y e Z, que correspondem à sua posição no espaço, e pode também incluir informações adicionais, como cor e intensidade.
Ao trabalhar com sensor_msgs/PointCloud2, é essencial considerar o sistema de coordenadas de referência do sensor a partir do qual os dados da nuvem de pontos foram adquiridos. A nuvem de pontos é inicialmente capturada no sistema de coordenadas de referência do sensor. Podes determinar este sistema de referência escutando o tópico /tf_static. No entanto, dependendo dos requisitos específicos da tua aplicação, poderá ser necessário converter a nuvem de pontos para outro sistema de referência. Esta transformação pode ser realizada com o pacote tf2_ros, que fornece ferramentas para gerir sistemas de coordenadas e transformar dados entre eles.
As nuvens de pontos podem ser obtidas utilizando vários sensores:
- LIDAR (deteção e medição de distância por luz): utiliza impulsos laser para medir distâncias até aos objetos e criar mapas 3D de alta-precisão.
- Câmaras de profundidade: capturam informações de profundidade para cada píxel, permitindo a reconstrução 3D da cena.
- Câmaras estéreo: utilizam duas ou mais câmaras para obter informações de profundidade através de triangulação.
- Scanners de luz estruturada: projetam um padrão conhecido sobre uma superfície e medem a deformação para calcular a profundidade.
Usar YOLO com nuvens de pontos#
Para integrar YOLO com mensagens do tipo sensor_msgs/PointCloud2, podemos utilizar um método semelhante ao usado para mapas de profundidade. Aproveitando as informações de cor incorporadas na nuvem de pontos, podemos extrair uma imagem 2D, executar a segmentação dessa imagem com YOLO e, em seguida, aplicar a máscara resultante aos pontos tridimensionais para isolar o objeto 3D de interesse.
Para trabalhar com nuvens de pontos, recomendamos utilizar o Open3D (pip install open3d), uma biblioteca Python fácil de utilizar. O Open3D fornece ferramentas robustas para gerir estruturas de dados de nuvens de pontos, visualizá-las e executar operações complexas de forma simples. Esta biblioteca pode simplificar significativamente o processo e melhorar a nossa capacidade de manipular e analisar nuvens de pontos em conjunto com a segmentação baseada em YOLO.
Utilização de nuvens de pontos passo a passo#
Importa as bibliotecas necessárias e instancia 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")Cria 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 nas dimensões 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. Estas podem ser consideradas dois canais de informação separados.
A função devolve as coordenadas xyz e os valores RGB no formato da resolução original da câmara (width x height). A maioria dos sensores tem uma distância máxima, conhecida como distância de recorte, além da qual os valores são representados como inf (np.inf). Antes do processamento, é importante filtrar estes valores nulos e atribuir-lhes 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, rgbEm seguida, subscreve o tópico /camera/depth/points para receber a mensagem da nuvem de pontos e converte a mensagem sensor_msgs/PointCloud2 em matrizes NumPy que contêm as coordenadas XYZ e os valores RGB (utilizando a função pointcloud2_to_array). Processa a imagem RGB com o modelo YOLO para extrair os objetos segmentados. Para cada objeto detetado, extrai a máscara de segmentação e aplica-a à imagem RGB e às coordenadas XYZ para isolar o objeto no espaço 3D.
O processamento da máscara é simples, pois consiste em valores binários, sendo 1 a indicação da presença do objeto e 0 a indicação da ausência. Para aplicar a máscara, basta multiplicar os canais originais pela máscara. Esta operação isola efetivamente o objeto de interesse na imagem. Por fim, cria um objeto de nuvem de pontos Open3D e visualiza 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)
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 no ROS, o teu robô pode executar deteção de objetos e segmentação em imagens RGB, imagens de profundidade e nuvens de pontos, transformando fluxos de sensores brutos em perceção acionável. A partir daqui, explora o modo Predict para obter mais opções de inferência ou segue os passos de um projeto de visão computacional para levar a tua aplicação robótica do protótipo à produção.
Perguntas frequentes#
O Sistema Operacional de Robôs (ROS) é uma estrutura de código aberto normalmente utilizada na robótica para ajudar os programadores a criar aplicações robóticas robustas. Fornece um conjunto de bibliotecas e ferramentas para criar e interagir com sistemas robóticos, permitindo um desenvolvimento mais fácil de aplicações complexas. O ROS suporta a comunicação entre nós através de mensagens em tópicos ou serviços.
A integração do Ultralytics YOLO com o ROS envolve configurar um ambiente ROS e utilizar YOLO para processar dados dos sensores. Começa por instalar as dependências necessárias, como
ros_numpye o Ultralytics YOLO:pip install ros_numpy ultralyticsEm seguida, cria um nó ROS e subscreve um tópico de imagem para processar os dados recebidos para deteção de objetos. Eis 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 numa rede ROS através de um modelo de publicação-assinatura. Um tópico é um canal nomeado que os nós utilizam para enviar e receber mensagens de forma assíncrona. No contexto do Ultralytics YOLO, podes fazer com que um nó subscreva um tópico de imagem, processe as imagens com YOLO para tarefas como deteção ou segmentação e publique os resultados em novos tópicos.
Por exemplo, subscreve um tópico de câmara e processa a imagem recebida para deteçã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 à câmara, o que é essencial para tarefas como evitar obstáculos, mapeamento 3D e localização. Ao utilizar informações de profundidade juntamente com imagens RGB, os robôs podem compreender melhor o seu ambiente 3D.Com YOLO, podes extrair máscaras de segmentação de imagens RGB e aplicar essas máscaras a 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:
- Converta as mensagens
sensor_msgs/PointCloud2em matrizes NumPy. - Use YOLO para segmentar imagens RGB.
- Aplique a máscara de segmentação à nuvem de pontos.
Veja um exemplo que usa 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])Essa abordagem fornece uma visualização 3D dos objetos segmentados, útil para tarefas como navegação e manipulação em aplicações de robótica.
- Converta as mensagens