1、Yolo9


1.1、YOLOv9+SAM实现动态目标检测和分割

主要介绍基于YOLOv9+SAM实现动态目标检测和分割

背景介绍

51c视觉~YOLO~合集8_视觉

    在本文中,我们使用YOLOv9+SAM在RF100 Construction-Safety-2 数据集上实现自定义对象检测模型。

    这种集成不仅提高了在不同图像中检测和分割对象的准确性和粒度,而且还扩大了应用范围——从增强自动驾驶系统到改进医学成像中的诊断过程。

    通过利用 YOLOv9 的高效检测功能和 SAM 以零样本方式分割对象的能力,这种强大的组合最大限度地减少了对大量再训练或数据注释的需求,使其成为一种多功能且可扩展的解决方案。

YOLOv9简介

    YOLOv9简介(You Only Look Once)

51c视觉~YOLO~合集8_YOLO_02

YOLOv9性能图示

51c视觉~YOLO~合集8_视觉_03

YOLOv9模型图

    YOLOv9 在实时目标检测方面取得了重大进步,结合了可编程梯度信息 (PGI) 和通用高效层聚合网络 (GELAN),以提高效率、准确性和适应性,其在 MS COCO 数据集上的性能证明了这一点。

    利用开源社区的协作工作并在 Ultralytics YOLOv5 的基础上构建,YOLOv9 通过信息瓶颈原理和可逆函数解决深度学习中信息丢失的挑战,跨层保留基本数据。

    这些创新策略提高了模型的结构效率,并确保精确的检测能力,而不会影响细节,即使在轻量级模型中也是如此。

    YOLOv9 的架构减少了不必要的参数和计算需求,使其能够在各种模型大小(从紧凑的 YOLOv9-S 到更广泛的 YOLOv9-E)上实现最佳性能,展示了速度和检测精度之间的和谐平衡。

    作为计算机视觉领域的里程碑,YOLOv9不仅建立了新的基准,而且拓宽了人工智能在目标检测和分割方面的应用视野,凸显了该领域战略创新和协作努力的影响。

SAM简介

    SAM简介(Segment-Anything-Model)

51c视觉~YOLO~合集8_YOLO_04

SAM模型图

    分割一切模型 (SAM) 通过简化图像分割来推动计算机视觉向前发展,这对于从科学研究到创造性工作的一系列用途至关重要。

    SAM 利用迄今为止最大的 Segment Anything 1-Billion (SA-1B) 掩模数据集,通过减少对专业知识、强大的计算能力和广泛的数据集标注的依赖来实现分割的民主化。

    在 Apache 2.0 许可证下,SAM 引入了一个基础模型框架,允许通过简单的提示轻松调整任务,反映自然语言处理中的进步。

    通过对超过 10 亿个不同掩模的训练,SAM 理解了物体的广义概念,促进了跨陌生领域的零镜头传输,并增强了其在 AR/VR、创意艺术和科学研究等各个领域的实用性。

    该模型的提示驱动灵活性和广泛的任务适应性标志着分割技术的重大飞跃,将 SAM 定位为研究社区和应用程序开发人员的多功能且易于访问的工具。

关于数据集

 该项目利用Roboflow 的RF100施工数据集,特别是Construction-Safety-2子集,来演示集成模型的功能。

https://blog.roboflow.com/roboflow-100/

51c视觉~YOLO~合集8_YOLO_05

51c视觉~YOLO~合集8_视觉_06

    RF100 是由英特尔发起的一项计划,旨在建立对象检测模型的开源基准。它侧重于 Roboflow Universe 上可用数据集的通用性,提高可访问性并加快人工智能和深度学习的开发过程。

    RF100 构建数据集与 Roboflow 中的其他项目一样,是开源的,可以免费使用。

实现步骤

    实现步骤如下:

  • 环境设置
  • 下载 YOLOv9 和 SAM 的预训练模型权重
  • 图像推理
  • 可视化和分析
  • 获取检测结果
  • 使用 SAM 进行分割

    环境设置

    需要有 Google 帐户才能访问 Google Colab,这是一项免费云服务,为深度学习任务提供必要的计算资源,包括访问高达 16 GB 的 GPU 和 TPU。

    GPU状态检查

    首先,我们确保 GPU 的可用性和就绪性,用于处理和运行 YOLOv9+SAM 模型。

51c视觉~YOLO~合集8_视觉_07

    安装 Google 云盘

    接下来,如果您已经下载了数据集,我们需要导航到存储数据集的文件夹,否则我们可以直接使用 Roboflow 加载数据集。

from google.colab import drive
drive.mount('/content/drive')


or


%cd {HOME}/
!pip install -q roboflow


from roboflow import Roboflow
rf = Roboflow(api_key="YOUR API KEY")
project = rf.workspace("roboflow-100").project("construction-safety-gsnvb")
dataset = project.version(2).download("yolov7")

    配置 YOLOv9

    数据集准备好后,克隆 YOLOv9 存储库,然后切换到 YOLOv9 目录并安装所需的依赖项,为对象检测和分割任务做好准备。

!git clone https://github.com/SkalskiP/yolov9.git
%cd yolov9
!pip3 install -r requirements.txt -q

    显示当前目录

    将当前工作目录的路径存储在HOME变量中以供参考。

import os
HOME = os.getcwd()
print(HOME)

    下载权重模型

    让我们为模型权重创建一个目录,并从 GitHub 上的发布页面下载特定的 YOLOv9 和 GELAN 模型权重,这对于使用预训练参数初始化模型至关重要。

!mkdir -p {HOME}/weights
!wget -P {HOME}/weights -q https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-c.pt
!wget -P {HOME}/weights -q https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-e.pt
!wget -P {HOME}/weights -q https://github.com/WongKinYiu/yolov9/releases/download/v0.1/gelan-c.pt
!wget -P {HOME}/weights -q https://github.com/WongKinYiu/yolov9/releases/download/v0.1/gelan-e.pt

    下载图像进行推理

    为了使用 YOLOv9 权重进行推理,我们必须设置一个数据目录并下载一个示例图像进行处理,并在变量中设置该图像的路径SOURCE_IMAGE_PATH。

!mkdir -p {HOME}/data
!wget -P {HOME}/data -q /content/drive/MyDrive/data/image9.jpeg
SOURCE_IMAGE_PATH = f"{HOME}/image9.jpeg"

    使用自定义数据运行检测

    之后,执行detect.py指定参数对图像进行目标检测,设置置信度阈值并保存检测结果。这将创建一个包含 class_ids、边界框坐标和置信度分数的文本文件,我们稍后将使用它。

!python detect.py --weights {HOME}/weights/gelan-c.pt --conf 0.1 --source /content/drive/MyDrive/data/image9.jpeg --device 0 --save-txt --save-conf

    显示检测结果

    然后,我们利用 IPython 的显示和图像功能来展示指定路径图像中检测到的对象,并进行调整以获得最佳观看效果。

51c视觉~YOLO~合集8_YOLO_08

    安装 Ultralytics

    安装 Ultralytics 包以访问 YOLO 对象检测模型实现和实用程序,不要忘记导入 YOLO 类以执行对象检测任务。

!pip install ultralytics 
from ultralytics import YOLO

    安装Segment-Anything模型

    现在让我们安装 Segment-Anything 库并下载 SAM 模型的权重文件,为高质量图像分割任务做好准备。

!pip install 'git+https://github.com/facebookresearch/segment-anything.git'


!wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth

    提取检测结果和置信度分数

    我们需要将 YOLOv9 检测结果保存在上面的文本文件中来提取类 ID、置信度分数和边界框坐标。这里的坐标已经标准化,所以我们先将它们转换为图像比例,然后打印它们进行验证。

import cv2


# Specify the path to your image
image_path = '/content/drive/MyDrive/data/image9.jpeg'


# Read the image to get its dimensions
image = cv2.imread(image_path)
image_height, image_width, _ = image.shape


detections_path = '/content/yolov9/runs/detect/exp/labels/image9.txt'


bboxes = []
class_ids = []
conf_scores = []


with open(detections_path, 'r') as file:
    for line in file:
        components = line.split()
        class_id = int(components[0])
        confidence = float(components[5])
        cx, cy, w, h = [float(x) for x in components[1:5]]


        # Convert from normalized [0, 1] to image scale
        cx *= image_width
        cy *= image_height
        w *= image_width
        h *= image_height


        # Convert the center x, y, width, and height to xmin, ymin, xmax, ymax
        xmin = cx - w / 2
        ymin = cy - h / 2
        xmax = cx + w / 2
        ymax = cy + h / 2


        class_ids.append(class_id)
        bboxes.append((xmin, ymin, xmax, ymax))
        conf_scores.append(confidence)


# Display the results
for class_id, bbox, conf in zip(class_ids, bboxes, conf_scores):
    print(f'Class ID: {class_id}, Confidence: {conf:.2f}, BBox coordinates: {bbox}')

    初始化 SAM 以进行图像分割

    使用指定的预训练权重初始化 SAM 后,我们继续从 SAM 模型注册表中选择模型类型以生成分段掩码。

from segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor
sam_checkpoint = "/content/yolov9/sam_vit_h_4b8939.pth"
model_type = "vit_h"
sam = sam_model_registry[model_type](checkpoint=sam_checkpoint)
predictor = SamPredictor(sam)

    加载图像进行分割

    通过 OpenCV 库,我们加载图像以使用 SAM 进行处理,为分割做好准备。

import cv2
image = cv2.cvtColor(cv2.imread('/content/drive/MyDrive/data/image9.jpeg'), cv2.COLOR_BGR2RGB)
predictor.set_image(image)

    结果可视化

    为了可视化检测和分割结果,我们必须使用 SAM 将边界框转换为分割掩模。我们随机为类 ID 分配唯一的颜色,然后定义用于显示掩码、置信度分数和边界框的辅助函数。coco.yaml 文件用于将 class_ids 映射到类名。

import matplotlib.patches as patches
from matplotlib import pyplot as plt
import numpy as np
import yaml


with open('/content/yolov9/data/coco.yaml', 'r') as file:
    coco_data = yaml.safe_load(file)
    class_names = coco_data['names']


for class_id, bbox, conf in zip(class_ids, bboxes, conf_scores):
    class_name = class_names[class_id]
    # print(f'Class ID: {class_id}, Class Name: {class_name}, BBox coordinates: {bbox}')


color_map = {}
for class_id in class_ids:
    color_map[class_id] = np.concatenate([np.random.random(3), np.array([0.6])], axis=0)


def show_mask(mask, ax, color):
    h, w = mask.shape[-2:]
    mask_image = mask.reshape(h, w, 1) * np.array(color).reshape(1, 1, -1)
    ax.imshow(mask_image)


def show_box(box, label, conf_score, color, ax):
    x0, y0 = box[0], box[1]
    w, h = box[2] - box[0], box[3] - box[1]
    rect = plt.Rectangle((x0, y0), w, h, edgecolor=color, facecolor='none', lw=2)
    ax.add_patch(rect)


    label_offset = 10


    # Construct the label with the class name and confidence score
    label_text = f'{label} {conf_score:.2f}'


    ax.text(x0, y0 - label_offset, label_text, color='black', fnotallow=10, va='top', ha='left',
            bbox=dict(facecolor=color, alpha=0.7, edgecolor='none', boxstyle='square,pad=0.4'))


plt.figure(figsize=(10, 10))
ax = plt.gca()
plt.imshow(image)


# Display and process each bounding box with the corresponding mask
for class_id, bbox in zip(class_ids, bboxes):
    class_name = class_names[class_id]
    # print(f'Class ID: {class_id}, Class Name: {class_name}, BBox coordinates: {bbox}')


    color = color_map[class_id]
    input_box = np.array(bbox)


    # Generate the mask for the current bounding box
    masks, _, _ = predictor.predict(
        point_coords=None,
        point_labels=None,
        box=input_box,
        multimask_output=False,
    )


    show_mask(masks[0], ax, color=color)
    show_box(bbox, class_name, conf, color, ax)


# Show the final plot
plt.axis('off')
plt.show()

51c视觉~YOLO~合集8_视觉_09

    提取掩码

    最后,我们生成一个合成图像,在白色背景下突出显示检测到的对象,从分割蒙版创建聚合蒙版,并将其应用到将原始图像与白色背景混合以增强可视化。

aggregate_mask = np.zeros(image.shape[:2], dtype=np.uint8)


# Generate and accumulate masks for all bounding boxes
for bbox in bboxes:
    input_box = np.array(bbox).reshape(1, 4)
    masks, _, _ = predictor.predict(
        point_coords=None,
        point_labels=None,
        box=input_box,
        multimask_output=False,
    )
    aggregate_mask = np.where(masks[0] > 0.5, 1, aggregate_mask)


# Convert the aggregate segmentation mask to a binary mask
binary_mask = np.where(aggregate_mask == 1, 1, 0)


# Create a white background with the same size as the image
white_background = np.ones_like(image) * 255


# Apply the binary mask to the original image
# Where the binary mask is 0 (background), use white_background; otherwise, use the original image
new_image = white_background * (1 - binary_mask[..., np.newaxis]) + image * binary_mask[..., np.newaxis]


# Display the new image with the detections and white background
plt.figure(figsize=(10, 10))
plt.imshow(new_image.astype(np.uint8))
plt.axis('off')
plt.show()

51c视觉~YOLO~合集8_YOLO_10




1.2、YOLOv9~

太快 太卷了 在目标检测领域,YOLOv9 实现了一代更比一代强,利用新架构和方法让传统卷积在参数利用率方面胜过了深度卷积。目标检测新SOTA:YOLOv9问世,新架构让传统卷积重焕生机

继 2023 年 1 月 YOLOv8 正式发布一年多以后,YOLOv9 终于来了!

我们知道,YOLO 是一种基于图像全局信息进行预测的目标检测系统。自 2015 年 Joseph Redmon、Ali Farhadi 等人提出初代模型以来,领域内的研究者们已经对 YOLO 进行了多次更新迭代,模型性能越来越强大。

此次,YOLOv9 由中国台湾 Academia Sinica、台北科技大学等机构联合开发,相关的论文《Learning What You Want to Learn Using Programmable Gradient Information 》已经放出。

论文地址:https://arxiv.org/pdf/2402.13616.pdf

GitHub 地址:https://github.com/WongKinYiu/yolov9

如今的深度学习方法重点关注如何设计最合适的目标函数,从而使得模型的预测结果能够最接近真实情况。同时,必须设计一个适当的架构,可以帮助获取足够的信息进行预测。然而,现有方法忽略了一个事实,即当输入数据经过逐层特征提取和空间变换时,大量信息将会丢失。 

因此,YOLOv9 深入研究了数据通过深度网络传输时数据丢失的重要问题,即信息瓶颈和可逆函数。

研究者提出了可编程梯度信息(programmable gradient information,PGI)的概念,来应对深度网络实现多个目标所需要的各种变化。PGI 可以为目标任务计算目标函数提供完整的输入信息,从而获得可靠的梯度信息来更新网络权值。

此外,研究者基于梯度路径规划设计了一种新的轻量级网络架构,即通用高效层聚合网络(Generalized Efficient Layer Aggregation Network,GELAN)。该架构证实了 PGI 可以在轻量级模型上取得优异的结果。

研究者在基于 MS COCO 数据集的目标检测任务上验证所提出的 GELAN 和 PGI。结果表明,与基于深度卷积开发的 SOTA 方法相比,GELAN 仅使用传统卷积算子即可实现更好的参数利用率。

对于 PGI 而言,它的适用性很强,可用于从轻型到大型的各种模型。我们可以用它来获取完整的信息,从而使从头开始训练的模型能够比使用大型数据集预训练的 SOTA 模型获得更好的结果。下图 1 展示了一些比较结果。

51c视觉~YOLO~合集8_视觉_11

对于新发布的 YOLOv9,曾参与开发了 YOLOv7、YOLOv4、Scaled-YOLOv4 和 DPT 的 Alexey Bochkovskiy 给予了高度评价,表示 YOLOv9 优于任何基于卷积或 transformer 的目标检测器。

51c视觉~YOLO~合集8_YOLO_12

还有网友表示,YOLOv9 看起来就是新的 SOTA 实时目标检测器,他自己的自定义训练教程也在路上了。

更有「勤劳」的网友已经为 YOLOv9 模型添加了 pip 支持。

接下来看 YOLOv9 的详细信息。

问题陈述

通常,人们将深度神经网络收敛困难问题归因于梯度消失或梯度饱和等因素,这些现象确实存在于传统的深度神经网络中。然而,现代深度神经网络通过设计各种归一化和激活函数,已经从根本上解决了上述问题。不过即便如此,深度神经网络中仍然存在着收敛速度慢或收敛效果差的问题。那么这个问题的本质到底是什么?

研究者通过对信息瓶颈的深入分析,推断出了该问题的根本原因:梯度最初从非常深层的网络传递出来后不久,就丢失了许多达成目标所需的信息。为了验证这一推断,研究者们对具有初始权重的不同架构的深度网络进行前馈处理。图 2 对此进行了可视化说明。显然,PlainNet 在深层丢失了很多进行对象检测所需的重要信息。至于 ResNet、CSPNet 和 GELAN 能够保留的重要信息比例,确实与训练后能够获得的准确性正相关。研究者进一步设计了基于可逆网络的方法来解决上述问题的原因。

方法介绍

可编程梯度信息(PGI)

该研究提出了一种新的辅助监督框架:可编程梯度信息(Programmable Gradient Information,PGI),如图 3(d)所示。 

PGI 主要包括三个部分,即(1)主分支,(2)辅助可逆分支,(3)多级辅助信息。

  • PGI 的推理过程仅使用了主分支,因此不需要额外的推理成本;
  • 辅助可逆分支是为了处理神经网络加深带来的问题, 网络加深会造成信息瓶颈,导致损失函数无法生成可靠的梯度;
  • 多级辅助信息旨在处理深度监督带来的误差累积问题,特别是多个预测分支的架构和轻量级模型。 

GELAN 网络

此外,该研究还提出了一个新的网络架构 GELAN(如下图所示),具体而言,研究者把 CSPNet、 ELAN 这两种神经网络架构结合起来,从而设计出兼顾轻量级、推理速度和准确性的通用高效层聚合网络(generalized efficient layer aggregation network ,GELAN)。研究者将最初仅使用卷积层堆叠的 ELAN 的功能泛化到可以使用任何计算块的新架构。

51c视觉~YOLO~合集8_YOLO_13

实验结果

为了评估 YOLOv9 的性能,该研究首先将 YOLOv9 与其他从头开始训练的实时目标检测器进行了全面的比较,结果如下表 1 所示。

51c视觉~YOLO~合集8_YOLO_14

该研究还将 ImageNet 预训练模型纳入比较中,结果如下图 5 所示。值得注意的是,使用传统卷积的 YOLOv9 在参数利用率上甚至比使用深度卷积的 YOLO MS 还要好。

51c视觉~YOLO~合集8_视觉_15

消融实验

为了探究 YOLOv9 中各个组件的作用,该研究进行了一系列消融实验。

该研究首先对 GELAN 的计算块进行消融实验。如下表 2 所示,该研究发现用不同的计算块替换 ELAN 中的卷积层后,系统可以保持良好的性能。

51c视觉~YOLO~合集8_YOLO_16

然后该研究又在不同尺寸的 GELAN 上针对 ELAN 块深度和 CSP 块深度进行了消融实验,结果如下表 3 所示。

51c视觉~YOLO~合集8_YOLO_17

在 PGI 方面,研究者分别在主干网络和 neck 上对辅助可逆分支和多级辅助信息进行了消融研究。表 4 列出了所有实验的结果。从表 4 中可以看出,PFH 只对深度模型有效,而本文提出的 PGI 在不同组合下都能提高精度。

51c视觉~YOLO~合集8_YOLO_18

研究者进一步在不同大小的模型上实现了 PGI 和深度监控,并对结果进行了比较,结果如表 5 所示。

51c视觉~YOLO~合集8_视觉_19

图 6 显示了从基准 YOLOv7 到 YOLOv9- E 逐步增加组件的结果。

51c视觉~YOLO~合集8_视觉_20

可视化

研究者探讨了信息瓶颈问题,并将其进行了可视化处理,图 6 显示了在不同架构下使用随机初始权重作为前馈获得的特征图的可视化结果。

51c视觉~YOLO~合集8_YOLO_21

图 7 说明了 PGI 能否在训练过程中提供更可靠的梯度,从而使用于更新的参数能够有效捕捉输入数据与目标之间的关系。

51c视觉~YOLO~合集8_YOLO_22

#做训练

如何安装YOLOv9

YOLOv9被打包为一系列脚本,您可以使用这些脚本进行工作。在撰写本指南时,没有官方的Python包或包装器可供您与模型进行交互。

要使用YOLOv9,您需要下载项目存储库。然后,您可以运行训练作业或从现有的COCO检查点进行推理。

本教程假定您正在使用Google Colab。如果您在笔记本环境之外的本地机器上工作,请根据需要调整命令。

YOLOv9中存在一个错误,阻止您对图像进行推理,但Roboflow团队正在维护一个非官方的分支,其中包含一个补丁,直到修复发布。要从我们的补丁分支安装YOLOv9,请运行以下命令:

git clone https://github.com/SkalskiP/yolov9.git

cd yolov9

pip3 install -r requirements.txt -q

让我们设置一个HOME目录来工作:

import os

HOME = os.getcwd()

print(HOME)

接下来,需要下载模型权重。目前只有v9 - C和v9 - E权重可用。可以使用以下命令进行下载:

!mkdir -p {HOME}/weights

!wget -P {HOME}/weights -q https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-c.pt

!wget -P {HOME}/weights -q https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-e.pt

!wget -P {HOME}/weights -q https://github.com/WongKinYiu/yolov9/releases/download/v0.1/gelan-c.pt

!wget -P {HOME}/weights -q https://github.com/WongKinYiu/yolov9/releases/download/v0.1/gelan-e.pt

现在可以使用项目库中的脚本在YOLOv9模型上运行推理和训练。

在YOLOv9模型上推理

在示例图像上使用v9 - C COCO检查点进行推理。创建一个新的数据目录,并将示例图像下载到您的笔记本中。你可以用我们的狗图片作为例子,也可以用你想要的任何其他图片。

!mkdir -p {HOME}/data

!wget -P {HOME}/data -q https://media.roboflow.com/notebooks/examples/dog.jpeg

SOURCE_IMAGE_PATH = f"{HOME}/dog.jpeg"

我们现在可以在我们的图像上进行推理:

!python detect.py --weights {HOME}/weights/gelan-c.pt --conf 0.1 --source {HOME}/data/dog.jpeg --device 0

Image(filename=f"{HOME}/yolov9/runs/detect/exp/dog.jpeg", width=600)

 

51c视觉~YOLO~合集8_YOLO_23

 我们的模型能够成功地识别出图像中的人、狗和汽车。有鉴于此,该模型错误地将背带识别为手提包,并且未能检测到背包。

让我们试试参数最多的v9 - E模型:

!python detect.py --weights {HOME}/weights/yolov9-e.pt --conf 0.1 --source {HOME}/data/dog.jpeg --device 0

Image(filename=f"{HOME}/yolov9/runs/detect/exp2/dog.jpeg", width=600)

51c视觉~YOLO~合集8_视觉_24

该模型能够成功识别人、狗、汽车和背包。

如何训练YOLOv9模型

您可以使用YOLOv9项目目录中的train.py文件来训练YOLOv9模型。

第1步:下载数据集

