YOLOv7-PTQ量化部署

目录

    • 前言
    • 一、PTQ量化浅析
    • 二、YOLOv7模型训练
      • 1. 项目的克隆和必要的环境依赖
        • 1.1 项目的克隆
        • 1.2 项目代码结构整体介绍
        • 1.3 环境安装
      • 2. 数据集和预训练权重的准备
        • 2.1 数据集
        • 2.2 预训练权重准备
      • 3. 训练模型
        • 3.1 修改模型配置文件
        • 3.2 修改数据配置文件
        • 3.3 训练模型
        • 3.4 mAP测试
    • 三、YOLOv7-PTQ量化部署
      • 1. 源码下载
      • 2. 环境配置
        • 2.1 配置CMakeLists.txt
        • 2.2 配置Makefile
      • 3. ONNX导出
        • 3.1 静态batch导出
        • 3.2 动态 batch 的导出
      • 4. PTQ量化
        • 4.1 前置工作
        • 4.2 源码修改
        • 4.3 编译运行
        • 4.4 PTQ模型mAP测试
    • 四、讨论
      • 1. 校准图片数量
      • 2. 不同精度模型对比
      • 3. YOLOv5-PTQ vs. YOLOv7-PTQ
    • 结语
    • 下载链接
    • 参考

前言

博主又来水文章了,最近在学习 YOLOv7 QAT 量化相关的一个 repo,本来想和大家直接分享 QAT 量化的,但转念一想貌似还可以水一篇 PTQ 量化的文章😂,因此博主就准备在这篇文章中分享基于 YOLOv7 的 PTQ 量化部署的相关实现,具体实现在 tensorRT_Pro 这个 repo 中已经提供,博主只是简单过了一遍流程。

博主为初学者,欢迎交流讨论,若有问题欢迎各位看官批评指正!!!😄

一、PTQ量化浅析

在正式开始之前我们先来回顾下关于 PTQ 量化的一些知识,具体可参考:TensorRT量化第四课:PTQ与QAT

TensorRT 有两种量化模式,分别是隐式(implicitly)量化和显式(explicitly)量化。前者在 TRT7 版本之前用得比较多,而后者在 TRT8 版本后才完全支持,具体就是可以加载带有 QDQ 信息的模型然后生成对应量化版本的 engine。

这篇文章主要分享隐式量化即 PTQ 量化,关于显式量化即 QAT 量化我们将在下篇文章中分享。

PTQ(Post-Training Quantization)即训练后量化也叫隐式量化,tensorRT 的训练后量化算法第一次公布是在 2017 年,那年 NVIDIA 放出了使用交叉熵量化的一个 PPT,简单说明了其量化原理和流程,其思想集中在 tensorRT 内部可供用户去使用。对用户是闭源的,我们只能通过 tensorRT 提供的 API 去实现量化。

PTQ 量化不需要训练,只需要提供一些样本图片,然后在已经训练好的模型上进行校准,统计出来需要的每一层的 scale 就可以实现量化了,大概流程如下:

  • 在准备好的校准数据集上评估预训练模型
  • 使用校准数据来校准模型(校准数据可以是训练集的子集)
  • 计算网络中权重和激活的动态范围用来算出量化参数 q-params
  • 使用 q-params 量化网络并执行推理

在这里插入图片描述

图2-1 PTQ量化流程

具体使用就是我们导出 ONNX 模型,转换为 engine 的过程中使用 tensorRT 提供的 Calibration 方法去校准,可以使用 tensorRT 官方提供的 trtexec 工具去实现,也可以使用它提供的 Python 或者 C++ 的 API 接口去实现。

在 tensorRT_Pro 中 INT8 模型的编译就是 PTQ 量化,因此我们只需要提供好 ONNX 模型和校准数据即可,其它不用我们关心。

tensorRT 还提供了多种校准算法,分别适用于不同的任务:

  • EntropyCalibratorV2:适合于基于 CNN 的网络
  • MinMaxCalibrator:适合于 NLP 任务,如 BERT
  • EntropyCalibrator:老版本的交叉熵校准
  • LegacyCalibrator

通过上述这些校准算法进行 PTQ 量化时,tensorRT 会在优化网络的时候尝试 INT8 精度,假设网络某一层在 INT8 精度下的速度优于默认精度(FP32/FP16),则优先使用 INT8。

值得注意的是,PTQ 量化中我们无法控制某一层的精度,因为 tensorRT 是以速度优化为优先的,很可能某一层你想让它跑 INT8 结果却是 FP16,当然 PTQ 优点是流程简单,速度快。

OK!关于 PTQ 量化我们就简单聊下,让我们开始具体的实现吧!!!🚀🚀🚀

二、YOLOv7模型训练

首先我们需要训练一个 YOLOv7 模型,当然拿官方的预训练权重也行,博主这边为了完整性还是整体走一遍流程,熟悉 YOLOv7 模型训练的看官可以跳过直接到量化部分

1. 项目的克隆和必要的环境依赖

1.1 项目的克隆

yolov7 的代码是开源的可直接从 github 官网上下载,源码下载地址是 https://github.com/WongKinYiu/yolov7,由于 yolov7 目前就只固定 v0.1 一个版本,而 v0.1 版本并未提供训练的详细说明,故采用主分支进行模型的训练和部署工作。Linux下代码克隆指令如下

git clone https://github.com/WongKinYiu/yolov7.git

也可手动点击下载,点击右上角的绿色的 Code 按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here【pwd:yolo】下载博主准备好的代码(注意该代码下载于 2023/10/14 日,若有改动请参考最新

在这里插入图片描述

1.2 项目代码结构整体介绍

将下载后的 yolov7 代码解压,其代码目录如下图所示:

在这里插入图片描述

现在来对代码的整体目录做一个介绍:

  • |-cfg:存放yolov7不同模型的yaml文件,如yolov7.yaml、yolov7-tiny.yaml等,包括训练和部署时的yolov7模型yaml
  • |-data:存放一些超参数的配置文件以及配置训练集和验证集路径的coco.yaml文件,如果需要修改自己的数据集,那么需要修改其中的yaml文件
  • |-deploy:针对部署的文件夹
  • |-figure:存放yolov7测试的效果图片
  • |-inference:存放推理时的图片
  • |-models:存放yolov7整体网络模型搭建的py文件
  • |-paper:存放yolov7论文
  • |-scripts:脚本文件,用于获取coco数据集
  • |-tools:该文件夹主要存放一些示例教程,如yolov7关键点检测、yolov7实例分割、yolov7onnx等等
  • |-utils:存放工具类函数,包括loss、metrics、plots函数等
  • |-
    • detect.py:检测代码,包括图像检测、视频流检测等
    • export.py:模型导出代码,如onnx导出
    • hubconf.py:pytorch扩展模型
    • requirements.txt:文本文件,里面包含使用yolov7项目的环境依赖包以及相应的版本号
    • test.py:测试代码
    • train.py:训练代码
    • train_aux.py:训练辅助头代码(不确定)
1.3 环境安装

关于深度学习的环境安装可参考炮哥的利用Anaconda安装pytorch和paddle深度学习环境+pycharm安装—免额外安装CUDA和cudnn(适合小白的保姆级教学),这里不再赘述。如果之前配置过 yolov5 的环境,yolov7 可直接使用。

2. 数据集和预训练权重的准备

2.1 数据集

这里训练采用的数据集是 PASCAL VOC 数据集,但博主并没有使用完整的 VOC 数据集,而是选用了部分数据,具体分布如下:

  • 训练集:(VOC2007train + VOC2007val) x 80% = 4013
  • 验证集:(VOC2007train + VOC2007val) x 20% = 998
  • 测试集:0

这里给出下载链接 Baidu Drive【pwd:yolo】下载解压后整个数据集文件夹内容如下所示:

在这里插入图片描述

其中 images 存放训练集和验证集的图片文件,labels 存放着对应的 YOLO 格式的 .txt 文件。

完整的 VOC 数据集的相关介绍和下载可参考:目标检测:PASCAL VOC 数据集简介

由于大家可能从其它地方拿到的是 XML 格式的标签文件,这里提供一个 XML2YOLO 转换的代码,如下所示:(from ChatGPT)

import os
import cv2
import xml.etree.ElementTree as ET
import shutil
from multiprocessing import Pool, cpu_count
from tqdm import tqdm
import numpy as np
from functools import partialdef process_xml(xml_filename, img_path, xml_path, img_save_path, label_save_path, class_dict, ratio):# 解析 xml 文件xml_file_path = os.path.join(xml_path, xml_filename)tree = ET.parse(xml_file_path)root = tree.getroot()# 获取图像的宽度和高度img_filename = os.path.splitext(xml_filename)[0] + ".jpg"img = cv2.imread(os.path.join(img_path, img_filename))height, width = img.shape[:2]# 随机决定当前图像和标签是属于训练集还是验证集subset = "train" if np.random.random() < ratio else "val"# 打开对应的标签文件进行写入label_file = os.path.join(label_save_path, subset, os.path.splitext(xml_filename)[0] + ".txt")with open(label_file, "w") as file:for obj in root.iter('object'):# 获取类别名并转换为类别IDclass_name = obj.find('name').textclass_id = class_dict[class_name]# 获取并处理边界框的坐标xmlbox = obj.find('bndbox')x1 = float(xmlbox.find('xmin').text)y1 = float(xmlbox.find('ymin').text)x2 = float(xmlbox.find('xmax').text)y2 = float(xmlbox.find('ymax').text)# 计算中心点坐标和宽高,并归一化x_center = (x1 + x2) / 2 / widthy_center = (y1 + y2) / 2 / heightw = (x2 - x1) / widthh = (y2 - y1) / height# 写入文件file.write(f"{class_id} {x_center} {y_center} {w} {h}\n")# 将图像文件复制到对应的训练集或验证集目录shutil.copy(os.path.join(img_path, img_filename), os.path.join(img_save_path, subset, img_filename))def check_and_create_dir(path):# 检查并创建 train 和 val 目录for subset in ['train', 'val']:if not os.path.exists(os.path.join(path, subset)):os.makedirs(os.path.join(path, subset))if __name__ == "__main__":# 1. 定义路径和类别字典,不要使用中文路径img_path = "D:\\Data\\PASCAL_VOC\\VOCdevkit\\VOC2007\\JPEGImages"xml_path = "D:\\Data\\PASCAL_VOC\\VOCdevkit\\VOC2007\\Annotations"img_save_path = "D:\\Data\\PASCAL_VOC\\dataset\\images"label_save_path = "D:\\Data\\PASCAL_VOC\\dataset\\labels"class_dict = {"aeroplane": 0,"bicycle": 1,"bird": 2,"boat": 3,"bottle": 4,"bus": 5,"car": 6,"cat": 7,"chair": 8,"cow": 9,"diningtable": 10,"dog": 11,"horse": 12,"motorbike": 13,"person": 14,"pottedplant": 15,"sheep": 16,"sofa": 17,"train": 18,"tvmonitor": 19
}train_val_ratio = 0.8  # 2. 定义训练集和验证集的比例# 检查并创建必要的目录check_and_create_dir(img_save_path)check_and_create_dir(label_save_path)# 获取 xml 文件列表xml_filenames = os.listdir(xml_path)# 创建进程池并执行with Pool(cpu_count()) as p:list(tqdm(p.imap(partial(process_xml, img_path=img_path, xml_path=xml_path, img_save_path=img_save_path, label_save_path=label_save_path, class_dict=class_dict, ratio=train_val_ratio), xml_filenames), total=len(xml_filenames)))

