Showing Posts From

Yolo

YOLOv8-Pose模型的网络结构及其输出数据解读

在YOLOv8模型网络架构解读一文中,我已经在对于YOLOv8与YOLOv5模型结构差异的基础上,详细解释了YOLOv8模型的网络结构尤其是检测头部分的设计与计算流程,本文则重点在对比YOLOv8与YOLOv8-Pose网络结构的基础上,对其检测头部分及其输出数据的维度和结构进行详细总结和说明。 回顾标准YOLOv8模型的检测头结构 首先给出结论:YOLOv8-Pose 与标准 YOLOv8 模型在 Backbone 和 Neck 上是完全一致的,其核心架构差异 100% 集中在 Head(检测头)的设计、损失函数的定义以及对应的数据后处理上。所以对YOLOv8-Pose模型的学习,需要建立在对标准的YOLOv8模型有比较清楚的理解的基础上,具体可以参考YOLOv8模型网络架构解读。 标准YOLOv8模型的检测头结构如下图所示。简单来讲,就是整体网络的BackBone部分对输入图像的特征进行提取,Neck部分对不同尺度特征图中所包含的特征信息进行充分的融合,最终把充分融合后的特征信息送入到检测头部分。YOLOv8模型的检测头部分分为两个独立的处理路径,回归头采用CIoU+DFL(抛弃了之前几代延续使用的Anchor-Based的机制)计算和输出检测框的坐标位置,分类头则采用标准的BCE计算各个检测框对应的类别分类。 最终YOLOv8模型的整个检测头输出8400个检测框坐标及其类别信息(置信度直接使用类别信息中的最大值),在后处理阶段则从这8400个检测框中过滤出来最终的有效检测框作为最终检测输出。 !image.png YOLOv8-Pose模型的检测头结构 如上所述,YOLOv8-Pose模型与标准的YOLOv8模型的差异只在于其检测头Head部分: - 标准 YOLOv8 Head: 与YOLOv5等前代版本最大的不同在于采用了解耦头(Decoupled Head),Head部分分为两个并行分支:Box Branch(回归边界框与 DFL)和 Cls Branch(分类)。 - YOLOv8-Pose Head: 在标准的YOLOv8 Head的基础上演变为三分支结构 (Tri-Branch)。在原有 Box 和 Cls 分支的基础上,新增了一个独立的关键点分支 Keypoint Branch。 下图是YOLOv8-Pose模型的检测头结构示意图: !e6de4f91-bf0b-4cbf-bb68-b57d9aa15400.png 可以看到,YOLOv8-Pose模型的输出头的结构中,坐标回归头分支Box Branch和分类分支Cls Branch与标准的YOLOv8模型的检测头部分没有任何差异,区别只在于Pose模型额外增加了一个独立并行的关键点分支KeyPoint Branch。 YOLOv8模型的输出及其过滤 要搞清楚YOLOv8-Pose模型输出数据的过滤逻辑和后处理流程,首先要彻底搞清楚标准YOLOv8模型的后处理流程。 YOLOv8 模型默认是使用COCO数据集来进行预训练的,COCO数据集有80个分类,所以对于标准的YOLOv8模型而言,其坐标回归头输出的每个检测框的坐标位置数据维度为64(具体可参考YOLOv8模型网络架构解读),其类别分类头的输出每个检测框对应的数据维度为80(因为有80个类别),而三个尺度(P3/P4/P5)的特征图总共输出8400个检测框,所以整个检测头的输出维度为 [8400,144]。 在进行后处理的NMS流程之前,坐标回归头的每个检测框的长度为64维度的数据,首先会通过 DFL 的积分操作(Softmax 加权求和)还原成 4 维的绝对坐标(通常是 )。因此,送入后处理阶段(包含 NMS)的模型原始输出张量,其每一个检测框所包含的数据是:4 维坐标 + 80 维类别概率 = 84 维。 至此,YOLOv8模型的输出的维度为 [8400,80+4]。然后,基于以上类别分支输出的80维度的类别数据,对其求 Max,只保留每个检测框的最高类别得分及其索引,把这个最高类别得分作为其置信度。至此的数据维度变成: - 坐标: - 得分: - 类别: 在此后的后处理流程中,主要包含有两个串行的核心步骤和两个阈值(conf_thres和 iou_thres): - 置信度初筛: 基于前述的置信度进行粗筛,去掉所有置信度小于conf_thres阈值的检测框,这样就可以使用极小的算力剔除绝大多数背景框。 - IoU 抑制 (IoU-based NMS): 进一步的精筛过程,这一步是利用几何交并比IoU(如果两个检测框的IoU大于iou_thres阈值,就认为是同一个物体)剔除同一个物体的冗余框。 最终通过以上IoU NMS过滤后的检测框就是模型检测最终输出的结果。 YOLOv8-Pose模型的输出及其过滤 YOLOv8-Pose 模型预训练使用的是 COCO Keypoints (Pose) 数据集。在这个数据集的官方标注中,只有人这一个类别拥有 17 个关键点的完整标注,其他 79 个类别在官方数据集中是没有关键点标注的。因此,Ultralytics 官方在训练 yolov8n-pose.pt 时,其配置文件(如 coco-pose.yaml)中硬编码了 nc: 1(Number of Classes = 1,即仅限人这一个检测类别)。 这就是为什么在官方提供的 YOLOv8-Pose 预训练模型中,Cls 分支的维度是 1,而不是 80 的原因所在。 而关键点分支的输出,在使用 COCO Keypoints (Pose) 数据集的情况下,标注数据中共包含有 17 个人体关键点。每个关键点需要回归 3 个值: - 特征图上的坐标位置 x - 特征图上的坐标位置 y - 可见度置信度 visibility,用于判定该点是否被遮挡或在图像视野外 因此可以看到,关键点分支针对每个检测框输出 17 x 3 = 51维度的关键点数据。 那么,对于YOLOv8-pose模型而言,增加了一个关键点分支的话,模型输出到最终的NMS过滤输出的后处理逻辑是什么呢? 答案是,在YOLOv8-Pos模型的后处理逻辑的运行过程中,整个后处理流程根本不看关键点长什么样,关键点仅仅是被当做边界框的附属数据。框生则点生,框灭则点灭。 也就是说,在YOLOv8-Pos模型的后处理流程中,只有 Box 坐标回归分支和 Cls 类别分支的输出参与置信度初筛和 IoU 计算(这部分流程与标准的YOLOv8模型完全相同),以上的后处理过程最终得到存留框的索引。最终在输出数据的组织中,再利用上一步生成的留存框索引,从原始的 [8400,56] 维张量中,把留存框的坐标、类别、置信度连同它的 51 维关键点一次性全部捞出来。 - 经过后处理流程过滤后得到了3个Person的检测框,也就会同时输出这3个Person框中每个关键点的位置。 所以从根本上讲,YOLOv8-Pos模型的后处理流程与标准的YOLOv8模型没有任何区别,只不过是在最后输出的时候“顺便”把关键点分支的数据一起输出了。 从源码的角度看待YOLOv8-Pose的输出 以下是一段使用YOLOv8-Pose模型对图像数据进行推理的代码: 以上代码中模型的推理结果results ,实际上是一个 Python的列表对象,这个列表的长度等于输入数据的 Batch Size。如果每次调用模型进行推理输入的是单张图像,其推理结果的核心数据就都在 results[0] 中。 results[0] 是一个 ultralytics.engine.results.Results 类的实例,它就像一个集装箱,其内部按任务装载了不同的子对象: - results[0].boxes: 封装了经过 NMS 过滤后的所有边界框信息。 - results[0].keypoints: 封装了跟随边界框存留的所有关键点信息。 - results[0].orig_img: 存储了原始输入的 Numpy 图像(主要用于后续调用 plot 绘制边界框和关键点的底图)。 - results[0].names: 检测类别的映射字典(例如 {0: 'person'})。 其中边界框所包含的主要数据信息及其维度(模型输出的有效检测框数量为N)为: - boxes.xyxy:维度为 [N, 4],对应每个框的绝对坐标,其中左上角 x1, y1,右下角 x2, y2。 - boxes.xywh:维度为 [N, 4],对应每个框的中心坐标与宽高(cx, cy, w, h) - boxes.conf:维度为 [N], 这 N 个框各自的最大类别置信度得分。 - boxes.cls:维度为 [N],这 N 个框各自对应的类别索引。 - boxes.data:维度为 [N, 6],原始的拼接张量,按照 x1, y1, x2, y2, conf, cls 顺序依次排列。 其中关键点数据所包含的主要数据信息及其维度(模型输出的有效检测框数量为N)为: - keypoints.xy:维度为 [N, 17, 2],N 个人,每个人 17 个关键点,每个点对应的绝对坐标 x, y。 - keypoints.xyn:维度为 [N, 17, 2],归一化坐标。x 除以图像宽,y 除以图像高,范围在 [0, 1] 之间。 - keypoints.conf:维度为 [N, 17],N 个人,每个人 17 个关键点的可见度/置信度。 - keypoints.data:维度为 [N, 17, 3],拼接张量,按 x, y, conf 顺序排列(即前述的 51 维数据的重组形态)。

如何把PT格式的模型转换为TFLite/LiteRT模型?

之前在尝试把Ultralytics的YOLOv8模型运行在Android手机上的过程(参考总结Android Flutter项目中YOLO模型的执行流程)中,直接使用了YOLO模型的export接口即可实现把该模型导出为ONNX、TFLite等格式。但是如何我们在开发过程中遇到的模型没有官方所提供的export接口,如何能够把pt格式的模型文件转换为TFLite,从而方便的部署在Android手机上呢? 因此,本文尝试解决这样的问题:如何把一个通用的 PyTorch 模型权重(.pt)文件,通过模型转换处理,变成在 Android 端侧能跑的 TFLite 模型文件(.tflite)? Google的AI Edge Torch/Litert 针对以上所提出的问题,一个比较流行的解决方案是: - PyTorch (.pt) → ONNX: ONNX 是模型交换的通用格式,所以这一步转换相当于是把模型文件脱离了 PyTorch 的框架限制。 - ONNX → TensorFlow (SavedModel): 这是一个转译过程,从ONNX的生态语言转换为TensorFlow生态,确保目标平台(即Google 手机APP)能正确的识别模型文件中的算子。 - TensorFlow → TFLite: 这有点类似于 Linux 下的 strip 和 objcopy,这一步操作会进行常量折叠、算子精简以及最重要的量化操作(Quantization),把默认的 FP32 的权重参数变成 INT8,以适应移动端 CPU/NPU 的算力和存储方面的限制。 但是,以上这个传统的 PT -> ONNX -> TF -> TFLite 路径并非是一个完美的解决方案。这是由于, PyTorch 坚持使用 NCHW (即所谓的Channel First) 而 TensorFlow 则默认使用 NHWC (即所谓的Channel Last),那么转换工具在以上传统的转换路径中为了保住数学上的等价性,会疯狂插入 Transpose 算子。这在手机 CPU/GPU 上运行过程中,会导致频繁的内存重排,严重拖慢推理速度。 因此,Google 在2024年4月推出了一个AI Edge Torch的工具,用于实现从 PT 模型文件向TFLite格式模型文件的直接转换。这个工具利用了 torch.export(PyTorch 2.x 的核心特性)直接捕获计算图,并将其映射为 TFLite 算子,更重要的是,它可以直接在模型转换过程中对齐推理过程中的数据格式,这样就消除了大量不必要的 Transpose 算子,避免后续推理过程要执行的大量内存重排。 到了2025年,Google 对移动端 AI 生态进行了一次品牌重塑:之前的TensorFlow Lite被重新命名为LiteRT。这主要是因为,Google 认为 TensorFlow Lite 这个名字太受限于 TensorFlow 框架了。现在整个AI模型生态的趋势是 PyTorch 统治科研与训练,而 TFLite 统治移动端部署。因此为了吸引 PyTorch 用户,Google 强调 TFLite 这个 Runtime 是通用的端侧AI部署工具,不只是给 TensorFlow 用的。所以现在新的品牌叫 LiteRT (Lite Runtime)。 !image.png 环境安装 要在PC端把一个Pytorch的模型转换为 TFLite/LiteRT 格式,主要是需要依赖于litert-torch和ai-edge-litert 这两个包。 - litert-torch:对应之前的 ai-edge-torch,负责将 PyTorch 算子映射到 LiteRT。 - ai-edge-litert: 对应之前的 tflite-runtime,是推理引擎,负责在 Android 或 PC 上执行生成的 .tflite (LiteRT) 文件。 截至目前(2026年3月份),litert-torch和ai-edge-litert 这两个包的接口变动仍然非常剧烈,导致其对环境的要求非常苛刻。所以一定要创建一个独立的Python环境,并且提前安装好其指定的torch版本,否则后面安装这两个包的过程中很容易出现包冲突的情况。经过仔细检查,目前比较稳定的环境安装步骤为(在Windows的WSL2环境中安装): 注意:这个模型转换和PC端推理的环境必须安装在Linux环境中,我自己在Windows中安装环境总是会以下错误。从报错信息上看,litert-torch依赖于ai-edge-tensorflow,而ai-edge-tensorflow 目前主要发布在 Google 的 Nightly/Experimental 仓库中,对 Windows 平台的原生支持非常滞后。所以这个转换工具最好还是在Linux或者Windows的WSL2环境中来安装。 模型转换 环境安装好以后,就可以开始把Pytor下的预训练pt格式的模型文件转换为TFLite格式了。我这里直接使用Ultralytics/YOLO项目中的yolov8s.pt这个文件做转换测试。 以上模型导出脚本能够成功的导出 FP32 的模型文件,FP32 虽然精度最高,但由于其对内存带宽和存储空间的占用比较大(对于YOLOv8s这个模型而言,转换后的模型文件大小为44MB),在端侧 CPU/NPU 上运行肯定不是性能最优选,因此接下来还需要进行量化(Quantization)处理。 量化 对于YOLO模型自带export接口而言,量化是一个很简单的事情,只需要设置导出时的fp16、int8等导出格式就可以了。但是使用litert_torch 完成模型导出之后,对TensorFlow Lite模型文件进行量化操作的接口发生的变化非常剧烈,导致现在在网上看到的几乎所有量化参考代码在最新的litert-torch 都无法正确、成功的完成量化操作。 通过学习https://github.com/google-ai-edge/litert的ReadME文件: > > > I have a PyTorch model... > > - Goal: Convert a model from PyTorch to run on LiteRT. > - Path1 (classic models): Use the LiteRT Torch Converter to transform your PyTorch model into the .tflite format, and use AI Edge Quantizer to optimize the model for optimal performance under resource constraints. From there, you can deploy it using the standard LiteRT runtime. > - Path2 (LLMs): Use LiteRT Generative Torch API to reauthor and convert your PyTorch LLMs into Apache format, and deploy it using LiteRT LM. > 可以看到对于目前比较新的LiteRT实现而言,量化需要用一个新的工具ai-edge-quantizer(https://github.com/google-ai-edge/ai-edge-quantizer)。这个工具是 Google 专门为了对LiteRT模型进行 INT8/INT4 权重压缩打造的新一代利器。在最新的 Edge AI 理念中,单纯的文件体积 FP16 截断已经不再流行,权重 INT8 + 激活值浮点 (即所谓的Dynamic Quantization) 才是能在手机等端侧设备上上平衡速度、体积和精度的终极王者。 以上的量化流程中选择了wi8-afp32的模式(即权重参数使用INT8量化,激活值仍然使用FP32量化),最终得到的量化后的模型文件只有11MB的样子,是量化前的FP32模型文件的1/4。 除了以上的wi8-afp32量化模式,ai-edge-quantizer还支持其他的量化模式,如下图所示,Google 的工程师用了一种非常直白的命名约定来定义这些量化模式: - w = Weight(权重参数,即模型训练出来的固定参数) - a = Activation(激活值,即模型在推理时流动的中间特征图) - i8 / i4 = 8位 / 4位整数(Integer) - fp32 = 32位浮点数(Float) !8a772873-2af8-4892-8f32-b1998f10d39e.png 推理测试 基于 LiteRT模型文件已经转换并量化完成,下一步就是在PC端的LiteRT runtime中对其进行推理测试。 如上所述,在 LiteRT生态中,ai-edge-litert包对应于之前的 tflite-runtime,也就是负责在 Android 或 PC 上基于LiteRT模型文件执行前向推理的运行时框架。因此,在PC端进行前向推理的流程大致也就是:加载模型以及推理的图片文件→数据预处理→执行推理→对推理结果执行NMS后处理→把NMS过滤的结果叠加在输入图像上。 参考资料 - AI Edge Torch: High Performance Inference of PyTorch Models on Mobile Devices - Google Developers Blog - Convert Models From Pytorch to TFLite With AI Edge Torch | by David Cochard | ailia Tech BLOG (EN) | Medium - https://github.com/google-ai-edge/litert-torch/blob/main/docs/pytorch_converter/README.md - https://github.com/google-ai-edge/ai-edge-quantizer

总结Android Flutter项目中YOLO模型的执行流程

之前基于 Flutter 实现了一个在Android APP中调用YOLO模型来实现图像目标检测功能,本文的 YOLO 模型的 TFLite 导出以及在 Android Flutter 项目中进行调用的完整执行流程进行总结。 在Android上跑AI模型,标准的做法是导出模型文件为 TFLite 格式,然后在 Android 的 TFLite+Interpreter+NCAPI 框架下执行AI模型的推理流程,与该框架相关的知识可参考 如何理解TFLite/LiteRT的核心概念和框架流程?一文。 YOLO模型的TFLite导出 因为 Ultralytics 项目中的模型导出的实现已经非常完备,所以可以直接在ultralytics项目中通过其export接口导出 tflite 模型文件即可,此处选择导出为half即float16的格式: 此外,后续在Flutter项目中调用以上模型文件进行推理,还需要创建一个与模型对应的label.txt文件,其中包含有模型检出类别对应的类别名称字符串,按顺序每行一个。因为我测试中直接使用的是YOLO的预训练pt文件,预训练模型文件对应的是COCO数据集,所以这里的label.txt中包含的类别名称字符串大致就是如下80个类别: 依赖管理 因为要在 Android Flutter APP 增加跑AI模型的的能力,首先需要在项目根目录下的pubspec.yaml和 android/app 目录下的build.gradle.kts增加相关的依赖项。 flutter项目中的pubspec.yaml是该项目的元数据中心(Metadata Center)和依赖管理器(Dependency Manager)。其中包含的主要内容是:项目的基本配置信息,生产环境的依赖库,开发/测试阶段的依赖库,资源与资产等。对于以上提到的依赖项,每次在构建项目时,都会自动调用 flutter pub get 从pub.dev下载指定依赖库的源代码到本地。 该项目中需要对手机camera和本地图片的图像调用YOLO模型进行检测,所以pubspec.yaml中相关的设置项包括: 因此,对于在 Android 项目中跑YOLO模型而言,以上配置文件最关键的就是其中导入了 flutter_vision 这个插件。该插件内置了 TensorFlow Lite C SDK,并针对 YOLO 这种特定模型,包含了输入输出的 ByteBuffer、维度转换(Tensor Shape)和 NMS(非极大值抑制)等的相关处理,对于应用层程序而言,只需要调用 vision.loadYoloModel() 接口,它就可以在 Android 原生层创建 TFLite 的 Interpreter并执行推理任务。 模型在Flutter中的完整推理流程 与其他板端模型的完整推理流程类似,在Android Flutter项目中调用YOLO模型执行推理任务同样包含模型加载、前向推理以及后处理这三个环节。 模型加载 基于flutter_vision插件对AI模型进行加载的整体流程如下: 整体的模型加载过程很简单,调用flutter_vision的loadYoloModel接口创建一个模型加载的异步任务,传递模型文件和label文件等参数,然后等待这个异步任务执行完成即可。 需要注意的是,flutter_vision 插件只能够支持ultralytics项目export接口直接以tflite格式导出的YOLOv5,YOLOv8,YOLOv11这几个模型,而且参数中指定的模型文件与modelVersion的参数必须设置正确,否则后续的后处理流程会出错: !074e2e0e-d2b9-48e6-b0b2-4a1f06769c23.png 那么如果我们要在 Android APP 中跑其他模型呢?那就只能使用 tflite_v2 插件、flutter_tflite插件或者底层 TensorFlow Lite API。 前向推理和后处理 flutter_vision插件的前向推理过程主要就是调用其yoloOnFrame(针对摄像头的图像)和yoloOnImage接口(针对本地图片)。 以上的推理接口执行中本身已经包含了YOLO不同版本的后处理流程,所以其输出结果是一个的数组。这个数组中的每一项都对应于一个最终检测框的类比和坐标+置信度,如下所示: 应用层拿到以上的这个数组以后,就可以对其中的内容进行解析并在图像上画框标注了。因为 yoloOnFrame 和 yoloOnImage 接口中已经包含了后处理的NMS等环节,所以不需要在应用层再执行后处理了。 参考资料 - flutter_vision | Flutter package

RT-DETR模型的自定义数据训练以及在RK3588上的适配

本文完整的记录了基于Ultralytics项目中的 RT-DETR 预训练模型在自定义数据集上进行的训练、ONNX导出,以及在RK3588硬件平台上的适配流程。 对于 RT-DETR 模型的实现而言,目前主要有两套独立的实现,即 Baidu 的 PaddleDetection 和 Ultralytics (YOLOv8/v11 生态)。因为我自己对于 Ultralytics/YOLO 较为熟悉,所以本文以 Ultralytics/YOLO 的实现进行试验和记录。 !image.png 1.自定义数据集的训练 因为这里使用的是Ultralytics项目下的RT-DETR实现,所以其训练过程、训练所生成的中间文件组织结构以及对训练数据的表格格式、目录结构等都与YOLO模型没有任何区别,只需要把之前在YOLO模型上训练所使用的数据集目录以及其 data.yaml 直接拷贝过来使用即可。所以如果对YOLO模型的训练流程有经验的话,在这个项目中训练RT-DETR就很容易上手了。可参考一文总结YOLOv5模型的自定义训练、测试与模型导出的全过程。 训练流程的代码也与Ultralytics项目下的其他YOLO模型类似,训练过程的代码如下: 训练流程基本上与普通的YOLO没有多大区别,有两点注意事项: - 在Windows环境中进行训练的话,训练代码需要包裹在__main__里面。这是因为在 Linux 系统中,进程的创建通常使用 fork,它会直接克隆当前的内存状态,不需要重新导入模块;而 Windows 不支持 fork,只能 spawn(冷启动一个新的 Python 解释器并导入以上脚本),这样的话,子进程启动时会再次运行一遍整个 train.py,从而又触发了一个新的训练请求,形成死循环。系统为了保护内存不被耗尽,主动抛出 RuntimeError来结束整个程序。而把训练代码包裹在__main__里面以后,只有主进程会执行train_model函数,子进程只负责执行训练过程,这样就不会进入反复新建子进程的死循环。 - batch和workers设置相对小一点,否则训练过程中很容易卡死。我一开始设置了batch为16,结果发现程序很容易卡死并报显存不足,所以如果出现训练过程稳定性问题的时候可以尝试把workers和batch这两个参数设置小一点。 以上的训练执行过程明显比使用相同数据集训练的YOLOv8模型耗时要长一些,应该是RT-DETR模型的计算复杂度要高于 YOLOv8。训练结果同样保存在runs目录下的best.pt文件。 2.Grid Sample算子的梦魇与Monkey Patch 在RK3588硬件上跑AI模型,大致的路径无非是: - 在PC端基于Pytorch等框架完成模型的训练; - 把训练的pt格式模型文件转换为更通用的ONNX格式; - 使用瑞芯微的RKNN-Toolkit2工具进行板端模型转换,把ONNX格式的模型转换为RKNN格式; - 在RK3588板端使用rknn-toolkit-lite2 工具(Python)或者C/C++语言把模型跑起来,在板端完成推理测试得到结果。 所以既然前面的训练过程已经完成,那么下一步自然就是进行ONNX格式的模型转换。但是这一步出现了一个由Grid Sample算子所导致的很大的问题: - Grid Sample算子是在Opset 16中被支持的,所以要能够导出ONNX成功,必须要设置Opset版本为16。设置为低于16的Opset版本,在导出ONNX模型这一步会报错。 - 如果把Opset设置为16的话,使用RKNN-Toolkit2工具(2.3.2版本)确实可以成功导出RKNN模型,但是这个模型放在RK3588板端跑的话,配合2.3.2版本的librknnrt.so文件会出现段错误的问题,原因是2.3.2版本的librknnrt.so文件根本不支持Grid Sample这个算子。也就是说,2.3.2版本的RKNN-Toolkit2工具认为Grid Sample算子是可以支持的,所以模型转换通过;而2.3.2版本的librknnrt.so文件根本就没有实现这个算子,所以板端运行会出现段错误的问题。瑞芯微的算法团队发布版本的时候不需要同步一下吗? 对于以上问题,当然是可以修改模型的结构,使用其他更通用的算子替换掉Grid Sample算子,然后重新训练,就可以从根本上解决这个问题。 但是,如果不考虑算法在板端的运行速度的话,其实是可以用一个简单的办法绕过这个算子:Monkey Patch。 所谓的Monkey Patch就是对于要导出ONNX的模型,在导出之前,先把某个板端不支持的算子用更常用的算子替换掉,这样在ONNX导出的时候,根本就看不到这个Grid Sample算子。具体来讲,就是在导出ONNX模型之前先用以下的代码进行Grid Sample算子(F.grid_sample)的替换: 3.ONNX模型的导出与RKNN转换 解决了以上的Grid Sample算子的问题,后续的ONNX导出就比较简单了。如如何理解ONNX模型导出的Opset版本?一文所总结的:在 Rockchip NPU的2.3.2版本的模型转换工具和板端NPU驱动上最高能够支持的Opset版本就是15。实际的部署过程中,最好的Opset版本设置应该是12或者13,避免版本过新有可能带来的稳定性和兼容性问题。 以上导出的ONNX格式的模型就是能够在RK3588上得到良好支持的OPSet=13的版本,接下来就是使用RKNN-Toolkit2工具进行板端模型转换,把ONNX格式的模型转换为RKNN格式: 4.板端推理运行 最后在RK3588板端使用rknn-toolkit-lite2 工具进行板端推理就可以看到运行的结果了。不过感觉推理执行的速度比较慢,可能跟手动替换了Grid Sample算子有关: !result_fp16_final.jpg

在Android Flutter项目中flutter_litert插件运行TFLite模型