要开始训练模型,您将需要一个数据集。对于本指南,我们将使用一个关于足球运动员的数据集。生成的模型将能够在场地上识别足球运动员。

如果您没有数据集,请查看Roboflow Universe,这是一个共享了超过200,000个计算机视觉数据集的社区。您可以找到涵盖从书脊到足球运动员再到太阳能电池板的数据集。

运行以下代码来下载我们在本指南中使用的数据集:

%cd {HOME}/yolov9

roboflow.login()

rf = roboflow.Roboflow()

project = rf.workspace("roboflow-jvuqo").project("football-players-detection-3zvbc")

dataset = project.version(1).download("yolov7")

当您运行此代码时,将会要求您通过Roboflow进行身份验证。请跟随在您的终端中出现的链接进行验证。如果您没有账户,将被带到一个页面,您可以在该页面创建一个账户。然后,再次点击链接以使用Python包进行身份验证。

此代码以YOLOv7格式下载数据集,该格式与YOLOv9模型兼容。

您可以使用任何按照YOLOv7格式格式化的数据集来进行此操作。

第2步:使用YOLOv9 Python脚本训练模型

让我们为我们的数据集训练一个模型,训练20个epochs。我们将使用GELAN-C架构进行此操作,该架构是YOLOv9 GitHub仓库发布的两种架构之一。GELAN-C的训练速度快。GELAN-C的推理时间也很快。

您可以使用以下代码进行此操作:

%cd {HOME}/yolov9

!python train.py \

--batch 16 --epochs 20 --img 640 --device 0 --min-items 0 --close-mosaic 15 \

--data {dataset.location}/data.yaml \

--weights {HOME}/weights/gelan-c.pt \

--cfg models/detect/gelan-c.yaml \

--hyp hyp.scratch-high.yaml

你的模型将开始训练。模型训练时,将看到每个epoch的训练指标。一旦模型完成训练,就可以使用YOLOv9生成的图来评估训练结果。

运行下面的代码来查看你的训练图:

Image(filename=f"{HOME}/yolov9/runs/train/exp/results.png", width=1000)

51c视觉~YOLO~合集8_YOLO_25

运行下面的代码来查看你的混淆矩阵:

51c视觉~YOLO~合集8_YOLO_26

运行以下代码,查看您的模型在验证集中的一批图像上的结果:

Image(filename=f"{HOME}/yolov9/runs/train/exp/val_batch0_pred.jpg",

width=1000)

51c视觉~YOLO~合集8_视觉_27

第3步:在自定义模型上运行推理

既然我们有了一个训练好的模型,我们就可以进行推理。为此,我们可以使用YOLOv9库中的detection . py文件。

运行以下代码对验证集中的所有图像进行推理:

!python detect.py \

--img 1280 --conf 0.1 --device 0 \

--weights {HOME}/yolov9/runs/train/exp/weights/best.pt \

--source {dataset.location}/valid/images

import glob

from IPython.display import Image, display

for image_path in glob.glob(f'{HOME}/yolov9/runs/detect/exp4/*.jpg')[:3]:

display(Image(filename=image_path, width=600))

print("\n")

我们在大小为640的图像上训练了我们的模型,这使得我们可以用较少的计算资源来训练模型。在推理过程中,我们将图像尺寸增加到1280,使得我们可以从我们的模型中得到更准确的结果。

下面是我们模型结果的三个例子:

51c视觉~YOLO~合集8_视觉_28

 我们的模型成功地识别了球员、裁判员和守门员。

结论

YOLOv9是由Chien-Yao Wang, I-Hau Yeh, and Hong-Yuan Mark Liao发布的一种新的计算机视觉模型架构。可以使用YOLOv9架构训练目标检测模型。

在本指南中,我们演示了如何在自定义数据集上运行推理和训练YOLOv9模型。我们克隆了YOLOv9项目代码,下载了模型权重,然后使用默认的COCO权重进行推理。然后,我们使用足球运动员检测数据集训练了一个微调的模型。我们回顾了训练图和混淆矩阵,然后在来自验证集的图像上测试了模型。

# 使用YOLOv9分割图像中的对象

为什么 YOLOv9 可以改变细分游戏规则

    基于YOLOv8 在分割方面的成功,YOLOv9 提供了几个潜在的优势,可以使其成为您下一个项目的首选:

  • 推测速度提升:YOLO 模型以其实时功能而闻名。由于 YOLOv9 预计比 YOLOv8 更快,因此它可能非常适合需要瞬间分割的应用,例如自动驾驶车辆或机器人对象操纵。
  • 潜在的准确性提升:YOLO 开发人员不断努力提高检测和分类的准确性。如果 YOLOv9 效仿,它可以转化为更精确的分割掩模,从而在医学成像或场景理解等任务中获得更好的性能。
  • 效率增强:YOLOv9 中的新架构改进可能会提高处理图像进行分割的效率。这可能有利于计算资源有限的用户或处理大量图像处理任务的用户。
  • 新分割技术的潜力:YOLOv9 核心架构的进步可能为全新的分割技术打开大门。我们可能会看到处理复杂对象形状、遮挡的创新,甚至探索超越像素标记的新分割形式。

如何使用 YOLOv9 处理图像

第 1 步:安装必要的库

pip install opencv-python ultralytics numpy

第 2 步:导入库

from ultralytics import YOLO
import random
import cv2
import numpy as np

第 3 步:选择模型

model = YOLO("yolov9e-seg.pt")

    在下面网站中,您可以比较不同的模型并权衡各自的优缺点。在本例中,我们选择了 yolov9e-seg.pt。

https://docs.ultralytics.com/models/yolov9/

第 4 步:使用 YOLOv9 分割图像中的对象

img = cv2.imread("YourImagePath")

# if you want all classes
yolo_classes = list(model.names.values())
classes_ids = [yolo_classes.index(clas) for clas in yolo_classes]

conf = 0.2

results = model.predict(img, conf=conf)
colors = [random.choices(range(256), k=3) for _ in classes_ids]
print(results)
for result in results:
    for mask, box in zip(result.masks.xy, result.boxes):
        points = np.int32([mask])
        # cv2.polylines(img, points, True, (255, 0, 0), 1)
        color_number = classes_ids.index(int(box.cls[0]))
        cv2.fillPoly(img, points, colors[color_number])

1. 加载图片:

  • img = cv2.imread("YourImagePath")使用OpenCV的函数从指定路径读取图像cv2.imread()。

    2. 预测准备:

  • yolo_classes = list(model.names.values())创建 YOLOv9 模型识别的类名列表。
  • classes_ids = [yolo_classes.index(clas) for clas in yolo_classes]创建与这些名称相对应的类 ID 列表。
  • conf = 0.2设置对象检测的置信度阈值。仅考虑置信度分数高于此阈值的预测。

    3. 运行模型预测:

  • results = model.predict(img, conf=conf)调用predict()YOLOv9模型的方法对加载的图像进行预测。结果包括检测到的对象、其边界框、掩模(多边形轮廓)、置信度分数和类别预测。
  • colors = [random.choices(range(256), k=3) for _ in classes_ids]生成一个随机颜色列表,每个类别一个,用于视觉表示。

    4. 处理结果和可视化掩模:

    该for循环迭代结果中每个检测到的对象:

  • mask, box = zip(result.masks.xy, result.boxes)解包对象的掩模坐标和边界框信息。
  • points = np.int32([mask])将掩模坐标(可能采用浮点格式)转换为整数,以便使用 OpenCV 在图像上绘图。
  • color_number = classes_ids.index(int(box.cls[0]))根据对象的预测类别确定视觉表示的颜色索引。
  • cv2.fillPoly(img, points, colors[color_number])用原始图像上相应的颜色填充掩模坐标定义的多边形,有效地创建对象的视觉分割。

    笔记:

  • 如果未注释,注释掉的线# cv2.polylines(img, points, True, (255, 0, 0), 1)将在蒙版周围绘制轮廓,而不是填充它们。

第 5 步:保存并绘制结果图像

cv2.imshow("Image", img)
cv2.waitKey(0)

cv2.imwrite("YourSavePath", img)

完整代码:

from ultralytics import YOLO
import random
import cv2
import numpy as np

model = YOLO("yolov9e-seg.pt")

img = cv2.imread("YourImagePath")

# if you want all classes
yolo_classes = list(model.names.values())
classes_ids = [yolo_classes.index(clas) for clas in yolo_classes]

conf = 0.2

results = model.predict(img, conf=conf)
colors = [random.choices(range(256), k=3) for _ in classes_ids]
print(results)
for result in results:
    for mask, box in zip(result.masks.xy, result.boxes):
        points = np.int32([mask])
        # cv2.polylines(img, points, True, (255, 0, 0), 1)
        color_number = classes_ids.index(int(box.cls[0]))
        cv2.fillPoly(img, points, colors[color_number])

cv2.imshow("Image", img)
cv2.waitKey(0)

cv2.imwrite("YourSavePath", img)

51c视觉~YOLO~合集8_视觉_29

参考

YOLOv9论文:

https://arxiv.org/abs/2402.13616

YOLOv9 Github地址:

https://github.com/WongKinYiu/yolov9

 








2、Yolo11


2.1、区域内目标跟踪

 计算机视觉领域正在迅速发展,尤其是随着生成式人工智能的出现,它正在推动该领域的进一步发展。当我们考虑检测物体时,通常首先想到的是物体检测。但为了获得更好的结果,应该考虑使用物体跟踪。这种方法不仅可以检测物体,还可以随着时间的推移跟踪它们,为每个物体分配唯一的 ID,以获得更准确、更全面的结果。

51c视觉~YOLO~合集8_YOLO_30

51c视觉~YOLO~合集8_视觉_31

什么是 TrackZone?

    顾名思义,区域内跟踪对象是一种 Ultralytics解决方案,旨在关注帧内的特定区域,以优化跟踪过程。

    这种方法仅处理帧的一部分而不是分析整个帧,从而显著提高了跟踪速度。

51c视觉~YOLO~合集8_视觉_32

    💡您可以将Ultralytics支持的任何模型与 TrackZone 一起使用。

TrackZone 的应用

    TrackZone 可在各个行业中广泛应用,实现高效的对象跟踪:

  • 智能交通管理:它可以跟踪车辆并预测停车位或公交车站等特定区域内的交通拥堵情况,而不是监控整条道路。
  • 零售和库存管理:它可以监控特定区域(例如零售货架),以有效跟踪商品。由于零售商场摄像头通常不是专门为货架监控而安装的,因此您可以只关注所需区域,而不是分析整个画面。
  • 生产线监控:在生产线场景中,它可用于仅监控活跃的生产区域而不是整个框架,确保在最需要的地方进行精确的跟踪。

代码演示

    让我们深入研究代码!Ultralytics 在 Python 中提供 TrackZone 功能,您也可以通过 CLI 使用单行命令轻松执行它。

    对于每个解决方案,您都需要配置视频路径,并且(可选)还要配置模型文件。这同样适用于 TrackZone:只需指定视频路径,如果需要,您还可以包含模型文件路径。但是,这是可选的,因为yolo11n.pt将使用默认模型进行处理。

import cv2


from ultralytics import solutions


cap = cv2.VideoCapture("path/to/video/file.mp4")
assert cap.isOpened(), "Error reading video file"
w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH,
                                       cv2.CAP_PROP_FRAME_HEIGHT,
                                       cv2.CAP_PROP_FPS))


# Define region points
region_points = [(150, 150), (1130, 150), (1130, 570), (150, 570)]
# Video writer
video_writer = cv2.VideoWriter("object_counting_output.avi",
                              cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))


# Init TrackZone (Object Tracking in Zones, not complete frame)
trackzone = solutions.TrackZone(
    show=True,  # Display the output
    reginotallow=region_points,  # Pass region points
    model="yolo11n.pt",  # i.e. YOLOv8, YOLOv9, YOLOv10, YOLO11
    # line_width=2,  # line width for bounding boxes and text display
    # classes=[0, 2],  # count specific classes i.e. person and car.
)


# Process video
while cap.isOpened():
    success, im0 = cap.read()
    if not success:
        break
    im0 = trackzone.trackzone(im0)
    video_writer.write(im0)


cap.release()
video_writer.release()
cv2.destroyAllWindows()

    CLI(命令行界面)与 Python 类似,您可以使用 、 等参数配置 TrackZone 解决方案region。可以在参数source部分找到可用参数的完整列表。

# Run a trackzone example
yolo solutions trackzone show=True


# Pass a source video
yolo solutions trackzone show=True source="path/to/video/file.mp4"


# Pass region coordinates
yolo solutions trackzone show=True \
reginotallow=[(150, 150), (1130, 150), (1130, 570), (150, 570)]

51c视觉~YOLO~合集8_YOLO_33

TrackZone 的优势

    与传统的对象跟踪模块相比,TrackZone 具有多种优势,其中最重要的优势如下所述。

  • 速度:通过仅处理帧的特定部分,与需要跟踪整个帧内对象的传统对象跟踪相比,它可以提供更快的速度。
  • 准确度:TrackZone 通过聚焦画面的裁剪和放大区域来提高准确度。这使模型能够更有效地检测和跟踪指定区域内的物体。
  • 边缘设备兼容性:TrackZone 可以在NVIDIA Jetson等低功耗边缘设备上表现良好,这使其非常适合物联网系统。





2.2、自定义数据集扑克牌识别

GPU环境配置

    安装CUDA和cudnn,配置环境变量。检查是否安装成功:

!nvidia-smi

51c视觉~YOLO~合集8_视觉_34

YOLOv11训练

 1. 安装必要依赖项并检查。

pip install ultralytics
import ultralytics
ultralytics.checks()

51c视觉~YOLO~合集8_视觉_35

    2. 下载数据集。

    数据集下载地址:

https://www.google.com/url?q=https%3A%2F%2Funiverse.roboflow.com%2F

    安装roboflow:

pip install roboflow

    下载数据集:

from roboflow import Roboflow
rf = Roboflow(api_key="UynPSQgEXONS23dKJYtP")
project = rf.workspace("roboflow-jvuqo").project("poker-cards-fmjio")
version = project.version(4)
dataset = version.download("yolov8")

51c视觉~YOLO~合集8_视觉_36

 3. 模型训练。

yolo task=detect mode=train model=yolo11s.pt data={dataset.location}/data.yaml epochs=10 imgsz=640 plots=True
Downloading https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11s.pt to 'yolo11s.pt'...
100% 18.4M/18.4M [00:00<00:00, 147MB/s] 
WARNING ⚠️ yolo11s.pt appears to require 'dill', which is not in Ultralytics requirements.
AutoInstall will run now for 'dill' but this feature will be removed in the future.
Recommend fixes are to train a new model using the latest 'ultralytics' package or to run a command with an official Ultralytics model, i.e. 'yolo predict model=yolov8n.pt'
requirements: Ultralytics requirement ['dill'] not found, attempting AutoUpdate...
Collecting dill
  Downloading dill-0.3.9-py3-none-any.whl.metadata (10 kB)