上述代码的功能是将 PASCAL VOC 格式的数据集(包括 JPEG 图像和 XML 格式的标签文件)转换为 YOLO 需要的 .txt 标签格式,同时会将转换后的数据集按照比例随机划分为训练集和验证集。

你需要修改以下几项:

  • img_path:需要转换的图像文件路径
  • xml_path:需要转换的 xml 标签文件路径
  • img_save_path:转换后保存的图像路径
  • label_save_path:转换后保存的 txt 标签路径
  • class_dict:数据集类别字典
  • train_val_ratio:训练集和验证集划分的比例
  • 注意:以上路径都不要包含中文,Windows 下路径记得使用 \\ 或者 / 防止转义

XML 标签文件中目标框保存的格式是 [xmin, ymin, xmax, ymax] 四个变量,分别代表着未经归一化的左上角和右下角坐标。

YOLO 标签中目标框保存的格式是每一行代表一个目标框信息,每一行共包含 [label_id, x_center, y_center, w, h] 五个变量,分别代表着标签 ID,经过归一化后的中心点坐标和目标框宽高。

关于代码的分析可以参考:tensorRT模型性能测试

至此,数据集的准备工作完毕。

2.2 预训练权重准备

yolov7 预训练权重可以通过 here【pwd:yolo】下载,注意这是 yolov7-v0.1 版本的预训练权重,若后续有版本更新,记得替换。本次训练 VOC 数据集使用的预训练权重为 yolov7-tiny.pt

在这里插入图片描述

3. 训练模型

将准备好的数据集文件夹即 VOC 复制到 yolov7 项目环境中,将准备好的预训练权重 yolov7-tiny.pt 复制到 yolov7 项目环境中,完整的项目结构如下图所示。训练目标检测模型主要修改 cfg 文件夹下的模型配置文件 yolov7-tiny.ymal 以及 data 文件夹下的数据配置文件 coco.yaml

在这里插入图片描述

3.1 修改模型配置文件

由于该项目使用的是 yolov7-tiny.pt 这个预训练权重,所以需要使用 cfg/training 目录下的 yolov7-tiny.yaml 这个文件(由于不同的预训练权重对应不同的网络结构,所以用错预训练权重会报错)。主要修改 yolov7-tiny.yaml 文件的第二行,即需要识别的类别数,由于这里识别 VOC 的 20 个类别,故修改为 20 即可,如下所示

在这里插入图片描述

3.2 修改数据配置文件

修改 data 目录下相应的 yaml 文件,找到目录下的 coco.yaml 文件,主要修改如下:

  • 1. 注释第 4 行
  • 2. 修改第 7 行训练集的路径
  • 3. 修改第 8 行验证集的路径
  • 4. 注释第 9 行,因为未使用到测试集
  • 5. 修改第 12 行需要检测的类别数个数
  • 6. 修改第 15 行需要检测的类别数名称

在这里插入图片描述

3.3 训练模型

在终端执行如下指令即可开始训练,参考自 yolov7 的 README.md/Training

python train.py --workers 8 --device 0 --batch-size 32 --data data/coco.yaml --img 640 640 --cfg cfg/training/yolov7-tiny.yaml --weights 'yolov7-tiny.pt' --name yolov7 --hyp data/hyp.scratch.p5.yaml --epochs 100

博主训练的模型为 p5 models 且使用的是单个 GPU 进行训练,显卡为 RTX3060,操作系统为 Ubuntu20.04,pytorch 版本为 1.12.0,训练时长大概 1 小时左右。训练的参数的指定和 yolov5 差不多,简要解释如下:

  • –-workers 最大工作核心数
  • –-device 指定训练的设备,CPU,0(代表第一个 GPU 设备)
  • –-batch-size 每次输入到网络的图片数
  • -–data 数据配置文件的路径
  • –-img 输入图像的尺寸
  • –-cfg 模型配置文件路径
  • –-weights 预训练权重路径
  • –-name 训练保存的文件夹名字
  • -–hyp 超参数文件路径
  • –epochs 训练轮数

还有其它参数博主并未设置,如 –-multi-scale 多尺度训练等。大家一定要根据自己的实际情况(如显卡算力)指定不同的参数,如果你之前训练过 yolov5,那我相信这对你来说应该是小 case😄

训练完成后的模型权重保存在 run/train/weights 文件夹下,和 yolov5 不同的是它保存了多个权重文件,使用 best.pt 进行后续模型部署量化即可,这里提供博主训练好的权重文件下载链接 Baidu Drive【pwd:yolo】

在这里插入图片描述

3.4 mAP测试

由于后续我们要对模型进行 PTQ 量化,需要一些指标来衡量模型的性能,mAP 是一个重要的衡量指标。我们需要对比量化前后模型的 mAP,首先来看量化前原始 pytorch 模型的 mAP,测试的数据集直接选用验证集的 998 张图片。

我们将置信度阈值设置为 0.001,NMS 阈值设置为 0.65,方便与后续 PTQ 量化模型对比。

mAP 测试的指令如下:

python test.py --data data/coco.yaml --img 640 --batch 32 --conf 0.001 --iou 0.65 --device 0 --weights best.pt --name yolov7_640_val

在这里插入图片描述

测试完成后的结果会保存在 runs/test/yolov7_640_val 文件夹下,这里总结下原始 pytorch 模型的性能

ModelSizemAPval
0.5:0.95
mAPval
0.5
Params
(M)
FLOPs
(G)
YOLOv7-tiny6400.4910.7445.813.3

三、YOLOv7-PTQ量化部署

由于博主手头没有合适的 Jetson 嵌入式设备,因此打算使用自己的主机完成 YOLOv7-PTQ 量化及部署工作,量化部署使用的 repo 是 tensorRT_Pro。

接下来我们主要是针对 tensorRT_Pro 项目中的 YOLOv7 完成 PTQ 模型的量化和部署,体现在 tensorRT_Pro 中其实就是 YOLOv7 的 INT8 量化,本次量化的模型是 YOLOv7-tiny.pt,数据集为 VOC,类别数为 20。

