DUDuuu.studio

Back

简介#

在无人机编队仿真的第三部分,我们聚焦于将目标识别技术集成到 ROS2+PX4 仿真环境 中,利用YOLOv8实现高效的实时目标检测。YOLO(You Only Look Once)是一种高效、实时的目标检测算法,广泛应用于计算机视觉任务,如无人机目标识别、自动驾驶和监控。

我们基于PX4 SITL(软件在环仿真)和Gazebo Ignition,结合gz_x500_depth四旋翼模型,展示如何通过ROS2订阅无人机搭载的RGB相机(OakD-Lite)图像,应用YOLOv8模型检测特定目标,并在Ubuntu 22.04环境中实时可视化检测结果。这可以为后续编队协同任务提供更多技术支持。

YOLOv8目标检测流程
图 1:YOLOv8目标检测流程示意图

创建Python虚拟环境#

我们使用的Python版本号为3.10.12,读者可以根据自己的Python版本更改命令。

# 创建虚拟环境
python3 -m venv ~/px4-venv
# 激活
source ~/px4-venv/bin/activate
# 链接ROS2环境
source /opt/ros/humble/setup.bash
export PYTHONPATH=/opt/ros/humble/lib/python3.10/site-packages:$PYTHONPATH
bash

安装MAVSDK#

如果后续出现Numpy的版本号冲突的问题,建议先使用 pip uninstall numpy -y 卸载当前版本,再按照以下命令安装Numpy 1.x版本。

pip install mavsdk
pip install aioconsole
pip install pygame
sudo apt install ros-humble-ros-gzgarden
pip install "numpy<2"
pip install opencv-python
bash

安装YOLO#

pip install ultralytics
bash

启动带相机的四旋翼模型并添加相机话题#

我们稍微修改一下前两文的启动仿真命令:

PX4_GZ_STANDALONE=1 PX4_SYS_AUTOSTART=4001 PX4_GZ_MODEL_POSE="0,0" PX4_SIM_MODEL=gz_x500_depth ./build/px4_sitl_default/bin/px4 -i 0
bash

我们再来看一下这个启动命令,对于x500模型,4001/4002分别对应两种不同的四旋翼配置(“X”型和十字型机架),在此处,我们使用带深度相机的 gz_x500_depth 的x_500模型,这与之前的有所不同。

我们前往models所在的文件夹,找到 x500_depthmodel.sdf 文件以及其相机模块 OakD_Lite 的sdf文件:

x500_depth model.sdf
图 2:x500_depth/model.sdf 配置
OakD-Lite SDF传感器部分
图 3:OakD_Lite/model.sdf 的传感器部分

我们发现,相机模块OakD_Lite一共搭载了两个相机传感器,分别为IMX214(RGB相机)和StereoOV7251(深度相机)。其中RGB相机的输出图像格式为RGB_INT8,深度相机的输出图像格式为R_FLOAT32。RGB相机是常用的进行图像识别的传感器,而深度相机用于探测距离,在避障任务中有很好的应用。在此处,我们先使用RGB相机。

现在我们需要关注sdf文件的这几行:

<width>1920</width>
<height>1080</height>
<format>RGB_INT8</format>
xml

这三个是比较重要的参数,代表图像的尺寸和格式,这在后续的代码中需要注意,尤其是图像的格式需要匹配,RGB_INT8是一个三通道的参数。

<always_on>1</always_on>
<update_rate>30</update_rate>
<visualize>true</visualize>
<topic>image_raw</topic>
xml

<always_on> 代表相机持续保持开启状态,我们在最后为这个相机添加一个ROS2话题(此处为笔者自行添加),名称为 image_raw,后续我们会利用这个话题进行消息的传输。

调用YOLOv8进行目标识别#

mkdir -p ~/YOLO
cd ~/YOLO
touch uav_camera_det.py
bash

创建一个/YOLO目录,现在编写一个调用YOLOv8进行目标检测的Python代码。

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
from ultralytics import YOLO

model = YOLO('yolov8m.pt')
python