Downloading dill-0.3.9-py3-none-any.whl (119 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 119.4/119.4 kB 4.0 MB/s eta 0:00:00
Installing collected packages: dill
Successfully installed dill-0.3.9


requirements: AutoUpdate success ✅ 2.4s, installed 1 package: ['dill']
requirements: ⚠️ Restart runtime or rerun command for updates to take effect


Ultralytics 8.3.1 🚀 Python-3.10.12 torch-2.4.1+cu121 CUDA:0 (Tesla T4, 15102MiB)
engine/trainer: task=detect, mode=train, model=yolo11s.pt, data=/content/datasets/poker-cards-4/data.yaml, epochs=10, time=None, patience=100, batch=16, imgsz=640, save=True, save_period=-1, cache=False, device=None, workers=8, project=None, name=train, exist_ok=False, pretrained=True, optimizer=auto, verbose=True, seed=0, deterministic=True, single_cls=False, rect=False, cos_lr=False, close_mosaic=10, resume=False, amp=True, fractinotallow=1.0, profile=False, freeze=None, multi_scale=False, overlap_mask=True, mask_ratio=4, dropout=0.0, val=True, split=val, save_jsnotallow=False, save_hybrid=False, cnotallow=None, iou=0.7, max_det=300, half=False, dnn=False, plots=True, source=None, vid_stride=1, stream_buffer=False, visualize=False, augment=False, agnostic_nms=False, classes=None, retina_masks=False, embed=None, show=False, save_frames=False, save_txt=False, save_cnotallow=False, save_crop=False, show_labels=True, show_cnotallow=True, show_boxes=True, line_width=None, format=torchscript, keras=False, optimize=False, int8=False, dynamic=False, simplify=True, opset=None, workspace=4, nms=False, lr0=0.01, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3.0, warmup_momentum=0.8, warmup_bias_lr=0.1, box=7.5, cls=0.5, dfl=1.5, pose=12.0, kobj=1.0, label_smoothing=0.0, nbs=64, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=0.0, translate=0.1, scale=0.5, shear=0.0, perspective=0.0, flipud=0.0, fliplr=0.5, bgr=0.0, mosaic=1.0, mixup=0.0, copy_paste=0.0, copy_paste_mode=flip, auto_augment=randaugment, erasing=0.4, crop_fractinotallow=1.0, cfg=None, tracker=botsort.yaml, save_dir=runs/detect/train
Downloading https://ultralytics.com/assets/Arial.ttf to '/root/.config/Ultralytics/Arial.ttf'...
100% 755k/755k [00:00<00:00, 13.6MB/s]
Overriding model.yaml nc=80 with nc=52


                   from  n    params  module                                       arguments                     
  0                  -1  1       928  ultralytics.nn.modules.conv.Conv             [3, 32, 3, 2]                 
  1                  -1  1     18560  ultralytics.nn.modules.conv.Conv             [32, 64, 3, 2]                
  2                  -1  1     26080  ultralytics.nn.modules.block.C3k2            [64, 128, 1, False, 0.25]     
  3                  -1  1    147712  ultralytics.nn.modules.conv.Conv             [128, 128, 3, 2]              
  4                  -1  1    103360  ultralytics.nn.modules.block.C3k2            [128, 256, 1, False, 0.25]    
  5                  -1  1    590336  ultralytics.nn.modules.conv.Conv             [256, 256, 3, 2]              
  6                  -1  1    346112  ultralytics.nn.modules.block.C3k2            [256, 256, 1, True]           
  7                  -1  1   1180672  ultralytics.nn.modules.conv.Conv             [256, 512, 3, 2]              
  8                  -1  1   1380352  ultralytics.nn.modules.block.C3k2            [512, 512, 1, True]           
  9                  -1  1    656896  ultralytics.nn.modules.block.SPPF            [512, 512, 5]                 
 10                  -1  1    990976  ultralytics.nn.modules.block.C2PSA           [512, 512, 1]                 
 11                  -1  1         0  torch.nn.modules.upsampling.Upsample         [None, 2, 'nearest']          
 12             [-1, 6]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 13                  -1  1    443776  ultralytics.nn.modules.block.C3k2            [768, 256, 1, False]          
 14                  -1  1         0  torch.nn.modules.upsampling.Upsample         [None, 2, 'nearest']          
 15             [-1, 4]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 16                  -1  1    127680  ultralytics.nn.modules.block.C3k2            [512, 128, 1, False]          
 17                  -1  1    147712  ultralytics.nn.modules.conv.Conv             [128, 128, 3, 2]              
 18            [-1, 13]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 19                  -1  1    345472  ultralytics.nn.modules.block.C3k2            [384, 256, 1, False]          
 20                  -1  1    590336  ultralytics.nn.modules.conv.Conv             [256, 256, 3, 2]              
 21            [-1, 10]  1         0  ultralytics.nn.modules.conv.Concat           [1]                           
 22                  -1  1   1511424  ultralytics.nn.modules.block.C3k2            [768, 512, 1, True]           
 23        [16, 19, 22]  1    839532  ultralytics.nn.modules.head.Detect           [52, [128, 256, 512]]         
YOLO11s summary: 319 layers, 9,447,916 parameters, 9,447,900 gradients, 21.7 GFLOPs


Transferred 493/499 items from pretrained weights
TensorBoard: Start with 'tensorboard --logdir runs/detect/train', view at http://localhost:6006/
Freezing layer 'model.23.dfl.conv.weight'
AMP: running Automatic Mixed Precision (AMP) checks with YOLO11n...
Downloading https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11n.pt to 'yolo11n.pt'...
100% 5.36M/5.36M [00:00<00:00, 66.1MB/s]
AMP: checks failed ❌. Anomalies were detected with AMP on your system that may lead to NaN losses or zero-mAP results, so AMP will be disabled during training.
train: Scanning /content/datasets/poker-cards-4/train/labels... 811 images, 0 backgrounds, 0 corrupt: 100% 811/811 [00:00<00:00, 2011.74it/s]
train: New cache created: /content/datasets/poker-cards-4/train/labels.cache
/usr/local/lib/python3.10/dist-packages/albumentations/__init__.py:13: UserWarning: A new version of Albumentations is available: 1.4.16 (you have 1.4.15). Upgrade using: pip install -U albumentations. To disable automatic update checks, set the environment variable NO_ALBUMENTATIONS_UPDATE to 1.
  check_for_updates()
albumentations: Blur(p=0.01, blur_limit=(3, 7)), MedianBlur(p=0.01, blur_limit=(3, 7)), ToGray(p=0.01, num_output_channels=3, method='weighted_average'), CLAHE(p=0.01, clip_limit=(1, 4.0), tile_grid_size=(8, 8))
val: Scanning /content/datasets/poker-cards-4/valid/labels... 44 images, 0 backgrounds, 0 corrupt: 100% 44/44 [00:00<00:00, 1103.19it/s]
val: New cache created: /content/datasets/poker-cards-4/valid/labels.cache
Plotting labels to runs/detect/train/labels.jpg... 
optimizer: 'optimizer=auto' found, ignoring 'lr0=0.01' and 'momentum=0.937' and determining best 'optimizer', 'lr0' and 'momentum' automatically... 
optimizer: AdamW(lr=0.000179, momentum=0.9) with parameter groups 81 weight(decay=0.0), 88 weight(decay=0.0005), 87 bias(decay=0.0)
TensorBoard: model graph visualization added ✅
Image sizes 640 train, 640 val
Using 2 dataloader workers
Logging results to runs/detect/train
Starting training for 10 epochs...
Closing dataloader mosaic
albumentations: Blur(p=0.01, blur_limit=(3, 7)), MedianBlur(p=0.01, blur_limit=(3, 7)), ToGray(p=0.01, num_output_channels=3, method='weighted_average'), CLAHE(p=0.01, clip_limit=(1, 4.0), tile_grid_size=(8, 8))


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       1/10      8.43G     0.7088      4.804      1.116         47        640: 100% 51/51 [00:32<00:00,  1.57it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:02<00:00,  1.47s/it]
                   all         44        197      0.354       0.32      0.137       0.12


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       2/10      8.23G     0.5137      2.753      0.967         53        640: 100% 51/51 [00:30<00:00,  1.69it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  2.02it/s]
                   all         44        197      0.567      0.504      0.539      0.474


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       3/10      8.22G     0.4965      1.702     0.9366         49        640: 100% 51/51 [00:30<00:00,  1.67it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  2.46it/s]
                   all         44        197        0.7      0.734      0.792       0.71


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       4/10      8.22G     0.4684      1.183     0.9193         52        640: 100% 51/51 [00:31<00:00,  1.64it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  2.56it/s]
                   all         44        197      0.738      0.875      0.895       0.81


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       5/10      8.23G     0.4554     0.8986     0.9102         48        640: 100% 51/51 [00:30<00:00,  1.67it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  2.46it/s]
                   all         44        197      0.851      0.858      0.935      0.845


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       6/10      8.21G     0.4378     0.6888     0.8992         50        640: 100% 51/51 [00:30<00:00,  1.66it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  2.59it/s]
                   all         44        197      0.909      0.894      0.969      0.879


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       7/10      8.22G     0.4268     0.5645     0.8986         47        640: 100% 51/51 [00:30<00:00,  1.65it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  2.70it/s]
                   all         44        197      0.928      0.941      0.979      0.889


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       8/10      8.22G     0.4144     0.4976     0.8868         50        640: 100% 51/51 [00:31<00:00,  1.64it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  3.05it/s]
                   all         44        197      0.951      0.941      0.985      0.893


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
       9/10      8.23G     0.4086      0.455     0.8818         48        640: 100% 51/51 [00:31<00:00,  1.64it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  3.19it/s]
                   all         44        197      0.958      0.942      0.984      0.894


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
      10/10      8.21G     0.3975     0.4244     0.8819         52        640: 100% 51/51 [00:30<00:00,  1.65it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:00<00:00,  3.12it/s]
                   all         44        197      0.955      0.947      0.985      0.899


10 epochs completed in 0.094 hours.
Optimizer stripped from runs/detect/train/weights/last.pt, 19.2MB
Optimizer stripped from runs/detect/train/weights/best.pt, 19.2MB


Validating runs/detect/train/weights/best.pt...
Ultralytics 8.3.1 🚀 Python-3.10.12 torch-2.4.1+cu121 CUDA:0 (Tesla T4, 15102MiB)
YOLO11s summary (fused): 238 layers, 9,432,924 parameters, 0 gradients, 21.4 GFLOPs
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 2/2 [00:01<00:00,  1.95it/s]
                   all         44        197      0.955      0.946      0.985      0.898
           10 of clubs          3          3      0.963          1      0.995       0.94
        10 of diamonds          7          7      0.981          1      0.995      0.959
          10 of hearts          7          7      0.996          1      0.995      0.877
          10 of spades          4          4       0.99          1      0.995      0.692
            2 of clubs          2          2      0.936          1      0.995      0.995
         2 of diamonds          2          2      0.951          1      0.995      0.995
           2 of hearts          1          1      0.914          1      0.995      0.895
           2 of spades          4          4          1       0.69      0.995      0.908
            3 of clubs          2          2      0.818          1      0.995      0.995
         3 of diamonds          2          2      0.928          1      0.995      0.895
           3 of hearts          1          1      0.917          1      0.995      0.895
           3 of spades          4          4       0.97       0.75      0.758      0.633
            4 of clubs          2          2      0.935          1      0.995      0.995
         4 of diamonds          2          2      0.969          1      0.995      0.995
           4 of hearts          1          1      0.925          1      0.995      0.895
           4 of spades          4          4          1       0.79      0.995      0.765
            5 of clubs          8          8          1       0.74      0.879      0.823
         5 of diamonds          3          3          1      0.446      0.995      0.941
           5 of hearts          3          3      0.969          1      0.995      0.863
           5 of spades          1          1      0.968          1      0.995      0.995
            6 of clubs          7          7      0.977          1      0.995      0.977
         6 of diamonds          3          3      0.624          1      0.995      0.929
           6 of hearts          3          3      0.965          1      0.995      0.895
           6 of spades          1          1      0.926          1      0.995      0.895
            7 of clubs          7          7      0.984          1      0.995      0.933
         7 of diamonds          3          3      0.982          1      0.995      0.995
           7 of hearts          3          3      0.974          1      0.995      0.809
           7 of spades          1          1      0.916          1      0.995      0.796
            8 of clubs          7          7          1      0.871      0.995      0.995
         8 of diamonds          3          3      0.898      0.333       0.83      0.797
           8 of hearts          3          3      0.972          1      0.995      0.929
           8 of spades          1          1      0.917          1      0.995      0.995
            9 of clubs          3          3      0.965          1      0.995      0.951
         9 of diamonds          7          7      0.981          1      0.995      0.952
           9 of hearts          7          7      0.982          1      0.995      0.863
           9 of spades          4          4      0.999          1      0.995      0.822
          ace of clubs          2          2      0.941          1      0.995      0.995
       ace of diamonds          2          2      0.964          1      0.995      0.796
         ace of hearts          1          1      0.922          1      0.995      0.895
         ace of spades          4          4          1      0.787      0.995      0.863
        jack  of clubs          3          3      0.956          1      0.995      0.995
      jack of diamonds          7          7          1      0.988      0.995      0.995
        jack of hearts          6          6      0.846          1      0.972      0.827
        jack of spades          4          4      0.973          1      0.995      0.487
         king of clubs          3          3      0.968          1      0.995      0.995
      king of diamonds          7          7      0.986          1      0.995      0.982
        king of hearts          7          7      0.985          1      0.995      0.906
        king of spades          4          4      0.975          1      0.995      0.846
        queen of clubs          3          3          1      0.819      0.995      0.963
     queen of diamonds          7          7       0.98          1      0.995      0.995
       queen of hearts          7          7      0.986          1      0.995       0.92
       queen of spades          4          4      0.978          1      0.995      0.773
Speed: 0.2ms preprocess, 9.6ms inference, 0.0ms loss, 1.9ms postprocess per image
Results saved to runs/detect/train
💡 Learn more at https://docs.ultralytics.com/modes/train

51c视觉~YOLO~合集8_YOLO_37

from IPython.display import Image as IPyImage


IPyImage(filename=f'{HOME}/runs/detect/train/confusion_matrix.png', width=600)


51c视觉~YOLO~合集8_视觉_38

from IPython.display import Image as IPyImage


IPyImage(filename=f'{HOME}/runs/detect/train/results.png', width=600)

51c视觉~YOLO~合集8_视觉_39

from IPython.display import Image as IPyImage


IPyImage(filename=f'{HOME}/runs/detect/train/val_batch0_pred.jpg', width=600)

51c视觉~YOLO~合集8_视觉_40

    4. 模型验证微调

yolo task=detect mode=val model={HOME}/runs/detect/train/weights/best.pt data={dataset.location}/data.yaml
Ultralytics 8.3.1 🚀 Python-3.10.12 torch-2.4.1+cu121 CUDA:0 (Tesla T4, 15102MiB)
YOLO11s summary (fused): 238 layers, 9,432,924 parameters, 0 gradients, 21.4 GFLOPs
val: Scanning /content/datasets/poker-cards-4/valid/labels.cache... 44 images, 0 backgrounds, 0 corrupt: 100% 44/44 [00:00<?, ?it/s]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100% 3/3 [00:03<00:00,  1.13s/it]
                   all         44        197      0.955      0.946      0.985      0.898
           10 of clubs          3          3      0.963          1      0.995       0.94
        10 of diamonds          7          7      0.981          1      0.995      0.959
          10 of hearts          7          7      0.996          1      0.995      0.877
          10 of spades          4          4       0.99          1      0.995      0.692
            2 of clubs          2          2      0.936          1      0.995      0.995
         2 of diamonds          2          2      0.951          1      0.995      0.995
           2 of hearts          1          1      0.914          1      0.995      0.895
           2 of spades          4          4          1       0.69      0.995      0.908
            3 of clubs          2          2      0.818          1      0.995      0.995
         3 of diamonds          2          2      0.928          1      0.995      0.895
           3 of hearts          1          1      0.917          1      0.995      0.895
           3 of spades          4          4       0.97       0.75      0.758      0.633
            4 of clubs          2          2      0.935          1      0.995      0.995
         4 of diamonds          2          2      0.969          1      0.995      0.995
           4 of hearts          1          1      0.925          1      0.995      0.895
           4 of spades          4          4          1       0.79      0.995      0.765
            5 of clubs          8          8          1       0.74      0.879      0.823
         5 of diamonds          3          3          1      0.446      0.995      0.941
           5 of hearts          3          3      0.969          1      0.995      0.863
           5 of spades          1          1      0.968          1      0.995      0.995
            6 of clubs          7          7      0.977          1      0.995      0.977
         6 of diamonds          3          3      0.624          1      0.995      0.929
           6 of hearts          3          3      0.965          1      0.995      0.895
           6 of spades          1          1      0.926          1      0.995      0.895
            7 of clubs          7          7      0.984          1      0.995      0.933
         7 of diamonds          3          3      0.982          1      0.995      0.995
           7 of hearts          3          3      0.974          1      0.995      0.809
           7 of spades          1          1      0.916          1      0.995      0.796
            8 of clubs          7          7          1      0.871      0.995      0.995
         8 of diamonds          3          3      0.898      0.333       0.83      0.797
           8 of hearts          3          3      0.972          1      0.995      0.929
           8 of spades          1          1      0.917          1      0.995      0.995
            9 of clubs          3          3      0.965          1      0.995      0.951
         9 of diamonds          7          7      0.981          1      0.995      0.952
           9 of hearts          7          7      0.982          1      0.995      0.863
           9 of spades          4          4      0.999          1      0.995      0.822
          ace of clubs          2          2      0.941          1      0.995      0.995
       ace of diamonds          2          2      0.964          1      0.995      0.796
         ace of hearts          1          1      0.922          1      0.995      0.895
         ace of spades          4          4          1      0.787      0.995      0.863
        jack  of clubs          3          3      0.956          1      0.995      0.995
      jack of diamonds          7          7          1      0.988      0.995      0.995
        jack of hearts          6          6      0.846          1      0.972      0.827
        jack of spades          4          4      0.973          1      0.995      0.487
         king of clubs          3          3      0.968          1      0.995      0.995
      king of diamonds          7          7      0.986          1      0.995      0.982
        king of hearts          7          7      0.985          1      0.995      0.906
        king of spades          4          4      0.975          1      0.995      0.846
        queen of clubs          3          3          1      0.819      0.995      0.963
     queen of diamonds          7          7       0.98          1      0.995      0.995
       queen of hearts          7          7      0.986          1      0.995       0.92
       queen of spades          4          4      0.978          1      0.995      0.773
Speed: 7.9ms preprocess, 16.0ms inference, 0.0ms loss, 27.2ms postprocess per image
Results saved to runs/detect/val
💡 Learn more at https://docs.ultralytics.com/modes/val

    5. 模型推理。

yolo task=detect mode=predict model={HOME}/runs/detect/train/weights/best.pt cnotallow=0.25 source={dataset.location}/test/images save=True
Ultralytics 8.3.1 🚀 Python-3.10.12 torch-2.4.1+cu121 CUDA:0 (Tesla T4, 15102MiB)
YOLO11s summary (fused): 238 layers, 9,432,924 parameters, 0 gradients, 21.4 GFLOPs


image 1/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_134701_jpg.rf.27aa29de9d6012ae05c64b156f7c07b8.jpg: 640x640 1 2 of spades, 1 3 of spades, 1 4 of spades, 1 ace of spades, 15.8ms
image 2/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_135012_jpg.rf.ee7179374d33235528db011cb5418226.jpg: 640x640 1 2 of spades, 1 3 of spades, 1 4 of spades, 1 ace of spades, 15.8ms
image 3/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_135021_jpg.rf.d038afdef1a927103dae268ff392888f.jpg: 640x640 1 2 of spades, 1 3 of spades, 1 4 of spades, 1 ace of spades, 15.8ms
image 4/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_140241_jpg.rf.c44522806b5455bfb03a638aa3ffa896.jpg: 640x640 2 5 of spadess, 1 6 of spades, 2 7 of spadess, 1 8 of spades, 15.7ms
image 5/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_140255_jpg.rf.0d10768652a0f20bea317e96632d3448.jpg: 640x640 1 5 of spades, 1 6 of spades, 1 7 of spades, 2 8 of spadess, 15.7ms
image 6/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_140335_jpg.rf.c3310d8f13f66189440daf8419b1ad9c.jpg: 640x640 1 5 of spades, 1 6 of spades, 1 7 of spades, 1 8 of spades, 15.7ms
image 7/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_140400_jpg.rf.3f21e54bd916b05218202fbf109d8a5f.jpg: 640x640 1 5 of spades, 1 6 of spades, 1 7 of spades, 1 8 of spades, 15.5ms
image 8/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_140723_jpg.rf.826bc2b212c4001c11115ef28f58d142.jpg: 640x640 1 5 of spades, 1 6 of spades, 1 7 of spades, 2 8 of spadess, 13.7ms
image 9/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_141442_jpg.rf.41913768e5d56c57566ee3b45391470d.jpg: 640x640 1 10 of spades, 1 9 of spades, 1 jack of spades, 1 king of spades, 1 queen of spades, 13.8ms
image 10/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_141917_jpg.rf.b3075e4161fe5fa2285d75bb2da3bc7a.jpg: 640x640 1 10 of spades, 1 9 of spades, 1 jack of spades, 1 king of spades, 1 queen of spades, 13.7ms
image 11/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_141940_jpg.rf.e0ca71cfc8dc86a6624c3f90fe6c5e9e.jpg: 640x640 1 10 of spades, 1 9 of spades, 1 jack of spades, 1 king of spades, 1 queen of spades, 13.7ms
image 12/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_142643_jpg.rf.13b0a2a65a9d4b17580b39ed19de5bba.jpg: 640x640 1 2 of hearts, 1 3 of hearts, 1 4 of hearts, 1 ace of hearts, 13.7ms
image 13/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_144500_jpg.rf.14c3cb5eadd6c3d449916f52d9f9381e.jpg: 640x640 1 5 of hearts, 1 6 of hearts, 1 7 of hearts, 1 8 of hearts, 13.7ms
image 14/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_144507_jpg.rf.ab9b95792bbdbe7694910967641fba38.jpg: 640x640 1 5 of hearts, 1 6 of hearts, 1 7 of hearts, 1 8 of hearts, 14.3ms
image 15/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_144511_jpg.rf.40ee049c8f854c558e2ca20f90be3787.jpg: 640x640 1 5 of hearts, 1 6 of hearts, 1 7 of hearts, 1 8 of hearts, 13.7ms
image 16/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_144650_jpg.rf.34b246c8ee646cbb5979a35d68c58901.jpg: 640x640 1 5 of hearts, 1 6 of hearts, 1 7 of hearts, 1 8 of hearts, 11.6ms
image 17/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_144652_jpg.rf.316d0dad84d3d696fa8fa3caf53fb700.jpg: 640x640 1 5 of hearts, 1 6 of hearts, 1 7 of hearts, 1 8 of hearts, 10.0ms
image 18/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_144657_jpg.rf.0b7bb9ab4b594b83097af9c4c1ea46c3.jpg: 640x640 1 5 of hearts, 1 6 of hearts, 1 7 of hearts, 1 8 of hearts, 10.0ms
image 19/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_144711_jpg.rf.19a6cc13f83f27b45e10a6056bb25721.jpg: 640x640 1 5 of hearts, 1 6 of hearts, 1 7 of hearts, 1 8 of hearts, 10.0ms
image 20/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_161217_jpg.rf.1755e5fefb14ca6df49690604289bb46.jpg: 640x640 1 10 of hearts, 1 9 of hearts, 1 jack of hearts, 1 king of hearts, 1 queen of hearts, 10.0ms
image 21/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_161313_jpg.rf.12498ecbc8985a8ff65bd2033d8f622a.jpg: 640x640 1 10 of hearts, 1 9 of hearts, 1 jack of hearts, 1 king of hearts, 1 queen of hearts, 10.0ms
image 22/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_161455_jpg.rf.635ccd0ee9f7dd762009f539d6b998e9.jpg: 640x640 1 10 of hearts, 1 9 of hearts, 1 jack of hearts, 1 king of hearts, 1 queen of hearts, 9.8ms
image 23/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_161515_jpg.rf.b22dd5d2b8037f009a9e65052d2d3b5c.jpg: 640x640 1 10 of hearts, 1 9 of hearts, 1 jack of hearts, 1 king of hearts, 1 queen of hearts, 9.9ms
image 24/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_161525_jpg.rf.f7962cdc50e0a3cd03e4c081a1d2a67a.jpg: 640x640 1 10 of hearts, 1 9 of hearts, 1 jack of hearts, 1 king of hearts, 1 queen of hearts, 9.8ms
image 25/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_162828_jpg.rf.db7557485f5c3e5ce01b2e1ab3d4621e.jpg: 640x640 1 2 of diamonds, 1 3 of diamonds, 1 4 of diamonds, 1 ace of diamonds, 9.9ms
image 26/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_163749_jpg.rf.3c41ec25d2a8390c760eb7fcf2d2466b.jpg: 640x640 1 5 of diamonds, 1 6 of diamonds, 1 7 of diamonds, 1 8 of diamonds, 9.9ms
image 27/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_163800_jpg.rf.e1ad0b7b78e379d5f9f0bec787a9050b.jpg: 640x640 1 5 of diamonds, 1 6 of diamonds, 1 7 of diamonds, 1 8 of diamonds, 8.9ms
image 28/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_164227_jpg.rf.e42455b79bae041d959f90597b7065bc.jpg: 640x640 1 5 of diamonds, 1 6 of diamonds, 1 7 of diamonds, 1 8 of diamonds, 8.5ms
image 29/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_164257_jpg.rf.0c3abfccf0f7f147946c89251b87f598.jpg: 640x640 1 5 of diamonds, 1 6 of diamonds, 1 7 of diamonds, 1 8 of diamonds, 8.4ms
image 30/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_165206_jpg.rf.1e20afbea0b132989e944ffbd800f348.jpg: 640x640 1 10 of diamonds, 1 9 of diamonds, 1 jack of diamonds, 1 king of diamonds, 1 queen of diamonds, 9.5ms
image 31/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_165221_jpg.rf.a5188caf60a7bd5e8c6dcf92d68f9505.jpg: 640x640 1 10 of diamonds, 1 9 of diamonds, 1 jack of diamonds, 1 king of diamonds, 1 queen of diamonds, 8.1ms
image 32/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_165711_jpg.rf.c64b84b61322947a5a8c7545e214278c.jpg: 640x640 1 10 of diamonds, 1 9 of diamonds, 1 jack of diamonds, 1 king of diamonds, 1 queen of diamonds, 9.0ms
image 33/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_165737_jpg.rf.11f2e19b001c300ee820e093b135409a.jpg: 640x640 1 10 of diamonds, 1 9 of diamonds, 1 jack of diamonds, 1 king of diamonds, 1 queen of diamonds, 8.4ms
image 34/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_170523_jpg.rf.a106d534bf3279ec40e771452a142c1e.jpg: 640x640 1 2 of clubs, 1 3 of clubs, 1 4 of clubs, 1 ace of clubs, 8.9ms
image 35/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_170755_jpg.rf.71804c21e1c7681d5b656427449e0a2a.jpg: 640x640 1 2 of clubs, 1 3 of clubs, 1 4 of clubs, 1 ace of clubs, 8.1ms
image 36/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_171557_jpg.rf.9c6f913ba56e578ef4c31cc3faffcf7d.jpg: 640x640 1 5 of clubs, 1 6 of clubs, 1 7 of clubs, 1 8 of clubs, 8.1ms
image 37/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_171653_jpg.rf.637e380597f1d844654a33f4a3555471.jpg: 640x640 1 5 of clubs, 1 6 of clubs, 1 7 of clubs, 1 8 of clubs, 9.2ms
image 38/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_171810_jpg.rf.be96dac3cbdda6973a920a0a787b33f2.jpg: 640x640 1 5 of clubs, 1 6 of clubs, 2 7 of clubss, 10.4ms
image 39/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_171916_jpg.rf.7c8b85f64455e815acc30b5111422bf2.jpg: 640x640 1 5 of clubs, 1 6 of clubs, 1 7 of clubs, 1 8 of clubs, 7.9ms
image 40/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_171936_jpg.rf.b6e31b1cc6b5e14dc66462becfa4a63d.jpg: 640x640 1 5 of clubs, 1 6 of clubs, 1 7 of clubs, 1 8 of clubs, 7.7ms
image 41/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_172435_jpg.rf.854fe0c471b03fe3a3894a3d2cbe00d0.jpg: 640x640 1 10 of clubs, 1 9 of clubs, 1 jack  of clubs, 1 king of clubs, 1 queen of clubs, 8.0ms
image 42/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_172537_jpg.rf.8fe076e115c111f04732f8e7f778e51d.jpg: 640x640 1 10 of clubs, 1 9 of clubs, 1 jack  of clubs, 1 king of clubs, 1 queen of clubs, 7.8ms
image 43/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_172800_jpg.rf.e63a3bae897cbf27dccbf969f543bb6f.jpg: 640x640 1 10 of clubs, 1 9 of clubs, 1 jack  of clubs, 1 king of clubs, 1 queen of clubs, 8.0ms
image 44/44 /content/datasets/poker-cards-4/test/images/IMG_20220316_173229_jpg.rf.1b93cc9a66ca2d0a28820a7dae74222a.jpg: 640x640 3 10 of clubss, 1 9 of clubs, 1 jack  of clubs, 1 king of clubs, 1 queen of clubs, 8.5ms
Speed: 1.5ms preprocess, 11.0ms inference, 14.9ms postprocess per image at shape (1, 3, 640, 640)
Results saved to runs/detect/predict
💡 Learn more at https://docs.ultralytics.com/modes/predict

    查看部分结果:

import glob
import os
from IPython.display import Image as IPyImage, display


latest_folder = max(glob.glob('/content/runs/detect/predict*/'), key=os.path.getmtime)
for img in glob.glob(f'{latest_folder}/*.jpg')[:3]:
    display(IPyImage(filename=img, width=600))
    print("\n")

51c视觉~YOLO~合集8_YOLO_41

51c视觉~YOLO~合集8_YOLO_42

51c视觉~YOLO~合集8_YOLO_43



3、Yolo3


3.1、YOLO3~DIou

现在都yolo7了  还老生常谈~~ 基于DIou改进的YOLOv3目标检测

DIoU要比GIou更加符合目标框回归的机制,将目标与anchor之间的距离,重叠率以及尺度都考虑进去,使得目标框回归变得更加稳定,不会像IoU和GIoU一样出现训练过程中发散等问题,并且方法能够简单地迁移到现有的算法中带来性能的提升,实验在YOLOv3上提升了5.91mAP。

51c视觉~YOLO~合集8_YOLO_44

51c视觉~YOLO~合集8_视觉_45

IoU loss顾名思义就是直接通过IoU计算梯度进行回归,论文提到IoU loss的无法避免的缺点:当两个box无交集时,IoU=0,很近的无交集框和很远的无交集框的输出一样,这样就失去了梯度方向,无法优化。IoU loss的实现形式有很多种,除公式2外,还有UnitBox的交叉熵形式和IoUNet的Smooth-L1形式。 

51c视觉~YOLO~合集8_视觉_46

51c视觉~YOLO~合集8_视觉_47

上图可以很好的来说明GIoU不稳定以及收敛很慢的原因。上图中第一行三张图展示的是GIoU的回归过程,其中绿色框为目标框,黑色框为anchor,蓝色框为不同次数的迭代后,anchor的偏移结果。第二行三张图展示的是DIoU的回归过程,其中绿色框为目标框,黑色框为anchor,红色框为不同次数的迭代后,anchor的偏移结果。从图中我们可以看到,GIoU在回归的过程中,从损失函数的形式我们发现,当IoU为0时,GIoU会先尽可能让anchor能够和目标框产生重叠,之后GIoU会渐渐退化成IoU回归策略,因此整个过程会非常缓慢而且存在发散的风险。而DIoU考虑到anchor和目标之间的中心点距离,可以更快更有效更稳定的进行回归。  

51c视觉~YOLO~合集8_YOLO_48

如上图中的包含情况,GIoU会退化成IoU(三个位置预测框和gt框所包围的最小面积相同,惩罚项c保持一致,梯度发散)。由于很大程度依赖IoU项,GIoU需要更多的迭代次数来收敛,特别是水平和垂直的bbox(后面会分析)。一般地,GIoU loss不能很好地收敛SOTA算法,反而造成不好的结果。

综合上面的分析,论文提出Distance-IoU(DIoU) loss,简单地在IoU loss基础上添加一个惩罚项,该惩罚项用于最小化两个bbox的中心点距离。如图1所示,DIoU收敛速度和效果都很好,而且DIoU能够用于NMS的计算中,不仅考虑了重叠区域,还考虑了中心点距离。另外,论文考虑bbox的三要素,重叠区域,中心点距离和长宽比,进一步提出了Complete IoU(CIoU) loss,收敛更快,效果更好。

IoU and GIoU Losses

为了全面地分析IoU loss和GIoU的性能,论文进行了模拟实验,模拟不同的距离、尺寸和长宽比的bbox的回归情况,如下图所示:

 

51c视觉~YOLO~合集8_视觉_49

  • 绿色框代表仿真实验需要回归的七个不同尺度的目标框,七个目标框的中心点坐标都是(10 * 10);
  • 蓝色的点代表了所有anchor的中心点,中心点的分布如上图所示,各个方向都有,各种距离都有,当然每个anchor的一个中心点都包含有七个不同面积的anchor框。而且每个面积的anchor框又有七种不同的比例尺寸。因此一共有5000个蓝色点,对应5000*7*7个anchor框,并且每个anchor框都需要回归到七个gt目标框上,因此一共有5000*7*7*7个回归案例。

最终的实验结果如下:图中展示的训练同样的步数后(200步),IoU,GIoU以及本文提出的DIoU、CIoU作为loss的情况下,每个anchor的误差分布。

51c视觉~YOLO~合集8_YOLO_50

  • IoU:从IoU误差的曲线我们可以发现,anchor越靠近边缘,误差越大,那些与目标框没有重叠的anchor基本无法回归;
  • GIoU:从GIoU误差的曲线我们可以发现,对于一些没有重叠的anchor,GIoU的表现要比IoU更好。但是由于GIoU仍然严重的依赖IoU,因此在两个垂直方向,误差很大,基本很难收敛,这就是GIoU不稳定的原因;
  • DIoU:从DIoU误差的曲线我们可以发现,对于不同距离,方向,面积和比例的anchor,DIoU都能做到较好的回归。

具体伪算法步骤如下:

51c视觉~YOLO~合集8_视觉_51

51c视觉~YOLO~合集8_YOLO_52

论文将5000个中心点上的bbox在最后阶段的total error进行了可视化。IoU loss只对与target box有交集的bbox有效,因为无交集的bbox的梯度为0。而GIoU由于增加了惩罚函数,中间大部分区域错误率明显减少,但是垂直和水平的区域依然保持着高的error,这是由于GIoU的惩罚项经常很小甚至为0,导致训练需要更多的迭代来收敛。 




3.2、YOLOv3~ COCO


 YOLOv3 模型使用COCO 数据集, 虽然yolo3也不用就全当学习哦

在 Kaggle 平台使用 GPU 跑通 YOLOv3 训练代码.

  • COCO 2014 数据集地址(原始):https://cocodataset.org/
  • COCO 2014 数据集地址(Kaggle):https://www.kaggle.com/datasets/jeffaudi/coco-2014-dataset-for-yolov3
  • YOLOv3 原文项目地址:https://pjreddie.com/darknet/yolo/
  • 本次使用的 YOLOv3 项目地址(PyTorch):https://github.com/eriklindernoren/PyTorch-YOLOv3

跑通 YOLOv3 训练过程,读者会熟悉 YOLOv3 项目的文件及 Linux 文件操作,整个过程分为以下几步。

  • 下载项目代码
  • 下载和配置数据集中的图像列表
  • 修改 coco.data 配置文件
  • 下载预训练模型权重
  • 训练模型

输出目录 第一行体现了 PyTorch-YOLOv3 项目在 Kaggle 平台的地址为 /kaggle/working/PyTorch-YOLOv3,每个文件夹的作用展示在下图中。 

51c视觉~YOLO~合集8_YOLO_53

51c视觉~YOLO~合集8_视觉_54

下载和配置数据集中的图像列表添加平台上存在的数据集

在 Kaggle 上点击【Add Data】输入数据集的名称或者地址,即可导入数据集,数据集比较大,需要十几分钟的时间。

51c视觉~YOLO~合集8_YOLO_55

 接着进入 /kaggle 地址下查看里面有哪些内容,图片太多了,便不显示全部文件了,只显示到了文件夹。 

51c视觉~YOLO~合集8_YOLO_56

点击数据集里文件夹的右侧,可以直接复制地址。 

由此获得:

  • Input 文件夹下存放数据集,地址为:/kaggle/input/coco-2014-dataset-for-yolov3
  • 图片的地址:/kaggle/input/coco-2014-dataset-for-yolov3/coco2014/images
  • 标签的地址:/kaggle/input/coco-2014-dataset-for-yolov3/coco2014/labels

下载 coco 工具包及配置数据集中的图像列表

项目文件中数据集的下载指令:

cd PyTorch-YOLOv3/  
./data/get_coco_dataset.sh

可以知道项目期望的数据集地址为 PyTorch-YOLOv3/data/coco/images

查看get_coco_dataset.sh,内容如下:

#!/bin/bash  
  
# CREDIT: https://github.com/pjreddie/darknet/tree/master/scripts/get_coco_dataset.sh  
  
# Clone COCO API,需要操作  
git clone https://github.com/pdollar/coco  
cd coco  
  
mkdir images  
cd images  
  
# Download Images 已经存在,无需操作  
wget -c https://pjreddie.com/media/files/train2014.zip  
wget -c https://pjreddie.com/media/files/val2014.zip  
  
# Unzip  
unzip -q train2014.zip  
unzip -q val2014.zip  
  
cd ..  
  
# Download COCO Metadata,此处只用下载 5k.part 和 trainvalno5k.part  
wget -c https://pjreddie.com/media/files/instances_train-val2014.zip  
wget -c https://pjreddie.com/media/files/coco/5k.part  
wget -c https://pjreddie.com/media/files/coco/trainvalno5k.part  
wget -c https://pjreddie.com/media/files/coco/labels.tgz  
tar xzf labels.tgz  
unzip -q instances_train-val2014.zip  
  
# Set Up Image Lists,关键操作  
paste <(awk "{print \"$PWD\"}" <5k.part) 5k.part | tr -d '\t' > 5k.txt  
paste <(awk "{print \"$PWD\"}" <trainvalno5k.part) trainvalno5k.part | tr -d '\t' > trainvalno5k.txt

因为在 Kaggle 中直接引用了数据集,里面的5k.part 和 trainvalno5k.part 没有处理成 5k.txt 和 trainvalno5k.txt,并且 Input 文件夹下没有操作的权限,便在 PyTorch-YOLOv3/data/coco/images 项目下处理。

51c视觉~YOLO~合集8_视觉_57

51c视觉~YOLO~合集8_视觉_58

 

图片在 Kaggle 里的地址:/kaggle/input/coco-2014-dataset-for-yolov3/coco2014/images/train2014/COCO_train2014_000000000009.jpg

修改 Set Up Image Lists 的代码并且执行

$PWD 替换为 /kaggle/input/coco-2014-dataset-for-yolov3/coco2014,保证图像的列表地址是可以对应上的,代码如下。

!wget -c https://pjreddie.com/media/files/coco/5k.part  
!paste <(awk "{print \"/kaggle/input/coco-2014-dataset-for-yolov3/coco2014\"}" <5k.part) 5k.part | tr -d '\t' > 5k.txt  
  
!wget -c https://pjreddie.com/media/files/coco/trainvalno5k.part  
!paste <(awk "{print \"/kaggle/input/coco-2014-dataset-for-yolov3/coco2014\"}" <trainvalno5k.part) trainvalno5k.part | tr -d '\t' > trainvalno5k.txt  
%ls

修改 coco.data 配置文件

原来项目的配置文件 coco.data 的内容如下:

classes= 80  
train=data/coco/trainvalno5k.txt  
valid=data/coco/5k.txt  
names=data/coco.names  
backup=backup/  
eval=coco

修改成 Kaggle 平台所在的地址:

classes= 80  
train=/kaggle/working/PyTorch-YOLOv3/data/coco/trainvalno5k.txt  
valid=/kaggle/working/PyTorch-YOLOv3/data/coco/5k.txt  
names=/kaggle/working/PyTorch-YOLOv3/data/coco.names  
backup=backup/  
eval=coco

使用 readlines 读取 coco.data 文件的内容到列表里,接下来修改 train\valid\names 的地址(字符串)。

51c视觉~YOLO~合集8_YOLO_59

将 coco.data 更名为 coco_original.data,新建 coco.data 文件,把列表 file_list 的每一行写入 coco.data 文件里。

51c视觉~YOLO~合集8_YOLO_60

 

下载预训练权重

预训练权重包括:

  • YOLOv3 权重(标准版)
  • Tiny-YOLOv3 权重(轻量版)
  • darknet53 权重(骨干网络)

先到 weights 文件夹下 %cd /kaggle/working/PyTorch-YOLOv3/weights/,执行命令!./download_weights.sh

51c视觉~YOLO~合集8_视觉_61

训练模型    

进入 /kaggle/working/PyTorch-YOLOv3 路径下,使用预训练模型 darknet53.conv.74 训练模型。

把 /kaggle/working/PyTorch-YOLOv3/pytorchyolo/train.py 下的 train.py 代码移动到 /kaggle/working/PyTorch-YOLOv3/ 。

!python train.py --data config/coco.data  --pretrained_weights weights/darknet53.conv.74

train文件命令行参数说明

  • --epochs: 训练大循环数量。类型为‘整形’,默认参数为100;
  • --batch_size: 批大小。类型为‘整形’,默认参数为8;
  • --gradient_accumulations: 累积多少个梯度再进行更新。类型为‘整形’,默认参数为2;
  • --model_def: 模型定义文件路径。类型为‘字符串’,默认参数为"config/yolov3.cfg";
  • --data config: 数据配置文件路径。类型为‘字符串’,默认参数为"config/coco.data";
  • --pretrained_weights: 如果从指定从检查点模型开始训练就填写检查点路径。类型为‘字符串’,无默认参数;
  • --n_cpu: 批量生成时使用的cpu线程数。类型为‘整形’,默认参数为8;
  • --img_size: 图片大小。类型为‘整形’,默认参数为416;
  • --checkpoint_interval: 保存模型权重的时间间隔。类型为‘整形’,默认参数为1;
  • --evaluation_interval: 使用验证集验证的时间间隔。类型为‘整形’,默认参数为1;
  • --compute_map: 如果是True则每10个batch计算一次mAP。类型为‘布尔值’,默认参数为False;
  • --multiscale_training: 是否允许进行多尺度训练。类型为‘布尔值’,默认参数为True。

在 Kaggle 平台上安装不上 poetry,没有使用官网推荐的训练方式,如下。

poetry run yolo-train --data config/coco.data  --pretrained_weights weights/darknet53.conv.74

在【Settings】选择 GPU 进行训练,GPU 的配置应该放在启动代码环境时设置。

安装需要的包  terminaltables 和 torchsummary,执行代码开始训练。

51c视觉~YOLO~合集8_YOLO_62

数据集太大了,选择 GPU P100 训练了 7 小时,才 4 个 Epoch,本次练习侧重让大家熟悉 YOLOv3 项目的文件。 











4、Yolo7


4.1、Yolo7~农业方向

这里使用了一种单阶段目标检测算法,并将基于CNN的人群计数思想转移并应用到鸭计数问题中,提出了一种基于注意力机制改进的YOLOv7算法CBAM-YOLOv7。

论文链接:https://www.mdpi.com/2077-0472/12/10/1659/pdf

饲养密度是影响畜禽大规模生产和动物福利的关键因素。然而,麻鸭养殖业目前使用的人工计数方法效率低、人工成本高、精度低,而且容易重复计数和遗漏。

在这方面,本文使用深度学习算法来实现对密集麻鸭群数量的实时监测,并促进智能农业产业的发展。本文构建了一个新的大规模大麻鸭目标检测图像数据集,其中包含1500个大麻鸭目标的检测全身帧标记和仅头部帧标记。

此外,本文提出了一种基于注意力机制改进的YOLOv7算法CBAM-YOLOv7,在YOLOv7的主干网络中添加了3个CBAM模块,以提高网络提取特征的能力,并引入SE-YOLOv7和ECA-YOLOv7进行比较实验。实验结果表明,CBAM-YOLOv7具有较高的精度,mAP@0.5和mAP@0.5:0.95略有改善。CBAM-YOLOv7的评价指标值比SE-YOLOw7和ECA-YOLOv 7的提高更大。此外,还对两种标记方法进行了比较测试,发现仅头部标记方法导致了大量特征信息的丢失,而全身框架标记方法显示了更好的检测效果。

算法性能评估结果表明,本文提出的智能麻鸭计数方法是可行的,可以促进智能可靠的自动计数方法的发展。

随着技术的发展,监控设备在农业中发挥着巨大的作用。有多种方法可以监测个体动物的行为,例如插入芯片记录生理数据、使用可穿戴传感器和(热)成像技术。一些方法使用附着在鸟类脚上的可穿戴传感器来测量它们的活动,但这可能会对受监测的动物产生额外影响。特别是,在商业环境中,技术限制和高成本导致这种方法的可行性低。

因此,基于光流的视频评估将是监测家禽行为和生理的理想方法。最初,许多监控视频都是人工观察的,效率低下,依赖于工作人员的经验判断,没有标准。然而,近年来,由于大数据时代的到来和计算机图形卡的快速发展,计算机的计算能力不断增强,加速了人工智能的发展。与人工智能相关的研究正在增加,计算机视觉在动物检测中的应用越来越广泛。

例如,2014年Girshick等人提出的R-CNN首次引入了两阶段检测方法。该方法使用深度卷积网络来获得优异的目标检测精度,但其许多冗余操作大大增加了空间和时间成本,并且难以在实际的养鸭场中部署。Law等人提出了一种单阶段的目标检测方法CornerNet和一种新的池化方法:角点池化。

然而,基于关键点的方法经常遇到大量不正确的目标边界框,这限制了其性能,无法满足鸭子饲养模型的高性能要求。Duan等人在CornerNet的基础上构建了CenterNet框架,以提高准确性和召回率,并设计了两个对特征级噪声具有更强鲁棒性的自定义模块,但Anchor-Free方法是一个具有前两个关键点组合的过程,并且由于网络结构简单、处理耗时、速率低和测量结果不稳定,它不能满足麻鸭工业化养殖所需的高性能和高准确率的要求。

本文的工作使用了一种单阶段目标检测算法,它只需要提取特征一次,就可以实现目标检测,其性能高于多阶段算法。目前,主流的单阶段目标检测算法主要包括YOLO系列、SSD、RetinaNet等。本文将基于CNN的人群计数思想转移并应用到鸭计数问题中。随着检测结果的输出,作者嵌入了一个目标计数模块来响应工业化的需求。目标计数也是计算机视觉领域的一项常见任务。目标计数可分为多类别目标计数和单类别目标计数;本工作采用了一群大麻鸭的单类别计数。

本文希望实现的目标是:

  1. 建立了一个新的大规模的德雷克图像数据集,并将其命名为“大麻鸭数据集”。大麻鸭数据集包含1500个标签,用于全身框架和头部框架,用于鸭的目标检测。该团队首次发布了大麻鸭数据集
  2. 本研究构建了大鸭识别、大鸭目标检测、大鸭图像计数等全面的工作基线,实现了麻鸭的智能养殖
  3. 该项目模型引入了CBAM模块来构建CBAM-YOLOv7算法

材料的获取

麻鸭是我国数量最丰富、分布最广泛、种类最多样的家鸭之一,具有体型小、省食、产蛋效率高等特点,具有重要的研究价值。使用DJI Pocket 2,一个适应性极强和灵活的微型万向架相机,来捕捉本研究中使用的图像和视频数据集。数据收集自中国四川省雅安市的原水禽养殖场,由四川农业大学著名水禽饲养员王林泉教授创建。

在准备数据集的过程中,首先通过多次改变图像拍摄的角度和距离,从10个不同的麻鸭屋采集数据。然后,手工筛选并丢弃了一些高重复的数据和一些由于麻鸭屋阻塞而未捕获的冗余数据。最后,数据集总共包含了1500张图像,其中包括训练集中的1300张图像和测试集中的200张图像。图1显示了非最大抑制对麻鸭的检测、识别和计数任务所带来的挑战的分析。图2显示了一个数据集标记工作的示例。

51c视觉~YOLO~合集8_视觉_63

在目标检测工作的预测阶段,网络输出多个候选Anchor,但其中许多Anchor在同一对象附近重叠,如图1b所示。非最大抑制能够保留这组候选Anchor中最好的一个,如图1c所示。将鸭A和鸭B命名为。当鸭A和鸭B太近时,由于筛选非最大入侵,可以消除鸭A的预测箱。因此,准确估计所包含的密集大麻鸭数据集的数量是一个挑战。

由于对整个麻鸭体进行标记导致了许多重叠的标记箱,影响了麻鸭个体计数的准确性,选择了只标记麻鸭头的方法,并对两者进行了比较实验。

数据预处理

混合数据增强

Mixup是一种基于简单的数据依赖数据增强原理的非常规数据增强方法,利用线性插值构建新的训练样本和标记。对数据标签的处理公式如下:

51c视觉~YOLO~合集8_YOLO_64

图3为不同融合比例Mixup数据增强过程后的数据结果。

51c视觉~YOLO~合集8_YOLO_65

Mosaic Data Augmentation

YOLOv4网络使用Mosaic数据增强,其思想是随机切割4幅图像,并将它们组合成一幅图像作为新生成的训练数据,极大地丰富了检测数据集,使网络更加鲁棒,并减少了GPU视频内存占用。图4显示了Mosaic数据扩充操作的工作流程。

51c视觉~YOLO~合集8_视觉_66

训练参数

实验中所使用的训练过程的训练参数如表1所示。

51c视觉~YOLO~合集8_视觉_67

评价指标

为了评价该算法的性能,本研究中使用的评价指标分别为精度(P)、查全率(R)、mAP、F1 Score和FPS。精度表示阳性样本占具有阳性预测结果的样本的比例。

其计算公式如下:

51c视觉~YOLO~合集8_视觉_68

mAP是每个类别的平均精度的平均值和平均AP值。其计算公式如下:

51c视觉~YOLO~合集8_视觉_69

其中,S为所有类别的个数,分母为所有类别的ap之和。本研究的目标检测目标仅为一种麻鸭,因此为AP = mAP。

Related Network

本节首先介绍了YOLOv7算法,然后详细介绍了本文提出的在YOLOv7中添加注意力机制的改进方法。

YOLOv7

本文提出了一种基于计算机视觉的基于家鸭目标检测和种群统计的识别和检测算法。利用该算法,育种者可以实时获取野鸭的数量和行为动态,实现养殖场的快速管理和战略制定,优化野鸭的繁殖率和生长速度,有助于实现经济效益的最大化。

鉴于鸭种群中个体密度小,且种群统计的实时要求,选择了最新的Yolov7模型。“你只看一次(Yolov7)是一种单阶段目标检测算法。图5为Yolov7的网络结构图。将Yolov7模型预处理方法与Yolov5集成,使用镶嵌数据增强适用于小目标检测。在体系结构方面,提出了基于ELAN的扩展ELAN(E-ELAN)。利用扩展、shuffle和合并基数,在不破坏原始梯度路径的情况下,不断提高网络的学习能力。在计算块的体系结构中,利用群卷积来扩展计算块的通道和基数。不同的计算块组被引导去学习更多样化的特征。

然后,它重点关注一些优化模块和方法,称为可训练的“bag-of-freebies”,包括以下内容:

  1. 采用无Identity的RepConv设计了规划的重参化卷积体系结构,为不同的特征图提供了更多的梯度多样性。
  2. 介绍了辅助检测头,并利用优化过程生成的软标签用于Lead Head和辅助头的学习。因此,由此生成的软标签应该能更好地表示源数据与目标之间的分布和相关性,从而获得更准确的结果。
  • 批次归一化层直接连接到卷积层,使得批次的归一化平均值和方差在推断阶段被集成到卷积层的偏差和权重中。
  • 利用YOLOR中隐式知识的加法和乘法方法,结合卷积特征映射,可以在推理阶段通过预先计算将其简化为向量,从而结合之前或后续卷积层的偏差和权重。
  • EMA模型纯粹被用作最终的推理模型。最后,实时目标检测可以在不增加推理成本的情况下大大提高检测精度,使5-160FPS范围内的速度和精度超过所有已知的目标检测器,可以实现目标检测的快速响应和准确预测。

51c视觉~YOLO~合集8_视觉_70

改进注意力机制的YOLOv7

注意力机制是一种常见的数据处理方法,广泛应用于各个领域的机器学习任务。计算机视觉注意力机制的核心思想是找到原始数据之间的相关性,然后突出重要的特征,如通道注意力、像素注意力、多阶注意力等。

CBAM主要包括通道注意力模块和空间注意力模块。模块结构如图6所示。

51c视觉~YOLO~合集8_视觉_71

CBAM是一个轻量级的注意力模块,可以在通道和空间维度上执行注意力操作。它由通道注意力模块(CAM)和空间注意力模块(SAM)组成。CAM可以使网络更加关注图像的前景和有意义的区域,而SAM可以使网络更关注富含整个画面上下文信息的位置。

YOLOv7 Introduces the CBAM Attention Mechanism

CBAM注意力机制被添加到YOLOV7网络结构中,网络结构如图7所示。该模块的功能是进一步提高特征提取网络的特征提取能力。一旦将注意力机制添加到骨干网络中,注意力机制模块就会降低骨干网络的一些原始权重。这导致网络的预测结果出现错误。在这方面选择将注意力机制添加到增强特征网络提取的部分,而不破坏网络提取的原始特征。

 

51c视觉~YOLO~合集8_视觉_72

CBAM注意机制的工作原理如下:

在通道注意力模块中,对H×W×C的输入特征图进行全局最大池化(GMP)和全局平均池化(GAP),得到2个大小为1×1×C的特征图。这2个特征图被发送到一个两层的多层感知器。MLP第一层的神经元数量为C/r(r为还原率),激活函数为ReLU。第二层神经元的数量为C,这两层神经网络的权值被共享。然后,基于元素级计算添加输出特征,并通过sigmoid生成最终的通道附着特征。最后,将通道注意力特征乘以原始输入特征图,得到空间注意力模块的输入特征。

在空间注意模块中,使用上一步中的特征图作为输入。

经过GMP和GAP后,得到了2个大小为H×W×1的特征图。然后执行Concat操作。将特征图降维后,通过sigmoid生成空间注意力特征。最后,将空间注意力特征乘以输入的特征图,得到最终的特征图。

实验结果

51c视觉~YOLO~合集8_YOLO_73

 目标检测网络比较实验结果

51c视觉~YOLO~合集8_视觉_74

 引入注意机制的对比实验结果

51c视觉~YOLO~合集8_视觉_75

不同数据标注方法的实验结果比较 

51c视觉~YOLO~合集8_视觉_76

消融实验结果 

51c视觉~YOLO~合集8_视觉_77

51c视觉~YOLO~合集8_视觉_78

 

51c视觉~YOLO~合集8_视觉_79





4.2、YOLOv7~ELAN

虽然还没升到7,先来学学吧

设计高效、高质量的表达性网络架构一直是深度学习领域最重要的研究课题。当今主流的网络设计策略大多基于前馈路径,即基于数据路径设计网络架构。在本文中希望通过提高网络学习能力来增强训练模型的表达能力。由于驱动网络参数学习的机制是反向传播算法,本文设计了基于反向传播路径的网络设计策略。提出了layer-level、stage-level和network-level的梯度路径设计策略,并通过理论分析证明了设计策略的优越性和可行性和实验。

深度神经网络(DNN)现在广泛用于各种设备,以解决不同类型的任务。数百万科学家、工程师和研究人员参与了深度学习相关的工作。他们都期待着设计出能够满足他们需求的高效、准确、低成本的解决方案。因此,如何设计适合其产品的网络架构变得尤为重要。

自2014年以来,许多DNN在各种任务上取得了接近人类或优于人类的性能。例如,谷歌的GoogLeNet和微软的PReLUNet在图像分类方面,Facebook的Deepface在人脸识别方面,DeepMind的AlphaGo在围棋棋盘上等等。基于上述领域的开始,一些研究人员继续开发更先进的新架构或算法以击败上述方法;

其他研究人员关注如何使DNN相关技术在人类日常生活中实用。Iandola等人提出的SqueezeNet是一个典型的例子,因为它将AlexNet的参数数量减少了50倍,但可以保持相当的精度。MobileNet和ShuffleNet也是很好的例子。前者将实际硬件操作延迟直接添加到架构设计的考虑中,而后者将硬件特性的分析用作设计神经网络架构的参考。

就在ResNet、ResNeXt和DenseNet架构解决了深层网络训练中遇到的收敛问题之后,近年来CNN架构的设计集中在以下几点:

1. 特征融合

2. 感受野增强

3. 注意机制

4. 分支选择机制

换句话说,大多数研究遵循深度网络的共同认知,即从浅层提取low-level特征,从深层提取high-level特征。根据上述原则,可以使用它们来设计神经网络架构,以有效地组合数据路径(前馈路径)中的不同level的特征。

然而,这样的设计策略是否一定正确?从图1中发现,通过调整目标和损失层的配置,可以控制每个层(浅层或深层)学习的特征。也就是说,权重学习什么样的特征主要取决于使用什么样的信息来教它,而不是输入来自哪些层的组合。基于这一发现,作者重新定义了网络设计策略。

51c视觉~YOLO~合集8_视觉_80

由于提出网络结构可以用目标函数可以指导神经网络学习信息的概念来设计,因此必须首先了解目标函数如何影响网络权重的更新。

目前,主要的权重更新方法是反向传播算法,它使用偏微分生成梯度,然后通过梯度下降来更新权重。该算法以链式规则的方式将梯度信息传播到浅层,并重复这些步骤,直到更新所有层的权重。换句话说,目标函数教导的信息以梯度的形式在层之间传播。

在本文中提出通过分析通过目标函数的引导生成的梯度,可以在执行反向传播过程时通过梯度路径来设计网络结构。作者分别为3个不同级别的策略设计网络架构,如layer-level、stage-level和network-level,如下所述:

1、Layer-level设计

在这个层次上设计了梯度分流策略,并使用它们来确认假设的有效性。调整层数并计算残差连接的通道比率,然后设计Partial Residual Network(PRN)。

2、Stage-level设计

添加硬件特征以加快网络推理。最大化梯度组合,同时最小化硬件计算成本,因此设计了Cross Stage Partial Network(CSPNet)。

3、Network-level设计

增加了梯度传播效率的考虑,以平衡网络的学习能力。当设计网络架构时,还考虑了整个网络的梯度传播路径长度,因此设计了Efficient Layer Aggregation Network(ELAN)。

本文方法

网络设计策略  

如图2所示,在本文中将网络设计策略分为两种:

1. 数据路径设计策略

2. 梯度路径设计策略

数据路径设计策略主要侧重于设计特征提取、特征选择和特征融合操作,以提取具有特定属性的特征。这些特征可以帮助后续层使用这些特征进一步获得更好的属性,以便进行更高级的分析。

应用梯度路径设计策略的目的是分析梯度的来源和组成,以及如何通过驱动参数更新梯度。然后,可以使用上述分析的结果来设计网络架构。设计理念是希望最终的参数利用率更高,从而达到最佳的学习效果。

51c视觉~YOLO~合集8_视觉_81

接下来将分别讨论数据路径设计策略和梯度路径设计策略的优点和缺点。

数据路径设计策略有3个优点:

1. 可以提取具有特定物理意义的特征。例如,使用非对称计算单元来提取具有不同感受野的特征;

2. 可以为不同的输入自动选择具有参数化模型的合适的操作单元。例如,使用kernel选择来处理输入具有不同的性质;

3. 可以直接重用所学习的特征。例如,特征金字塔网络可以直接利用从不同层提取的特征进行更准确的预测。

数据路径设计策略有2个缺点:

1. 在训练过程中,有时会导致效果的不可预测的降级,此时需要设计更复杂的架构来解决问题。例如,non-local网络的成对关系很容易退化为一元信息;

2. 各种专门设计的运算单元容易导致性能优化的困难。例如,在专用于AI的ASIC设计中,如果设计者想要添加算术单元,则需要额外的一组电路。

对于梯度路径设计策略,总共有3个优点:

1. 可以有效地使用网络参数,在这一部分中提出通过调整梯度传播路径,不同计算单元的权重可以学习各种信息,从而实现更高的参数利用效率;

2. 具有稳定的模型学习能力,由于梯度路径设计策略直接确定并传播信息以更新权重到每个计算单元,因此所设计的架构可以避免训练期间的退化;

3. 具有高效的推理速度,梯度路径设计策略使得参数利用非常有效,因此网络可以在不增加额外复杂架构的情况下实现更高的精度。

由于上述原因,所设计的网络在架构上可以更轻、更简单。

梯度路径设计策略有1个缺点:

1. 当梯度更新路径不是网络的简单反向前馈路径时,编程的难度将大大增加。

Partial Residual Networks  

Partial Residual Networks(PRN)其设计理念属于layer-level设计策略。在PRN的设计中,主要概念是最大化用于更新每层的权重的梯度组合。影响梯度组合的主要因素有2个:

1. 第一个是梯度的源层。源层由连接到梯度路径的不相交边缘的节点组成;

2. 第二个因素是梯度流通过链式法则的操作从损失层到达特定层所需的时间。

需要注意的一点是,当梯度在链规则更新过程中发生变化时,其覆盖的丢失信息量将随着链的增长而逐渐减少。作者将上述持续时间定义为梯度流从损失层行进到特定层所需的层数。在PRN中提出了以下2种结构来改进ResNet:

Masked residual layer

在ResNet的设计中,将每个计算块的输出与identity连接一起添加,这样的结构称为残差层。在PRN中,将identity连接乘以二进制Mask,并且只允许将某些通道的特征映射添加到计算块的输出中。将此结构称为Masked residual layer,其架构如图3所示。使用Masked residual layer的机制允许将特征图分为两部分,其中与被Mask的通道相对应的权重和与具有identity连接的通道相对的权重将由于上述identity效应而显著增加梯度组合的数量。此外,梯度源的差异将同时影响整个梯度时间戳(沿时间轴的时间节点),从而使梯度组合更加丰富。

51c视觉~YOLO~合集8_视觉_82

Asymmetric residual layer

在ResNet架构下,只能添加相同大小的特征图,这就是为什么它是一个非常受限的架构。通常,当执行优化的架构的计算量和推理速度时,常常受到这种架构的限制,无法设计出符合要求的架构。在PRN架构下,提出的Masked residual layer可以将通道数量的不一致性视为某些通道被阻塞,从而允许具有不同通道数量的特征图执行Masked residual操作。

将以上述方式操作的层称为Asymmetric residual layer。Asymmetric residual layer以这样的方式设计,即网络架构更灵活,并且更能够保持基于梯度路径的模型的属性。例如,当进行特征集成时,一般方法需要额外的过渡层来将不同的特征映射投影到同一维度,然后执行添加操作。然而,上述操作将增加大量参数和计算量,并且还将使梯度路径更长,从而影响网络的收敛。引入Asymmetric residual layer可以完美地解决类似问题。

Cross Stage Partial Networks  

CSPNet于2019年提出,它是一个基于Stage-level梯度路径的网络。与PRN一样,CSPNet基于最大化梯度组合的概念。CSPNet和PRN的区别在于,后者侧重于从理论角度确认梯度组合对网络学习能力的改善,而前者则是为了进一步优化硬件推理速度而额外设计的。因此,在设计CSPNet时,将架构从Layer-level扩展到Stage-level,并优化整体架构。

CSPNet主要有以下2种结构:

Cross stage partial operation

从最大化梯度源的角度来看可以很容易地发现,当每个通道具有不同的梯度路径时,梯度源可以最大化。此外,从最大化梯度时间戳的角度来看,当每个通道具有不同深度的计算块时,可以最大化梯度时戳的数量。

根据上述概念可以导出一种设计用于最大化梯度源和梯度时间戳的架构。这种架构将是类似fractal-like的架构和具有深度卷积的类似分形的架构。虽然上述设计可以有效地提高参数利用率,但会大大降低并行化能力。此外,这将导致模型显著降低GPU和TPU等推理引擎上的推理速度。

从前面的分析知道划分通道可以增加梯度源的数量,并且使不同通道连接的子网络具有不同的层可以增加梯度时间戳的数量。本文设计的Cross stage部分操作可以最大化梯度的组合,并在不破坏架构的情况下提高推理速度,并且可以并行化。

该体系结构如图4所示。在图4中,将stage的输入特征图分成两部分,并使用这种方式增加梯度源的数量。

51c视觉~YOLO~合集8_视觉_83

具体步骤如下:首先将输入特征图分成两部分,其中一部分通过计算块,该计算块可以是任何计算块,如Res Block、ResX Block或Dense Block。至于另一部分,它直接跨越整个Stage,然后与经过计算块的部分集成。由于只有部分特征图进入计算区进行操作,这种设计可以有效地减少参数量、操作、内存流量和内存峰值,从而使系统实现更快的推理速度。

Gradient flow truncate operation

为了使设计的网络架构更强大进一步分析了用于更新CSPNet的梯度流。由于计算块中经常使用shortcut连接,提供2条路径的梯度源必然会重叠很多。当特征图通过kernel函数时,它相当于空间投影。通常可以在两条路径的末端插入过渡层,以截断复制的梯度流。通过以上步骤可以使从2条路径和相邻Stage学习到的信息具有更明显的多样性。

作者设计了3种不同的重复梯度流截断操作组合,如图5所示。这些操作可以与不同的架构相匹配,例如计算块和下采样块,以获得更好的结果。

高效的层聚合网络

高效层聚合网络(ELAN)的代码于2022年7月发布。在网络层面,它属于梯度路径设计网络的范畴。设计ELAN的主要目的是解决在执行模型缩放时深度模型的收敛性会逐渐恶化的问题。

作者分析了通过整个网络中每个层的最短梯度路径和最长梯度路径,从而设计了具有有效梯度传播路径的层聚合架构。ELAN主要由VoVNet和CSPNet组成,并利用计算块中的堆栈结构优化了整个网络的梯度长度。在接下来的内容中将详细说明计算块中的堆栈是如何工作的。

堆栈在计算块中。当在做模型缩放时会出现一种现象,即当网络达到一定深度时,如果继续堆叠计算块,精度增益将越来越小。更糟糕的是,当网络达到某个临界深度时,其收敛性开始恶化,导致总体精度比浅层网络差。

最好的例子之一是Scaled-YOLOv4,看到它的P7模型使用了昂贵的参数和操作,但只有少量的精度增益,而且在许多流行网络中也会出现同样的现象。例如,ResNet-152的计算强度大约是ResNet-50的3倍,但在ImageNet上的精度提高不到1%。当ResNet堆叠到200层时,其精度甚至比ResNet-152更差。此外,当VoVNet堆叠到99层时,其精度甚至远低于VoVNet-39。

从梯度路径设计策略的角度来看,作者推测VoVNet的精度退化比ResNet快得多的原因是因为VoVNet堆叠基于OSA模块。每个OSA模块都包含一个过渡层,因此每次堆叠OSA模块时,网络中所有层的最短梯度路径都会增加一个。

至于ResNet,它是由残余块堆叠的,残余层的堆叠只会增加最长的梯度路径,而不会增加最短的梯度路径。为了验证模型缩放的可能影响,作者基于YOLOR-CSP进行了一些实验。从实验结果中发现,当堆叠层达到80+层时,CSP fusion first的精度开始优于正常的CSPNet。此时,每个Stage的计算块的最短梯度路径将减少1。随着网络的不断扩大和加深,CSP fusion last将获得最高的精度,但此时所有层的最短梯度路径将减少1。

上述实验结果证实了先前的假设。在上述实验的支持下,如图6所示,在ELAN中设计了“计算块中的堆栈”策略。设计的目的是避免使用过多过渡层的问题,并使整个网络的最短梯度路径快速变长。当网络堆叠得更深时,上述设计策略能够成功地训练ELAN。

51c视觉~YOLO~合集8_YOLO_84

分析

在本节中,将分析基于经典网络架构提出的梯度路径设计策略。

首先,将使用梯度组合的概念分析现有的网络架构和所提出的PRN,该示例表明表现良好的网络架构确实具有更丰富的梯度组合。

然后,将分析所提出的CSPNet如何带来更丰富的梯度组合和其他好处。

 最后,通过stop 梯度分析了梯度路径长度的重要性,从而证实了所提出的ELAN具有设计概念上的优势。

梯度组合分析  

一般研究人员通常使用最短梯度路径和集成特征的数量来衡量网络架构的学习效率和能力。然而,从SparseNet中可以发现,如表1所示,这些度量与准确性和参数使用并不完全相关。作者观察了梯度传播的过程,发现用于更新不同层权重的梯度组合与网络的学习能力非常匹配,在本节中将分析梯度组合。

梯度组合由2种类型的组件组成,即Gradient Timestamp和Gradient Source。

51c视觉~YOLO~合集8_YOLO_85

Gradient Timestamp

图7显示了ResNet、PRN、DenseNet和SparseNet的架构。其中,作者展开了cascaded residual connection和concatenation connection以便于观察梯度传播过程。此外,图7还显示了每个架构上的gradient flow delivery timestamps。gradient sequence相当于广度优先搜索过程,每个sequence将访问上一轮遍历所到达的所有outdegree nodes。

从图7中可以看到PRN使用通道分割策略来丰富由对应于不同通道的权重接收的Gradient Timestamp。至于SparseNet,它使用稀疏连接来使与不同层相对应的权重连接所接收的Timestamp更加可变。

上述两种方法都可以学习具有不同权重的更多样的信息,这使得提出的架构更强大。

51c视觉~YOLO~合集8_YOLO_86

Gradient Source

图8显示了第1个梯度时间戳处ResNet、PRN和DenseNet的Gradient Source。从图8可以看出,connection-based的架构,如DenseNet和SparseNet,属于必须特别处理的网络。这是因为在梯度传播过程中,如果它是由同一层在某个Gradient Timestamp传播的梯度信息,因为梯度流已经被预先分割,所以它不能像普通网络那样被处理。

51c视觉~YOLO~合集8_YOLO_87

对于基于残差连接的架构,如ResNet和PRN,完全相同的梯度信息被传播到所有的outdegree层。由于PRN的outdegree仅连接到其他层的某些通道,因此它可以具有比ResNet整体更丰富的梯度组合。

此外,还有使用其他split-transform merge策略的网络架构,例如基于group convolution-based 的ResNeXt和基于depth-wise convolution的MobileNet等,这也可以增加Gradient Source的数量。

总结

总之,通过对梯度反向传播过程中产生的Gradient Timestamp和Gradient Source的分析,可以清楚地解释现有的流行网络架构以及提出的PRN所学习的信息和参数的利用效率。在ResNet中,不同的层共享相同Timestamp和相同Gradient Source的许多梯度,DenseNet将相同Timestamp但不同Source的梯度信息传递给相应的层。这部分清楚地解释了为什么基于串联连接的DenseNet可以避免像基于残差连接的ResNet那样容易地学习大量无用信息的问题。本文提出的PRN使用简单的masked residual layer来增加沿时间轴的梯度组合的数量,同时保持ResNet网络拓扑,并转移Gradient Source,从而增加Gradient Source的可变性。

跨阶段部分策略分析  

CSPNet旨在增强在线学习能力,同时加快推理速度,因此将分别从这两个方面讨论CSPNet策略的优势。在第3.1节中进行的分析中观察到,即使Gradient Source生成的组合数量相同,当不同组合之间接收到的公共分量减少时,这使得梯度分量更丰富,也使网络学习更好。这种现象实际上发生在为单层权重学习大量参数的过程中。

例如,dropout使用随机伯努利Mask神经元来防止参数学习协同适应信息。从数学模型的角度来看,dropout是通过使用不同输入产生的梯度来更新不同部分的权重,这相当于随机集成结构。至于CSPNet,它通过梯度路径上梯度的时间差和空间变换,直接增加了梯度组合的丰富性。接下来将介绍CSPNet使用什么策略来解决重复梯度信息的问题,以及它如何提高资源利用率。

Duplicated Gradient Information

在第3.1节中分析了梯度组合的数量以及多样性对网络学习能力的影响。在CSPNet中进一步分析了不同层接收的梯度信息内容,并设计了架构以提高参数使用效率。从PRN和SparseNet的梯度组合可以发现,它们在增加梯度组合丰富度的过程中具有共同性,即通过残差连接或密集连接接收大量重复梯度信息的情况显著减少。作者推测,这些重复的梯度是大量权重容易学习共适应信息的主要原因。

对于PRN,它利用梯度定时差来更新部分通道的权重。随着链式规则的更新过程,上述时间差将扩散到整个网络,然后实现更丰富的梯度组合。

此外,CSPNet直接使用cross stage使整个stage的两条路径具有很大的时间差,并使用不同的融合结构来减少stage与stage之间或计算块路径与cross stage connection路径之间的重复梯度信息。

Resource Usage Efficiency

以Darknet-53为例,假设cross stage部分操作根据通道的方向将特征图分成两个相等的部分。此时,残差块的输入通道数和输出通道数减半,而中间的通道数保持不变。

根据上述结构,计算块的总体计算和参数量将减少到原始的一半,而内存峰值是输入特征图和输出特征图的大小之和,因此将减少到原来的2/3。此外,由于整个计算块中卷积层的输入通道和输出通道相等,因此此时的存储器访问成本将最小。

总结

总之,CSPNet成功地将梯度组合的概念与硬件利用效率相结合,使得所设计的网络架构同时提高了学习能力和推理速度。CSPNet仅使用简单的channel split、 cross stage connection并在不改变原有网络计算单元的情况下成功完成预设目标。

CSPNet的另一个优点是它可以应用于许多流行的网络架构,并在各个方面提高整体网络效率。在表2中展示了应用于几种流行网络架构的CSPNet的优异性能。最后,由于CSPNet对许多硬件资源的要求较低,因此它适用于具有更严格硬件约束的设备上的高速推理。

51c视觉~YOLO~合集8_YOLO_88

坡度路径长度分析 

如第3.1节所述,整个网络的梯度路径越短并不意味着学习能力越强。此外,即使整个梯度组合路径的长度是固定的,作者发现ResNet的学习能力仍然下降当堆叠非常深时。然而,作者发现上述问题可以用于在训练阶段使用随机深度将ResNet分解为较浅的随机子网络进行训练,这可以使超深ResNet收敛到更好的结果。

上述现象告诉我们,在分析梯度路径时,不仅可以查看整个网络的最短梯度路径和最长梯度路径,还需要更详细的梯度路径分析。在下文中,将通过在训练期间调整梯度流来控制梯度路径长度,然后根据结果讨论在设计网络架构时的梯度长度策略。

Stop gradient

首先基于ResNet探讨最短梯度长度的重要性。与PlainNet相比,ResNet中的每个残差块除了通过计算块的梯度之外,还通过identity连接在计算块上具有一部分梯度。这里,如图9所示,分别对计算块和identity连接执行Stop梯度操作。

51c视觉~YOLO~合集8_视觉_89

 

当在identity连接上执行Stop梯度时,整个网络的梯度路径将类似于PlainNet。也就是说,最长的梯度路径与最短的梯度路径长度相同,网络深度也相同。当在计算块上执行Stop梯度时,最短的梯度路径将直接穿过所有残差连接并直接到达起始层,此时最短梯度路径长度为1。由于每个计算块有两层,因此其最长梯度路径为2。可以使用这两组设置来观察残差学习本身的好处和梯度路径的减少。 

51c视觉~YOLO~合集8_视觉_90

使用Microsoft COCO数据集中的目标检测和实例分割作为基线模型,对YOLOR CSP进行消融研究,结果如表3所示。实验结果表明,在ResNet中执行缩短的梯度路径确实是深度网络更好收敛的重要因素。

Gradient path planning

根据以上分析和在YOLOR-CSP中使用CSP融合进行的模型缩放实验,重新规划了VoVNet的过渡层并进行了实验。首先移除深度VoVNet的每个OSA模块的过渡层,只留下每个阶段中最后一个OSA模块中的过渡层。以与上述相同的方式组织网络的最长梯度路径和通过每个层的最短梯度路径。同时,还将CSPNet结构应用于上述网络,以进一步观察CSPNet的多功能性,相关实验结果如表4所示。可以清楚地看到,深度VoVNet已经从无法收敛变为能够很好地收敛并获得非常好的精度。

51c视觉~YOLO~合集8_YOLO_91


总结

简而言之,从上述实验和分析中推断,在规划整个网络的梯度路径时,不仅应该考虑最短的梯度路径,而且应该确保每个层的最短梯度路径都可以有效地训练。至于整个网络的最长梯度路径的长度,它将大于或等于任何层的最长的梯度路径。因此,在实施网络级梯度路径设计策略时,需要考虑网络中所有层的最长最短梯度路径长度,以及整个网络的最长梯度路径。

实验

Layer-level梯度路径设计策略

51c视觉~YOLO~合集8_YOLO_92

在PRN实验中将被masked residual layer mask的通道数设置为原始通道数的一半,实验结果如表5所示。由于PRN的设计维护了整个网络的所有参数和拓扑结构,所以只有残差连接中的加法操作减少了一半,所以总体计算量几乎没有变化。

然而,YOLOR-PRN在精度上得到了显著的提高,因为每层都使用了用来更新权重的梯度组合。与YOLOR-v3相比,PRN在目标检测上提高了0.5%的AP,也可以观察到高质量和显著的改进。在实例分割上,将AP提高了0.1%,AP75提高了0.3%。

Stage-level梯度路径设计策略

51c视觉~YOLO~合集8_视觉_93

 

在CSPNet实验中遵循优化推理速度的原则,将梯度分割比设置为50%-50%,实验结果如表6所示。由于只有一半的通道特征图将进入计算块可以清楚地看到,与YOLOR-v3相比,YOLOR-CSP显著减少了22%的计算量。然而,通过丰富的梯度组合,YOLOR-CSP在目标检测上显著提高了1.5%。与YOLOR-v3相比,YOLOR和CSPNet(YOLOR-CSP)的组合增加了更多高质量的结果。

作者进一步比较了梯度流截断操作来减少重复梯度信息,清楚地看到YOLOR-CSP策略比CSP fusion first和CSP fusion last学习得更好。值得一提的是,无论采用何种融合策略,基于csp的体系结构的计算负荷都比YOLOR-v3低得多,精度也远高于YOLOR-v3。

Network-level梯度路径设计策略

51c视觉~YOLO~合集8_视觉_94

 

在ELAN实验中分别测试了计算块在Backbone和Neck的叠加时间,结果如表7所示。从这个表中可以清楚地看出,ELAN在比YOLOR-v3少35%的计算量下,仍然可以提高0.7%的目标检测性能。

在ELAN中可以灵活地设置堆栈的数量,从而在精度和计算量之间进行权衡。从表7中列出的实验结果可以看到,在堆栈设置2,1s,YOLOR-ELAN可以在降低26%计算量的情况下,显著提高目标检测和实例分割的性能分别1.9%和0.6%。

比较  

最后,综合比较了Layer-level 设计策略设计的YOLOR-PRN、Stage-level 设计策略设计的YOLOR-CSP和Network-level 设计策略设计的YOLOR-ELAN,以及基线YOLOR-v3和YOLOv5(r6.2),结果如表8所示。

51c视觉~YOLO~合集8_YOLO_95

从表中可以看出,基于梯度路径设计策略设计的模型在各个方面都优于基于基线的方法。此外,无论计算量如何。由Network-level 设计策略设计的YOLOR-ELAN在参数数量和精度方面可以全面获得最突出的性能。

研究结果表明,基于梯度路径分析能够设计出更好的网络架构设计策略。如果与一般的基于数据路径的策略相比,由数据路径策略设计的体系结构通常需要额外的参数或计算成本来获得更好的精度。相比之下,这三种基于梯度路径设计策略的体系结构可以显著提高整体性能。










5、Yolo5


5.1、YOLO5~火焰烟雾检测

这里提出了一种基于YOLOv5的火焰烟雾检测算法。基于改进的YOLOv5模型,建立了一种具有泛化性能的火焰烟雾检测算法。改进后的YOLOv5模型精度可达99.5%,对火焰烟雾的检测效果更加准确。

本次介绍的文章是广东石油化工学院发表的一篇火焰烟雾检测的文章,题目为《Flame smoke detection algorithm based on YOLOv5 in petrochemical plant》。

石油化工装置火灾烟雾检测,可以预防火灾,保证生产安全和生命安全。文章旨在解决复杂工厂背景下火焰烟雾检测中的漏检和误检问题。

文章提出了一种基于YOLOv5的火焰烟雾检测算法。目标回归损失函数(CIoU)用于改善目标检测中的漏检和误检,提高模型的检测性能。改进后的激活函数避免了梯度消失,保持了算法较高的实时性。数据增强技术用于增强网络提取特征的能力,提高小目标检测模型的准确性。根据火焰烟雾的实际情况,改进了YOLOv5模型的损失函数和激活函数。基于改进的YOLOv5模型,建立了一种具有泛化性能的火焰烟雾检测算法。将改进后的模型与SSD和YOLOv4-tiny进行了比较。改进后的YOLOv5模型精度可达99.5%,对火焰烟雾的检测效果更加准确。改进后的网络模型在运行时间和精度上均优于现有方法。

针对火焰烟雾检测的实际特殊性,建立了一种改进的基于YOLOv5的火焰烟雾检测网络模型。通过改进损失函数来达到优化模型的目的,并结合非线性能力较强的激活函数来避免网络的过拟合。该方法有助于改善火焰烟雾检测中的漏检和误检问题,可进一步推广到行人目标检测和车辆行驶识别中。

工厂的安全生产一直是一个不容忽视的问题。以石化工厂为例,由于生产车间密集,存在大量易燃易爆危险品,一旦发生火灾,极易诱发灾难性后果,造成环境污染,严重威胁生产安全和人员生命财产安全。因此,及时发现和预警控制早期火灾是安全生产的现实需求。厂区内火焰烟雾检测报警技术已受到国际、国内的重视。

火灾早期探测主要通过烟雾传感器和温度传感器来实现。例如,烟雾传感器通过检测烟雾浓度来完成防火。这种方法在室内或一些小场所有很好的表现。但在复杂环境中,由于气流环境、热障效应等因素的影响,再加上传感器检测距离较近、稳定性较低,依靠传感器检测温度、浓度等指标,很难准确获取现场实时信号数据信息。

虽然现有的目标检测研究已经取得了重大突破,但在实际的目标检测中,需要对整个图像进行卷积,需要更大的视场来满足算法的简洁快速。YOLO是一种目标检测器,它使用深度卷积神经网络学习的特征来检测目标。近年来,大量研究将其用于不同类型图像的智能检测,然而,模型结构复杂,火焰检测精度不够。

针对YOLOv5算法模型体积小、检测速度快的优异性能,以及处理厂区火焰烟雾检测问题的复杂性,文章在专门改进的YOLOv5的基础上建立了一种植物火焰烟雾检测算法,用于解决火焰烟雾漏检和误检问题。该算法将原有的GIoU _ Loss替换为CIoU _ Loss作为边界盒的损失函数,并利用SiLU激活函数来避免网络的过拟合。最后,通过与SSD、YOLOv4-tiny和YOLOv7算法的比较,验证了所提算法的有效性和可达性。

模型

A. YOLOv5网络模型及算法改进

损失函数的改进

损失函数是评估回归和分类问题的重要指标。在深度学习网络中,反向传播时间对误差估计至关重要。因此,本节通过引入更好的理论CIoU _ Loss 损失函数,改进了GIoU _ Loss 。

在目标检测中,需要对检测盒与真实盒的检测效果进行比较。通用网络中使用的GIoU _ Loss解决了在IoU的基础上,由于不同目标盒重叠而导致的比值无法优化的问题。GIoU的计算过程如下式第一个算式所示。

51c视觉~YOLO~合集8_YOLO_96

上式第二个算式中,A为预测箱,B为实箱,IoU为传统的交点-并集比,即预测箱与实箱的交点面积与并集面积之比;Ac为预测框与实框最小包围矩形的面积。U为预测框和实框的面积。第三个算式中,LGIOU为GIoU的损失。

当预测框包含在目标框内时,GIoU _ Loss退化为IoU _ Loss,相对位置关系无法区分。为了更好地优化目标盒和预测盒,本文提出用CIoU _ Loss代替原来的GIoU _ Loss作为包围盒损失函数。CIoU的计算过程如下边第一个算式所示。

51c视觉~YOLO~合集8_YOLO_97

上述第二个算式中,ωA, hA为实际帧宽,ωB, hB为预测帧宽,α是权重,υ度量长宽比的相似度。C为两个矩形的最小对角线限定比例尺;ρ是A和B的中心点的欧氏距离;上述第三个算式中,LCIoU为CIoU的损失。

激活函数的改进

激活函数的目的是提高神经网络的非线性拟合能力。在YOLOv5网络结构中,骨干网中的LeakyReLU激活函数替换为SiLU(Sigmoid-Weighted Linear Units)激活函数。SiLU激活函数借鉴了ReLU函数族的思想,但更流畅。由于SiLU激活函数具有较强的非线性能力,可以解决LeakyReLU梯度爆炸的问题。如下图所示,改进后的CBS模块表示卷积层、批处理归一层和SiLU激活函数的串联模式。

51c视觉~YOLO~合集8_视觉_98

SiLU激活函数表达式如下: 

51c视觉~YOLO~合集8_YOLO_99

如上式第一个算式中,Sigmoid是常用的激活单元,SiLU函数表示Sigmoid的加权组合。由于SiLU激活函数本身具有自稳定特性,如上述第二个算式所示,它可以有效抑制大量权重的学习,从而避免网络过拟合,降低网络泛化性能。

实验结果与分析

A. 获取数据集

所获得的数据包含图像和视频两种格式类型。将得到的视频转换为视频图像序列,去除部分相似度高的图片。经过处理的样本图片被打乱,生成一个新的数据集用于训练。为了更好地评价工厂中火焰烟雾的目标识别性能,实验选择火焰和烟雾两种类型作为目标识别对象如下图,可以更好地反映YOLO模型在真实环境中的效果。

51c视觉~YOLO~合集8_YOLO_100

为了从有限的工厂火焰烟雾数据集中充分获取目标特征信息,通过随机变换剔除模型中部分相似度较高的样本图像,防止过拟合导致特征学习停止,有利于模型的泛化。随机变换包括翻转、旋转、尺度变换、随机抠图、高斯噪声、随机模糊、颜色抖动等,以达到扩展数据集的目的。文章采用马赛克数据增强技术,通过随机缩放、随机裁剪和随机发射,将4张随机图像拼接成1张图像,丰富了数据集,提高了小目标的检测精度,增强了网络特征提取能力。下图显示了Mosaic数据增强结果。

51c视觉~YOLO~合集8_视觉_101

B. 自适应图像缩放

在一般的检测任务中,数据集图像的长度和宽度是不同的。由于小目标分布不均匀,严重影响了网络运行效率。

常见的处理方法是在训练和测试时将样本图像缩放到固定大小,然后将其发送到网络进行学习。如果在输入端仅以马赛克增强作为小样本图像处理的主要手段,则提高了小目标的检测精度,而减少了大目标的数据集,导致算法的平均精度下降。因此,传统的数据处理方法效果并不理想。

文章采用自适应缩放技术对数据输入端的数据进行处理。通过将原始图像缩放到标准尺寸,解决了原始数据集样本大小不同的问题。通过计算缩放后的尺度大小,得到黑边的填充值,在图像周围自适应添加最小黑边,减少计算量,从而加快检测速度。向这个示例添加黑边的效果如下图所示。

51c视觉~YOLO~合集8_视觉_102

C. 数据标注

本文使用图像标注软件labellmg对样本图像的真实值进行手动标注,选择对应目标区域的标注矩形框,自定义目标类别,标注信息将以.xml格式保存。双击打开“saved.xml”文件,文件中包含箱子所属类别、图像文件名、路径信息、目标类型和数量、箱子大小和中心点坐标。使用LabImage标注工具完成标注,最终获得2585张样本图像,包括火和烟2类,按照8:2的比例分为训练集和测试集,其中训练集图像2068张,测试集图像517张。 

D. 模型训练

用于模型训练的硬件环境:Intel (R) Core (TM) i9-10900K CPU, 32.0 GB内存,NVIDIAGeForceRTX3090。软件环境:Windows10操作系统,Python3.10开发语言,PyTorch深度学习框架。在超参数设置中,batch-size设置为16,最大迭代次数的epoch设置为300,num-workers设置为2。Adam优化器可以在训练过程中达到参数优化的目的,同时保证网络检测精度,减小网络规模和参数数量,增强网络的检测和识别能力。随着训练轮数的增加,总损失值呈下降趋势。当训练轮数为300轮时,模型的精度趋于稳定,总损失值基本稳定在0.03,满足模型收敛的基本要求。可以确定300为模型的最佳训练轮,模型达到了较好的拟合效果。

E. 基于YOLOv5的工厂火焰烟雾检测及结果分析

为了更直观地感受三种算法的测试结果,选取了一组复杂背景和小目标样本图像进行检测。将选取的样本组发送到三种算法的网络模型中进行测试。YOLOv5l、SSD和YOLOv4-tiny算法的检测结果分别如下三图所示。

51c视觉~YOLO~合集8_YOLO_103

YOLOv5l检测四张样本图像的时间分别为0.196s、0.075s、0.082s和0.07s。从上述的检测结果可以得出,在保证目标盒位置准确的前提下,检测小目标的可靠性达到80%以上,检测大目标的可靠性达到90%以上,满足厂区火焰烟雾检测要求,具有良好的检测性能。使用SSD算法对样本组进行测试,从图中可以看出,部分目标未被检测到,被检测目标的可靠性为60%-70%。可以看出,使用SSD算法检测样本组存在目标漏检、置信度低等问题。出现这种现象的原因可能是样本中的背景比较复杂,目标比较小。该模型在训练和学习过程中无法充分提取目标特征。因此SSD算法不能较好地满足厂区火焰烟雾目标检测的指标要求。使用YOLOv4-tiny算法检测样本组的结果如上图所示。yolov -tiny算法对样本组的检测时间分别为0.81 s、0.94 s、0.75 s和0.60 s,是改进YOLOv5算法的8-9倍。

从以上性能指标分析可以看出,基于我们提出的改进YOLOv5算法的火焰烟雾检测结果AP值高于99%,提高了目标检测精度,总损失率下降到0.03,显著低于一般目标检测算法的损失值。与SSD、YOLOv4-tiny和YOLOv7算法相比,基于改进YOLOv5算法的石化工厂火焰烟雾检测具有速度快、体积小的优点,较好地满足了石化工厂火焰烟雾检测的基本要求。

结论

文章提出了一种基于改进YOLOv5的工厂背景下火焰烟雾实时检测算法。目的是解决复杂背景、小目标和多目标火焰烟雾检测中的漏检和误检问题。

该模型采用速度快、精度高、尺寸自适应的YOLOv5l作为基本模型。文章的主要贡献如下:

(1)为了更好地优化目标盒和预测盒,采用CIoU _ Loss代替原有的GIoU _ Loss作为包围盒损失函数,加快了模型的收敛速度,提高了模型的检测性能;

(2)为避免网络过拟合,将卷积模块的LeakyReLU激活函数替换为SiLU激活函数,解决LeakyReLU梯度消失和梯度爆炸的问题,防止其泛化性能降低;

(3)针对数据集中目标小、背景复杂的问题,采用马赛克数据增强技术,提高小目标的检测精度,增强网络特征提取能力。自适应图像调整技术可以自适应地在图像周围添加最少的黑边,减少参数计算量,从而更好地平衡网络模型的精度和速度。

(4)对预训练的YOLOv5进行火焰烟雾测试和评估,并与其他算法进行比较。

实验结果表明,该算法能够很好地检测出火焰烟雾样本图像中的多目标和小目标,对于数据集中的复杂背景、漏检、误检等问题有较好的改善。该方法在火焰烟雾检测的实际场景中具有一定的优势。但是需要考虑的是,当检测背景与烟雾的相似度过高时,算法在检测视频样本时会出现较低的帧率。原因是数据集本身存在很多小目标,使用马赛克数据增强会导致模型泛化能力较差。

 




5.2、YOLOv5~手势识别

YOLO系列算法以其强悍的性能在图像领域大放异彩。本文利用YOLOV5实现了一个有趣的功能,对手势进行训练识别,并识别显示出对应的emoji。本文给出了详细教程和完整实践代码,感兴趣的同学可以跟着动起手来复现。

本文利用YOLOV5对手势进行训练识别,并识别显示出对应的emoji,如同下图:

51c视觉~YOLO~合集8_视觉_104

本文整体思路如下。提示:本文含完整实践代码,代码较长,建议先看文字部分的实践思路,代码先马后看

51c视觉~YOLO~合集8_YOLO_105

一 、YOLOV5训练数据集

1. 安装环境依赖

本教程所用环境:YOLOV5版本是V3.1。

通过git clone 将源码下载到本地,通过pip install -r requirements.txt 安装依赖包  (其中官方要求python>=3.8 and torch>=1.6)。

我的环境是:系统环境Ubuntu16.04;cuda版本10.2;cudnn版本7.6.5;torch版本1.6.0;python版本3.8

2. 准备手势识别数据集

其中手势数据集已上传至开源数据平台Graviti,包含了完整代码。

手势数据集地址:https://gas.graviti.cn/dataset/datawhale/HandPose?utm_medium=0831datawhale

注:代码在数据地址的讨论区

2.1 数据集的采集以及标注

手势数据采集的代码:

import cv2

def main():
    total_pics = 1000
    cap = cv2.VideoCapture(0)
    
    pic_no = 0
    flag_start_capturing = False
    frames = 0
    
    while True:
        ret,frame = cap.read()
        frame = cv2.flip(frame,1)
        cv2.imwrite("hand_images/" +str(pic_no) +".jpg",frame)
        cv2.imshow("Capturing gesture",frame)
        cv2.waitKey(10)
        pic_no += 1
        if pic_no == total_pics:
            break

main()

在yolov5目录下创建VOC2012文件夹(名字自己定义的),目录结构就是VOC数据集的,对应如下:

VOC2012
../Annotations   #这个是存放数据集图片对应的xml文件
../images  #这个存放图片的
../ImageSets/Main  #这个主要是存放train.txt,test.txt,val.txt和trainval.txt四个文件。里面的内容是训练集、测试集、验证集以及训练验证集的名字(不带扩展后缀名)。

示例:

VOC2012文件夹下内容:

51c视觉~YOLO~合集8_YOLO_106

Annotations文件中是xml文件(labelimg标注的): 

51c视觉~YOLO~合集8_视觉_107

images为VOC数据集格式中的JPRGImages: 

51c视觉~YOLO~合集8_YOLO_108

ImageSets文件中Main子文件夹主要存放训练,测试验证集的划分txt。这个划分通过以下脚本代码生成:

# coding:utf-8

import os
import random
import argparse

parser = argparse.ArgumentParser()
#xml文件的地址,根据自己的数据进行修改 xml一般存放在Annotations下
parser.add_argument('--xml_path', default='C:\\Users\\Lenovo\\Desktop\\hand_datasets\\VOC2012\\Annotations\\', type=str, help='input xml label path')
#数据集的划分,地址选择自己数据下的ImageSets/Main
parser.add_argument('--txt_path', default='C:\\Users\\Lenovo\\Desktop\\hand_datasets\\VOC2012\\ImageSets\\Main\\', type=str, help='output txt label path')
opt = parser.parse_args()

trainval_percent = 1.0
train_percent = 0.99
xmlfilepath = opt.xml_path
txtsavepath = opt.txt_path
total_xml = os.listdir(xmlfilepath)
if not os.path.exists(txtsavepath):
    os.makedirs(txtsavepath)

num = len(total_xml)
list_index = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list_index, tv)
train = random.sample(trainval, tr)