1. 源码下载

tensorRT_Pro 的代码可以直接从 GitHub 官网上下载,源码下载地址是 https://github.com/shouxieai/tensorRT_Pro,Linux 下代码克隆指令如下:

$ git clone https://github.com/shouxieai/tensorRT_Pro

也可手动点击下载,点击右上角的 Code 按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 Baidu Drive【pwd:yolo】 下载博主准备好的源代码(注意代码下载于 2023/9/24 日,若有改动请参考最新

2. 环境配置

需要使用的软件环境有 TensorRT、CUDA、cuDNN、OpenCV、Protobuf,所有软件环境的安装可以参考 Ubuntu20.04部署YOLOv5,这里不再赘述,需要各位看官自行配置好相关环境😄,外网访问较慢,这里提供下博主安装过程中的软件安装包下载链接 Baidu Drive【pwd:yolo】🚀🚀🚀

tensorRT_Pro 提供 CMakeLists.txt 和 Makefile 两种方式编译,二者选一即可

2.1 配置CMakeLists.txt

主要修改六处

1. 修改第 10 行,选择不支持 python (也可选择支持)

set(HAS_PYTHON OFF)

2. 修改第 18 行,修改 OpenCV 路径

set(OpenCV_DIR   "/usr/local/include/opencv4/")

3. 修改第 20 行,修改 CUDA 路径

set(CUDA_TOOLKIT_ROOT_DIR     "/usr/local/cuda-11.6")

4. 修改第 21 行,修改 cuDNN 路径

set(CUDNN_DIR    "/usr/local/cudnn8.4.0.27-cuda11.6")

5. 修改第 22 行,修改 tensorRT 路径

set(TENSORRT_DIR "/opt/TensorRT-8.4.1.5")

6. 修改第 33 行,修改 protobuf 路径

set(PROTOBUF_DIR "/home/jarvis/protobuf")

完整的 CMakeLists.txt 的内容如下:

cmake_minimum_required(VERSION 2.6)
project(pro)option(CUDA_USE_STATIC_CUDA_RUNTIME OFF)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/workspace)# 如果要支持python则设置python路径
set(HAS_PYTHON OFF)                                         # ===== 修改 1 =====
set(PythonRoot "/datav/software/anaconda3")
set(PythonName "python3.9")# 如果你是不同显卡,请设置为显卡对应的号码参考这里:https://developer.nvidia.com/zh-cn/cuda-gpus#compute
#set(CUDA_GEN_CODE "-gencode=arch=compute_75,code=sm_75")# 如果你的opencv找不到,可以自己指定目录
set(OpenCV_DIR   "/usr/local/include/opencv4/")             # ===== 修改 2 =====set(CUDA_TOOLKIT_ROOT_DIR     "/usr/local/cuda-11.6")       # ===== 修改 3 =====
set(CUDNN_DIR    "/usr/local/cudnn8.4.0.27-cuda11.6")       # ===== 修改 4 =====
set(TENSORRT_DIR "/opt/TensorRT-8.4.1.5")                   # ===== 修改 5 =====# set(CUDA_TOOLKIT_ROOT_DIR     "/data/sxai/lean/cuda-10.2")
# set(CUDNN_DIR    "/data/sxai/lean/cudnn7.6.5.32-cuda10.2")
# set(TENSORRT_DIR "/data/sxai/lean/TensorRT-7.0.0.11")# set(CUDA_TOOLKIT_ROOT_DIR  "/data/sxai/lean/cuda-11.1")
# set(CUDNN_DIR    "/data/sxai/lean/cudnn8.2.2.26")
# set(TENSORRT_DIR "/data/sxai/lean/TensorRT-7.2.1.6")# 因为protobuf,需要用特定版本,所以这里指定路径
set(PROTOBUF_DIR "/home/jarvis/protobuf")                   # ===== 修改 6 ======find_package(CUDA REQUIRED)
find_package(OpenCV)include_directories(${PROJECT_SOURCE_DIR}/src${PROJECT_SOURCE_DIR}/src/application${PROJECT_SOURCE_DIR}/src/tensorRT${PROJECT_SOURCE_DIR}/src/tensorRT/common${OpenCV_INCLUDE_DIRS}${CUDA_TOOLKIT_ROOT_DIR}/include${PROTOBUF_DIR}/include${TENSORRT_DIR}/include${CUDNN_DIR}/include
)# 切记,protobuf的lib目录一定要比tensorRT目录前面,因为tensorRTlib下带有protobuf的so文件
# 这可能带来错误
link_directories(${PROTOBUF_DIR}/lib${TENSORRT_DIR}/lib${CUDA_TOOLKIT_ROOT_DIR}/lib64${CUDNN_DIR}/lib
)if("${HAS_PYTHON}" STREQUAL "ON")message("Usage Python ${PythonRoot}")include_directories(${PythonRoot}/include/${PythonName})link_directories(${PythonRoot}/lib)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DHAS_PYTHON")
endif()set(CMAKE_CXX_FLAGS  "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O0 -Wfatal-errors -pthread -w -g")
set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -std=c++11 -O0 -Xcompiler -fPIC -g -w ${CUDA_GEN_CODE}")
file(GLOB_RECURSE cpp_srcs ${PROJECT_SOURCE_DIR}/src/*.cpp)
file(GLOB_RECURSE cuda_srcs ${PROJECT_SOURCE_DIR}/src/*.cu)
cuda_add_library(plugin_list SHARED ${cuda_srcs})
target_link_libraries(plugin_list nvinfer nvinfer_plugin)
target_link_libraries(plugin_list cuda cublas cudart cudnn)
target_link_libraries(plugin_list protobuf pthread)
target_link_libraries(plugin_list ${OpenCV_LIBS})add_executable(pro ${cpp_srcs})# 如果提示插件找不到,请使用dlopen(xxx.so, NOW)的方式手动加载可以解决插件找不到问题
target_link_libraries(pro nvinfer nvinfer_plugin)
target_link_libraries(pro cuda cublas cudart cudnn)
target_link_libraries(pro protobuf pthread plugin_list)
target_link_libraries(pro ${OpenCV_LIBS})if("${HAS_PYTHON}" STREQUAL "ON")set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/example-python/pytrt)add_library(pytrtc SHARED ${cpp_srcs})target_link_libraries(pytrtc nvinfer nvinfer_plugin)target_link_libraries(pytrtc cuda cublas cudart cudnn)target_link_libraries(pytrtc protobuf pthread plugin_list)target_link_libraries(pytrtc ${OpenCV_LIBS})target_link_libraries(pytrtc "${PythonName}")target_link_libraries(pro "${PythonName}")
endif()add_custom_target(yoloDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro yolo
)add_custom_target(yolo_gpuptrDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro yolo_gpuptr
)add_custom_target(yolo_fastDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro yolo_fast
)add_custom_target(centernetDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro centernet
)add_custom_target(alphapose DEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro alphapose
)add_custom_target(retinafaceDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro retinaface
)add_custom_target(dbfaceDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro dbface
)add_custom_target(arcface DEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro arcface
)add_custom_target(bert DEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro bert
)add_custom_target(fallDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro fall_recognize
)add_custom_target(scrfdDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro scrfd
)add_custom_target(lessonDEPENDS proWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/workspaceCOMMAND ./pro lesson
)add_custom_target(pyscrfdDEPENDS pytrtcWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-pythonCOMMAND python test_scrfd.py
)add_custom_target(pyinstallDEPENDS pytrtcWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-pythonCOMMAND python setup.py install
)add_custom_target(pytorchDEPENDS pytrtcWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-pythonCOMMAND python test_torch.py
)add_custom_target(pyyolov5DEPENDS pytrtcWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-pythonCOMMAND python test_yolov5.py
)add_custom_target(pycenternetDEPENDS pytrtcWORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/example-pythonCOMMAND python test_centernet.py
)
2.2 配置Makefile

主要修改六处

1. 修改第 4 行,修改 protobuf 路径

lean_protobuf  := /home/jarvis/protobuf

2. 修改第 5 行,修改 tensorRT 路径

lean_tensor_rt := /opt/TensorRT-8.4.1.5

3. 修改第 6 行,修改 cuDNN 路径

lean_cudnn     := /usr/local/cudnn8.4.0.27-cuda11.6

4. 修改第 7 行,修改 OpenCV 路径

lean_opencv    := /usr/local

5. 修改第 8 行,修改 CUDA 路径

lean_cuda      := /usr/local/cuda-11.6

6. 修改第 9 行,选择不支持 python (也可选择支持)

use_python     := false

完整的 Makefile 的内容如下:

cc        := g++
nvcc      = ${lean_cuda}/bin/nvcclean_protobuf  := /home/jarvis/protobuf		# ===== 修改 1 =====
lean_tensor_rt := /opt/TensorRT-8.4.1.5		# ===== 修改 2 =====
lean_cudnn     := /usr/local/cudnn8.4.0.27-cuda11.6	# ===== 修改 3 =====
lean_opencv    := /usr/local				# ===== 修改 4 =====
lean_cuda      := /usr/local/cuda-11.6		# ===== 修改 5 =====
use_python     := false						# ===== 修改 6 =====
python_root    := /datav/software/anaconda3# python_root指向的lib目录下有个libpython3.9.so,因此这里写python3.9
# 对于有些版本,so名字是libpython3.7m.so,你需要填写python3.7m
# /datav/software/anaconda3/lib/libpython3.9.so
python_name    := python3.9# 如果是其他显卡,请修改-gencode=arch=compute_75,code=sm_75为对应显卡的能力
# 显卡对应的号码参考这里:https://developer.nvidia.com/zh-cn/cuda-gpus#compute
cuda_arch := # -gencode=arch=compute_75,code=sm_75cpp_srcs  := $(shell find src -name "*.cpp")
cpp_objs  := $(cpp_srcs:.cpp=.cpp.o)
cpp_objs  := $(cpp_objs:src/%=objs/%)
cpp_mk    := $(cpp_objs:.cpp.o=.cpp.mk)cu_srcs  := $(shell find src -name "*.cu")
cu_objs  := $(cu_srcs:.cu=.cu.o)
cu_objs  := $(cu_objs:src/%=objs/%)
cu_mk    := $(cu_objs:.cu.o=.cu.mk)include_paths := src        \src/application \src/tensorRT	\src/tensorRT/common  \$(lean_protobuf)/include \$(lean_opencv)/include/opencv4 \$(lean_tensor_rt)/include \$(lean_cuda)/include  \$(lean_cudnn)/include library_paths := $(lean_protobuf)/lib \$(lean_opencv)/lib    \$(lean_tensor_rt)/lib \$(lean_cuda)/lib64  \$(lean_cudnn)/liblink_librarys := opencv_core opencv_imgproc opencv_videoio opencv_imgcodecs \nvinfer nvinfer_plugin \cuda cublas cudart cudnn \stdc++ protobuf dl# HAS_PYTHON表示是否编译python支持
support_define    := ifeq ($(use_python), true) 
include_paths  += $(python_root)/include/$(python_name)
library_paths  += $(python_root)/lib
link_librarys  += $(python_name)
support_define += -DHAS_PYTHON
endifempty         :=
export_path   := $(subst $(empty) $(empty),:,$(library_paths))run_paths     := $(foreach item,$(library_paths),-Wl,-rpath=$(item))
include_paths := $(foreach item,$(include_paths),-I$(item))
library_paths := $(foreach item,$(library_paths),-L$(item))
link_librarys := $(foreach item,$(link_librarys),-l$(item))cpp_compile_flags := -std=c++11 -g -w -O0 -fPIC -pthread -fopenmp $(support_define)
cu_compile_flags  := -std=c++11 -g -w -O0 -Xcompiler "$(cpp_compile_flags)" $(cuda_arch) $(support_define)
link_flags        := -pthread -fopenmp -Wl,-rpath='$$ORIGIN'cpp_compile_flags += $(include_paths)
cu_compile_flags  += $(include_paths)
link_flags        += $(library_paths) $(link_librarys) $(run_paths)ifneq ($(MAKECMDGOALS), clean)
-include $(cpp_mk) $(cu_mk)
endifpro    : workspace/pro
pytrtc : example-python/pytrt/libpytrtc.so
expath : library_path.txtlibrary_path.txt : @echo LD_LIBRARY_PATH=$(export_path):"$$"LD_LIBRARY_PATH > $@workspace/pro : $(cpp_objs) $(cu_objs)@echo Link $@@mkdir -p $(dir $@)@$(cc) $^ -o $@ $(link_flags)example-python/pytrt/libpytrtc.so : $(cpp_objs) $(cu_objs)@echo Link $@@mkdir -p $(dir $@)@$(cc) -shared $^ -o $@ $(link_flags)objs/%.cpp.o : src/%.cpp@echo Compile CXX $<@mkdir -p $(dir $@)@$(cc) -c $< -o $@ $(cpp_compile_flags)objs/%.cu.o : src/%.cu@echo Compile CUDA $<@mkdir -p $(dir $@)@$(nvcc) -c $< -o $@ $(cu_compile_flags)objs/%.cpp.mk : src/%.cpp@echo Compile depends CXX $<@mkdir -p $(dir $@)@$(cc) -M $< -MF $@ -MT $(@:.cpp.mk=.cpp.o) $(cpp_compile_flags)objs/%.cu.mk : src/%.cu@echo Compile depends CUDA $<@mkdir -p $(dir $@)@$(nvcc) -M $< -MF $@ -MT $(@:.cu.mk=.cu.o) $(cu_compile_flags)yolo : workspace/pro@cd workspace && ./pro yoloyolo_gpuptr : workspace/pro@cd workspace && ./pro yolo_gpuptrdyolo : workspace/pro@cd workspace && ./pro dyolodunet : workspace/pro@cd workspace && ./pro dunetdmae : workspace/pro@cd workspace && ./pro dmaedclassifier : workspace/pro@cd workspace && ./pro dclassifieryolo_fast : workspace/pro@cd workspace && ./pro yolo_fastbert : workspace/pro@cd workspace && ./pro bertalphapose : workspace/pro@cd workspace && ./pro alphaposefall : workspace/pro@cd workspace && ./pro fall_recognizeretinaface : workspace/pro@cd workspace && ./pro retinafacearcface    : workspace/pro@cd workspace && ./pro arcfacetest_warpaffine    : workspace/pro@cd workspace && ./pro test_warpaffinetest_yolo_map    : workspace/pro@cd workspace && ./pro test_yolo_maparcface_video    : workspace/pro@cd workspace && ./pro arcface_videoarcface_tracker    : workspace/pro@cd workspace && ./pro arcface_trackertest_all : workspace/pro@cd workspace && ./pro test_allscrfd : workspace/pro@cd workspace && ./pro scrfdcenternet : workspace/pro@cd workspace && ./pro centernetdbface : workspace/pro@cd workspace && ./pro dbfacehigh_perf : workspace/pro@cd workspace && ./pro high_perflesson : workspace/pro@cd workspace && ./pro lessonplugin : workspace/pro@cd workspace && ./pro pluginpytorch : pytrtc@cd example-python && python test_torch.pypyscrfd : pytrtc@cd example-python && python test_scrfd.pypyretinaface : pytrtc@cd example-python && python test_retinaface.pypycenternet : pytrtc@cd example-python && python test_centernet.pypyyolov5 : pytrtc@cd example-python && python test_yolov5.pypyyolov7 : pytrtc@cd example-python && python test_yolov7.pypyyolox : pytrtc@cd example-python && python test_yolox.pypyarcface : pytrtc@cd example-python && python test_arcface.pypyinstall : pytrtc@cd example-python && python setup.py installclean :@rm -rf objs workspace/pro example-python/pytrt/libpytrtc.so example-python/build example-python/dist example-python/pytrt.egg-info example-python/pytrt/__pycache__@rm -rf workspace/single_inference@rm -rf workspace/scrfd_result workspace/retinaface_result@rm -rf workspace/YoloV5_result workspace/YoloX_result@rm -rf workspace/face/library_draw workspace/face/result@rm -rf build@rm -rf example-python/pytrt/libplugin_list.so@rm -rf library_path.txt.PHONY : clean yolo alphapose fall debug# 导出符号,使得运行时能够链接上
export LD_LIBRARY_PATH:=$(export_path):$(LD_LIBRARY_PATH)

3. ONNX导出

  • 训练的模型使用 yolov7-tiny.pt,torch 版本 1.12.1,onnx 版本 1.13.1
  • ONNX 导出参考自 YoloV5案例第一部分,导出ONNX

关于静态 batch 和动态 batch 有以下几点说明,更多细节请查看 YoloV8的动态静态batch如何理解和使用

静态batch

  • 导出的 onnx 指定所有维度均为明确的数字,是静态 shape 模型
  • 在推理的时候,它永远都是同样的 batch 推理,即使你目前只有一个图推理,它也需要 n 个 batch 的耗时
  • 适用于大部分场景,整个代码逻辑非常简单

动态batch

  • 导出的时候指定特定维度为 dynamic,也就是不确定状态
  • 模型推理时才决定所需推理的 batch 大小,耗时最优,但 onnx 复杂度提高了
  • 适用于如 server 有大量不均匀的请求时的场景
3.1 静态batch导出

静态 batch 的导出不需要修改任何内容,直接将训练好的 VOC 权重 best.pt 放在 yolov7 主目录下,在终端执行如下指令:

cd yolov7
python export.py --grid --weights=best.pt

执行完成后会在当前目录生成导出的 best.onnx 模型,用于后续量化部署。

3.2 动态 batch 的导出

动态 batch 的导出也不需要修改任何文件的内容,我们这次利用 onnxsim 第三方库来简化我们的 onnx 模型,首先确保你当前的环境中安装了 onnxsim,否则执行如下指令进行安装:

pip install onnxsim -i https://pypi.tuna.tsinghua.edu.cn/simple

然后将训练好的 VOC 权重 best.pt 放在 yolov7 主目录下,在终端执行如下指令:

cd yolov7
python export.py --dynamic-batch --grid --weights=best.pt

执行完成后会在当前目录生成导出的 best.onnx 模型,用于后续量化部署。

4. PTQ量化

4.1 前置工作

在开始PTQ 量化之前我们需要准备两个东西:模型和校准图片

模型我们采用动态 batch 导出的 best.onnx 模型,将它放在 tensorRT_Pro/workspace 文件夹下

校准图片我们从训练集随机选取 1000 张图片进行校准,将它也放在 tensorRT_Pro/workspace 文件夹下

1000 张校准数据集随机选取的代码如下:

import os
import random
import shutildef random_copy_images(source_folder, destination_folder, num_images=1000):# 确保目标文件夹存在if not os.path.exists(destination_folder):os.makedirs(destination_folder)# 获取源文件夹中的所有图片文件image_files = [file for file in os.listdir(source_folder) if file.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]# 随机选择1000张图片selected_images = random.sample(image_files, min(num_images, len(image_files)))# 复制选中的图片到目标文件夹for image_file in selected_images:source_path = os.path.join(source_folder, image_file)destination_path = os.path.join(destination_folder, image_file)shutil.copy(source_path, destination_path)source_folder = '/home/jarvis/Learn/Datasets/VOC_PTQ/images/train'  # 带有图片的文件夹路径
destination_folder = 'calib_data'       # 目标文件夹路径
num_images = 1000                       # 需要随机获取的图片数量random_copy_images(source_folder, destination_folder, num_images)

你需要修改以下几项:

  • source_folder:源训练集文件夹路径
  • destination_folder:校准数据集文件夹路径
  • num_images:随机选择的图片数量
4.2 源码修改

将上述模型和校准图片准备好后还要修改下源码,yolo 模型的推理代码主要在 src/application/app_yolo.cpp 文件中,我们就只需要修改这一个文件中的内容即可,源码修改较简单主要有以下几点:

  • 1. app_yolo.cpp 177 行,TRT::Mode 修改为 INT8,“yolov7” 改成 “best”
  • 2. app_yolo.cpp 25 行,新增 voclabels 数组,添加 voc 数据集的类别名称
  • 3. app_yolo.cpp 100 行,cocolabels 修改为 voclabels
  • 4. app_yolo.cpp 149 行,“inference” 修改为 “calib_data” 指定校准图片的路径

具体修改如下:

test(Yolo::Type::V7, TRT::Mode::INT8, "best")				// 修改1 177行"yolov7"改成"best"static const char *voclabels[] = {"aeroplane",   "bicycle", "bird",   "boat",       "bottle","bus",         "car",     "cat",    "chair",      "cow","diningtable", "dog",     "horse",  "motorbike",  "person","pottedplant",  "sheep",  "sofa",   "train",      "tvmonitor"};		 // 修改2 25行新增代码,为自训练模型的类别名称for(auto& obj : boxes){...auto name    = voclabels[obj.class_label];	 			// 修改3 100行cocolabels修改为voclabels...
}TRT::compile(mode,                       // FP32、FP16、INT8test_batch_size,            // max batch sizeonnx_file,                  // source model_file,                 // save to{},int8process,"calib_data"				// 修改4 149行 "inference" 修改为 "calib_data"
);
4.3 编译运行

OK!源码修改好了,Makefile 编译文件也搞定了,可以编译运行了,直接在终端执行如下指令即可:

make yolo

图解如下所示:

在这里插入图片描述
在这里插入图片描述

编译运行后在 workspace 文件夹下会生成 INT8 的 engine 模型 best.INT8.trtmodel 用于模型推理,同时它还会生成 best_Yolov5_INT8_result 文件夹,该文件夹下保存了推理的图片

模型推理效果如下图所示:

在这里插入图片描述

4.4 PTQ模型mAP测试

我们再来测试下经过 PTQ 量化后模型的 mAP,tensorRT_Pro 中已经提供了对应 mAP 测试的代码,在 src/application/test_yolo_map.cpp 文件中,我们就只需要修改这一个文件中的内容即可,源码修改较简单主要有以下几点:

  • 1. test_yolo_map.cpp 172 行,修改要测试的验证集文件夹路径
  • 2. test_yolo_map.cpp 175 行,修改要测试的 INT8 模型,yolov5s 修改为 best
  • 3. test_yolo_map.cpp 176 行,Yolo::Type 修改为 V7,TRT::Mode 修改为 INT8
  • 4. test_yolo_map.cpp 125 行,将 save_to_json 函数简单修改下

修改后完整的 test_yolo_map.cpp 如下所示:

#include <builder/trt_builder.hpp>
#include <infer/trt_infer.hpp>
#include <common/ilogger.hpp>
#include <common/json.hpp>
#include "app_yolo/yolo.hpp"
#include <vector>
#include <string>using namespace std;bool requires(const char* name);struct BoxLabel{int label;float cx, cy, width, height;float confidence;
};struct ImageItem{string image_file;Yolo::BoxArray detections;
};vector<ImageItem> scan_dataset(const string& images_root){vector<ImageItem> output;auto image_files = iLogger::find_files(images_root, "*.jpg");for(int i = 0; i < image_files.size(); ++i){auto& image_file = image_files[i];if(!iLogger::exists(image_file)){INFOW("Not found: %s", image_file.c_str());continue;}ImageItem item;item.image_file = image_file;output.emplace_back(item);}return output;
}static void inference(vector<ImageItem>& images, int deviceid, const string& engine_file, TRT::Mode mode, Yolo::Type type, const string& model_name){auto engine = Yolo::create_infer(engine_file, type, deviceid, 0.001f, 0.65f,Yolo::NMSMethod::CPU, 10000);if(engine == nullptr){INFOE("Engine is nullptr");return;}int nimages = images.size();vector<shared_future<Yolo::BoxArray>> image_results(nimages);for(int i = 0; i < nimages; ++i){if(i % 100 == 0){INFO("Commit %d / %d", i+1, nimages);}image_results[i] = engine->commit(cv::imread(images[i].image_file));}for(int i = 0; i < nimages; ++i)images[i].detections = image_results[i].get();
}void detect_images(vector<ImageItem>& images, Yolo::Type type, TRT::Mode mode, const string& model){int deviceid = 0;auto mode_name = TRT::mode_string(mode);TRT::set_device(deviceid);auto int8process = [=](int current, int count, const vector<string>& files, shared_ptr<TRT::Tensor>& tensor){INFO("Int8 %d / %d", current, count);for(int i = 0; i < files.size(); ++i){auto image = cv::imread(files[i]);Yolo::image_to_tensor(image, tensor, type, i);}};const char* name = model.c_str();INFO("===================== test %s %s %s ==================================", Yolo::type_name(type), mode_name, name);if(not requires(name))return;string onnx_file = iLogger::format("%s.onnx", name);string model_file = iLogger::format("%s.%s.trtmodel", name, mode_name);int test_batch_size = 16;if(not iLogger::exists(model_file)){TRT::compile(mode,                       // FP32、FP16、INT8test_batch_size,            // max batch sizeonnx_file,                  // source model_file,                 // save to{},int8process,"inference");}inference(images, deviceid, model_file, mode, type, name);
}bool save_to_json(const vector<ImageItem>& images, const string& file){Json::Value predictions(Json::arrayValue);for(int i = 0; i < images.size(); ++i){auto& image = images[i];auto file_name = iLogger::file_name(image.image_file, false);string image_id = file_name;auto& boxes = image.detections;for(auto& box : boxes){Json::Value jitem;jitem["image_id"] = image_id;jitem["category_id"] = box.class_label;jitem["score"] = box.confidence;auto& bbox = jitem["bbox"];bbox.append(box.left);bbox.append(box.top);bbox.append(box.right - box.left);bbox.append(box.bottom - box.top);predictions.append(jitem);}}return iLogger::save_file(file, predictions.toStyledString());
}int test_yolo_map(){/*结论:1. YoloV5在tensorRT下和pytorch下,只要输入一样,输出的差距最大值是1e-32. YoloV5-6.0的mAP,官方代码跑下来是mAP@.5:.95 = 0.367, mAP@.5 = 0.554,与官方声称的有差距3. 这里的tensorRT版本测试的精度为:mAP@.5:.95 = 0.357, mAP@.5 = 0.539,与pytorch结果有差距4. cv2.imread与cv::imread,在操作jpeg图像时,在我这里测试读出的图像值不同,最大差距有19。而png图像不会有这个问题若想完全一致,请用png图像5. 预处理部分,若采用letterbox的方式做预处理,由于tensorRT这里是固定640x640大小,测试采用letterbox并把多余部分设置为0. 其推理结果与pytorch相近,但是依旧有差别6. 采用warpAffine和letterbox两种方式的预处理结果,在mAP上没有太大变化(小数点后三位差)7. mAP差一个点的原因可能在固定分辨率这件事上,还有是pytorch实现的所有细节并非完全加入进来。这些细节可能有没有找到的部分*/auto images = scan_dataset("/home/jarvis/Learn/Datasets/VOC_PTQ/images/val");INFO("images.size = %d", images.size());string model = "best";detect_images(images, Yolo::Type::V7, TRT::Mode::INT8, model);save_to_json(images, model + ".prediction.json");return 0;
}

