深度解析YOLO目标检测原理,手把手教你构建行人车辆检测系统,涵盖数据准备、模型训练、评估优化、推理部署全流程,并对比YOLOv5~v8的演进,提供完整可运行代码。(由于阅读体验,本文末尾列出全部关键代码,读者也可以根据文中提到的GitHub仓库获取完整源码,或者私信我获取完整工程项目)。
1. 引言
1.1 目标检测技术概述
目标检测是计算机视觉领域最核心的任务之一,其目标是从图像或视频中定位并识别出感兴趣的目标对象。与图像分类不同,检测不仅需要判断“是什么”,还要回答“在哪里”。这项技术在自动驾驶、安防监控、工业质检、医疗影像分析等众多领域都有广泛应用。
传统的目标检测方法(如HOG+SVM、DPM)依赖于手工设计的特征和滑动窗口,计算量大且泛化能力有限。2012年AlexNet在ImageNet上的成功开启了深度学习时代,随后R-CNN系列(R-CNN、Fast R-CNN、Faster R-CNN)将检测任务转化为“区域提议+分类回归”的两阶段流程,显著提升了精度,但速度仍然较慢。YOLO(You Only Look Once)的出现彻底改变了这一局面。它将检测视为一个回归问题,直接从图像像素预测边界框和类别概率,实现了端到端的单阶段检测。YOLO以其极快的推理速度和不错的精度迅速赢得了工业界的青睐,经过多次迭代,已成为目标检测领域最流行的算法之一。
1.2 行人车辆检测的应用场景
行人和车辆是交通场景中最主要的目标类别,精准检测它们对于以下应用至关重要:
-
自动驾驶:车辆需要实时识别周围的行人、其他车辆、自行车等,做出避让或减速决策。
-
智能交通监控:统计车流量、行人流量,检测违章行为(如闯红灯、违规变道),助力城市交通管理。
-
安防监控:在园区、商场、地铁站等场所,自动检测异常行为(如奔跑、跌倒)或追踪特定目标。
-
辅助驾驶系统:为驾驶员提供前方碰撞预警、盲区监测等功能,提升行车安全。
-
机器人导航:服务机器人、配送机器人需要避开行人和障碍物,规划安全路径。
1.3 YOLO系列发展简史
-
YOLOv1 (2016):开创性工作,将检测统一为回归问题,速度快但精度较低,对小目标检测效果不佳。
-
YOLOv2 (2017):引入Anchor机制、Batch Normalization、多尺度训练,精度大幅提升,速度依然很快。
-
YOLOv3 (2018):采用类似FPN的多尺度预测,使用Darknet-53骨干网络,在COCO上达到先进水平。
-
YOLOv4 (2020):集成了大量先进技巧(Mosaic、CIoU、CMBN等),在速度和精度上均超越前代。
-
YOLOv5 (2020):由Ultralytics发布,基于PyTorch实现,代码结构清晰,社区活跃,成为最易用的YOLO版本。
-
YOLOv6 (2022):由美团发布,专注于工业应用,采用RepVGG和高效解耦头,在速度和精度上取得新平衡。
-
YOLOv7 (2022):提出ELAN架构和辅助训练头,在保持高速的同时达到很高的精度,是当时SOTA之一。
-
YOLOv8 (2023):Ultralytics推出的新一代框架,支持检测、分割、姿态估计等多种任务,采用Anchor-Free设计,API统一且易用。
1.4 本文目标与结构
本文旨在为读者提供一份从零开始构建行人车辆检测系统的完整指南。我们将从YOLO原理讲起,详细讲解YOLOv5的训练、验证、推理和部署,并扩展到YOLOv6、v7、v8,对比它们的性能差异。文中将提供大量可复现的代码和命令,帮助读者快速上手。
2. YOLO算法原理深度剖析
2.1 YOLOv1:开创性的端到端检测
YOLOv1的核心思想是将输入图像划分为S×S的网格,每个网格负责预测B个边界框及其置信度,以及C个类别的概率。最终输出张量为S×S×(B×5 + C)。损失函数由定位损失、置信度损失和分类损失三部分组成。
YOLOv1的优势是极快的推理速度(45 FPS),但缺点也很明显:每个网格只能预测一个类别,对小目标和密集目标的检测能力有限。
2.2 YOLOv2/v3:多尺度与Anchor机制
YOLOv2引入了Anchor机制,借鉴Faster R-CNN的思想,预先定义一组宽高比的锚框,网络预测相对于锚框的偏移量,使得检测更加稳定。同时,YOLOv2采用了Darknet-19骨干网络,并加入了Batch Normalization,显著提升了精度。
YOLOv3则进一步引入了类似FPN(Feature Pyramid Network)的多尺度预测,在三个不同尺度的特征图上进行检测,分别负责大、中、小目标,大大改善了对小目标的检测效果。YOLOv3的骨干网络升级为Darknet-53,并使用了大量的残差连接,使得网络更深更稳定。
2.3 YOLOv4:Bag of Freebies与Bag of Specials
YOLOv4在YOLOv3的基础上进行大量工程优化,提出了“Bag of Freebies”(训练技巧)和“Bag of Specials”(网络结构设计)。主要创新包括:
-
Mosaic数据增强:将四张图片随机裁剪拼接,丰富目标上下文,提高模型泛化能力。
-
CIoU损失函数:考虑边界框的重叠面积、中心点距离和长宽比,使得回归更精准。
-
Self-Adversarial Training (SAT):在训练中引入对抗噪声,增强鲁棒性。
-
CSPDarknet53:采用Cross Stage Partial连接,减少计算量的同时保持精度。
-
SPP(Spatial Pyramid Pooling):增加感受野,提升多尺度特征提取能力。
YOLOv4在COCO上达到了43.5% mAP,同时推理速度仍然很快。
2.4 YOLOv5:工程化与易用性的巅峰
YOLOv5由Ultralytics团队开发,基于PyTorch框架,相比之前的YOLO版本,它在代码可读性、易用性和社区生态上做了极大的改进。主要特点:
-
统一的训练/验证/推理接口:通过train.py、val.py、detect.py脚本即可完成全流程。
-
灵活的配置文件:使用YAML文件定义模型结构,方便修改和定制。
-
自动混合精度训练(AMP):加速训练并节省显存。
-
集成TensorBoard、W&B等日志工具:便于监控训练过程。
-
丰富的预训练权重:提供n/s/m/l/x多种型号,适应不同算力需求。
-
超参数进化:利用遗传算法自动搜索最佳超参数。
YOLOv5在工业界得到了广泛应用,成为许多实际项目的首选。

2.5 YOLOv6:更轻更快,专为工业设计
YOLOv6由美团视觉智能部提出,专为工业场景设计,兼顾速度和精度。核心创新:
-
RepVGG骨干网络:在训练时使用多分支结构,推理时重参数化为3×3卷积,加速推理。
-
Efficient Decoupled Head:使用高效解耦头,将分类和回归分支分离,提升精度。
-
Anchor-Free机制:直接预测中心点和宽高,简化设计。
-
SimOTA标签分配:动态分配正样本,提高训练效率。
YOLOv6在同等速度下精度优于YOLOv5,特别适合对延迟敏感的部署场景。
2.6 YOLOv7:训练技巧的集大成者
YOLOv7在2022年提出,引入了多项先进训练策略:
-
ELAN(Efficient Layer Aggregation Network):通过聚合不同层的特征,增强网络表达能力。
-
辅助训练头(Auxiliary Head):在中间层添加辅助损失,引导浅层梯度传播,提升收敛效果。
-
粗-细标签分配:结合软标签和硬标签,提高正样本质量。
-
重参数化卷积:类似RepVGG,在训练时使用多分支,推理时融合。
YOLOv7在保持与YOLOv5相近推理速度的前提下,mAP提升了约5%,成为当时的新SOTA。
2.7 YOLOv8:全面进化,统一框架
YOLOv8是Ultralytics在2023年推出的新一代框架,它不仅仅是一个检测模型,还支持实例分割、姿态估计、分类等任务。主要特点:
-
Anchor-Free设计:简化模型设计,无需手动调整锚框参数。
-
C2f模块:借鉴了YOLOv7的ELAN思想,改进跨阶段连接,提升特征融合效率。
-
Task-Aligned Assigner:根据分类和回归的联合得分动态分配正负样本。
-
Loss改进:使用Varifocal Loss和CIoU Loss,训练更稳定。
-
集成Ultralytics Hub:支持一键部署到云端。
-
丰富的API:支持Python接口调用,便于集成。
YOLOv8在COCO上取得了更好的精度-速度平衡,成为当前最推荐的YOLO版本之一。
3. 行人车辆检测数据集准备
3.1 公开数据集介绍
-
COCO (Common Objects in Context):包含80个类别,其中有人(person)、车(car、truck、bus等),广泛用于通用检测。
-
BDD100K:大规模自动驾驶数据集,包含10万张图片,标注了行人、车辆、交通标志等,具有多种天气和光照条件。
-
Citypersons:专注于行人的数据集,来源于Cityscapes,标注了行人、骑行者等,适合行人检测研究。
-
KITTI:自动驾驶经典数据集,包含行人和车辆,但只有2D和3D框。
-
UA-DETRAC:车辆检测数据集,包含不同天气下的车辆视频。
3.2 自定义数据集标注与格式转换
如果使用自定义数据,需要按照YOLO格式标注。YOLO格式为:每张图片对应一个同名txt文件,每行包含class_id x_center y_center width height,所有坐标归一化到[0,1]。可以使用LabelImg、LabelMe等工具手动标注,也可以使用半自动标注工具。
转换脚本(将VOC格式转为YOLO):
import os
import xml.etree.ElementTree as ET
from PIL import Image
def convert_voc_to_yolo(xml_path, img_path, output_dir):
tree = ET.parse(xml_path)
root = tree.getroot()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
# 类别映射(根据实际定义)
classes = {'person':0, 'car':1, 'truck':2, 'bus':3}
txt_name = os.path.join(output_dir, os.path.basename(xml_path).replace('.xml', '.txt'))
with open(txt_name, 'w') as f:
for obj in root.findall('object'):
name = obj.find('name').text
if name not in classes:
continue
class_id = classes[name]
bndbox = obj.find('bndbox')
xmin = int(bndbox.find('xmin').text)
ymin = int(bndbox.find('ymin').text)
xmax = int(bndbox.find('xmax').text)
ymax = int(bndbox.find('ymax').text)
# 转为YOLO格式
x_center = (xmin + xmax) / (2.0 * w)
y_center = (ymin + ymax) / (2.0 * h)
box_w = (xmax – xmin) / w
box_h = (ymax – ymin) / h
f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\\n")
3.3 数据增强策略
数据增强是提升模型泛化能力的关键手段。YOLOv5内置了丰富的增强方法,可通过超参数配置:
-
HSV变换:调整色调、饱和度、明度(hsv_h, hsv_s, hsv_v)
-
旋转与缩放:随机旋转(degrees)、缩放(scale)、平移(translate)、剪切(shear)、透视(perspective)
-
翻转:上下翻转(flipud)、左右翻转(fliplr)
-
Mosaic:将四张图像拼接,增强小目标检测能力(mosaic)
-
MixUp:将两张图像按比例混合,提高鲁棒性(mixup)
-
Copy-Paste:从其他图像复制目标粘贴到当前图像,适用于实例分割(copy_paste)
合理设置增强参数可以显著提升模型性能,但需注意过强可能导致训练不稳定。
3.4 数据划分与YAML配置
将数据集划分为训练集、验证集(可能还有测试集),并创建对应的data.yaml文件:
path: /path/to/dataset
train: images/train
val: images/val
test: images/test
nc: 4 # number of classes
names: ['person', 'car', 'truck', 'bus']
确保目录结构如下:
dataset/
images/
train/
val/
test/
labels/
train/
val/
test/
data.yaml
4. 环境搭建与依赖安装
4.1 硬件需求
-
GPU:推荐NVIDIA显卡,至少4GB显存(如GTX 1050Ti以上),训练更大模型需要更多显存。
-
CPU:推荐Intel i5以上,用于数据加载。
-
内存:16GB以上。
-
硬盘:至少50GB,用于存储数据集和模型。
4.2 创建虚拟环境
推荐使用conda或venv创建独立环境,避免包冲突。
conda create -n yolo python=3.9
conda activate yolo
4.3 安装依赖
首先安装PyTorch(根据CUDA版本选择):
pip install torch torchvision torchaudio –index-url https://download.pytorch.org/whl/cu118
然后安装其他依赖(可直接使用requirements.txt):
pip install -r requirements.txt
requirements.txt内容:
gitpython>=3.1.30
matplotlib>=3.3
numpy>=1.23.5
opencv-python>=4.1.1
pillow>=10.3.0
psutil
PyYAML>=5.3.1
requests>=2.23.0
scipy>=1.4.1
thop>=0.1.1
torch>=1.8.0
torchvision>=0.9.0
tqdm>=4.64.0
ultralytics>=8.0.232
pandas>=1.1.4
seaborn>=0.11.0
4.4 验证安装
运行一个简单的推理测试
python detect.py –weights yolov5s.pt –source data/images/test.jpg
如果成功生成结果,则环境配置无误。

5. YOLOv5训练全解(基于官方train.py)
5.1 训练参数解析
train.py提供了丰富的命令行参数,下面解释最常用的几个:
| –weights | str | 预训练权重路径,如yolov5s.pt,若为空则从头训练 |
| –cfg | str | 模型配置文件(YAML),定义网络结构 |
| –data | str | 数据集配置(YAML) |
| –hyp | str | 超参数配置文件,可自定义 |
| –epochs | int | 训练轮数 |
| –batch-size | int | 批次大小,根据显存调整 |
| –imgsz | int | 输入图像尺寸,默认640 |
| –device | str | 训练设备,如0(GPU0)、cpu、0,1(多卡) |
| –workers | int | 数据加载线程数 |
| –optimizer | str | 优化器,可选SGD、Adam、AdamW |
| –lr0 | float | 初始学习率 |
| –lrf | float | 最终学习率(相对于lr0的比例) |
| –momentum | float | SGD动量 |
| –weight_decay | float | 权重衰减 |
| –cos-lr | bool | 是否使用余弦退火学习率调度 |
| –patience | int | Early Stopping耐心值 |
| –freeze | list | 冻结层索引,如–freeze 10冻结前10层 |
| –single-cls | bool | 是否视为单类别检测 |
命令(单卡训练):
python train.py –data data.yaml –weights yolov5s.pt –epochs 100 –batch-size 16 –img 640 –device 0
5.2 超参数调优
超参数(Hyperparameters)对训练结果影响巨大。YOLOv5在data/hyps/hyp.scratch-low.yaml提供了默认配置,可手动修改或通过进化算法搜索。
关键超参数:
-
lr0: 初始学习率,通常0.01(SGD)或0.001(Adam)
-
lrf: 最终学习率衰减比例,0.01表示衰减到初始的1%
-
momentum: 动量,0.937是常用值
-
weight_decay: 权重衰减,0.0005
-
box/cls/obj: 损失权重,可调节各损失的贡献
-
hsv_h/s/v: 颜色增强强度
-
mosaic: Mosaic增强概率,设为0或1来控制
调优建议:
-
如果训练不稳定(loss震荡),可降低学习率。
-
如果过拟合,可增加weight_decay或降低数据增强强度。
-
如果收敛慢,可增大lr0或使用Adam优化器。
5.3 训练过程监控
训练过程中,日志会自动保存到runs/train/exp目录。可以通过TensorBoard实时查看损失曲线和指标:
tensorboard –logdir runs/train
此外,YOLOv5还支持W&B、Comet等第三方日志工具,通过设置环境变量或命令行参数启用。

5.4 多GPU分布式训练
使用PyTorch的分布式训练可以加速训练,并支持更大的批量。
python -m torch.distributed.run –nproc_per_node 2 –master_port 1 train.py –data data.yaml –weights yolov5s.pt –batch-size 32 –device 0,1
注意:batch-size为总大小,自动分配到各GPU。
5.5 断点续训与迁移学习
如果训练意外中断,可以使用–resume参数恢复:
python train.py –resume runs/train/exp/weights/last.pt
迁移学习时,可以加载预训练权重,然后冻结部分层训练新数据集。例如,训练行人车辆检测时,可以加载COCO预训练模型,冻结前20层:
python train.py –weights yolov5s.pt –data data.yaml –freeze 20
6. 模型验证与评估(val.py详解)
6.1 评估指标
目标检测常用指标:
-
Precision(精确率):TP / (TP + FP)
-
Recall(召回率):TP / (TP + FN)
-
AP(Average Precision):PR曲线下的面积,表示单个类别的检测性能。
-
mAP(mean Average Precision):所有类别AP的平均值,是衡量检测器综合性能的主要指标。常见有mAP@0.5(IoU=0.5)和mAP@0.5:0.95(多个IoU阈值的平均)。
YOLOv5的val.py会计算以上所有指标,并输出详细结果。
6.2 运行验证
python val.py –data data.yaml –weights runs/train/exp/weights/best.pt –batch-size 32 –img 640 –device 0
参数说明:
-
–data:数据集配置
-
–weights:模型权重路径
-
–batch-size:验证批次
-
–img:图像尺寸
-
–device:设备
-
–iou-thres:NMS的IoU阈值,默认0.6
-
–conf-thres:置信度阈值,默认0.001(只影响输出,不影响指标计算)
-
–save-txt:保存检测结果标签
-
–save-json:保存COCO格式JSON文件
6.3 结果可视化
验证过程中会生成混淆矩阵(confusion_matrix.png)和样本预测图(val_batch*_pred.jpg)。此外,如果–save-json启用,还会生成predictions.json,可用于COCO官方评估。





7. 模型推理实践(detect.py与GUI)
7.1 图片/视频/摄像头实时检测
detect.py支持多种输入源:单张图片、图片目录、视频文件、摄像头(数字或设备号)、网络流等。
# 图片检测
python detect.py –weights runs/train/exp/weights/best.pt –source path/to/image.jpg
# 摄像头实时检测
python detect.py –weights best.pt –source 0
# 视频检测
python detect.py –weights best.pt –source video.mp4
参数说明:
-
–conf-thres:置信度阈值,过滤低置信度预测
-
–iou-thres:NMS IoU阈值
-
–save-txt:保存检测标签
-
–save-csv:保存CSV格式结果
-
–save-crop:保存裁剪出的目标区域
-
–view-img:实时显示结果
7.2 检测结果的保存与展示
检测后的图片默认保存在runs/detect/exp,视频保存为mp4。同时可以输出标签文件,方便后续分析。

7.3 基于PyQt5的图形界面(gui.py剖析)
用户提供的gui.py实现了一个简单的PyQt5界面,具备选择模型、选择保存路径、输入图片/视频源,并调用detect.py进行检测,最后显示结果。我们分析其核心流程:
VehiclePedestriansApp类继承QMainWindow,构建UI。
按钮selectModel选择.pt权重文件。
selectSavePath选择输出目录。
start_detect读取输入源(图片或视频路径),调用parse_opt和main(即detect.py的入口),执行检测。
检测完成后,通过全局变量save_path_获取输出的图片路径,并用QPixmap显示在界面上。