file_trainval = open(txtsavepath + 'trainval.txt', 'w')
file_test = open(txtsavepath + 'test.txt', 'w')
file_train = open(txtsavepath + 'train.txt', 'w')
file_val = open(txtsavepath + 'val.txt', 'w')

for i in list_index:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        file_trainval.write(name)
        if i in train:
            file_train.write(name)
        else:
            file_val.write(name)
    else:
        file_test.write(name)

file_trainval.close()
file_train.close()
file_val.close()
file_test.close()

运行代码在Main文件下生成txt文档如下:

51c视觉~YOLO~合集8_视觉_109

2.2 生成yolo训练格式labels

把xml标注信息转换成yolo的txt格式。其中yolo的txt标签格式信息:每个图像对应一个txt文件,文件每一行为一个目标信息,包括classx_center, y_center, width, height 格式。如下图所示:

51c视觉~YOLO~合集8_YOLO_110

创建voc_label.py文件,将训练集,验证集以及测试集生成txt标签,代码如下:

# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
from os import getcwd

sets = ['train', 'val', 'test']
classes = ["four_fingers","hand_with_fingers_splayed","index_pointing_up","little_finger","ok_hand","raised_fist","raised_hand","sign_of_the_horns","three","thumbup","victory_hand"] 
# 11 classes  # 改成自己的类别
abs_path = os.getcwd()
print(abs_path)