上述代码会将 INT8 模型在验证集中所有图像的检测结果存储到一个 JSON 文件中,每个检测到的物体都被序列化为 JSON 格式信息,包括图像 ID、类别 ID、置信度和边界框坐标。后续我们就可以拿着这个预测结果的 JSON 文件和我们真实标签的 JSON 文件通过 COCO Python API 去计算 mAP 指标。

有以下几点需要注意:

  • 博主将 JSON 文件中的 image_id 保存为一个字符串,考虑到图片命名的差异性
  • 博主将 JSON 文件中的 category_id 直接保存为类别标签,没有做转换
  • mAP 测试使用的 NMS_threshold = 0.65f,Conf_threshold = 0.001f 与 pytorch 保持一致
  • 关于 mAP 的相关原理介绍可参考 目标检测mAP计算以及coco评价标准

将源码修改好后,直接在终端执行如下指令即可:

make test_yolo_map

图解如下所示:

在这里插入图片描述

运行成功后在 workspace 文件夹下会生成 best.prediction.json 文件,该 JSON 文件中保存着 INT8 模型在验证集上的推理结果。

我们拿到了模型预测结果的 JSON 文件后,还需要拿到真实标签的 JSON 文件,但是现在我们只有验证集真实的 YOLO 标签文件,因此需要将 YOLO 标签转换为 JSON 文件,转换代码如下:(from chatGPT)

