上一教程: OpenCV 与 OpenVINO 的使用
下一教程: 如何在浏览器中运行深度神经网络
| |
原作者 | Alessandro de Oliveira Faria |
扩展者 | Abduragim Shtanchaev |
兼容性 | OpenCV >= 4.9.0 |
在 OpenCV 中运行预训练的 YOLO 模型
部署预训练模型是机器学习中的一项常见任务,尤其是在使用不支持 PyTorch 等某些框架的硬件时。本指南全面概述了如何从 PyTorch 导出预训练的 YOLO 系列模型,并使用 OpenCV 的 DNN 框架进行部署。为了演示,我们将重点介绍 YOLOX 模型,但该方法也适用于其他受支持的模型。
- 注意
- 目前,OpenCV 支持以下 YOLO 模型:
此支持包括特定于这些模型的预处理和后处理程序。虽然 OpenCV 也支持 Darknet 格式的其他旧版 YOLO 模型,但它们不在本教程的范围之内。
假设我们已成功训练 YOLOX 模型,下一步涉及使用 OpenCV 导出和运行此模型。在继续此过程之前,需要考虑几个关键因素。让我们深入探讨这些方面。
YOLO 的预处理和输出
了解与 YOLO 系列检测器相关的输入和输出的性质至关重要。这些检测器与大多数深度神经网络 (DNN) 类似,其输入大小通常会根据模型的规模而有所不同。
模型规模 | 输入大小 |
小型模型 1 | 416x416 |
中型模型 2 | 640x640 |
大型模型 3 | 1280x1280 |
此表提供了快速参考,用于了解各种 YOLO 模型输入中常用的不同输入维度。这些是标准输入形状。如果训练模型的输入大小与表中提到的大小不同,请确保使用训练模型的输入大小。
此过程中下一个关键要素是了解 YOLO 检测器的图像预处理细节。虽然 YOLO 系列的基本预处理方法保持一致,但仍存在细微但至关重要的差异,必须加以考虑以避免性能下降。其中关键的是应用后调整大小的resize 类型
和填充值
。例如,YOLOX 模型 使用LetterBox
调整大小方法和114.0
的填充值。务必确保这些参数以及归一化常数与要导出的模型相匹配。
关于模型的输出,它通常采用 [BxNxC+5] 或 [BxNxC+4] 维度的张量形式,其中“B”表示批大小,“N”表示锚点的数量,“C”表示类的数量(例如,如果模型在 COCO 数据集上进行训练,则为 80 个类)。前一个张量结构中的额外 5 对应于目标得分 (obj)、置信度得分 (conf) 和边界框坐标 (cx, cy, w, h)。值得注意的是,YOLOv8 模型的输出形状为 [BxNxC+4],其中没有明确的目标得分,目标得分直接从类得分推断。特别是对于 YOLOX 模型,还需要结合锚点才能将预测重新缩放回图像域。此步骤将集成到 ONNX 图中,我们将在后续部分详细介绍此过程。
PyTorch 模型导出
既然我们已经了解了预处理的参数,我们就可以继续将模型从 Pytorch 导出到 ONNX 图。由于本教程使用 YOLOX 作为示例模型,因此让我们使用它的导出进行演示(除了YOLOv10
模型外,其余 YOLO 检测器的过程相同,稍后将在文章中详细介绍如何导出它)。要导出 YOLOX,我们只需使用 导出脚本。特别是我们需要以下命令:
git clone https://github.com/Megvii-BaseDetection/YOLOX.git
cd YOLOX
wget https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/yolox_s.pth # 下载预训练权重
python3 -m tools.export_onnx --output-name yolox_s.onnx -n yolox-s -c yolox_s.pth --decode_in_inference
注意: 这里的--decode_in_inference
用于在 ONNX 图本身中包含锚框创建。它将 此值 设置为True
,随后包含锚点生成函数。
下面我们演示了导出脚本的最小版本(可用于除 YOLOX 之外的其他模型),以备需要时使用。但是,通常每个 YOLO 存储库都有预定义的导出脚本。
import onnx
import torch
from onnxsim import simplify
ckpt = torch.load(ckpt_file, map_location="cpu")
model.load_state_dict(ckpt)
dummy_input = torch.randn(args.batch_size, 3, exp.test_size[0], exp.test_size[1])
torch.onnx._export(
model,
dummy_input,
"yolox.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={"input": {0: 'batch'},
"output": {0: 'batch'}})
onnx_model = onnx.load(args.output_name)
model_simp, check = simplify(onnx_model)
assert check, "简化的 ONNX 模型无法验证"
onnx.save(model_simp, args.output_name)
导出 YOLOv10 模型
为了运行 YOLOv10,需要从 torch 中去除动态形状的后处理,然后将其转换为 ONNX。如果您正在寻找如何去除后处理的方法,可以参考 YOLOv10 的这个 分支版本。该分支通过 返回模型在后处理过程之前的输出 来去除后处理。要将 torch 模型转换为 ONNX,请按照以下步骤操作。
conda create -n yolov10 python=3.9
conda activate yolov10
pip install -r requirements.txt
python export_opencv.py --model=<模型名称> --imgsz=<输入图像大小>
默认情况下,--model="yolov10s"
和 --imgsz=(480,640)
。这将生成文件 yolov10s.onnx
,该文件可用于 OpenCV 中的推理。
使用 OpenCV 示例运行 Yolo ONNX 检测器
一旦我们有了模型的 ONNX 图,就可以简单地使用 OpenCV 的示例运行它。为此,我们需要确保:
- OpenCV 使用 -DBUILD_EXAMLES=ON 标记构建。
- 导航到 OpenCV 的
build
目录
- 运行以下命令:
./bin/example_dnn_yolo_detector --input=<输入文件路径> \
--classes=<类名文件路径> \
--thr=<置信度阈值> \
--nms=<非最大抑制阈值> \
--mean=<均值归一化值> \
--scale=<缩放因子> \
--yolo=<yolo 模型版本> \
--padvalue=<填充值> \
--paddingmode=<填充模式> \
--backend=<计算后端> \
--target=<目标计算设备> \
--width=<模型输入宽度> \
--height=<模型输入高度> \
- –input: 输入图像或视频的文件路径。如果省略,则将从摄像头捕获帧。
- –classes: 包含目标检测类名的文本文件的路径。
- –thr: 检测的置信度阈值(例如,0.5)。
- –nms: 非最大抑制阈值(例如,0.4)。
- –mean: 均值归一化值(例如,0.0 表示无均值归一化)。
- –scale: 输入归一化的缩放因子(例如,1.0、1/255.0 等)。
- –yolo: YOLO 模型版本(例如,YOLOv3、YOLOv4 等)。
- –padvalue: 预处理中使用的填充值(例如,114.0)。
- –paddingmode: 处理图像调整大小和填充的方法。选项:0(调整大小无需额外处理)、1(调整大小后裁剪)、2(保持纵横比调整大小)。
- –backend: 计算后端的选项(0 为自动,1 为 Halide,2 为 OpenVINO 等)。
- –target: 目标计算设备的选项(0 为 CPU,1 为 OpenCL 等)。
- –device: 摄像头设备编号(0 为默认摄像头)。如果未提供
--input
,则默认使用索引为 0 的摄像头。
- –width: 模型输入宽度。不要与图像宽度混淆。(例如,416、480、640、1280 等)。
- –height: 模型输入高度。不要与图像高度混淆。(例如,416、480、640、1280 等)。
此处 mean
、scale
、padvalue
、paddingmode
应与我们在预处理部分讨论的内容完全匹配,以便模型与 PyTorch 中的结果匹配。
要演示如何在没有您自己的预训练模型的情况下运行 OpenCV YOLO 示例,请按照以下说明操作:
- 确保您的平台上已安装 Python。
- 确认 OpenCV 使用
-DBUILD_EXAMPLES=ON
标记构建。
运行 YOLOX 检测器(使用默认值)
git clone https://github.com/opencv/opencv_extra.git
cd opencv_extra/testdata/dnn
python download_models.py yolox_s_inf_decoder
cd ..
export OPENCV_TEST_DATA_PATH=$(pwd)
cd <OpenCV 的构建目录>
./bin/example_dnn_yolo_detector
这将使用您的摄像头执行 YOLOX 检测器。对于 YOLOv8(例如),请执行以下额外步骤:
cd opencv_extra/testdata/dnn
python download_models.py yolov8
cd ..
export OPENCV_TEST_DATA_PATH=$(pwd)
cd <OpenCV 的构建目录>
./bin/example_dnn_yolo_detector --model=onnx/models/yolov8n.onnx --yolo=yolov8 --mean=0.0 --scale=0.003921568627 --paddingmode=2 --padvalue=144.0 --thr=0.5 --nms=0.4 --rgb=0
对于 YOLOv10,请按照以下步骤操作:
cd opencv_extra/testdata/dnn
python download_models.py yolov10
cd ..
export OPENCV_TEST_DATA_PATH=$(pwd)
cd <OpenCV 的构建目录>
./bin/example_dnn_yolo_detector --model=onnx/models/yolov10s.onnx --yolo=yolov10 --width=640 --height=480 --scale=0.003921568627 --padvalue=114
这将在您的系统上找到的第一个摄像头运行 YOLOv10
检测器。如果您想在图像/视频文件上运行它,可以使用 --input
选项指定文件的路径。
视频演示
构建自定义管道
有时需要对推理管道进行一些自定义调整。使用 OpenCV DNN 模块,这也很容易实现。下面我们将概述示例实现细节。
#include <fstream>
#include <sstream>
#include "iostream"
#include "common.hpp"
int backend = parser.get<
int>(
"backend");
float paddingValue = parser.get<float>("padvalue");
bool swapRB = parser.get<bool>("rgb");
int inpWidth = parser.get<int>("width");
int inpHeight = parser.get<int>("height");
ImagePaddingMode paddingMode = static_cast<ImagePaddingMode>(parser.get<int>("paddingmode"));
scale,
size,
mean,
swapRB,
DNN_LAYOUT_NCHW,
paddingMode,
paddingValue);
std::vector<Mat> outs;
std::vector<int> keep_classIds;
std::vector<float> keep_confidences;
std::vector<Rect2d> keep_boxes;
std::vector<Rect> boxes;
所有后处理步骤都在函数yoloPostProcess
中实现。请注意,NMS步骤不包含在onnx图中。示例使用OpenCV函数来实现它。
yoloPostProcessing(
outs, keep_classIds, keep_confidences, keep_boxes,
confThreshold, nmsThreshold,
yolo_model,
nc);
for (auto box : keep_boxes)
{
}
for (size_t idx = 0; idx < boxes.size(); ++idx)
{
drawPrediction(keep_classIds[idx], keep_confidences[idx], box.
x, box.
y,
}
const std::string kWinName = "Yolo目标检测器";