def convert(size, box):
    dw = 1. / (size[0])
    dh = 1. / (size[1])
    x = (box[0] + box[1]) / 2.0 - 1
    y = (box[2] + box[3]) / 2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return x, y, w, h

def convert_annotation(image_id):
    in_file = open('/home/yanyq/Ryan/yolov5/VOC2012/Annotations/%s.xml' % (image_id), encoding='UTF-8')
    out_file = open('/home/yanyq/Ryan/yolov5/VOC2012/labels/%s.txt' % (image_id), 'w')
    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)
    for obj in root.iter('object'):
        # difficult = obj.find('difficult').text
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult) == 1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
             float(xmlbox.find('ymax').text))
        b1, b2, b3, b4 = b
        # 标注越界修正
        if b2 > w:
            b2 = w
        if b4 > h:
            b4 = h
        b = (b1, b2, b3, b4)
        bb = convert((w, h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

wd = getcwd()
for image_set in sets:
    if not os.path.exists('/home/yanyq/Ryan/yolov5/VOC2012/labels/'):
        os.makedirs('/home/yanyq/Ryan/yolov5/VOC2012/labels/')
    image_ids = open('/home/yanyq/Ryan/yolov5/VOC2012/ImageSets/Main/%s.txt' % (image_set)).read().strip().split()
    list_file = open('%s.txt' % (image_set), 'w')
    for image_id in image_ids:
        list_file.write(abs_path + '/images/%s.jpg\n' % (image_id))
        convert_annotation(image_id)
    list_file.close()

运行上述脚本后会生成labels文件夹和三个包含数据集的txt文件,其中labels中为图像的yolo格式标注文件,train.txt,test.txt, val.txt文件为划分后图像所在位置的绝对路径。

三个txt文件内容:

51c视觉~YOLO~合集8_视觉_111

2.3 配置文件

1)数据集的配置

在yolov5目录的data文件夹新建一个Emoji.yaml文件(自己定义)。用来存放训练集验证集的划分文件train.txt和val.txt(其中这两个文件是voc_label.py生成的)。具体内容如下:

51c视觉~YOLO~合集8_视觉_112

2)模型的配置文件