import os
import cv2
import json
import logging
import os.path as osp
from tqdm import tqdm
from functools import partial
from multiprocessing import Pool, cpu_countdef set_logging(name=None):rank = int(os.getenv('RANK', -1))logging.basicConfig(format="%(message)s", level=logging.INFO if (rank in (-1, 0)) else logging.WARNING)return logging.getLogger(name)LOGGER = set_logging(__name__)def process_img(image_filename, data_path, label_path):# Open the image file to get its sizeimage_path = os.path.join(data_path, image_filename)img = cv2.imread(image_path)height, width = img.shape[:2]# Open the corresponding label filelabel_file = os.path.join(label_path, os.path.splitext(image_filename)[0] + ".txt")with open(label_file, "r") as file:lines = file.readlines()# Process the labelslabels = []for line in lines:category, x, y, w, h = map(float, line.strip().split())labels.append((category, x, y, w, h))return image_filename, {"shape": (height, width), "labels": labels}def get_img_info(data_path, label_path):LOGGER.info(f"Get img info")image_filenames = os.listdir(data_path)with Pool(cpu_count()) as p:results = list(tqdm(p.imap(partial(process_img, data_path=data_path, label_path=label_path), image_filenames), total=len(image_filenames)))img_info = {image_filename: info for image_filename, info in results}return img_infodef generate_coco_format_labels(img_info, class_names, save_path):# for evaluation with pycocotoolsdataset = {"categories": [], "annotations": [], "images": []}for i, class_name in enumerate(class_names):dataset["categories"].append({"id": i, "name": class_name, "supercategory": ""})ann_id = 0LOGGER.info(f"Convert to COCO format")for i, (img_path, info) in enumerate(tqdm(img_info.items())):labels = info["labels"] if info["labels"] else []img_id = osp.splitext(osp.basename(img_path))[0]img_h, img_w = info["shape"]dataset["images"].append({"file_name": os.path.basename(img_path),"id": img_id,"width": img_w,"height": img_h,})if labels:for label in labels:c, x, y, w, h = label[:5]# convert x,y,w,h to x1,y1,x2,y2x1 = (x - w / 2) * img_wy1 = (y - h / 2) * img_hx2 = (x + w / 2) * img_wy2 = (y + h / 2) * img_h# cls_id starts from 0cls_id = int(c)w = max(0, x2 - x1)h = max(0, y2 - y1)dataset["annotations"].append({"area": h * w,"bbox": [x1, y1, w, h],"category_id": cls_id,"id": ann_id,"image_id": img_id,"iscrowd": 0,# mask"segmentation": [],})ann_id += 1with open(save_path, "w") as f:json.dump(dataset, f)LOGGER.info(f"Convert to COCO format finished. Resutls saved in {save_path}")if __name__ == "__main__":# Define the pathsdata_path   = "/home/jarvis/Learn/Datasets/VOC_PTQ/images/val"label_path  = "/home/jarvis/Learn/Datasets/VOC_PTQ/labels/val"class_names = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus","car", "cat", "chair", "cow", "diningtable", "dog", "horse","motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]  # 类别名称请务必与 YOLO 格式的标签对应save_path   = "./val.json"img_info = get_img_info(data_path, label_path)generate_coco_format_labels(img_info, class_names, save_path)

