4. 目标检测基础
目标检测是PX4目标跟随项目中最关键的感知环节。与图像分类不同,目标检测不仅要回答"画面里有什么",还要回答"目标在哪里"。在无人机视觉场景中,我们需要从机载摄像头获取的实时视频流中,持续定位目标物体(如行人、车辆或特定标记物),并输出其边界框位置、类别标签和置信度分数。这些信息经过坐标转换后,才能生成飞行控制指令。本章将系统讲解目标检测的核心概念、主流算法原理,以及如何在ROS2环境中集成OpenCV与深度学习模型,为后续的目标跟踪与飞行控制奠定基础。
上图展示了目标检测在PX4视觉感知链路中的位置。机载摄像头采集图像后,检测算法输出三类关键信息:边界框、类别和置信度。这些信息随后进入坐标转换模块,最终生成飞行控制指令。理解这三要素的物理意义和数学表达,是正确配置检测参数、调试跟踪效果的前提。
4.1 目标检测核心概念与数学表达
目标检测任务可以形式化地描述为:给定输入图像 I ∈ RH×W×3,输出一组检测结果 D = {d1, d2, ..., dn},其中每个检测 di 包含三个组成部分:边界框 bi、类别标签 ci 和置信度分数 si。边界框通常采用两种表示方式:一种是 (x, y, w, h) 形式,其中 (x, y) 是框左上角坐标,w 和 h 是宽高;另一种是 (cx, cy, w, h) 形式,使用中心点坐标。在PX4项目中,我们更推荐使用中心点表示,因为它与后续的相机坐标系转换公式衔接更自然。
置信度分数 si 是一个介于0和1之间的实数,它综合反映了两个层面的信息:一是该边界框内确实存在目标的概率,二是该目标属于类别 ci 的概率。在YOLO系列算法中,置信度被定义为 Pr(Object) × IoUpredtruth,即目标存在概率与预测框和真实框交并比的乘积。实际使用中,我们通常设置一个置信度阈值(如0.5),低于该阈值的检测结果会被过滤掉,以减少误检。
4.2 常用目标检测算法对比与选型
当前主流的目标检测算法可以分为两大流派:两阶段检测器和单阶段检测器。两阶段检测器以Faster R-CNN为代表,先通过区域提议网络(RPN)生成候选区域,再对每个候选区域进行分类和回归。这种方法的优点是精度高,但速度较慢,难以满足无人机实时性要求。单阶段检测器则直接在特征图上预测边界框和类别,代表算法包括YOLO系列和SSD。
| 算法 | 速度 (FPS) | 精度 (mAP) | 模型大小 | 嵌入式部署难度 | 适用场景 |
|---|---|---|---|---|---|
| Faster R-CNN | 5~15 | 高 (75+) | 大 (>200MB) | 高 | 离线分析、高精度需求 |
| SSD | 30~60 | 中 (70~75) | 中 (100MB) | 中 | 实时检测、嵌入式设备 |
| YOLOv5s | 60~100 | 中高 (72~78) | 小 (14MB) | 低 | 无人机实时目标跟随 |
| YOLOv8s | 80~120 | 高 (78~82) | 小 (22MB) | 低 | 高性能实时检测 |
| YOLO-NAS | 70~100 | 高 (80+) | 中 (40MB) | 中 | 需要精度与速度平衡 |
在PX4目标跟随项目中,我们优先推荐YOLOv5s或YOLOv8s。原因有三:第一,模型体积小,适合部署在Jetson Nano、树莓派等嵌入式平台;第二,推理速度快,能够满足30FPS以上的实时处理需求;第三,PyTorch生态完善,可以方便地导出为ONNX格式,再通过OpenCV DNN模块或TensorRT进行加速推理。SSD虽然也是单阶段算法,但其在小型目标上的检测效果不如YOLO系列,且模型转换工具链相对复杂。
4.3 在ROS2中集成OpenCV与深度学习模型
ROS2(Robot Operating System 2)是PX4无人机常用的中间件框架。在ROS2中集成目标检测功能,需要完成三个层面的工作:图像采集与发布、检测节点实现、结果发布与可视化。下面给出一个完整的集成流程。
4.3.1 图像采集与话题通信
首先,我们需要从相机驱动节点获取图像话题。在PX4无人机中,通常使用 gscam 或 v4l2_camera 驱动USB摄像头,发布 sensor_msgs/msg/Image 类型的消息。对于树莓派CSI摄像头,可以使用 raspicam2 节点。图像话题名称一般为 /camera/image_raw。在检测节点中,我们订阅该话题,并将ROS图像消息转换为OpenCV的 cv::Mat 格式。
// ROS2 C++ 图像订阅与转换示例
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>
class DetectionNode : public rclcpp::Node {
public:
DetectionNode() : Node("detection_node") {
// 订阅图像话题
sub_ = this->create_subscription<sensor_msgs::msg::Image>(
"/camera/image_raw", 10,
std::bind(&DetectionNode::imageCallback, this, std::placeholders::_1));
}
private:
void imageCallback(const sensor_msgs::msg::Image::SharedPtr msg) {
// 将ROS图像消息转换为cv::Mat
cv::Mat frame = cv_bridge::toCvCopy(msg, "bgr8")->image;
// 在此处调用目标检测函数
detectObjects(frame);
}
void detectObjects(const cv::Mat& frame) {
// 检测逻辑将在下文实现
}
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
};
4.3.2 加载ONNX模型并执行推理
OpenCV 4.x及以上版本提供了 cv::dnn::Net 类,可以直接加载ONNX格式的深度学习模型。我们需要先将PyTorch训练的YOLO模型导出为ONNX格式。导出命令如下:
# 在YOLOv5仓库目录下执行
python export.py --weights yolov5s.pt --include onnx --img-size 640 640
# 导出后的文件为 yolov5s.onnx
在ROS2节点中加载ONNX模型并进行推理的代码如下:
#include <opencv2/dnn.hpp>
// 在类中添加成员变量
cv::dnn::Net net_;
// 构造函数中加载模型
DetectionNode() : Node("detection_node") {
// 加载ONNX模型
net_ = cv::dnn::readNetFromONNX("yolov5s.onnx");
// 设置计算后端,优先使用CUDA
net_.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
net_.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);
}
// 推理函数
void detectObjects(const cv::Mat& frame) {
// 预处理:缩放至640x640,归一化
cv::Mat blob = cv::dnn::blobFromImage(frame, 1/255.0,
cv::Size(640, 640),
cv::Scalar(0,0,0), true, false);
net_.setInput(blob);
// 前向推理
std::vector<cv::Mat> outputs;
net_.forward(outputs, net_.getUnconnectedOutLayersNames());
// 后处理:解析边界框、类别和置信度
// 具体解析逻辑取决于模型输出格式
// YOLOv5输出形状为 [1, 25200, 85]
// 其中85 = 5 (x,y,w,h,obj_conf) + 80 (COCO类别概率)
}
4.3.3 检测结果后处理与话题发布
模型输出的原始数据需要经过后处理才能得到可用的检测结果。后处理包括:置信度过滤、非极大值抑制(NMS)和坐标转换。处理后的结果可以封装为自定义消息或使用 vision_msgs/msg/Detection2DArray 标准消息发布。下面给出一个简化的后处理流程:
- 置信度过滤:遍历所有预测框,将目标置信度低于0.5的框剔除。
- 类别筛选:根据任务需求,只保留特定类别的目标(如person或car)。
- NMS处理:对同一类别的重叠框进行抑制,保留得分最高的框。
- 坐标转换:将640×640网络输入尺寸下的坐标映射回原始图像尺寸。
- 发布结果:将检测结果发布到
/detection/result话题,供后续跟踪节点订阅。
4.4 实战案例:基于YOLOv5s的行人检测节点
下面我们通过一个完整的案例,演示如何在ROS2中实现一个行人检测节点。该节点将用于PX4无人机对地面行人的目标跟随任务。
场景背景:无人机在10米高度悬停,需要检测并跟随地面移动的行人。机载平台为Jetson Nano,运行Ubuntu 20.04和ROS2 Foxy。摄像头为USB广角摄像头,分辨率为1280×720,发布频率30FPS。
初始现象:直接使用YOLOv5s原始模型进行推理,发现检测速度仅有12FPS,无法满足实时性要求。同时,由于广角镜头畸变,远处行人的检测框位置存在明显偏移。
分析与解决过程:
| 问题 | 原因分析 | 解决方案 | 效果 |
|---|---|---|---|
| 推理速度慢 | Jetson Nano GPU算力不足,FP32推理开销大 | 使用TensorRT进行FP16量化,输入尺寸降至416 | 推理速度提升至28FPS |
| 检测框偏移 | 广角镜头畸变未校正 | 使用OpenCV的 initUndistortRectifyMap 进行畸变校正 |
检测框位置误差降低60% |
| 远处行人漏检 | 小目标特征不明显 | 在检测前进行图像增强,提高对比度 | 10米外行人检测率从45%提升至78% |
关键代码实现:在ROS2节点中集成TensorRT推理,核心代码如下:
// 使用TensorRT加载FP16引擎
cv::dnn::Net net = cv::dnn::readNetFromTensorRT("yolov5s_fp16.engine");
net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);
// 畸变校正参数(通过相机标定获得)
cv::Mat cameraMatrix = (cv::Mat_<double>(3,3) <<
800, 0, 640, 0, 800, 360, 0, 0, 1);
cv::Mat distCoeffs = (cv::Mat_<double>(1,5) <<
-0.32, 0.12, 0.001, -0.002, 0.05);
// 在图像回调中先校正畸变
cv::Mat undistorted;
cv::undistort(frame, undistorted, cameraMatrix, distCoeffs);
结果对比:优化前,系统延迟约180ms(从图像采集到检测输出),优化后延迟降至65ms。在10米距离下,行人检测的置信度从0.62提升至0.81,边界框中心点误差从35像素降至12像素。这为后续的坐标转换和飞行控制提供了更可靠的数据源。
4.5 常见误区与调试经验
在目标检测集成过程中,开发者常遇到以下几类问题,需要特别注意:
- 误区一:忽略图像预处理一致性。训练时使用RGB格式和特定归一化参数,部署时却使用BGR格式或不同的缩放因子,导致检测精度大幅下降。解决方法是严格保持训练和推理时的预处理一致。
- 误区二:NMS阈值设置不当。IoU阈值过高(如0.9)会导致同一目标产生多个检测框,过低(如0.3)则会合并相邻的不同目标。建议从0.5开始调试,根据实际效果微调。
- 误区三:忽视相机延迟。ROS2图像话题的传输延迟加上推理延迟,可能导致检测结果滞后于实际目标位置。在高速运动场景下,需要引入卡尔曼滤波或运动补偿。
- 误区四:模型类别与任务不匹配。使用COCO预训练模型检测特定目标(如无人机),效果往往不佳。建议使用迁移学习,在自有数据集上微调模型。
调试建议:在开发阶段,建议在检测节点中增加可视化输出,将检测框绘制在图像上并通过 rqt_image_view 实时查看。同时,记录检测结果的置信度分布直方图,帮助判断阈值设置是否合理。当检测速度不达标时,优先检查是否启用了GPU加速,以及输入图像尺寸是否过大。