在之前的一篇笔记(总结Android Flutter项目中YOLO模型的执行流程)中,我详细总结了基于flutter_vision插件在一个Android Flutter项目中把Ultralytics项目中导出的YOLOv8模型运行起来的完整流程。 但是Flutter_Vision插件的使用有诸多限制,例如只能运行YOLO的几个模型,并且只能配合Ultralytics的export接口导出的TFLite模型文件来使用。那么如果想要在Android上跑YOLO之外的模型,就没有办法是用Flutter_Vision插件了。 因此,要把一个自定义的模型转换为TFLite格式并且能够在Android APP中跑起来,就需要一个更灵活的应对方式。而这个过程分为两个步骤: - 把自定义模型的pt文件转换为TFLite格式。这部分在如何把PT格式的模型转换为TFLite/LiteRT模型?已有详细总结。 - 把以上转换的TFLite格式的模型文件在Android Flutter项目中跑起来,避免受到FlutterVision插件所带来的限制。这一点就是本文要解决的问题。 flutter_litert vs tflite_flutter 在Android Flutter项目中跑 TFLite模型,目前比较常用的是flutter_litert和tflite_flutter这两个插件。 - tflite_flutter:这个插件是 Flutter 社区中最经典、最老牌的 TFLite 插件,目前搜到的信息大部分都是使用这个插件在Android上跑AI模型。它通过 Dart FFI 直接封装了 C 语言的 TFLite 解释器。目前仍有社区维护版(如tflite_flutter_plus),但配置分散,社区维护分散,在新的项目中不建议使用。 - flutter_litert:这个插件是为了响应 Google 对 TensorFlow Lite 品牌的正式更名和升级 (LiteRT) 而推出的现代继任者。它也是目前推荐的、更符合 Google 最新人工智能路线图的插件,其本质上是 tflite_flutter 的演进版本,旨在解决旧插件的维护痛点。因此强烈建议在新的项目中使用flutter_litert这个插件。 !image.png flutter_litert 这个插件本质上就是一个工作在 Flutter(Dart 语言)层级的硬件抽象层 (HAL) Wrapper。这个插件总的特点是: - 底层机制: 该插件并不是用 Dart 重写模型的推理逻辑,而是通过 Dart FFI (Foreign Function Interface) 接口极其轻量地绑定了 Google 已经预编译好的 C++ LiteRT Native 二进制库(.so 文件),这样的好处当然是可以利用底层的C/C++语言的执行达到最高的效率。 - 零拷贝理念: 其核心思想是在内存里开辟一段连续的 Buffer(如 Float32List),使用Dart 语言往里填数据,C++ 引擎则会直接读取这段物理内存进行模型的矩阵运算,避免了跨语言传值所带来的性能损耗。 flutter_litert 插件运行的整体架构与数据流程 下图是在Android Flutter项目中使用flutter_litert 插件运行AI模型的整体架构与数据流程图: !litert_architecture_high_res.jpg 从以上的整体流程图中,模型的前向推理过程中的数据流动可以大体分为四个层次和阶段。 1.采集与调度层:Flutter Dart Zone (User Space) 这部分运行在 Flutter 的 Dart 虚拟机中,相当于系统的用户态应用程序。它的首要任务是协调前端 UI 显示与模型检测结果的时序、图像预处理这两部分工作。 当然在模型能够对图像进行检测和识别之前,首先要把模型加载到内存中,模型及其label文件一般整合在app的assets中,因此采用assets对应的API加载模型: 对于手机的摄像头而言,在打开的情况下,其以 30/60fps 的帧率向主线程推送高分辨率的 YUV420 裸流数据并显示在屏幕上。而一般情况下,手机所具备的算力无法支持以这么高的算力实现对每一帧图像的检测,可能1秒钟只能完成10帧图像的处理,这样的情况下,Camera采集图像与Model处理图像这两部分的工作就不是同步的。因此需要分开两个线程同时进行处理,主线程按照Camera的采集帧率刷新显示实时图像,而针对模型对图像的处理而言,就必须开辟一个独立的 Isolate(类似后台高优先级线程),执行 YUV 转 RGB、图像缩放等预处理后送入litert_flutter插件进行识别和检测 。 众所周知,图像在送入YOLO等模型中进行前向推理之前,还必须要进行一系列的预处理流程,以符合模型对输入图像数据在各个维度上的要求。因此,对于每一帧从摄像头中取出的YUV420数据,其分辨率可能是1280x720,那么要进行的预处理过程就至少要包括:分辨率缩放到640x640、YUV转RGB、图像归一化到0-1.0之间、图像数据排列格式调整为NCHW(Pytorch模型的数据排列格式默认为NHWC)等。 以上经过预处理以后,输入图像就准备好了,下一步就是调用flutter_litert插件的API接口进行处理了: 2.跨语言函数接口与内存零拷贝机制 在 Interpreter 对输入图像数据所进行的前向推理的执行流程中,图像数据要从APP应用层的Dart语言传递到模型推理代码的C++语言,而且为了在两种语言之间高效的传递大片的图像内存数据,最好能够直接通过类似内存指针的方式来实现零拷贝的图像内存传递机制,这就是 Flutter 框架为了提升应用程序性能的 Dart FFI (Foreign Function Interface)的用武之地了,它可以允许 Dart 直接与底层 C/C++ 共享同一块物理内存的指针,从而避免在调用者和被调用者之间反复拷贝大片的内存。 如上图所示,无论是在 Dart 语言所写的 interpreter 的函数调用,还是最终 interpreter 把模型的运行结果从底层的C++语言返回给用户态的Dart语言层,都采用了 Dart FFI所提供的内存零拷贝机制。 3.底层算力的执行:Native C++ Zone 这部分是模型使用C/C++语言真正执行前向推理的部分。 在如何理解TFLite/LiteRT的核心概念和框架流程? 一文中,已经详细总结了在Android的TFLite/LiteRT框架下运行AI模型的关键概念和执行流程,Google 使用 C++ 语言写了一个 LiteRT Runtime运行时环境(实际上也就是所谓的interpreter),负责执行和调用 TFLite\LiteRT 格式模型在Android系统中的运行和算子的调度。 具体的算子调度执行过程中,Interpreter 进一步 通过Delegates 来决定把具体的运算算子派发给谁: - GpuDelegate (Adreno GPU): 适合处理模型中大规模并行的浮点矩阵乘法。 - NnApiDelegate (Hexagon NPU): 针对 Android Neural Networks API 设计的专用 NPU。如果模型完全量化为 INT8,交给 NPU 跑不仅极快,而且功耗极低。 - XNNPACK (CPU SIMD): 最底层的兜底方案。如果某个算子 GPU/NPU 不支持,就会退回到 CPU 用向量指令硬算,当然这通常是模型运算速度慢导致卡顿的罪魁祸首。 具体的执行和调度逻辑可以参考如何理解TFLite/LiteRT的核心概念和框架流程?以及TFLite如何处理算子版本和兼容性的问题?这两篇文章。 4.模型返回与渲染 通过以上 LiteRT Runtime 所调度的模型前向推理的执行后,该部分的 C++ 引擎更新内存状态,通过Dart FFI机制,应用层的 Dart 语言层捕获到模型执行完成的信号。此时用户层应用程序就可以按照模型输出的数据结构和类型,从连续的内存块中拆解出 YOLO/RT-DETRv2等模型输出的张量信息(例如类别、边框、置信度等),对这些张量信息进行后处理(如YOLO还需要进行一步NMS处理),然后交回给 Flutter 的 GPU 渲染管道,根据屏幕比例映射坐标,在画布上轻量级地画出最终的目标检测框。 模型推理的GPU/NPU加速设置 如上所述,在LiteRT Runtime运行时环境对模型所进行的前向推理的计算过程中,interpreter 会根据具体算子的版本情况选择对应的delegate执行选项,主要是GPU、NPU和CPU SIMD等选项。那么在flutter框架下使用litert_flutter插件运行AI模型的时候,如何设置使用GPU/NPU来实现这些算子的硬件加速呢? 需要通过 InterpreterOptions 来进行设置,具体代码如下: 以上代码的配置逻辑是,在Interpreter进行算子调度的过程中,优先使用GpuDelegateV2,如果算子在GPU上不支持的话,则会回退到CPU上用4线程来进行计算。 在当前(2026年4月)最新的2.0.11版本的 flutter_litert 插件中,InterpreterOptions 能够支持的加速模式包括: - GPU Delegates (图形处理器):适用于大多数模型,特别是 CNN 类网络。优点是速度快,功耗相对较低。缺点是部分算子不支持,可能回退到 CPU。 - GpuDelegateV2():针对android平台,其底层API是OpenGL ES 3.1 / Vulkan。 - GpuDelegate():针对iOS平台,其底层API是iOS的Metal。 - Core ML Delegate (iOS):针对iOS设备,iOS 11+,A12 芯片以上的设备。优点是在Apple 设备上性能最佳;缺点是仅支持 iOS。 - iOS 上应优先尝试 CoreMLDelegate() ,如果不支持再回退到 GpuDelegate() 。 - XNNPACK Delegate (CPU 优化):无 GPU 或 GPU 不支持时。优点是兼容性比较好,速度快于普通的CPU;缺点是仍比 GPU 慢很多。 需要注意的是:通过options.addDelegate只能设置一个加速选项,如果多次调用addDelegate接口,只有最后一个会生效。因此实践中应使用try…catch的方式选择最合适的加速选项:

YOLOv5模型的损失函数计算总结

本文以YOLOv5模型的v6.0/v6.1版本为基础,详细总结和解释了损失函数各个部分的计算流程。 YOLOv5模型的损失函数由三个部分组成: - 目标框定位损失,Box Loss - 目标置信度损失,Obj Loss - 分类损失,Cls Loss 其中,边界框回归损失采用了CIoU(Complete IoU)损失,而目标置信度和分类损失均使用二元交叉熵(BCE)损失。 目标置信度损失Obj Loss YOLOv5的目标置信度损失Obj Loss是唯一一个正样本和负样本(背景)都要参与计算的损失项。它的核心任务是教模型回答一个问题:“这个网格里到底有没有物体?如果有,这个预测框的可信度有多高?” 此外,在进行损失计算的过程中,build_targets所返回的正样本列表中,所有正样本的置信度标签值并非固定是1,而是作为正样本的这个预测框与真值框的IOU。也就是说,正样本与真值框之间的重合度越高,这个正样本的置信度标签值也就越高。这就是YOLOv5损失计算所采用的软标签(Soft Label)策略: - 所有的负样本 Target () = 0 - 所有的正样本 Target () = CIoU Score 整个目标置信度损失 L_{obj} 的计算可以分为四个步骤。 1.预测值 (p_{obj})的预处理 YOLOv5模型的前向推理流程在三个特征层(P3, P4, P5)输出预测张量。对于置信度通道,其推理输出的是未经激活的数值。因为计算置信度损失值使用二元交叉熵BCE算法,因此要首先对模型输出的 Logits 值进行 Sigmoid 激活,将数值映射到 (0, 1) 区间。 2.构建目标值 (t_{obj}) 如YOLOv5模型损失函数的背景知识总结一文所总结的,YOLOv5模型损失函数计算类ComputeLoss中的build_targes负责基于训练样本的真值框列表以及各个检测层的Anchor列表构建并返回一个正样本列表。 此处计算目标置信度损失需要构建目标值 : - 首先初始化一个与预测特征图形状完全一样的全零张量。 - 然后遍历以上build_targets返回的所有正样本: - 计算当前作为正样本的预测框(Pred Box)与真值框(GT Box)的CIoU值。 - 然后将该 CIoU 值填入全零张量中对应的坐标位置 (b, a, gj, gi)。 - 最终得到的 的结果就是一个大部分位置是 0(背景),少数位置是 浮点数(前景 IoU)的 Target 张量。 3.计算二值交叉熵 (BCE Loss) 上面有了预测值 和 目标值 ,就可以计算其二元交叉熵损失了。YOLOv5 使用 PyTorch 的BCEWithLogitsLoss。对于针对单个预测框而言,其计算公式是: - 对于负样本 ():前半部分消失,只优化后半部分 ,迫使 趋近于 0。 - 对于正样本 ():前后两部分都存在,迫使 趋近于 CIoU 值。 此外,如YOLOv5模型损失函数的背景知识总结所描述的,如果通过超参数开启了Focal Loss功能,还需要对其进行Focal Loss处理。目的是压制负样本背景框的损失,提升正样本的损失权重。 以上公式计算的是一个预测框的置信度损失值,而一个检测层的置信度损失,则是该层输出的所有预测框计算置信度损失的总和: 其中 B 是 Batch Size,G 是网格尺寸,A=3 是每个网格设置的 Anchor 数量,默认为3。 4.层级加权平衡 (Layer Balancing) 因为小目标的特征信息少,背景干扰多,回归难度大,模型需要更强的梯度信号来学习如何检测它们。所以在对三个检测层的置信度损失求和得到总的置信度损失时,还需要对各个检测层的损失值加权求和:对输出小目标特征信息的 P3 层乘以更高的权重,对输出大目标特征信息的 P5 层乘以较小的权重,以强迫模型更加重视高分辨率特征图(P3)上的预测结果,从而提升小目标的召回率和准确性。 再次强调一下,只有置信度损失的计算流程中,会被负样本考虑在内,以下的类别损失以及定位框损失均不考虑负样本。 类别损失Cls Loss 类别损失的目标非常清晰:对于被确定为正样本的预测框,它必须正确识别出该物体属于哪个类别。 类别损失 的计算流程具有以下两个关键特征: - 只计算正样本: 和定位框损失 一样,只有那些被分配了真实框的正样本(也就是build_targets所返回的正样本列表)才参与类别损失的计算。模型推理输出的两万多个负样本检测框(即背景框)的类别损失为零,不参与梯度回传。 - 多类别支持: 其目标是从所有可能的 C 个类别中,能够正确识别出目标的具体类别。 在YOLOv5中,类别损失同样采用标准的二值交叉熵损失BCE,但在工程实现上有一些巧妙的处理,使其能应用于多类别分类。其详细计算流程包含以下四个步骤。 预测值 ()的预处理 与置信度损失的计算流程相同,首先是要定位正样本。基于模型前向推理的输出张量以及模型在各个预测层所预设的Anchor框列表,调用build_targets函数提取出所有正样本的列表。后续类别损失值的计算只在这些正样本上进行计算。 因为要进行BCE计算,从build_targets返回的正样本列表中得到其中所包含的类别预测 Logits,然后对这些 Logits 进行 Sigmoid 激活。模型针对每个预测框,都会输出一个 C 维度的向量,其中 C 是总类别数(例如 COCO 数据集 C=80)。对每个类别输出的预测值都通过 Sigmoid 激活,将每个类别的分数独立地映射到 [0, 1] 区间,从而得到每个类别的预测概率_ 。 构建目标值 () 构造一个与正样本 维度相同的目标张量 。其维度为。对其中的标签赋值: - 对于其中的每个正样本,找到它对应真值框所标注的真实类别 k,在 向量的第 k 个位置上赋值 1,其余 C-1 个位置全部赋值0。 最终的结果就是,我们得到了一组稀疏的 one-hot 编码来作为下一步进行BCE计算的目标值。 在YOLOv5的损失计算流程中,为了防止模型对标签过拟合,其在训练中还会使用一种标签平滑(Label Smoothing)的技术。 大致就是将真实类别所设置的 t=1 的目标值稍微降低,将 t=0 的目标值稍微提高。 - 例如,对于某个正样本的类别标签设置而言,不是设置 ,而是 。 其目的是鼓励模型预测的概率不要过于绝对(如 1.0),从而提升模型的泛化能力和鲁棒性。 计算二值交叉熵 (BCE Loss) 的计算依赖于 BCE,但BCE的计算是针对每个类别独立计算的,然后对所有类别的损失求和。 假设 i 是正样本的索引,k 是类别的索引, 是模型预测的第 i 个样本属于类别 k 的概率, 是真实标签(0或1)。那么针对单个样本的单个类别 k 的损失公式就是: 由于 t_{i,k} 只有在真实类别 处为 1,在其他 C-1 个类别处为 0,这个计算可简化为: - 对于真实类别 :损失为 ,迫使模型提高对正确类别的预测概率。 - 对于错误类别 :损失为 ,迫使模型降低对所有错误类别的预测概率。 类别总损失的聚合 最后,将所有正样本在所有类别上的损失进行聚合(将每个正样本在 C 个类别上的损失加起来),就得到最终的 。 目标框定位损失Box Loss 在YOLOv5中,定位损失采用的是 CIoU Loss(Complete IoU Loss,完整交并比损失)。它的核心目标是:对于被确定为正样本的预测框,准确地调整其位置、大小和长宽比,使其尽可能地与真实框(GT)重合。 相比于普通的IOU指标,CIoU Loss 不仅仅关注预测框与真值框的重叠面积,还引入了中心点距离和长宽比一致性这两个惩罚项,从而提供了更全面、更稳定的梯度信号,极大地提升了模型的定位精度。CIoU Loss 的完整定义是: 其中, 是 CIoU 的惩罚项,它由中心点距离项和长宽比一致性项构成。 的计算流程如下所述。 筛选正样本并解码 这一点与以上计算类别损失相同。首先是定位正样本,即仅从 build_targets 函数生成的正样本列表中,取出对应的预测框(Logits),然后将模型输出的偏移量解码成真实的归一化坐标 (x, y, w, h)。 与类别损失相同,定位损失的计算同样只考虑build_targets所返回的正样本列表,不考虑其他的背景负样本。 计算 IoU 和 CIoU 基于每个正样本预测框,计算这个预测框与真值框之间的 IoU ,以及针对CIoU计算流程的距离和长宽比惩罚项。最终将所有项代入 CIoU Loss 的计算公式,计算每个正样本的 CIoU 损失 。 目标定位框的损失值聚合 对所有正样本的 值进行求和(通常会进行平均)得到总的定位框损失 。 总损失 经过以上三个部分的独立计算,就可以得到一个批次(batch size)的目标定位框总损失 ,目标置信度总损失 以及分类总损失 。整体的损失则是这三个损失的加权和: 其中的 , 和 分别对应于三个损失的加权权重,在超参数文件中进行设置,分别对应于超参数文件中的box,obj和cls设置项: 至此,针对训练过程中一个批次输出图片推理输出的完整损失值就计算出来了。

YOLOv5模型损失函数的背景知识总结