上述代码的功能是将 YOLO 格式的数据集(包括图像文件和对应的 .txt 标签文件)转换成 COCO JSON 格式的标注。转换后的数据包括一个 JSON 标签文件,JSON 标签文件中包含了每个图像的所有物体的类别和边界框信息。

你需要修改以下几项:

  • data_path:需要转换的图像文件路径
  • label_path:需要转换的 txt 标签文件路径
  • class_names:数据集的类别列表,请务必与 YOLO 标签的相对应
  • save_path:转换后 JSON 文件保存的路径
  • 注意:以上路径都不要包含中文,Windows 下路径记得使用 \\ 或者 / 防止转义

YOLO 标签中目标框保存的格式是每一行代表一个目标框信息,每一行共包含 [label_id, x_center, y_center, w, h] 五个变量,分别代表着标签 ID,经过归一化后的中心点坐标和目标框宽高。

JSON 文件中目标框保存的格式是 [x,y,w,h] 四个变量,分别代表着经过归一化的左上角坐标和目标框宽高。

关于代码的分析可以参考:tensorRT模型性能测试

至此,两个 JSON 文件都准备好了,一个是模型推理的预测结果,一个是真实结果。拿到两个 JSON 文件后我们就可以进行 mAP 测试了,具体代码如下:

from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval# Run COCO mAP evaluation
# Reference: https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynbannotations_path = "val.json"
results_file = "best.prediction.json"
cocoGt = COCO(annotation_file=annotations_path)
cocoDt = cocoGt.loadRes(results_file)
imgIds = sorted(cocoGt.getImgIds())
cocoEval = COCOeval(cocoGt, cocoDt, 'bbox')
cocoEval.params.imgIds = imgIds
cocoEval.evaluate()
cocoEval.accumulate()
cocoEval.summarize()

你需要修改以下几项:

  • annotations_path:真实标签的 JSON 文件路径
  • results_file:模型预测结果的 JSON 文件路径

执行后测试结果如下图所示:

在这里插入图片描述

我们将它与原始 pytorch 的模型放在一起进行对比下:

ModelSizemAPval
0.5:0.95
mAPval
0.5
Params
(M)
FLOPs
(G)
YOLOv7-tiny6400.4910.7445.813.3
YOLOv7-tiny-INT86400.3460.562--

可以看到相比于原始 pytorch 模型,PTQ 量化后的模型 mAP 下降了近 18 个点

那博主之前有测试过 YOLOv5 的 PTQ 量化,其 mAP 也就下降了近 6 个点,YOLOv7 的 PTQ 量化模型精度损失未免太严重了呀,是什么原因导致的呢?🤔

经博主测试发现是由于校准图片数量的原因,1000 张校准图片能将 YOLOv5 量化得很好,但这并不适用于 YOLOv7,YOLOv7 的校准图片选取 10 张的校准结果也比 1000 张要好,具体细节可以查看 4.1 小节

