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 量化网络并执行推理
具体使用就是我们导出 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 模型的性能
Model | Size | mAPval 0.5:0.95 | mAPval 0.5 | Params (M) | FLOPs (G) |
---|---|---|---|---|---|
YOLOv7-tiny | 640 | 0.491 | 0.744 | 5.8 | 13.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 的模型放在一起进行对比下:
Model | Size | mAPval 0.5:0.95 | mAPval 0.5 | Params (M) | FLOPs (G) |
---|---|---|---|---|---|
YOLOv7-tiny | 640 | 0.491 | 0.744 | 5.8 | 13.3 |
YOLOv7-tiny-INT8 | 640 | 0.346 | 0.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 张图片是整个训练集的数量。
测试结果如下表所示:
Model | Calib Data | mAPval 0.5:0.95 | mAPval 0.5 |
---|---|---|---|
YOLOv7-tiny-INT8 | 10 | 0.399 | 0.621 |
YOLOv7-tiny-INT8 | 50 | 0.464 | 0.699 |
YOLOv7-tiny-INT8 | 100 | 0.469 | 0.703 |
YOLOv7-tiny-INT8 | 500 | 0.471 | 0.703 |
YOLOv7-tiny-INT8 | 600 | 0.345 | 0.559 |
YOLOv7-tiny-INT8 | 700 | 0.471 | 0.703 |
YOLOv7-tiny-INT8 | 800 | 0.473 | 0.705 |
YOLOv7-tiny-INT8 | 900 | 0.347 | 0.562 |
YOLOv7-tiny-INT8 | 1000 | 0.346 | 0.562 |
YOLOv7-tiny-INT8 | 4013(all) | 0.344 | 0.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模型:
FP16模型:
INT8模型:
值得注意的是,关于速度的测试我们之前似乎并没有提到,它具体是如何测试的呢?🤔
其实在 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 次不停的塞进去。让模型经历完整的图像的预处理,后处理
测试结果如下表所示:
Model | Precision | mAPval 0.5:0.95 | mAPval 0.5 | Elapsed Time/ms | FPS |
---|---|---|---|---|---|
YOLOv7-tiny.pt | - | 0.491 | 0.744 | - | - |
YOLOv7-tiny-FP32 | FP32 | 0.488 | 0.724 | 2.82 | 355.15 |
YOLOv7-tiny-FP16 | FP16 | 0.489 | 0.725 | 1.26 | 792.23 |
YOLOv7-tiny-INT8 | INT8 | 0.473 | 0.705 | 0.94 | 1066.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。
博主对比了同一张图片在不同精度模型下的推理效果,如下所示,让大家有个更直观的感受。
3. YOLOv5-PTQ vs. YOLOv7-PTQ
最后我们当然是来对比下 YOLOv5-PTQ 量化后模型的性能和 YOLOv7-PTQ 量化后模型的性能哪个会更好,那其实两个模型训练用的数据集都是同一个啦,所以还是有可比性的
结果对比如下表所示:
Model | Precision | mAPval 0.5:0.95 | mAPval 0.5 | Elapsed Time/ms | FPS |
---|---|---|---|---|---|
YOLOv5s.pt | - | 0.471 | 0.711 | - | - |
YOLOv7-tiny.pt | - | 0.491 | 0.744 | - | - |
YOLOv5s-FP32 | FP32 | 0.447 | 0.684 | 3.15 | 317.79 |
YOLOv7-tiny-FP32 | FP32 | 0.488 | 0.724 | 2.82 | 355.15 |
YOLOv5s-FP16 | FP16 | 0.448 | 0.683 | 1.34 | 748.93 |
YOLOv7-tiny-FP16 | FP16 | 0.489 | 0.725 | 1.26 | 792.23 |
YOLOv5s-INT8 | INT8 | 0.409 | 0.657 | 0.99 | 1008.93 |
YOLOv7-tiny-INT8 | INT8 | 0.473 | 0.705 | 0.94 | 1066.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(适合小白的保姆级教学)
相关文章:

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测试 三、…...

【网络协议】聊聊ICMP与ping是如何测试网络联通性
ICMP协议格式 ping是基于iCMP协议工作的,ICMP全称Internet Control Message Protocol,就是互联网控制报文协议。其实就是有点类似于古代行军打仗,哨探进行前方探明具体情况。 IMCP本身处于网络层,将报文封装在IP包里,…...

nginx tomcat 动静分离
动静分离: 访问静态和动态页面分开 实现动态和静态页面负载均衡。 五台虚拟机 实验1,动静分离 思路: 需要设备:三台虚拟机 一台nginx 代理又是静态 两台tomcat 请求动态页面 在全局模块中配置upstream tomcat 新建location…...