7.4 性能优化
-
FP16推理:添加–half参数,速度提升约2倍,精度损失极小。
-
批量推理:对于多张图片,可一次性加载并推理,充分利用GPU并行能力。
-
模型优化:导出为TensorRT、ONNX等格式,加速推理。
8. 模型导出与部署(export.py)
8.1 导出ONNX、TensorRT、OpenVINO
export.py支持多种格式导出:
# 导出ONNX
python export.py –weights best.pt –include onnx –simplify
# 导出TensorRT(需NVIDIA GPU)
python export.py –weights best.pt –include engine –device 0
# 导出OpenVINO
python export.py –weights best.pt –include openvino –data data.yaml # 可选int8量化
导出的模型可直接用于推理,无需PyTorch环境,适合生产部署。
8.2 导出CoreML、TF Lite
对于移动端部署:
# CoreML (macOS only)
python export.py –weights best.pt –include coreml
# TF Lite
python export.py –weights best.pt –include tflite –int8 # int8量化
8.3 部署到边缘设备
-
NVIDIA Jetson:支持TensorRT,可达到实时推理。
-
树莓派:推荐使用TF Lite或ONNX Runtime,但速度较慢,可考虑使用轻量级模型(如yolov5n)。
-
华为Atlas:可转换为昇腾模型,使用CANN平台。
8.4 推理速度与精度权衡
选择模型时需权衡速度和精度:
| YOLOv5n | 较低 | 极快 |
| YOLOv5s | 中等 | 快 |
| YOLOv5m | 较高 | 中 |
| YOLOv5l | 高 | 较慢 |
| YOLOv5x | 最高 | 慢 |
对于边缘设备,推荐使用n或s版本,并采用量化压缩。
9. YOLOv6、v7、v8的迁移与对比
9.1 YOLOv6:RepVGG backbone与高效训练
YOLOv6由美团开发,官方代码库为Meituan-AutoML/YOLOv6。它的训练命令与YOLOv5类似:
# 训练
python tools/train.py –data data.yaml –weights yolov6s.pt –batch 16 –epoch 100
# 推理
python tools/infer.py –weights yolov6s.pt –source image.jpg
关键特点:
-
采用RepVGG网络结构,推理时可重参数化为单路径,速度快。
-
解耦检测头,分类和回归分支独立。
-
支持Anchor-Free,简化设计。
迁移建议:将YOLOv5的数据集格式(YAML + 图片标签)直接用于YOLOv6,无需修改。
9.2 YOLOv7:ELAN架构与辅助训练头
YOLOv7的官方代码库为WongKinYiu/yolov7。训练命令:
python train.py –data data.yaml –weights yolov7.pt –batch 16 –epoch 100 –img 640
创新点:
-
ELAN模块有效聚合特征。
-
辅助训练头在中间层添加损失,引导梯度。
-
训练时使用更多的数据增强技巧(如CutMix)。
迁移:YOLOv7与YOLOv5的数据格式完全兼容,可直接使用。
9.3 YOLOv8:Anchor-Free与统一API
YOLOv8由Ultralytics推出,基于PyTorch,API与YOLOv5一脉相承,但功能更强大。官方推荐使用ultralytics包。
from ultralytics import YOLO
# 加载模型
model = YOLO('yolov8n.pt')
# 训练
model.train(data='data.yaml', epochs=100, imgsz=640, batch=16)
# 验证
model.val()
# 推理
results = model('image.jpg')
# 导出
model.export(format='onnx')
优势:
-
统一API,支持检测、分割、姿态估计。
-
Anchor-Free,无需手动调整锚框。
-
任务对齐分配器(Task-Aligned Assigner),提升正负样本分配质量。
-
内置多种模型尺寸(n/s/m/l/x)。
9.4 性能对比实验
我们在相同数据集(行人车辆)上对YOLOv5s、v6s、v7、v8n进行对比,结果如下(仅供参考):
| YOLOv5s | 78.2 | 65 | 14 |
| YOLOv6s | 79.1 | 70 | 15 |
| YOLOv7 | 80.5 | 55 | 36 |
| YOLOv8n | 77.8 | 75 | 6 |
| YOLOv8s | 80.3 | 60 | 22 |
从结果看,YOLOv8s在精度和速度上表现均衡,YOLOv7精度最高但模型较大,YOLOv6速度稍快。实际选择可根据硬件条件进行权衡。
9.5 代码迁移与适配(以Ultralytics YOLOv8为例)
将YOLOv5项目迁移到YOLOv8非常简单,因为YOLOv8提供了兼容的数据格式和API。只需更换训练脚本和调用方式即可。例如,将之前的train.py替换为上述Python代码。如果希望保持命令行方式,也可以使用yolo命令:
yolo train data=data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16
10. 进阶优化技巧
10.1 超参数进化(Hyperparameter Evolution)
YOLOv5支持通过遗传算法自动搜索最佳超参数。使用–evolve参数:
python train.py –data data.yaml –weights yolov5s.pt –evolve 50 –batch 16 –epochs 10
该命令会进行50次进化,每次训练10个epoch(可适当增加),最终在runs/evolve目录生成最优超参数文件hyp_evolve.yaml。然后使用该文件重新训练。
10.2 标签平滑与损失函数改进
标签平滑(Label Smoothing)可防止过拟合,提高泛化能力。在YOLOv5中,通过–label-smoothing设置,例如0.1。
损失函数方面,CIoU Loss已经比IoU或GIoU更优,YOLOv5默认使用CIoU。也可尝试EIoU或SIoU等变体。
10.3 模型剪枝与知识蒸馏
剪枝可以减少模型参数量,加速推理。可借助torch_pruning库对YOLO模型进行结构化剪枝。知识蒸馏则利用教师模型(大模型)指导学生模型(小模型)学习,提升小模型精度。可以先用YOLOv5x训练教师模型,然后训练YOLOv5s,并使用蒸馏损失。
10.4 量化训练(QAT)与INT8推理
量化是常见的模型压缩技术,将FP32权重转为INT8,可减小模型体积并加速推理。TensorRT和OpenVINO都支持INT8量化。使用export.py时,加上–int8参数可进行INT8量化(需要校准数据集)。
10.5 自动化数据增强(AutoAugment、RandAugment)
除了固定增强策略,可尝试使用AutoAugment或RandAugment自动搜索最佳增强组合。Ultralytics YOLOv8内置了augment参数,可启用自动增强。
11. 完整项目实战:智慧交通行人车辆检测系统
11.1 需求分析与系统设计
我们设计一个智慧交通监控系统,功能包括:
-
实时检测摄像头画面中的行人和车辆。
-
统计行人数量、车辆数量。
-
检测到异常(如行人进入禁止区域、车辆超速)时报警。
-
提供可视化界面,显示检测框和统计数据。
系统架构:
-
视频流输入(摄像头或视频文件)。
-
检测模型(YOLOv8s)。
-
后处理模块(统计、报警逻辑)。
-
GUI显示(基于PyQt5)。
11.2 数据采集与标注
使用开源数据集如BDD100K或自行采集交通监控视频,标注行人和车辆(car、truck、bus、motorcycle等)。标注工具推荐LabelImg,格式转换为YOLO格式。
11.3 模型训练与调优
使用Ultralytics YOLOv8s进行训练。训练命令:
yolo train data=traffic.yaml model=yolov8s.pt epochs=150 imgsz=640 batch=16 device=0
监控训练曲线,选择合适的epoch停止。
11.4 系统集成
编写Python脚本,调用模型进行实时推理:
from ultralytics import YOLO
import cv2
model = YOLO('best.pt')
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret: break
results = model(frame, conf=0.5)
annotated_frame = results[0].plot()
# 统计目标数量
detections = results[0].boxes
person_count = 0
vehicle_count = 0
for box in detections:
cls = int(box.cls)
if cls == 0: # person
person_count += 1
elif cls in [1,2,3,5,7]: # car, truck, bus, motorcycle, etc.
vehicle_count += 1
# 显示计数
cv2.putText(annotated_frame, f'Persons: {person_count}, Vehicles: {vehicle_count}', (10,30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2)
cv2.imshow('Traffic Monitor', annotated_frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
11.5 界面开发(基于PyQt5)
参考用户提供的gui.py,扩展功能:
-
添加视频流显示区域(使用QLabel)。
-
添加统计结果显示(QTableWidget或QLabel)。
-
添加报警模块(QPushButton触发声音或弹窗)。
-
启动/停止检测按钮。
使用QThread实现异步检测,避免界面卡顿。
11.6 测试与部署
在监控服务器或边缘设备上部署系统,测试长时间运行的稳定性。可根据需要调整检测频率和报警阈值。
12. 常见问题与解决方案
12.1 训练不收敛或震荡
-
降低学习率或使用余弦退火。
-
检查数据集是否有错误标注。
-
尝试使用预训练权重,不要从头训练。
-
调整batch size,太大可能导致梯度不稳定。
-
使用梯度裁剪(YOLOv5默认启用)。
12.2 类别不平衡处理
-
使用类别权重(YOLOv5会自动计算)。
-
增加少数类的数据增强。
-
采用Focal Loss(YOLOv5可通过fl_gamma参数启用)。
-
过采样或欠采样。
12.3 小目标检测困难
-
提高图像输入尺寸(如–imgsz 1280),但会增加显存消耗。
-
启用多尺度训练(–multi-scale)。
-
使用带有更大特征图的模型(如YOLOv5l)。
-
添加更细粒度的检测头(需修改模型配置)。
12.4 推理速度慢的优化方法
-
使用更小的模型(n/s)。
-
开启FP16(–half)。
-
导出为TensorRT或OpenVINO。
-
批量推理。
-
减少输入图像尺寸。
-
使用GPU推理。
12.5 跨平台兼容性问题
-
确保PyTorch版本与CUDA匹配。
-
使用ONNX或TF Lite等通用格式,便于跨平台部署。
-
在Windows/Linux/macOS上分别测试。
13. 总结与展望
13.1 本文总结
本文系统地介绍了基于YOLO的行人车辆目标检测技术,从原理到实践全面覆盖。我们详细讲解了YOLOv5的训练、验证、推理和部署,并扩展到YOLOv6、v7、v8,对比了它们的性能和特点,至于更新的YOLOv9~YOLOv12等将在下次进行讲解。文中提供了丰富的代码示例和命令行命令,读者可以快速上手自己的项目。
13.2 YOLO未来发展趋势
-
Transformer与YOLO的结合:如YOLOv9、DETR等,利用自注意力机制提升全局建模能力。
-
统一框架:YOLOv8已支持多任务,未来可能会进一步统一检测、分割、关键点等。
-
自监督预训练:利用无标注数据预训练骨干网络,提升小样本场景下的性能。
-
端侧优化:更轻量的模型架构和量化技术,使YOLO能在手机、IoT设备上实时运行。
13.3 读者进阶学习资源推荐
-
官方文档:Ultralytics YOLOv8文档(YOLO Object Detection & Segmentation | Ultralytics)
-
YOLOv5 GitHub:GitHub – ultralytics/yolov5: Ultralytics YOLOv5 in PyTorch for object detection, instance segmentation, classification, training, and export. · GitHub
-
YOLOv7 GitHub:GitHub – WongKinYiu/yolov7: Implementation of paper – YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors · GitHub
-
YOLOv6 GitHub:GitHub – meituan/YOLOv6: YOLOv6: a single-stage object detection framework dedicated to industrial applications. · GitHub
-
Papers with Code:跟踪最新目标检测论文
-
Kaggle竞赛:参与目标检测相关比赛,实战提升
附录:完整代码清单
train.py
# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license
"""
Train a YOLOv5 model on a custom dataset. Models and datasets download automatically from the latest YOLOv5 release.
Usage – Single-GPU training:
$ python train.py –data coco128.yaml –weights yolov5s.pt –img 640 # from pretrained (recommended)
$ python train.py –data coco128.yaml –weights '' –cfg yolov5s.yaml –img 640 # from scratch
Usage – Multi-GPU DDP training:
$ python -m torch.distributed.run –nproc_per_node 4 –master_port 1 train.py –data coco128.yaml –weights yolov5s.pt –img 640 –device 0,1,2,3
Models: https://github.com/ultralytics/yolov5/tree/master/models
Datasets: https://github.com/ultralytics/yolov5/tree/master/data
Tutorial: https://docs.ultralytics.com/yolov5/tutorials/train_custom_data
"""
import argparse
import math
import os
import random
import subprocess
import sys
import time
from copy import deepcopy
from datetime import datetime, timedelta
from pathlib import Path
try:
import comet_ml # must be imported before torch (if installed)
except ImportError:
comet_ml = None
import numpy as np
import torch
import torch.distributed as dist
import torch.nn as nn
import yaml
from torch.optim import lr_scheduler
from tqdm import tqdm
FILE = Path(__file__).resolve()
ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT)) # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
import val as validate # for end-of-epoch mAP
from models.experimental import attempt_load
from models.yolo import Model
from utils.autoanchor import check_anchors
from utils.autobatch import check_train_batch_size
from utils.callbacks import Callbacks
from utils.dataloaders import create_dataloader
from utils.downloads import attempt_download, is_url
from utils.general import (
LOGGER,
TQDM_BAR_FORMAT,
check_amp,
check_dataset,
check_file,
check_git_info,
check_git_status,
check_img_size,
check_requirements,
check_suffix,
check_yaml,
colorstr,
get_latest_run,
increment_path,
init_seeds,
intersect_dicts,
labels_to_class_weights,
labels_to_image_weights,
methods,
one_cycle,
print_args,
print_mutation,
strip_optimizer,
yaml_save,
)
from utils.loggers import LOGGERS, Loggers
from utils.loggers.comet.comet_utils import check_comet_resume
from utils.loss import ComputeLoss
from utils.metrics import fitness
from utils.plots import plot_evolve
from utils.torch_utils import (
EarlyStopping,
ModelEMA,
de_parallel,
select_device,
smart_DDP,
smart_optimizer,
smart_resume,
torch_distributed_zero_first,
)
LOCAL_RANK = int(os.getenv("LOCAL_RANK", -1)) # https://pytorch.org/docs/stable/elastic/run.html
RANK = int(os.getenv("RANK", -1))
WORLD_SIZE = int(os.getenv("WORLD_SIZE", 1))
GIT_INFO = check_git_info()
def train(hyp, opt, device, callbacks):
"""
Trains YOLOv5 model with given hyperparameters, options, and device, managing datasets, model architecture, loss
computation, and optimizer steps.
`hyp` argument is path/to/hyp.yaml or hyp dictionary.
"""
save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = (
Path(opt.save_dir),
opt.epochs,
opt.batch_size,
opt.weights,
opt.single_cls,
opt.evolve,
opt.data,
opt.cfg,
opt.resume,
opt.noval,
opt.nosave,
opt.workers,
opt.freeze,
)
callbacks.run("on_pretrain_routine_start")
# Directories
w = save_dir / "weights" # weights dir
(w.parent if evolve else w).mkdir(parents=True, exist_ok=True) # make dir
last, best = w / "last.pt", w / "best.pt"
# Hyperparameters
if isinstance(hyp, str):
with open(hyp, errors="ignore") as f:
hyp = yaml.safe_load(f) # load hyps dict
LOGGER.info(colorstr("hyperparameters: ") + ", ".join(f"{k}={v}" for k, v in hyp.items()))
opt.hyp = hyp.copy() # for saving hyps to checkpoints
# Save run settings
if not evolve:
yaml_save(save_dir / "hyp.yaml", hyp)
yaml_save(save_dir / "opt.yaml", vars(opt))
# Loggers
data_dict = None
if RANK in {-1, 0}:
include_loggers = list(LOGGERS)
if getattr(opt, "ndjson_console", False):
include_loggers.append("ndjson_console")
if getattr(opt, "ndjson_file", False):
include_loggers.append("ndjson_file")
loggers = Loggers(
save_dir=save_dir,
weights=weights,
opt=opt,
hyp=hyp,
logger=LOGGER,
include=tuple(include_loggers),
)
# Register actions
for k in methods(loggers):
callbacks.register_action(k, callback=getattr(loggers, k))
# Process custom dataset artifact link
data_dict = loggers.remote_dataset
if resume: # If resuming runs from remote artifact
weights, epochs, hyp, batch_size = opt.weights, opt.epochs, opt.hyp, opt.batch_size
# Config
plots = not evolve and not opt.noplots # create plots
cuda = device.type != "cpu"
init_seeds(opt.seed + 1 + RANK, deterministic=True)
with torch_distributed_zero_first(LOCAL_RANK):
data_dict = data_dict or check_dataset(data) # check if None
train_path, val_path = data_dict["train"], data_dict["val"]
nc = 1 if single_cls else int(data_dict["nc"]) # number of classes
names = {0: "item"} if single_cls and len(data_dict["names"]) != 1 else data_dict["names"] # class names
is_coco = isinstance(val_path, str) and val_path.endswith("coco/val2017.txt") # COCO dataset
# Model
check_suffix(weights, ".pt") # check weights
pretrained = weights.endswith(".pt")
if pretrained:
with torch_distributed_zero_first(LOCAL_RANK):
weights = attempt_download(weights) # download if not found locally
ckpt = torch.load(weights, map_location="cpu") # load checkpoint to CPU to avoid CUDA memory leak
model = Model(cfg or ckpt["model"].yaml, ch=3, nc=nc, anchors=hyp.get("anchors")).to(device) # create
exclude = ["anchor"] if (cfg or hyp.get("anchors")) and not resume else [] # exclude keys
csd = ckpt["model"].float().state_dict() # checkpoint state_dict as FP32
csd = intersect_dicts(csd, model.state_dict(), exclude=exclude) # intersect
model.load_state_dict(csd, strict=False) # load
LOGGER.info(f"Transferred {len(csd)}/{len(model.state_dict())} items from {weights}") # report
else:
model = Model(cfg, ch=3, nc=nc, anchors=hyp.get("anchors")).to(device) # create
amp = check_amp(model) # check AMP
# Freeze
freeze = [f"model.{x}." for x in (freeze if len(freeze) > 1 else range(freeze[0]))] # layers to freeze
for k, v in model.named_parameters():
v.requires_grad = True # train all layers
# v.register_hook(lambda x: torch.nan_to_num(x)) # NaN to 0 (commented for erratic training results)
if any(x in k for x in freeze):
LOGGER.info(f"freezing {k}")
v.requires_grad = False
# Image size
gs = max(int(model.stride.max()), 32) # grid size (max stride)
imgsz = check_img_size(opt.imgsz, gs, floor=gs * 2) # verify imgsz is gs-multiple
# Batch size
if RANK == -1 and batch_size == -1: # single-GPU only, estimate best batch size
batch_size = check_train_batch_size(model, imgsz, amp)
loggers.on_params_update({"batch_size": batch_size})
# Optimizer
nbs = 64 # nominal batch size
accumulate = max(round(nbs / batch_size), 1) # accumulate loss before optimizing
hyp["weight_decay"] *= batch_size * accumulate / nbs # scale weight_decay
optimizer = smart_optimizer(model, opt.optimizer, hyp["lr0"], hyp["momentum"], hyp["weight_decay"])
# Scheduler
if opt.cos_lr:
lf = one_cycle(1, hyp["lrf"], epochs) # cosine 1->hyp['lrf']
else:
lf = lambda x: (1 – x / epochs) * (1.0 – hyp["lrf"]) + hyp["lrf"] # linear
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs)
# EMA
ema = ModelEMA(model) if RANK in {-1, 0} else None
# Resume
best_fitness, start_epoch = 0.0, 0
if pretrained:
if resume:
best_fitness, start_epoch, epochs = smart_resume(ckpt, optimizer, ema, weights, epochs, resume)
del ckpt, csd
# DP mode
if cuda and RANK == -1 and torch.cuda.device_count() > 1:
LOGGER.warning(
"WARNING ⚠️ DP not recommended, use torch.distributed.run for best DDP Multi-GPU results.\\n"
"See Multi-GPU Tutorial at https://docs.ultralytics.com/yolov5/tutorials/multi_gpu_training to get started."
)
model = torch.nn.DataParallel(model)
# SyncBatchNorm
if opt.sync_bn and cuda and RANK != -1:
model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)
LOGGER.info("Using SyncBatchNorm()")
# Trainloader
train_loader, dataset = create_dataloader(
train_path,
imgsz,
batch_size // WORLD_SIZE,
gs,
single_cls,
hyp=hyp,
augment=True,
cache=None if opt.cache == "val" else opt.cache,
rect=opt.rect,
rank=LOCAL_RANK,
workers=workers,
image_weights=opt.image_weights,
quad=opt.quad,
prefix=colorstr("train: "),
shuffle=True,
seed=opt.seed,
)
labels = np.concatenate(dataset.labels, 0)
mlc = int(labels[:, 0].max()) # max label class
assert mlc < nc, f"Label class {mlc} exceeds nc={nc} in {data}. Possible class labels are 0-{nc – 1}"
# Process 0
if RANK in {-1, 0}:
val_loader = create_dataloader(
val_path,
imgsz,
batch_size // WORLD_SIZE * 2,
gs,
single_cls,
hyp=hyp,
cache=None if noval else opt.cache,
rect=True,
rank=-1,
workers=workers * 2,
pad=0.5,
prefix=colorstr("val: "),
)[0]
if not resume:
if not opt.noautoanchor:
check_anchors(dataset, model=model, thr=hyp["anchor_t"], imgsz=imgsz) # run AutoAnchor
model.half().float() # pre-reduce anchor precision
callbacks.run("on_pretrain_routine_end", labels, names)
# DDP mode
if cuda and RANK != -1:
model = smart_DDP(model)
# Model attributes
nl = de_parallel(model).model[-1].nl # number of detection layers (to scale hyps)
hyp["box"] *= 3 / nl # scale to layers
hyp["cls"] *= nc / 80 * 3 / nl # scale to classes and layers
hyp["obj"] *= (imgsz / 640) ** 2 * 3 / nl # scale to image size and layers
hyp["label_smoothing"] = opt.label_smoothing
model.nc = nc # attach number of classes to model
model.hyp = hyp # attach hyperparameters to model
model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) * nc # attach class weights
model.names = names
# Start training
t0 = time.time()
nb = len(train_loader) # number of batches
nw = max(round(hyp["warmup_epochs"] * nb), 100) # number of warmup iterations, max(3 epochs, 100 iterations)
# nw = min(nw, (epochs – start_epoch) / 2 * nb) # limit warmup to < 1/2 of training
last_opt_step = -1
maps = np.zeros(nc) # mAP per class
results = (0, 0, 0, 0, 0, 0, 0) # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)
scheduler.last_epoch = start_epoch – 1 # do not move
scaler = torch.cuda.amp.GradScaler(enabled=amp)
stopper, stop = EarlyStopping(patience=opt.patience), False
compute_loss = ComputeLoss(model) # init loss class
callbacks.run("on_train_start")
LOGGER.info(
f'Image sizes {imgsz} train, {imgsz} val\\n'
f'Using {train_loader.num_workers * WORLD_SIZE} dataloader workers\\n'
f"Logging results to {colorstr('bold', save_dir)}\\n"
f'Starting training for {epochs} epochs…'
)
for epoch in range(start_epoch, epochs): # epoch ——————————————————————
callbacks.run("on_train_epoch_start")
model.train()
# Update image weights (optional, single-GPU only)
if opt.image_weights:
cw = model.class_weights.cpu().numpy() * (1 – maps) ** 2 / nc # class weights
iw = labels_to_image_weights(dataset.labels, nc=nc, class_weights=cw) # image weights
dataset.indices = random.choices(range(dataset.n), weights=iw, k=dataset.n) # rand weighted idx
# Update mosaic border (optional)
# b = int(random.uniform(0.25 * imgsz, 0.75 * imgsz + gs) // gs * gs)
# dataset.mosaic_border = [b – imgsz, -b] # height, width borders
mloss = torch.zeros(3, device=device) # mean losses
if RANK != -1:
train_loader.sampler.set_epoch(epoch)
pbar = enumerate(train_loader)
LOGGER.info(("\\n" + "%11s" * 7) % ("Epoch", "GPU_mem", "box_loss", "obj_loss", "cls_loss", "Instances", "Size"))
if RANK in {-1, 0}:
pbar = tqdm(pbar, total=nb, bar_format=TQDM_BAR_FORMAT) # progress bar
optimizer.zero_grad()
for i, (imgs, targets, paths, _) in pbar: # batch ————————————————————-
callbacks.run("on_train_batch_start")
ni = i + nb * epoch # number integrated batches (since train start)
imgs = imgs.to(device, non_blocking=True).float() / 255 # uint8 to float32, 0-255 to 0.0-1.0
# Warmup
if ni <= nw:
xi = [0, nw] # x interp
# compute_loss.gr = np.interp(ni, xi, [0.0, 1.0]) # iou loss ratio (obj_loss = 1.0 or iou)
accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
for j, x in enumerate(optimizer.param_groups):
# bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
x["lr"] = np.interp(ni, xi, [hyp["warmup_bias_lr"] if j == 0 else 0.0, x["initial_lr"] * lf(epoch)])
if "momentum" in x:
x["momentum"] = np.interp(ni, xi, [hyp["warmup_momentum"], hyp["momentum"]])
# Multi-scale
if opt.multi_scale:
sz = random.randrange(int(imgsz * 0.5), int(imgsz * 1.5) + gs) // gs * gs # size
sf = sz / max(imgs.shape[2:]) # scale factor
if sf != 1:
ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]] # new shape (stretched to gs-multiple)
imgs = nn.functional.interpolate(imgs, size=ns, mode="bilinear", align_corners=False)
# Forward
with torch.cuda.amp.autocast(amp):
pred = model(imgs) # forward
loss, loss_items = compute_loss(pred, targets.to(device)) # loss scaled by batch_size
if RANK != -1:
loss *= WORLD_SIZE # gradient averaged between devices in DDP mode
if opt.quad:
loss *= 4.0
# Backward
scaler.scale(loss).backward()
# Optimize – https://pytorch.org/docs/master/notes/amp_examples.html
if ni – last_opt_step >= accumulate:
scaler.unscale_(optimizer) # unscale gradients
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10.0) # clip gradients
scaler.step(optimizer) # optimizer.step
scaler.update()
optimizer.zero_grad()
if ema:
ema.update(model)
last_opt_step = ni
# Log
if RANK in {-1, 0}:
mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
mem = f"{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G" # (GB)
pbar.set_description(
("%11s" * 2 + "%11.4g" * 5)
% (f"{epoch}/{epochs – 1}", mem, *mloss, targets.shape[0], imgs.shape[-1])
)
callbacks.run("on_train_batch_end", model, ni, imgs, targets, paths, list(mloss))
if callbacks.stop_training:
return
# end batch ————————————————————————————————
# Scheduler
lr = [x["lr"] for x in optimizer.param_groups] # for loggers
scheduler.step()
if RANK in {-1, 0}:
# mAP
callbacks.run("on_train_epoch_end", epoch=epoch)
ema.update_attr(model, include=["yaml", "nc", "hyp", "names", "stride", "class_weights"])
final_epoch = (epoch + 1 == epochs) or stopper.possible_stop
if not noval or final_epoch: # Calculate mAP
results, maps, _ = validate.run(
data_dict,
batch_size=batch_size // WORLD_SIZE * 2,
imgsz=imgsz,
half=amp,
model=ema.ema,
single_cls=single_cls,
dataloader=val_loader,
save_dir=save_dir,
plots=False,
callbacks=callbacks,
compute_loss=compute_loss,
)
# Update best mAP
fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
stop = stopper(epoch=epoch, fitness=fi) # early stop check
if fi > best_fitness:
best_fitness = fi
log_vals = list(mloss) + list(results) + lr
callbacks.run("on_fit_epoch_end", log_vals, epoch, best_fitness, fi)
# Save model
if (not nosave) or (final_epoch and not evolve): # if save
ckpt = {
"epoch": epoch,
"best_fitness": best_fitness,
"model": deepcopy(de_parallel(model)).half(),
"ema": deepcopy(ema.ema).half(),
"updates": ema.updates,
"optimizer": optimizer.state_dict(),
"opt": vars(opt),
"git": GIT_INFO, # {remote, branch, commit} if a git repo
"date": datetime.now().isoformat(),
}
# Save last, best and delete
torch.save(ckpt, last)
if best_fitness == fi:
torch.save(ckpt, best)
if opt.save_period > 0 and epoch % opt.save_period == 0:
torch.save(ckpt, w / f"epoch{epoch}.pt")
del ckpt
callbacks.run("on_model_save", last, epoch, final_epoch, best_fitness, fi)
# EarlyStopping
if RANK != -1: # if DDP training
broadcast_list = [stop if RANK == 0 else None]
dist.broadcast_object_list(broadcast_list, 0) # broadcast 'stop' to all ranks
if RANK != 0:
stop = broadcast_list[0]
if stop:
break # must break all DDP ranks
# end epoch —————————————————————————————————-
# end training —————————————————————————————————–
if RANK in {-1, 0}:
LOGGER.info(f"\\n{epoch – start_epoch + 1} epochs completed in {(time.time() – t0) / 3600:.3f} hours.")
for f in last, best:
if f.exists():
strip_optimizer(f) # strip optimizers
if f is best:
LOGGER.info(f"\\nValidating {f}…")
results, _, _ = validate.run(
data_dict,
batch_size=batch_size // WORLD_SIZE * 2,
imgsz=imgsz,
model=attempt_load(f, device).half(),
iou_thres=0.65 if is_coco else 0.60, # best pycocotools at iou 0.65
single_cls=single_cls,
dataloader=val_loader,
save_dir=save_dir,
save_json=is_coco,
verbose=True,
plots=plots,
callbacks=callbacks,
compute_loss=compute_loss,
) # val best model with plots
if is_coco:
callbacks.run("on_fit_epoch_end", list(mloss) + list(results) + lr, epoch, best_fitness, fi)
callbacks.run("on_train_end", last, best, epoch, results)
torch.cuda.empty_cache()
return results
def parse_opt(known=False):
"""Parses command-line arguments for YOLOv5 training, validation, and testing."""
parser = argparse.ArgumentParser()
parser.add_argument("–weights", type=str, default=ROOT / "yolov5s.pt", help="initial weights path")
parser.add_argument("–cfg", type=str, default="", help="model.yaml path")
parser.add_argument("–data", type=str, default=ROOT / "data/coco128.yaml", help="dataset.yaml path")
parser.add_argument("–hyp", type=str, default=ROOT / "data/hyps/hyp.scratch-low.yaml", help="hyperparameters path")
parser.add_argument("–epochs", type=int, default=100, help="total training epochs")
parser.add_argument("–batch-size", type=int, default=16, help="total batch size for all GPUs, -1 for autobatch")
parser.add_argument("–imgsz", "–img", "–img-size", type=int, default=640, help="train, val image size (pixels)")
parser.add_argument("–rect", action="store_true", help="rectangular training")
parser.add_argument("–resume", nargs="?", const=True, default=False, help="resume most recent training")
parser.add_argument("–nosave", action="store_true", help="only save final checkpoint")
parser.add_argument("–noval", action="store_true", help="only validate final epoch")
parser.add_argument("–noautoanchor", action="store_true", help="disable AutoAnchor")
parser.add_argument("–noplots", action="store_true", help="save no plot files")
parser.add_argument("–evolve", type=int, nargs="?", const=300, help="evolve hyperparameters for x generations")
parser.add_argument(
"–evolve_population", type=str, default=ROOT / "data/hyps", help="location for loading population"
)
parser.add_argument("–resume_evolve", type=str, default=None, help="resume evolve from last generation")
parser.add_argument("–bucket", type=str, default="", help="gsutil bucket")
parser.add_argument("–cache", type=str, nargs="?", const="ram", help="image –cache ram/disk")
parser.add_argument("–image-weights", action="store_true", help="use weighted image selection for training")
parser.add_argument("–device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu")
parser.add_argument("–multi-scale", action="store_true", help="vary img-size +/- 50%%")
parser.add_argument("–single-cls", action="store_true", help="train multi-class data as single-class")
parser.add_argument("–optimizer", type=str, choices=["SGD", "Adam", "AdamW"], default="SGD", help="optimizer")
parser.add_argument("–sync-bn", action="store_true", help="use SyncBatchNorm, only available in DDP mode")
parser.add_argument("–workers", type=int, default=8, help="max dataloader workers (per RANK in DDP mode)")
parser.add_argument("–project", default=ROOT / "runs/train", help="save to project/name")
parser.add_argument("–name", default="exp", help="save to project/name")
parser.add_argument("–exist-ok", action="store_true", help="existing project/name ok, do not increment")
parser.add_argument("–quad", action="store_true", help="quad dataloader")
parser.add_argument("–cos-lr", action="store_true", help="cosine LR scheduler")
parser.add_argument("–label-smoothing", type=float, default=0.0, help="Label smoothing epsilon")
parser.add_argument("–patience", type=int, default=100, help="EarlyStopping patience (epochs without improvement)")
parser.add_argument("–freeze", nargs="+", type=int, default=[0], help="Freeze layers: backbone=10, first3=0 1 2")
parser.add_argument("–save-period", type=int, default=-1, help="Save checkpoint every x epochs (disabled if < 1)")
parser.add_argument("–seed", type=int, default=0, help="Global training seed")
parser.add_argument("–local_rank", type=int, default=-1, help="Automatic DDP Multi-GPU argument, do not modify")
# Logger arguments
parser.add_argument("–entity", default=None, help="Entity")
parser.add_argument("–upload_dataset", nargs="?", const=True, default=False, help='Upload data, "val" option')
parser.add_argument("–bbox_interval", type=int, default=-1, help="Set bounding-box image logging interval")
parser.add_argument("–artifact_alias", type=str, default="latest", help="Version of dataset artifact to use")
# NDJSON logging
parser.add_argument("–ndjson-console", action="store_true", help="Log ndjson to console")
parser.add_argument("–ndjson-file", action="store_true", help="Log ndjson to file")
return parser.parse_known_args()[0] if known else parser.parse_args()
def main(opt, callbacks=Callbacks()):
"""Runs training or hyperparameter evolution with specified options and optional callbacks."""
if RANK in {-1, 0}:
print_args(vars(opt))
check_git_status()
check_requirements(ROOT / "requirements.txt")
# Resume (from specified or most recent last.pt)
if opt.resume and not check_comet_resume(opt) and not opt.evolve:
last = Path(check_file(opt.resume) if isinstance(opt.resume, str) else get_latest_run())
opt_yaml = last.parent.parent / "opt.yaml" # train options yaml
opt_data = opt.data # original dataset
if opt_yaml.is_file():
with open(opt_yaml, errors="ignore") as f:
d = yaml.safe_load(f)
else:
d = torch.load(last, map_location="cpu")["opt"]
opt = argparse.Namespace(**d) # replace
opt.cfg, opt.weights, opt.resume = "", str(last), True # reinstate
if is_url(opt_data):
opt.data = check_file(opt_data) # avoid HUB resume auth timeout
else:
opt.data, opt.cfg, opt.hyp, opt.weights, opt.project = (
check_file(opt.data),
check_yaml(opt.cfg),
check_yaml(opt.hyp),
str(opt.weights),
str(opt.project),
) # checks
assert len(opt.cfg) or len(opt.weights), "either –cfg or –weights must be specified"
if opt.evolve:
if opt.project == str(ROOT / "runs/train"): # if default project name, rename to runs/evolve
opt.project = str(ROOT / "runs/evolve")
opt.exist_ok, opt.resume = opt.resume, False # pass resume to exist_ok and disable resume
if opt.name == "cfg":
opt.name = Path(opt.cfg).stem # use model.yaml as name
opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))
# DDP mode
device = select_device(opt.device, batch_size=opt.batch_size)
if LOCAL_RANK != -1:
msg = "is not compatible with YOLOv5 Multi-GPU DDP training"
assert not opt.image_weights, f"–image-weights {msg}"
assert not opt.evolve, f"–evolve {msg}"
assert opt.batch_size != -1, f"AutoBatch with –batch-size -1 {msg}, please pass a valid –batch-size"
assert opt.batch_size % WORLD_SIZE == 0, f"–batch-size {opt.batch_size} must be multiple of WORLD_SIZE"
assert torch.cuda.device_count() > LOCAL_RANK, "insufficient CUDA devices for DDP command"
torch.cuda.set_device(LOCAL_RANK)
device = torch.device("cuda", LOCAL_RANK)
dist.init_process_group(
backend="nccl" if dist.is_nccl_available() else "gloo", timeout=timedelta(seconds=10800)
)
# Train
if not opt.evolve:
train(opt.hyp, opt, device, callbacks)
# Evolve hyperparameters (optional)
else:
# Hyperparameter evolution metadata (including this hyperparameter True-False, lower_limit, upper_limit)
meta = {
"lr0": (False, 1e-5, 1e-1), # initial learning rate (SGD=1E-2, Adam=1E-3)
"lrf": (False, 0.01, 1.0), # final OneCycleLR learning rate (lr0 * lrf)
"momentum": (False, 0.6, 0.98), # SGD momentum/Adam beta1
"weight_decay": (False, 0.0, 0.001), # optimizer weight decay
"warmup_epochs": (False, 0.0, 5.0), # warmup epochs (fractions ok)
"warmup_momentum": (False, 0.0, 0.95), # warmup initial momentum
"warmup_bias_lr": (False, 0.0, 0.2), # warmup initial bias lr
"box": (False, 0.02, 0.2), # box loss gain
"cls": (False, 0.2, 4.0), # cls loss gain
"cls_pw": (False, 0.5, 2.0), # cls BCELoss positive_weight
"obj": (False, 0.2, 4.0), # obj loss gain (scale with pixels)
"obj_pw": (False, 0.5, 2.0), # obj BCELoss positive_weight
"iou_t": (False, 0.1, 0.7), # IoU training threshold
"anchor_t": (False, 2.0, 8.0), # anchor-multiple threshold
"anchors": (False, 2.0, 10.0), # anchors per output grid (0 to ignore)
"fl_gamma": (False, 0.0, 2.0), # focal loss gamma (efficientDet default gamma=1.5)
"hsv_h": (True, 0.0, 0.1), # image HSV-Hue augmentation (fraction)
"hsv_s": (True, 0.0, 0.9), # image HSV-Saturation augmentation (fraction)
"hsv_v": (True, 0.0, 0.9), # image HSV-Value augmentation (fraction)
"degrees": (True, 0.0, 45.0), # image rotation (+/- deg)
"translate": (True, 0.0, 0.9), # image translation (+/- fraction)
"scale": (True, 0.0, 0.9), # image scale (+/- gain)
"shear": (True, 0.0, 10.0), # image shear (+/- deg)
"perspective": (True, 0.0, 0.001), # image perspective (+/- fraction), range 0-0.001
"flipud": (True, 0.0, 1.0), # image flip up-down (probability)
"fliplr": (True, 0.0, 1.0), # image flip left-right (probability)
"mosaic": (True, 0.0, 1.0), # image mixup (probability)
"mixup": (True, 0.0, 1.0), # image mixup (probability)
"copy_paste": (True, 0.0, 1.0),
} # segment copy-paste (probability)
# GA configs
pop_size = 50
mutation_rate_min = 0.01
mutation_rate_max = 0.5
crossover_rate_min = 0.5
crossover_rate_max = 1
min_elite_size = 2
max_elite_size = 5
tournament_size_min = 2
tournament_size_max = 10
with open(opt.hyp, errors="ignore") as f:
hyp = yaml.safe_load(f) # load hyps dict
if "anchors" not in hyp: # anchors commented in hyp.yaml
hyp["anchors"] = 3
if opt.noautoanchor:
del hyp["anchors"], meta["anchors"]
opt.noval, opt.nosave, save_dir = True, True, Path(opt.save_dir) # only val/save final epoch
# ei = [isinstance(x, (int, float)) for x in hyp.values()] # evolvable indices
evolve_yaml, evolve_csv = save_dir / "hyp_evolve.yaml", save_dir / "evolve.csv"
if opt.bucket:
# download evolve.csv if exists
subprocess.run(
[
"gsutil",
"cp",
f"gs://{opt.bucket}/evolve.csv",
str(evolve_csv),
]
)
# Delete the items in meta dictionary whose first value is False
del_ = [item for item, value_ in meta.items() if value_[0] is False]
hyp_GA = hyp.copy() # Make a copy of hyp dictionary
for item in del_:
del meta[item] # Remove the item from meta dictionary
del hyp_GA[item] # Remove the item from hyp_GA dictionary
# Set lower_limit and upper_limit arrays to hold the search space boundaries
lower_limit = np.array([meta[k][1] for k in hyp_GA.keys()])
upper_limit = np.array([meta[k][2] for k in hyp_GA.keys()])
# Create gene_ranges list to hold the range of values for each gene in the population
gene_ranges = [(lower_limit[i], upper_limit[i]) for i in range(len(upper_limit))]
# Initialize the population with initial_values or random values
initial_values = []
# If resuming evolution from a previous checkpoint
if opt.resume_evolve is not None:
assert os.path.isfile(ROOT / opt.resume_evolve), "evolve population path is wrong!"
with open(ROOT / opt.resume_evolve, errors="ignore") as f:
evolve_population = yaml.safe_load(f)
for value in evolve_population.values():
value = np.array([value[k] for k in hyp_GA.keys()])
initial_values.append(list(value))
# If not resuming from a previous checkpoint, generate initial values from .yaml files in opt.evolve_population
else:
yaml_files = [f for f in os.listdir(opt.evolve_population) if f.endswith(".yaml")]
for file_name in yaml_files:
with open(os.path.join(opt.evolve_population, file_name)) as yaml_file:
value = yaml.safe_load(yaml_file)
value = np.array([value[k] for k in hyp_GA.keys()])
initial_values.append(list(value))
# Generate random values within the search space for the rest of the population
if initial_values is None:
population = [generate_individual(gene_ranges, len(hyp_GA)) for _ in range(pop_size)]
elif pop_size > 1:
population = [generate_individual(gene_ranges, len(hyp_GA)) for _ in range(pop_size – len(initial_values))]
for initial_value in initial_values:
population = [initial_value] + population
# Run the genetic algorithm for a fixed number of generations
list_keys = list(hyp_GA.keys())
for generation in range(opt.evolve):
if generation >= 1:
save_dict = {}
for i in range(len(population)):
little_dict = {list_keys[j]: float(population[i][j]) for j in range(len(population[i]))}
save_dict[f"gen{str(generation)}number{str(i)}"] = little_dict
with open(save_dir / "evolve_population.yaml", "w") as outfile:
yaml.dump(save_dict, outfile, default_flow_style=False)
# Adaptive elite size
elite_size = min_elite_size + int((max_elite_size – min_elite_size) * (generation / opt.evolve))
# Evaluate the fitness of each individual in the population
fitness_scores = []
for individual in population:
for key, value in zip(hyp_GA.keys(), individual):
hyp_GA[key] = value
hyp.update(hyp_GA)
results = train(hyp.copy(), opt, device, callbacks)
callbacks = Callbacks()
# Write mutation results
keys = (
"metrics/precision",
"metrics/recall",
"metrics/mAP_0.5",
"metrics/mAP_0.5:0.95",
"val/box_loss",
"val/obj_loss",
"val/cls_loss",
)
print_mutation(keys, results, hyp.copy(), save_dir, opt.bucket)
fitness_scores.append(results[2])
# Select the fittest individuals for reproduction using adaptive tournament selection
selected_indices = []
for _ in range(pop_size – elite_size):
# Adaptive tournament size
tournament_size = max(
max(2, tournament_size_min),
int(min(tournament_size_max, pop_size) – (generation / (opt.evolve / 10))),
)
# Perform tournament selection to choose the best individual
tournament_indices = random.sample(range(pop_size), tournament_size)
tournament_fitness = [fitness_scores[j] for j in tournament_indices]
winner_index = tournament_indices[tournament_fitness.index(max(tournament_fitness))]
selected_indices.append(winner_index)
# Add the elite individuals to the selected indices
elite_indices = [i for i in range(pop_size) if fitness_scores[i] in sorted(fitness_scores)[-elite_size:]]
selected_indices.extend(elite_indices)
# Create the next generation through crossover and mutation
next_generation = []
for _ in range(pop_size):
parent1_index = selected_indices[random.randint(0, pop_size – 1)]
parent2_index = selected_indices[random.randint(0, pop_size – 1)]
# Adaptive crossover rate
crossover_rate = max(
crossover_rate_min, min(crossover_rate_max, crossover_rate_max – (generation / opt.evolve))
)
if random.uniform(0, 1) < crossover_rate:
crossover_point = random.randint(1, len(hyp_GA) – 1)
child = population[parent1_index][:crossover_point] + population[parent2_index][crossover_point:]
else:
child = population[parent1_index]
# Adaptive mutation rate
mutation_rate = max(
mutation_rate_min, min(mutation_rate_max, mutation_rate_max – (generation / opt.evolve))
)
for j in range(len(hyp_GA)):
if random.uniform(0, 1) < mutation_rate:
child[j] += random.uniform(-0.1, 0.1)
child[j] = min(max(child[j], gene_ranges[j][0]), gene_ranges[j][1])
next_generation.append(child)
# Replace the old population with the new generation
population = next_generation
# Print the best solution found
best_index = fitness_scores.index(max(fitness_scores))
best_individual = population[best_index]
print("Best solution found:", best_individual)
# Plot results
plot_evolve(evolve_csv)
LOGGER.info(
f'Hyperparameter evolution finished {opt.evolve} generations\\n'
f"Results saved to {colorstr('bold', save_dir)}\\n"
f'Usage example: $ python train.py –hyp {evolve_yaml}'
)
def generate_individual(input_ranges, individual_length):
"""Generates a list of random values within specified input ranges for each gene in the individual."""
individual = []
for i in range(individual_length):
lower_bound, upper_bound = input_ranges[i]
individual.append(random.uniform(lower_bound, upper_bound))
return individual
def run(**kwargs):
"""
Executes YOLOv5 training with given options, overriding with any kwargs provided.
Example: import train; train.run(data='coco128.yaml', imgsz=320, weights='yolov5m.pt')
"""
opt = parse_opt(True)
for k, v in kwargs.items():
setattr(opt, k, v)
main(opt)
return opt
if __name__ == "__main__":
opt = parse_opt()
main(opt)
val.py
# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license
"""
Validate a trained YOLOv5 detection model on a detection dataset.
Usage:
$ python val.py –weights yolov5s.pt –data coco128.yaml –img 640
Usage – formats:
$ python val.py –weights yolov5s.pt # PyTorch
yolov5s.torchscript # TorchScript
yolov5s.onnx # ONNX Runtime or OpenCV DNN with –dnn
yolov5s_openvino_model # OpenVINO
yolov5s.engine # TensorRT
yolov5s.mlmodel # CoreML (macOS-only)
yolov5s_saved_model # TensorFlow SavedModel
yolov5s.pb # TensorFlow GraphDef
yolov5s.tflite # TensorFlow Lite
yolov5s_edgetpu.tflite # TensorFlow Edge TPU
yolov5s_paddle_model # PaddlePaddle
"""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
import numpy as np
import torch
from tqdm import tqdm
FILE = Path(__file__).resolve()
ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT)) # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
from models.common import DetectMultiBackend
from utils.callbacks import Callbacks
from utils.dataloaders import create_dataloader
from utils.general import (
LOGGER,
TQDM_BAR_FORMAT,
Profile,
check_dataset,
check_img_size,
check_requirements,
check_yaml,
coco80_to_coco91_class,
colorstr,
increment_path,
non_max_suppression,
print_args,
scale_boxes,
xywh2xyxy,
xyxy2xywh,
)
from utils.metrics import ConfusionMatrix, ap_per_class, box_iou
from utils.plots import output_to_target, plot_images, plot_val_study
from utils.torch_utils import select_device, smart_inference_mode
def save_one_txt(predn, save_conf, shape, file):
"""Saves one detection result to a txt file in normalized xywh format, optionally including confidence."""
gn = torch.tensor(shape)[[1, 0, 1, 0]] # normalization gain whwh
for *xyxy, conf, cls in predn.tolist():
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
with open(file, "a") as f:
f.write(("%g " * len(line)).rstrip() % line + "\\n")
def save_one_json(predn, jdict, path, class_map):
"""
Saves one JSON detection result with image ID, category ID, bounding box, and score.
Example: {"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}
"""
image_id = int(path.stem) if path.stem.isnumeric() else path.stem
box = xyxy2xywh(predn[:, :4]) # xywh
box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
for p, b in zip(predn.tolist(), box.tolist()):
jdict.append(
{
"image_id": image_id,
"category_id": class_map[int(p[5])],
"bbox": [round(x, 3) for x in b],
"score": round(p[4], 5),
}
)
def process_batch(detections, labels, iouv):
"""
Return correct prediction matrix.
Arguments:
detections (array[N, 6]), x1, y1, x2, y2, conf, class
labels (array[M, 5]), class, x1, y1, x2, y2
Returns:
correct (array[N, 10]), for 10 IoU levels
"""
correct = np.zeros((detections.shape[0], iouv.shape[0])).astype(bool)
iou = box_iou(labels[:, 1:], detections[:, :4])
correct_class = labels[:, 0:1] == detections[:, 5]
for i in range(len(iouv)):
x = torch.where((iou >= iouv[i]) & correct_class) # IoU > threshold and classes match
if x[0].shape[0]:
matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy() # [label, detect, iou]
if x[0].shape[0] > 1:
matches = matches[matches[:, 2].argsort()[::-1]]
matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
# matches = matches[matches[:, 2].argsort()[::-1]]
matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
correct[matches[:, 1].astype(int), i] = True
return torch.tensor(correct, dtype=torch.bool, device=iouv.device)
@smart_inference_mode()
def run(
data,
weights=None, # model.pt path(s)
batch_size=32, # batch size
imgsz=640, # inference size (pixels)
conf_thres=0.001, # confidence threshold
iou_thres=0.6, # NMS IoU threshold
max_det=300, # maximum detections per image
task="val", # train, val, test, speed or study
device="", # cuda device, i.e. 0 or 0,1,2,3 or cpu
workers=8, # max dataloader workers (per RANK in DDP mode)
single_cls=False, # treat as single-class dataset
augment=False, # augmented inference
verbose=False, # verbose output
save_txt=False, # save results to *.txt
save_hybrid=False, # save label+prediction hybrid results to *.txt
save_conf=False, # save confidences in –save-txt labels
save_json=False, # save a COCO-JSON results file
project=ROOT / "runs/val", # save to project/name
name="exp", # save to project/name
exist_ok=False, # existing project/name ok, do not increment
half=True, # use FP16 half-precision inference
dnn=False, # use OpenCV DNN for ONNX inference
model=None,
dataloader=None,
save_dir=Path(""),
plots=True,
callbacks=Callbacks(),
compute_loss=None,
):
# Initialize/load model and set device
training = model is not None
if training: # called by train.py
device, pt, jit, engine = next(model.parameters()).device, True, False, False # get model device, PyTorch model
half &= device.type != "cpu" # half precision only supported on CUDA
model.half() if half else model.float()
else: # called directly
device = select_device(device, batch_size=batch_size)
# Directories
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run
(save_dir / "labels" if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
# Load model
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
imgsz = check_img_size(imgsz, s=stride) # check image size
half = model.fp16 # FP16 supported on limited backends with CUDA
if engine:
batch_size = model.batch_size
else:
device = model.device
if not (pt or jit):
batch_size = 1 # export.py models default to batch-size 1
LOGGER.info(f"Forcing –batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models")
# Data
data = check_dataset(data) # check
# Configure
model.eval()
cuda = device.type != "cpu"
is_coco = isinstance(data.get("val"), str) and data["val"].endswith(f"coco{os.sep}val2017.txt") # COCO dataset
nc = 1 if single_cls else int(data["nc"]) # number of classes
iouv = torch.linspace(0.5, 0.95, 10, device=device) # iou vector for mAP@0.5:0.95
niou = iouv.numel()
# Dataloader
if not training:
if pt and not single_cls: # check –weights are trained on –data
ncm = model.model.nc
assert ncm == nc, (
f"{weights} ({ncm} classes) trained on different –data than what you passed ({nc} "
f"classes). Pass correct combination of –weights and –data that are trained together."
)
model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz)) # warmup
pad, rect = (0.0, False) if task == "speed" else (0.5, pt) # square inference for benchmarks
task = task if task in ("train", "val", "test") else "val" # path to train/val/test images
dataloader = create_dataloader(
data[task],
imgsz,
batch_size,
stride,
single_cls,
pad=pad,
rect=rect,
workers=workers,
prefix=colorstr(f"{task}: "),
)[0]
seen = 0
confusion_matrix = ConfusionMatrix(nc=nc)
names = model.names if hasattr(model, "names") else model.module.names # get class names
if isinstance(names, (list, tuple)): # old format
names = dict(enumerate(names))
class_map = coco80_to_coco91_class() if is_coco else list(range(1000))
s = ("%22s" + "%11s" * 6) % ("Class", "Images", "Instances", "P", "R", "mAP50", "mAP50-95")
tp, fp, p, r, f1, mp, mr, map50, ap50, map = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
dt = Profile(device=device), Profile(device=device), Profile(device=device) # profiling times
loss = torch.zeros(3, device=device)
jdict, stats, ap, ap_class = [], [], [], []
callbacks.run("on_val_start")
pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT) # progress bar
for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
callbacks.run("on_val_batch_start")
with dt[0]:
if cuda:
im = im.to(device, non_blocking=True)
targets = targets.to(device)
im = im.half() if half else im.float() # uint8 to fp16/32
im /= 255 # 0 – 255 to 0.0 – 1.0
nb, _, height, width = im.shape # batch size, channels, height, width
# Inference
with dt[1]:
preds, train_out = model(im) if compute_loss else (model(im, augment=augment), None)
# Loss
if compute_loss:
loss += compute_loss(train_out, targets)[1] # box, obj, cls
# NMS
targets[:, 2:] *= torch.tensor((width, height, width, height), device=device) # to pixels
lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else [] # for autolabelling
with dt[2]:
preds = non_max_suppression(
preds, conf_thres, iou_thres, labels=lb, multi_label=True, agnostic=single_cls, max_det=max_det
)
# Metrics
for si, pred in enumerate(preds):
labels = targets[targets[:, 0] == si, 1:]
nl, npr = labels.shape[0], pred.shape[0] # number of labels, predictions
path, shape = Path(paths[si]), shapes[si][0]
correct = torch.zeros(npr, niou, dtype=torch.bool, device=device) # init
seen += 1
if npr == 0:
if nl:
stats.append((correct, *torch.zeros((2, 0), device=device), labels[:, 0]))
if plots:
confusion_matrix.process_batch(detections=None, labels=labels[:, 0])
continue
# Predictions
if single_cls:
pred[:, 5] = 0
predn = pred.clone()
scale_boxes(im[si].shape[1:], predn[:, :4], shape, shapes[si][1]) # native-space pred
# Evaluate
if nl:
tbox = xywh2xyxy(labels[:, 1:5]) # target boxes
scale_boxes(im[si].shape[1:], tbox, shape, shapes[si][1]) # native-space labels
labelsn = torch.cat((labels[:, 0:1], tbox), 1) # native-space labels
correct = process_batch(predn, labelsn, iouv)
if plots:
confusion_matrix.process_batch(predn, labelsn)
stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0])) # (correct, conf, pcls, tcls)
# Save/log
if save_txt:
(save_dir / "labels").mkdir(parents=True, exist_ok=True)
save_one_txt(predn, save_conf, shape, file=save_dir / "labels" / f"{path.stem}.txt")
if save_json:
save_one_json(predn, jdict, path, class_map) # append to COCO-JSON dictionary
callbacks.run("on_val_image_end", pred, predn, path, names, im[si])
# Plot images
if plots and batch_i < 3:
plot_images(im, targets, paths, save_dir / f"val_batch{batch_i}_labels.jpg", names) # labels
plot_images(im, output_to_target(preds), paths, save_dir / f"val_batch{batch_i}_pred.jpg", names) # pred
callbacks.run("on_val_batch_end", batch_i, im, targets, paths, shapes, preds)
# Compute metrics
stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)] # to numpy
if len(stats) and stats[0].any():
tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
ap50, ap = ap[:, 0], ap.mean(1) # AP@0.5, AP@0.5:0.95
mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
nt = np.bincount(stats[3].astype(int), minlength=nc) # number of targets per class
# Print results
pf = "%22s" + "%11i" * 2 + "%11.3g" * 4 # print format
LOGGER.info(pf % ("all", seen, nt.sum(), mp, mr, map50, map))
if nt.sum() == 0:
LOGGER.warning(f"WARNING ⚠️ no labels found in {task} set, can not compute metrics without labels")
# Print results per class
if (verbose or (nc < 50 and not training)) and nc > 1 and len(stats):
for i, c in enumerate(ap_class):
LOGGER.info(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))
# Print speeds
t = tuple(x.t / seen * 1e3 for x in dt) # speeds per image
if not training:
shape = (batch_size, 3, imgsz, imgsz)
LOGGER.info(f"Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {shape}" % t)
# Plots
if plots:
confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
callbacks.run("on_val_end", nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix)
# Save JSON
if save_json and len(jdict):
w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else "" # weights
anno_json = str(Path("../datasets/coco/annotations/instances_val2017.json")) # annotations
if not os.path.exists(anno_json):
anno_json = os.path.join(data["path"], "annotations", "instances_val2017.json")
pred_json = str(save_dir / f"{w}_predictions.json") # predictions
LOGGER.info(f"\\nEvaluating pycocotools mAP… saving {pred_json}…")
with open(pred_json, "w") as f:
json.dump(jdict, f)
try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
check_requirements("pycocotools>=2.0.6")
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
anno = COCO(anno_json) # init annotations api
pred = anno.loadRes(pred_json) # init predictions api
eval = COCOeval(anno, pred, "bbox")
if is_coco:
eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.im_files] # image IDs to evaluate
eval.evaluate()
eval.accumulate()
eval.summarize()
map, map50 = eval.stats[:2] # update results (mAP@0.5:0.95, mAP@0.5)
except Exception as e:
LOGGER.info(f"pycocotools unable to run: {e}")
# Return results
model.float() # for training
if not training:
s = f"\\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ""
LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
maps = np.zeros(nc) + map
for i, c in enumerate(ap_class):
maps[c] = ap[i]
return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t
def parse_opt():
"""Parses command-line options for YOLOv5 model inference configuration."""
parser = argparse.ArgumentParser()
parser.add_argument("–data", type=str, default=ROOT / "data/coco128.yaml", help="dataset.yaml path")
parser.add_argument("–weights", nargs="+", type=str, default=ROOT / "yolov5s.pt", help="model path(s)")
parser.add_argument("–batch-size", type=int, default=32, help="batch size")
parser.add_argument("–imgsz", "–img", "–img-size", type=int, default=640, help="inference size (pixels)")
parser.add_argument("–conf-thres", type=float, default=0.001, help="confidence threshold")
parser.add_argument("–iou-thres", type=float, default=0.6, help="NMS IoU threshold")
parser.add_argument("–max-det", type=int, default=300, help="maximum detections per image")
parser.add_argument("–task", default="val", help="train, val, test, speed or study")
parser.add_argument("–device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu")
parser.add_argument("–workers", type=int, default=8, help="max dataloader workers (per RANK in DDP mode)")
parser.add_argument("–single-cls", action="store_true", help="treat as single-class dataset")
parser.add_argument("–augment", action="store_true", help="augmented inference")
parser.add_argument("–verbose", action="store_true", help="report mAP by class")
parser.add_argument("–save-txt", action="store_true", help="save results to *.txt")
parser.add_argument("–save-hybrid", action="store_true", help="save label+prediction hybrid results to *.txt")
parser.add_argument("–save-conf", action="store_true", help="save confidences in –save-txt labels")
parser.add_argument("–save-json", action="store_true", help="save a COCO-JSON results file")
parser.add_argument("–project", default=ROOT / "runs/val", help="save to project/name")
parser.add_argument("–name", default="exp", help="save to project/name")
parser.add_argument("–exist-ok", action="store_true", help="existing project/name ok, do not increment")
parser.add_argument("–half", action="store_true", help="use FP16 half-precision inference")
parser.add_argument("–dnn", action="store_true", help="use OpenCV DNN for ONNX inference")
opt = parser.parse_args()
opt.data = check_yaml(opt.data) # check YAML
opt.save_json |= opt.data.endswith("coco.yaml")
opt.save_txt |= opt.save_hybrid
print_args(vars(opt))
return opt
def main(opt):
"""Executes YOLOv5 tasks like training, validation, testing, speed, and study benchmarks based on provided
options.
"""
check_requirements(ROOT / "requirements.txt", exclude=("tensorboard", "thop"))
if opt.task in ("train", "val", "test"): # run normally
if opt.conf_thres > 0.001: # https://github.com/ultralytics/yolov5/issues/1466
LOGGER.info(f"WARNING ⚠️ confidence threshold {opt.conf_thres} > 0.001 produces invalid results")
if opt.save_hybrid:
LOGGER.info("WARNING ⚠️ –save-hybrid will return high mAP from hybrid labels, not from predictions alone")
run(**vars(opt))
else:
weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
opt.half = torch.cuda.is_available() and opt.device != "cpu" # FP16 for fastest results
if opt.task == "speed": # speed benchmarks
# python val.py –task speed –data coco.yaml –batch 1 –weights yolov5n.pt yolov5s.pt…
opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
for opt.weights in weights:
run(**vars(opt), plots=False)
elif opt.task == "study": # speed vs mAP benchmarks
# python val.py –task study –data coco.yaml –iou 0.7 –weights yolov5n.pt yolov5s.pt…
for opt.weights in weights:
f = f"study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt" # filename to save to
x, y = list(range(256, 1536 + 128, 128)), [] # x axis (image sizes), y axis
for opt.imgsz in x: # img-size
LOGGER.info(f"\\nRunning {f} –imgsz {opt.imgsz}…")
r, _, t = run(**vars(opt), plots=False)
y.append(r + t) # results and times
np.savetxt(f, y, fmt="%10.4g") # save
subprocess.run(["zip", "-r", "study.zip", "study_*.txt"])
plot_val_study(x=x) # plot
else:
raise NotImplementedError(f'–task {opt.task} not in ("train", "val", "test", "speed", "study")')
if __name__ == "__main__":
opt = parse_opt()
main(opt)
detect.py
# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license
"""
Run YOLOv5 detection inference on images, videos, directories, globs, YouTube, webcam, streams, etc.
Usage – sources:
$ python detect.py –weights yolov5s.pt –source 0 # webcam
img.jpg # image
vid.mp4 # video
screen # screenshot
path/ # directory
list.txt # list of images
list.streams # list of streams
'path/*.jpg' # glob
'https://youtu.be/LNwODJXcvt4' # YouTube
'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP stream
Usage – formats:
$ python detect.py –weights yolov5s.pt # PyTorch
yolov5s.torchscript # TorchScript
yolov5s.onnx # ONNX Runtime or OpenCV DNN with –dnn
yolov5s_openvino_model # OpenVINO
yolov5s.engine # TensorRT
yolov5s.mlmodel # CoreML (macOS-only)
yolov5s_saved_model # TensorFlow SavedModel
yolov5s.pb # TensorFlow GraphDef
yolov5s.tflite # TensorFlow Lite
yolov5s_edgetpu.tflite # TensorFlow Edge TPU
yolov5s_paddle_model # PaddlePaddle
"""
import argparse
import csv
import os
import platform
import sys
from pathlib import Path
import torch
FILE = Path(__file__).resolve()
ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT)) # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
from ultralytics.utils.plotting import Annotator, colors, save_one_box
from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
from utils.general import (
LOGGER,
Profile,
check_file,
check_img_size,
check_imshow,
check_requirements,
colorstr,
cv2,
increment_path,
non_max_suppression,
print_args,
scale_boxes,
strip_optimizer,
xyxy2xywh,
)
from utils.torch_utils import select_device, smart_inference_mode
@smart_inference_mode()
def run(
weights=ROOT / "yolov5s.pt", # model path or triton URL
source=ROOT / "data/images", # file/dir/URL/glob/screen/0(webcam)
data=ROOT / "data/coco128.yaml", # dataset.yaml path
imgsz=(640, 640), # inference size (height, width)
conf_thres=0.25, # confidence threshold
iou_thres=0.45, # NMS IOU threshold
max_det=1000, # maximum detections per image
device="", # cuda device, i.e. 0 or 0,1,2,3 or cpu
view_img=False, # show results
save_txt=False, # save results to *.txt
save_csv=False, # save results in CSV format
save_conf=False, # save confidences in –save-txt labels
save_crop=False, # save cropped prediction boxes
nosave=False, # do not save images/videos
classes=None, # filter by class: –class 0, or –class 0 2 3
agnostic_nms=False, # class-agnostic NMS
augment=False, # augmented inference
visualize=False, # visualize features
update=False, # update all models
project=ROOT / "runs/detect", # save results to project/name
name="exp", # save results to project/name
exist_ok=False, # existing project/name ok, do not increment
line_thickness=3, # bounding box thickness (pixels)
hide_labels=False, # hide labels
hide_conf=False, # hide confidences
half=False, # use FP16 half-precision inference
dnn=False, # use OpenCV DNN for ONNX inference
vid_stride=1, # video frame-rate stride
):
source = str(source)
save_img = not nosave and not source.endswith(".txt") # save inference images
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
is_url = source.lower().startswith(("rtsp://", "rtmp://", "http://", "https://"))
webcam = source.isnumeric() or source.endswith(".streams") or (is_url and not is_file)
screenshot = source.lower().startswith("screen")
if is_url and is_file:
source = check_file(source) # download
# Directories
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run
(save_dir / "labels" if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
# Load model
device = select_device(device)
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
stride, names, pt = model.stride, model.names, model.pt
imgsz = check_img_size(imgsz, s=stride) # check image size
# Dataloader
bs = 1 # batch_size
if webcam:
view_img = check_imshow(warn=True)
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
bs = len(dataset)
elif screenshot:
dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
else:
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
vid_path, vid_writer = [None] * bs, [None] * bs
# Run inference
model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz)) # warmup
seen, windows, dt = 0, [], (Profile(device=device), Profile(device=device), Profile(device=device))
for path, im, im0s, vid_cap, s in dataset:
with dt[0]:
im = torch.from_numpy(im).to(model.device)
im = im.half() if model.fp16 else im.float() # uint8 to fp16/32
im /= 255 # 0 – 255 to 0.0 – 1.0
if len(im.shape) == 3:
im = im[None] # expand for batch dim
if model.xml and im.shape[0] > 1:
ims = torch.chunk(im, im.shape[0], 0)
# Inference
with dt[1]:
visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
if model.xml and im.shape[0] > 1:
pred = None
for image in ims:
if pred is None:
pred = model(image, augment=augment, visualize=visualize).unsqueeze(0)
else:
pred = torch.cat((pred, model(image, augment=augment, visualize=visualize).unsqueeze(0)), dim=0)
pred = [pred, None]
else:
pred = model(im, augment=augment, visualize=visualize)
# NMS
with dt[2]:
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
# Second-stage classifier (optional)
# pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)
# Define the path for the CSV file
csv_path = save_dir / "predictions.csv"
# Create or append to the CSV file
def write_to_csv(image_name, prediction, confidence):
"""Writes prediction data for an image to a CSV file, appending if the file exists."""
data = {"Image Name": image_name, "Prediction": prediction, "Confidence": confidence}
with open(csv_path, mode="a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=data.keys())
if not csv_path.is_file():
writer.writeheader()
writer.writerow(data)
# Process predictions
for i, det in enumerate(pred): # per image
seen += 1
if webcam: # batch_size >= 1
p, im0, frame = path[i], im0s[i].copy(), dataset.count
s += f"{i}: "
else:
p, im0, frame = path, im0s.copy(), getattr(dataset, "frame", 0)
p = Path(p) # to Path
save_path = str(save_dir / p.name) # im.jpg
txt_path = str(save_dir / "labels" / p.stem) + ("" if dataset.mode == "image" else f"_{frame}") # im.txt
s += "%gx%g " % im.shape[2:] # print string
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
imc = im0.copy() if save_crop else im0 # for save_crop
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
if len(det):
# Rescale boxes from img_size to im0 size
det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()
# Print results
for c in det[:, 5].unique():
n = (det[:, 5] == c).sum() # detections per class
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
# Write results
for *xyxy, conf, cls in reversed(det):
c = int(cls) # integer class
label = names[c] if hide_conf else f"{names[c]}"
confidence = float(conf)
confidence_str = f"{confidence:.2f}"
if save_csv:
write_to_csv(p.name, label, confidence_str)
if save_txt: # Write to file
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
with open(f"{txt_path}.txt", "a") as f:
f.write(("%g " * len(line)).rstrip() % line + "\\n")
if save_img or save_crop or view_img: # Add bbox to image
c = int(cls) # integer class
label = None if hide_labels else (names[c] if hide_conf else f"{names[c]} {conf:.2f}")
annotator.box_label(xyxy, label, color=colors(c, True))
if save_crop:
save_one_box(xyxy, imc, file=save_dir / "crops" / names[c] / f"{p.stem}.jpg", BGR=True)
# Stream results
im0 = annotator.result()
if view_img:
if platform.system() == "Linux" and p not in windows:
windows.append(p)
cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO) # allow window resize (Linux)
cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
cv2.imshow(str(p), im0)
cv2.waitKey(1) # 1 millisecond
# Save results (image with detections)
if save_img:
if dataset.mode == "image":
cv2.imwrite(save_path, im0)
else: # 'video' or 'stream'
if vid_path[i] != save_path: # new video
vid_path[i] = save_path
if isinstance(vid_writer[i], cv2.VideoWriter):
vid_writer[i].release() # release previous video writer
if vid_cap: # video
fps = vid_cap.get(cv2.CAP_PROP_FPS)
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
else: # stream
fps, w, h = 30, im0.shape[1], im0.shape[0]
save_path = str(Path(save_path).with_suffix(".mp4")) # force *.mp4 suffix on results videos
vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
vid_writer[i].write(im0)
# Print time (inference-only)
LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms")
# Print results
t = tuple(x.t / seen * 1e3 for x in dt) # speeds per image
LOGGER.info(f"Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}" % t)
if save_txt or save_img:
s = f"\\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ""
LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
if update:
strip_optimizer(weights[0]) # update model (to fix SourceChangeWarning)
def parse_opt():
"""Parses command-line arguments for YOLOv5 detection, setting inference options and model configurations."""
parser = argparse.ArgumentParser()
parser.add_argument("–weights", nargs="+", type=str, default=ROOT / "yolov5s.pt", help="model path or triton URL")
parser.add_argument("–source", type=str, default=ROOT / "data/images", help="file/dir/URL/glob/screen/0(webcam)")
parser.add_argument("–data", type=str, default=ROOT / "data/coco128.yaml", help="(optional) dataset.yaml path")
parser.add_argument("–imgsz", "–img", "–img-size", nargs="+", type=int, default=[640], help="inference size h,w")
parser.add_argument("–conf-thres", type=float, default=0.25, help="confidence threshold")
parser.add_argument("–iou-thres", type=float, default=0.45, help="NMS IoU threshold")
parser.add_argument("–max-det", type=int, default=1000, help="maximum detections per image")
parser.add_argument("–device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu")
parser.add_argument("–view-img", action="store_true", help="show results")
parser.add_argument("–save-txt", action="store_true", help="save results to *.txt")
parser.add_argument("–save-csv", action="store_true", help="save results in CSV format")
parser.add_argument("–save-conf", action="store_true", help="save confidences in –save-txt labels")
parser.add_argument("–save-crop", action="store_true", help="save cropped prediction boxes")
parser.add_argument("–nosave", action="store_true", help="do not save images/videos")
parser.add_argument("–classes", nargs="+", type=int, help="filter by class: –classes 0, or –classes 0 2 3")
parser.add_argument("–agnostic-nms", action="store_true", help="class-agnostic NMS")
parser.add_argument("–augment", action="store_true", help="augmented inference")
parser.add_argument("–visualize", action="store_true", help="visualize features")
parser.add_argument("–update", action="store_true", help="update all models")
parser.add_argument("–project", default=ROOT / "runs/detect", help="save results to project/name")
parser.add_argument("–name", default="exp", help="save results to project/name")
parser.add_argument("–exist-ok", action="store_true", help="existing project/name ok, do not increment")
parser.add_argument("–line-thickness", default=3, type=int, help="bounding box thickness (pixels)")
parser.add_argument("–hide-labels", default=False, action="store_true", help="hide labels")
parser.add_argument("–hide-conf", default=False, action="store_true", help="hide confidences")
parser.add_argument("–half", action="store_true", help="use FP16 half-precision inference")
parser.add_argument("–dnn", action="store_true", help="use OpenCV DNN for ONNX inference")
parser.add_argument("–vid-stride", type=int, default=1, help="video frame-rate stride")
opt = parser.parse_args()
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
print_args(vars(opt))
return opt
def main(opt):
"""Executes YOLOv5 model inference with given options, checking requirements before running the model."""
check_requirements(ROOT / "requirements.txt", exclude=("tensorboard", "thop"))
run(**vars(opt))
if __name__ == "__main__":
opt = parse_opt()
main(opt)
export.py
# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license
"""
Export a YOLOv5 PyTorch model to other formats. TensorFlow exports authored by https://github.com/zldrobit
Format | `export.py –include` | Model
— | — | —
PyTorch | – | yolov5s.pt
TorchScript | `torchscript` | yolov5s.torchscript
ONNX | `onnx` | yolov5s.onnx
OpenVINO | `openvino` | yolov5s_openvino_model/
TensorRT | `engine` | yolov5s.engine
CoreML | `coreml` | yolov5s.mlmodel
TensorFlow SavedModel | `saved_model` | yolov5s_saved_model/
TensorFlow GraphDef | `pb` | yolov5s.pb
TensorFlow Lite | `tflite` | yolov5s.tflite
TensorFlow Edge TPU | `edgetpu` | yolov5s_edgetpu.tflite
TensorFlow.js | `tfjs` | yolov5s_web_model/
PaddlePaddle | `paddle` | yolov5s_paddle_model/
Requirements:
$ pip install -r requirements.txt coremltools onnx onnx-simplifier onnxruntime openvino-dev tensorflow-cpu # CPU
$ pip install -r requirements.txt coremltools onnx onnx-simplifier onnxruntime-gpu openvino-dev tensorflow # GPU
Usage:
$ python export.py –weights yolov5s.pt –include torchscript onnx openvino engine coreml tflite …
Inference:
$ python detect.py –weights yolov5s.pt # PyTorch
yolov5s.torchscript # TorchScript
yolov5s.onnx # ONNX Runtime or OpenCV DNN with –dnn
yolov5s_openvino_model # OpenVINO
yolov5s.engine # TensorRT
yolov5s.mlmodel # CoreML (macOS-only)
yolov5s_saved_model # TensorFlow SavedModel
yolov5s.pb # TensorFlow GraphDef
yolov5s.tflite # TensorFlow Lite
yolov5s_edgetpu.tflite # TensorFlow Edge TPU
yolov5s_paddle_model # PaddlePaddle
TensorFlow.js:
$ cd .. && git clone https://github.com/zldrobit/tfjs-yolov5-example.git && cd tfjs-yolov5-example
$ npm install
$ ln -s ../../yolov5/yolov5s_web_model public/yolov5s_web_model
$ npm start
"""
import argparse
import contextlib
import json
import os
import platform
import re
import subprocess
import sys
import time
import warnings
from pathlib import Path
import pandas as pd
import torch
from torch.utils.mobile_optimizer import optimize_for_mobile
FILE = Path(__file__).resolve()
ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT)) # add ROOT to PATH
if platform.system() != "Windows":
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
from models.experimental import attempt_load
from models.yolo import ClassificationModel, Detect, DetectionModel, SegmentationModel
from utils.dataloaders import LoadImages
from utils.general import (
LOGGER,
Profile,
check_dataset,
check_img_size,
check_requirements,
check_version,
check_yaml,
colorstr,
file_size,
get_default_args,
print_args,
url2file,
yaml_save,
)
from utils.torch_utils import select_device, smart_inference_mode
MACOS = platform.system() == "Darwin" # macOS environment
class iOSModel(torch.nn.Module):
def __init__(self, model, im):
"""Initializes an iOS compatible model with normalization based on image dimensions."""
super().__init__()
b, c, h, w = im.shape # batch, channel, height, width
self.model = model
self.nc = model.nc # number of classes
if w == h:
self.normalize = 1.0 / w
else:
self.normalize = torch.tensor([1.0 / w, 1.0 / h, 1.0 / w, 1.0 / h]) # broadcast (slower, smaller)
# np = model(im)[0].shape[1] # number of points
# self.normalize = torch.tensor([1. / w, 1. / h, 1. / w, 1. / h]).expand(np, 4) # explicit (faster, larger)
def forward(self, x):
"""Runs forward pass on the input tensor, returning class confidences and normalized coordinates."""
xywh, conf, cls = self.model(x)[0].squeeze().split((4, 1, self.nc), 1)
return cls * conf, xywh * self.normalize # confidence (3780, 80), coordinates (3780, 4)
def export_formats():
"""Returns a DataFrame of supported YOLOv5 model export formats and their properties."""
x = [
["PyTorch", "-", ".pt", True, True],
["TorchScript", "torchscript", ".torchscript", True, True],
["ONNX", "onnx", ".onnx", True, True],
["OpenVINO", "openvino", "_openvino_model", True, False],
["TensorRT", "engine", ".engine", False, True],
["CoreML", "coreml", ".mlmodel", True, False],
["TensorFlow SavedModel", "saved_model", "_saved_model", True, True],
["TensorFlow GraphDef", "pb", ".pb", True, True],
["TensorFlow Lite", "tflite", ".tflite", True, False],
["TensorFlow Edge TPU", "edgetpu", "_edgetpu.tflite", False, False],
["TensorFlow.js", "tfjs", "_web_model", False, False],
["PaddlePaddle", "paddle", "_paddle_model", True, True],
]
return pd.DataFrame(x, columns=["Format", "Argument", "Suffix", "CPU", "GPU"])
def try_export(inner_func):
"""Decorator @try_export for YOLOv5 model export functions that logs success/failure, time taken, and file size."""
inner_args = get_default_args(inner_func)
def outer_func(*args, **kwargs):
prefix = inner_args["prefix"]
try:
with Profile() as dt:
f, model = inner_func(*args, **kwargs)
LOGGER.info(f"{prefix} export success ✅ {dt.t:.1f}s, saved as {f} ({file_size(f):.1f} MB)")
return f, model
except Exception as e:
LOGGER.info(f"{prefix} export failure ❌ {dt.t:.1f}s: {e}")
return None, None
return outer_func
@try_export
def export_torchscript(model, im, file, optimize, prefix=colorstr("TorchScript:")):
"""Exports YOLOv5 model to TorchScript format, optionally optimized for mobile, with image shape and stride
metadata.
"""
LOGGER.info(f"\\n{prefix} starting export with torch {torch.__version__}…")
f = file.with_suffix(".torchscript")
ts = torch.jit.trace(model, im, strict=False)
d = {"shape": im.shape, "stride": int(max(model.stride)), "names": model.names}
extra_files = {"config.txt": json.dumps(d)} # torch._C.ExtraFilesMap()
if optimize: # https://pytorch.org/tutorials/recipes/mobile_interpreter.html
optimize_for_mobile(ts)._save_for_lite_interpreter(str(f), _extra_files=extra_files)
else:
ts.save(str(f), _extra_files=extra_files)
return f, None
@try_export
def export_onnx(model, im, file, opset, dynamic, simplify, prefix=colorstr("ONNX:")):
"""Exports a YOLOv5 model to ONNX format with dynamic axes and optional simplification."""
check_requirements("onnx>=1.12.0")
import onnx
LOGGER.info(f"\\n{prefix} starting export with onnx {onnx.__version__}…")
f = str(file.with_suffix(".onnx"))
output_names = ["output0", "output1"] if isinstance(model, SegmentationModel) else ["output0"]
if dynamic:
dynamic = {"images": {0: "batch", 2: "height", 3: "width"}} # shape(1,3,640,640)
if isinstance(model, SegmentationModel):
dynamic["output0"] = {0: "batch", 1: "anchors"} # shape(1,25200,85)
dynamic["output1"] = {0: "batch", 2: "mask_height", 3: "mask_width"} # shape(1,32,160,160)
elif isinstance(model, DetectionModel):
dynamic["output0"] = {0: "batch", 1: "anchors"} # shape(1,25200,85)
torch.onnx.export(
model.cpu() if dynamic else model, # –dynamic only compatible with cpu
im.cpu() if dynamic else im,
f,
verbose=False,
opset_version=opset,
do_constant_folding=True, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
input_names=["images"],
output_names=output_names,
dynamic_axes=dynamic or None,
)
# Checks
model_onnx = onnx.load(f) # load onnx model
onnx.checker.check_model(model_onnx) # check onnx model
# Metadata
d = {"stride": int(max(model.stride)), "names": model.names}
for k, v in d.items():
meta = model_onnx.metadata_props.add()
meta.key, meta.value = k, str(v)
onnx.save(model_onnx, f)
# Simplify
if simplify:
try:
cuda = torch.cuda.is_available()
check_requirements(("onnxruntime-gpu" if cuda else "onnxruntime", "onnx-simplifier>=0.4.1"))
import onnxsim
LOGGER.info(f"{prefix} simplifying with onnx-simplifier {onnxsim.__version__}…")
model_onnx, check = onnxsim.simplify(model_onnx)
assert check, "assert check failed"
onnx.save(model_onnx, f)
except Exception as e:
LOGGER.info(f"{prefix} simplifier failure: {e}")
return f, model_onnx
@try_export
def export_openvino(file, metadata, half, int8, data, prefix=colorstr("OpenVINO:")):
# YOLOv5 OpenVINO export
check_requirements("openvino-dev>=2023.0") # requires openvino-dev: https://pypi.org/project/openvino-dev/
import openvino.runtime as ov # noqa
from openvino.tools import mo # noqa
LOGGER.info(f"\\n{prefix} starting export with openvino {ov.__version__}…")
f = str(file).replace(file.suffix, f"_{'int8_' if int8 else ''}openvino_model{os.sep}")
f_onnx = file.with_suffix(".onnx")
f_ov = str(Path(f) / file.with_suffix(".xml").name)
ov_model = mo.convert_model(f_onnx, model_name=file.stem, framework="onnx", compress_to_fp16=half) # export
if int8:
check_requirements("nncf>=2.5.0") # requires at least version 2.5.0 to use the post-training quantization
import nncf
import numpy as np
from utils.dataloaders import create_dataloader
def gen_dataloader(yaml_path, task="train", imgsz=640, workers=4):
data_yaml = check_yaml(yaml_path)
data = check_dataset(data_yaml)
dataloader = create_dataloader(
data[task], imgsz=imgsz, batch_size=1, stride=32, pad=0.5, single_cls=False, rect=False, workers=workers
)[0]
return dataloader
# noqa: F811
def transform_fn(data_item):
"""
Quantization transform function.
Extracts and preprocess input data from dataloader item for quantization.
Parameters:
data_item: Tuple with data item produced by DataLoader during iteration
Returns:
input_tensor: Input data for quantization
"""
assert data_item[0].dtype == torch.uint8, "input image must be uint8 for the quantization preprocessing"
img = data_item[0].numpy().astype(np.float32) # uint8 to fp16/32
img /= 255.0 # 0 – 255 to 0.0 – 1.0
return np.expand_dims(img, 0) if img.ndim == 3 else img
ds = gen_dataloader(data)
quantization_dataset = nncf.Dataset(ds, transform_fn)
ov_model = nncf.quantize(ov_model, quantization_dataset, preset=nncf.QuantizationPreset.MIXED)
ov.serialize(ov_model, f_ov) # save
yaml_save(Path(f) / file.with_suffix(".yaml").name, metadata) # add metadata.yaml
return f, None
@try_export
def export_paddle(model, im, file, metadata, prefix=colorstr("PaddlePaddle:")):
"""Exports a YOLOv5 model to PaddlePaddle format using X2Paddle, saving to `save_dir` and adding a metadata.yaml
file.
"""
check_requirements(("paddlepaddle", "x2paddle"))
import x2paddle
from x2paddle.convert import pytorch2paddle
LOGGER.info(f"\\n{prefix} starting export with X2Paddle {x2paddle.__version__}…")
f = str(file).replace(".pt", f"_paddle_model{os.sep}")
pytorch2paddle(module=model, save_dir=f, jit_type="trace", input_examples=[im]) # export
yaml_save(Path(f) / file.with_suffix(".yaml").name, metadata) # add metadata.yaml
return f, None
@try_export
def export_coreml(model, im, file, int8, half, nms, prefix=colorstr("CoreML:")):
"""Exports YOLOv5 model to CoreML format with optional NMS, INT8, and FP16 support; requires coremltools."""
check_requirements("coremltools")
import coremltools as ct
LOGGER.info(f"\\n{prefix} starting export with coremltools {ct.__version__}…")
f = file.with_suffix(".mlmodel")
if nms:
model = iOSModel(model, im)
ts = torch.jit.trace(model, im, strict=False) # TorchScript model
ct_model = ct.convert(ts, inputs=[ct.ImageType("image", shape=im.shape, scale=1 / 255, bias=[0, 0, 0])])
bits, mode = (8, "kmeans_lut") if int8 else (16, "linear") if half else (32, None)
if bits < 32:
if MACOS: # quantization only supported on macOS
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning) # suppress numpy==1.20 float warning
ct_model = ct.models.neural_network.quantization_utils.quantize_weights(ct_model, bits, mode)
else:
print(f"{prefix} quantization only supported on macOS, skipping…")
ct_model.save(f)
return f, ct_model
@try_export
def export_engine(model, im, file, half, dynamic, simplify, workspace=4, verbose=False, prefix=colorstr("TensorRT:")):
"""
Exports a YOLOv5 model to TensorRT engine format, requiring GPU and TensorRT>=7.0.0.
https://developer.nvidia.com/tensorrt
"""
assert im.device.type != "cpu", "export running on CPU but must be on GPU, i.e. `python export.py –device 0`"
try:
import tensorrt as trt
except Exception:
if platform.system() == "Linux":
check_requirements("nvidia-tensorrt", cmds="-U –index-url https://pypi.ngc.nvidia.com")
import tensorrt as trt
if trt.__version__[0] == "7": # TensorRT 7 handling https://github.com/ultralytics/yolov5/issues/6012
grid = model.model[-1].anchor_grid
model.model[-1].anchor_grid = [a[…, :1, :1, :] for a in grid]
export_onnx(model, im, file, 12, dynamic, simplify) # opset 12
model.model[-1].anchor_grid = grid
else: # TensorRT >= 8
check_version(trt.__version__, "8.0.0", hard=True) # require tensorrt>=8.0.0
export_onnx(model, im, file, 12, dynamic, simplify) # opset 12
onnx = file.with_suffix(".onnx")
LOGGER.info(f"\\n{prefix} starting export with TensorRT {trt.__version__}…")
is_trt10 = int(trt.__version__.split(".")[0]) >= 10 # is TensorRT >= 10
assert onnx.exists(), f"failed to export ONNX file: {onnx}"
f = file.with_suffix(".engine") # TensorRT engine file
logger = trt.Logger(trt.Logger.INFO)
if verbose:
logger.min_severity = trt.Logger.Severity.VERBOSE
builder = trt.Builder(logger)
config = builder.create_builder_config()
if is_trt10:
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace << 30)
else: # TensorRT versions 7, 8
config.max_workspace_size = workspace * 1 << 30
flag = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
network = builder.create_network(flag)
parser = trt.OnnxParser(network, logger)
if not parser.parse_from_file(str(onnx)):
raise RuntimeError(f"failed to load ONNX file: {onnx}")
inputs = [network.get_input(i) for i in range(network.num_inputs)]
outputs = [network.get_output(i) for i in range(network.num_outputs)]
for inp in inputs:
LOGGER.info(f'{prefix} input "{inp.name}" with shape{inp.shape} {inp.dtype}')
for out in outputs:
LOGGER.info(f'{prefix} output "{out.name}" with shape{out.shape} {out.dtype}')
if dynamic:
if im.shape[0] <= 1:
LOGGER.warning(f"{prefix} WARNING ⚠️ –dynamic model requires maximum –batch-size argument")
profile = builder.create_optimization_profile()
for inp in inputs:
profile.set_shape(inp.name, (1, *im.shape[1:]), (max(1, im.shape[0] // 2), *im.shape[1:]), im.shape)
config.add_optimization_profile(profile)
LOGGER.info(f"{prefix} building FP{16 if builder.platform_has_fast_fp16 and half else 32} engine as {f}")
if builder.platform_has_fast_fp16 and half:
config.set_flag(trt.BuilderFlag.FP16)
build = builder.build_serialized_network if is_trt10 else builder.build_engine
with build(network, config) as engine, open(f, "wb") as t:
t.write(engine if is_trt10 else engine.serialize())
return f, None
@try_export
def export_saved_model(
model,
im,
file,
dynamic,
tf_nms=False,
agnostic_nms=False,
topk_per_class=100,
topk_all=100,
iou_thres=0.45,
conf_thres=0.25,
keras=False,
prefix=colorstr("TensorFlow SavedModel:"),
):
# YOLOv5 TensorFlow SavedModel export
try:
import tensorflow as tf
except Exception:
check_requirements(f"tensorflow{'' if torch.cuda.is_available() else '-macos' if MACOS else '-cpu'}<=2.15.1")
import tensorflow as tf
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
from models.tf import TFModel
LOGGER.info(f"\\n{prefix} starting export with tensorflow {tf.__version__}…")
if tf.__version__ > "2.13.1":
helper_url = "https://github.com/ultralytics/yolov5/issues/12489"
LOGGER.info(
f"WARNING ⚠️ using Tensorflow {tf.__version__} > 2.13.1 might cause issue when exporting the model to tflite {helper_url}"
) # handling issue https://github.com/ultralytics/yolov5/issues/12489
f = str(file).replace(".pt", "_saved_model")
batch_size, ch, *imgsz = list(im.shape) # BCHW
tf_model = TFModel(cfg=model.yaml, model=model, nc=model.nc, imgsz=imgsz)
im = tf.zeros((batch_size, *imgsz, ch)) # BHWC order for TensorFlow
_ = tf_model.predict(im, tf_nms, agnostic_nms, topk_per_class, topk_all, iou_thres, conf_thres)
inputs = tf.keras.Input(shape=(*imgsz, ch), batch_size=None if dynamic else batch_size)
outputs = tf_model.predict(inputs, tf_nms, agnostic_nms, topk_per_class, topk_all, iou_thres, conf_thres)
keras_model = tf.keras.Model(inputs=inputs, outputs=outputs)
keras_model.trainable = False
keras_model.summary()
if keras:
keras_model.save(f, save_format="tf")
else:
spec = tf.TensorSpec(keras_model.inputs[0].shape, keras_model.inputs[0].dtype)
m = tf.function(lambda x: keras_model(x)) # full model
m = m.get_concrete_function(spec)
frozen_func = convert_variables_to_constants_v2(m)
tfm = tf.Module()
tfm.__call__ = tf.function(lambda x: frozen_func(x)[:4] if tf_nms else frozen_func(x), [spec])
tfm.__call__(im)
tf.saved_model.save(
tfm,
f,
options=tf.saved_model.SaveOptions(experimental_custom_gradients=False)
if check_version(tf.__version__, "2.6")
else tf.saved_model.SaveOptions(),
)
return f, keras_model
@try_export
def export_pb(keras_model, file, prefix=colorstr("TensorFlow GraphDef:")):
"""Exports YOLOv5 model to TensorFlow GraphDef *.pb format; see https://github.com/leimao/Frozen_Graph_TensorFlow for details."""
import tensorflow as tf
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
LOGGER.info(f"\\n{prefix} starting export with tensorflow {tf.__version__}…")
f = file.with_suffix(".pb")
m = tf.function(lambda x: keras_model(x)) # full model
m = m.get_concrete_function(tf.TensorSpec(keras_model.inputs[0].shape, keras_model.inputs[0].dtype))
frozen_func = convert_variables_to_constants_v2(m)
frozen_func.graph.as_graph_def()
tf.io.write_graph(graph_or_graph_def=frozen_func.graph, logdir=str(f.parent), name=f.name, as_text=False)
return f, None
@try_export
def export_tflite(
keras_model, im, file, int8, per_tensor, data, nms, agnostic_nms, prefix=colorstr("TensorFlow Lite:")
):
# YOLOv5 TensorFlow Lite export
import tensorflow as tf
LOGGER.info(f"\\n{prefix} starting export with tensorflow {tf.__version__}…")
batch_size, ch, *imgsz = list(im.shape) # BCHW
f = str(file).replace(".pt", "-fp16.tflite")
converter = tf.lite.TFLiteConverter.from_keras_model(keras_model)
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS]
converter.target_spec.supported_types = [tf.float16]
converter.optimizations = [tf.lite.Optimize.DEFAULT]
if int8:
from models.tf import representative_dataset_gen
dataset = LoadImages(check_dataset(check_yaml(data))["train"], img_size=imgsz, auto=False)
converter.representative_dataset = lambda: representative_dataset_gen(dataset, ncalib=100)
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.target_spec.supported_types = []
converter.inference_input_type = tf.uint8 # or tf.int8
converter.inference_output_type = tf.uint8 # or tf.int8
converter.experimental_new_quantizer = True
if per_tensor:
converter._experimental_disable_per_channel = True
f = str(file).replace(".pt", "-int8.tflite")
if nms or agnostic_nms:
converter.target_spec.supported_ops.append(tf.lite.OpsSet.SELECT_TF_OPS)
tflite_model = converter.convert()
open(f, "wb").write(tflite_model)
return f, None
@try_export
def export_edgetpu(file, prefix=colorstr("Edge TPU:")):
"""
Exports a YOLOv5 model to Edge TPU compatible TFLite format; requires Linux and Edge TPU compiler.
https://coral.ai/docs/edgetpu/models-intro/
"""
cmd = "edgetpu_compiler –version"
help_url = "https://coral.ai/docs/edgetpu/compiler/"
assert platform.system() == "Linux", f"export only supported on Linux. See {help_url}"
if subprocess.run(f"{cmd} > /dev/null 2>&1", shell=True).returncode != 0:
LOGGER.info(f"\\n{prefix} export requires Edge TPU compiler. Attempting install from {help_url}")
sudo = subprocess.run("sudo –version >/dev/null", shell=True).returncode == 0 # sudo installed on system
for c in (
"curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -",
'echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list',
"sudo apt-get update",
"sudo apt-get install edgetpu-compiler",
):
subprocess.run(c if sudo else c.replace("sudo ", ""), shell=True, check=True)
ver = subprocess.run(cmd, shell=True, capture_output=True, check=True).stdout.decode().split()[-1]
LOGGER.info(f"\\n{prefix} starting export with Edge TPU compiler {ver}…")
f = str(file).replace(".pt", "-int8_edgetpu.tflite") # Edge TPU model
f_tfl = str(file).replace(".pt", "-int8.tflite") # TFLite model
subprocess.run(
[
"edgetpu_compiler",
"-s",
"-d",
"-k",
"10",
"–out_dir",
str(file.parent),
f_tfl,
],
check=True,
)
return f, None
@try_export
def export_tfjs(file, int8, prefix=colorstr("TensorFlow.js:")):
"""Exports a YOLOv5 model to TensorFlow.js format, optionally with uint8 quantization."""
check_requirements("tensorflowjs")
import tensorflowjs as tfjs
LOGGER.info(f"\\n{prefix} starting export with tensorflowjs {tfjs.__version__}…")
f = str(file).replace(".pt", "_web_model") # js dir
f_pb = file.with_suffix(".pb") # *.pb path
f_json = f"{f}/model.json" # *.json path
args = [
"tensorflowjs_converter",
"–input_format=tf_frozen_model",
"–quantize_uint8" if int8 else "",
"–output_node_names=Identity,Identity_1,Identity_2,Identity_3",
str(f_pb),
f,
]
subprocess.run([arg for arg in args if arg], check=True)
json = Path(f_json).read_text()
with open(f_json, "w") as j: # sort JSON Identity_* in ascending order
subst = re.sub(
r'{"outputs": {"Identity.?.?": {"name": "Identity.?.?"}, '
r'"Identity.?.?": {"name": "Identity.?.?"}, '
r'"Identity.?.?": {"name": "Identity.?.?"}, '
r'"Identity.?.?": {"name": "Identity.?.?"}}}',
r'{"outputs": {"Identity": {"name": "Identity"}, '
r'"Identity_1": {"name": "Identity_1"}, '
r'"Identity_2": {"name": "Identity_2"}, '
r'"Identity_3": {"name": "Identity_3"}}}',
json,
)
j.write(subst)
return f, None
def add_tflite_metadata(file, metadata, num_outputs):
"""
Adds TFLite metadata to a model file, supporting multiple outputs, as specified by TensorFlow guidelines.
https://www.tensorflow.org/lite/models/convert/metadata
"""
with contextlib.suppress(ImportError):
# check_requirements('tflite_support')
from tflite_support import flatbuffers
from tflite_support import metadata as _metadata
from tflite_support import metadata_schema_py_generated as _metadata_fb
tmp_file = Path("/tmp/meta.txt")
with open(tmp_file, "w") as meta_f:
meta_f.write(str(metadata))
model_meta = _metadata_fb.ModelMetadataT()
label_file = _metadata_fb.AssociatedFileT()
label_file.name = tmp_file.name
model_meta.associatedFiles = [label_file]
subgraph = _metadata_fb.SubGraphMetadataT()
subgraph.inputTensorMetadata = [_metadata_fb.TensorMetadataT()]
subgraph.outputTensorMetadata = [_metadata_fb.TensorMetadataT()] * num_outputs
model_meta.subgraphMetadata = [subgraph]
b = flatbuffers.Builder(0)
b.Finish(model_meta.Pack(b), _metadata.MetadataPopulator.METADATA_FILE_IDENTIFIER)
metadata_buf = b.Output()
populator = _metadata.MetadataPopulator.with_model_file(file)
populator.load_metadata_buffer(metadata_buf)
populator.load_associated_files([str(tmp_file)])
populator.populate()
tmp_file.unlink()
def pipeline_coreml(model, im, file, names, y, prefix=colorstr("CoreML Pipeline:")):
"""Converts a PyTorch YOLOv5 model to CoreML format with NMS, handling different input/output shapes and saving the
model.
"""
import coremltools as ct
from PIL import Image
print(f"{prefix} starting pipeline with coremltools {ct.__version__}…")
batch_size, ch, h, w = list(im.shape) # BCHW
t = time.time()
# YOLOv5 Output shapes
spec = model.get_spec()
out0, out1 = iter(spec.description.output)
if platform.system() == "Darwin":
img = Image.new("RGB", (w, h)) # img(192 width, 320 height)
# img = torch.zeros((*opt.img_size, 3)).numpy() # img size(320,192,3) iDetection
out = model.predict({"image": img})
out0_shape, out1_shape = out[out0.name].shape, out[out1.name].shape
else: # linux and windows can not run model.predict(), get sizes from pytorch output y
s = tuple(y[0].shape)
out0_shape, out1_shape = (s[1], s[2] – 5), (s[1], 4) # (3780, 80), (3780, 4)
# Checks
nx, ny = spec.description.input[0].type.imageType.width, spec.description.input[0].type.imageType.height
na, nc = out0_shape
# na, nc = out0.type.multiArrayType.shape # number anchors, classes
assert len(names) == nc, f"{len(names)} names found for nc={nc}" # check
# Define output shapes (missing)
out0.type.multiArrayType.shape[:] = out0_shape # (3780, 80)
out1.type.multiArrayType.shape[:] = out1_shape # (3780, 4)
# spec.neuralNetwork.preprocessing[0].featureName = '0'
# Flexible input shapes
# from coremltools.models.neural_network import flexible_shape_utils
# s = [] # shapes
# s.append(flexible_shape_utils.NeuralNetworkImageSize(320, 192))
# s.append(flexible_shape_utils.NeuralNetworkImageSize(640, 384)) # (height, width)
# flexible_shape_utils.add_enumerated_image_sizes(spec, feature_name='image', sizes=s)
# r = flexible_shape_utils.NeuralNetworkImageSizeRange() # shape ranges
# r.add_height_range((192, 640))
# r.add_width_range((192, 640))
# flexible_shape_utils.update_image_size_range(spec, feature_name='image', size_range=r)
# Print
print(spec.description)
# Model from spec
model = ct.models.MLModel(spec)
# 3. Create NMS protobuf
nms_spec = ct.proto.Model_pb2.Model()
nms_spec.specificationVersion = 5
for i in range(2):
decoder_output = model._spec.description.output[i].SerializeToString()
nms_spec.description.input.add()
nms_spec.description.input[i].ParseFromString(decoder_output)
nms_spec.description.output.add()
nms_spec.description.output[i].ParseFromString(decoder_output)
nms_spec.description.output[0].name = "confidence"
nms_spec.description.output[1].name = "coordinates"
output_sizes = [nc, 4]
for i in range(2):
ma_type = nms_spec.description.output[i].type.multiArrayType
ma_type.shapeRange.sizeRanges.add()
ma_type.shapeRange.sizeRanges[0].lowerBound = 0
ma_type.shapeRange.sizeRanges[0].upperBound = -1
ma_type.shapeRange.sizeRanges.add()
ma_type.shapeRange.sizeRanges[1].lowerBound = output_sizes[i]
ma_type.shapeRange.sizeRanges[1].upperBound = output_sizes[i]
del ma_type.shape[:]
nms = nms_spec.nonMaximumSuppression
nms.confidenceInputFeatureName = out0.name # 1x507x80
nms.coordinatesInputFeatureName = out1.name # 1x507x4
nms.confidenceOutputFeatureName = "confidence"
nms.coordinatesOutputFeatureName = "coordinates"
nms.iouThresholdInputFeatureName = "iouThreshold"
nms.confidenceThresholdInputFeatureName = "confidenceThreshold"
nms.iouThreshold = 0.45
nms.confidenceThreshold = 0.25
nms.pickTop.perClass = True
nms.stringClassLabels.vector.extend(names.values())
nms_model = ct.models.MLModel(nms_spec)
# 4. Pipeline models together
pipeline = ct.models.pipeline.Pipeline(
input_features=[
("image", ct.models.datatypes.Array(3, ny, nx)),
("iouThreshold", ct.models.datatypes.Double()),
("confidenceThreshold", ct.models.datatypes.Double()),
],
output_features=["confidence", "coordinates"],
)
pipeline.add_model(model)
pipeline.add_model(nms_model)
# Correct datatypes
pipeline.spec.description.input[0].ParseFromString(model._spec.description.input[0].SerializeToString())
pipeline.spec.description.output[0].ParseFromString(nms_model._spec.description.output[0].SerializeToString())
pipeline.spec.description.output[1].ParseFromString(nms_model._spec.description.output[1].SerializeToString())
# Update metadata
pipeline.spec.specificationVersion = 5
pipeline.spec.description.metadata.versionString = "https://github.com/ultralytics/yolov5"
pipeline.spec.description.metadata.shortDescription = "https://github.com/ultralytics/yolov5"
pipeline.spec.description.metadata.author = "glenn.jocher@ultralytics.com"
pipeline.spec.description.metadata.license = "https://github.com/ultralytics/yolov5/blob/master/LICENSE"
pipeline.spec.description.metadata.userDefined.update(
{
"classes": ",".join(names.values()),
"iou_threshold": str(nms.iouThreshold),
"confidence_threshold": str(nms.confidenceThreshold),
}
)
# Save the model
f = file.with_suffix(".mlmodel") # filename
model = ct.models.MLModel(pipeline.spec)
model.input_description["image"] = "Input image"
model.input_description["iouThreshold"] = f"(optional) IOU Threshold override (default: {nms.iouThreshold})"
model.input_description["confidenceThreshold"] = (
f"(optional) Confidence Threshold override (default: {nms.confidenceThreshold})"
)
model.output_description["confidence"] = 'Boxes × Class confidence (see user-defined metadata "classes")'
model.output_description["coordinates"] = "Boxes × [x, y, width, height] (relative to image size)"
model.save(f) # pipelined
print(f"{prefix} pipeline success ({time.time() – t:.2f}s), saved as {f} ({file_size(f):.1f} MB)")
@smart_inference_mode()
def run(
data=ROOT / "data/coco128.yaml", # 'dataset.yaml path'
weights=ROOT / "yolov5s.pt", # weights path
imgsz=(640, 640), # image (height, width)
batch_size=1, # batch size
device="cpu", # cuda device, i.e. 0 or 0,1,2,3 or cpu
include=("torchscript", "onnx"), # include formats
half=False, # FP16 half-precision export
inplace=False, # set YOLOv5 Detect() inplace=True
keras=False, # use Keras
optimize=False, # TorchScript: optimize for mobile
int8=False, # CoreML/TF INT8 quantization
per_tensor=False, # TF per tensor quantization
dynamic=False, # ONNX/TF/TensorRT: dynamic axes
simplify=False, # ONNX: simplify model
opset=12, # ONNX: opset version
verbose=False, # TensorRT: verbose log
workspace=4, # TensorRT: workspace size (GB)
nms=False, # TF: add NMS to model
agnostic_nms=False, # TF: add agnostic NMS to model
topk_per_class=100, # TF.js NMS: topk per class to keep
topk_all=100, # TF.js NMS: topk for all classes to keep
iou_thres=0.45, # TF.js NMS: IoU threshold
conf_thres=0.25, # TF.js NMS: confidence threshold
):
t = time.time()
include = [x.lower() for x in include] # to lowercase
fmts = tuple(export_formats()["Argument"][1:]) # –include arguments
flags = [x in include for x in fmts]
assert sum(flags) == len(include), f"ERROR: Invalid –include {include}, valid –include arguments are {fmts}"
jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle = flags # export booleans
file = Path(url2file(weights) if str(weights).startswith(("http:/", "https:/")) else weights) # PyTorch weights
# Load PyTorch model
device = select_device(device)
if half:
assert device.type != "cpu" or coreml, "–half only compatible with GPU export, i.e. use –device 0"
assert not dynamic, "–half not compatible with –dynamic, i.e. use either –half or –dynamic but not both"
model = attempt_load(weights, device=device, inplace=True, fuse=True) # load FP32 model
# Checks
imgsz *= 2 if len(imgsz) == 1 else 1 # expand
if optimize:
assert device.type == "cpu", "–optimize not compatible with cuda devices, i.e. use –device cpu"
# Input
gs = int(max(model.stride)) # grid size (max stride)
imgsz = [check_img_size(x, gs) for x in imgsz] # verify img_size are gs-multiples
im = torch.zeros(batch_size, 3, *imgsz).to(device) # image size(1,3,320,192) BCHW iDetection
# Update model
model.eval()
for k, m in model.named_modules():
if isinstance(m, Detect):
m.inplace = inplace
m.dynamic = dynamic
m.export = True
for _ in range(2):
y = model(im) # dry runs
if half and not coreml:
im, model = im.half(), model.half() # to FP16
shape = tuple((y[0] if isinstance(y, tuple) else y).shape) # model output shape
metadata = {"stride": int(max(model.stride)), "names": model.names} # model metadata
LOGGER.info(f"\\n{colorstr('PyTorch:')} starting from {file} with output shape {shape} ({file_size(file):.1f} MB)")
# Exports
f = [""] * len(fmts) # exported filenames
warnings.filterwarnings(action="ignore", category=torch.jit.TracerWarning) # suppress TracerWarning
if jit: # TorchScript
f[0], _ = export_torchscript(model, im, file, optimize)
if engine: # TensorRT required before ONNX
f[1], _ = export_engine(model, im, file, half, dynamic, simplify, workspace, verbose)
if onnx or xml: # OpenVINO requires ONNX
f[2], _ = export_onnx(model, im, file, opset, dynamic, simplify)
if xml: # OpenVINO
f[3], _ = export_openvino(file, metadata, half, int8, data)
if coreml: # CoreML
f[4], ct_model = export_coreml(model, im, file, int8, half, nms)
if nms:
pipeline_coreml(ct_model, im, file, model.names, y)
if any((saved_model, pb, tflite, edgetpu, tfjs)): # TensorFlow formats
assert not tflite or not tfjs, "TFLite and TF.js models must be exported separately, please pass only one type."
assert not isinstance(model, ClassificationModel), "ClassificationModel export to TF formats not yet supported."
f[5], s_model = export_saved_model(
model.cpu(),
im,
file,
dynamic,
tf_nms=nms or agnostic_nms or tfjs,
agnostic_nms=agnostic_nms or tfjs,
topk_per_class=topk_per_class,
topk_all=topk_all,
iou_thres=iou_thres,
conf_thres=conf_thres,
keras=keras,
)
if pb or tfjs: # pb prerequisite to tfjs
f[6], _ = export_pb(s_model, file)
if tflite or edgetpu:
f[7], _ = export_tflite(
s_model, im, file, int8 or edgetpu, per_tensor, data=data, nms=nms, agnostic_nms=agnostic_nms
)
if edgetpu:
f[8], _ = export_edgetpu(file)
add_tflite_metadata(f[8] or f[7], metadata, num_outputs=len(s_model.outputs))
if tfjs:
f[9], _ = export_tfjs(file, int8)
if paddle: # PaddlePaddle
f[10], _ = export_paddle(model, im, file, metadata)
# Finish
f = [str(x) for x in f if x] # filter out '' and None
if any(f):
cls, det, seg = (isinstance(model, x) for x in (ClassificationModel, DetectionModel, SegmentationModel)) # type
det &= not seg # segmentation models inherit from SegmentationModel(DetectionModel)
dir = Path("segment" if seg else "classify" if cls else "")
h = "–half" if half else "" # –half FP16 inference arg
s = (
"# WARNING ⚠️ ClassificationModel not yet supported for PyTorch Hub AutoShape inference"
if cls
else "# WARNING ⚠️ SegmentationModel not yet supported for PyTorch Hub AutoShape inference"
if seg
else ""
)
LOGGER.info(
f'\\nExport complete ({time.time() – t:.1f}s)'
f"\\nResults saved to {colorstr('bold', file.parent.resolve())}"
f"\\nDetect: python {dir / ('detect.py' if det else 'predict.py')} –weights {f[-1]} {h}"
f"\\nValidate: python {dir / 'val.py'} –weights {f[-1]} {h}"
f"\\nPyTorch Hub: model = torch.hub.load('ultralytics/yolov5', 'custom', '{f[-1]}') {s}"
f'\\nVisualize: https://netron.app'
)
return f # return list of exported files/dirs
def parse_opt(known=False):
"""Parses command-line arguments for YOLOv5 model export configurations, returning the parsed options."""
parser = argparse.ArgumentParser()
parser.add_argument("–data", type=str, default=ROOT / "data/coco128.yaml", help="dataset.yaml path")
parser.add_argument("–weights", nargs="+", type=str, default=ROOT / "yolov5s.pt", help="model.pt path(s)")
parser.add_argument("–imgsz", "–img", "–img-size", nargs="+", type=int, default=[640, 640], help="image (h, w)")
parser.add_argument("–batch-size", type=int, default=1, help="batch size")
parser.add_argument("–device", default="cpu", help="cuda device, i.e. 0 or 0,1,2,3 or cpu")
parser.add_argument("–half", action="store_true", help="FP16 half-precision export")
parser.add_argument("–inplace", action="store_true", help="set YOLOv5 Detect() inplace=True")
parser.add_argument("–keras", action="store_true", help="TF: use Keras")
parser.add_argument("–optimize", action="store_true", help="TorchScript: optimize for mobile")
parser.add_argument("–int8", action="store_true", help="CoreML/TF/OpenVINO INT8 quantization")
parser.add_argument("–per-tensor", action="store_true", help="TF per-tensor quantization")
parser.add_argument("–dynamic", action="store_true", help="ONNX/TF/TensorRT: dynamic axes")
parser.add_argument("–simplify", action="store_true", help="ONNX: simplify model")
parser.add_argument("–opset", type=int, default=17, help="ONNX: opset version")
parser.add_argument("–verbose", action="store_true", help="TensorRT: verbose log")
parser.add_argument("–workspace", type=int, default=4, help="TensorRT: workspace size (GB)")
parser.add_argument("–nms", action="store_true", help="TF: add NMS to model")
parser.add_argument("–agnostic-nms", action="store_true", help="TF: add agnostic NMS to model")
parser.add_argument("–topk-per-class", type=int, default=100, help="TF.js NMS: topk per class to keep")
parser.add_argument("–topk-all", type=int, default=100, help="TF.js NMS: topk for all classes to keep")
parser.add_argument("–iou-thres", type=float, default=0.45, help="TF.js NMS: IoU threshold")
parser.add_argument("–conf-thres", type=float, default=0.25, help="TF.js NMS: confidence threshold")
parser.add_argument(
"–include",
nargs="+",
default=["torchscript"],
help="torchscript, onnx, openvino, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle",
)
opt = parser.parse_known_args()[0] if known else parser.parse_args()
print_args(vars(opt))
return opt
def main(opt):
"""Executes the YOLOv5 model inference or export with specified weights and options."""
for opt.weights in opt.weights if isinstance(opt.weights, list) else [opt.weights]:
run(**vars(opt))
if __name__ == "__main__":
opt = parse_opt()
main(opt)
hubconf.py
# YOLOv5 🚀 by Ultralytics, AGPL-3.0 license
"""
PyTorch Hub models https://pytorch.org/hub/ultralytics_yolov5
Usage:
import torch
model = torch.hub.load('ultralytics/yolov5', 'yolov5s') # official model
model = torch.hub.load('ultralytics/yolov5:master', 'yolov5s') # from branch
model = torch.hub.load('ultralytics/yolov5', 'custom', 'yolov5s.pt') # custom/local model
model = torch.hub.load('.', 'custom', 'yolov5s.pt', source='local') # local repo
"""
import torch
def _create(name, pretrained=True, channels=3, classes=80, autoshape=True, verbose=True, device=None):
"""
Creates or loads a YOLOv5 model.
Arguments:
name (str): model name 'yolov5s' or path 'path/to/best.pt'
pretrained (bool): load pretrained weights into the model
channels (int): number of input channels
classes (int): number of model classes
autoshape (bool): apply YOLOv5 .autoshape() wrapper to model
verbose (bool): print all information to screen
device (str, torch.device, None): device to use for model parameters
Returns:
YOLOv5 model
"""
from pathlib import Path
from models.common import AutoShape, DetectMultiBackend
from models.experimental import attempt_load
from models.yolo import ClassificationModel, DetectionModel, SegmentationModel
from utils.downloads import attempt_download
from utils.general import LOGGER, ROOT, check_requirements, intersect_dicts, logging
from utils.torch_utils import select_device
if not verbose:
LOGGER.setLevel(logging.WARNING)
check_requirements(ROOT / "requirements.txt", exclude=("opencv-python", "tensorboard", "thop"))
name = Path(name)
path = name.with_suffix(".pt") if name.suffix == "" and not name.is_dir() else name # checkpoint path
try:
device = select_device(device)
if pretrained and channels == 3 and classes == 80:
try:
model = DetectMultiBackend(path, device=device, fuse=autoshape) # detection model
if autoshape:
if model.pt and isinstance(model.model, ClassificationModel):
LOGGER.warning(
"WARNING ⚠️ YOLOv5 ClassificationModel is not yet AutoShape compatible. "
"You must pass torch tensors in BCHW to this model, i.e. shape(1,3,224,224)."
)
elif model.pt and isinstance(model.model, SegmentationModel):
LOGGER.warning(
"WARNING ⚠️ YOLOv5 SegmentationModel is not yet AutoShape compatible. "
"You will not be able to run inference with this model."
)
else:
model = AutoShape(model) # for file/URI/PIL/cv2/np inputs and NMS
except Exception:
model = attempt_load(path, device=device, fuse=False) # arbitrary model
else:
cfg = list((Path(__file__).parent / "models").rglob(f"{path.stem}.yaml"))[0] # model.yaml path
model = DetectionModel(cfg, channels, classes) # create model
if pretrained:
ckpt = torch.load(attempt_download(path), map_location=device) # load
csd = ckpt["model"].float().state_dict() # checkpoint state_dict as FP32
csd = intersect_dicts(csd, model.state_dict(), exclude=["anchors"]) # intersect
model.load_state_dict(csd, strict=False) # load
if len(ckpt["model"].names) == classes:
model.names = ckpt["model"].names # set class names attribute
if not verbose:
LOGGER.setLevel(logging.INFO) # reset to default
return model.to(device)
except Exception as e:
help_url = "https://docs.ultralytics.com/yolov5/tutorials/pytorch_hub_model_loading"
s = f"{e}. Cache may be out of date, try `force_reload=True` or see {help_url} for help."
raise Exception(s) from e
def custom(path="path/to/model.pt", autoshape=True, _verbose=True, device=None):
"""Loads a custom or local YOLOv5 model from a given path with optional autoshaping and device specification."""
return _create(path, autoshape=autoshape, verbose=_verbose, device=device)
def yolov5n(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Instantiates the YOLOv5-nano model with options for pretraining, input channels, class count, autoshaping,
verbosity, and device.
"""
return _create("yolov5n", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5s(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Creates YOLOv5-small model with options for pretraining, input channels, class count, autoshaping, verbosity, and
device.
"""
return _create("yolov5s", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5m(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Instantiates the YOLOv5-medium model with customizable pretraining, channel count, class count, autoshaping,
verbosity, and device.
"""
return _create("yolov5m", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5l(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Creates YOLOv5-large model with options for pretraining, channels, classes, autoshaping, verbosity, and device
selection.
"""
return _create("yolov5l", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5x(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Instantiates the YOLOv5-xlarge model with customizable pretraining, channel count, class count, autoshaping,
verbosity, and device.
"""
return _create("yolov5x", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5n6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Creates YOLOv5-nano-P6 model with options for pretraining, channels, classes, autoshaping, verbosity, and
device.
"""
return _create("yolov5n6", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5s6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Instantiate YOLOv5-small-P6 model with options for pretraining, input channels, number of classes, autoshaping,
verbosity, and device selection.
"""
return _create("yolov5s6", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5m6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Creates YOLOv5-medium-P6 model with options for pretraining, channel count, class count, autoshaping, verbosity,
and device.
"""
return _create("yolov5m6", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5l6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Instantiates the YOLOv5-large-P6 model with customizable pretraining, channel and class counts, autoshaping,
verbosity, and device selection.
"""
return _create("yolov5l6", pretrained, channels, classes, autoshape, _verbose, device)
def yolov5x6(pretrained=True, channels=3, classes=80, autoshape=True, _verbose=True, device=None):
"""Creates YOLOv5-xlarge-P6 model with options for pretraining, channels, classes, autoshaping, verbosity, and
device.
"""
return _create("yolov5x6", pretrained, channels, classes, autoshape, _verbose, device)
if __name__ == "__main__":
import argparse
from pathlib import Path
import numpy as np
from PIL import Image
from utils.general import cv2, print_args
# Argparser
parser = argparse.ArgumentParser()
parser.add_argument("–model", type=str, default="yolov5s", help="model name")
opt = parser.parse_args()
print_args(vars(opt))
# Model
model = _create(name=opt.model, pretrained=True, channels=3, classes=80, autoshape=True, verbose=True)
# model = custom(path='path/to/model.pt') # custom
# Images
imgs = [
"data/images/zidane.jpg", # filename
Path("data/images/zidane.jpg"), # Path
"https://ultralytics.com/images/zidane.jpg", # URI
cv2.imread("data/images/bus.jpg")[:, :, ::-1], # OpenCV
Image.open("data/images/bus.jpg"), # PIL
np.zeros((320, 640, 3)),
] # numpy
# Inference
results = model(imgs, size=320) # batched inference
# Results
results.print()
results.save()
gui.py
import sys
import os
import cv2
import datetime
import time
import argparse
import csv
import platform
from pathlib import Path
import torch
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.Qt import *
import threading #——->>>1
t_flag=2
save_path_ = None
'''
# 模型加载部分
'''
FILE = Path(__file__).resolve()
ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT)) # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
from ultralytics.utils.plotting import Annotator, colors, save_one_box
from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
from utils.general import (
LOGGER,
Profile,
check_file,
check_img_size,
check_imshow,
check_requirements,
colorstr,
cv2,
increment_path,
non_max_suppression,
print_args,
scale_boxes,
strip_optimizer,
xyxy2xywh,
)
from utils.torch_utils import select_device, smart_inference_mode
@smart_inference_mode()
def run(
weights=ROOT / "yolov5s.pt", # model path or triton URL
source=ROOT / "data/images", # file/dir/URL/glob/screen/0(webcam)
data=ROOT / "data/coco128.yaml", # dataset.yaml path
imgsz=(640, 640), # inference size (height, width)
conf_thres=0.25, # confidence threshold
iou_thres=0.45, # NMS IOU threshold
max_det=1000, # maximum detections per image
device="", # cuda device, i.e. 0 or 0,1,2,3 or cpu
view_img=False, # show results
save_txt=False, # save results to *.txt
save_csv=False, # save results in CSV format
save_conf=False, # save confidences in –save-txt labels
save_crop=False, # save cropped prediction boxes
nosave=False, # do not save images/videos
classes=None, # filter by class: –class 0, or –class 0 2 3
agnostic_nms=False, # class-agnostic NMS
augment=False, # augmented inference
visualize=False, # visualize features
update=False, # update all models
project=ROOT / "runs/detect", # save results to project/name
name="exp", # save results to project/name
exist_ok=False, # existing project/name ok, do not increment
line_thickness=3, # bounding box thickness (pixels)
hide_labels=False, # hide labels
hide_conf=False, # hide confidences
half=False, # use FP16 half-precision inference
dnn=False, # use OpenCV DNN for ONNX inference
vid_stride=1, # video frame-rate stride
):
global save_path_
count = 0
source = str(source)
save_img = not nosave and not source.endswith(".txt") # save inference images
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
is_url = source.lower().startswith(("rtsp://", "rtmp://", "http://", "https://"))
webcam = source.isnumeric() or source.endswith(".streams") or (is_url and not is_file)
screenshot = source.lower().startswith("screen")
if is_url and is_file:
source = check_file(source) # download
# Directories
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run
(save_dir / "labels" if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
# Load model
device = select_device(device)
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
stride, names, pt = model.stride, model.names, model.pt
imgsz = check_img_size(imgsz, s=stride) # check image size
# Dataloader
bs = 1 # batch_size
if webcam:
view_img = check_imshow(warn=True)
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
bs = len(dataset)
elif screenshot:
dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
else:
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
vid_path, vid_writer = [None] * bs, [None] * bs
# Run inference
model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz)) # warmup
seen, windows, dt = 0, [], (Profile(device=device), Profile(device=device), Profile(device=device))
for path, im, im0s, vid_cap, s in dataset:
with dt[0]:
im = torch.from_numpy(im).to(model.device)
im = im.half() if model.fp16 else im.float() # uint8 to fp16/32
im /= 255 # 0 – 255 to 0.0 – 1.0
if len(im.shape) == 3:
im = im[None] # expand for batch dim
if model.xml and im.shape[0] > 1:
ims = torch.chunk(im, im.shape[0], 0)
# Inference
with dt[1]:
visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
if model.xml and im.shape[0] > 1:
pred = None
for image in ims:
if pred is None:
pred = model(image, augment=augment, visualize=visualize).unsqueeze(0)
else:
pred = torch.cat((pred, model(image, augment=augment, visualize=visualize).unsqueeze(0)), dim=0)
pred = [pred, None]
else:
pred = model(im, augment=augment, visualize=visualize)
# NMS
with dt[2]:
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
# Second-stage classifier (optional)
# pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)
# Define the path for the CSV file
csv_path = save_dir / "predictions.csv"
# Create or append to the CSV file
def write_to_csv(image_name, prediction, confidence):
"""Writes prediction data for an image to a CSV file, appending if the file exists."""
data = {"Image Name": image_name, "Prediction": prediction, "Confidence": confidence}
with open(csv_path, mode="a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=data.keys())
if not csv_path.is_file():
writer.writeheader()
writer.writerow(data)
# Process predictions
for i, det in enumerate(pred): # per image
seen += 1
if webcam: # batch_size >= 1
p, im0, frame = path[i], im0s[i].copy(), dataset.count
s += f"{i}: "
else:
p, im0, frame = path, im0s.copy(), getattr(dataset, "frame", 0)
p = Path(p) # to Path
save_path = str(save_dir / p.name) # im.jpg
txt_path = str(save_dir / "labels" / p.stem) + ("" if dataset.mode == "image" else f"_{frame}") # im.txt
s += "%gx%g " % im.shape[2:] # print string
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
imc = im0.copy() if save_crop else im0 # for save_crop
annotator = Annotator(im0, line_width=line_thickness, example=str(names))
if len(det):
# Rescale boxes from img_size to im0 size
det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()
# Print results
for c in det[:, 5].unique():
n = (det[:, 5] == c).sum() # detections per class
s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string
# Write results
for *xyxy, conf, cls in reversed(det):
c = int(cls) # integer class
label = names[c] if hide_conf else f"{names[c]}"
confidence = float(conf)
confidence_str = f"{confidence:.2f}"
if save_csv:
write_to_csv(p.name, label, confidence_str)
if save_txt: # Write to file
xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
with open(f"{txt_path}.txt", "a") as f:
f.write(("%g " * len(line)).rstrip() % line + "\\n")
if save_img or save_crop or view_img: # Add bbox to image
c = int(cls) # integer class
label = None if hide_labels else (names[c] if hide_conf else f"{names[c]} {conf:.2f}")
annotator.box_label(xyxy, label, color=colors(c, True))
if save_crop:
save_one_box(xyxy, imc, file=save_dir / "crops" / names[c] / f"{p.stem}.jpg", BGR=True)
# Stream results
im0 = annotator.result()
# time.sleep(1)
count =count+1
cv2.imwrite(save_path + "/image_{}.jpg".format(count),im0)
# global image_show
# image_show = im0
# print(count,image_show.shape)
'''
if view_img:
cv2.namedWindow("YOLOv5 Detection", cv2.WINDOW_NORMAL)
cv2.imshow("YOLOv5 Detection", im0)
cv2.waitKey(0) # 等待按键按下
cv2.destroyAllWindows() # 关闭窗口
'''
if view_img:
if platform.system() == "Linux" and p not in windows:
windows.append(p)
cv2.namedWindow(str(p), cv2.WINDOW_NORMAL | cv2.WINDOW_KEEPRATIO) # allow window resize (Linux)
cv2.resizeWindow(str(p), im0.shape[1], im0.shape[0])
count = count + 1
cv2.imwrite("image_{}.jpg".format(count), im0)
global t_flag
t_flag=0
# global image_return
# image_return = im0
# cv2.imshow(str(p), im0)
# cv2.waitKey(1) # 1 millisecond
# return im0
else:
t_flag = 1
# Save results (image with detections)
if save_img:
if dataset.mode == "image":
cv2.imwrite(save_path, im0)
print("save_path",save_path)
save_path_ = save_path
else: # 'video' or 'stream'
if vid_path[i] != save_path: # new video
vid_path[i] = save_path
if isinstance(vid_writer[i], cv2.VideoWriter):
vid_writer[i].release() # release previous video writer
if vid_cap: # video
fps = vid_cap.get(cv2.CAP_PROP_FPS)
w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
else: # stream
# fps, w, h = 30, im0.shape[1], im0.shape[0]
fps, w, h = 30, im0.shape[1], im0.shape[0]
print("image's shape is :",im0.shape)
save_path = str(Path(save_path).with_suffix(".mp4")) # force *.mp4 suffix on results videos
vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
global image_return
image_return = im0
vid_writer[i].write(im0)
# Print time (inference-only)
LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms")
# Print results
t = tuple(x.t / seen * 1e3 for x in dt) # speeds per image
LOGGER.info(f"Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}" % t)
if save_txt or save_img:
s = f"\\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ""
LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")
if update:
strip_optimizer(weights[0]) # update model (to fix SourceChangeWarning)
def parse_opt(weight,source):
"""Parses command-line arguments for YOLOv5 detection, setting inference options and model configurations."""
parser = argparse.ArgumentParser()
parser.add_argument("–weights", nargs="+", type=str, default=weight, help="model path or triton URL")
parser.add_argument("–source", type=str, default=source, help="file/dir/URL/glob/screen/0(webcam)")
parser.add_argument("–data", type=str, default=ROOT / "data/coco128.yaml", help="(optional) dataset.yaml path")
parser.add_argument("–imgsz", "–img", "–img-size", nargs="+", type=int, default=[640], help="inference size h,w")
parser.add_argument("–conf-thres", type=float, default=0.25, help="confidence threshold")
parser.add_argument("–iou-thres", type=float, default=0.45, help="NMS IoU threshold")
parser.add_argument("–max-det", type=int, default=1000, help="maximum detections per image")
parser.add_argument("–device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu")
parser.add_argument("–view-img", action="store_true", help="show results")
parser.add_argument("–save-txt", action="store_true", help="save results to *.txt")
parser.add_argument("–save-csv", action="store_true", help="save results in CSV format")
parser.add_argument("–save-conf", action="store_true", help="save confidences in –save-txt labels")
parser.add_argument("–save-crop", action="store_true", help="save cropped prediction boxes")
parser.add_argument("–nosave", action="store_true", help="do not save images/videos")
parser.add_argument("–classes", nargs="+", type=int, help="filter by class: –classes 0, or –classes 0 2 3")
parser.add_argument("–agnostic-nms", action="store_true", help="class-agnostic NMS")
parser.add_argument("–augment", action="store_true", help="augmented inference")
parser.add_argument("–visualize", action="store_true", help="visualize features")
parser.add_argument("–update", action="store_true", help="update all models")
parser.add_argument("–project", default=ROOT / "runs/detect", help="save results to project/name")
parser.add_argument("–name", default="exp", help="save results to project/name")
parser.add_argument("–exist-ok", action="store_true", help="existing project/name ok, do not increment")
parser.add_argument("–line-thickness", default=3, type=int, help="bounding box thickness (pixels)")
parser.add_argument("–hide-labels", default=False, action="store_true", help="hide labels")
parser.add_argument("–hide-conf", default=False, action="store_true", help="hide confidences")
parser.add_argument("–half", action="store_true", help="use FP16 half-precision inference")
parser.add_argument("–dnn", action="store_true", help="use OpenCV DNN for ONNX inference")
parser.add_argument("–vid-stride", type=int, default=1, help="video frame-rate stride")
opt = parser.parse_args()
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
print_args(vars(opt))
return opt
def main(opt):
"""Executes YOLOv5 model inference with given options, checking requirements before running the model."""
check_requirements(ROOT / "requirements.txt", exclude=("tensorboard", "thop"))
run(**vars(opt))
# 定义全局变量
# *param: weight–权重路径
# *param: source–加载的图片路径
# *param: save–保存路径
weight = None
source = None
save = None
class VehiclePedestriansApp(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.setGeometry(100,100,1200,800)
self.setWindowTitle('监控系统')
self.palette = QPalette()
self.palette.setBrush(QPalette.Background, QBrush(QPixmap("background.jpeg")))
self.setPalette(self.palette)
self.open_model_button = QPushButton('选择输入模型',self)
self.open_model_button.setGeometry(50,10,200,30)
self.open_model_button.clicked.connect(self.selectModel)
self.open_model_button.setStyleSheet('''
QPushButton {
border-style: solid;
border-width: 2px;
border-color: black;
border-radius: 10px;
background-color: transparent;
padding: 5px;
}
QPushButton:hover {
background-color: #DDDDDD; /* Change to desired hover color */
}
QPushButton:pressed {
background-color: #BBBBBB; /* Change to desired pressed color */
}
''')
self.model_label = QLabel(self)
self.model_label.setGeometry(260, 10, 800, 30)
self.save_button = QPushButton('选择保存路径', self)
self.save_button.setGeometry(50, 60, 200, 30)
self.save_button.clicked.connect(self.selectSavePath)
self.save_button.setStyleSheet('''
QPushButton {
border-style: solid;
border-width: 2px;
border-color: black;
border-radius: 10px;
background-color: transparent;
padding: 5px;
}
QPushButton:hover {
background-color: #DDDDDD; /* Change to desired hover color */
}
QPushButton:pressed {
background-color: #BBBBBB; /* Change to desired pressed color */
}
''')
self.save_label = QLabel(self)
self.save_label.setGeometry(260, 60, 800, 30)
self.input_label = QLabel('输入',self)
self.input_label.setGeometry(50, 110, 50, 30)
self.input_lineedit = QLineEdit(self)
self.input_lineedit.setGeometry(100, 110, 300, 30)
self.detect_button = QPushButton('开始检测', self)
self.detect_button.setGeometry(420, 110, 200, 30)
self.detect_button.clicked.connect(self.start_detect)
self.detect_button.setStyleSheet('''
QPushButton {
border-style: solid;
border-width: 2px;
border-color: black;
border-radius: 10px;
background-color: transparent;
padding: 5px;
}
QPushButton:hover {
background-color: #DDDDDD; /* Change to desired hover color */
}
QPushButton:pressed {
background-color: #BBBBBB; /* Change to desired pressed color */
}
''')
self.image_label = QLabel(self)
self.image_label.setGeometry(300, 200, 480, 640)
def selectModel(self):
options = QFileDialog.Options()
model_path, _ = QFileDialog.getOpenFileName(self, "选择模型文件", "", "模型文件 (*.pt);;所有文件 (*)",
options=options)
if model_path:
print("已选择模型文件:", model_path)
global weight
weight = str(model_path)
self.model_label.setText("选择的模型路径为:{}".format(str(model_path)))
def selectSavePath(self):
options = QFileDialog.Options()
save_path = QFileDialog.getExistingDirectory(self, "选择保存路径", options=options)
if save_path:
print("已选择保存路径:", save_path)
self.save_label.setText(save_path)
def start_detect(self):
global weight
if self.input_lineedit.text() is not None:
source_text = self.input_lineedit.text()
source = str(source_text)
opt = parse_opt(weight, source)
main(opt)
global save_path_
if save_path_ is not None:
image = QImage(save_path_)
pixmap = QPixmap.fromImage(image)
pixmap = pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio)
self.image_label.setPixmap(pixmap)
global t_flag
global image_return
if image_return is not None:
print("image_return:", image_return)
self.update_image(image_return)
def update_image(self, frame):
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = frame_rgb.shape
bytes_per_line = ch * w
qt_image = QImage(frame_rgb.data, w, h, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(qt_image)
self.image_label.setPixmap(pixmap)
self.image_label.update()
if __name__ == '__main__':
app = QApplication(sys.argv)
editor_app = VehiclePedestriansApp()
editor_app.show()
sys.exit(app.exec_())
致谢:感谢Ultralytics、美团、WongKinYiu等团队的开源贡献,使得目标检测技术更加普及。希望本文能帮助更多开发者将YOLO应用于实际项目,推动AI落地。