调用一些需要的库和图像转换工具,类似于上一篇文章,rclpy是ROS2的Python客户端,CvBridge是ROS与openCV的图像转换工具,yolov8m.pt 是中等规模模型(Medium),基于COCO数据集训练,支持80类目标。以下是COCO数据集的80个完整类别:

0: person         1: bicycle       2: car           3: motorcycle
4: airplane       5: bus           6: train         7: truck
8: boat           9: traffic light 10: fire hydrant 11: stop sign
12: parking meter 13: bench        14: bird         15: cat
16: dog           17: horse        18: sheep        19: cow
20: elephant      21: bear         22: zebra        23: giraffe
24: backpack      25: umbrella     26: handbag      27: tie
28: suitcase      29: frisbee      30: skis         31: snowboard
32: sports ball   33: kite         34: baseball bat 35: baseball glove
36: skateboard    37: surfboard    38: tennis racket 39: bottle
40: wine glass    41: cup          42: fork         43: knife
44: spoon         45: bowl         46: banana       47: apple
48: sandwich      49: orange       50: broccoli     51: carrot
52: hot dog       53: pizza        54: donut        55: cake
56: chair         57: couch        58: potted plant 59: bed
60: dining table  61: toilet       62: tv           63: laptop
64: mouse         65: remote       66: keyboard     67: cell phone
68: microwave     69: oven         70: toaster      71: sink
72: refrigerator  73: book         74: clock        75: vase
76: scissors      77: teddy bear   78: hair drier   79: toothbrush
plaintext

为了减少计算消耗,提高检测速度,我们往往将检测目标进行缩小,我们稍后讲。

class ImageSubscriber(Node):
    def __init__(self):
        super().__init__('image_subscriber')
        self.subscription = self.create_subscription(
            Image,
            '/image_raw',
            self.listener_callback,
            10)
        self.br = CvBridge()
        self.frame_count = 0
        self.process_interval = 1

        cv2.namedWindow('YOLOv8 Detection', cv2.WINDOW_NORMAL)
        cv2.resizeWindow('YOLOv8 Detection', 640, 480)
python

类似于上一篇文章使用C++编写的Offboard指令,这里我们定义一个新的类,发布一个名为 image_subscriber 的ROS2订阅者实例,进行初始化。我们在这里需要注意,在创建订阅者实例时,话题名称要与我们之前修改的RGB相机的sdf文件的话题名称相同,这里为我们之前发布的 /image_raw,需要匹配。调用CvBridge函数,这是因为我们需要将ROS的图像格式(RGB8)转换为OpenCV的图像格式(BGR8),最后将窗口可视化,以640×480的尺寸进行可视化。

def listener_callback(self, data):
    self.frame_count += 1
    if self.frame_count % self.process_interval != 0:
        self.get_logger().info("跳过帧")
        return
    try:
        self.get_logger().info(f"收到视频帧,编码: {data.encoding}")
        image = self.br.imgmsg_to_cv2(data, desired_encoding="bgr8")
        self.get_logger().info(f"图像尺寸: {image.shape}")
        image = cv2.resize(image, (640, 480))
        cv2.imwrite("input_image.jpg", image)
        results = model.predict(image, classes=[0, 2])
        annotated_image = results[0].plot()
        cv2.imwrite("yolo_output.jpg", annotated_image)
        cv2.imshow('YOLOv8 Detection', annotated_image)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            raise KeyboardInterrupt
        self.get_logger().info(f"检测到 {len(results[0].boxes)} 个目标")
        for box in results[0].boxes:
            self.get_logger().info(f"检测到: 类别={box.cls}, 置信度={box.conf}, 坐标={box.xyxy}")
    except Exception as e:
        self.get_logger().error(f"处理图像错误: {str(e)}")
python