java读取指定文件夹下的全部文件,并输出文件名,文件大小,文件创建时间
import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.*; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { try { Path startingDir Paths.get("你的目…...

leetcode 105. 从前序与中序遍历序列构造二叉树
2023.10.21 本题需要根据前序遍历序列和中序遍历序列来构造出一颗二叉树。类似于从中序与后序遍历序列构造二叉树 。使用递归, java代码如下: /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* …...

【试题039】 多个逻辑或例题
题目:设int n;,执行表达式(n0)||(n1)||(n2)||(n3)后,n的值是?代码分析: //设int n; , 执行表达式(n 0) || (n 1) ||(n 2) ||(n 3)后, n的值是?int n;printf("n%d\n", (n 0) || (n 1) || (n 2) || (n 3));//分析࿱…...

打卡go学习第一天
8.1 下面展示一些 代码。 package mainimport ("fmt""net""os""time" )type Clock struct {Name stringAddr string } func main() {clocks : []Clock{{Name: "New York", Addr: "localhost:8000"…...
julia 笔记: 流程控制
1 复合表达式 用一个表达式有序地计算若干子表达式,并返回最后一个子表达式的值 1.1 begin代码块 z beginx 1y 2x yend z #3begin x 1; y 2; x y end #3 1.2 ;链 (x 1; y 2; x y) #3(x 1; y 2; x y) 3 2 条件表达式 x1 y2if x < yprintln(&q…...
【c++】 跟webrtc学周期性任务:tgcalls 5 网络超时检查
tgcalls 网络超时检查 G:\CDN\P2P-DEV\tdesktop-offical\Telegram\ThirdParty\tgcalls\tgcalls\NetworkManager.cppPostDelayedTask 使用的是rtc thread 方法 PostDelayedTask :G:\CDN\P2P-DEV\Libraries\tg_owt\src\rtc_base\thread.htemplate <class Closure,typename st…...
面试算法33:变位词组
题目 给定一组单词,请将它们按照变位词分组。例如,输入一组单词[“eat”,“tea”,“tan”,“ate”,“nat”,“bat”],这组单词可以分成3组,分别是[“eat”,“…...

【C语言】每日一题(旋转数组)
旋转数组,链接奉上 目录 方法:创建额外的数组:整体思路:代码实现: 数组反转:整体思路:代码实现:小插曲: 方法: 创建额外的数组: 整体思路: 创建一个额外的…...

系统架构师考试科目一:综合知识
某软件公司欲开发一个 Windows 平台上的公告板系统。在明确用户需求后,该公司的 架构师决定采用 Command 模式实现该系统的界面显示部分,并设计 UML 类图如下 图所示。图中与 Command 模式中的 Invoker 角色相对应的类是( ) ,与 ConcreteComm…...
面向对象与面向过程讲解
目录 简介 面向过程编程(Procedural Programming) 什么是面向过程编程? 特点: 面向对象编程(Object-Oriented Programming) 什么是面向对象编程? 特点: 面向对象 vs. 面向过程…...
【SA8295P 源码分析 (四)】23 - QNX Ethernet MAC 驱动 之 emac1_config.conf 配置文件解析
【SA8295P 源码分析】23 - QNX Ethernet MAC 驱动 之 emac1_config.conf 配置文件解析 系列文章汇总见:《【SA8295P 源码分析 (四)】网络模块 文章链接汇总 - 持续更新中》 本文链接:《【SA8295P 源码分析 (四)】23 - QNX Ethernet MAC 驱动 之 emac1_config.conf 配置文件解…...
Python【list列表去重】
目录 要求: 将list中的重复数据去重,至少使用两种方案 方案一: 方案二: 要求: 将list中的重复数据去重,至少使用两种方案 方案一: 使用set ,可以将list转换为set࿰…...

Leetcode——字符
520. 检测大写字母 class Solution { public:bool detectCapitalUse(string word) {int big 0, small 0, len word.length();for (int i 0; i < len; i) {if (word[i] > 65 && word[i] < 90) {big;}else {small;}}if (big len || small len) {return tr…...

深入解析docker内核网桥
今天做虚拟桌面,朋友问我,为什么vnc 连接另一个docker 容器一直超时,原因是在docker 启动的时候没有组网,那么接下来我就要解析下docker的内核网络。 我们思考几个问题,带你了解linux 中docker 网络实现的基本原理。 文…...
ubuntu18.04服务器双网口配置上外网
记录一下配置服务器过程,本以为简单,结果整了一天。 服务器有2个网口,网口2是用来上外网的,原来用的01-netcfg.yaml进行ip地址设置,主要就用2条命令: vi /etc/netplan/01-netcfg.yaml (打开后…...
【安全体系架构】——防御深度架构
防御深度架构: 防御深度架构是一种多层次的安全模型,旨在通过在网络和系统的各个层次上部署多个安全措施,以抵御不同类型的威胁和攻击。这个模型承认单一的安全措施可能无法全面防御所有潜在威胁,因此采用了多层次的安全防御策略…...

Opencv之RANSAC算法用于直线拟合及特征点集匹配详解
Opencv之RANSAC算法用于直线拟合及特征点集匹配详解 讲述Ransac拟合与最小二乘在曲线拟合上的优缺点 讲述在进行特征点匹配时,最近邻匹配与Ransac匹配的不同之处 另外,Ransac也被用于椭圆拟合、变换矩阵求解等 1. 直线拟合 1.1 原理 RANSAC(RANdom …...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...

负载均衡器》》LVS、Nginx、HAproxy 区别
虚拟主机 先4,后7...