一般训练yolo模型的时候,是可以聚类自己标注的框作为先验框(这样可以保证标注样本最大化的利用)。我们这里就直接采用默认值了。

选择一个需要的模型,YOLOV5有提供s、m、l、x版本,其是逐渐增大的架构,也就是训练时间和推理时间都对应增加,我们这里选择s版本。在yolov5文件夹下的models文件夹中打开yolov5s.yaml文件,修改内容如下图(我们选择默认anchor,所以不做修改,只需要更改nc中的类别数,由于我们是11类,所以改成11就可以了):

51c视觉~YOLO~合集8_YOLO_113

到这里我们的自定义数据集以及配置文件创建完毕,下面就是训练模型了。

3.模型训练

3.1、下载预训练模型

在源码yolov5目录下的weights文件夹下提供了下载smlx模型的脚本--download_weights.sh,执行这个脚本就可以下载这四个模型的预训练模型了。

3.2、训练模型

51c视觉~YOLO~合集8_视觉_114

以上参数解释如下:epochs:指的就是训练过程中整个数据集将被迭代多少次,显卡不行你就调小点。batch-size:一次看完多少张图片才进行权重更新,梯度下降的mini-batch,显卡不行你就调小点。cfg:存储模型结构的配置文件。data:存储训练、测试数据的文件。img-size:输入图片宽高,显卡不行你就……。rect:进行矩形训练。resume:恢复最近保存的模型开始训练。nosave:仅保存最终checkpoint。notest:仅测试最后的epoch。evolve:进化超参数。bucket:gsutil bucket。** cache-images:缓存图像以加快训练速度。 weights:权重文件路径。name:重命名results.txt to results_name.txt。device:cuda device, i.e. 0 or 0,1,2,3 or cpu。adam:使用adam优化。multi-scale:多尺度训练,img-size +/- 50%。single-cls:**单类别的训练集

训练只需要运行训练命令就可以了,如下:

$ python train.py  --data Emoji.yaml --cfg yolov5s.yaml --weights weights/yolov5s.pt --batch-size 64 --device "0,1,2,3" --epochs 200 --img-size 640