定义回调函数,image = cv2.resize(image, (640, 480)) 是用于图像的预处理的函数,将我们的sdf里定义的1920×1080的图像压缩为640×480。随后使用 results = model.predict(image, classes=[0, 2]) 运行YOLOv8,限制检测类别为0,2(对应人、车),这样可以提高检测的速度。如果要检测其他物体,可以按照上述的80个类别编号进行添加。如果80个类别都要检测,可以使用 results = model.predict(image) 实现全部检测,最后进行可视化,但这样肯定会极大地浪费计算效率。

def destroy_node(self):
    cv2.destroyAllWindows()
    super().destroy_node()
python

重写一个销毁ROS2节点的方法,关闭OpenCV的窗口,清理ROS2节点和环境。

def main(args=None):
    rclpy.init(args=args)
    image_subscriber = ImageSubscriber()
    try:
        rclpy.spin(image_subscriber)
    except KeyboardInterrupt:
        pass
    finally:
        image_subscriber.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()
python

最后执行 main() 函数,实现对话题的订阅以及调用YOLOv8实现无人机的目标识别及检测。至此,我们进行ROS2+PX4+YOLOv8的目标识别任务的准备就做好了。

实现仿真#

# 终端1
python3 simulation-gazebo

# 终端2
cd ~/PX4-Autopilot
PX4_GZ_STANDALONE=1 PX4_SYS_AUTOSTART=4001 PX4_GZ_MODEL_POSE="0,0" PX4_SIM_MODEL=gz_x500_depth ./build/px4_sitl_default/bin/px4 -i 0

# 终端3
MicroXRCEAgent udp4 -p 8888
bash

按照之前的步骤启动Gazebo仿真、带深度相机的四旋翼模型以及打开通信,现在我们需要运行这个命令:

ros2 run ros_gz_image image_bridge /image_raw
bash

这是一个将ROS2与Gazebo进行桥接的命令,可以将Gazebo里的话题消息转换为ROS2的 sensor_msgs.msg.Image 消息,并发布到ROS2话题。此时我们订阅的就是我们之前在sdf文件里添加的 /image_raw 话题。

运行这个命令后,我们执行一下我们的YOLOv8检测的代码,并且运行一下我们的Offboard控制,让飞机飞起来,在可视化窗口查看一下摄像头和检测信息。

# 终端1 — 激活虚拟环境并运行YOLO检测
source ~/px4-venv/bin/activate
cd ~/YOLO
python3 uav_camera_det.py

# 终端2 — 运行Offboard控制
ros2 run multioffboardcontrol uav0_1
bash

结果展示#

视频 1:成功启动YOLO检测

我们发现,带深度相机的四旋翼飞机成功启动仿真,并且YOLOv8成功启动了检测。由于笔者的世界搭建里还没有加入车和人,所以没有目标探测到,读者朋友们可以进行添加并验证。

同时,由于相机是固连在机身坐标系上,而作者此时的Offboard模式使用的是盘旋指令,因此相机会跟着飞机姿态进行旋转,图像不稳定。后续可以采取平飞+调整相机姿态的方式实现更稳定的目标识别。

总结#

在这篇文章里,我们通过调用YOLOv8实现了在ROS2+PX4中进行目标检测的任务。需要着重注意的是相机种类(RGB相机和深度相机)的区别,同时注意sdf文件里是否有相应的话题配置,如果没有,需要手动添加。在仿真过程中我们发现还有一些细节可以调整,例如如何适应相机随着机身坐标系的偏转,如何提高识别成功率等,读者朋友们可以在实际应用场景中进行改进。

在后续的任务中,搭配RGB相机和深度相机的四旋翼飞机可以实现场景下的目标识别、自主避障(深度相机)等任务。如果需要在多机编队中集成视觉感知,可以参考前文的 ROS2+PX4 多机 Offboard 控制,将目标检测与编队飞行结合。期待大家的实际应用。

最后,再次感谢读者朋友们的支持和耐心的阅读,欢迎大家对本文进行批评指正、补充及建议。

参考资料#

无人机仿真环境调用YOLO的简单示例
https://duduuu.xyz/zh/posts/px4-ros2-yolo
Author dudu
Published at 2026年7月10日
阅读
总访问