OK!至此 YOLOv7 模型的 PTQ 量化到这里结束了,各位看官可以在自己的数据集测试下 PTQ 量化后模型的性能。

四、讨论

1. 校准图片数量

那可能有不少看官好奇为什么校准图片选择 1000 张呢?是由什么来决定的呢?🤔

这小节我们就来看看校准图片数量对 PTQ 量化模型的影响,博主测试了在不同校准图片下量化的 PTQ 模型在同一个验证集上的 mAP,分别在训练集随机挑选了 10、50、100、500、600、700、800、900、1000、4013 张图片,其中 4013 张图片是整个训练集的数量。

测试结果如下表所示:

ModelCalib DatamAPval
0.5:0.95
mAPval
0.5
YOLOv7-tiny-INT8100.3990.621
YOLOv7-tiny-INT8500.4640.699
YOLOv7-tiny-INT81000.4690.703
YOLOv7-tiny-INT85000.4710.703
YOLOv7-tiny-INT86000.3450.559
YOLOv7-tiny-INT87000.4710.703
YOLOv7-tiny-INT88000.4730.705
YOLOv7-tiny-INT89000.3470.562
YOLOv7-tiny-INT810000.3460.562
YOLOv7-tiny-INT84013(all)0.3440.557

可视化图如下所示:

在这里插入图片描述

从表中的数据我们可以分析得到下面的一些结论:

1. 校准数据量与模型性能的关系:校准数据的数量对模型 PTQ 量化后的性能有明显的影响。特别是当校准数据从 10 增加到 500 时,模型的 mAP 明显增加,说明在这个区间内,增加校准数据可以有效提高模型的性能。

2. 最佳校准数据量:在这个测试中,当使用 800 张校准图片时,模型达到了最高点的 mAP(分别为 0.473 和 0.705)。这意味着并不是校准数据越多越好,需要找到一个适当的平衡点。

3. 校准数据过多可能导致性能下降:当校准数据从 800 增加到 900、100 或 4013 时,模型的性能反而有所下降。这可能是因为过多的校准数据可能引入了噪声,使得量化的过程过于复杂,从而降低了模型的性能。

4. 整个训练集并非最佳选择:尽管使用整个训练集(4013 张图片)进行校准可能看起来是一个直观的选择,但在这个测试中,它并没有提供最佳的性能。这可能意味着在实际应用中,只需要选择一个子集进行校准即可,无需使用整个训练集。

5. 初步校准数据的不足:当仅使用 10 张校准图片时,模型的性能也较低。这说明在实际应用中,如果只有有限的校准数据,可能需要考虑采集更多的数据以提高量化后的模型性能。

综上所述,选择合适的校准数据量是 PTQ 量化的一个重要步骤。不同的模型和应用场景可能需要不同的校准数据量。因此,为了得到最佳的量化性能,可能需要进行多次实验来确定最佳的校准数据量。

博主一般推荐校准图片的数量在 500~1000 张即可,没必要太多,当然也不能太少。

2. 不同精度模型对比

PTQ 量化的模型性能到底怎么样呢?与其它精度的模型相比有哪些优势又有哪些劣势呢?🤔

这个小节我们就来看看不同精度的模型的性能对比,主要从 mAP 和速度两个方面衡量。博主测试了在同一个验证集上原始 pytorch 模型,FP32 模型,FP16 模型,INT8 模型的性能。

原始 pytorch 模型和 INT8 模型性能我们之前已经了解过了,下面我们来看看 FP32 模型和 FP16 模型的性能。

FP32模型

在这里插入图片描述

图4-1 FP32模型速度测试

在这里插入图片描述

图4-2 FP32模型mAP测试

FP16模型

在这里插入图片描述

图4-3 FP16模型速度测试

在这里插入图片描述

图4-4 FP16模型mAP测试

INT8模型

在这里插入图片描述

图4-5 INT8模型速度测试

在这里插入图片描述

图4-6 INT8模型mAP测试(800张校准)

值得注意的是,关于速度的测试我们之前似乎并没有提到,它具体是如何测试的呢?🤔

其实在 inference_and_performance 函数中就有关于速度相关的测试,主要说明如下:

  • 1. 输入分辨率 640x640
  • 2. batch_size = 1
  • 3. 图像预处理 + 推理 + 后处理
  • 4. CUDA-11.6,cuDNN-8.4.0,TensorRT-8.4.1.5
  • 5. NVIDIA RTX3060
  • 6. 测试次数,100 次取平均,去掉 warmup
  • 7. 测试代码:src/application/app_yolo.cpp
  • 8. 测试图像 6 张,位于 workspace/inference
    • 分辨率分别为:810x1080,500x806,1024x684,550x676,1280x720,800x533
  • 9. 测试方式,加载 6 张图后,以原图重复 100 次不停的塞进去。让模型经历完整的图像的预处理,后处理

测试结果如下表所示:

ModelPrecisionmAPval
0.5:0.95
mAPval
0.5
Elapsed Time/msFPS
YOLOv7-tiny.pt-0.4910.744--
YOLOv7-tiny-FP32FP320.4880.7242.82355.15
YOLOv7-tiny-FP16FP160.4890.7251.26792.23
YOLOv7-tiny-INT8INT80.4730.7050.941066.55

可视化图如下所示:

在这里插入图片描述

从表中的数据我们可以分析得到下面的一些结论:

1. 精度与模型性能的关系

  • 当我们从原始 pytorch 模型转到 FP32 模型时,正常来说应该基本是无损的,但是 mAP 掉了将近 2 个点左右,这并不符合我们的直觉。
  • mAP 差 2 个点的原因可能是在固定分辨率这件事上,tensorRT 将图片分辨率固定在 640x640 大小。还有就是 pytorch 实现的所有细节并未完全加入进来,这些细节可能有没有找到的部分。
  • FP32 模型和 FP16 模型的 mAP 几乎一样,没有任何精度的损失,这倒是符合我们的直觉

2. 速度与模型性能的关系

  • FP16 和 INT8 的 FPS 分别为 792.23 和 1066.55,远高于 FP32 的 355.15
  • INT8 模型是所有模型中最快的,达到了 1000 FPS 的速度,尽管其精度稍低。

3. 权衡速度与精度

  • FP32 提供了较好的精度,但速度较慢
  • FP16 提供了与 FP32 类似的精度,但速度提高了约 2.2 倍,是一个非常不错的选择
  • INT8 提供了略低的精度,但速度却是最快的,比 FP32 快约 3 倍。

综上所述,在实际应用中,需要根据具体的需求权衡速度和精度。例如,对于实时应用,可能会选择 FP16 或 INT8 以获得更高的速度,尽管可能牺牲一些精度。而对于需要高精度的应用,可能会选择 FP32。

博主对比了同一张图片在不同精度模型下的推理效果,如下所示,让大家有个更直观的感受。

在这里插入图片描述

图4-7 car-FP32

在这里插入图片描述

图4-8 car-FP16

在这里插入图片描述

图4-9 car-INT8

3. YOLOv5-PTQ vs. YOLOv7-PTQ

最后我们当然是来对比下 YOLOv5-PTQ 量化后模型的性能和 YOLOv7-PTQ 量化后模型的性能哪个会更好,那其实两个模型训练用的数据集都是同一个啦,所以还是有可比性的

结果对比如下表所示:

ModelPrecisionmAPval
0.5:0.95
mAPval
0.5
Elapsed Time/msFPS
YOLOv5s.pt-0.4710.711--
YOLOv7-tiny.pt-0.4910.744--
YOLOv5s-FP32FP320.4470.6843.15317.79
YOLOv7-tiny-FP32FP320.4880.7242.82355.15
YOLOv5s-FP16FP160.4480.6831.34748.93
YOLOv7-tiny-FP16FP160.4890.7251.26792.23
YOLOv5s-INT8INT80.4090.6570.991008.93
YOLOv7-tiny-INT8INT80.4730.7050.941066.55

可视化图如下:

在这里插入图片描述

从表中我们可以看到对博主当前的 VOC 数据集而言,YOLOv7-tiny 模型似乎碾压 YOLOv5s 模型呀😂,不论是 pytorch 模型的效果,还是 PTQ 量化后模型的推理速度和效果,YOLOv7-tiny 都比 YOLOv5s 要优秀不少

当然也不排除 YOLOv5s 训练过程中并没有完全收敛得到最佳的性能,因为博主只训练了 100 个 epoch,那具体的对比结果各位看官可以自行测试,博主这边只是简单分析下。

OK!YOLOv7-PTQ 量化的内容到这里就结束了,各位看官可以自行测试。

结语

本篇博客介绍了关于 yolov7 的 PTQ 量化以及部署流程,博主在这里只做了最基础的演示,如果有更多的需求需要各位看官自己去挖掘啦😄。下篇文章我们将会分享关于 yolov7 的 QAT 量化以及部署流程,感谢各位看到最后,创作不易,读后有收获的看官帮忙点个👍⭐️