本文以YOLOv5模型的v6.0/v6.1版本为基础,详细总结和解释损失函数的计算流程及代码实现中所设计到的各项背景知识。 如下图所示的YOLOv5的检测头部分的结构,YOLOv5模型每个网格输出的检测框数据与其前几代类似,每个检测框包含的信息(5+NC)就是三个部分:检测边界框的坐标位置,检测的置信度,类别判断概率。 !image.png 相对应的,其损失函数也就由三个部分组成: - 边界框回归损失,Box Loss - 目标置信度损失,Obj Loss - 分类损失,Cls Loss 其中,边界框回归损失采用了CIoU(Complete IoU)损失,而目标置信度和分类损失均使用二元交叉熵(BCE)损失。 二元交叉熵(BCE) 首先以最简单的二分类问题解释二元交叉熵BCE的计算流程。假设训练数据的真实标签为 (为0或1),模型预测为 (为0到1之间的概率值),那么BCE损失函数的计算公式为: _因为对0到1之间的小数进行log计算,其结果永远小于0,所以以上的BCE公式的计算结果总是正数。对于log(x)的计算而言,当x=1时,log(x)=0;当x在0到1之间时,log(x)小于0且随着x变小,log(x)越来越小;当x接近0时,log(x)趋向于负无穷大。_ 因此: - 当标签 时,以上损失函数的计算可以简化为 。此时预测概率 越高,损失值越小越接近0。 - 而当 时,其损失可以简化为 。此时预测概率 越低,损失值越小越接近0。 如上所示,进行交叉熵计算的预测值 应该在0-1之间,所以对于YOLOv5模型三个检测头所输出的logits数据而言,在执行BCE损失计算之前,需要先对logits值进行sigmoid计算,把sigmoid的计算结果再进行BCE计算,因此实际上YOLOv5源代码中计算目标置信度损失和分类损失均使用了BCEWithLogitsLoss函数: BCEWithLogitsLoss函数的实现实际上对应于以下公式: 其中, 是模型输出的logit原始值, 是sigmoid激活函数, 是目标值。 正样本损失计算的权重控制 需要注意的是,以上的BCEWithLogitsLoss函数定义通过超参数的形式传递了两个pos_weight参数:cls_pw和obj_pw,默认情况下,这两个参数在超参数文件中的值均为1.0。此时,以上BCE计算公式中,正样本(t>0)的损失会被乘以 γ,负样本(t=0)的损失保持不变,实际上的等效公式变为: 因此,如果在模型训练的过程中,需要对类别损失和置信度损失计算过程中的正样本损失值的比重进行调节,就可以适当调节cls_pw和obj_pw这两个参数。设置这两个参数为大于1.0的值,将会强化训练正样本的损失值;否则将会弱化训练正样本的损失值。 - 例如,如果训练中观察到高漏检率或正样本预测置信度偏低的情况,可以尝试设置 obj_pw > 1.0(如 1.5~5.0),以加强正样本惩罚。而反之,如果出现过多较多的假阳性即把环境误判为正样本的情况,可以略微降低obj_pw( 总的思路是,如果模型对这个样本很有把握(置信度比较高),在进行损失计算时就把这个样本的Loss权重降到接近0;如果对这个样本没有把握(置信度比较低),损失计算时就保留这个样本的Loss。 具体的计算上,Focal Loss 在标准的 BCE 结果上乘了一个系数 : 这里 (gamma,也就是超参数fl_gamma的设置值) 是一个超参数,通常取值为 2。 - 对简单样本来说,模型的预测输出非常准,比如 = 0.99。在这种情况下按照以上算式计算出来的Focal Loss系数为: 。其结果就是原本的 Loss 被缩小了 10000倍,对梯度几乎没有贡献了。 - 而对于困难样本而言,模型的预测很差,例如 。其Focal Loss的系数为: 。结果就是Loss 只是稍微变小了一点,但依然保持了较大的数值,保留了对模型的指导意义。 当然,在 YOLOv5 中,Focal Loss 只应用于 Objectness Loss ()和 Classification Loss ()的计算,不用于 Box Regression Loss。

开源数据集管理工具FiftyOne

本文对Voxel51所开发的开源数据集可视化和管理引擎Fiftyone的工作特性、环境搭建以及常用的应用场景进行总结。 FiftyOne是由 Voxel51 开发的一个开源数据集可视化和管理引擎,其官网地址为:Maximize your Computer Vision Performance | Try Voxel51。 !image.png 对于数据集的管理而言,Fiftyone的核心功能包括: - 数据集的可视化(The App): Fiftyone提供了一个网页界面,可以让你像刷朋友圈一样查看图片及其叠加的标注框(Bounding Boxes)信息,检查和审核标注数据的质量。 - 高效过滤(Querying): 允许你用 Python 语句快速筛选数据。例如使用简单的代码快速找出数据集中所有包含指定目标类型且图片分辨率大于指定分辨率的数据。 - 模型分析:训练完自己的模型(例如YOLO)后,可以把预测结果倒回 FiftyOne,直接在其中对比标准答案和模型预测,一眼看出模型在什么情况下会出现漏判或者误判,从而更有针对性的改善数据质量。 环境的准备 首先,需要安装 FiftyOne 及其依赖。建议在一个独立的虚拟环境中进行。 浏览数据集 使用 FiftyOne 浏览本地及其在线数据集非常直观。通过使用Fiftyone,不仅能通过其Web页面看到数据集中所包含的图片及其标注信息,还能通过强大的查询语句像操作数据库一样过滤和操作这些视觉数据。 以下代码分别演示了对Fiftyone维护的在线数据集以及本地的YOLOv5格式的数据集进行访问的代码流程。 执行以上代码后,直接在本地通过浏览器访问 http://localhost:5151/ 即可对对应的数据集机器中的标注信息进行浏览和管理。 !7815904f-7a16-4925-8e6a-46ec239303b7.jpg 其实fiftyone来访问数据集的代码比较适合在Jupiter Notebook中运行。如上图所示,通过 launch_app 启动fiftyone的运行以后,在其Web UI界面中可以浏览数据集,此外,launch_app返回一个session变量,那么在Jupiter Notebook中运行以上代码时,就可以在代码中通过这个session访问打开的数据集,对其进行一些在线的处理和操作。 例如,我们可以在web UI界面中选择质量不好的图片,然后在notebook中对这些选中的图片执行删除操作,并导出到一个新的数据集中: !image.png session支持的选项很多,通过以上方式,就可以实现与已打开的数据集进行动态的交互。 从公开数据集中下载并清洗数据 FiftyOne中内置了一个叫做 Dataset Zoo 的模块。该模块预先写好了对接 COCO、Open Images、VOC 等几十个主流公开数据集的下载和解析脚本。在没有 FiftyOne的情况下,对于这些公开数据集的访问和使用,往往需要你去官网注册、下载几十 GB 的压缩包、研究它们复杂的 JSON 或 CSV 格式并写脚本解析和转换不同格式的坐标和标注信息。在FiftyOne的帮助下,只需要一行命令 foz.load_zoo_dataset("coco-2017", ...),它就会自动完成筛选、下载、解压、格式对齐等工作。 通过使用以下代码,就可以实现从fiftyone官网上自动下载COCO-2017数据集中前1000张人形的标注照片,并且把标注信息格式自动修改为YOLOv5格式,保存在当前脚本目录的my_yolo_dataset/coco_subset/train/子目录下。 > 注意,按照以下代码的流程,从指定数据集(coco-2017)中下载指定数量(1000张)的指标类别(human)的图片集,FiftyOne 在下载时只会流式下载用户指定的这个图片子集(先下载到C:\Users\用户\fiftyone,然后整理到当前脚本所在目录下),不需要把完整的几百个GB的数据集全部下载下来,这样的按需下载方式可以极大地节省本地硬盘空间和下载所需要的时间。 此外,从在线数据集中下载数据的过程中,为了提升数据集的质量,还可以在导出之前,对下载的数据集进行一定程度的清洗,例如以下代码,可以实现从下载的数据集中自从删除掉无标注框的样本,以及删除掉那些过小的标注框: 对数据经过以上清洗操作以后,再通过export命令把这些清洗后的数据导出来,可以有效的提升训练数据集的质量。 辅助YOLOv8模型微调 不只是可以简化训练数据集的下载、数据清洗和浏览数据集的过程,Fiftyone还可以与模型的推理过程相结合,以可视化的形式直观的显示出模型在推理数据上的表现以及分析其失效模式。例如,通过配合使用Fiftyone和YOLO模型,可以在YOLO模型训练完成后,可视化和评估该模型在验证数据集上的预测结果,从而更好地了解模型的预测能力在哪里失效,并针对性的改善模型的训练数据和指标。 以下代码演示了把YOLO模型的验证集数据导入FiftyOne,在FiftyOne中同时显示标注框和预测框的流程。这样训练人员就可以非常直观的从FiftyOne的Web UI界面上看到验证集上的标注框和训练模型的推理框,直观的了解到当前模型会在哪些数据上失效: 以下为在FiftyOne中显示YOLO模型验证集数据、标签以及推理结果的效果。 !a95a1162-1e30-459e-8466-b3e44ed840f6.jpg 参考资料 - Maximize your Computer Vision Performance | Try Voxel51 - Fine-tune YOLOv8 models for custom use cases with the help of FiftyOne — FiftyOne 1.11.0 documentation

YOLO模型的配置参数文件详细解释

本文对ultralytics/YOLO项目中的配置参数文件default.yaml文件中所包含的所有参数(可视化和导出类的参数除外)进行详细总结和解释。 ultralytics/YOLO项目中的配置参数文件路径在ultralytics/cfg/default.yaml。default.yaml文件是Ultralytics项目的核心配置文件,理解其中各项参数的含义对于成功训练自定义数据集以及执行推理、导出、部署等环节至关重要。 task与mode default.yaml配置文件的开头,使用 task 和 mode 这两个参数设定了模型的基本运行模式。这两个参数共同决定了YOLO模型的运行方式,不同的task会加载不同的模型架构和损失函数,而不同的mode则会执行相应的工作流程(训练、验证、推理等)。 task参数用于指定模型的任务类型,指定task参数以后,项目会根据指定的任务类型创建网络结构。当前的Ultralytics项目的实现中,包含有五种任务类型: - 目标检测(detect):目标检测任务,用于检测和定位图像中的目标对象。detect是该项目最常用的任务类型,也是default.yaml中的默认task。 - 实例分割 (segment):实例分割任务,不仅检测对象位置,还提供像素级的分割掩码。 - 图像分类 (classify): 图像分类任务,仅对整个图像进行分类。 - 姿态估计 (pose):姿态估计任务,用于识别人或动物的关键点/关节位置,提供类似火柴人一样的识别和检测效果。 - obb :旋转边界框检测任务,用于检测非轴向对齐的目标(如无人机视角下的物体) 对于以上各种不同类型的task,可以参考计算机视觉类AI应用领域及其主流模型总结一文。 mode参数用于指定当前的运行模式: - train:训练模式,用于执行模型训练过程。 - val:验证模式,用于评估当前训练好的模型在验证集上的性能,生成验证指标文件。 - predict:预测推理模式,用于使用训练好的模型对指定的数据进行推理输出。 - export:导出模式,用于将模型导出为不同的部署格式(如ONNX、TensorRT等)。 - track:在detect模型上启用跟踪模式,可用于对视频中的为每个检测到的目标分配唯一ID并持续跟踪其运动轨迹。启用后需通过tracker参数指定跟踪配置文件。 - benchmark:基准测试模式,用于更全面地评估模型在不同硬件和配置下的性能、速度与精度。 训练相关参数 default.yaml文件中与模型训练流程相关的参数及其解释如下表所示: !image.png 以下对上面的部分参数做更为详细的解释和说明。 save(默认为True)和save_period(默认为-1)参数用于指定是否保存训练的权重文件到save_dir/weights/目录下。在save设置为true的情况下,每个epoch结束以后,都会把当前训练的权重覆盖保存在last.pt文件中,而如果当前训练的效果比之前的指标更好的话,该权重文件也会保存在best.pt文件中;当save_period参数设置为N时(N不等于-1),每隔N个epoch就会在save_dir/weights/目录下多保存一个epoch_N.pt的中间训练权重文件。 cache参数决定了是否以及如何在训练开始的时候,将数据集预先加载并缓存起来,以避免在训练过程中重复进行耗时的磁盘读取和数据处理操作。设置为False(默认值)表示不做缓存处理,每次都是从磁盘加载新一个batch的训练数据;设置为True或'ram'表示提前把训练集中的数据缓存在RAM中,每次从RAM中取数据;设置为‘disk’时在第一个epoch期间,训练程序会将处理好的图像以特殊格式(.npy)保存到硬盘的临时目录,后续的epoch从该缓存读取。 project和name参数搭配起来可以按照自定义的方式指定模型训练文件的保存路径。不设置这两个参数的话,默认训练流程所产生的文件会保存在runs/detect/train/exp这样的目录下,而如果设置以后,这些文件的保存路径就是project/name。exist_ok这个选项则表示多次执行的时候是覆盖这个project/name还是在name指定的目录名后面加执行次数的索引后缀名来对多次执行的结果进行区分。 pretrained参数设置为False的情况下,模型训练只会使用model参数所指定的模型,模型各层的权重使用完全随机的参数进行初始化;pretrained参数设置为True的情况下,表示直接使用model参数所指定的模型文件中的权重;pretrained参数设置为某个权重文件路径时,表示使用model参数所指定的模型,但是模型的各层参数用pretrained所指定的参数文件进行设置和初始化。 seed和deterministic这两个参数用于控制模型训练过程和结果的随机性和可复现性。默认的配置下(seed=0,deterministic=False),模型训练的流程和结果保留了一定的随机性,比较适合日常的开发情况。而对于一些要求更为严谨的应用(如论文发表等),模型的训练需要提供严格的确定性和可复现性,此时应该把这两个参数设置为seed=42,deterministic=True。 single_cls参数是一个布尔值,默认为False。当设置为 True时,模型的输出会强制将所有对象都视为同一个类别,即模型只输出一个类别。如果应用只关心画面中是否有某个类别的物体存在时,可以把这个参数设置为True以将复杂的多类别检测问题简化为更基础的目标存在性检测问题。 rect参数用于指定是否使用固定的正方形长宽比作为模型训练图像的输入。在rect=False(默认值)的情况下,所有进行训练的图像无论其长宽比是什么,都会先通过缩放+填充的手段转换为正方形送入模型进行训练,这种模式一般是针对数据集长宽比不一致且差异比较大的情况;而设置rect=False的情况下,YOLO会首先会对每个批次的图像长宽比信息进行统计,找到最合适的长宽比,然后在训练的过程中,把这个批次的所有图像转换到新的长宽比送入模型进行训练,针对数据集中所有图片长宽比基本相同的情况(例如监控摄像头一般为4:3或者16:9),这种方式可以提升训练效率。设置rect参数的作用主要是可以根据数据集长宽比的情况对输入图像尺寸进行调整,尽量减少GPU计算的浪费,提升训练的效率和速度。 close_mosaic参数用于指定在训练过程的最后N轮训练禁用mosaic数据增强的功能。mosaic数据增强技术可以通过把四张训练图片以不同的、每个批次随机的方式剪切并且合并到一起,形成一张新的训练图片对模型参数进行训练,这种方式可以通过训练数据的多样性变化提升训练的效果。但是在训练的末期,我们一般希望参数尽快稳定下来,而mosaic增强会导致参数发生的变化过于剧烈,所以此时可以通过设置close_mosaic参数指定训练过程提前N轮禁用mosaic增强,确保参数在训练过程的最后阶段尽快稳定下来。 amp参数用于控制训练过程中所使用的数据及其计算精度。在传统的模型训练过程中,一般在全流程中使用FP32精度进行训练。当开启AMP模式以后,对于前向、后向运算等环节使用更节省内存的FP16精度,仅对梯度等计算相关的部分采用更高的FP32精度。这样做的好处是可以基本保持训练指标不变的情况下,大幅度减少训练过程对于内存的需求,以及加快训练速度。默认情况下一般推荐使能AMP模式,但是需要注意的是部分比较老旧的硬件不支持AMP模式。 fraction参数用于指定训练过程中参与训练的数据量占总的数据集数量的比例。一般情况下,我们对于模型的训练肯定都是要把100%的训练集数据送入模型对模型参数进行训练。但是在某些需要快速验证、并且数据集数据量很大的情况下,可以通过设置fraction参数只使用一定比例的训练集数据对模型进行训练,在这种情况下,每个epoch就只会随机的从训练集中提取出来fraction参数指定比例的数据进行训练。 Profile参数用于控制是否启用训练过程中的性能指标分析。在启用(Profile=true)后,整个训练过程中会详细的针对每一步的操作进行细粒度统计和分析,最终给出非常详细的性能分析数据。因此这个过程会需要更多的内存保存这些统计信息,并且大幅度拖慢训练的速度。因此除非是要对模型的框架进行调整,需要从很详细的程度上了解模型的执行效率,否则不建议开启profile模式。 freeze参数用于指定在训练的过程中哪些层的权重需要冻结,在训练中不更新这些层的参数。如果是在官方预训练的YOLO模型的基础上,基于自己的数据集进行模型的微调,因为官方的模型参数一般都是COCO这类比较大的数据集上进行了非常充分的训练,因此其预训练参数已经包含了非常强大的特征提取能力。在自己的数据集数量不是很大(几千张之内)、并且检测任务区别不大的话,建议可以使用freeze参数把网络的主干网络部分冻结,只使用自己的数据训练颈部网络和检测头部分。可以使用freeze参数控制冻结前N层或以列表的形式冻结指定的层参数。 multi_scale参数用于控制训练过程中输入图像的尺寸大小。如果multi_scale设置为False,训练过程中输入的图像尺寸大小固定为imgsz设置为640x640。而在multi_scale设置为True的情况下,首先针对每个batch随机选择一个图像尺寸大小(必须以32为单位),然后在这个batch的图片的训练过程中,图像在送入模型进行训练之前,首先会被缩放为这个新的尺寸。这样每个batch的输入图像尺寸大小都不同,可以随机调整训练数据的多样性,从而提升模型的适应性和泛化能力。 compile参数是YOLOv8基于Pytorch2.0的torch.compile引入的新功能,在开启的情况下,可以在训练的过程中优化模型的计算图代码来加速训练执行的代码运行效率,降低训练时间。如果训练数据集比较大、且训练所使用的硬件规格比较新的话,可以开启这个选项来加速训练过程。 图像分割与分类参数 以下的overlap_mask,mask_ratio以及dropout这三个参数仅在图像分割和图像分类任务中有效。 验证和测试参数 下表为default.yaml配置文件中Val/Test settings部分的配置参数、默认值及其简单的解释说明。 > 需要注意的是,下表中的部分参数虽然被归类在配置文件的Val/Test settings部分,但是这些参数同样会被用于predict过程。例如NMS算法相关的iou、max_det、conf等参数。 !image.png split参数用于指定当验证功能启用时(即val参数设置为True),验证过程执行时所使用的数据集。其取值范围为[val, test, train],分别表示使用验证集、测试集或训练集进行模型评估。绝大多数情况下应设置为val,以避免测试集数据泄露和确保评估的公正性。 conf,iou,max_det这几个参数是NMS算法的过滤参数。conf是目标检测的置信度,iou用于对重叠框进行过滤,max_det用于限制一张图片最大输出的检测结果数量。 half参数用于指定在模型前向推理的过程中使用的精度是FP32还是FP16。dnn参数用于指定在模型执行前向推理的过程中使用OpenCV的dnn模块还是使用pytorch框架。 预测参数 下表为default.yaml配置文件中Predict settings部分的配置参数、默认值及其简单的解释说明。 !image.png visualize参数用于控制在验证和推理执行过程中是否生成可视化统计分析结果以及图像识别的中间层特征图、热力图等信息,通过查看这些结果,可以辅助调试和定位模型所存在的问题。当设置为True的情况下,因为要生成这些额外的文件并写入磁盘,所以会显著增加内存需求以及影响推理的速度。 augment参数用于控制推理过程的执行,是否需要对输入的图像执行增强操作。在augment=False的情况下,模型在推理的过程中只会把原始图像输入后执行一次推理并给出结果;如果设置为True,模型会对输入的图像做各种增强操作(如翻转、缩放等)后执行多次推理操作,最后合并多次推理的结果为最终输出,因此预测的精度会有所提升,但是耗时会大幅增加。 agnostic_nms参数是一个NMS算法执行流程中的参数,用于在NMS对重复框过滤的过程中,是否需要区分不同的类别。agnostic_nms参数设置为False的标准NMS流程中,NMS算法会对不同类别进行区分,各个类别的检测框不会相互影响;如果设置为True,NMS会在过滤的过程中,把所有的类别放在一起进行过滤,这种情况下当两个不同类别的物体相距比较近的时候,有可能会被错杀。 classes参数指定推理过程中需要检测的目标类别索引列表。该参数未设置的情况下,表示对模型所支持的所有目标类型进行检测并输出;设置为某个列表时表示在执行NMS过滤的过程中仅关注该列表中的目标类型,其他目标都会被过滤掉。 超参数 模型在训练过程中的学习率与优化器方面的参数、默认值及其解释说明如下所示。 !image.png lr0和lrf这两个参数是与模型训练过程中反向运算时对参数调整的学习率参数。训练过程中的学习率参数会依据训练的优化器optimizer动态调整,其中lr0是模型开始训练时的初始学习率,此后学习率会逐渐变小,最终降低到lr0 * lrf的程度。momentum参数同样是一个optimizer优化器算法对模型参数进行调整的技术。引入momentum机制后,每次对参数的更新不仅依赖于当前计算的梯度和学习率,而且依赖于历史梯度,momentum参数用于指定历史梯度在本次参数调整中的加权系数。momentum的值越高,历史梯度对参数调整的影响越显著。 weight_decay参数用于控制权重参数的大小,避免权重参数过大导致过拟合问题。具体在训练过程中,通过把weight_decay参数和当前模型的总体权重信息增加到损失函数计算出来的损失值以及参数的调整量中,确保在权重参数过大的情况下,刻意的抑制参数的大小。 模型刚开始训练时,模型内部的部分参数处于随机初始化的状态,这个时候如果直接使用最大的学习率lr0对参数进行调整,会容易导致模型参数调节幅度过大而处于不稳定的状态。因此YOLO模型的warmup机制,就是指在刚开始训练的前几个epoch,把参数更新的学习率从0逐渐线性上升到lr0设置值,一开始的学习率很低,然后逐渐增大到lr0,有助于模型的参数快速达到基本稳定的状态,然后再开始正式的训练。warmup_epochs、warmup_momentum、warmup_bias_lr这三个参数均为warmup执行流程的相关参数。其中warmup_epochs是指该机制在前几个epoch中发挥作用,在warmup阶段,momentum会从warmup_momentum逐步线性上升到momentum参数的设置值,学习率(非偏置项)会从0逐步上升到lr0参数的设置值,偏置项学习率会从0逐步上升到warmup_bias_lr设置值,最后warmup阶段完成后,统一从学习率lr0参数开始正式训练。 与损失函数相关的参数、默认值及其解释说明如下所示: !image.png box,cls和dfl参数分别对应于YOLO模型的三个损失函数值的权重,其中cls对应于分类准确性的损失值,box和dfl一起对应了预测框位置准确度的损失值,box主要是从IOU的角度评估预测框与真值框的重合性,dfl则从上下左右坐标偏移量的角度来评估定位的准确性。适当调节box,cls和dfl参数可以用于调整不同类型的损失值在整体损失值中所占的比例,从而调整不同损失值所对应的指标。 nbs参数是名义上的batch size。实际的训练过程中,因为训练所使用的硬件限制方面的原因,batch size没办法设置的比较大,一般会设置为16。在batch size设置相对比较小的情况下,模型在训练过程中往往会存在波动较大、难以收敛的问题。比较理想的情况是使用batch size=64这样的配置。那么针对硬件配置限制的问题,batch size以及实际送入模型进行训练的图像batch仍然是按照实际设置的batch size(例如16)来进行训练,然后训练结果得到的损失值按照名义batch size与实际batch size的比例进行放大,当nbs设置为64时,两者相差4倍,把损失值乘以4倍送入模型反向传递进行参数更新。使用这种方法也可以解决实际batch size不足带来的模型训练稳定性问题。 训练数据颜色与几何增强相关的参数、默认值及其解释说明如下所示: !image.png hsv_h,hsv_s,hsv_v这三个参数对应的是训练过程中针对图像输入数据的颜色增强参数。HSV颜色空间包含色调H、色彩饱和度S和亮度V三个维度,以上三个参数以比例因子的形式定义了在训练过程中,训练图片数据会在原始图像的色调、饱和度以及亮度这三个维度上随机调整的幅度,通过这种对图像HSV空间三个维度的动态随机调整,来提升模型针对不同光照、色彩环境下的泛化能力。 degree参数用于指定训练集图片送入模型训练之前,在原始图像角度的基础上,以多大的幅度随机旋转一定的角度再进行训练。translate参数用于指定训练图片在原始图像的基础上,以多大的幅度向上下左右四个方向平移一定的比例再进行训练。scale参数用于指定在训练图片的原始图像基础上,以多大的比例范围内进行随机缩放后再进行训练。 shear和perspective这两个参数用于控制图像增强处理的几何变形功能,用于应对自动驾驶、无人机这类运动场景下的图像状况。其中shear是指把原始的长方形图像变形为平行四边形的拉伸动作,而perspective是指把原始的长方形图像从上到下或者从下到上逐渐变小的俯仰视角的变形动作,这两个参数的大小分别指定了两者变形的幅度大小。 图像变换增强参数部分的参数名、默认值及其解释说明如下所示: !image.png 以上三个参数比较简单。flipud表示训练图像在送入模型训练时有多大的概率上下翻转,fliplr表示有多大比例左右翻转,bgr表示训练图像有多大比例在训练之前首先把RGB通道顺序的图像转换为BGR通道顺序。三个参数均用于基于现有图像提升图像的多样化和模型的泛化能力。 高级混合增强参数部分的参数名、默认值及其解释说明如下所示: !image.png mosaic参数用于控制在训练过程以多大的比例开启mosaic增强。mosaic增强是指把四张训练图片缩放、裁切等处理后按照2x2的方式拼接为一张图像送入模型进行训练。该参数设置为1.0表示100%的训练数据都使用mosaic拼接处理后的图片,设置为0表示禁用mosaic增强,0-1之间表示训练数据采用mosaic增强处理的比例。 mixup参数用于控制多大比例的训练图片在训练之前首先进行mixup增强处理。mixup是指通过两张图片的像素值大小,乘以随机大小的比例,把两张图片的像素叠加在一起形成一张图片(两张图片的标签置信度也按照相同的比例处理),送入模型进行训练。cutmix参数用于多大比例的训练图片在进行训练之前首先进行cutmix增强处理。cutmix增强处理是指从一张图片随机裁切出来一部分矩形区域,粘贴到另外一张图片上,形成新的训练图像。 copy_paste和copy_paste_mode这两个参数仅用于图像分割模型。 auto_augment和earsing这两个参数仅用于图像分类模型。 配置文件相关参数 如果通过命令行或者在此处设置cfg参数,在YOLO模型的各项任务执行时,会把此处配置的yaml格式的cfg配置文件中的参数合并到default.yaml中。同名参数会以cfg传递的配置文件中的参数优先。 此外如之前部分对于mode中的track模式选项所解释的,track模式用于在detect模型上启用跟踪模式。此时需要在此通过tracker参数来指定跟踪模式运行的跟踪配置文件: tracker参数对应的配置文件所在的目录为ultralytics\cfg\trackers,当前版本中包含两种跟踪模式:botsort和bytetrack。前者是默认的跟踪模式,跟踪精度更高;后者主要针对实时性要求较高的跟踪类型,跟踪计算的速度更快。

YOLOv8模型网络架构解读

本文从YOLOv8和YOLOv5模型对比的角度,来详细阐述和总结两个模型在网络架构设计上的异同点,从而对YOLOv8模型的结构有比较全面和深入的理解。 以下是YOLOv5模型的总体架构图: !image.png 以下是YOLOv8模型的整体网络架构图: !image.png 主干网络 对比以上YOLOv5和YOLOv8架构图左侧的主干网络BackBone部分,可以看到这两个模型的主干网络的整体结构非常类似。区别主要只是以下两点: - 在P1层:YOLOv5使用的是FOCUS模块(老版本)或者6x6,步长为2的卷积层(新版本)实现输入图像640x640x3到320x320x64特征图的初始下采样处理;而YOLOv8模型使用的与主干网络中其他降维模块相同的3x3,步长为2的卷积层实现分辨率的降低,输入仍然是相同的320x320x64特征图。 - YOLOv8模型使用C2f模块替换了YOLOv5模型的CSPLayer模块(也就是C3模块),而C2f模块是YOLOv8的核心创新之一。 C2f模块 如上所述,YOLOv8主干网络中的C2f模块是其架构设计上的最大创新之一。首先对比YOLOv5的C3模块与YOLOv8的C2f模块在结构上的差异: !image.png 可以看到,C3模块采用的是双分支并行结构,将输入的特征图分为两部分分别给主分支和旁路分支分别处理: - 主分支:通过1×1卷积降维后,经过多个Bottleneck残差模块(残差模块数量可以通过depth_multiple参数设置)进行特征提取。 - 旁路分支:直接通过1×1卷积处理,以保持原始的特征信息。 - 最后进行特征融合:最后把两个分支的输出在通道维度直接拼接起来,再通过1×1卷积融合后输出。 而C2f模块采用的是多阶段串联结构,其设计看起来更加复杂一些: - 初始处理:输入的特征图首先统一通过1×1卷积生成中间特征图。 - 特征拆分:将中间特征图拆分为两部分,一部分特征通过多个Bottleneck模块串联处理,另一部分直接传递到末端。 - 多阶段处理:后续连续多个Bottleneck残差模块,每个残差模块的输出都分为两部分,一部分继续在下游的残差模块中进行处理,另外一部分直接传递到末端合并。 - 多尺度融合:最后将所有处理阶段的特征图拼接在一起,再通过1×1卷积融合。 比较以上两者的结构,C2f相比C3最大的差异就在于,C2f模块通过把内部残差模块输出的多阶段特征图拼接在一起,融合了包含中间处理阶段在内的不同深度的特征信息,特征更丰富;此外通过引入更多的跨层连接,形成更丰富的梯度流设计,使得在训练过程中梯度信息能够更有效地在网络中反向传播,缓解了深层网络的梯度消失问题,提升了训练稳定性和收敛速度。 但无论是C3模块还是C2f模块,其输出和输出特征图信息的维度都是相同的。 与YOLOv5模型相同,YOLOv8模块也是在8x,16x以及32x三个维度上分别输出三路不同分辨率的特征信息给颈部网络:80x80x256,40x40x512,20x20x512(YOLOv5默认的最小分辨率特征输出维度为20x20x1024)。 颈部网络 在颈部网络方面两者的区别不大,YOLOv8在YOLOv5新版本的基础上做了一些简化,但是整体的结构基本上是一致的:都是采用PANET的结构,通过自顶向下和自底向上的路径聚合,增强了主干网络输出的三种不同尺度特征之间的信息流动,最终把充分融合后的特征信息输出到检测头部分。 YOLOv8相比YOLOv5版本在颈部网络上的改进主要是: - 简化网络结构,去掉了上采样UpSample路径中的两个卷积块。 - 与主干网络相同,在颈部网络中同样使用C2f模块替换掉C3模块。 在颈部网络中对于三种不同尺度的特征信息进行各种融合处理后,YOLOv8模型最终从颈部网络输出三路不同尺度的特征信息给检测头部分:80x80x256,40x40x512,20x20x512。 检测头 相比于前面的主干网络和颈部网络,YOLOv8模型在检测头部分相比YOLOv5就做出了革命性的改进,主要体现在解耦头(Decoupled Head)和Anchor-Free机制这两个方面。 在YOLOv5网络的检测头设计上,其采用了耦合头(Coupled Head)的结构,即针对类别判断的分类任务和针对预测框坐标的回归任务共享同一组卷积层,最后在输出上通过不同的输出通道分别预测类别和边界框坐标。而YOLOv8的检测头采用了解耦头(Decoupled Head)的设计思路,将分类和回归任务完全分离为两个独立的网络分支,分类分支专门负责目标类别判断,使用独立的卷积层处理特征;回归分支专注于边界框位置预测,通过专门的卷积层输出坐标信息,避免两个任务目标在训练的过程中相互影响。两者的对比如下: !image.png 从上图可以看到,对于YOLOv8的解耦检测头的设计而言,分类分支和回归分支分别包含各自独立的两个3x3,步长为1的卷积模块的处理,最后再通过一个1x1卷积按照各自输出信息的维度进行降维处理。对于COCO数据集而言,模型针对一张图片所进行的推理,在检测头部分最终的输出数据维度为[1,8400,144]。 其中的1表示batch size,因为是对一张图片进行处理所以batch size为1。第二个8400表示从颈部网络送入检测头的三种不同尺度的特征信息,这些特征图上所有网格点的总和就是80x80+40x40+20x20=8400,特征图上的每一个网格点输出一个一维的张量信息,表示在这个网格点上的检测结果。最后的144可以分为两个部分:类别判断部分的长度为80,对应与COCO数据集的80个类别,类别分数越高,表示该网格点检测到的物体类别越有可能是什么;边界框位置检测部分长度为64,这个64的长度就与YOLOv8检测头的Anchor-Free机制相关。 从YOLOv2模型开始,YOLO的前几代都使用了基于Anchor的预测框机制,YOLOv8则采用了Anchor-free的设计,不再依赖于基于数据集聚类出来的长宽比来辅助定位物体。YOLOv8 Anchor-Free模式的实现则需要依赖于(DFL, Distribution Focal Loss)机制。 所谓的DFL机制,就是在预测框分支路径所输出的每个网格中,包含一个由这个网格检测到的物体预测框的坐标位置。以当前网格的中心点在原图分辨率(640x640)中的位置坐标(x,y)为基准,目标坐标位置由四个偏移量l(left),t(top),r(right),b(bottom)四个偏移量来进行计算,得到的最终预测框的坐标位置左上角是(x-left,y-top),右下角是(x+right,y+bottom)。l,t,r,b这四个偏移量中每一个使用16维的向量来表示,四个偏移量就是64维向量,这就是预测框分支路径中针对每个网格的输出数据。 例如对于P3层(stride=8)这一路的特征输出而言,锚点网格索引为 (10, 20)的情况下,该锚点网格的中心点在原图中的坐标就是((i+0.5)×stride, (j+0.5)×stride)=(84,164)。同样的道理,对于P4和P5的特征输出而言,就需要把stride分别修改为16和32来计算其锚点网格在原图中的坐标位置。这个锚点坐标位置加上l,t,r,b这四个偏移量就是一个检测框的左上角和右下角在原图中的位置输出。 那么对于l,t,r,b这四个偏移量而言,如何使用它的16维向量来进行计算呢? 这涉及到两个参数: - reg_max:通常为16,表示将偏移量的距离范围从0到最大距离均分为16个区间。 - stride:特征层的步长(P3=8, P4=16, P5=32) 以P3层为例解释偏移量的计算逻辑,其stride=8,能够支持的最大偏移量是16x8=128像素,这个128像素被按照8像素的间隔分为16段: !image.png 预测框回归分支针对每个偏移量输出的16维向量表示实际偏移量落在这16段各段范围内的概率。最终计算出来的实际偏移量是这16个概率与16段中心点的加权平均值(而不是取最大概率对应的那一段中心点),以下是偏移量的计算逻辑流程: !image.png 按照以上逻辑就可以计算出来四个偏移量,然后与锚点网格的中心点一起,就可以计算出来这个锚点网格检测出来的物体预测框的左上角和右下角的坐标。 对于P4和P5层的特征输出而言,偏移量的概率输出逻辑和计算逻辑是完全相同的,只不过stride分别变成16和32而已。 按照以上逻辑,YOLOv8模型最终就从检测头部分输出8400个预测框信息。这么多的预测框后续还要再经过NMS算法的过滤才会作为模型最终的检测输出。 至此基本上算是把检测头部分的解耦输出算是彻底搞清楚了:对于P3、P4、P5这三路不同尺度的特征输出而言,在检测头部分解耦分为两个分支,类别判断的分支以及预测框坐标的分支。对于COCO数据集而言,前者的每个网格输出了80维度的类别判断信息;后者则使用64维长度的向量表示出来这个预测框检测到的物体的坐标位置信息。所以每个网格的总的输出维度的长度是144。 但是目标检测置信度信息呢? 答案是,YOLOv8没有独立的置信度预测分支,而是把目标检测的置信度信息隐藏在类别预测信息中。最终每个网格锚点输出的目标检测置信度就是其类别预测分支输出的80维张量中的最大值,即可能性最大的那个类别的识别概率,当成该锚点存在目标的置信度。 参考资料 - 【YOLOv8】YOLOv8结构解读-腾讯云开发者社区-腾讯云 - What is YOLOv8? A Complete Guide - YOLOv8 原理和实现全解析 — MMYOLO 0.6.0 文档

YOLOv5模型的推理输出结果及其NMS算法解析

本文总结了YOLOv5模型对输入图片数据执行前向推理流程后,在其检测头部分输出的数据结构,以及在其输出数据上所执行的NMS算法流程。 YOLOv5模型的输出数据 在YOLOv5模型网络架构解读一文中详细整理了该模型的网络架构,对于分析和总结YOLOv5模型的输出数据而言,最重要的就是其中的检测头部分的结构: !image.png 从上图可以看到,YOLOv5采用了多尺度检测的机制,在检测头部分输出三个不同尺度的特征图,分别用于检测不同大小的目标。对于标准的640×640输入图像,其输出张量的形状为: - 大尺度特征图(P3层):[batch_size, 3, 80, 80, 5+NC] - 中尺度特征图(P4层):[batch_size, 3, 40, 40, 5+NC] - 小尺度特征图(P5层):[batch_size, 3, 20, 20, 5+NC] 每个输出张量的维度含义如下: - batch_size:批处理大小,表示同时处理的图像数量。 - 3:每个网格单元预测的锚框数量(anchor boxes),YOLOv5模型默认为3个。 - H, W:特征图的高度和宽度(如80×80、40×40、20×20)。YOLOv5模型输入为640x640,分别在8倍、16倍、32倍尺度输出特征图,对应的特征图分辨率就是80x80,40x40以及20x20。 - 5+NC:每个预测框的输出维度。 每个预测框的输出所包含的信息如下: !image.png 总结起来,对于P3层输出而言,共计输出80x80x3=19200个预测框;对于P4层输出,共计输出40x40x3=4800个预测框;对于P5层输出,共计输出20x20x3=1200个预测框。每个预测框都包含有(5+NC)长度的信息用于描述该预测框的信息。 NMS算法 如上所述,YOLOv5模型对一张图像进行的推理,最终会在三个尺寸维度上总共输出超过两万个预测框,其中通常会为同一个物体生成大量重叠的预测框。因此要经过过滤后才会输出真正的检测框,这个从模型输出张量数据过滤出来最终检测框的过程就是NMS算法。NMS算法的任务就是从重叠的框中筛选出最准确的一个,并抑制掉(删除)其他冗余的框,最终得到清晰、不重叠的检测结果。 NMS算法的基本执行流程如下: - 排序:将模型输出的所有预测框按照其置信度(Confidence Score)从高到低排序。 - 选取:选取置信度最高的框,将其添加到最终输出框的列表中。 - 抑制:以前一步骤选出来的最高置信度的预测框为基础,计算该框与剩余所有框的交并比(IoU)。删除那些IoU超过预设阈值(如0.5)的框,因为它们很可能指向的是同一个物体。 - 循环:重复步骤2和3,直到没有剩余的框。 YOLOv5在基础的NMS算法上做了一些改进: - 多尺度预测框的统一处理。因为YOLOv5模型的推理输出包括三个不同特征图(P3, P4, P5)的预测框,在进行NMS算法处理时就把所有的预测框合并在一起,再统一进行NMS过滤。在这种情况下,无论一个预测框是来自负责小目标的P3层,还是来自负责大目标的P5层,只要它的置信度高,它就有机会被保留。最终选出来的就是针对这个目标的置信度最高的框。 - 可支持多标签与单标签检测。该选项通过multi_label参数进行控制。在multi_label=False的情况下(单标签),每个预测框只属于一个置信度最高的类别,这是最常见的目标检测模式。而如果multi_label=True(多标签),一个预测框可以同时属于多个类别(例如,一个物体既是汽车又是出租车),在具体实现上,它会将一个原始预测框根据超过阈值的类别数拆分成多个检测条目。 - Merge-NMS(加权框融合)。该步骤是一个可选的后处理步骤,通过merge参数控制。对于IoU超过阈值的预测框,不是删除低置信度的框,而是将它们与高置信度的框进行加权平均。此举理论上可以提升定位的精度,因为融合了多个框的位置信息。 non_max_suppression源代码解读 以下结合YOLOv5源代码中的non_max_suppression函数来解释清楚对模型输出所进行的NMS过滤算法的执行逻辑和流程。 > YOLOv5源代码中的non_max_suppression函数在utils/general.py文件中。 non_max_suppression函数从YOLOv5模型得到的prediction预测数据是一个[batch_size, num_boxes, 5 + nc]结构的张量。其中的batch_size是一次送入模型进行推理的图片数量,则num_boxes对应于每张图片推理结果的预测框总数25200(19200+4800+1200),5+nc表示每个预测框的预测输出数据(center_x, center_y, width, height, object_confidence,class_1,...,class_n)。 以下对于non_max_suppression函数实现的重点流程代码截取出来进行解释。 以上就是YOLOv5源代码中non_max_suppression函数的声明,其中: - prediction:模型推理输出的原始预测数据,包含了大中小三种尺度的所有检测框的信息。 - conf_thres:置信度阈值,用于在执行NMS算法时控制和过滤低置信度的检测框。只有当检测框的置信度(包括物体置信度和类别置信度)大于此阈值时,才会被保留进行后续处理。提高此值会减少检测框数量,但可能会遗漏一些真实物体;降低此值会增加检测框数量,但可能引入更多误检。基于对精确度和召回率的要求设置该值。 - iou_thres:IoU(交并比)阈值,用于NMS算法中判断边界框是否重叠。当两个检测框的IoU大于此阈值时,NMS会保留置信度较高的那个,移除置信度较低的那个。提高此值会保留更多重叠的检测框;降低此值会更积极地移除重叠框。 - classes:指定只保留特定类别的检测结果。如果设置为某个类别索引列表,函数将只返回这些类别的检测框,其他类别会被过滤掉。 - agnostic:控制是否进行类别无关的NMS处理。 - 当为False(默认)时,NMS在每个类别内部独立执行,不同类别的重叠框不会相互抑制。 - 当为True时,NMS将所有类别视为同一类,重叠的框会相互抑制,不管它们的类别如何。 - multi_label:控制是否允许多标签检测(即一个物体可能属于多个类别,如检测结果同时是人和男人)。 - 设置为False(默认)时,每个检测框只保留置信度最高的一个类别。 - 设置为True时,一个检测框可以保留所有置信度大于conf_thres阈值的类别。 - labels:用于自动标注模式,使用该参数传入先验标签信息。 - max_det:限制每张图像最多输出的检测框数量。默认情况下一张图像最多输出300个有效检测框。 - nm:指定掩码的数量,只用于分割模型。 从参数prediction中读取出来批量大小bs(batch size),模型可以一次对多张图像预测,其预测结果张量的第一个维度就是图像的张数bs。nc是模型输出的类别数量,其中的nm仅用于分割模型,对于常用的目标检测模型而言nm为0。xc是比较prediction输出的所有检测框的置信度与参数传递的置信度阈值conf_thres,置信度大于阈值的框被标记为true,否则为false。实际上prediction输出的绝大多数预测框都在置信度阈值比较这一步被过滤掉了。 声明一个output变量用于保存后续NMS过滤后的有效检测框,每个检测框的检测信息长度都是6(坐标位置+置信度+类别)。然后用一个for循环从prediction变量中取出每一帧图像进行单独处理,后续的操作都是在这个for循环中针对一帧图像的过滤。 通过x = x[xc[xi]]就会在x中仅保留第xi张图片置信度大于conf_thres阈值的检测框,其他检测框都被去掉了。然后判断x列表中的检测框数量是否为0,为0的话就表示当前这一帧图像中没有检测到有效的目标,直接继续对下一帧图像进行处理。 此时,x列表中保存的就是当前图像帧中检测到的所有置信度>conf_thres阈值的检测框信息。 对x列表中的所有检测框的信息进行处理:给所有类别的判断概率乘以整个检测目标的置信率,得到各个类别的绝对概率;把模型输出的xywh坐标信息转换为nms算法所需要的xyxy格式。 以上代码整理给nms算法所需要的检测框列表信息,并且利用类别的绝对概率进行一轮过滤。此时分为多label和单label两种情况: - 多label的情况下,比较各个类别的绝对概率与置信度阈值conf_thres,保留所有绝对概率大于阈值的类别。 - 单label的情况下,从所有类别中找出绝对概率最大的那个类别,然后与置信度阈值conf_thres进行比较。大于阈值的情况下保留。 此时的x列表中的每个检测框中只包含有一个类别,其格式为[x1,y1,x2,y2,confidence,class]。如果一个检测框同时支持两个类别的话(multi_label开启的情况下),就会在x列表中有两个独立的检测框。 如果设置classes参数的话,就在x列表中仅保留classes所指定的类别列表,其他检测框信息全部过滤掉。然后再检查一下当前剩下的检测框数量,如果等于0的话直接进行下一帧图像的处理。 以上这部分就到了NMS算法的核心处理步骤了。首先对x列表中的所有检测框按照置信度进行降序排列,并且只保留前max_num个检测框;然后根据agnostic参数的设置,决定后续的nms过滤是否需要区分不同类别,在agnostic=false的情况下,给其类别乘一个很大的数字max_wh(默认为7680),确保各个类别在不同的空间中进行计算,这样就不会在NMS过滤中相互影响了。 最后从x列表中分别取出各个检测框的坐标boxes及其检测置信度scores,然后调用torchvision.ops.nms执行NMS过滤。torchvision.ops.nms返回的是一个一维整型张量 ,其中包含了经过NMS算法处理后应该保留的边界框的索引,这些索引按照置信度从高到低的顺序排列。 以上代码对torchvision.ops.nms的返回结果仅保留前max_det个结果作为最终的输出。在merge功能开启的情况下,基于IOU的比较对最终输出检测框的坐标位置进行精调,提升坐标位置的定位精度。 最后一步就是把NMS过滤后的检测框列表赋值给output作为non_max_suppression函数的最终输出。 参考资料 - yolov5 极大值抑制 nms 代码详解 - 金色旭光 - 博客园

机器视觉目标检测模型的P、R、PR、F1指标详解

YOLOv5模型训练结果文件中的F1_curve、P_curve、PR_curve、R_curve这四张图片文件,分别对应于模型训练完成后的F1、P、PR以及R指标的曲线图。本文对这四个指标进行了详细的总结和整理。 P_Curve P曲线对应于模型的精确率(Precision),该指标衡量的是模型预测为正例的样本中,有多少是真正的正例。它关注的是预测结果的准确性,即“查得准不准”。精确率越高,说明模型误检和误判(将背景或其他物体误认为目标)的情况越少。例如,在安防监控中,高精确率就意味着报警的准确性高,减少了误报的情况。下图是一张典型的P_Curve图: !image.png 可以看到,P_Curve图的纵坐标是精确度Precision,横坐标是置信度Confidence。置信度阈值提高,精确率通常呈现出上升的状态。这也是很好理解的事情:置信度越低,就有越多的误判目标被统计在内,这样自然会拉低精确度指标。而随着置信度阈值提高,大量置信度较低的检测结果被过滤掉,误判情况减少,精确度指标自然就上升了。 理想情况下的P_Curve曲线是一条Precision始终在1.0附近运行的直线,这意味着无论你如何设置置信度阈值,模型的预测都非常可靠,误检率很少。当然实际上这种情况是不可能出现的。绝大多数情况下,P_Curve都呈现出Precision随着置信度阈值Confidence迅速上升的曲线,直到某个点上曲线的纵坐标接近为1,并一直平缓运行。但无论如何,P_Curve曲线下所包围的面积越大(实际上也就意味着Precision能够越快达到接近1的程度),说明模型在不同阈值下都能保持较高的精确率,性能越稳健。 如果只有有个别类别的曲线(如上图中的crib曲线)远低于其他曲线,这可能意味着数据集中该类别的样本数量不足(实际上确实如此),或者样本质量较差,导致模型难以准确学习到该类别的特征,这时就需要考虑为该类别收集更多数据或进行数据增强。此外,正常情况下,P_Curve曲线应该是平滑上升的,而如果曲线出现剧烈波动或异常陡降,这可能就暗示训练过程中存在着过拟合或欠拟合的问题,需要做针对性的处理。 P_Curve曲线最主要的价值在于辅助科学地设置模型预测的置信度阈值,在精确率(Precision)和召回率(Recall)之间达到平衡,并且能够满足项目应用需求和目标。例如,在无法接受误检、追求高精度的应用场景中(如安全监控中的误报警成本很高),此时应该根据P_Curve曲线,从中选择一个高精确率(如0.95或更高)所对应的置信度阈值。这种情况下模型的输出会非常保守,只输出它极其确定的结果,但代价就是可能会漏掉一些不太确定的正样本,从而导致召回率降低。 R_Curve 与P曲线相对应的则是R曲线,即召回率(Recall)曲线。召回率衡量的是在所有真实正例中,有多少被模型成功预测出来了。因此,Recall指标关注的是模型发现正例的能力,即“查得全不全”。召回率越高,说明模型漏检(未能检测到真实目标)的情况越少。 与P曲线类似,R曲线的横坐标同样是置信度(Confidence),纵坐标则是模型的召回率(Recall)。因此它展示的是当模型输出的置信度阈值从0到1变化时,召回率是如何变化的。下图是一张典型的召回率曲线图: !image.png P_Curve曲线和R_Curve曲线都是以置信度阈值Confidence作为横坐标,但这两个指标相对比置信度阈值的变化则是刚好相反的。在R_Curve中,随着置信度阈值的提高,召回率会呈现出下降的状态。从直观上也是比较容易理解的:置信度越高,也就会导致越多的正例判定被过滤掉(因为置信度达不到阈值的要求),自然也就导致漏检增加、召回率降低了。 整个R_Curve在上图中的整体位置越高,曲线与两个坐标轴所包围的面积越大,说明模型在不同置信度阈值下都能保持较高的召回率,性能越稳健,漏检越少。 类似的,R曲线的主要价值也在于帮助科学地选择和设置置信度阈值,在满足应用需求的情况下平衡精确率(P)和召回率(R)。例如在追求高召回率、完全不能接受漏检的应用场景中(医疗诊断、自动驾驶中的障碍物检测等),此时就应该根据R曲线,选择一个可接受的高召回率(如0.95或更高)所对应的置信度阈值。 下图是P曲线与R曲线的对比和总结表: !image.png PR曲线 PR曲线(Precision-Recall Curve,精确率-召回率曲线)是评估目标检测模型性能的核心工具,它可以直观地展示模型在不同置信度阈值下,精确率(Precision)和召回率(Recall)之间的权衡关系。 PR曲线以召回率 (Recall)为横坐标,精确率 (Precision)为纵坐标。曲线上的每一个点都代表了在某个特定的置信度阈值下,模型所达到的精确率和召回率组合。下图是一个典型的PR曲线的示例: !image.png 针对某个特定类别的PR曲线的绘制流程如下: - 首先将模型在验证集上所有预测出来的边界框,按照其置信度分数从高到低进行排序。 - 然后遍历阈值,从最高置信度(如0.99)开始,逐步降低阈值(如0.98, 0.97, ..., 0.01)。在每个阈值下,将所有置信度高于该阈值的预测框视为正例,计算此时的精确率和召回率。 - 最后把计算出的所有的(Recall, Precision)点连接起来,就形成了PR曲线。 通常情况下,PR曲线的形态是一条从左上角开始逐渐向右下角下降的曲线。这意味着,随着召回率的提高(想检测出更多真实目标),精确率会逐渐下降(误检会增加)。而反之,如果想要提高精确率(减少误检),召回率就会下降(漏检会增加)。 在对两个模型的性能进行比较的时候(当然是使用不同的模型对相同的验证数据集进行的测试),如果模型A的PR曲线完全包住模型B的PR曲线,则可以断言模型A的性能优于模型B。而如果两条曲线发生交叉,则需要通过分别计算两条曲线与两个坐标轴之间的面积(这实际上就是AP指标)来比较两者的优劣。 PR曲线与AP/mAP指标的关系 PR曲线看上去直观,但是不是一个具体的数字,所以使用起来尤其是对不同的模型进行比较的时候,并不是很方便。所以基于PR曲线进一步引入了AP和mAP指标。 AP (Average Precision)指标是指PR曲线与两个坐标轴之间的面积。AP值越高,说明该模型在保持高召回率的同时也能维持较高的精确率,综合性能也就越好。当然,AP指标是针对单个检测类别的指标。 而大多数目标检测模型都支持对多个不同类别的目标类型进行检测,因此要比较模型的整体优劣,就要评估所有类别目标的平均AP指标,这个平均AP指标就是mAP(mean Average Precision)指标。mAP指标是目标检测模型经过训练后所支持的所有类别AP值的平均值,也是目前目标检测领域最权威、最常用的评估指标。 F1曲线 完整理解了以上的P曲线和R曲线,F1曲线就比较好理解了。F1分数是精确率(Precision)和召回率(Recall)的调和平均数,用于综合评估模型的性能,其计算公式为: 因为Precision和Recall指标都是介于0-1之间的小数,所以以上公式计算出来的F1分数的取值范围也在0到1之间。F1分数的值越高表示模型在精确率和召回率上的综合表现越好。下图是一张典型的F1曲线图: !image.png 从上图可以看到,F1曲线图中同样以置信度阈值Confidence作为其横坐标,纵坐标则是不同置信度情况下的F1分数。正常情况下,F1曲线通常呈现一个先上升后下降的山峰形状。在较低的置信度阈值下,模型会预测出很多结果(高召回率R),但其中包含大量误检(低精确率P),因此导致F1分数在低置信度情况下较低。随着阈值提高,误检减少,精确率上升,F1分数也随之上升。当达到某个峰值后,继续提高阈值会过滤掉一些正确的预测(低召回率),从而导致F1分数下降。 - 曲线形态高且宽:如果F1曲线的两侧很陡峭,并且在较高的位置形成一个宽阔的平台,说明模型在很宽的置信度范围内都能保持较高的性能,模型非常稳健。这就是非常理想的F1曲线的形态。 - 曲线形态低且窄:如果F1曲线的整体位置较低,且峰值尖锐,那就说明模型只有在非常特定的阈值下才表现尚可,整体的泛化能力较差。 整体来讲,F1曲线与P曲线(Precision-Confidence)和R曲线(Recall-Confidence)密切相关,它们共同构成了一个完整的评估体系。三者结合起来可以根据应用的具体需求,辅助更为科学的选择出来模型推理的最佳置信度阈值: - 在追求高精确率(避免误检)的应用需求中,可以选择峰值点右侧的更高阈值,牺牲一些召回率来换取更高的精确率。 - 在追求高召回率(避免漏检)的应用需求中,可以选择峰值点左侧的更低阈值,牺牲一些精确率来换取更高的召回率。

YOLO模型训练生成文件解读

本文对YOLO模型基于自定义数据集在训练过程中所生成的文件进行解读,帮助建立对YOLO模型训练流程更加深入的理解。 如一文总结YOLOv5模型的自定义训练、测试与模型导出的全过程所总结的,使用train.py脚本基于自定义的数据集对YOLOv5模型训练后,会在YOLOv5的源代码目录下的runs/train/exp目录(exp是默认的目录名称,如多次训练,可能会生成 exp2, exp3等)下生成诸多训练结果文件。本文从功能分类的角度对这些文件及其文件夹的内容进行详细的解释。 !image.png weights/模型权重文件目录 exp目录下的weights子目录是最重要的输出,其中保存了本次训练好的模型权重文件。包含有last.pt和best.pt两个文件。 best.pt文件是本次训练过程所生成的最佳模型权重文件。这里所谓的最佳,是指在验证数据集上表现最好的模型权重文件。在train.py训练的过程中,每个epoch结束后都会在验证集上使用mAP0.5指标评估模型的性能。如果当前epoch的性能超过了之前的最佳记录,就会使用当前epoch的权重参数更新best.pt文件。而best.pt也是实践中最常用的模型文件,因为它代表了训练过程中的最佳性能。实际生产流程中会把该模型文件导出为ONNX、TensorRT等格式,在生产环境中进行部署。 last.pt文件则是模型训练时最后一个epoch的模型权重文件。无论使用验证集进行验证的性能如何,在每个epoch训练结束后都会把当前的模型权重信息更新一个last.pt文件,确保其中始终保存最新的训练状态。last.pt存在的意义是,当训练过程意外中断或者我们希望在当前训练的模型基础上继续进行训练时,可以基于last.pt文件基于当前的状态继续进行训练。此外,使用last.pt文件还可以分析训练过程中的收敛情况,观察最终训练状态。 训练结果指标 YOLOv5模型训练完成后的训练结果指标对应于results.png和results.csv两个文件,分别对应于训练过程中每个epoch的关键指标数据及其图表。 results.png 下图是results.png的一个示例: !image.png 可以看到在results.png上显示了模型的多个指标: - 训练集/数据集(box_loss,obj_loss,cls_loss):分别对应训练集和数据集上的BOX坐标位置损失,置信度损失以及分类损失。 - 在理想情况下,所有的损失曲线都平稳下降,并且最终保持在一个较低的水平。训练损失略低于验证损失是正常的。 - 过拟合:如果训练损失持续下降,但验证损失在某个点后出现开始上升的情况,说明模型过拟合了训练数据。(上图中的置信度损失就是这种情况)。此时应该尝试增加训练的数据量、降低模型的复杂度或者减少训练的轮次数量。 - 欠拟合:如果训练和验证损失都处于一个较高的水平且不再下降(比较典型的情况就是训练和验证损失都还处于较高水平时就进入了平台期,基本不再下降),说明模型可能欠拟合,需要更复杂的模型、更长的训练时间、适当调整超参数。 - 精度指标 (precision, recall, mAP@0.5, mAP@0.5:0.95): - Precision (精确率):表示模型预测为正例的样本中,有多少是真正的正例。该指标越高越好。 - Recall (召回率):表示所有真正的正例中,有多少被模型预测出来了。该指标越高越好。 - mAP@0.5:在IoU阈值为0.5时的平均精度均值。这是最常用的一个指标,越高越好。 - mAP@0.5:0.95:在IoU阈值从0.5到0.95(步长0.05)的平均精度均值。这是一个更严格的指标,能更好地反映模型定位的准确性。 以上提到的精度指标中,mAP@0.5指标主要衡量的是分类能力和基础的定位能力。只要定位框的位置大致正确,就能在该指标上得到高分。而实际的效果,一个模型可能有很高的mAP@0.5指标,但它的预测框可能很“飘”,不够精确。mAP@0.5:0.95相比之下则更为严格,该指标强制要求模型必须同时具备精确的分类和定位能力。要求模型不仅要能正确地识别出物体,还要能准确地框出物体的边界。一个模型如果mAP@0.5:0.95很高,说明它的预测框非常精准。 > mAP@0.5:0.95指标的计算逻辑是:首先在IoU阈值从 0.5 到 0.95 的范围内,以 0.05 为步长,计算10个不同的IoU阈值(即0.5, 0.55, 0.6, ..., 0.95);然后对每个IoU阈值,都计算一次该阈值对应的mAP指标;最后,将这10个mAP值取平均,就得到可最终的mAP@0.5:0.95指标。 results.csv 如下图所示,results.csv文件中的内容跟上面的results.png图表的内容基本一致,就是每个epoch的损失值和精度指标计算结果的列表。除了以上数据以外,results.csv中还报了三个参数x/lr0、x/lr1、x/lr2。 !image.png x/lr0、x/lr1、x/lr2这三个参数分别对应于YOLOv5模型主干部分、颈部网络、检测头(详情可参考YOLOv5模型网络架构解读一文)三个模块在训练过程中所使用的学习率。在YOLOv5模型的训练过程中,三个部分的学习率是各自独立的,而且每个部分的学习率也并非一成不变。为了帮助模型更好地收敛,训练过程中会使用学习率调度器(Learning Rate Scheduler)来动态调整学习率。x/lr0、x/lr1、x/lr2这三个不同参数组的学习率值,就会根据具体的调度策略(如余弦退火)在每个训练步骤(Step)或每个 Epoch 后发生调整和变化,results.csv文件中就记录了这些学习率值的变化情况。 混淆矩阵 confusion_matrix YOLOv5模型的训练结果文件中有一个confusion_matrix.png文件用于直观的显示,训练出来的模型在验证数据集上对各个类别的预测情况,因此主要用于评估模型的分类性能。 下图是一个典型的包含有6个分类的confusion_matrix.png文件: !image.png 对于以上文件的解读: - 对角线上的值,是模型正确预测样本结果的比例。值越高越好(对应的方块颜色越深),说明该类别能够被正确的识别。 - 非对角线上的值,表示错误预测所占的比例,即模型将真实类A的样本错误预测为类B的数量比例。值越低越好(对应方块颜色越浅),说明类别混淆的情况越少。 从上图可以看到,除了crib类别在判断中容易误判以外,其他类别的类别的混淆都很少。针对产生混淆较多的类别,就需要检查该类别容易与哪些类别之间产生混淆的情况(上图可以看到crib主要是与background产生混淆,此时就应该多提供一些能够区分这些不同类别的训练图片的数量,或者针对性的使用数据增强的方式,模拟两个类别容易产生混淆的场景并重新进行训练。 F1/P/R/PR指标图 YOLOv5模型每次训练完成后,都会在exp目录下生成P_curve、R_curve、PR_curve、F1_curve这四个指标的图形文件。关于这四个指标在机器视觉目标检测模型的P、R、PR、F1指标详解一文中已有非常详尽的介绍,在此不再赘述。 train_batch和val_batch图片文件 YOLOv5训练过程中,还会基于训练数据集生成三张训练样本图train_batch,以及基于验证数据集生成三张真实标签图val_batch_labels和三张验证集预测结果图val_batch_pred,共计9张图。如下所示: !image.png train_batch的三张图展示了训练集中不同批次的样本图像。YOLOv5在训练过程中,会自动对送入模型进行训练的图像进行数据增强操作,这些操作包括缩放、旋转、色彩调整以及拼接等。可以通过这三张图看到训练数据进行增强后的效果,每张图片上的真实边界框(Ground Truth)和类别标签,有助于了解训练数据的多样性。从以上的train_batch的图像中可以看到,YOLOv5模型在训练过程中采用了Mosaic数据增强的技术,其核心思想是:将四张不同的训练图片,通过随机缩放、裁剪和拼接的方式,组合成一张新的、更大的训练图像,然后缩放到模型要求的640x640分辨率图像送入模型进行训练。 val_batch_labels的三张图展示了验证图片集中不同批次的真实标签(Ground Truth)的效果,相当于是模型基于验证集进行测试的标准答案。这些验证集图片直接标注了真实存在的目标物体的类别,以及它们的具体位置。 val_batch_pred图片的显示内容与val_batch_labels的三张图相对应,展示出来针对同一批验证集图片,模型执行验证测试的过程中推理出来的目标物体的类别和位置。通过对比真实标签图val_batch_labels和预测结果图val_batch_pred,可以发现模型在哪些地方容易出错,分析模型的误检、漏检等错误模式。

YOLOv5源代码解读之Detect.py

本文对YOLOv5源代码中用于执行推理任务的detect.py脚本的源代码进行详细解读,完整理解从参数传递与解析、推理执行与结果输出的全过程。 YOLOv5源代码中的detect.py脚本专门用于在模型训练好之后执行推理(Inference)任务,也就是利用训练好的模型对新的图像、视频、实时流等数据进行预测,找出+标记其中感兴趣的目标并存储。 detect.py的典型调用方式 使用detect.py脚本执行基本的推理任务,最重要的传递参数有两个:通过--weights指定模型的pt文件,通过--source指定执行推理的目标任务(图片、视频还是流媒体)。 图片(目录)检测并显示结果: 视频文件检测,保存结果和检测数据: 使用UVC摄像头并只检测特定类别: 以上执行detect.py脚本,生成的结果文件会保存在YOLOv5源代码的runs/detect/exp目录(可以通过--project和--name参数进修改)下。 detect.py脚本的参数列表 与YOLO代码中的其他几个执行脚本相同,detect.py使用argparse包的ArgumentParser Class对诸多的运行参数进行管理。在调用该脚本时,首先需要传递对应的选项参数。 下图整理出来detect.py脚本支持的所有参数选项、默认值以及对该参数的功能解释: !image.png 对以上操作列表中部分不是很直观的参数详细解释如下: - 注意,绝大多数情况下,不需要传递--data参数。因为在--weights参数所传递的模型文件(.pt)中已经包含了进行目标检测和识别所必需的分类信息。如果通过--data强制使用指定的YAML配置文件中的names列表,会覆盖模型文件内嵌的类别信息,这反而会引起混淆。所以默认且推荐的方式是直接使用pt文件内置的分类信息,此时模型使用自身训练时的类别定义,行为正确且简洁。 - dnn参数:在某些情况下可能需要使用OPENCV中的dnn模块(而不是默认的pytorch算子)来执行推理流程的计算,以期望与现有的基于OPENCV开发的C++应用程序结合并产生更好的执行性能,此时可以尝试在推理时传递dnn参数,这样在推理过程中的底层计算就使用opencv的dnn而非pytorch。当然传递这个参数的前提是要保证传递的模型文件是onnx格式才行,其他模型文件格式不支持dnn参数。 - visualize参数:表示在执行推理的过程中,可以把YOLOv5主干网络各个网络层提取出来的特征图也保存下来,这样在通过观察和分析各层特征图来判断推理过程中存在的问题,更直观的了解卷积神经网络的工作流程和原理。 - augment参数:对应于YOLOv5推理过程中的TTA(Test Time Augmentation)操作。众所周知,训练过程中的Augmentation操作是通过对原始图片的随机缩放、剪裁、色彩变化等操作有效的扩展训练数据量,并提升模型的泛化能力,防止过拟合。而推理过程中对于一张待测图片,以类似的逻辑(如缩放、翻转操作)产生多张图片,分别送入模型执行推理操作,生成多个检测结果,然后把这些检测结果映射回原始的图像坐标空间,再通过NMS等步骤集成,从而得到更稳定、更准确的检测结果。当然,可以看到开启这个TTA操作以后,涉及到对对同一张图片的多次前向推理操作,肯定会显著增加单次推理的时间。 - update参数:我们完成自定义训练模型所生成的best.pt文件中,还包含有优化器状态、训练轮数、最近评估结果等信息,这些信息对于最终的部署没有意义。传递update参数后,detect.py在执行预测时会移除这些信息,生成一个只保留模型权重、模型架构定义和必要的元数据(如类别名称)的更干净的模型版本,这使得模型文件更精简,更适合最终部署和分享。当然对于自己训练出来的best.pt文件,也可以strip_optimizer('runs/train/exp/weights/best.pt')命令来精简模型。需要注意,使用--update参数清理模型文件中的优化器信息是不可逆的操作,处理后的模型文件将无法直接用于恢复训练或断点续训,因此在执行前请务必保留原始的训练权重副本。 推理执行流程代码解读 detect.py脚本中主要的函数只有两个:parse_opt用于实现对参数解析,run则用于执行具体的推理过程。 run函数所执行的整体推理过程如下图所示: !image.png 以下从代码执行层面,对detect.py脚本执行流程的重点部分进行总结。 模型的加载 加载模型部分的代码如下所示: 首先使用select_device函数基于--device参数的指定,从CPU、CUDA以及Apple的Metal Performance Shaders (MPS)选择对应的设备类型,根据设备类型设置模型推理运行的环境变量,并创建与之对应的torch.device对象,后续的模型推理流程就执行在这个torch.device上。 DetectMultiBackend就是模型的加载过程而已,之所以要使用这个单独的函数来加载模型,是因为这个推理过程可以支持多种不同的模型文件格式,并不限于训练生成的pt文件: DetectMultiBackend函数会根据--weights参数传递的模型文件名称或者url自动解析其对应的模型文件类型,然后加载到系统中,用于后续的推理流程中。 根据输入源创建dataloader 接下来就是根据--source参数所指定的输入源的类型不同创建对应的dataloader,而后续的推理流程就是不断地从dataloader中读取出来一帧帧的图像执行推理并保存推理结果。 --source所传递的输入源主要可以分为三种类型: - webcam:电脑上的PC摄像头,通过http、https、rtmp、rtsp等前缀指定的流媒体url。 - screenshot:当前电脑的截屏图像。 - 其他:普通图片、视频文件,以及目录。 对于以上三种类型的source,分别调用LoadStreams、LoadScreenshots、LoadImages返回一个dataloader类对象,后续的推理流程就是从这个对象中取出每一帧图像执行推理: 注意以上代码中的bs变量。该变量表示的是batch size,即每次送入模型执行推理的图片数量: - 对于webcam而言,batch size等于同时处理的摄像头或视频流数量(在--source参数中传递摄像头列表)。 - 对于screenshot而言,batch size固定为1,表示每次处理一帧屏幕截图图像。 - 对于通路径场景对应的oadImages类,batch size同样固定为1,表示每次通过模型对一张图片文件或者一帧视频图像进行预测。 推理流程 每一帧图像的推理可以分为三个子过程:图像的预处理,推理,以及对推理结果的NMS过滤。 在执行推理之前首先预热模型和创建三个用于统计各个子过程执行时间的Profile对象: 模型在之前的阶段已经加载,这个warmup操作的作用,是在执行真正的推理流程之前,首先向模型传递2-3个dummy图像执行几次forward操作,从而让模型的参数和运行状态在执行真正的推理之前达到最佳状态。当然,如果只使用detect.py推理一张图片的话,这个warmup就意义不大了,而如果推理连续的视频图像流,在启动推理之前执行一次warmup,可以让正式开启推理之前硬件处于良好的状态下,有利于加快连续视频流的推理。 此外,就是通过创建三个Profile(device=device)对象的dt数组,后续分别用于记录推流流程三个子过程的执行时间。 以下是推理流程三个子过程的执行流程代码,分别使用一个Profile对各个子过程的执行时间进行统计: - 图像预处理过程,主要是把图像格式转换为pytorch张量,拷贝到torch device上,然后把所有的像素数据除以255做归一化处理。 - 推理过程,对于普通的pt文件或者onnx格式的模型而言,实际上就是调用model()操作执行一次前向操作而已。 - NMS过程,对前一步推理过程所产生的推理结果pred执行NMS处理。 推理结果的解析 以上推理流程最后一步通过non_max_suppression()过滤后返回最终的目标检测结果pred。 pred的数据结构是一个PyTorch张量,其中包含有batch size(也就是上面的bs变量)个det数据结构。det同样是一个张量,表示在单张图像在进行目标检测的检测结构。det的格式为(n,6),表示这张图像中检测到n个目标,每个目标对应一个六元素的元组,按顺序描述如下: - x1,y1:表示检测目标的左上角坐标。 - x2,y2:表示检测目标的右下角坐标。 - conf:检测结构的置信度。 - cls:类别判断。 接下来的后处理阶段的代码就比较简单了,主要就是对NMS处理后的pred及其中包含的det数据结构解析,得到所有图片中检测到的各个不同目标的坐标位置、置信度以及类别判断等信息,标注在图像上或者保存在其他文件中。 以上代码首先对pred结构进行循环处理,每次处理对应其中一张图片的检测结果det。而对于每张图片的检测结果,可以按顺序分别得到检测对象的坐标位置、置信度和类别。当然,因为模型的输入固定是640x640分辨率,所以det结果输出的也是基于以上分辨率的坐标,此时要在原图上标注检测坐标信息,就需要通过scale_boxes把模型输出的坐标转换为原图的坐标。

YOLOv5模型的配置文件详解

本文配合YOLOv5模型的架构图,对该模型的yaml配置文件各个部分的配置参数细节内容进行了详细的总结和解释,有助于更细致的理解模型不同规模版本之间的差异。 在YOLOv5之前的版本中,普遍采用cfg文件对其各种配置参数进行控制。从YOLOv5版本开始,正式采用了yaml配置文件格式。YOLOv5的配置文件在其源代码目录的models子目录下,该目录默认情况下包含有yolov5n.yaml、yolov5n.yaml、yolov5n.yaml、yolov5n.yaml、yolov5n.yaml共五个模型配置文件,分别对应各个不同规模的模型结构。 - 有关YOLOv5模型的不同版本之间的差异,可以参考YOLOv5模型的不同版本总结。 !image.png 从这个配置文件的结构上看,YOLOv5的配置参数信息可以分为全局参数部分和网络结构参数部分,而网络结构参数部分又可以分为backbone和head两个子部分。 全局参数 配置文件的全局参数部分如下所示: nc nc(number of classes)这个参数比较简单,就是用于定义模型需要检测的目标类别数量。因为YOLOv5模型默认是使用COCO数据集训练的,所以这里默认的80就是COCO数据集所支持的目标类别数量。如果要使用自定义数据集对YOLOv5模型进行从头训练的话,nc对应为自己数据集所支持的类别数量(在数据集配置文件中设置并覆盖网络模型配置文件中的nc设置值)。 depth_multiple depth_multiple是一个缩放因子,用于实现对网络结构中主干网路和颈部网络每个CSPBlock(在YOLOv5 v6.0中被称为C3模块)中所包含的残差块(Bottleneck模块)的数量的控制。下图是YOLOv5网络中CSPBlock的结构。这里的depth_multiple就表示下图中n(也就是DarknetBottleneck残差块的数量)的数量。每个CSPBlock中包含的DarknetBottleneck残差块的数量越多,自然就表示网络的深度越深,这就是depth的由来。 !image.png 与depth_multiple参数相关的计算代码如下: 其中的n是在配置文件中预设的模块重复次数,max()函数用于确保计算后的模块数是一个不小于1的整数。以YOLOv5s为例,主干网络部分第三个CSP Layer的配置为[-1, 9, C3, [512]],即这个CSP Layer中包含有连续9个残差块DarknetBottleneck,YOLOv5s的depth_multiple为0.33,因此在YOLOv5s中该层实际的残差块数量 = max(round(9 * 0.33), 1) = max(round(2.97), 1) = 3。这意味着,在YOLOv5s中,这个位置原本设计重复9次的C3模块,最终只会堆叠3次。 YOLOv5各种不同规模模型的depth_multiple参数及其对应的CSP Layer残差块的数量如下图所示: !image.png depth_multiple width_multiple的逻辑与depth_multiple类似,通用是以乘数因子的形式控制着网络中所有卷积层和C3模块(包括主干网络和颈部网络部分)的输出通道数(即特征图的卷积核数量)。在YOLOv5的模型构建过程中,width_multiple参数会与配置文件中的预设通道数相乘,然后通过make_divisible函数确保结果是8的倍数(为了GPU计算效率): 以YOLOv5s的第一个卷积层为例,在配置文件中的预设通道数:[64, 6, 2, 2](表示默认设置的输出通道数为64),YOLOv5s对应的width_multiple参数为0.50,在这种情况下,第一个卷积层实际输出的通道数为:64 × 0.50 = 32,这就意味着在YOLOv5s中,原本设计输出64个通道的卷积层,实际上只输出32个通道。 下图是YOLOv5各个不同规模模型的默认width_multiple参数: !image.png anchor 与YOLOv3和v4版本相同,YOLOv5版本同样通过Anchor锚框机制来预设的目标检测边界框尺寸,以帮助模型更准确地定位和识别目标。YOLOv5模型的主干网络输出大中小三种尺寸分辨率的特征图,每个特征图的每个网格会放三个不同尺寸和长宽比的anchor框: - 配置文件中的anchor配置信息总共三行,分别对应不同尺度的特征图:P3/8、P4/16、P5/32。 - 每行包含3组宽高值,每组代表一个anchor的[width, height]。 - 小特征图(P5/32)使用大anchor检测大目标,中特征图(P4/16)使用中等anchor检测中等目标,大特征图(P3/8)使用小anchor检测小目标 对比以上YOLOv5模型配置文件中提供的默认anchor参数,会发现其与YOLOv3-输出、Anchor与损失函数的进化总结中YOLOv3模型基于COCO数据集使用k-means机制统计出来的anchor框尺寸完全一致,也就是说,这个配置文件中所设置的anchor框的尺寸是基于COCO数据集的。 实际上在使用自定义数据集进行训练的时候,YOLOv5模型会首先使用一个Auto Learning Bounding Box Anchors的机制,检查自定义数据集的anchor先验框的尺寸与配置文件中提供的anchor参数的设置值(基于COCO数据集)是否基本匹配。如果匹配的话继续使用参数文件指定的默认anchor框进行训练,如果不匹配就会重新使用k-means方法计算这个自定义数据集自身的anchor,并在后续训练过程中使用自己计算的anchor。 与数据集配置选项的冲突 除了YOLOv5各个不同规模的模型配置文件之外,在调用train.py对模型进行自定义数据训练时,还需要设置自定义训练数据集的配置文件(数据集配置文件在data目录下)。而各个数据集的配置文件中也有nc或者anchors等配置选项,那么数据集的这些配置选项与模型的配置选项之间是什么关系呢? 答案是:YOLO训练脚本会优先采用数据配置文件中的配置选项。如果模型配置文件(如yolov5s.yaml)中的nc值与数据集配置文件中的nc选项不同,终端上会打印出警告信息:Overriding model.yaml nc=80 with nc=5。这表示程序正自动将模型输出层的类别数从默认值(例如COCO数据集的80类)覆盖成训练数据集的实际类别数(例如5类)。 在yolo.py源代码文件DetectionModel Class的实现中有以下代码,可以看到:在数据集配置文件中提供了nc和anchors参数的情况下,YOLO会使用数据集配置文件(如data/VOC.yaml)中的配置选项覆盖掉模型配置文件(如models/yolov5s.yaml)中的配置选项。 网络结构参数 YOLOv5的网络结构通过如下列表的形式对网络的每一层进行定义:[from, number, module, args]。以下对各个参数做详细解释和总结。 from用于指定当前层的输入来自哪一层: !b7e45d3d-2326-42fd-90b9-a1c3fbec9413.png 主干网络backbone的设置,每一层基本上都是从上一层接收数据,因此这里的from设置为-1就可以了。在head部分中定义的颈部网络结构,包含有很多类似 [-1, 6]这样的设置,这表示将上一层(通常是上采样后的结果)与骨干网络中的第6层(一个高分辨率的特征图)进行拼接,用于融合深层语义信息和浅层位置信息。 number参数定义了当前模块需要重复堆叠的次数。对于普通的卷积层Conv和SPPF这类不需要重复堆叠的结构,该参数设置为1。对于C3模块,该参数与前面详细解释的depth_multiple参数协同工作,最终的堆叠次数= number× depth_multiple (结果四舍五入取整,且至少为1)。 module参数指定了该网络层使用的网络模块。在YOLOv5网络结构的定义上用到的网络模块的类型如下图所示: !a84965e0-5355-429a-9b59-8c0836d384e2.png args通过列表的形式详细定义module所需要的模块参数。参数的具体含义取决于模块类型,其设置格式如下图所示。 !e57c69f2-d646-461b-b7b1-0a2fb3a08b22.png 在配置文件的网络结构参数部分,分别使用Backbone和Head两个段定义了主干网络和颈部网络的结构。 Backbone 主干网络结构的配置如下所示: 对比以上主干网络的配置,与主干网络的结构图就比较好理解了,整个网络结构由上到下: - 卷积块,6x6,stride=2,64通道 - 卷积块,3x3,stride=2,128通道 - C3块,128通道 - 卷积块,3x3,stride=2,256通道 - C3块,256通道 - 卷积块,3x3,stride=2,512通道 - C3块,512通道 - 卷积块,3x3,stride=2,1024通道 - C3块,1024通道 - 最后是一个SPPF模块,1024通道 !image.png Head Head部分实际上主要定义的是YOLOv5颈部网络的结构。以下为YOLOv5模型配置文件默认的head部分的参数: 看上去比backbone部分稍微难理解一点,但是仍然结合YOLOv5模型网络架构图中的Neck网络部分就比较清楚了: !image.png 对于上面配置文件中配置信息的解读,从架构图左下角第10层的卷积块开始,这个卷积层的from参数为-1,表示从主干网络最后的SPPF层接收输入在第10层(也就是卷积层)进行卷积处理,然后送到第11层上采样模块Upsample 2来进行上采样......以此类推,直到最后在第23层即最后一个CSPLayer上输出。 配置文件的最后一句[[17, 20, 23], 1, Detect, [nc, anchors]]`表示从17、20、23层这三层输出检测信息,用于最终在模型的检测头上输出最终的检测结果。 参考资料 - YOLOv5 原理和实现全解析 — MMYOLO 0.6.0 文档

YOLOv5模型网络架构解读

本文详细以YOLOv5的P3版本为基础,详细总结了该模块的整体网络架构、各个子模块的工作逻辑,可为后续更深入的学习YOLOv5模型打好基础。 注:本文所总结的YOLOv5版本为YOLOv5l,v6.0,P5版本。具体各个版本的差异可以参考YOLOv5模型的不同版本总结。 主干网络Backbone 在完整总结整个主干网络的结构之前,首先在下面以小节的形式整理主干网络中的各个子模块。 Focus与6x6卷积 YOLOv5的早期版本在主干网络的起始部分,使用了Focus结构(一种切片操作)来减少计算量并保留信息。该模块主要用于图像的下采样和特征提取,在减少空间分辨率的同时,保留了更多的原始信息,这个操作的本质就是将空间信息(宽和高)转换到通道维度。 Focus模块的操作分为三个步骤: - 切片操作(slice):将输入图像在宽度和高度方向上每隔一个像素进行采样,得到4个互补的子特征图。 - 通道拼接:将4个子特征图在通道维度拼接在一起,实际上就是通道数x4。 - 卷积操作:对拼接后的特征图进行一次标准的卷积。 以YOLOv5s的结构为例,原始的608x608x3的图像输入Focus模块,经过切片操作,变成304x304x12的特征图,然后再经过一次32个卷积核的1x1卷积操作,变成304x304x32的特征图输出。 切片操作的处理如下图所示:可以看到这个Slice操作跟YOLOv2模型(参考YOLOv2模型网络架构解读)中的Reorg操作完全相同。 !image.png Focus处理的流程如下图所示: !image.png 但在YOLOv5的后续版本(如v6.0之后)中,Focus结构被一个标准的、卷积核大小为6、步长为2的卷积层(Conv2d 6x6, stride=2)所取代。这也就是我们在YOLOv5不同版本的架构图上看到有些是Focus模块,有些是6x6卷积模块的原因所在。尽管Focus模块在理论上可以减少信息丢失,但在实际应用中,一个精心设计的卷积层(使用较大的6x6卷积核)也能有效捕获类似的空间信息,并且在GPU等硬件上,标准卷积通常能得到极佳的优化,实际的推理速度可能反而更有优势或相当。因此,在YOLOv5的新版本中,使用通用的卷积层替换掉特殊的Focus结构,使得整体网络结构可以更加统一和简洁,减少了自定义模块,降低了代码的复杂性和维护成本。 激活函数 在YOLOv5的主干网络中,普遍使用SiLU激活函数(也称为Swish函数)代替了之前版本常用的LeakyReLU。相比于LeakyReLU,SiLU激活函数在0点附近更为平滑,有助于提升梯度的流动性和模型的表达能力。 SiLU激活函数的表达式为: 其中的Sigmoid(x)就是标准的sigmoid函数,它的值在0-1之间。SiLU函数的特点是非线性,连续可导。 !image.png 下图是通过Netron查看YOLOv5的onnx文件,主干网络图像输入的前两个卷积块的结构。这个结构图中可以清楚的看到SiLU激活函数的计算流程,其计算可分解为两个清晰的步骤: - Sigmoid变换:首先,将卷积层的输出送入到Sigmoid函数中。Sigmoid函数会将其中的每个元素的值映射到 (0, 1)的区间内。 - 逐元素乘法:然后,将原始的输入与第一步得到的Sigmoid权重进行逐元素相乘。这一步就是下图中的 act/Mul 操作。其效果是:根据Sigmoid计算出的权重,对原始特征进行放大(权重接近1)、抑制(权重接近0)或部分保留(权重在0-1之间)。 !image.png 但是仍然需要注意的是,YOLOv5的不同版本中使用的激活函数是不同的,其早期版本(如v1.0-v3.0)默认配置的激活函数仍然是LeakyReLU,所以可以看到这些早期版本的主干网络中仍然采用了大量的CBL(Conv-BN-LeakyReLU)块;从大约v5.0版本开始,官方将默认的激活函数全面切换为SiLU,CBL块也随之变为Conv-BN-SiLU。 这就是网上看到的多数YOLOv5的模型架构图中仍然大量采用CBL块和LeakyReLU激活函数的原因所在。 !image.png CSPLayer 与YOLOv4相同,YOLOv5在主干网络的实现中也采用了CSP结构。也就是说,将特征图在通道维度上拆分为两部分,一部分进行复杂的卷积变换,另一部分直接抄近道走捷径,最后在输出之前将两部分结果合并。概念上与YOLOv4没有太大区别,与CSP处理的更详细解释可以参考YOLOv4模型网络架构解读. YOLOv5中的CSP块的结构如下图所示:可以看到,YOLOv5模型结构中使用的CSP块有两种类型: - CSP1用于主干网络,其中的卷积变化处理部分使用Resnet的残差块结构(如下图中的DarknetBottleneck add=true部分)。 - CSP2用于颈部网络,其中的卷积处理部分就只是两个连续的卷积块而已(如下图中的DarknetBottleneck add=false部分)。 !image.png SPPFBottleneck SPPF模块的全称是Spatial Pyramid Pooling-Fast(快速空间金字塔池化),本质上仍然是一个SPP模块,只不过是在计算速度和性能上进行了优化而已。 SPPF和SPP模块的核心目标都是要扩大特征信息的感受野,融合多尺度上下文信息。两者的区别只不过是:传统的SPP模块是在多个大池化核(如5x5, 9x9, 13x13)对输入的特征图并行处理并把处理结果拼接在一起,而SPPF模块则是使用同一个5x5池化核重复工作,多次池化,然后把不同阶段的池化结果拼接在一起。 - 有关SPP模块的工作逻辑,可以参考另外一篇笔记YOLOv4模型网络架构解读。 相比于前者,SPPF模块串联相同池化核的工作方式在硬件上更易优化,计算速度更快,可有效提升模型对不同尺度目标的适应性。在保持甚至提升模型性能的同时,实现更快的推理速度。 !image.png 至此,YOLOv5模型的主干网络部分就呼之欲出了:在输入图像为640x640分辨率的情况下,主干网络的输出仍然是以8倍,16倍,32倍的倍率分别输出三路分辨率和通道数不同的特征信息,由颈部网络对特征信息进行检测。 !image.png 总结起来,从上到下整个主干网络可以分为五个部分: - Stem Layer:也就是上面所介绍的Focus模块,只不过在较新的模型版本中,Focus模块已经变成了6x6,步长为2的普通卷积网络。在输入图像分辨率为640x640x3的情况下,该模块的输出为320x320x64维度的特征图。 - 从Layer 1到Layer 3都是3x3卷积+多个连续CSP块的结构,前面的步长为2的3x3卷积模块负责把输入特征图的分辨率减半,通道数加倍。 - Layer 1包含1个3x3卷积+连续的3个CSP块,其输出为160x160x128维度的特征图。 - Layer 2包含1个3x3卷积+连续的6个CSP块,其输出为80x80x256维度的特征图。该层输出作为大尺寸分辨率特征图送入颈部网络。 - Layer 3包含1个3x3卷积+连续的9个CSP块,其输出为40x40x512维度的特征图。该层输出作为中等尺寸分辨率特征图送入颈部网络。 - 最后的Layer 4,前半部分的处理仍然跟Layer1-3相同,包含有1个3x3卷积+连续3个CSP块,只不过在最后还额外增加了一个SPPFBottleneck模块进一步扩展输出特征图的感知野。该层的输出为20x20x1024维度的特征图。最终该层输出作为小尺寸分辨率特征图送入颈部网络。 颈部网络Neck 颈部网络部分是YOLOv5模型实现多尺度目标检测的核心组件,其工作目标是把主干网络提取的特征进行高效融合,使模型能够同时识别不同大小的目标。 整体来讲,YOLOv5和YOLOv4在颈部网络设计上的架构和思想没有太大变化,但其核心区别在于,YOLOv5在颈部网络中引入了上面主干网络部分所介绍的CSP2模块等方面的改进,显著加强了特征融合能力并提升了计算效率。 网络的整体架构上,YOLOv5的颈部网络仍然采用了与YOLOv4相同的FPN (特征金字塔网络) + PAN (路径聚合网络) 的双向结构。其中FPN负责自顶向下传递强语义特征,而PAN负责自底向上传递精确定位特征,形成强大的特征金字塔。 - FPN处理:在通道维度上通过Concat操作,将上采样(Upsample)后的深层特征图与对应的浅层特征图进行拼接,结合了浅层的细节信息和深层的语义信息。 - PAN处理:在PAN路径中,使用步长为2的卷积层,将融合后的特征图再次下采样(Downsample),以便与更高层级的特征进行进一步融合。 !image.png 检测头Head 检测头部分相比之前的版本仍然是没有太大差异,输出的三路特征图经过各自独立的1x1卷积处理,输出不同分辨率的检测结果张量。 !image.png YOLOv5模型的整体网络架构 最后再放一张YOLOv5模型的整体网络架构图,结合以上各个子模块的描述,理解整体的架构图就比较容易了: !image.png 参考资料 - 深入浅出Yolo系列之Yolov5核心基础知识完整讲解 - 知乎 - YOLOv5 詳細解讀. 前言 | by Steven Meng | Medium - YOLOv5 原理和实现全解析 — MMYOLO 0.6.0 文档

YOLOv5模型的不同版本总结

本文基于模型规模、算法迭代、输出结构等各方面的维度,对YOLOv5模型的不同版本及其相互之间的差异进行了总结。 YOLOv5模型是由Ultralytics公司的Glenn Jocher推出的开源模型,在Github上的地址为:GitHub - ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite。 模型规模 YOLOv5模型根据其参数量的多少可以分为nano(参数量1.9M)、small(参数量7.2M)、medium(参数量21.2M)、large(参数量46.5M)、X-large(参数量86.7M)五种类型,其参数量依次上升,效果自然也是越来越好。基本上如果是要部署在移动端或者嵌入式处理器的边缘计算节点,唯二可以选择的就是YOLOv5s和YOLOv5n。 - _模型的选择取决于对图像识别和检测的精度要求_:对于简单的物体存在性检测,YOLOv5s可能已足够。但对于需要区分细微差别或检测小物体的复杂场景(如医学影像)而言,则需要选择更大的模型,例如YOLOv5l或YOLOv5x。 !image.png 以上各个版本模型的差异,都源于YOLOv5源代码配置文件中两个核心参数的不同:depth_multiple(深度系数)和width_multiple(宽度系数)。这两个参数通过控制主干网络(Backbone)和颈部网络(Neck)中卷积层的通道数(即宽度)以及CSP块中残差层/卷积层的堆叠数量(即深度),来生成不同复杂度的YOLOv5模型(n, s, m, l, x)。 - width_multiple(宽度系数):这个系数主要用于控制网络特征图的通道数。系数越大,每一层卷积的通道数就越多,模型的宽度和参数容量就越大,学习能力更强,但计算量也随之增加。 - depth_multiple(深度系数):这个系数主要控制某些关键模块(如主干网络和颈部网络中的C3模块)的重复堆叠次数。系数越大,网络层数越多,模型的深度越深,就能提取更复杂的特征,当然也需要更多计算资源。 算法实现迭代 不同于YOLO模型的前几代,YOLOv5模型直接以一个基于Pytorch的软件项目推出,并且在2020年推出之后的几年中,该项目的具体实现不断更新和优化,从而形成了从v1.0到v7.0的算法实现迭代序列。 截止到目前为止,YOLOv5项目对应的几个软件版本、发布的时间线以及重点更新特性如下图所示。可以看到这些版本中,从模型结构角度上最大的更新是其中的v6.0版本。该版本中包含了:使用普通的步长为2的6x6卷积网络代替Focus模块,在主干网络末端输出的小分辨率特征图上使用SPPF替换了SPP,通过激活函数也是用SiLU替换到之前默认的LeakReLU。 !image.png 目前对于新的项目和实际部署,建议直接使用v6.0或v7.0等最新版本。这些版本不仅包含了所有重要的架构优化,通常还具有更好的稳定性和更完善的文档支持。 输出结构 除了以上在模型规模和软件项目迭代版本的差异之外,YOLOv5还提供了两种输出结构配置(P5和P6),用于进一步平衡检测精度、模型大小和推理速度,以适应不同的应用场景和硬件条件。P6版本可以在计算资源充足的情况下(如智能驾驶,医疗影像等领域),为大量微小目标的检测和识别提供极致的检测精度。当然,目前在产业领域中最常用的仍然是P5版本。 P5和P6版本的差异如下图所示: !image.png 下图是YOLOv5l P5输出结构的网络架构图: !image.png 下图是YOLOv5l P6输出结构的网络架构图: !fbd0e5d7-8c19-4e31-aba9-961e0077388a.png 从架构图上可以看到,P5版本的默认输入为640x640x3分辨率图像,输出为三路经过8倍、16倍、32倍降采样的特征信息;而P6版本的默认输入为1280x1280x3分辨率的更高清图像,输出为四路分别经过8倍、16倍、32倍、64倍降采样的特征信息。 版本命名规则与任务类型 基于以上总结的YOLOv5模型在不同维度上的版本差异,在实际的使用中,YOLOv5的模型文件名就包含了这些维度的信息,其命名遵循一个清晰的命名规则:yolov5{规模}{输出结构}-{任务}.pt。 例如: - yolov5s.pt:一个小规模(s)、P5输出结构、用于目标检测的模型。 - yolov5x6.pt:一个超大规模(x)、P6输出结构、用于目标检测的模型。 - yolov5m-seg.pt:一个中等规模(m)、P5输出结构、用于实例分割应用的模型。 以上提到的实例分割应用,是因为在YOLOv5的最新版本中,除了基本的目标检测功能以外,还通过不同的任务后缀支持图像分类(-cls) 和实例分割(-seg),这些变体通常基于P5结构开发。 参考资料 - YOLOv5 原理和实现全解析 — MMYOLO 0.6.0 文档

YOLOv4模型网络架构解读

YOLO模型的前三个版本v1(2015年6月),v2(2016年12月),v3(2018年4月)全都都是由Joseph Redmon提出和开发,但该作者因为YOLO模型不可避免地被用于军事领域,所以决定退出计算机视觉领域(为作者伟大的博爱精神点赞)。新的YOLOv4版本则于2020年4月由俄罗斯的Alexey Bochkovskiy以及中国台湾的两名研究员廖弘源和王建尧联合推出。 下图是YOLOv4与YOLOv3以及EfficientDet等竞品模型在COCO2017数据集上的对比测试结果:可以看到,YOLOv4模型在检测速度FPS大体一致的情况下,检测精度AP指标相比YOLOv3版本有了接近10%的提升。 !image.png YOLOv4官方源代码的仓库地址:GitHub - AlexeyAB/darknet: YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object Detection (Windows and Linux version of Darknet )。 同之前的YOLO版本相同,YOLOv4的网络架构也可以分为主干、颈部和检测头三个部分。 - 主干网络:YOLOv4的主干网络是CSP Darknet53,YOLOv3是Darknet53 - 颈部网络:YOLOv4的颈部网络是PANet,YOLOv3是FPN(特征金字塔) - 检测头:YOLOv4沿用YOLOv3的检测头YOLO HEAD 主干网络CSPDarknet-53 在主干网络的设计上,YOLOv4借鉴了Cross Stage Partial Network(CSPNet)的设计理念,为YOLOv4构建出一个全新的高效且强大的主干网络:CSPDarkNet-53。CSPDarkNet-53相比于YOLOv3的Darknet-53而言,结构上没有太大变化,主要的区别是使用CSP残差块代替了普通残差块,以及使用Mish激活函数替代了LeakyReLU激活函数。 CSP block 如YOLOv3模型网络架构解读一文对YOLOv3主干网络结构Darknet-53的学习和总结,Darknet-53网络结构的设计引入了当时在Resnet中流行起来的残差结构,所以可以把Darknet-53网络看成是连续很多个残差块的堆积。CSPDarkenet-53则借鉴CSPNet的概念,用CSP残差块代替了普通的残差块。 下图是CSPDarknet-53所使用的CSP残差块的结构: !image.png 从以上结构上可以看到,所谓的CSP残差块,实际上就是把输入的特征图分为两个部分C1和C2,C1不做任何处理,C2经过一个普通残差块的处理以后,与C1拼接聚合后输出。整个CSP残差块的输入和输出张量的维度完全相同,这一点跟Darknet-53中普通残差块的处理是相同的。 以上CSP结构的合理性在于:卷积神经网络的特征图中往往存在很多冗余信息,尤其是不同的通道之间的信息很有可能是相似的,那么在这种情况下,对这些特征信息的处理就没有处理全部的信息,只需要处理其中一部分,另外一部分保持不变即可,这样既可以保证不损失模型的性能,还能够从很大程度上降低模型的计算量和参数量。 具体到CSP残差块在CSPDartnet-53主干网络上的实现,可以参考下图:对于每个CSP残差块输入的特征图张量,先使用两个独立的1x1卷积块,得到两个通道数减半的特征图,其中一条不做任何处理,另外一条经过一个普通的残差块处理,然后把两部分在拼接合并在一起,最后再通过一个1x1卷积处理后作为输出。 !image.png 从以上的CSP残差块结构可以看到,这个结构比之前的普通残差块多增加了输入和输出环节的三个1x1卷积块。 Mish激活函数 如YOLOv2模型网络架构解读一文所总结的,YOLOv2在卷积层的实现上导入了BN层,因此其基本的卷积块就是CBL块:卷积层+BN层+LeakyReLU激活函数。这一设计在YOLOv3中也得以继续沿用。在YOLOv4的主干网络中,则使用Mish激活函数代替了在之前版本中普遍使用的LeakyReLU激活函数。 Mish激活函数的公式为: !image.png 该函数的曲线如下图所示: !image.png 与ReLU相比,Mish在负值区域不是直接置零,而是保留一个较小的负值,这使得网络能够学习到更丰富的特征。而与LeakyReLU相比,Mish最大的区别是更为平滑的梯度流,尤其是在0点附近。由于其函数曲线是光滑的,在梯度的反向传播时就能提供更稳定、更平滑的梯度。这有助于缓解ReLU在负区间梯度为零所导致的神经元死亡问题,让权重更新更加顺畅,从而加速收敛并提升模型的泛化能力。 当然与ReLU和LeakyReLU相比,Mish函数的计算量明显加大,但其在深度神经网络中的应用显示了比ReLU更好的结果,所以被YOLOv4的作者导入到主干网络中。 > 需要注意,YOLOv4只是在主干网络中使用了Mish激活函数,在后面的网络部分仍然采用LeakyReLU激活函数。 下图就是YOLOv4主干网络CSPDarknet-53的网络架构。可以看到,这个网络架构与Darknet-53完全相同,区别就只是把其中的RES块替换成CSP块,把LeakyReLU激活函数替换成Mish激活函数而已。CSP残差块的堆叠结构仍然是跟Darknet-53一样的分为五个部分,每个部分的CSP块的数量仍然是经典的1,2,8,8,4。按照这个堆叠,主干网络仍然向后续的颈部网络输出8倍、16倍以及32倍的特征信息。 !image.png 颈部网络:SPP+PAN YOLO模型中的颈部网络负责收集来自主干网络所输出的不同阶段、不同分辨率的特征图信息,经过融合处理以后,并将它们传递到头部进行目标位置检测和类别判断。 SPP SPP,Spatial Pyramid Pooling,空间金字塔池化,其核心思想在于:使用不同大小的池化核来捕捉不同尺度的特征。具体实现中,就是使用不同尺寸的最大池化层(在YOLOv4中通常是5x5, 9x9, 13x13)对输入的特征图同时进行处理,提取多个尺度的特征,最终这些不同尺度的特征在通道维度上融合,就形成一个融合了多尺度信息的丰富特征。如下图所示: !image.png 在YOLOv4的网络结构中,主干网络所输出的特征信息,再经过这个SPP模块的并行多尺度池化处理,在不牺牲速度的前提下,有效扩大了模型的感受野并融合了丰富的上下文信息,从而显著提升了模型的特征表达能力和最终的目标检测性能,特别适用于大尺寸目标和复杂场景。 PANet 颈部网络的另外一个改进是采用了路径汇聚网络(Path Aggregation Network,PANet)。 在YOLOv3版本的颈部网络中,其采用的传统特征金字塔结构(FPN),仅包含自顶向下(top-down)的特征融合结构,也就是说,只会把主干网络尾端输出的低分辨率深层特征图向中段输出的高分辨率浅层特征图部分融合,这样的情况下,浅层输出的高分辨率特征图中就包含了深层的特征信息,但网络深层输出的低分辨率特征图并不包含浅层的特征信息。 YOLOv4的颈部网络引入的PANet,在FPN的结构的基础上,增加了一条自底向上(bottom-up)的特征融合路径,这样的结构就可以让不同尺度的特征信息得到更加充分的融合,在所有的输出上都同时包含了深层和浅层的特征信息。 !image.png 检测头 YOLOv4的检测头与YOLOv3相同,都是在三路输出的最后使用一个1x1卷积层同时完成坐标、置信度和类别等信息的三部分预测。 YOLOv4的完整网络结构 基于以上对于网络三个部分的详细总结,理解如下所示的YOLOv4整体网络架构就比较简单了: !image.png 参考资料 - YOLO演進 — 3 — YOLOv4詳細介紹. 之前有介紹過 YOLOv1… | by 李謦伊 | 謦伊的閱讀筆記 | Medium - YOLOv4 中的 Mish 激活函数-腾讯云开发者社区-腾讯云 - YOLO目标检测》杨建华,李瑞峰 第8章 YOLOv4

YOLOv3-输出、Anchor与损失函数的进化总结

本文对YOLOv3模型在输出数据的组织格式、Anchor预测框以及损失函数的计算等方面的内容进行了总结。 YOLOv3模型的输出 如下图所示的YOLOv3模型的整体架构图。不同于之前的YOLOv1和YOLOv2模型仅输出一组三维张量,然后在这一组三维张量上学习和解释待检物体的位置类别等信息,YOLOv3模型最终的输出是三组独立的三维张量,分别用于对不同尺寸的待检物体进行定位和检测。 !image.png 以上图输出的13x13x255的这一组三维张量为例,输入图像被分割为13x13个网格,每个网格会预置三个大小与尺寸比例不同的预测Anchor框(关于YOLOv3 Anchor框的信息参考下一节),然后通过一个长度为255的一维向量对该网格中检测到的物体信息(该物体的中心位于该网格)进行描述。也就是说,这个255长度的向量中包含了3个预测框的信息,其解释如下图所示。 !image.png 每个预测框中所包含的信息与YOLOv2的单个预测框的输出没有区别,都是中心坐标位置+长宽+预测置信度+80个类别(针对COCO数据集)的识别信息。这样的话每个预测框的信息长度就是4+1+80=85,一个网格中包含的3个预测框的信息长度就是255。这就是13x13x255这个三维张量的维度解释和由来。 > 如果每个网格放置B个Anchor Box,模型要支持的待检物体的类别数量为C,那么每个网格输出的数据长度就是B x (4+1+C)。 YOLOv3的Anchor框 在网格的Anchor框设计方面,YOLOv3仍然沿用了在YOLOv2模型上导入的先验Anchor框。只不过,在YOLOv2模型中,每个网格会提前放置5个Anchor框,到了YOLOv3模型中,则是给每个网格放置3个Anchor框(这一点在前一节解释YOLOv3模型的张量输出时已有解释)。 至于Anchor先验框的尺寸选择,YOLOv3仍然采用YOLOv2一样的做法,在训练数据集上使用 k 均值聚类方法来得到与这个数据集相匹配的9个先验框的尺寸。 - 关于如何使用 k 均值聚类法得到数据集的先验框,可以参考YOLOv2的Anchor锚框机制详解一文,对于如何从数据集得到Anchor框的长宽比的计算流程,已经有非常详细的描述。 在YOLOv3的论文中,作者基于COCO数据集,计算出来了9个Anchor先验框的尺寸。并且面积大小排序,均匀分为3份,分别针对不同分辨率的特征图: - 大框3个,针对13×13分辨率的特征图,共有13x13x3=507个预测框,负责大目标的检测:(116×90), (156×198), (373×326) - 中框3个,针对26×26分辨率的特征图,共有19x19x3=2028个预测框,负责中目标的检测:(30×61), (62×45), (59×119) - 小框3个,针对52×52分辨率的特征图,共有52x52x3=8112个预测框,负责小目标的检测:(10×13), (16×30), (33×23) !image.png YOLOv3的损失函数优化 如以上所总结的,YOLOv3模型会输出三组独立的三维张量,用于对不同大小的待检物体进行检测。那么其损失函数也就需要针对这三组输出独立进行。三个输出计算损失值的损失函数是完全相同的,而且各个输出对于损失值的计算流程完全独立进行互不干扰,最终得到三个损失值Loss_13,Loss_26,Loss_52,整个模型总的损失值就是这三个损失值之和。 而无论是以上输出的哪个尺度,YOLOv3的损失函数都由以下三个部分构成: - 边界框回归损失(lbox):负责评估预测框位置(中心坐标x, y)和大小(宽高w, h)的准确性。 - 置信度损失(lobj):负责判断预测框内是否包含目标物体。 - 分类损失(lcls):负责判断预测框内物体的具体类别。 因此整个模型总体的损失函数的计算流程如下图所示: !image.png 以下以13x13尺度特征图为例来解释损失函数各个部分的计算流程。 首先进行YOLOv3训练过程中样本类型的定义解释。总体来讲,YOLOv3在训练过程中进行损失函数计算时,把样本分为三种类型:正样本,负样本和忽略样本。不同类型的样本会被用于或者不用于计算不同类型的损失值。如前所述,对于13x13分辨率的模型输出而言,实际上总共会输出13x13x3=507个预测框。而对于正负以及忽略样本的判定,实际上也就是对这507个预测框进行判断和分类,然后基于分类结果决定后续是否用于不同损失值的计算。 - 正样本:对于每个训练样本标记的真值框而言,首先会计算真值框的中心位于哪个网格,然后在这个网格输出的三个预测框中,再找出来与真值框IOU最大的那个预测框作为正样本。所以每个真值框对应于一个正样本。正样本会用于所有的损失计算类型(边界框、置信度和分类)。 - 忽略样本:与任何一个真值框的IOU大于指定的阈值(如0.5),但是又不是该真值框的正样本。忽略样本不会参与任何损失计算,因此对于最终的损失值没有任何影响。 - 负样本:与所有的真值框的IOU都小于指定的阈值(如0.5),实际上也就是除了正样本和忽略样本的其他所有样本。负样本会被用于置信度损失,但是不会参与边界框和分类损失计算。 与之前的YOLO版本相同,YOLOv3的损失函数同样包含三个部分:坐标损失,置信度损失以及类别损失。 对于坐标损失的计算而言,因为只有正样本会参与坐标损失的计算,所以实际上坐标损失值就是所有正样本坐标与真值框坐标之间的损失值。因为已经有了所有真值框及其对应的正样本的中心位置坐标和长宽,那么计算坐标损失就比较简单了: !image.png 接下来是置信度损失的计算。在YOLOv3中,使用了二元交叉熵BCE来作为置信度损失的计算函数。模型为每个预测框输出一个置信度 logit 值。这个值需要通过 Sigmoid 函数激活,将其映射到 (0, 1) 区间,才能作为表示含有目标概率的置信度分数 \hat C。然后基于以下公式计算单个预测框的置信度损失,最后把所有预测框的置信度损失加起来就是总的置信度损失: 如前所述,在置信度损失计算时,需要考虑正样本和负样本,不需要考虑忽略样本。 - 对于正样本(C=1)而言,置信度损失可简化为。这意味着模型预测的 越接近1,损失越小。 - 对于负样本(C=0)而言,其损失可简化为 。这意味着模型预测的  越接近0,损失越小。 最后是类别损失的计算。类别损失的计算逻辑与置信度损失基本相同,都是采用二元交叉熵BCE来进行其损失值的计算。不过,对于类别损失的计算而言,只有正样本需要参与进来。同样的,模型会为每个预测框输出一个包含所有类别信息的原始logit向量。这个向量的长度等于数据集的类别数量(例如,COCO数据集是80类)。这些原始 logit 值需要通过Sigmoid 函数进行激活,将其映射到 (0, 1) 区间,作为类别损失计算的参数 。 - 需要注意的是,YOLOv3对于类别的判断可以允许一个框同时属于多个类别(如人+男人两个标签)。 然后使用以下公式对该正样本在所有的类别上计算类别损失,然后把所有正样本的类别损失加起来就是总的类别损失。 参考资料 - 【YOLO系列】YOLOv3论文超详细解读(翻译 +学习笔记)-阿里云开发者社区

YOLOv3模型网络架构解读

本文详细总结了YOLOv3模型的整体架构方面的内容,包括其主干网络Darknet-53、特征金字塔的概念以及多级特征图检测头的设计思路和实现理论。 全新主干网络DarkNet-53 从YOLOv2开始,YOLO模型的作者为模型设计了专门的主干网络Darknet。在YOLOv2版本中所使用的主干网络是DarkNet-19(在YOLOv2模型网络架构解读一文可参考YOLO2模块的网络架构设计),即该网络结构中总共包含了19个卷积层。到了YOLOv3阶段,DarkNet的结构设计借鉴了当时已成业界主流设计理念的Resnet的残差网络结构,退出DarkNet-53并将其作为YOLOv3的主干网络。 - 关于Resnet的残差网络结构可以参考CNN经典网络模型架构学习之Resnet一文。 在DarkNet-53的设计上,其卷积层的实现仍然采用了与Darknet-19一样的卷积层+BN层+LeakyReLU激活函数的CBL组合结构。Darknet-53网络包含有53个卷积层并由此得名。 !image.png 在实现特征图空降降采样操作时,不同于DarkNet-19所采用的最大池化层,DarkNet-53普遍采用了步长为2(stride=2)的3x3卷积层来实现该功能。这种方式能够在减少特征图尺寸的同时,能够保留更多的空间信息。因此,在整个Darknet-53的网络结构中不包含有在传统卷积网络中非常常用的MaxPool层,所有的特征图空间降采样操作都适应步长为2的 3×3 卷积来完成。 此外就是以上提到的Darknet-53的网络设计上,借鉴了ResNet的残差块的设计理念。Darknet-53网络所使用的残差块结构如下图所示。残差块的输出和输出在特征图的尺寸和通道数量上完全相同,残差块内部的实现就是连续的1x1卷积块与3x3卷积块的堆叠,然后其输出再与输入相加实现残差连接。 !image.png 与Resnet类似,DarkNet-53网络的大部分实现实际上就是残差块的堆叠。下图是YOLOv2主干网络Darknet19与YOLOv3主干网络Darknet53的对比。可以看到,Darknet19中的降采样操作全部使用传统的最大池化层,而Darknet53的降采样操作则全部使用了步进为2的3x3卷积层。 !image.png Darknet-53的网络结构就是反复堆叠以上所描述的残差块。从整体结构上,Darkent-53依据于各层的特征图尺寸,可以分为5个部分(如上图右侧圈起来的5个部分)。从第一个到第五个部分各自所包含的残差块数量分别为1,2,8,8,4。各个部分的输入和输出特征图的尺寸与通道数量均保持一致,然后各部分之间使用一个步进为2的3x3卷积层来实现特征图尺寸降维的操作。 这种“12884”的堆叠数量配置也成为后续诸多YOLO框架的设计范式之一。 Darknet-53本质上仍然是一个对图像特征提取以及进行类别分类的网络,所以在最后仍然通过全局平均池化+全连接层配合Softmax来实现对ImageNet数据集的识别分类输出。当然,作为YOLOv3模型的主干网络使用时,全局平均池化及其后的部分会被拿掉,只保留前面的特征提取部分输出不同阶段的特征图进行目标检测。 以下是Darknet-53网络与Darknet-19、Resnet-101、Resnet-152等网络架构在识别率、运行速度等指标方面的比较。可以看到,尽管Darknet-53的速度相比Darknet-19下降不少,但是也能够达到实时的水准,而且识别正确率有了比较明显的提升。 !image.png 特征金字塔与特征融合 所谓的特征金字塔(FPN,Feature Pyramid Network),来源于另外一种目标检测网络结构SSD,其设计背后的理念在于:随着神经网络层数的加深以及降采样操作的增多,其各层输出的特征图中包含有不同的信息,浅层特征图的分辨率更高,所以包含有更多的位置信息,同时因为降采样操作比较少而导致感受野较小,对于目标类别的识别而言也就更困难;而深层特征图的分辨率更低,目标的位置和细节等信息在逐级降采样过程中丢失了,但是因为感受野相对更大,对于大目标的识别和检测性能就更好。因此,针对以上存在的矛盾,解决方案也就应运而生:综合使用浅层特征和深层特征,其中浅层特征负责检测较小的目标,深层特征负责检测较大的目标。 - 特征图分辨率越大,位置信息的细节越丰富,但是因为感受野过小,对于目标类别的判断越困难。 - 特征图分辨率越小,在多级降采样的过程中,目标位置信息的损失越多,但感受野逐步增大,更容易判断待检物体(尤其是大尺寸物体)的类别。 下图是特征金字塔的结构:左侧的特征提取网络输出三种分辨率和维度的特征数据,在右侧的检测网络中分别实现对大中小三种尺寸的目标进行检测和识别。 !image.png 对于大尺寸的目标进行检测,只需要用到最小分辨率的特征信息(上图最上层的预测输出)就足够了。因为对于较大尺寸的物体而言,尽管小分辨率特征图的位置信息损失比较多,但是对于定位大尺寸物体而言足够了,而且小分辨率特征图的感受野更大,分辨物体的类别更容易,所以不需要依赖于大分辨率的特征信息。 而对于较小尺寸的目标进行检测,则不仅需要用到特征提取网络中输出的较大分辨率的特征信息(利用其中包含的丰富位置信息对小分辨率问题进行准确定位),还需要用到较小分辨率的特征信息(用于辅助判断定位物体的类别)。所以就需要把小分辨率的特征信息和大分辨率的特征信息融合在一起,来实现对中小尺寸目标进行准确的定位和类别判断。 那么接下来的问题就是,不同分辨率的特征图如何进行融合处理呢? 答案是,要对深层次网络输出的小分辨率特征图,执行空间上采样(Upsample,也就是上图中的2x up)操作,把其分辨率大小提升到与浅层分辨率相同的配置,然后再与浅层分辨率特征图进行融合。这个过程主要涉及到两个操作:空间上采样(Upsample)和特征图融合(Concat)。 以下以YOLOv3模型13x13分辨率特征图与26x26分辨率特征图的融合来解释Upsample操作和Concat操作。 - Upsample操作:基于13x13分辨率的特征图,使用_近邻插值_的方法,执行2x2插值处理,每一张13x13分辨率特征图都通过这样的插值方法,上采样为26x26分辨率的特征图。 - Concat操作:以上13x13分辨率的特征图,通过Upsample操作以后插值为16x16分辨率。这样就有了两组16x16分辨率的特征图,只需要把两组特征图在通道轴上_首尾相接拼接_在一起就可以了。 不同分辨率的特征图所进行的融合处理以及输出流程如下图所示: !image.png YOLOv3的架构图 基于以上对主干网络Darknet-53以及特征金字塔多路特征图输出结构的理解,再来学习YOLOv3的网络结构就比较容易了: !image.png 上图的左上角是YOLOv3的主干网络部分Darknet53,去掉了全局平均池化和用于分类的全连接层。在主干网络的第三部分残差块、第四部分残差块、第五部分残差块的输出上分别输出8倍、16倍、32倍降采样的特征图。 - 对于输入图像分辨率为608x608的情况下,以上三部分所输出的特征图分辨率也就是76x76,38x38以及19x19。 - 而对于当时在目标检测场景中更常见的416x416输入图像分辨率而言,以上三路特征图分辨率则为52x52,26x26,13x13。 上图的右半部分则是基于特征金字塔理论的特征检测部分,以三路不同分辨率的特征图为输入,在不同曾经对其进行上采样和Concat操作,最终输出不同疏密度的检测网络:越密的网格越适合检测小而密集的物体,而越疏的网格越适合检测大而稀疏的物体。 参考资料 - 《YOLO目标检测》杨建华,李瑞峰 第7章 YOLOv3 - YOLOv3 詳細解讀. YOLOv3… | by Steven Meng | Medium - 《深度学习之PyTorch物体检测实战》 董洪义 6.3 多尺度与特征融合 YOLOv3 - YOLOv3 — You Only Look Once (Object Detection) | by Moris | Computer Vision Note | Medium

YOLOv2特性总结-从YOLOv1的进化

本文对YOLOv2在YOLOv1基础上所进化的特性进行了完整的总结,解释模型在进化中各项指标得以提升的特性和工作逻辑。 YOLOv2模型在YOLOv2的设计基础上进行了多项模型设计、训练等方面的调整和优化,在VOC2007数据集上的mAP指标由63.4%大幅度提升到了78.6%,同时保持了YOLOv1 one-stage进行目标检测+类别识别的设计,推理速度依然很快,并且能够识别更多的物体类别。 - 关于mAP指标的概念和计算逻辑,可以参考目标检测模型的评价指标详解:mAP,召回率,准确率 下图中总结了YOLOv2在YOLOv1基础上的所有改进项的列表,本文对其中的每一项展开描述: !image.png Batch Norm YOLOv2为模型中的每一个卷积层之后都增加了批归一化的处理,所以从之前的卷积+LeakyReLU激活函数的形式,变成了卷积+批归一化+LeakyReLU激活函数的CBL块。 具体的细节在YOLOv2模型网络架构解读一文中有详细描述。 !image.png 在加入了BN层之后,YOLOv1的性能得到了第一次提升。在VOC2007测试集上,其mAP性能从原本的63.4%提升到65.8%。 Hi-res Classifier 因为ImageNet数据集有着海量的图像数据标注信息,所以大多数目标检测算法,都会首先基于ImageNet数据集对模型的主干网络部分进行预训练,把这个预训练出来的参数作为主干网络的初始参数,即所谓的Image Pretrained技术。 但是基于以上技术所进行的预训练过程,主干网络进行预训练的过程中,所接受的图像尺寸是224x224,这个小的分辨率,对于目标检测应用而言,很容易丢失一些小物体的细节,给后续的检测环节带来困难。所以在YOLOv2的设计上,采取了先使用ImageNet数据集在224x224分辨率上进行预训练,预训练完成后还要增加一个模型的微调(fine tune)阶段,在预训练的参数上,对网络使用448x448分辨率继续训练,使之适应更高分辨率的图像。 具体而言,YOLOv2模型预训练的流程是: - 先使用224x224分辨率在ImageNet数据集上从头开始训练网络,大概训练160个epoch。 - 然后把模型的输入调整为448x448,继续在ImageNet数据集上,进行10个Epoch的训练就好了。 在这样的策略之下,YOLOv1的性能又一次得到了提升:mAP从65.8%提升到69.5%。 > 为什么不直接从448x448分辨率从头进行网络的训练,而要先使用224x224的小分辨率进行预训练,然后再使用448x448的大分辨率进行微调?一方面是因为从224x224到448x448,分辨率增大四倍,在当时算力的情况下模型的训练时间会拉长很多,另外一方面则是因为224×224分辨率是当时分类网络的标准输入,有大量预训练权重与超参数经验可供参考,因此先沿用成熟配置做热身,再小幅微调,在工程上最稳妥。 全卷积神经网络和Anchor先验框机制 在网络结构上的设计上,YOLOv2相比YOLOv1最大的变化,就是在网络的最后阶段去掉了全连接层,将其改成全卷积网络结构。具体的网络结构可参考YOLOv2模型网络架构解读. 此外,YOLOv2借鉴了Faster R-CNN网络的思路,引进了先验框Anchor的机制,在图片的推理阶段,模型就只需要学习能够将先验框映射到待检物体目标框的尺寸的偏移量,无须再学习整个目标框的完整尺寸信息,这使得训练变得更加容易。YOLOv2导入的Anchor先验框的机制详细总结可参考YOLOv2的Anchor锚框机制详解。 基于模型网络结构上的优化以及Anchor框的引入,最终由主干网络输出的特征图的尺寸由YOLOv1提升为YOLOv2的13x13,每个网格提前放置 K 个Anchor框(在YOLOv2模型中默认的Anchor框数量是5个),这样也就意味着整幅图可以输出13x13xK=169K个Bounding Box,并且每个Bounding Box的预测信息都是完全独立的,各个Bounding Box都有自己的中心点x和y坐标,Box的长宽w和h,预测置信度confidence以及各个类别的识别概率(在YOLOv1模型中一个网络的两个Bounding Box共享同一个类别识别概率,因此实际上每个Bounding Box只能检测一个物体)。所以YOLOv2同一网格输出的各个Bounding Box的相互独立的,这样就可以解决两个物体的中心在同一个网格中会存在的漏检问题。YOLOv1和YOLOv2模型的输出信息差异如下图所示: !image.png Multi-Scale training 经过对网络结构的改造,去掉全连接层,YOLOv2采取了全卷积网络的结构,网络中只有卷积层和最大池化层,这样的情况下,整个网络的输入分辨率就可以不再局限于只使用416x416的图片,可以使用各种不同分辨率的图像尺寸,因此,在训练过程中,也就可以不断更改输入图像的尺寸,使之适应不同分辨率的图像以及待检物体的大小。 YOLOv2模型主干网络即图像特征提取部分的的下采样步长为32倍,即由图像特征提取部分提取出来的特征图尺寸是输入图像分辨率的1/32,因此输入图像的分辨率需要是32的倍数(最小尺寸为320x320,最大尺寸为608x608)。因此在YOLOv2模型的训练过程中,每执行10个epoch迭代,模型就从320、352、384、416、448、480、512、544、576、608中依次选择一个新的图像尺寸,用作后续10个epoch训练的图像尺寸。如下图所示: !image.png 实践证明,这个Multi Scale Training的训练策略,可以使得同一个网络能够对不同分辨率的图片都可以更高的进行检测,从而提升了网络的检测性能。 参考资料 - YOLOv2 詳細解讀. YOLOv2 是在 YOLOv1 的基礎上使用了上圖紅色框框裡的方法改進,在… | by Steven Meng | Medium - 《YOLO目标检测》杨建华,李瑞峰 第6章 YOLOv2

YOLOv2的Anchor锚框机制详解

本文总结了YOLOv2模型基于Faster R-CNN的理论,在其模型网格预测框设计上增加的Anchor机制的来源、在YOLOv2模型中的计算和训练流程,尝试更清楚的认识YOLOv2模型的工作原理。 Anchor锚框的概念 首先给出Anchor框的定义:所谓的Anchor就是在每一个图像分割网格中,在训练之前提前固定放置一些尺寸大小和长宽比例不同的边界框,通常情况下,对于每一个网格而言,提前放置的边界框的大小和数量都是相同的。在推理的时候,模型基于输入图像中待检物体的情况,对每一个提前放置的边界框预测出若干的偏移量(中心位置的偏移量,宽高的偏移量等),然后基于这些偏移量调整之前放置的边界框,得出最终的预测边界框。因此,在每个网格处提前放置的边界框就是anchor框,其本质就是提供边界框的尺寸预计,然后用模型在推理过程中计算出来的偏移量来调整其位置和尺寸,从而得到最终的边界框尺寸。 从下图中可以直接的看出所谓的Anchor框究竟是什么:下图中把整幅图像分成与YOLOv1一样的7x7网格,然后在每个网格上提前放置了三个大小和比例不同的anchor框。为了方便演示,下图中仅对每六个网格放一组anchor框,实际中应该是每个网格都有一组anchor框,全画出来密密麻麻,演示反而不够直观。 !image.png 从Anchor框这个概念来讲,YOLOv1版本并没有提前放置anchor框的设计,而只是为每个网格定义了两个BOX,这两个BOX的中心位置和尺寸大小都是需要模型在基于输入图像推理的过程在预测出来的。所以从这个角度上讲,YOLOv1就是典型的Anchor-free(不依赖于Anchor)的模型。 每个网格提前预设Anchor框的设计,首先来源于另外一种目标检测模型:Faster R-CNN。 Faster R-CNN是一个两阶段的目标检测模型,在第一个阶段定位物体,第二个阶段识别物体的类别。其对物体定位检测的思路与YOLOv1是类似的,也是把输入图像分为多个网格,然后逐网格找物体,只不过Faster R-CNN的第一阶段只查找哪个网格中有物体,不关注该物体的类别,在第二个阶段再去识别检测到的物体的具体类别,而YOLOv1则是同时定位物体和识别物体的类别。这就是Faster R-CNN与YOLO各版本的目标查找和预测流程的最大差异所在。 对于Faster R-CNN模型而言,首先会把输入分割为多个网格,然后为每个网格预置 k 个不同尺寸和不同宽高比的ancher框(如上图所示)。在YOLOv2版本中,也把这个预置Anchor框的机制导入了进来,显著的提升了模型的mAP指标。增加Anchor框以后,在推理阶段,模型就不需要从头直接预测待检物体的尺寸与坐标,而只需要预测Anchor框到真实物体的偏移量即可,这样就降低了预测的难度。 YOLOv2锚框的k均值聚类算法 但问题是,如果确定每个网格应该放几个Anchor框,以及各个Anchor框的大小和长宽比又如何确定呢? 对于Faster R-CNN模型而言,其Anchor框的数量、尺寸等参数都是作为超参数由人工设定的。这种设计很难确保人工指定的Anchor框的参数与数据集之间是相互匹配的,所以对于不同数据集的输入,其网络性能的表现很难稳定。 对于以上问题,YOLOv2模型的做法,是采用 k 均值聚类算法,从指定的数据集中提取出来最适合的Anchor框。 首先把整个训练集中图片的所有真值标记框(GT,Ground Truth)全部提取出来,然后缩放到统一的输入尺寸(416x416):该算法只关心真值框的width和height信息,也就是长宽比。 !image.png 传统的K-means聚类算法在进行聚类计算的过程中使用的是欧氏距离,但是这样也就意味着较大尺寸的Anchor框相比较小尺寸会产生更多的错误,聚类计算的结果也就会发生偏离。而YOLOv2聚类计算的目的,是让预先选定的Anchor框的尺寸与其接近的真值框有更大的IoU,与Anchor框和真值框的尺寸大小无关。因此YOLOv2模型定义了以下距离公式执行聚类算法: !image.png 从以上统计出来的所有真值框列表中随机挑选出来5个做为初始Anchor框,执行以下所描述的k均值聚类算法: - 基于以上所描述的聚类算法,计算数据集中所有真值框与5个Anchor框之间的dist值,每个真值框与哪个Anchor框之间的dist值最大,就把这个真值框与该Anchor框归为一类。直到所有的真值框按照以上逻辑计算完毕,被分为以Anchor框为标准的5个集合。 - 对每个集合中所包含的真值框列表进行以下计算: - 取出所有真值框的width(之前已经统一缩放真值框的分辨率为416x416),计算出来所有width的几何平均值,把该组合Anchor框的width替换为计算出来的这个几何平均值。 - 同样的,取出所有真值框的height,计算出来所有height的几何平均值,把该组合Anchor框的height替换为计算出来的这个几何平均值。 - 经过以上运算以后,就得到5个新的Anchor框的(width,height)。 - 然后基于这5个新的Anchor框重新从头执行这个聚类算法,得到新的Anchor框的(width,height)。经过以上的反复迭代计算,最终新Anchor框的width和height基本不再变化,这就是最终由算法自动基于数据集所自动生成的Anchor框。 以上的整个k均值聚类算法的执行流程可以简化为下图所示: !image.png 基于以上逻辑和流程计算出来5个Anchor框的长宽参数以后,就可以按照其面积大小为顺序,写入YOLOv2模型的cfg文件中,后续在模型的训练和推理过程中,每个网格都会放置5组以上长宽比例的Anchor框用于对图像的中物体进行检测。 > 不过从以上计算逻辑也可以看出来这个算法存在的严重局限性:通过以上算法选出来的Anchor框是基于指定的数据集的,那么也就只能在这个数据集上有良好的表现。如果新的数据集的待检对象尺寸等与训练数据集存在较大差异化的,初选的anchor框就不合适了。 基于Anchor的Bounding Box预测 经过如上所述的Anchor框的聚类计算以后,在模型开始正式训练之前,就得到了5组长宽比确定下来的Anchor框。在训练过程中有了这个Anchor边界框的先验尺寸信息,网络就不必再从头去学习整个目标框的宽高了。 假设预定义Anchor框的尺寸为 p_w 和 p_h , 待检物体中心所在网格的左上角坐标位置为 grid_x,grid_y ,网络推理输出的中心偏移量是 (t_x,t_y) ,网络输出宽高的偏移量是 (t_w,t_h) 。那么YOLOv2模型使用以下公式计算待检物体的中心坐标和边界框: !image.png YOLOv2模型预测输出的中心偏移量 (t_x,t_y) ,是其相对所在网格左上角位置的相对偏移量。而且在计算实际中心位置时,还会使用Sigmoid函数限制叠加到 grid_x,grid_y 之上的偏移量在0-1之间,这样就可以使得预测出来的bounding box的中心点保持在该网格内部,不会偏移很多超出到网格之外。 可基于下图来理解上面的公式:虚线框为Anchor框,蓝色框为模型预测输出计算出来的Bounding box。 !image.png 参考资料 - 《YOLO目标检测》杨建华,李瑞峰 第6章 YOLOv2 - 《深度学习之Pytorch物体检测实战》 董洪义 6.2 依赖锚框:YOLOv2 - YOLOv2 詳細解讀. YOLOv2 是在 YOLOv1 的基礎上使用了上圖紅色框框裡的方法改進,在… | by Steven Meng | Medium

YOLOv1模型的损失函数与训练过程解读

本文对YOLOv1模型的训练正样本标记方法、损失函数以及基于训练样本对模型进行训练的完整过程进行了总结,为理解YOLO后续版本的处理流程打好基础。 YOLOv1训练正样本的标记 如YOLOv1输出信息以及NMS过滤算法详细解读一文所总结的,YOLOv1对于图像中物体的检测逻辑是,如果一个物体的中心点落在某个网格内,就由这个网格负责检测该物体,这样也就意味着,在制作YOLOv1训练样本的时候,只有包含待检物体中心点的网格被认为是有物体的。 以下图进行解释: !image.png 尽管图中的小猫这个物体的标记检测框占据的多达24个网格,但是因为这个标记框的中心位置落在粉色网格中,所以,对于YOLOv1模型在制作这个图像的训练样本时,只有粉色网格会被标记为有物体,该网格的检测置信度接近1;其他网格均为无物体,网格的检测置信度接近0。 > 有物体的网格会被标记为正样本候选区域,也就是说,在训练过程中,训练的正样本(positive sample)只会从此网格处的预测中得到,而其他区域的预测都是该目标的负样本。 YOLOv1的损失函数解读 从总体上看,YOLOv1的损失函数的计算主要包含三个部分: - 坐标损失(x,y,w,h):评估模型输出的检测框与训练样本标记框的贴合程度。 - 置信度损失(confidence):衡量模型输出的每个预测框的confidence值与该框中是否真正包含待检目标之间的吻合程度。 - 类别判断损失(class):判断模型输出的类别判断是否与训练样本的标记分类一致。 下图是损失函数计算过程中用到的各个参数的含义: !image.png 下图是YOLOv1损失函数的计算公式: !image.png YOLOv1损失函数的前两行表示预测边界框坐标位置参数的损失,第三和第四行表示正样本和负样本边界框置信度的损失,最后一行则是正样本处的类别损失。 以上损失函数的计算逻辑看上去很复杂,但是实际的计算逻辑还是比较直观的,以下分为3个部分分别详细解释。 坐标损失 坐标损失的计算,关键是要理解 的含义: - 对于训练样本的每一个标记框(也就是所谓的ground truth),先找到这个标记框中心所在的网格。然后计算这个网格输出的两个预测框与该标记框的IOU,把IOU值较大的那个预测框的 值设置为1,另外一个预测框的 值为0。 - 在这种情况下,实际上对于训练样本的每一个标记框,实际上都只会找到一个值为1的预测框。 以上逻辑就是找到训练样本每一个标记框所对应的负责预测框。然后就是按照对应的公式,分别计算标记框和负责预测框中心坐标以及长宽的损失值,两个损失值的和就是总的坐标损失值。 从以上的损失函数的计算公式可以看到,坐标损失值还需要乘以 的系数,这是为了加大预测框定位精度损失在整个损失值中的权重,优先更精确的对预测框定位。 置信度损失 置信度损失的计算包含两个部分:负责预测框的损失,不负责预测框的损失。 负责预测框的损失:对于训练样本的每一个标记框(ground truth),仍然是按照与以上计算坐标损失相同的步骤,找到这个标记框对应的负责预测框。每个标记框只会有一个负责预测框,然后计算标记框与负责预测框的置信度损失,此时标记框的置信度为1,负责预测框的置信度则是模型对这个预测框的置信度输出值。 不负责预测框的损失:不负责预测框的逻辑很简单。模型输出的98的预测框,除了上面的负责预测框以外,其他的97个框都是不负责预测框。 按照以上公式分别计算负责预测框的损失值和不负责预测框的损失值,然后把不负责预测框的损失值乘以0.5以压制背景框对真实预测框损失值的影响,两者相加就是整个置信度的损失值。 类别损失 类别损失的计算,仍然是仅对负责预测框进行类别损失的计算,其他预测框的类别损失值均为0。 对于每一个负责预测框,计算这个预测框的20种类别识别概率与训练样本标记框(ground truth)类别的损失。值得注意的是,训练样本标记框的类别是以one-hot形式提供的20维标签(只有一个类别为1,其他全部为0),所以类别损失就是每个负责预测框对应的20个类别的识别概率,与标记框one-hot类别的20维概率标签之差的平方差。 参考资料 - 《YOLO目标检测》杨建华,李瑞峰 第三章 YOLOv1 - YOLO演進 — 1. 在上篇文章有提到過 YOLO 的作法以及如何訓練,在這篇再更詳細的說明YOLO… | by 李謦伊 | 謦伊的閱讀筆記 | Medium

YOLOv1输出信息以及NMS过滤算法详细解读

对于YOLOv1模型的网络架构,在[[YOLOv1模型网络架构解读]]一文中已经有非常详尽的解释。以下是YOLOv1网络的架构图: !image.png 从以上架构图上可以看到,相比于对图像类别进行识别的各种经典卷积神经网络模型而言,YOLOv1的网络结构并没有特别大的创新点,仍然是在网络的前半段通过反复的卷积+池化操作提取图像中的特征,在网络的后半段通过连续两个全连接层进行类别识别和位置判断。但相比其他网络,YOLOv1架构在设计上的精髓在于其最后的7x7x30的输出解读、对模型参数的训练及其损失函数的评估设计方面。 YOLOv1模型的输出信息 YOLOv1的输出是一个7x7x30的三维张量,可以认为该模型把输入的448x448分辨率切分为7x7的网格,从每个网络中搜索待识别对象的中心点及其Bounding Box、置信度、对象类别等信息,每个网络通过一个30个元素的向量来表征该网格中检测到的这些信息。 如下图所示,每个网格会输出两个Bounding BOX的信息,每个BOX的信息包含检测物体中心点坐标(相对于该网格左上角)以及该检测物体的长宽(相对于整幅图即448x448的长宽比例),这个检测框的置信度信息。 !image.png 需要注意,YOLOv1对于图像中物体的检测逻辑是,如果一个物体的中心点落在某个网格内,就由这个网格负责检测和输出该物体对应的Bounding Box等信息。因此,每个网格输出BOX信息中,x和y表示这个待检物体的中心点相对于当前网格左上角的位置,而w和h则表示这个网格预测Bounding Box的相对于整个输入图像分辨率的长和宽。 每个BOX都有一个置信度Confidence的参数,这个参数用于表征当前BOX中真正包含待检物体的概率,如果这个BOX中包含有某个待检物体的中心点的话,这个置信度参数会比较高,达到接近1的程度;而如果这个BOX中不包含任何待检物体的中心点,那么其置信度会接近0,这样就会在后面的过滤算法中被过滤掉。 20 Classes则用于表示各个待检物体类别的最终识别概率。对于VOC数据集而言,可以支持对20种类别的检测识别,如果是COCO数据集的话,各个网格输出的类别数量就需要修改为80个种类的识别概率了。 两个BOX,一组类别识别概率? 从以上所总结的YOLOv1模型各个网格的输出信息可以看到,每个网格的输出包含了两个预测BOX的信息,以及一组识别待检物体的识别概率。那么如果一个网格中刚好包含了两个待检物体的中心,那这组类别识别概率究竟表示的是哪一个物体的识别类别呢? 答案是:这种情况下肯定会漏检,这也是YOLOv1在密集小目标场景下召回率骤降的根本原因所在,也是后续版本(YOLOv2起)引入anchor、把“负责检测单元”从网格细化到“网格+anchor”甚至“pixel”级别的动机。 基于下图来理解这个事情: !image.png 对于每个网格所输出的两个BOX而言,这个网格在输出时只会把两个BOX与实际待检物体吻合度(实际上就是IOU指标)最好的那个框标记为正样本。对于上图而言,实际物体的Bounding Box是上图中的浅灰色框,那么预测框A的吻合度远好于预测框B。那么,这个网格的输出中,预测框A的置信度就会比较高,预测框B的置信度会被压得比较低,而这个网络中所包含的类别信息对应的也就是预测框A所圈选的部分图像的类别识别概率。 因此,YOLOv1整个输入图像被划分为7x7个网格,每个网格输出两个BOX,看起来YOLOv1最多可以实现对7x7x2=98个物体的检测,但实际上,每个网格最多只能输出一个BOX及其对应的识别类别,所以整个图像最多也就只能识别49个独立物体。即:每个网格输出的两个BOX不是为了确保在一个网格中检测出来两个物体,而是为了在一个网格中检测出来物体以后,通过两个BOX提供二选一的选项,让模型能够选择匹配性最好的框来定位其位置。 NMS NMS:Non Maximal Suppression,非极大值抑制算法,顾名思义就是抑制不是极大值的边框,这里的抑制通常是过滤掉针对同一个检测物体的冗余边框。 如上所述,对于每一个输入YOLOv1模型进行推理和检测的图片而言,模型会通过7x7x30这个三维张量输出7x7x2=98个预测框。这98个预测框还需要经过NMS算法的过滤才真正输出最终的检测结果,否则一个物体可能会包含多个输出检测框。 NMS算法的基本执行逻辑:利用得分高的边框抑制得分低且重叠程度高的边框。 NMS算法的执行,按照其所支持的类别分类独立进行处理,每进行一次NMS运算,得到一个类别的最终检测框。但不同类别的计算逻辑是完全相同的,最终把所有类别的最终检测框汇总在一起就是模型最终的检测结果。 步骤0: 首先针对某个指定的类别,通过BOX的置信度confidence和这个类别的识别概率相乘得到这个BOX针对这个类别的分数score。因为模型输出98个BOX,所以每个类别也就有98个score。 步骤1:对这个类别的98个score与NMS的检测阈值(例如0.2)进行比较,小于这个阈值的BOX删除掉,只保留大于阈值的BOX放入备选列表。 步骤2:对备选列表中所有BOX基于score的大小降序排序,把备选列表中score最大的那个BOX直接放入该类别的检测输出中。 步骤3:计算备选列表中score值最大的BOX,与备选列表中所有其他BOX计算IOU指标,当计算出来的IOU值>IOU阈值(一般为0.5)时,表示两个BOX检测的是同一个目标,此时把score较小的那个BOX从备选列表中删除。 步骤4:从当前备选列表中把score最大值的BOX删除掉。 反复执行以上的步骤2-步骤4,直到备选列表的BOX为空。此时就得到了这个类别的检测输出结果,然后对所有的20个类别全部执行完成以上步骤,把所有类别的检测输出结果汇总,就是模型最终的检测输出。 !image.png 如上图是针对小狗这个类别的NMS算法过滤流程。 - 首先针对小狗这个类别,通过每个BOX的置信度及其所属网格中小狗这个类别的识别概率相乘,得到98个分数score。 - 对这98个score与阈值0.2进行比较,删掉阈值小于0.2的BOX,把其他的BOX放入备选列表中。 - 对备选列表中的BOX基于score进行降序排序,score最大的BOX直接放入小狗这个类别的检测输出中,同时执行以上描述的NMS算法,每次都是把score最大的BOX放入该类别的输出列表,并通过IOU去掉与该BOX重合度较大的其他BOX(避免一个检测物体出现多个检测框),直到整个备选列表中的BOX全部清空。 参考资料 - 《深度学习之Pytorch物体检测实战》6.1 无锚框预测YOLOv1 - All of the start: YOLOv1. YOLO: you only look once. | by John Shen | Medium - YOLOv1 詳細解讀. 設計概念: | by Steven Meng | Medium

YOLOv2模型网络架构解读

本文对YOLOv2模型的BN层、Darknet-19主干网络,以及模型的总体结构进行了详细的总结和学习。 批归一化BN层的引入 在YOLOv1模型的主干网络所使用的卷积结构,仍然采用的是传统的线性卷积+非线性激活函数的模式,并没有用到在后来十分流行的各种归一化层的设计。因为批归一化层(Batch Normalization,BN)在越来越多的模型中应用,其工作性能得到了业界的广泛认可,因此逐步成为卷积神经网络的标配设计。YOLOv2模型的架构设计中也就顺势引进了被广泛验证的BN层。 具体来讲,就是在网络架构的卷积结构由之前的线性卷积+非线性激活函数的模式,变成了线性卷积层+BN层+非线性激活函数的形式。这种结构也称之为CBL(Conv-BN-LeakyReLU)结构。 !image.png Darknet-19主干网络 如[[YOLOv1模型网络架构解读]]所介绍的,YOLOv1模型的主干网络采用了类似GoogLeNet的设计。到了YOLOv2版本,作者设计了一个新的名为DarkNet-19的网络结构取代了前一代的主干网络。 Darknet-19网络包含有19个卷积层,而且每个卷积层都是以上所描述的CBL结构,即卷积层+BN层+激活函数的形式。下图为Darknet-19网络架构各层的配置参数: - 下表Output对应的特征图维度,对应的是输入分辨率为224x224的图片。当Darknet-19作为YOLOv2的主干网络使用时(输入图像分辨率416x416),其各层输出的特征图维度要对应发生变化。 !image.png > 以上的架构图中,darknet-19网络可以分为两个部分,前半部分是特征提取部分,预训练完成后会被作为YOLOv2的主干网络;最后的卷积层+全局平均池化层+Softmax部分是分类部分,在YOLOv2模型中会被去掉。 可以看到,以上的darknet-19网络架构中,共包含了19个卷积层(3x3卷积和1x1卷积),5个2x2最大池化层,最后借鉴了NiN网络(参考[[CNN经典网络模型架构学习之NiN]])的做法,使用全局平均池化层代替全连接层,在最后一层使用Softmax输出1000个分类的预测结果。 Darknet-19的作者团队用ImageNet数据集去训练网络参数,最终的检测精度上,DarkNet-19网络达到了VGG网络相同的水平,但模型更小(参数量只有25.5M),浮点运算量更是只有其1/5左右,因此运算速度极快。 Darknet-19网络在ImageNet数据集上预训练完毕后,去掉其中用于分类任务的最后一层卷积、平均池化层和Softmax层等,然后将其用作YOLOv2的新主干网络。 Passthrough与Reorg 在主干网络的设计上,除了设计了一个新的darknet-19主干网络以外,YOLOv2还借鉴了SSD模型使用更高分辨率特征图的设计。 > 通常,不同的特征图的分辨率不同,越是浅层的特征图,越被较少地做降采样处理,因而分辨率就越高,所划分的网格就越精细,这显然有助于去提取更多的细节信息。 对于YOLOv2的主干网络设计而言,除了主干网络darknet-19特征提取部分最后一个卷积层(第19个卷积层,如下图红色箭头所示)输出的低分辨率特征图以外,YOLOv2还从darknet-19网络的第13个卷积层输出(如下图绿色箭头所指)高分辨率特征图,然后把两个特征图合并在一起作为整个主干网络的特征提取输出。在这种情况下,主干网络的特征输出中网络尾段低分辨率的特征和网络中段高分辨率的特征,提取出来的信息更加完整。这就是YOLOv2模型的所谓Passthrough架构。 !image.png 在输入图像分辨率为416x416的情况下,主干网络中段输出的高分辨率特征图的维度是26x26x512,而其末端输出的低分辨率特征图的维度是13x13x1024。不同分辨率的特征图要如何才能合并在一起输出呢?答案是Reorg操作。 Darknet-19末端的低分辨率特征图维度为13x13x1024,再经过一个3x3,stride=1的CBL卷积块处理,仍然是13x13x1024维度。 而Darknet-19中段的高分辨率特征图维度为26x26x512,则要执行一个reorg的操作,总体数据量不变,但是分辨率会减半,通道数增加为原来的四倍。对于单通道特征图的Reorg操作(相当于只是把数据重排一下而已)如下图所示,一个通道变成了4个通道。通过这样的操作,中段高分辨率的维度就变成了13x13x2048。 !image.png 经过以上的各自处理后,末端和中段的特征图的空间分辨率都变成了13x13,现在就只需要把所有的特征图合并在一起就可以了。因此经过以上合并后,输出的特征图的通道数就是1024+2048=3072通道,整体的特征图数据维度就是13x13x3072。 YOLOv2模型的总体架构 YOLOv2模型的总体网络架构如下图所示: !image.png > 需要注意的是,上面的总体网络架构图与Passthrough部分所提供的网络架构图,在主干网络经过merge以后输出的通道数有分歧。上图中中段高分辨率特征图在Passthrough的处理过程中会通过一个卷积CBL块先降低到64通道,再reorg为13x13x256维度,因此Merge后输出的总的特征数据维度就是13x13x1280;而前一节的网络结构图中,Passthrough的处理过程中对高分辨率特征数据不做降通道操作,直接reorg为13x13x2048维度,这样Merge后的总的特征输出数据的维度就是13x13x3072。不过Merge后输出无论是1280通道还是3072通道,后续都会通过一个CBL卷积块把通道数降低为1024。真正用于最终预测的特征数据维度都是13x13x1024。 输入图像的分辨率为RGB 3通道416x416分辨率的图像,经过其主干网络darknet-19处理并合并其末端和中段特征提取数据后,最终输出的特征图为13x13x1024维度。然后再基于这个13x13x1024维度的特征数据进行图像中待检物体类别和位置的预测。 最后的这个预测过程就比较简单了:就是对13x13x1024的特征提取数据执行1x1卷积操作。最终的输出数据维度根据待检物体的类别数量来确定。YOLOv2的每个网格中包含有5个预测BOX(在YOLOv2的术语也称为anchor),每个BOX所包含的数据有:中心坐标x和y,长宽度w和h,置信度confidence,各个类别的概率(依据待识别物体的类别种类数量而定)。 - 对于COCO数据集而言,可以支持80个类别的检测。那么每个anchor的数据量是85,每个网格包含5个anchor,因此每个网格的数据量就是425,针对COCO数据集而言模型输出推理结果的数据维度就是13x13x425。 - 而对于VOC数据集而言,支持20种类别,每个网格的数据量是125,针对COCO数据集而言模型输出推理结果的数据维度就是13x13x125。 从以上的YOLOv2的总体架构图可以看出,所有的网络层都是卷积网络,YOLOv1的全连接层被彻底拿掉了,所以YOLOv2也就成了全卷积网络。 参考资料 - 《YOLO目标检测》杨建华,李瑞峰 第6章 YOLOv2 - DAY28 深度學習-卷積神經網路-Yolo v2 (二) - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天

YOLOv1模型网络架构解读

YOLOv1的架构总结 YOLOv1的架构设计逻辑是:仅使用一个完整的卷积神经网络结构来端到端地检测图像中所包含的目标及其位置,对应于当时在目标检测领域主流的R-CNN系列为代表的two-stage流派,YOLOv1的架构可以说是开启了one-state来实现目标位置检测+类别识别的架构设计思路的先河。 YOLOv1的网络架构参考了GoogLeNet网络来设计主干网络,但并未采用GoogLeNet的Inception模块,而采用了串联的1x1卷积和3x3卷积层所组成的模块,因此其主干网络的架构非常简单。 YOLOv1网络的架构图如下所示: !image.png 从以上YOLOv1的架构图可以看到,YOLOv1模型包含有24个卷积层用于提取输入图像的特征信息,以及两个全连接层用于预测图像中所包含的目标位置、置信度以及类别识别几率信息。卷积层部分,采用了多个1x1卷积层用于压缩特征图的通道数量。而激活函数方面,除了最后一层采用线性激活以外,其他各层均采用了Leaky ReLU。 整个网络架构与各种经典的基于卷积神经网络的图像识别模型没有太大差异,其差异主要在图像的分类和输出部分。最后一个卷积层输出的特征图是1024通道7x7分辨率。这个特征图展平(flatten)为7x7x1024的一维向量,经过连续的两个全连接层(分别包含4096个和1470个神经元)进行处理,最后的1470个神经元的全连接层输出最终的预测。因为YOLOv1执行的目标检测任务是一个空间任务,所以这个全连接层的1470个输出数据转换为7x7x30的三维张量,其中7x7是这个三维张量的空间分辨率,30则是这个张量的通道数量。 后续部分对这个7x7x30所代表的目标检测信息详细解释。 因为在图像在进行目标位置及其类别的检测和识别需要更细粒度的视觉信息,所以YOLOv1的图像输入分辨率从各种图像识别模型标准输入的224x224分辨率,提升为448x448。 YOLOv1的参数量 YOLOv1模型存在的一个明显的问题就是参数量实在是太大了。 仅计算最后一个卷积层的输出(维度为7x7x1024),与第一个全连接层(4096个神经元)之间的连接,其参数量就达到了:7x7x1024x4096,大概2亿个参数。这样势必导致模型的推理速度很慢,以及模型运行需要的内存空间也会比较大。 不过这也是没办法的事情,毕竟在YOLOv1模型所推出的2015年,卷积神经网络架构的标准设计就是卷积层与全连接层相连,各自分工,那么全连接层参数众多的问题就很难避免,直到全局平均池化层的出现,才解决了这个问题。 YOLOv1的执行流程 YOLOv1对进行推理的图片所进行的目标检测流程主要可以分为输入图像的预处理、基于卷积神经网络提取特征、基于全连接层进行目标及其位置检测、最终输出检测结果这几个步骤。 !image.png 输入图像的预处理环节主要包含两个方面。首先如上所述,YOLOv1的输入图片的分辨率为448x448的RGB图像,所以对于不同分辨率的图像,首先要缩放到这个尺寸。同时为了避免输入图像的长宽比失真,对于长宽比非1:1的输入图像,还需要通过填充等步骤来进行处理。经过以上分辨率缩放等环节的处理后,再执行像素归一化,把输入图像中各个像素的值从[0,255]缩放到[0,1]区间,或进一步标准化为均值为0、方差为1的分布。 接下来经过以上预处理以后的RGB三通道448x448分辨率的图像,经过连续24个卷积层(包含对应的池化层)处理,逐步提取其中所包含的特征,输出7x7x1024的特征图。这个7x7x1024的特征图展平之后,再经过两个全连接层基于特征数据进行目标种类和位置检测,检测结果resize为7x7x30的三维张量,对这个输出的三维张量进行解析就可以得到图像中所包含的目标种类及其位置。 _整个执行流程的关键,实际上就在于卷积层输出的7x7x1024特征图中所包含的信息,与整个模型最终输出的7x7x30三维张量的映射_。 从以上的这一点上,可以把整个流程看成是对输入的448x448分辨率的图像切分为7x7的网格,无论是卷积层输出的7x7x1024的特征图,还是最终输出的7x7x30的三维张量,其中的1024和30对应的都是每一个网格的特征信息和检测结果:对于每一网格而言,经过24个卷积层的处理,得到一个长度为1024的特征向量,这个特征向量中包含了该网格中的某种高级特征信息。然后在全连接层中再通过遍历这些网格、处理其中的特征信息来预测每个网格处是否有目标的中心点坐标,以及相应的目标类别,把结果输出为一个长度为30的向量,用以表征该网格的检测信息。 !image.png 因此,从以上流程可以看到,图像的输入以及卷积层的检测,与之前的图像识别模型如VGG、GoogLeNet等并无区别,YOLOv1的最大差异在于全连接层的处理结果,需要resize为一个三维张量进行输出。这个三维张量中所包含的数据,就对应了从输入图像中检测到的目标种类和位置。 YOLOv1的输出信息解读 如上所述,YOLOv1的输出为一个7x7x30的三维张量,对应为7x7个网格,那么每个网格所包含的输出信息就是一个长度为30的一维向量。这个一维向量包含以下信息:两个目标检测边界框BOX1和BOX2的参数,以及20个目标检测种类的识别概率。 每个BOX的参数包含其中心坐标(x,y)和长宽参数(w,h),以及这个BOX检测的置信度confidence,因此每个BOX有5个参数。两个BOX的5个参数,再加上20个目标检测类别的识别概率,总共加起来就是长度为30的一维向量。如下图所示。 > 为什么是20个种类?这是因为YOLOv1的原论文是使用VOC数据集进行训练的。VOC数据集总共支持对20个种类的目标进行检测,所以默认YOLOv1模型的输出就是20个类别的识别率。如果使用支持80个类别的COCO数据集,那么每个网格的输出长度就应该是2x5+80=90。 !image.png 如何检测? YOLOv1在“谁来负责进行目标预测”的这个问题上有一个最核心的设计:待检物体的中心点落在哪个网格cell里,就由那个cell负责把这个物体找出来。 这一点,对于理解YOLOv1的输出而言非常关键。 以下图为例,尽管小猫这个物体在整个图像中占据的范围跨越了多个网格,但其中心点在粉色网格中,那么YOLOv1模型的输出中,就只有粉色网格所输出的BOX包含有小猫这个待检物体的相关检测信息,其他网格就都成为背景框。 !image.png BOX的坐标位置参数 如上所述,每个网格中包含有两个BOX的检测结果,其中的每个BOX用x,y,w,h来表示其在网格中的位置。仍然使用上面的小猫图对x,y,w,h参数进行解释。 粉色网格输出的其中一个BOX会对应对小猫的检测框信息,其中(x,y)表示这个预测BOX的中心相对于其所在网格左上角的偏移量。以上图的小猫为例说明,粉色框中的红点为小猫的中心点,那么小猫这个检测框(大的红框)所应对的(x,y)就是红点相对于粉色框左上角的偏移量。w和h则表示这个预测BOX(大的红框)的width和height在整幅图像(448x448分辨率)中的长和宽上所占的比例。 BOX的置信度Confidence如何理解? BOX边界框的置信度参数Confidence,本质上就是用来判断这个BOX中是否包含有某个待检物体的中心点。理想情况下,依据YOLOv1的设定,包含有待检物体中心点的网格所输出的边界框置信度很高,而不包含中心点的网格则输出的边界框置信度很低甚至为0。 > BOX的置信度直接决定一个网格中是否包含有某个待检目标的中心点。 在后期处理中,通过判断Confidence这个参数,就可以知道当前网格中是否包含有某个待检物体的中心点,在确认该网格包含有待检物体的中心点的情况下,再检查20个目标检测类别的识别概率,判断这个待检物体的类别。 如果同一个网格中包含了两个物体的中心点? 按照YOLOv1的设计,每个网格的输出信息只能把两个预测框里最好的那一个标记成“正样本”,并且只能给它分配 1 个类别标签。 因此,当两个不同物体的中心不幸落在同一个网格里时,YOLOv1网络只能: - 选其中一个物体(通常是与默认框 IoU 更大的那个)当作该网格的回归/分类目标; - 另一个物体会直接被当成不存在,训练时既无坐标损失也无类别损失; - 在推理阶段,这个网格最多只能输出第一个物体的检测框,第二个物体必然漏检。 这就是 YOLOv1 在密集小目标场景下召回率骤降的根本原因。 参考资料 - YOLOv1 詳細解讀. 設計概念: | by Steven Meng | Medium - 《YOLO目标检测》第3章 YOLOv1 - YOLO演進 — 1. 在上篇文章有提到過 YOLO 的作法以及如何訓練,在這篇再更詳細的說明YOLO… | by 李謦伊 | 謦伊的閱讀筆記 | Medium

目标检测模型的评价指标详解:mAP,召回率,准确率

对于计算机视觉中的目标检测领域而言,主要的评估指标就是mAP与召回率、准确率。本文对IoU、准确率、召回率、AP以及mAP的概念和计算逻辑进行了详细的整理。 IOU IOU:Intersection Over Union。 对于目标检测算法的执行而言,每一张训练图片中的每个待检测物体,都会标注一个该物体位置对应的bounding box,这个标注的bounding box就是真实物体区域对应的检测区域。而目标检测算法对图片中物体所进行的检测和推理,会输出一个检测出来的Bounding box。IOU指标就是通过两个Bouding Box重合的程度,来评估目标检测算法对于该物体的检测精度。标注Bounding Box与检测Bounding Box的重合度越高,表示检测的精度越好。 !image.png 用下图可以非常直观的解释IoU如何计算。具体的解释就是:系统检测到的物体区域与真实物体区域的交集(如下图中Area of Overlap部分,也就是Intersection部分),除以两个区域之间的并集(如下图中Area of Union部分,也就是Union部分)。 !image.png 因此,对于一个检测图片中的某个待检物体而言,其IoU指标的计算,就是用检测框与标记框的交集面积,处于两个框的并集面积。这个值的计算结果当然是在0-1之间,0表示检测结果与真实的标记框完全无关,检测完全错误,而1则表示检测框和标记框完全重合,对于算法的评估而言,这个值当然是越高越好。一般使用IoU=0.5来作为判断识别率的基准,大于这个阈值表示是有效的检测,否则是无效的检测。 在理解以上IoU概念的基础上,对一张图片中的多个物体进行目标检测,就可能存在多种检测结果: !image.png - TP(True Positive):算法准确的检测到了图片中的物体,并且两个检测框的IoU重合度大于0.5,因此是一个正确的预测。 - FP(False Positive):算法检测出来的检测框,与该图片所有物体的标记框的重合度都小于0.5,最典型的就是把背景识别成了物体,因此是一种误检的情况。 - TN(True Negative):本身是背景,算法也没有输出对应的检测框,因此是正常现象,在目标检测的指标计算中不需要考虑。 - FN(False Negative):本来需要算法检测出来的物体,算法没有检测出来,所以相当于是漏检的情况。 以上检测结果的分类整理成表格就是: !image.png 召回率与准确率 准确率:Precision。召回率:Recall。 有了以上对于IoU以及目标检测算法的各种不同检测结果的类型的理解,关于目标检测算法执行准确率和召回率的概念及其计算就比较简单了。 所谓的准确率,表示的就是:目标检测算法所输出的检测结果中,其类别判断和位置检测(IoU>0.5)均正确的检测所占的比例。准确率的计算公式如下图所示: !image.png 而所谓的召回率,则表示的是:目标检测算法对图片中的物体进行的检测,把多少个物体正确地检测(类别判断正确,且IoU>0.5)出来了。召回率的计算公式如下图所示: !image.png 以上图中的茶杯的检测为例,准确率表示的就是,算法输出的茶杯检测结果,真的是茶杯的情况所占的比例。而召回率则表示的是,图片中所有的茶杯,被算法准确检测出来所占的比例。 AP与mAP AP:Average Precision,表示某个类别的检测精度。mAP:mean Average Precision,表示所有类别的平均检测精度。 下面以一个例子来更详细地解释说明AP的计算过程。 以某个数据集进行小狗目标的检测为例。数据集中真实标注的小狗目标有8个,分别对应于GT1-GT8(这里的GT表示Grounded Truth的意思,也就是通过手动标注出来的真实的小狗目标所在的位置)。而通过算法检测到的小狗目标及其位置有10个,使用BB1-BB10来表示(BB表示检测算法检测到的每个小狗目标的Bounding Box)。 使用算法对数据集中小狗目标的检测结果,部分检测位置的Bounding Box只检测到小狗的头或者脚,也就是说IoU很低(例如BB3, BB4, BB5, BB7, BB8这个检测输出的IoU均比较低),而且两个真实的小狗目标GT6和GT7没有能够检测到(FN=(GT7, GT8)),也就是说这两个真实目标漏检了。基于以上信息整理目标检测算法的执行结果如下图所示:需要注意的是,目标检测算法输出的每个检测结果,不仅包含这个检测结果对应的检测类别,Bounding Box的坐标信息,还有一个检测的置信度(Confidence),也就是这个检测目标正确的概率。 !image.png 从上图可以看到,对于一个真实的待检小狗目标,检测算法可能输出多个独立的检测结果与之对应,例如BB1和BB3对应的都是GT1。而一般情况下,只有在IoU>0.5的情况下,我们才会认为算法输出的检测结果是正确的TN,因此对以上算法检测所输出的10个检测结果BB1-BB10进行IoU判断如下: !image.png 然后按照流程,基于检测结果的置信度(即上图中属于狗的概率)对其进行排序: !image.png 然后按照以上排序,在不同的rank上计算各自的准确率Precision和召回率Recall。 - 对于Rank1而言,其准确率=(>=rank1的TP数量)/(>=rank1的TP+FP的总数量)=1/1=1,其召回率=(>=rank1的TP数量)/(TP的总数量)=1/8=0.125。 - 对于Rank2而言,其准确率=(>=rank2的TP数量)/(>=rank2的TP+FP的总数量)=2/2=1,其召回率=(>=rank2的TP数量)/(TP的总数量)=2/8=0.25。 - 对于Rank3而言,其准确率=(>=rank3的TP数量)/(>=rank3的TP+FP的总数量)=3/3=1,其召回率=(>=rank3的TP数量)/(TP的总数量)=3/8=0.375。 - 对于Rank4而言,其准确率=(>=rank4的TP数量)/(>=rank4的TP+FP的总数量)=3/4=0.75,其召回率=(>=rank4的TP数量)/(TP的总数量)=3/8=0.375。 - 以此类推,得到rank5的准确率=4/5=0.8,其召回率=4/8=0.5。 - rank6的准确率=5/6=0.833,其召回率=5/8=0.625。 - rank7的准确率=5/7=0.714,其召回率=5/8=0.625。 - rank8的准确率=5/8=0.625,其召回率=5/8=0.625。 - rank9的准确率=5/9=0.556,其召回率=5/8=0.625。 - rank10的准确率=5/10=0.5,其召回率=5/8=0.625。 因此,以上根据目标检测算法输出的10个检测结果,得到10组准确率和召回率的数据如下: !image.png 根据10组(Precision,Recall)就可以画出目标检测算法输出以上结果对应的P-R图: !image.png 所谓的AP指标计算上的定义,就是上面P-R中标记的蓝色P-R曲线下的面积或者积分。因为Precision和Recall指标都是在0-1之间,所以以上计算出来的AP指标当然也是在0-1之间。 !image.png P-R曲线面积或者积分的计算比较复杂,所以可以采用上图提供的两个离散序列公式来计算AP指标。 第一个公式中,P(k) 是第k个rank的准确度,而 Δr(k) 是指第k个rank的召回率与第k-1个rank的召回率的差值,因此按照该公式计算AP指标就是: 第二个公式中,P(k) 是第k个rank的准确度,而 rel(k) 的定义则是,在第k个rank为有效检测TP时, rel(k) 为1,否则为0。number of relevant documents表示总的TP个数,这是是8个(GT1-GT8)。因此基于该公式计算AP指标就是: 所以可以看到,以上的两个公式虽然算式不同,但是最终的结果殊途同归,最后计算出来的AP值都是0.579。 以上AP的计算说完了,那么mAP的计算就比较简单了。上面所进行AP的计算,只是针对小狗这个类别计算出来的AP,一般情况下目标检测算法可以输出多个不同类别的检测结果,例如在COCO数据集中就包含了80个类别的标注信息,那么一个目标检测算法的mAP指标,就是先独立的计算它所支持的每个类别的检测AP指标,然后再把所有的AP值加起来求平均值就是整个算法对所有类别检测性能的mAP指标。 !image.png 参考资料 - 《深度学习之Pytorch物体检测实战》1.2 - Yy's Program: 影像辨識常見的IOU、AP、mAP是什麼意思? - Learning Model :什麼是AP/mAP/IoU?[轉錄] | by Ryan Lu | AI反斗城 | Medium

一文总结YOLOv5模型的自定义训练、测试与模型导出的全过程

本文详细总结了基于用户自己指定的训练数据集,对训练数据集及其标注按照YOLO的格式进行预处理,然后对YOLOv5模型进行训练、测试以及ONNX导出等的完整流程。 YOLOv5模型的下载和测试 要对YOLOv5模型进行自定义的训练和部署,首先要做的当然就是下载YOLOv5的源代码。在Github上搜索YOLOv5找到Ultralytics的源代码仓库:ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite 。 可以看到尽管YOLOv5是比较早期的模型了,但是其开发和维护并没有停止,一直到现在仍然在持续更新中: !image.png 通过Git Clone或者直接使用HTTPS下载最新版本的代码到本地。 下载下来以后,可以先使用默认训练好的模型调用其中的脚本进行一些测试。对于YOLOv5模型的基本测试使用而言,主要用到其中的三个脚本文件:trian.py(训练)、detect.py(新样本数据推理)、export.py(模型导出)。 train.py的主要调用参数: - -weights : 初始权重文件路径,默认为 yolov5s.pt (官方基于COCO数据集预训练好的参数文件,首次使用时会自动下载),用于加载预训练模型 - -cfg : 模型配置文件 (model.yaml) 路径,定义网络结构。默认的模型配置文件在models目录下。 - -data:训练数据集配置文件。默认的数据集配置文件在data目录下。 - -batch-size:训练过程中每个mini batch的数量,一般是8或者16 - -epochs:训练的轮次数量 - -device : 指定 CUDA 设备,如 0 或 0,1,2,3 或 cpu 基于以上weights和cfg参数的设置情况,对于模型的训练可以分为以下情况: - 基于YOLO官方提供的预训练模型参数进行训练:此时就只需要通过weights参数传递预训练模型的pt文件即可,其中已经包含了模型的配置信息。 - 基于YOLO模型从头开始训练:将 weights 参数设为空字符串,并提供 cfg 参数设置模型配置。 - 如果同时在命令中提供了 cfg 和 weights 参数,则会优先使用 cfg 定义的模型结构,并尝试加载 weights 中的权重参数(如果结构匹配)。 detect.py的主要调用参数: - -weights : 指定模型的权重文件路径,默认为 yolov5s.pt 。 - -source : 推理数据的输入源路径,可以是单张图片、视频文件、目录、URL(rtsp、rtmp等)、摄像头索引(0)、屏幕(screen)等。 - -device : 运行设备,可指定为 cpu 或GPU索引(如 0,1 ),默认为自动选择。 - -classes : 指定要检测的类别索引列表,如 --class 0 2 3 ,默认为 None (检测所有类别)。 - -project : 推理结果保存的项目目录,默认为 runs/detect。 - -name : 实验名称(用于创建子目录),默认为 exp。 - -conf-thres:置信度阈值。只有识别的置信度大于该阈值才会输出结果并标记出来。 export.py的主要调用参数: - -data : 模型训练所使用的数据集配置YAML文件路径,默认为 data/coco128.yaml ,用于提供类别信息等元数据。 - -weights : 要导出的模型权重文件路径。 - -batch-size : 批处理大小,默认为 1。 - -include : 指定要导出的模型格式列表,默认为 torchscript ,支持的格式包括: - torchscript : PyTorch的TorchScript格式 - onnx : ONNX格式 - openvino : Intel OpenVINO格式 - engine : NVIDIA TensorRT格式 - coreml : Apple CoreML格式 - saved_model : TensorFlow SavedModel格式 - pb : TensorFlow GraphDef格式 - tflite : TensorFlow Lite格式 - edgetpu : TensorFlow Edge TPU格式 - tfjs : TensorFlow.js格式 - paddle : PaddlePaddle格式 YOLOv5源代码目录中的data子目录里面,包含了多个标准图像数据集的配置文件,可以先使用这些标准数据集及其配置文件测试YOLOv5模型的训练流程(此处使用coco128数据集): > 每次调用以上脚本进行的训练流程,都会在run/train下创建一个exp目录,其中包含有训练过程中所生成的各种中间文件。训练完成后,生成的权重参数文件就是这个目录下的weights/best.pt文件。 同样的,可以使用以下命令对Yolov5源代码中自带的图片,使用detect.py进行推理测试: 以上脚本会对data/images下的所有图片进行推理,推理生成的标注图片在runs/detect/exp目录下。 !image.png 训练数据集的准备 前面对Yolo模型的基本使用做了简单的测试。接下来,就是准备要准备自定义的数据集进行模型的训练了。 本文以口罩检测作为学习案例,网络上有大量的这类公共数据集可用,因此可以直接首先搜索可以满足训练需求的公共数据集。 - 网站kaggle,Roboflow,Huggingface等提供了大量的公开数据集,可以根据自己的需求在这些网站上搜索。 本文以Kaggle上的Face Mask Detection数据集对Yolov5模型进行训练:Face Mask Detection。该数据集对图片中的人是否戴口罩进行检测,整个数据集共853张图片,检测结果有三种:未戴口罩,戴口罩,未正确戴口罩。标注格式为Pascal VOC格式。 !image.png Kaggle上的Face Mask Detection数据集下载后,需要首先对其图片及其标注信息进行预处理,预处理为Yolo模型所需要的格式。对于Face Mask Detection数据集而言,要做的预处理包括: - 把该数据集所采用的Pascal VOC标注格式转换为Yolo格式。 - 把该数据集中的图片和标注集以85:10:5的比例切分为训练集、验证集、测试集三个部分。 - 按照Yolo模型训练所需要的训练数据集目录要求分别保存在train、valid、test三个子目录中。 - 准备该数据集用于Yolo训练的配置描述文件FaceMask.yaml。 Pascal VOC是一种常用的目标检测类型的标注格式,与Yolo模型训练所要求的标注格式大相迥异,所以我专门写了一个voc_to_yolo.py的脚本用于实现以上预处理流程的第1-3步:标注格式转换、数据切分、yolo训练目录结构建立。使用该脚本就可以实现从Pascal VOC数据集到Yolo训练数据集的转换: 下图是Face Mask数据集转换前后的目录结构示意图。 - 转换前的原始数据集中,所有的图片在images目录下,所有的Pascal VOC标注格式的标注信息在annotations目录下。 - 转换后的完整yolo数据集在yolo目录下,其中包含有test、train、valid三个子目录,分别对应测试、训练和验证数据集,其下又包含各自的图片文件子目录和Yolo标注文件子目录。 !image.png 基于以上已经准备好的Yolo格式的训练数据集,整理与之对应的数据集描述配置文件FaceMask.yaml如下。后续在训练过程需要使用该文件指定训练数据集的路径、识别和检测类型名称数量等信息。 模型训练和测试 基于以上预处理好的数据集在Yolov5预训练模型上进行训练: 在CPU上进行训练的时间比较长,不到1000张图片,在我的Thinkpad X260笔记本上训练的时间超过了一天。训练完成后生成的模型参数文件在runs\train\exp9\weights目录下的best.pt文件。 使用以上训练生成的模型参数文件对前一阶段分割出来的测试数据集进行测试: 以上脚本会对datasets\FaceMask\yolo\test\images\所指定的测试数据集中的所有图片进行推理,推理结果保存在runs/detect下的一个新建exp目录中,置信度大于0.5的识别结果会画框标记出来是否有正确的戴口罩。 !image.png 模型的ONNX导出 接下来就可以使用Yolov5源代码目录中的export.py,把前一步骤训练出来的best.pt参数权重文件导出为onnx标准模型文件,在onnxruntime等标准化的环境中进行部署了: 以上脚本执行后就会在best.pt相同路径下生成一个best.onnx文件,这就是模型导出的ONNX文件了。 参考资料 - ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite 。 - Face Mask Detection

计算机视觉类AI应用领域及其主流模型总结

本文总结了目前的人工智能领域中,针对计算机视觉方面的主要应用方向(图像分类,图像定位,目标检测,图像分割,语义分割,实例分割)的差异以及各个应用方向所存在的主流模型。 在当前的计算机视觉领域,图像分类、物体检测以及图像分割是最基础,也是目前发展最为迅速的三大领域。 图像分类 Image Classification 计算机视觉领域的图像分类任务,要解决的问题是:判断图像中所包含图像的类别是什么? 例如,对于下图而言,图像识别任务给出的识别结果是这幅图中包含了一只狗: !image.png 所以对于传统的图像分类任务而言,该任务识别结果对应的就是这幅图中最显著的物体所对应的分类(例如上图中的狗),并把这张图片直接按照这个最显著的物体来进行分类。因此,对于图像分类任务的图片样本而言,一般总是存在一个非常突出、最容易用于进行分类的显著物体存在,如下图的各个子图中均可以一目了然的判断该图片对应的分类(实际上也就是这个图片中所包含的最显著的物体): !image.png 当然,很有可能,我们提交给模型进行分类的图片中包含了多个较为显著的待识别物体,如下图中同时包含了一只狗和一只猫: !image.png 针对这种图像分类任务就需要多目标分类(Multi Label Classfier)。实际上从分类的角度上看,机器学习的分类算法可以分为三种类型: - Binary classification:有/无,是/非 - Multi-class Classification:从多个类别中返回一个 - Multi-Label Classification:从多个类别中返回多个 !image.png 针对Multi-Class和Multi-Label类型的分类算法,模型进行分类识别的判断结果区别就是,Multi-Class的输出结果是从多个类别中的1个,而Multi-Label则是多个类别中的多个: !image.png 无论如何,图像分类任务始终只关注要进行分类的图像中存在的物体类别,并不关注这些物体在图像中的位置和数量。 用于图像分类领域的模型训练数据集有:ImageNet,MNIST,CIFAR等。 图像分类领域的经典模型包括: - VGG,Visual Geometry Group at the University of Oxford - GoogLeNet (Inception),Google - ResNet,Microsoft Research - EfficientNet,Google - DenseNet,Cornell University - MobileNet,Google 目标检测 Object Detection 如果不光要关注图片中物体的类别识别,还需要准确的找到这些物体在图片中的位置,那么就需要用到计算机视觉中的目标检测任务。 如上所述,图像分类应用只是通过识别给图像一个单独的标签,而与之相对的目标检测应用,则需要在图像和视频中对检测到的每个对象提供空间坐标(边界框)和分类标签,这使得在更详细的层面上分析和处理可视化数据成为可能。 - 图像分类应用的输出结果,就只是图像中所包含对象的标签/标签。 - 目标检测应用的输出结果,则需要包含图像中包含对象的标签类别,以及以矩形框标记该对象在图像中所在的位置。 如果是图像中只包含一个最显著的目标,对该目标进行识别并定位的应用,就是Image Localization;而如果图像中有很多目标,要对所有的这些目标进行识别和定位的应用,就是Object Detection: !image.png 目标检测领域的经典模型包括: !image.png 图像分割 Image Segmentation 图像分割是把图像中以像素为单位分割为不同的对象,并使用不同的色彩涂色进行区分。 图像分割应用(Image Segmentation)又可以细分为语义分割(Semantic Segmentation)和实例分割(Instance Segmentation)。这两者之间的区别是: - 语义分割:图像分割的处理中,同一个类别的所有物体构成一个分类,因此,同一类别的所有物体都使用同一种颜色着色。 - 实例分割:在进行图像分割的处理过程中,同一个类别的多个不同实例被认为是单独的段,即同一类的每个对象被视为不同的。因此,即使属于同一类,每个独立的物体也会被涂上不同的颜色。 下图是语义分割的典型案例,整个图像中的不同类别的对象被涂以不同的颜色,但是同一类别的对象的颜色相同。 !image.png 下图是实例分割的典型案例,可以看到在该图片中识别到的三只猫被分割为不同的对象: !image.png 图像分割领域的主流模型: - Mask R-CNN - U-Net - SegNet - Deeplab - PSPNet - Vision Transformers (ViTs) 参考资料 - 人工智能中图像分类、目标检测、语义分割和实例分割等任务是什么? - 知乎 - Image Classification vs. Object Detection vs. Image Segmentation | by Pulkit Sharma | Analytics Vidhya | Medium - Object Detection Models - GeeksforGeeks - Image Segmentation Models - GeeksforGeeks