其中device batch-size 等需要根据自己机器进行设置。

51c视觉~YOLO~合集8_YOLO_115

4.模型测试

评估模型好坏就是在有标注的测试集或验证集上进行模型效果的评估,在目标检测中最常使用的评估指标为mAP。yolov5文件下的test.py文件中指定了数据集的配置文件和训练结果模型如下:

51c视觉~YOLO~合集8_视觉_116

通过以下命令进行模型测试:

python test.py --data data/Emoji.yaml --weights runs/train/exp2/weights/best.pt --augment

模型测试效果:

51c视觉~YOLO~合集8_YOLO_117

测试结果图:

51c视觉~YOLO~合集8_YOLO_118

二、YOLOV5模型转换

1.安装依赖库

pip install onnx coremltools onnx-simplifier

2.导出ONNX模型

python models/export.py --weights runs/train/exp2/weights/best.pt --img 640 --batch 1

51c视觉~YOLO~合集8_YOLO_119

此时在best.pt同级目录下生成了best.mlmodel best.onnx best.torchscript.pt三个文件,我们只需best.onnx,这个文件可以直接用netron打开查看模型结构。

3.用onnx-simplifer简化模型

为什么要简化?

在训练完深度学习的pytorch或者tensorflow模型后,有时候需要把模型转成 onnx,但是很多时候,很多节点比如cast节点,Identity 这些节点可能都不需要,我们需要进行简化,这样会方便我们把模型转成ncnn或者mnn等这些端侧部署的模型格式或者通过tensorRT进行部署。

python -m onnxsim best.onnx yolov5-best-sim.onnx

51c视觉~YOLO~合集8_YOLO_120

 

完成后就生成了简化版本的模型yolov5-best-sim.onnx。

三、YOLOV5转换成ncnn模型

1、onnx转.param .bin

由上述生成了yolov5-best-sim.onnx这个模型,我们利用ncnn自带的工具onnx2ncnn.exe(这个工具是自己编译生成的,我这里是在windows下编译生成的,可以用linux下的可执行文件)生成yolov5s.param  yolov5s.bin两个文件。

在windows平台下ctrl+r   cmd命令行窗口输入:

onnx2ncnn.exe yolov5-best-sim.onnx yolov5s.param yolov5s.bin

51c视觉~YOLO~合集8_视觉_121

转换的过程中会出现上图所示的ncnn不支持层,下边就是要修改param文件,把不支持层改成支持层。

2、修改.param 参数去除不支持的网络层

去掉不支持的网络层,打开转换得到的yolov5s.param文件,前面几行需要删除的是标红部分。(注意我们训练yoloV5的版本是V3.1,这里不同的版本可能会不同。)

51c视觉~YOLO~合集8_视觉_122

修改结果如下绿色框和红色框中的。因为去掉了10层所以变成191  228。并用YoloV5Focus网络层代替去掉的10层,而YoloV5Focus网络层中的images代表该层的输入,207代表的输出名,这个是根据下边一层的卷积层输入层数写的。 

51c视觉~YOLO~合集8_YOLO_123

修改网路的输出shape:

当基于修改后的网路使用ncnn/examples/yolov5测试时会发现出现图片中一堆乱框,这种情况需要修改网路的输出部分。在保证输出名一致的情况下,修改Reshape中的0=-1,使的最终的输出shape不固定。具体的修改地方以及修改之前和之后见下图。

51c视觉~YOLO~合集8_YOLO_124

3、ncnn的c++测试代码实现

以下是用C++实现的完整代码。建议一划到底,先看最后的整体思路

#include <string>
#include <vector>
#include "iostream"  
//#include <fstream>  
//#include < ctime >
//#include <direct.h>
//#include <io.h>

// ncnn
#include "ncnn/layer.h"
#include "ncnn/net.h"
#include "ncnn/benchmark.h"
//#include "gpu.h"

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2/imgproc.hpp>
#include "opencv2/opencv.hpp"  

using namespace std;
using namespace cv;

static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
static ncnn::PoolAllocator g_workspace_pool_allocator;

static ncnn::Net yolov5;

class YoloV5Focus : public ncnn::Layer
{
public:
 YoloV5Focus()
 {
  one_blob_only = true;
 }

 virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const
 {
  int w = bottom_blob.w;
  int h = bottom_blob.h;
  int channels = bottom_blob.c;

  int outw = w / 2;
  int outh = h / 2;
  int outc = channels * 4;

  top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator);
  if (top_blob.empty())
   return -100;

#pragma omp parallel for num_threads(opt.num_threads)
  for (int p = 0; p < outc; p++)
  {
   const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2);
   float* outptr = top_blob.channel(p);

   for (int i = 0; i < outh; i++)
   {
    for (int j = 0; j < outw; j++)
    {
     *outptr = *ptr;

     outptr += 1;
     ptr += 2;
    }

    ptr += w;
   }
  }

  return 0;
 }
};
DEFINE_LAYER_CREATOR(YoloV5Focus)

struct Object
{
 float x;
 float y;
 float w;
 float h;
 int label;
 float prob;
};

static inline float intersection_area(const Object& a, const Object& b)
{
 if (a.x > b.x + b.w || a.x + a.w < b.x || a.y > b.y + b.h || a.y + a.h < b.y)
 {
  // no intersection
  return 0.f;
 }

 float inter_width = std::min(a.x + a.w, b.x + b.w) - std::max(a.x, b.x);
 float inter_height = std::min(a.y + a.h, b.y + b.h) - std::max(a.y, b.y);

 return inter_width * inter_height;
}

static void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right)
{
 int i = left;
 int j = right;
 float p = faceobjects[(left + right) / 2].prob;

 while (i <= j)
 {
  while (faceobjects[i].prob > p)
   i++;

  while (faceobjects[j].prob < p)
   j--;

  if (i <= j)
  {
   // swap
   std::swap(faceobjects[i], faceobjects[j]);

   i++;
   j--;
  }
 }

#pragma omp parallel sections
 {
#pragma omp section
  {
   if (left < j) qsort_descent_inplace(faceobjects, left, j);
  }
#pragma omp section
  {
   if (i < right) qsort_descent_inplace(faceobjects, i, right);
  }
 }
}


static void qsort_descent_inplace(std::vector<Object>& faceobjects)
{
 if (faceobjects.empty())
  return;

 qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1);
}

static void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold)
{
 picked.clear();

 const int n = faceobjects.size();

 std::vector<float> areas(n);
 for (int i = 0; i < n; i++)
 {
  areas[i] = faceobjects[i].w * faceobjects[i].h;
 }
 for (int i = 0; i < n; i++)
 {
  const Object& a = faceobjects[i];

  int keep = 1;
  for (int j = 0; j < (int)picked.size(); j++)
  {
   const Object& b = faceobjects[picked[j]];

   // intersection over union
   float inter_area = intersection_area(a, b);
   float union_area = areas[i] + areas[picked[j]] - inter_area;
   // float IoU = inter_area / union_area
   if (inter_area / union_area > nms_threshold)
    keep = 0;
  }
  if (keep)
   picked.push_back(i);
 }
}

static inline float sigmoid(float x)
{
 return static_cast<float>(1.f / (1.f + exp(-x)));
}

static void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects)
{
 const int num_grid = feat_blob.h;

 int num_grid_x;
 int num_grid_y;
 if (in_pad.w > in_pad.h)
 {
  num_grid_x = in_pad.w / stride;
  num_grid_y = num_grid / num_grid_x;
 }
 else
 {
  num_grid_y = in_pad.h / stride;
  num_grid_x = num_grid / num_grid_y;
 }

 const int num_class = feat_blob.w - 5;

 const int num_anchors = anchors.w / 2;

 for (int q = 0; q < num_anchors; q++)
 {
  const float anchor_w = anchors[q * 2];
  const float anchor_h = anchors[q * 2 + 1];

  const ncnn::Mat feat = feat_blob.channel(q);

  for (int i = 0; i < num_grid_y; i++)

  {
   for (int j = 0; j < num_grid_x; j++)
   {
    const float* featptr = feat.row(i * num_grid_x + j);

    // find class index with max class score
    int class_index = 0;
    float class_score = -FLT_MAX;
    for (int k = 0; k < num_class; k++)
    {
     float score = featptr[5 + k];
     if (score > class_score)
     {
      class_index = k;
      class_score = score;
     }
    }

    float box_score = featptr[4];

    float confidence = sigmoid(box_score) * sigmoid(class_score);

    if (confidence >= prob_threshold)
    {
     float dx = sigmoid(featptr[0]);
     float dy = sigmoid(featptr[1]);
     float dw = sigmoid(featptr[2]);
     float dh = sigmoid(featptr[3]);

     float pb_cx = (dx * 2.f - 0.5f + j) * stride;
     float pb_cy = (dy * 2.f - 0.5f + i) * stride;

     float pb_w = pow(dw * 2.f, 2) * anchor_w;
     float pb_h = pow(dh * 2.f, 2) * anchor_h;

     float x0 = pb_cx - pb_w * 0.5f;
     float y0 = pb_cy - pb_h * 0.5f;
     float x1 = pb_cx + pb_w * 0.5f;
     float y1 = pb_cy + pb_h * 0.5f;

     Object obj;
     obj.x = x0;
     obj.y = y0;
     obj.w = x1 - x0;
     obj.h = y1 - y0;
     obj.label = class_index;
     obj.prob = confidence;
     objects.push_back(obj);
    }
   }
  }
 }
}

extern "C" {

 void release()
 {
  fprintf(stderr, "YoloV5Ncnn finished!");

  //ncnn::destroy_gpu_instance();
 }

 int init()
 {
  fprintf(stderr, "YoloV5Ncnn init!\n");
  ncnn::Option opt;
  opt.lightmode = true;
  opt.num_threads = 4;
  opt.blob_allocator = &g_blob_pool_allocator;
  opt.workspace_allocator = &g_workspace_pool_allocator;
  opt.use_packing_layout = true;

  yolov5.opt = opt;

  yolov5.register_custom_layer("YoloV5Focus", YoloV5Focus_layer_creator);
  // init param
  {
   int ret = yolov5.load_param("yolov5s.param");  
   if (ret != 0)
   {
    std::cout << "ret= " << ret << std::endl;
    fprintf(stderr, "YoloV5Ncnn, load_param failed");
    return -301;
   }
  }

  // init bin
  {
   int ret = yolov5.load_model("yolov5s.bin");  
   if (ret != 0)
   {
    fprintf(stderr, "YoloV5Ncnn, load_model failed");
    return -301;
   }
  }
  return 0;
 }

 int detect(cv::Mat img, std::vector<Object> &objects)
 {

  double start_time = ncnn::get_current_time();
  const int target_size = 320;

  // letterbox pad to multiple of 32
  const int width = img.cols;//1280
  const int height = img.rows;//720
  int w = img.cols;//1280
  int h = img.rows;//720
  float scale = 1.f;
  if (w > h)
  {
   scale = (float)target_size / w;//640/1280
   w = target_size;//640
   h = h * scale;//360
  }
  else
  {
   scale = (float)target_size / h;
   h = target_size;
   w = w * scale;
  }
  cv::resize(img, img, cv::Size(w, h));
  ncnn::Mat in = ncnn::Mat::from_pixels(img.data, ncnn::Mat::PIXEL_BGR2RGB, w, h);

  // pad to target_size rectangle
  // yolov5/utils/datasets.py letterbox
  int wpad = (w + 31) / 32 * 32 - w;
  int hpad = (h + 31) / 32 * 32 - h;
  ncnn::Mat in_pad;
  ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f);
  // yolov5
  //std::vector<Object> objects;
  {
   const float prob_threshold = 0.4f;
   const float nms_threshold = 0.51f;

   const float norm_vals[3] = { 1 / 255.f, 1 / 255.f, 1 / 255.f };
   in_pad.substract_mean_normalize(0, norm_vals);

   ncnn::Extractor ex = yolov5.create_extractor();
   //ex.set_vulkan_compute(use_gpu);

   ex.input("images", in_pad);
   std::vector<Object> proposals;

   // anchor setting from yolov5/models/yolov5s.yaml

   // stride 8
   {
    ncnn::Mat out;
    ex.extract("output", out);
    ncnn::Mat anchors(6);
    anchors[0] = 10.f;
    anchors[1] = 13.f;
    anchors[2] = 16.f;
    anchors[3] = 30.f;
    anchors[4] = 33.f;
    anchors[5] = 23.f;

    std::vector<Object> objects8;
    generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8);
    proposals.insert(proposals.end(), objects8.begin(), objects8.end());
   }

   // stride 16
   {
    ncnn::Mat out;
    ex.extract("771", out);

    ncnn::Mat anchors(6);
    anchors[0] = 30.f;
    anchors[1] = 61.f;
    anchors[2] = 62.f;
    anchors[3] = 45.f;
    anchors[4] = 59.f;
    anchors[5] = 119.f;

    std::vector<Object> objects16;
    generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16);

    proposals.insert(proposals.end(), objects16.begin(), objects16.end());
   }
   // stride 32
   {
    ncnn::Mat out;
    ex.extract("791", out);
    ncnn::Mat anchors(6);
    anchors[0] = 116.f;
    anchors[1] = 90.f;
    anchors[2] = 156.f;
    anchors[3] = 198.f;
    anchors[4] = 373.f;

    anchors[5] = 326.f;

    std::vector<Object> objects32;
    generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32);

    proposals.insert(proposals.end(), objects32.begin(), objects32.end());
   }

   // sort all proposals by score from highest to lowest
   qsort_descent_inplace(proposals);
   // apply nms with nms_threshold
   std::vector<int> picked;
   nms_sorted_bboxes(proposals, picked, nms_threshold);

   int count = picked.size();
   objects.resize(count);
   for (int i = 0; i < count; i++)
   {
    objects[i] = proposals[picked[i]];

    // adjust offset to original unpadded
    float x0 = (objects[i].x - (wpad / 2)) / scale;
    float y0 = (objects[i].y - (hpad / 2)) / scale;
    float x1 = (objects[i].x + objects[i].w - (wpad / 2)) / scale;
    float y1 = (objects[i].y + objects[i].h - (hpad / 2)) / scale;

    // clip
    x0 = std::max(std::min(x0, (float)(width - 1)), 0.f);
    y0 = std::max(std::min(y0, (float)(height - 1)), 0.f);
    x1 = std::max(std::min(x1, (float)(width - 1)), 0.f);
    y1 = std::max(std::min(y1, (float)(height - 1)), 0.f);
    objects[i].x = x0;
    objects[i].y = y0;
    objects[i].w = x1;
    objects[i].h = y1;
   }
  }

  return 0;
 }
}


static const char* class_names[] = {
 "four_fingers","hand_with_fingers_splayed","index_pointing_up","little_finger",
 "ok_hand","raised_fist","raised_hand","sign_of_the_horns","three","thumbup","victory_hand"
};


void draw_face_box(cv::Mat& bgr, std::vector<Object> object) //主要的emoji显示函数
{
 for (int i = 0; i < object.size(); i++)
 {
  const auto obj = object[i];
  cv::rectangle(bgr, cv::Point(obj.x, obj.y), cv::Point(obj.w, obj.h), cv::Scalar(0, 255, 0), 3, 8, 0);
  std::cout << "label:" << class_names[obj.label] << std::endl;
  string emoji_path = "emoji\\" + string(class_names[obj.label]) + ".png"; //这个是emoji图片的路径
  cv::Mat logo = cv::imread(emoji_path);
  if (logo.empty()) {
   std::cout << "imread logo failed!!!" << std::endl;
   return;
  }
  resize(logo, logo, cv::Size(80, 80));
  cv::Mat imageROI = bgr(cv::Range(obj.x, obj.x + logo.rows), cv::Range(obj.y, obj.y + logo.cols));  //emoji的图片放在图中的位置,也就是手势框的旁边
  logo.copyTo(imageROI); //把emoji放在原图中
 }

}

int main()
{
 Mat frame;

 VideoCapture capture(0);
 init();
 while (true)
 {
  capture >> frame;            
  if (!frame.empty()) {          
   std::vector<Object> objects;
   detect(frame, objects);
   draw_face_box(frame, objects);
   imshow("window", frame);  
  }
  if (waitKey(20) == 'q')    
   break;
 }

 capture.release();     

 return 0;
}

这里是首先用yolov5s识别出手势,然后利用图像ROI融合,把相应的Emoji缩放到80x80大小显示在手势框的旁边,实现根据不同的手势显示相应的Emoji。

4、实现emoji和手势的映射

到这里,我们终于大功告成!达成了开头的效果。

图就是最上面的





5.3、YOLO5~PCB板缺陷检测

利用YOLOv5算法实现对PCB板上的缺陷进行检测识别。

一、数据集介绍

使用的DeepPCB缺陷数据集中的所有图像都是从线性扫描CCD获得的,分辨率约为每1毫米48个像素,以上述方式从采样图像中手动检查

测试图像的原始大小约为16k x 16k像素, 然后将它们裁剪成许多大小为640 x 640的子图像,共1500张图片,DeepPCB数据集中的部分图片如下图所示。

PCB面板数据集自己去找

51c视觉~YOLO~合集8_视觉_125

对于测试图像中的每个缺陷,我们使用轴对齐的边界框和一个类ID。如上所示,我们标注了六种常见的PCB缺陷类型:open、short、mousebite、spur、pin-hole、spur。

由于实际测试图像中只有少数缺陷,我们根据 PCB 缺陷模式在每个测试图像上手动论证一些人工缺陷,这导致每个640 x 640图像中大约有3到12个缺陷。

PCB缺陷数如下图所示。我们将1000 张图像作为训练集,剩下的作为测试集。

51c视觉~YOLO~合集8_视觉_126

二、构建训练数据集 1、先构建数据集文件夹

本人按照VOC格式创建数据集,具体格式如下:

├── data
│   ├── xml  进行 detection 任务时的标签文件,xml 形式,文件名与图片名一一对应
│   ├── image  存放.jpg 格式的图片文件
│   ├── label  存放label标注信息的txt文件,与图片一一对应
│   ├── txt  存放原始标注信息,x1,y1,x2,y2,type
├── dataSet(train,val,test建议按照8:1:1比例划分)
│   ├── train.txt  写着用于训练的图片名称
│   ├── val.txt  写着用于验证的图片名称
│   ├── trainval.txt  train与val的合集
│   ├── test.txt  写着用于测试的图片名称

2、数据集格式转换

原始的标注信息是保存成txt文件,txt文件里面的每一行都包含一个标注信息,格式为x1,y1,x2,y2,type,这里 (x1,y1) 和 (x2,y2) 是缺陷边界框的左上角和右下角

type是匹配后的整数 ID:0-background、1-open、2-short、3-mousebite、4-spur、5-copper、6-pin-hole。通过一下代码进行转换:

import os
import cv2
import time
from xml.dom import minidom
 
name_dict = {'0': 'background', '1': 'open', '2': 'short',
             '3': 'mousebite', '4': 'spur', '5': 'copper', '6': 'pin-hole'}
 
 
def transfer_to_xml(pic, txt, file_name,xml_save_path):
    if not os.path.exists(xml_save_path):
        os.makedirs(xml_save_path,exist_ok=True)
 
    img = cv2.imread(pic)
    img_w = img.shape[1]
    img_h = img.shape[0]
    img_d = img.shape[2]
    doc = minidom.Document()
 
    annotation = doc.createElement("annotation")
    doc.appendChild(annotation)
    folder = doc.createElement('folder')
    folder.appendChild(doc.createTextNode('visdrone'))
    annotation.appendChild(folder)
 
    filename = doc.createElement('filename')
    filename.appendChild(doc.createTextNode(file_name))
    annotation.appendChild(filename)
 
    source = doc.createElement('source')
    database = doc.createElement('database')
    database.appendChild(doc.createTextNode("Unknown"))
    source.appendChild(database)
 
    annotation.appendChild(source)
 
    size = doc.createElement('size')
    width = doc.createElement('width')
    width.appendChild(doc.createTextNode(str(img_w)))
    size.appendChild(width)
    height = doc.createElement('height')
    height.appendChild(doc.createTextNode(str(img_h)))
    size.appendChild(height)
    depth = doc.createElement('depth')
    depth.appendChild(doc.createTextNode(str(img_d)))
    size.appendChild(depth)
    annotation.appendChild(size)
 
    segmented = doc.createElement('segmented')
    segmented.appendChild(doc.createTextNode("0"))
    annotation.appendChild(segmented)
 
    with open(txt, 'r') as f:
        lines = [f.readlines()]
        for line in lines:
            for boxes in line:
                box = boxes.strip('\n')
                box = box.split(" ")
                x_min = box[0]
                y_min = box[1]
                x_max = box[2]
                y_max = box[3]
                object_name = name_dict[box[4]]
                if object_name != "background":
                    object = doc.createElement('object')
                    nm = doc.createElement('name')
                    nm.appendChild(doc.createTextNode(object_name))
                    object.appendChild(nm)
                    pose = doc.createElement('pose')
                    pose.appendChild(doc.createTextNode("Unspecified"))
                    object.appendChild(pose)
                    truncated = doc.createElement('truncated')
                    truncated.appendChild(doc.createTextNode("1"))
                    object.appendChild(truncated)
                    difficult = doc.createElement('difficult')
                    difficult.appendChild(doc.createTextNode("0"))
                    object.appendChild(difficult)
                    bndbox = doc.createElement('bndbox')
                    xmin = doc.createElement('xmin')
                    xmin.appendChild(doc.createTextNode(x_min))
                    bndbox.appendChild(xmin)
                    ymin = doc.createElement('ymin')
                    ymin.appendChild(doc.createTextNode(y_min))
                    bndbox.appendChild(ymin)
                    xmax = doc.createElement('xmax')
                    xmax.appendChild(doc.createTextNode(str(x_max)))
                    bndbox.appendChild(xmax)
                    ymax = doc.createElement('ymax')
                    ymax.appendChild(doc.createTextNode(str(y_max)))
                    bndbox.appendChild(ymax)
                    object.appendChild(bndbox)
                    annotation.appendChild(object)
                    with open(os.path.join(xml_save_path, file_name + '.xml'), 'w') as x:
                        x.write(doc.toprettyxml())
                    x.close()
    f.close()
 
 
if __name__ == '__main__':
    t = time.time()
    print('Transfer .txt to .xml...ing....')
    txt_folder = 'data/PCBDatasets/txt'
    txt_file = os.listdir(txt_folder)
    img_folder = 'data/PCBDatasets/image'
    xml_save_path = 'data/PCBDatasets/xml/'
 
    for txt in txt_file:
        txt_full_path = os.path.join(txt_folder, txt)
        img_full_path = os.path.join(img_folder, txt.split('.')[0] + '.jpg')
 
        try:
            transfer_to_xml(img_full_path, txt_full_path, txt.split('.')[0],xml_save_path)
        except Exception as e:
            print(e)
 
    print("Transfer .txt to .XML sucessed. costed: {:.3f}s...".format(time.time() - t))

3、训练集划分代码

主要是将数据集分类成训练数据集和测试数据集,默认train,val,test按照比例进行随机分类,运行后dataSet文件夹中会出现四个文件

主要是生成的训练数据集和测试数据集的图片名称,如下图。同时data目录下也会出现这四个文件,内容是训练数据集和测试数据集的图片路径。

import os
import random
 
 
trainval_percent = 0.9
train_percent = 0.9
xmlfilepath = 'data/PCBDatasets/xml/'
txtsavepath = 'data/PCBDatasets/dataSet/'
total_xml = os.listdir(xmlfilepath)
 