下载链接

  • 软件安装包下载链接【提取码:yolo】🚀🚀🚀

  • 源代码、权重、数据集下载链接【提取码:yolo】

参考

  • COCO Python API
  • tensorRT模型性能测试
  • Ubuntu20.04部署YOLOv5
  • YoloV5案例第一部分,导出ONNX
  • TensorRT量化第四课:PTQ与QAT
  • 目标检测mAP计算以及coco评价标准
  • 目标检测:PASCAL VOC 数据集简介
  • YoloV8的动态静态batch如何理解和使用
  • https://github.com/ultralytics/yolov5
  • https://github.com/shouxieai/tensorRT_Pro
  • 利用Anaconda安装pytorch和paddle深度学习环境+pycharm安装—免额外安装CUDA和cudnn(适合小白的保姆级教学)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/115061.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Spring] SpringBoot2 简介(一)—— 基础配置

目录 一、SpringBoot 简介 1、Spring 的缺点 2、SpringBoot 功能 二、SpringBoot 入门案例 1、实现步骤 2、访问服务器 3、入门小结 4、Idea 快速构建 SpringBoot 工程 5、起步依赖无需版本号 6、主启动类的在项目中的位置&#xff08;*重要*&#xff09; 三、Sprin…

【小白专用 已验证】PHP连接SQLServer数据库

PHP是一门强大的服务器端脚本语言&#xff0c;而SQL Server是Microsoft开发的一款关系型数据库管理系统。为了在PHP中直接操纵SQL Server数据库&#xff0c;需要通过安装SQL Server扩展来实现。这篇文章将详细介绍如何在PHP中使用SQL Server扩展来操作数据库。 首先&#xff0…

链表的中间结点-力扣

1、题目描述 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海…

使用 Rust 和 cURL 库下载程序

以下是一个使用 Rust 和 cURL 库的下载器程序&#xff0c;用于下载 图像。此程序使用了 https://www.duoip.cn/get_proxy 的代码。 extern crate curl; ​ use std::io::{self, Read}; use std::error::Error; ​ fn main() {let url "https://www.baidu.com";let …

USB学习(1):USB基础之接口类型、协议标准、引脚分布、架构、时序和数据格式

连接计算机外围设备最简单的方法是通过USB(通用串行总线)。USB是即插即用接口&#xff0c;可以将扫描仪、打印机、数码相机、闪存驱动器等计算机外围设备连接到计算机上。本篇文章就来介绍一下USB的一些基础知识&#xff0c;包括。 文章目录 1 接口类型和标准规范2 引脚分布3 …

centos如何根据端口号查询程序路径

centos如何根据端口号查询程序路径 如果是半路接受的应用&#xff0c;上个人只给你说了程序的端口号&#xff0c;别的都没&#xff0c;那怎么找程序的路径哪&#xff1f;一是给上上个人要&#xff0c;二是自己找&#xff08;我是自己找的&#xff09; 小白教程&#xff0c;一…

【计网 DNS】计算机网络 DNS协议详解:中科大郑烇老师笔记 (六)

目录 0 引言1 DNS概述1.1 定义1.2 DNS域名结构1.2 域名解析步骤 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;计算机四大基础专栏&#x1f4dc; 其他章节&#xff1a;网络快速入门系列、计算机网络&#xff08;一&#xff09;、计算机网络&…

分享一下我家网络机柜,家庭网络设备推荐

家里网络机柜搞了几天终于搞好了&#xff0c;非专业的&#xff0c;走线有点乱&#xff0c;勿喷。 从上到下的设备分别是&#xff1a; 无线路由器&#xff08;当ap用&#xff09;:TL-XDR6088 插排&#xff1a;德木pdu机柜插排 硬盘录像机&#xff1a;TL-NVR6108-L8P 第二排左边…

【Spring Cloud Alibaba】seata分布式事务官方入门案例(实战版)

文章目录 1. 业务介绍1.1. 用例1.2. 架构图1.3. 3个服务的代码及业务逻辑&#xff08;略&#xff09; 2. SEATA 的分布式交易解决方案3. 由Dubbo SEATA提供支持的示例&#xff08;实战&#xff09;3.1. 步骤 1&#xff1a;建立数据库&#xff0c;如seata数据库3.2. 步骤 2&…

2023高频前端面试题(含答案)

一、简单页面1、CSS选择器样式优先级2、CSS实现三列布局&#xff08;左右固定宽度&#xff0c;中间自适应&#xff09; &#xff08;1&#xff09;CSS浮动 第一个float:left&#xff0c;第二个float:right&#xff0c;第三个设置margin-left和margin-right &#xff08;2&#…

无纸化办公小程序数据交互、wxs的使用

前言 很多同志们再写小程序的过程中&#xff0c;不知道该怎么发起HTTP请求到后端&#xff0c;在Web环境中发起HTTPS请求是很常见的&#xff0c;但是微信小程序是腾讯内部的产品&#xff0c;不能直接打开一个外部的链接。例如&#xff0c;在微信小程序中不能直接打开www.taobao…

ASRPRO语音识别模块

ASRPRO语音识别模块 SOFT IIC 与PCA9685模块通信 pca9685 iic通信 地址位 ADDR<<1|0 左移一位 #define I2C_WRITE 0 #define I2C_READ 1 否则通信地址错误 asrpro 通过UART与电脑连接&#xff0c;可以进行简单的交互 将STM32作为接口扩展&#xff0c;通过SPI或I…

Seata学习

Seata Seata 是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 官网地址&#xff1a;https://seata.io/zh-cn/index.html 为什么会产生分布式事务&#xff1f; 示例&#xff1a;用户下单后需要创建订单&#xff0c;同时…

DDR电源硬件设计要点

一、DDR电源简介 1. 电源 DDR的电源可以分为三类: a、主电源VDD和VDDQ,主电源的要求是VDDQ=VDD,VDDQ是给IO buffer供电的电源,VDD是给但是一般的使用中都是把VDDQ和VDD合成一个电源使用。 有的芯片还有VDDL,是给DLL供电的,也和VDD使用同一电源即可。电源设计时,需要考…

最短路相关笔记

Floyd Floyd 算法&#xff0c;是一种在图中求任意两点间最短路径的算法。 Floyd 算法适用于求解无负边权回路的图。 时间复杂度为 O ( n 3 ) O(n^3) O(n3)&#xff0c;空间复杂度 O ( n 2 ) O(n^2) O(n2)。 对于两点 ( i , j ) (i,j) (i,j) 之间的最短路径&#xff0c;有…

如何让ChatGPT生成图片?

目录 一、那么如何解决让ChatGPT具有画图能力的问题呢&#xff1f; 二、那ChatGPT为什么能生成图片呢&#xff1f; 我们都知道ChatGPT只是个纯文本的AI模型&#xff0c;不具备画图能力。它可以生成文本&#xff0c;但如果让他生成图片就会显示如下的声明&#xff1a; 但通过本…

图像信号处理板设计原理图:2-基于6U VPX的双TMS320C6678+Xilinx FPGA K7 XC7K420T的图像信号处理板

综合图像处理硬件平台包括图像信号处理板2块&#xff0c;视频处理板1块&#xff0c;主控板1块&#xff0c;电源板1块&#xff0c;VPX背板1块。 一、板卡概述 图像信号处理板包括2片TI 多核DSP处理器-TMS320C6678&#xff0c;1片Xilinx FPGA XC7K420T-1FFG1156&#xff0c;1片X…

strcpy函数

文章目录 strcpy函数描述函数使用总结目标空间为什么必须可变&#xff1f;模拟实现 strcpy函数描述 重点&#xff1a;including the terminating null character (and stopping at that point).意为拷贝的值包括停止字符 传参时第一个参数为要拷贝参数&#xff0c;第二个参数为…

游戏盾如何有效防护DDoS

从进入计算机时代以来&#xff0c;DDoS攻击一直是网络世界中的一大威胁&#xff0c;让无数服务陷入瘫痪。这种攻击的原理非常简单&#xff1a;攻击者使用大量的僵尸主机或蠕虫病毒&#xff0c;向目标服务器发送海量请求&#xff0c;迅速耗尽服务器的资源&#xff0c;使其无法继…

某全球领先的芯片供应商:优化数据跨网交换流程,提高安全管控能力

1、客户介绍 某全球领先的芯片供应商&#xff0c;成立于2005年&#xff0c;总部设于北京&#xff0c;在国内上海、深圳、合肥等地及国外多个国家和地区均设有分支机构和办事处&#xff0c;致力于为客户提供更优质、便捷的服务。 2、建设背景 该公司基于网络安全管理的需求&am…