num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)
 
ftrainval = open('data/PCBDatasets/dataSet/trainval.txt', 'w')
ftest = open('data/PCBDatasets/dataSet/test.txt', 'w')
ftrain = open('data/PCBDatasets/dataSet/train.txt', 'w')
fval = open('data/PCBDatasets/dataSet/val.txt', 'w')
 
for i in list:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftrain.write(name)
        else:
            fval.write(name)
    else:
        ftest.write(name)
 
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()

4、生成yolo格式的标签

主要是将图片数据集标注后的xml文件中的标注信息读取出来并写入txt文件,运行后在label文件夹中出现所有图片数据集的标注信息

# xml解析包
 
import xml.etree.ElementTree as ET
import pickle
import os
 
# os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表
 
from os import listdir, getcwd
from os.path import join
 
 
sets = ['train', 'test', 'val']
classes = ['open', 'short','mousebite','spur', 'copper', 'pin-hole']
 
 
# 进行归一化操作
 
def convert(size, box):  # size:(原图w,原图h) , box:(xmin,xmax,ymin,ymax)
    dw = 1./size[0]     # 1/w
    dh = 1./size[1]     # 1/h
    x = (box[0] + box[1])/2.0   # 物体在图中的中心点x坐标
    y = (box[2] + box[3])/2.0   # 物体在图中的中心点y坐标
    w = box[1] - box[0]         # 物体实际像素宽度
    h = box[3] - box[2]         # 物体实际像素高度
    x = x*dw    # 物体中心点x的坐标比(相当于 x/原图w)
    w = w*dw    # 物体宽度的宽度比(相当于 w/原图w)
    y = y*dh    # 物体中心点y的坐标比(相当于 y/原图h)
    h = h*dh    # 物体宽度的宽度比(相当于 h/原图h)
    return (x, y, w, h)    # 返回 相对于原图的物体中心点的x坐标比,y坐标比,宽度比,高度比,取值范围[0-1]
 
 
 
def convert_annotation(image_id):
    '''
    将对应文件名的xml文件转化为label文件,xml文件包含了对应的bunding框以及图片长款大小等信息,
    通过对其解析,然后进行归一化最终读到label文件中去,也就是说
    一张图片文件对应一个xml文件,然后通过解析和归一化,能够将对应的信息保存到唯一一个label文件中去
    labal文件中的格式:calss x y w h  同时,一张图片对应的类别有多个,所以对应的bounding的信息也有多个
    '''
    # 对应的通过year 找到相应的文件夹,并且打开相应image_id的xml文件,其对应bund文件
    in_file = open('data/PCBDatasets/xml/%s.xml' % (image_id), encoding='utf-8')
    # 准备在对应的image_id 中写入对应的label,分别为
    # <object-class> <x> <y> <width> <height>
    out_file = open('data/PCBDatasets/label/%s.txt' % (image_id), 'w', encoding='utf-8')
    # 解析xml文件
    tree = ET.parse(in_file)
    # 获得对应的键值对
    root = tree.getroot()
    # 获得图片的尺寸大小
    size = root.find('size')
    # 如果xml内的标记为空,增加判断条件
    if size != None:
        # 获得宽
        w = int(size.find('width').text)
        # 获得高
        h = int(size.find('height').text)
        # 遍历目标obj
        for obj in root.iter('object'):
            # 获得difficult ??
            difficult = obj.find('difficult').text
            # 获得类别 =string 类型
            cls = obj.find('name').text
            # 如果类别不是对应在我们预定好的class文件中,或difficult==1则跳过
            if cls not in classes or int(difficult) == 1:
                continue
            # 通过类别名称找到id
            cls_id = classes.index(cls)
            # 找到bndbox 对象
            xmlbox = obj.find('bndbox')
            # 获取对应的bndbox的数组 = ['xmin','xmax','ymin','ymax']
            b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
                 float(xmlbox.find('ymax').text))
            print(image_id, cls, b)
            # 带入进行归一化操作
            # w = 宽, h = 高, b= bndbox的数组 = ['xmin','xmax','ymin','ymax']
            bb = convert((w, h), b)
            # bb 对应的是归一化后的(x,y,w,h)
            # 生成 calss x y w h 在label文件中
            out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
 
 
# 返回当前工作目录
 
wd = getcwd()
print(wd)
 
 
for image_set in sets:
    '''
    对所有的文件数据集进行遍历
    做了两个工作:
    1.将所有图片文件都遍历一遍,并且将其所有的全路径都写在对应的txt文件中去,方便定位
    2.同时对所有的图片文件进行解析和转化,将其对应的bundingbox 以及类别的信息全部解析写到label 文件中去
         最后再通过直接读取文件,就能找到对应的label 信息
    '''
    # 先找labels文件夹如果不存在则创建
    if not os.path.exists('data/PCBDatasets/labels/'):
        os.makedirs('data/PCBDatasets/labels/')
    # 读取在ImageSets/Main 中的train、test..等文件的内容
    # 包含对应的文件名称
    image_ids = open('data/PCBDatasets/dataSet/%s.txt' % (image_set)).read().strip().split()
   
    list_file = open('data/PCBDatasets/%s.txt' % (image_set), 'w')
    # 将对应的文件_id以及全路径写进去并换行
    for image_id in image_ids:
        list_file.write('data/PCBDatasets/image/%s.jpg\n' % (image_id))
        # 调用  year = 年份  image_id = 对应的文件名_id
        convert_annotation(image_id)
    # 关闭文件
    list_file.close()

label文件夹中某文件内容如下:

51c视觉~YOLO~合集8_YOLO_127

三、修改配置文件1、数据配置文件

首先需要在/yolov5-master/data文件夹中,新建一个PCBDetect.yaml文件,内容设置如下:

train: data/PCBDatasets/dataSet/train.txt
val:  data/PCBDatasets/dataSet/val.txt
test: data/PCBDatasets/dataSet/test.txt
 
nc: 6
 
names: ['copper', 'mousebite', 'open', 'pin-hole', 'short', 'spur']

2、网络参数修改

对yolov5-master/model文件夹中,对yolov5x.yaml(根据自己选择的模型而定)文件内容修改。

51c视觉~YOLO~合集8_YOLO_128

3、trian.py修改

主要用到的几个参数:–weights,–cfg,–data,–epochs,–batch-size,–img-size,–project,-workers

重点注意:–weights,–cfg,–data,其他的默认即可(batch_size,workers根据自己电脑属性进行设置)。

51c视觉~YOLO~合集8_YOLO_129

四、训练及测试1、训练

在完成上述所有的操作之后,就可以进行训练,在命令窗口输入python train.py即可以进行训练。

51c视觉~YOLO~合集8_视觉_130

 

2、测试

在训练完成后可以利用测试集对训练后的模型进行测试,利用val.py文件进行测试,主要修改一下地方:

51c视觉~YOLO~合集8_视觉_131

测试完成后会输出map、precision、recall等指标,具体如下图所示: 

51c视觉~YOLO~合集8_YOLO_132

P-R曲线如下图所示:

51c视觉~YOLO~合集8_YOLO_133

同时也可以利用detect.py文件对测试集进行测试,将检测后的框绘制在图像上,部分测试结果如下图所示: 

51c视觉~YOLO~合集8_YOLO_134

 

 大大的demo 完事了 不过靠不靠谱我也不敢保证~~






5.4、YOLOv5-IoU

还是yolo5系列哦

这次主要讲是用于边界框回归的DIoU损失和CIoU损失和用于抑制冗余检测框的DIoU-NMS。

边界框回归是目标检测的关键步骤,在现有方法中,虽然-norm loss 被广泛用于边界框回归,但它不是针对评估指标量身定制的,即 Intersection over Union (IoU)。最近,已经提出了 IoU 损失和generalized IoU (GIoU) Loss作为评估IoU的指标 ,但仍然存在收敛速度慢和回归不准确的问题。在本文中,我们通过结合预测框和目标框之间的归一化距离来提出距离-IoU (DIoU) Loss,它在训练中的收敛速度比 IoU 和 GIoU Loss快得多。

此外,本文总结了边界框回归中的三个几何因素,即

重叠面积(overlap area)、中心点距离(central point distance)和高宽比(aspect ratio),在此基础上提出了完全损失,从而促进了更快的收敛和更优的性能。通过将 和损失结合到最先进的目标检测算法中,例如 YOLO v3、SSD 和 Faster RCNN,我们不仅在 IoU 指标方面而且在 GIoU 指标方面都获得了显着的性能提升。此外,DIoU 可以很容易地用于非最大抑制(NMS)作为标准,进一步促进性能提升。

注释:这里IoU指标方面和GIoU指标方面指的是在:目标检测精度测量(mAP值 ),IoU损失计算稳定性等一些方面。

目标检测是计算机视觉任务中的关键问题之一,几十年来一直受到了广泛的研究关注 (Redmon et al. 2016; Redmon and Farhadi 2018; Ren et al. 2015; He et al. 2017; Yang et al. 2018; Wang et al. 2019; 2018). 通常,现有的目标检测方法可以分为:

  • 单阶段-检测,如YOLO系列 (Redmon et al. 2016; Red- mon and Farhadi 2017; 2018) 和SSD (Liu et al. 2016; Fu et al. 2017),
  • 两阶段检测,如 R-CNN系列检测 (Girshick et al. 2014; Girshick 2015; Ren et al. 2015; He et al. 2017),
  • 甚至是多阶段的检测, 像Cascade R-CNN (Cai and Vasconcelos 2018). 尽管存在这些不 同的检测框架,但边界框回归预测一个矩形框来定位目标对象仍然是其中关键步骤。

本文主要是结合论文Distance-IoU Loss: Faster and Better Learning for Bounding Box Regression(https://arxiv.org/pdf/1911.08287.pdf) 对 IoU 的解析学习。

IoU

IoU介绍

Intersection over Union (IoU)在指标评估概述的小节有介绍过IoU,已经对IoU有了初步的认识(其实在yolov5项目中并不是简单的使用,而是用的后面介绍的CIoU )
计算公式:

51c视觉~YOLO~合集8_视觉_135

51c视觉~YOLO~合集8_视觉_136

IoU loss

计算公式:

 

51c视觉~YOLO~合集8_YOLO_137

IoU Loss 优缺点分析

有明显的缺陷 IoU loss只在边界框有重叠时才能工作, 对于不重叠的情况不会提供任何移动梯度 (移动代表预测框朝着目标框重叠的方向移动) 。移动梯度表示无法衡量完全不相交的两个框所产生的的损失(iou固定为0),和两个不同形状的预测框可能产生相同的loss(相同的iou)分别如下图的左边和右边所示。

51c视觉~YOLO~合集8_视觉_138

GIoUGIoU介绍

GIoU的设计初衷就是想解决IoU Loss存在的问题(预测框与真实框不相交时iou恒定为0),设计了一套Generalized Intersection over Union Loss。在IoU的基础上,GIoU还需要找到预测框和真实框的最小外接矩形,然后求出最小外接矩形减去两个预测框union的面积,具体算法流程如下: 

51c视觉~YOLO~合集8_视觉_139

GIoU loss

计算公式 :

51c视觉~YOLO~合集8_视觉_140

51c视觉~YOLO~合集8_YOLO_141

GIoU 优缺点分析

GIoU Loss解决了IoU Loss在不相交情况的问题,在所有性能指标中都可以作为IoU的适当替代品,在目标检测任务中能够得到更高的准确率。

缺点:虽然GIoU可以缓解重叠情况下的梯度消失问题,但它仍有一些局限性。即无法衡量有包含关系时的框回归损失,如下图,三个回归框具有相同的GIoU Loss,但是显然第三个框的回归效果更好。

51c视觉~YOLO~合集8_视觉_142

IoU & GIoU 分析

首先,在本文上部分我们分析了关于原始的IoU损失和GIoU 损失的局限性。下面将通过模拟实验结果对边界框回归的过程进行进一步的解析。(补充说明: 为什么要进行模型实验?  因为仅仅从检测结果来分析边界框回归的过程很难,因为在不受控制的基准中的回归情况往往不全面比如:不同的距离(distances),不同的尺度(scales)和不同的长宽比(aspect ratios)。相反,进行模拟实验,在实验中综合考虑回归情况,然后就可以很容易地分析给定损失函数的问题。)

模拟实验

51c视觉~YOLO~合集8_视觉_143

51c视觉~YOLO~合集8_YOLO_144

 

51c视觉~YOLO~合集8_视觉_145

IoU 和 GIoU 损失的限制

51c视觉~YOLO~合集8_视觉_146

 

在图4中,我们可视化迭代T时对5000个分散点的最终回归误差。

  • 从图4(a)中很容易看出,IoU损失只适用于与目标框重叠的情况。由于∇B总是0,没有重叠的锚框将不会移动。通过添加一个惩罚项见公式(3),
  • GIoU 损失能够更好的缓解非重叠案例的问题,如图所示4(b), 但GIoU的损失显著扩大了盆地,即GIoU的工作面积。但是,在水平方向和垂直方向的情况下,仍然很可能有很大的误差。这是因为GIoU损失中的惩罚项是用来最小化|C−A∪B|,但是C−A∪B的面积通常很小或为0(当两个盒子有包含关系时),然后GIoU几乎退化为IoU损失。只要以适当的学习速率运行足够的迭代GIoU 损失能收敛到很好的解决方案,但收敛速度却是非常慢。从几何上来说,从如图1所示的回归步骤来看,GIoU实际上增大了预测的框大小,用来和目标框重叠,然后IoU项用于预测框与目标框匹配,产生非常缓慢的收敛。

综上所述,在非重叠情况下,IoU损失收敛是糟糕的解决方式,而GIoU损失收敛速度较慢,特别是对于水平和垂直方向的框。在目标检测流程中,IoU和GIoU的损失都不能保证回归的准确性。

DIoU & CIoU

通过前面的IoU和GIoU的分析我们很自然会问以下问题:

  1. 第一,是否可以直接最小化预测框和目标框之间的归一化距离,以实现更快的收敛?
  2. 第二,当与目标框有重叠甚至包含时,如何使回归更准确、更快?

DIoU loss

Distance-IoU 损失:更快更好的边界框回归损失,一般来说, IoU-based损失可以定义为

51c视觉~YOLO~合集8_YOLO_147

 

51c视觉~YOLO~合集8_视觉_148

 

DIoU 和 IoU/GIoU 损失比较

新提出的DIoU损失继承IoU和GIoU损失的一些属性

  1. DIoU损失对回归问题的尺度仍然是不变的
  2. 与GIoU损失类似, DIoU损失可以在与目标框不重叠时为边界框提供移动方向。

51c视觉~YOLO~合集8_YOLO_149

 

Complete IoU Loss

51c视觉~YOLO~合集8_视觉_150

NMS(Non-Maximum Suppression)

介绍

NMS是大多数目标检测算法的最后一步,其中删除了冗余的检测框当它与最高分框的重叠超过一个阈值。Soft-NMS (Bodla et al. 2017) 用连续函数w.r.t.惩罚相邻框的检测分数IoU,产生比原始NMS产生更柔和大和更强大的抑制。IoU-Net (Jiang et al. 2018) 提出了一个新的网络分支来预测定位置信度来指导NMS。最近,自适应NMS(Liu,Huang,和Wang 2019)和Softer-NMS(He et al. 2019)被提出分别研究适当的阈值策略和加权平均策略。在本工作中,简单将DIoU作为原始NMS的标准, 在抑制冗余框时,同时考虑边界框的重叠面积和两个中心点之间的距离。  

Non-Maximum Suppression using DIoU

51c视觉~YOLO~合集8_视觉_151

总结 

51c视觉~YOLO~合集8_YOLO_152

参考文章










6、Yolo8


6.1、YOLOv8~2

又是一个讲v8的 , 本人还没有试 不过听说不错

YOLOv8 是 ultralytics 公司在 2023 年 1月 10 号开源的 YOLOv5 的下一个重大更新版本,目前支持图像分类、物体检测和实例分割任务,在还没有开源时就收到了用户的广泛关注。开源地址:https://github.com/ultralytics/ultralytics

YOLOv8 是一个 SOTA 模型,它建立在以前 YOLO 版本的成功基础上,并引入了新的功能和改进,以进一步提升性能和灵活性。具体创新包括一个新的骨干网络、一个新的 Ancher-Free 检测头和一个新的损失函数,可以在从 CPU 到 GPU 的各种硬件平台上运行。

下面是使用YOLOv8做目标检测和实例分割的演示视频:发不了....

YOLO: A Brief History

YOLO(You Only Look Once)是一种流行的对象检测和图像分割模型,由华盛顿大学的Joseph Redmon和Ali Farhadi开发。YOLO于2015年推出,以其高速度和高精度迅速走红。

  • YOLOv2于2016年发布,通过合并批处理规范化、锚盒和维度集群来改进原始模型
  • 2018年推出的YOLOv3使用更高效的骨干网络、多个锚点和空间金字塔池进一步增强了该模型的性能
  • YOLOv4于2020年发布,引入了Mosaic数据增强、新的无锚检测头和新的丢失功能等创新
  • YOLOv5进一步提高了模型的性能,并添加了超参数优化、集成实验跟踪和自动导出到流行导出格式等新功能
  • YOLOv6于2022年由美团开源,目前正在该公司的许多自动配送机器人中使用
  • YOLOv7在COCO关键点数据集上添加了额外的任务,如姿态估计
  • YOLOv8是Ultralytics公司推出的YOLO的最新版本。作为一款尖端、最先进的(SOTA)车型,YOLOv8在之前版本的成功基础上,引入了新的功能和改进,以增强性能、灵活性和效率。YOLOv8支持全方位的视觉AI任务,包括检测、分割、姿态估计、跟踪和分类。这种多功能性允许用户在不同的应用程序和域中利用YOLOv8的功能

YOLOv8的新特性与可用模型

Ultralytics 并没有直接将开源库命名为 YOLOv8,而是直接使用 ultralytics 这个词,原因是 ultralytics 将这个库定位为算法框架,而非某一个特定算法,一个主要特点是可扩展性。其希望这个库不仅仅能够用于 YOLO 系列模型,而是能够支持非 YOLO 模型以及分类分割姿态估计等各类任务。总而言之,ultralytics 开源库的两个主要优点是:

  • 融合众多当前 SOTA 技术于一体
  • 未来将支持其他 YOLO 系列以及 YOLO 之外的更多算法

Ultralytics为YOLO模型发布了一个全新的存储库。它被构建为 用于训练对象检测、实例分割和图像分类模型的统一框架。

  1. 提供了一个全新的 SOTA 模型,包括 P5 640 和 P6 1280 分辨率的目标检测网络和基于 YOLACT 的实例分割模型。和 YOLOv5 一样,基于缩放系数也提供了 N/S/M/L/X 尺度的不同大小模型,用于满足不同场景需求
  2. 骨干网络和 Neck 部分可能参考了 YOLOv7 ELAN 设计思想,将 YOLOv5 的 C3 结构换成了梯度流更丰富的 C2f 结构,并对不同尺度模型调整了不同的通道数,属于对模型结构精心微调,不再是无脑一套参数应用所有模型,大幅提升了模型性能。不过这个 C2f 模块中存在 Split 等操作对特定硬件部署没有之前那么友好了
  3. Head 部分相比 YOLOv5 改动较大,换成了目前主流的解耦头结构,将分类和检测头分离,同时也从 Anchor-Based 换成了 Anchor-Free
  4. Loss 计算方面采用了 TaskAlignedAssigner 正样本分配策略,并引入了 Distribution Focal Loss
  5. 训练的数据增强部分引入了 YOLOX 中的最后 10 epoch 关闭 Mosiac 增强的操作,可以有效地提升精度

YOLOv8 还高效灵活地支持多种导出格式,并且该模型可以在 CPU 和 GPU 上运行。YOLOv8 模型的每个类别中有五个模型用于检测、分割和分类。YOLOv8 Nano 是最快和最小的,而 YOLOv8 Extra Large (YOLOv8x) 是其中最准确但最慢的。

51c视觉~YOLO~合集8_YOLO_153

如下是使用YOLOv8x做目标检测和实例分割模型的输出:

51c视觉~YOLO~合集8_YOLO_154

如何使用YOLOv8

Pip install the ultralytics package including all requirements in a Python>=3.7 environment with PyTorch>=1.7.

pip install ultralytics

YOLOv8可以通过yolo命令直接在命令行界面(CLI)中使用:

yolo predict model=yolov8n.pt source='https://ultralytics.com/images/bus.jpg'

yolo可以用于各种任务和模式,并接受额外的参数,即imgsz=640。有关示例,请参阅YOLOv8 CLI文档。

YOLOv8 CLI文档:https://docs.ultralytics.com/usage/cli/

YOLOv8也可以直接在Python环境中使用,并接受与上面CLI示例中相同的参数:

from ultralytics import YOLO
# Load a model
model = YOLO("yolov8n.yaml")  # build a new model from scratch
model = YOLO("yolov8n.pt")  # load a pretrained model (recommended for training)
# Use the model
model.train(data="coco128.yaml", epochs=3)  # train the model
metrics = model.val()  # evaluate model performance on the validation set
results = model("https://ultralytics.com/images/bus.jpg")  # predict on an image
success = model.export(format="onnx")  # export the model to ONNX format

模型自动从最新的Ultralytics版本下载。有关更多示例,请参阅YOLOv8 Python文档。

https://docs.ultralytics.com/usage/python/

51c视觉~YOLO~合集8_视觉_155

推理在笔记本电脑GTX1060 GPU上以接近105 FPS的速度运行。我们得到以下输出:

51c视觉~YOLO~合集8_YOLO_156

YOLOv8 Nano 模型在几帧中将猫混淆为狗。让我们使用 YOLOv8 Extra Large 模型对同一视频运行检测并检查输出:

yolo task=detect mode=predict model=yolov8x.pt source='input/video_3.mp4' show=True

Extra Large模型在GTX1060 GPU上的平均运行速度为 17 FPS。

 

51c视觉~YOLO~合集8_视觉_157

实例分割的推理结果

使用YOLOv8 实例分割模型运行推理同样简单。我们只需要更改上面命令中的task和model名称。

yolo task=segment mode=predict model=yolov8x-seg.pt source='input/video_3.mp4' show=True

因为实例分割与对象检测相结合,所以这次的平均 FPS 约为 13。

51c视觉~YOLO~合集8_视觉_158

分割图在输出中看起来非常干净。即使猫在最后几帧中躲在方块下,模型也能够检测并分割它。

图像分类推理结果

最后,由于YOLOv8已经提供了预训练的分类模型,让我们使用该yolov8x-cls模型对同一视频进行分类推理。这是存储库提供的最大分类模型。

yolo task=classify mode=predict model=yolov8x-cls.pt source='input/video_3.mp4' show=True

51c视觉~YOLO~合集8_视觉_159

 

默认情况下,视频使用模型预测的前5个类进行注释。在没有任何后处理的情况下,注释直接匹配ImageNet类名。

 案例

51c视觉~YOLO~合集8_视觉_160

快速检测缺陷并提供重要的安全功能 

计算机视觉可以取代生产线上容易出错的手动零件组装和质量检查。在车内,计算机视觉可以为重要的安全功能提供动力,如分心的驾驶员监控、检测车道偏离、识别其他车辆和行人以及读取交通信号。收集用于训练的图像和视频数据。将从车辆和生产线摄像头收集的视频和图像转换为数据,以建立您的计算机视觉模型。

51c视觉~YOLO~合集8_YOLO_161