当前位置: 首页 > news >正文

YOLOv5目标检测标准化流程

一. 数据标注流程

├─ 1.1 标注工作
│ ├─ 工具:LabelImg(YOLO格式)
│ ├─ 标准:矩形框精确包围主体,类别命名一致
│ ├─ 文件结构:图像与同名txt标签文件
│ └─ 数据集清洗:剔除模糊/重复/遮挡样本
├─ 1.2 自动标注工具
│ └─ X-AnyLabeling:支持深度学习模型辅助标注
├─ 1.3 多格式数据转换
│ ├─ 支持格式:YOLO/VOC/COCO/DOT4等
│ └─ JSON转TXT脚本(关键坐标转换)

二. 首次训练配置

├─ 2.1 虚拟环境配置
│ ├─ Miniconda安装
│ ├─ 创建环境:conda create -n yolov5pconv python=3.9
│ └─ 激活环境:conda activate yolov5pconv
├─ 2.2 下载代码库
│ ├─ git clone https://github.com/ultralytics/yolov5
│ ├─ 安装依赖:pip install -r requirements.txt
│ └─ CUDA加速配置
├─ 2.3 数据集配置
│ └─ custom.yaml文件
│ ├─ train/val路径
│ ├─ nc(类别数)
│ └─ names(类别列表)
├─ 2.4 启动训练
│ ├─ 命令:python train.py --img 640 --batch 16 --epochs 100 --data custom.yaml --weights yolov5s.pt
│ └─ 参数说明
│ ├─ --weights:初始权重
│ ├─ --epochs:训练轮数
│ └─ --img:图像尺寸

三. 迭代训练优化

├─ 继续训练:python train.py --weights runs/train/exp/weights/best.pt
├─ 超参数调整
│ ├─ hyp.scratch-low.yaml
│ └─ hyp.scratch-high.yaml
└─ 学习率/数据增强优化

四. 模型产出与导出

├─ 4.1 模型产出
│ └─ 路径:runs/train/exp/weights/best.pt
├─ 4.2 模型导出
│ ├─ ONNX格式:python export.py --weights best.pt --include onnx
│ └─ OpenVINO IR格式
│ ├─ FP32/FP16/INT8量化
│ └─ 转换脚本(ONNX→XML/BIN)

五. Python模型测试

├─ 5.1 基础测试
│ ├─ detect.py:python detect.py --weights best.pt --source test_images/
│ └─ 自定义脚本
├─ 5.2 多轮测试
│ ├─ FPS计算
│ └─ 性能评估脚本
├─ 5.3 Int8量化
│ └─ OpenVINO量化流程
└─ 5.4 OpenVINO推理
└─ 预处理/后处理实现

六. C++模型部署

├─ 6.1 环境配置
│ ├─ VS2022设置
│ ├─ 包含目录:OpenVINO/OpenCV头文件
│ └─ 库目录:OpenVINO/OpenCV库文件
├─ 6.2 代码部署
│ ├─ 头文件设计(openvino_yolov5n.h)
│ ├─ 模型初始化
│ └─ 推理流程
└─ 6.3 预处理/后处理
├─ 保持与Python一致
└─ 坐标转换关键逻辑

七. 性能验证指标

├─ 7.1 量化性能对比
│ ├─ 推理时间对比
│ └─ 精度变化
├─ 7.2 训练指标解析
│ ├─ 混淆矩阵
│ ├─ F1-置信度曲线
│ ├─ PR曲线
│ ├─ 训练损失图
│ └─ mAP指标
└─ 7.3 可视化结果
├─ train_batch0.jpg
└─ val_batch0_labels.jpg

1.数据标注流程

1.1 标注工作

在进行YOLOv5模型训练前,首先需要完成数据的标注工作。

数据标注是目标检测流程中最关键的一环,其质量直接影响最终模型的精度和泛化能力。

​ 本项目采用主流的yolo格式进行图像标注,每张图像对应一个同名的.txt标签文件,其中包含目标类别、边界框坐标等信息。推荐使用LabelImg工具进行手动标注,操作简便且兼容性强。标注过程中需遵循统一标准,例如:每个目标均需用矩形框精确包围主体,避免遗漏或冗余;类别命名需与配置文件中的类别列表保持一致。

在这里插入图片描述

这是检测的图像,但是真正标注的时候不会显示置信度,到时候根据类别来进行标注。

在这里插入图片描述

上图是labelImg的使用界面,对各个功能板块介绍比较详细。一般要打开这个界面,需要我们安装插件

# 在终端输入命令
pip install labelImg
# 启动
labelImg

在这里插入图片描述

​ 此外,为提高训练效率,建议对数据集进行初步清洗,剔除模糊、重复或严重遮挡的样本。最后将数据划分为训练集、验证集(通常按8:2比例),并整理成YOLOv5支持的数据结构目录。

​ 使用标注工具LabelImg对数据集进行标注,确保标注格式为YOLO格式(每个图像对应一个.txt文件,内容为class_id x_center y_center width height,数值为归一化后的比例)。就以这次的双目标检测为例。图像名要与标签名一一对应,否则训练就会出错。

标注完成后,数据集需按以下结构组织:

dataset/
├── images/    存放图像
│   ├── train/ 训练集
│   └── val/   验证集
└── labels/    存放标签├── train/└── val/

在这里插入图片描述

标签一一对应。

在这里插入图片描述

1.2 自动标注工具 X-AnyLabeling

X-AnyLabeling是一款集众多主流深度学习算法模型和丰富功能特性于一体的强大图像标注软件,其专注于解决实际应用。该软件能够显著提升标注效率和精度,为学术界和工业界提供高效的一站式解决方案,可快速且准确地完成多种复杂任务的标注。
在这里插入图片描述

在这里插入图片描述

通过导入自己的模型,然后点击运行,那么该工具就会自动标注。比较传统的labelImg标注速度块,但是有时候也要手动来进行数据的纠正。

标注样式描述适用场景
矩形框快速框选目标对象,简单高效目标检测、物体跟踪等基础任务
多边形绘制不规则形状,实现精确标注图像分割、显著性检测等复杂任务
旋转框描述物体的方向和姿态,更加灵活旋转目标检测
标记图像中的特定关键点姿态估计、关键点检测等任务
线段用于直线关系的标注需求道路标线、简单图形边界等
折线段绘制连续折线,适配弯曲线条标注车道线检测等任务
圆形提供便捷的圆形目标标注方式车辆轮胎、瞳孔等圆形目标标注场景

自动化标注的使用教程:
X-AnyLabeling里面采用的是读取yaml文件和加载onnx文件的方式加载自定义模型。普通的pt文件不行,一定要转onnx文件。将onnx文件和yaml文件放一起!!!
在这里插入图片描述
如果是框架中已经支持的网络结构,那么可以按照以下步骤进行适配,这里以 yolov5n 模型为例,其它类似。

首先,在正式开始之前,我们可以看下该模型对应的配置文件:

type: yolov5
name: yolov5s-r20230520
display_name: Fruits (YOLOv5s)
model_path: fruits.onnx
input_width: 640
input_height: 640
stride: 32
nms_threshold: 0.45
confidence_threshold: 0.45
classes:- apple- banana- orange
  • type: 网络类型定义,不可更改,目前已适配的网络类型定义可参见 model_manager.py 文件中的 load_custom_model() 函数;
  • name: 该字段为当前模型对应的配置文件索引标记,如果是加载用户自定义模型,此字段可忽略,详情可参见 models.yaml 文件;
  • display_name: 即展示到界面上显示的名称,可根据自定义任务自行命名,如 Fruits (YOLOv5s);
  • model_path: 即相对于自定义配置文件 *.yaml 所对应的模型权重路径,要求是 *.onnx 文件格式;

1.3 多格式数据转换

X-AnyLabeling 支持主流深度学习框架的数据格式导入导出,如 YOLO、OpenMMLab 和 PaddlePaddle 等。这极大地简化了数据集准备流程,让您能够更专注于模型开发本身。

在这里插入图片描述

默认会生成json格式的数据,一般用yolo训练模型的话,label标签是txt格式,所以要将其转化为txt格式。

# json2txtimport os
import json
import numpy as np
# 类和索引
CLASSES=["mark","pool"]
def convert_2(size,box):w = (box[1] - box[0]) / size[0]h = (box[3] - box[2]) / size[1]x = (box[0]+box[1]) /2 /size[0]y = (box[2]+box[3]) /2 /size[1]return (x,y,w,h)def json2txt(path_json,path_txt):with open(path_json,"r") as path_json:jsonx=json.load(path_json)width=int(jsonx["imageWidth"])      # 原图的宽height=int(jsonx["imageHeight"])    # 原图的高with open(path_txt,"w+") as ftxt:# 遍历每一个bbox对象for shape in jsonx["shapes"]:obj_cls=str(shape["label"])     # 获取类别cls_id=CLASSES.index(obj_cls)   # 获取类别索引points=np.array(shape["points"])    # 获取(x1,y1,x2,y2)#print(points)xmin = int(points[0][0])xmax = int(points[0][0])ymin = int(points[0][1])ymax = int(points[0][1])for point in shape["points"]:if point[0] > xmax:xmax = point[0]if point[0] < xmin:xmin = point[0]if point[1] > ymax:ymax = point[1]if point[1] < ymin:ymin = point[1]#print(xmin,xmax,ymin,ymax)# (左上角,右下角) -> (中心点,宽高) 归一化bb=convert_2((width,height),(xmin,xmax,ymin,ymax))ftxt.write(str(cls_id)+" "+" ".join([str(a) for a in bb])+"\n")#.join()
if __name__=="__main__":# json文件夹dir_json="F:\\CAM1_out\\"# txt文件夹dir_txt="F:\\txtout\\"if not os.path.exists(dir_txt):os.makedirs(dir_txt)# 得到所有json文件list_json=os.listdir(dir_json)# 遍历每一个json文件,转成txt文件for cnt, json_name in enumerate(list_json):#print("cnt=%d,name=%s"%(cnt, json_name))file,fileext = os.path.splitext(json_name)if(fileext == ".json"):path_json = dir_json + json_namepath_txt= dir_txt + json_name.replace(".json",".txt")# (x1,y1,x2,y2)->(x,y,w,h)json2txt(path_json,path_txt)else:pass

注意!!!

转换的json包含这些信息:

  • name:图片文件名
  • category:类别id
  • bbox:目标框信息xyrb格式,分别指[左上角x坐标,左上角y坐标,右下角x坐标,右下角y坐标]
  • score:预测的分数
  [{"name": "235_2_t20201127123021723_CAM2.jpg","image_height": 6000,"image_width": 8192,"category": 5,"bbox": [1876.06,998.04,1883.06,1004.04]},{"name": "235_2_t20201127123021723_CAM2.jpg","image_height": 6000,"image_width": 8192,"category": 5,"bbox": [1655.06,1094.04,1663.06,1102.04]}]

其中 yolo 需要的标注数据格式:
235_2_t20201127123021723_CAM2.txt 文件
数据格式:类别id 中心点x坐标 中心点y坐标 w h

5 0.229438 0.16684 0.000854 0.001
5 0.202522 0.183007 0.000977 0.001333

2.首次训练配置

2.1 虚拟环境的配置:

############################################################################################
在下载代码之前先配置好虚拟环境
可以先行下载好conda。本人选择miniconda,较为轻量。
https://www.anaconda.com/docs/getting-started/miniconda/main 下载链接
无脑下一步,路径自己修改一下。打开bin文件配置环境变量。
############################################################################################1.conda env list
可以先查看一下你当前的虚拟环境有那些? 不要出现重名的环境2.conda create -n yolov5pconv python==3.9.0
创建虚拟环境,   yolov5pconv->环境名称,最好见名知意   python->python版本,根据自己情况修改python版本 3.conda activate yolov5pconv
激活虚拟环境 后面的安装操作都要激活本环境,否则安装软件包路径不对。
激活之后会出现   
(yolov5pconv) C:\Users\LIXINJIAN>  这样代表激活成功4.conda remove -n yolov5pconv --all
删除环境,如果环境安装错误,可以使用本命令来进行删除

在这里插入图片描述

2.2 下载YOLOv5官方代码库

git clone https://github.com/ultralytics/yolov5
cd yolov5
pip install -r requirements.txt############################################################################################
如果要使用cuda来加速训练,可以先行把torch和torchvision进行注释,使用conda进行导入
我测试使用的是python==3.9.0,其他版本没有测试。尽量创建环境的时候版本一致,否则出现各种错误。
前面的环境创建就不再说,只是说导包过程
1.conda install pytorch==1.12.1 torchvision==0.13.1 torchaudio==0.12.1 cudatoolkit=11.6 -c pytorch -c conda -forge2.pip install -r requirements.txt
这两步基本就安装了大部分的包,其他的一些包就是缺啥装啥
# 这是requirements.txt,可以进行参考修改
# 导出命令 pip freeze > requirements.txt# YOLOv5 requirements
# Usage: pip install -r requirements.txt
# Base ------------------------------------------------------------------------
gitpython==3.1.30
matplotlib==3.3
numpy==1.23.5
opencv-python==4.8.0.74
pillow==10.3.0
psutil  # system resources
PyYAML==5.3.1
requests==2.32.2
scipy==1.4.1
thop== 0.1.1.post2207130030  # FLOPs computation
torch==1.8.0  # see https://pytorch.org/get-started/locally (recommended)
torchvision==0.9.0
tqdm==4.66.3
ultralytics==8.2.64  # https://ultralytics.com
# protobuf<=3.20.1  # https://github.com/ultralytics/yolov5/issues/8012
# Logging ---------------------------------------------------------------------
# tensorboard>=2.4.1
# clearml>=1.2.0
# comet
# Plotting --------------------------------------------------------------------
pandas==1.1.4
seaborn==0.11.0
# Export ----------------------------------------------------------------------
# coremltools>=6.0  # CoreML export
# onnx>=1.10.0  # ONNX export
# onnx-simplifier>=0.4.1  # ONNX simplifier
# nvidia-pyindex  # TensorRT export
# nvidia-tensorrt  # TensorRT export
# scikit-learn<=1.1.2  # CoreML quantization
# tensorflow>=2.4.0,<=2.13.1  # TF exports (-cpu, -aarch64, -macos)
# tensorflowjs>=3.9.0  # TF.js export
# openvino-dev>=2023.0  # OpenVINO export
# Deploy ----------------------------------------------------------------------
setuptools>=70.0.0 # Snyk vulnerability fix
# tritonclient[all]~=2.24.0
# Extras ----------------------------------------------------------------------
# ipython  # interactive notebook
# mss  # screenshots
# albumentations>=1.0.3
# pycocotools>=2.0.6  # COCO mAP

修改数据集配置文件(如data/custom.yaml):

train: ../dataset/images/train/
val: ../dataset/images/val/
nc: <类别数>
names: ['class1', 'class2', ...]

在这里插入图片描述

2.3 启动训练命令

  • 训练之前先要自己去下载权重文件,yolov5n.pt或者yolov5s.pt权重。本次训练采用s权重文件。
  • 下载链接YOLOv5 权重下载

在这里插入图片描述

python train.py --img 640 --batch 16 --epochs 100 --data 2target.yaml --weights yolov5s.pt

参数介绍

--weights: 初始权重路径,默认为'yolov5s.pt'--cfg: model.yaml文件路径,默认为空字符串。
--data: data.yaml文件路径,默认为'data/coco128.yaml'--hyp: 超参数文件路径,默认为'data/hyp.scratch.yaml'--epochs: 训练的总轮数,默认为10--batch-size: 所有GPU的总批量大小,默认为2--img-size: 图像尺寸,包括训练和测试图像尺寸,默认为[640, 640]--rect: 是否使用矩形训练,默认为False--resume: 是否恢复最近的训练,默认为False--nosave: 是否仅保存最终检查点,默认为False--notest: 是否仅测试最终轮次,默认为False--noautoanchor: 是否禁用自动锚点检查,默认为False--evolve: 是否进行超参数进化,默认为False--bucket: gsutil存储桶,默认为空字符串。
--cache-images: 是否缓存图像以加快训练速度,默认为False--image-weights: 是否使用加权图像选择进行训练,默认为False--device: CUDA设备,例如00,1,2,3或cpu,默认为空字符串。
--multi-scale: 是否在图像尺寸上变化+/- 50%,默认为False--single-cls: 是否将多类数据作为单类进行训练,默认为False--adam: 是否使用torch.optim.Adam()优化器,默认为False--sync-bn: 是否使用SyncBatchNorm,仅在DDP模式下可用,默认为False--local_rank: DDP参数,不要修改,默认为-1--log-imgs: W&B日志中要记录的图像数量,最大为100,默认为16--log-artifacts: 是否记录工件,例如最终训练模型,默认为False--workers: 数据加载器的最大工作进程数,默认为0--project: 保存到的项目/名称,默认为'runs/train'--name: 保存到的项目/名称,默认为'exp'--exist-ok: 如果项目/名称已存在,则允许存在,不增加计数,默认为False--quad: 是否使用四重数据加载器,默认为False
  • 出现下面这些信息代表可以进行训练

在这里插入图片描述

3.迭代训练优化

使用上次训练的权重继续训练:

python train.py --weights runs/train/exp/weights/best.pt --data custom.yaml --epochs 50

调整超参数(如学习率、数据增强)可修改hyp.scratch-low.yamlhyp.scratch-high.yaml文件。

4.模型产出与导出

4.1 模型产出

训练完成后,最佳模型保存在runs/train/exp/weights/best.pt

导出为ONNX格式:

python export.py --weights best.pt --include onnx

如需TensorRT引擎,可追加--include engine参数。

1 要通过c++进行部署的话,要经过来两次的模型转化 pt -> onnx -> IR(bin和xml文件)

2.pt转换onnx十分简单,yolo提供了内置函数,也就是上面的指令就可以得到onnx,会得到.onnx文件,如果不做C++部署,可以对其onnx进行更深一步研究。onnx+GPU来进行部署,可以得到一个很好的效果,实测可以达到100fps以上。

  • 测试结果:

在这里插入图片描述

4.2 模型导出

从onnx到IR(bin 和 xml)

  • 1这个是根据openvino的版本来进行划分,在2022之前,可以通过mo来进行转化,它有内置脚本,方便转化。

在这里插入图片描述

python mo_onnx.py --input_model <输入模型路经> --output_dir <输出模型路经> --input_shape [1,3,32,32](可修改)
  • 转换之后的结果:

在这里插入图片描述

  • 2 通过脚本进行转化,同样可以得到相同的效果。
# coding:UTF-8
import os
from openvino.tools.ovc import convert_model
from openvino.runtime import serialize# 定义 ONNX 模型路径
onnx_model_path = r"E:\yolo11-up-42-master\output\11_2_fp32\best.onnx"# 定义输出目录路径
output_dir = r"E:\yolo11-up-42-master\output\11_2_fp32"# 设置模型转换参数
try:# 使用新的 convert_model 函数,直接传递模型路径,输出目录单独指定ir_model = convert_model(onnx_model_path)  # 修改:移除关键字参数,直接使用位置参数# 保存模型到指定目录output_xml_path = os.path.join(output_dir, "model.xml")output_bin_path = os.path.join(output_dir, "model.bin")serialize(ir_model, output_xml_path, output_bin_path)  # 使用 openvino.runtime.serialize() 方法保存模型print(f"Model successfully converted and saved to: {output_dir}")
except Exception as e:print(f"Error during model conversion: {e}")

5.Python版模型测试

5.1 detect.py脚本测试

python detect.py --weights best.pt --source test_images/ --conf 0.5

在这里插入图片描述

检测结果如下:

在这里插入图片描述

5.2 自定义测试脚本示例

import torch
model = torch.hub.load('ultralytics/yolov5', 'custom', path='best.pt')
results = model('test_image.jpg')
results.show()

5.3 多轮图片测试

import os
import time
import cv2
import torch
from ultralytics import YOLO
# ------------------ 配置参数 ------------------
model_path = "42_demo/runs/yolo11n2mark/train/weights/best.pt"  # 替换为你自己的模型路径
image_folder = "TestTuPian"            					   # 测试图片文件夹路径
save_results = False                    		           # 是否保存检测结果图像
show_images = True										   # 是否显示图像(调试用)
num_warmup = 10                                 	       # 预热次数(避免首次推理影响 FPS)
# ------------------------------------------------def test_image_fps(model, image_paths):inference_times = []for i, img_path in enumerate(image_paths):im = cv2.imread(img_path)# 预热阶段if i < num_warmup:model(im)continue# 推理时间记录start_time = time.time()results = model(im)end_time = time.time()# 计算耗时和 FPSinference_time = end_time - start_timeinference_times.append(inference_time)# 可视化if show_images:annotated_frame = results[0].plot()cv2.imshow("Detection", annotated_frame)cv2.waitKey(1)# 保存结果if save_results:annotated_frame = results[0].plot()filename = os.path.basename(img_path)cv2.imwrite(f"results_{filename}", annotated_frame)'''print(f"Image {i - num_warmup + 1}/{len(image_paths) - num_warmup} | "f"Inference Time: {inference_time:.4f}s | FPS: {1 / inference_time:.2f}")# 输出平均 FPSavg_time = sum(inference_times) / len(inference_times)avg_fps = 1 / avg_timeprint("\nTest Complete.")print(f"Average Inference Time: {avg_time:.4f}s")print(f"Average FPS: {avg_fps:.2f}")
'''if __name__ == "__main__":# 检查 GPU 是否可用#device = "cuda" if torch.cuda.is_available() else "cpu"device = "cpu"print(f"Using device: {device}")# 加载模型model = YOLO(model_path).to(device)# 获取所有图片路径supported_formats = [".jpg", ".jpeg", ".png", ".bmp"]image_paths = [os.path.join(image_folder, fname)for fname in os.listdir(image_folder)if os.path.splitext(fname)[-1].lower() in supported_formats]print(f"Found {len(image_paths)} images to test.\n")# 开始测试test_image_fps(model, image_paths)

5.4 Int8量化

openvino==2022.3.1 #openvino的版本,虚拟环境配置,非常重要brotlicffi==1.0.9.2
fastdownload==0.0.7
filelock==3.18.0
fsspec==2025.5.1
GitPython==3.1.44
Jinja2==3.1.6
mkl_fft==1.3.11
mkl_random==1.2.8
nncf==2.17.0
onnx==1.13.1
onnxruntime==1.14.1
openvino-dev==2022.3.1
pip==25.1
PySocks==1.7.1
seaborn==0.13.2
sentry-sdk==2.32.0
tensorboard==2.19.0
thop==0.1.1.post2209072238
torchaudio==0.12.1
ultralytics==8.3.163
wheel==0.45.1
win-inet-pton==1.1.0

通过YOLOv5提供的export.py将预训练的Pytorch模型转换为OpenVINO FP32 IR模型。

python export.py --weights yolov5n.pt --imgsz 640 --batch-size 1 --include openvino --opset 13

在这里插入图片描述

量化代码,进行量化之后会转化为FP32、FP16、INT8模型

from pathlib import Path
from utils.dataloaders import create_dataloader
from utils.general import check_dataset
from export import attempt_load, yaml_save
from val import run as validation_fn
from openvino.tools import mo
from openvino.runtime import serialize  # 这个包可能爆红,不用管!
from openvino.tools.pot.api import DataLoader
from openvino.tools.pot.engines.ie_engine import IEEngine
from openvino.tools.pot.graph import load_model
from openvino.tools.pot.pipeline.initializer import create_pipeline
from openvino.tools.pot.graph.model_utils import compress_model_weights
from openvino.tools.pot.graph import load_model, save_modelIMAGE_SIZE = 640
MODEL_NAME = "yolov5n" #自己模型的名称
DATASET_CONFIG = "./data/coco128.yaml" #修改对应的yaml文件,根据自己的类别修改
class YOLOv5POTDataLoader(DataLoader):'''Inherit from DataLoader function and implement for YOLOv5.'''def __init__(self, data_source):super().__init__({})self._data_loader = data_sourceself._data_iter = iter(self._data_loader)def __len__(self):return len(self._data_loader.dataset)def __getitem__(self, item):try:batch_data = next(self._data_iter)except StopIteration:self._data_iter = iter(self._data_loader)batch_data = next(self._data_iter)im, target, path, shape = batch_dataim = im.float()im /= 255nb, _, height, width = im.shapeimg = im.cpu().detach().numpy()target = target.cpu().detach().numpy()annotation = dict()annotation["image_path"] = pathannotation["target"] = targetannotation["batch_size"] = nbannotation["shape"] = shapeannotation["width"] = widthannotation["height"] = heightannotation["img"] = imgreturn (item, annotation), img
if __name__ == "__main__":'''Conversion of the YOLOv5 model to OpenVINO'''onnx_path = f"./{MODEL_NAME}.onnx"# fp32 IR modelfp32_path = f"./FP32_openvino_model/{MODEL_NAME}_fp32.xml"print(f"Export ONNX to OpenVINO FP32 IR to: {fp32_path}")model = mo.convert_model(onnx_path)serialize(model, fp32_path)# fp16 IR modelfp16_path = f"./FP16_openvino_model/{MODEL_NAME}_fp16.xml"print(f"Export ONNX to OpenVINO FP16 IR to: {fp16_path}")model = mo.convert_model(onnx_path, compress_to_fp16=True)serialize(model, fp16_path)'''Prepare dataset for quantization'''data = check_dataset(DATASET_CONFIG)data_source = create_dataloader(data["val"], imgsz=640, batch_size=1, stride=32, pad=0.5, workers=0)[0]pot_data_loader = YOLOv5POTDataLoader(data_source)'''Configure quantization pipeline'''algorithms_config = [{"name": "DefaultQuantization","params": {"preset": "mixed","stat_subset_size": 300,"target_device": "CPU"},}]engine_config = {"device": "CPU"}model_config = {"model_name": f"{MODEL_NAME}","model": fp32_path,"weights": fp32_path.replace(".xml", ".bin"),}pot_model = load_model(model_config)engine = IEEngine(config=engine_config, data_loader=pot_data_loader)pipeline = create_pipeline(algorithms_config, engine)'''Perform model optimization'''compressed_model = pipeline.run(pot_model)compress_model_weights(compressed_model)optimized_save_dir = Path(f"./POT_INT8_openvino_model/")save_model(compressed_model, optimized_save_dir, model_config["model_name"] + "_int8")pot_int8_path = f"{optimized_save_dir}/{MODEL_NAME}_int8.xml"'''Compare accuracy FP32 and INT8 models'''model = attempt_load(f"./{MODEL_NAME}.pt", device="cpu", inplace=True, fuse=True) metadata = {"stride": int(max(model.stride)), "names": model.names}  # model metadatayaml_save(Path(pot_int8_path).with_suffix(".yaml"), metadata)yaml_save(Path(fp32_path).with_suffix(".yaml"), metadata)print("Checking the accuracy of the original model:")fp32_metrics = validation_fn(data=DATASET_CONFIG,weights=Path(fp32_path).parent,batch_size=1,workers=0,plots=False,device="cpu",iou_thres=0.65,)fp32_ap5 = fp32_metrics[0][2]fp32_ap_full = fp32_metrics[0][3]print(f"mAP@.5 = {fp32_ap5}")print(f"mAP@.5:.95 = {fp32_ap_full}")print("Checking the accuracy of the POT int8 model:")int8_metrics = validation_fn(data=DATASET_CONFIG,weights=Path(pot_int8_path).parent,batch_size=1,workers=0,plots=False,device="cpu",iou_thres=0.65,)pot_int8_ap5 = int8_metrics[0][2]pot_int8_ap_full = int8_metrics[0][3]print(f"mAP@.5 = {pot_int8_ap5}")print(f"mAP@.5:.95 = {pot_int8_ap_full}")

运行之后会生成对应的量化文件,默认是上面的名称,简易自己先创建出对应的目录。
在这里插入图片描述

自定义推理代码:

import cv2
import numpy as np
from openvino.inference_engine import IECore
names = ['mark', 'pool']
conf_thres = 0.5
nms_thres = 0.5
model_path = "yolov5n_2target.onnx"  # onnx推理支持fp32和fp16
model_xml = "5n_POT_INT8_openvino_model/yolov5n_2target_int8.xml"  # IR推理支持fp32、fp16和int8
model_bin = "5n_POT_INT8_openvino_model/yolov5n_2target_int8.bin"
def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), scaleup=False, stride=32):shape = img.shape[:2]  # current shape [height, width]if isinstance(new_shape, int):new_shape = (new_shape, new_shape)r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])if not scaleup:  # only scale down, do not scale up (for better test mAP)r = min(r, 1.0)ratio = r  # width, height ratiosnew_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh paddingdw /= 2dh /= 2if shape[::-1] != new_unpad:  # resizeimg = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))left, right = int(round(dw - 0.1)), int(round(dw + 0.1))img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add borderreturn img, ratio, (dw, dh)
def iou(b1, b2):b1_x1, b1_y1, b1_x2, b1_y2 = b1[0], b1[1], b1[2], b1[3]b2_x1, b2_y1, b2_x2, b2_y2 = b2[:, 0], b2[:, 1], b2[:, 2], b2[:, 3]inter_rect_x1 = np.maximum(b1_x1, b2_x1)inter_rect_y1 = np.maximum(b1_y1, b2_y1)inter_rect_x2 = np.minimum(b1_x2, b2_x2)inter_rect_y2 = np.minimum(b1_y2, b2_y2)inter_area = np.maximum(inter_rect_x2 - inter_rect_x1, 0) * np.maximum(inter_rect_y2 - inter_rect_y1, 0)area_b1 = (b1_x2 - b1_x1) * (b1_y2 - b1_y1)area_b2 = (b2_x2 - b2_x1) * (b2_y2 - b2_y1)iou = inter_area / np.maximum((area_b1 + area_b2 - inter_area), 1e-6)return iou
def non_max_suppression(boxes, conf_thres=0.5, nms_thres=0.4, ratio=1, pad=(20, 20)):# 取出batch_sizebs = np.shape(boxes)[0]# xywh___ to____ xyxyshape_boxes = np.zeros_like(boxes[:, :, :4])shape_boxes[:, :, 0] = boxes[:, :, 0] - boxes[:, :, 2] / 2shape_boxes[:, :, 1] = boxes[:, :, 1] - boxes[:, :, 3] / 2shape_boxes[:, :, 2] = boxes[:, :, 0] + boxes[:, :, 2] / 2shape_boxes[:, :, 3] = boxes[:, :, 1] + boxes[:, :, 3] / 2boxes[:, :, :4] = shape_boxesboxes[:, :, 5:] *= boxes[:, :, 4:5]# output存放每一张图片的预测结果,推理阶段一般是一张图片output = []for i in range(bs):predictions = boxes[i]  # 预测位置xyxy  shape==(12700,85)score = np.max(predictions[:, 5:], axis=-1)# score = predictions[:,4]  # 存在物体置信度,shape==12700mask = score > conf_thres  # 物体置信度阈值mask==[False,False,True......],shape==12700,True将会被保留,False列将会被删除detections = predictions[mask]  # 第一次筛选  shape==(115,85)class_conf = np.expand_dims(np.max(detections[:, 5:], axis=-1), axis=-1)  # 获取每个预测框预测的类别置信度class_pred = np.expand_dims(np.argmax(detections[:, 5:], axis=-1), axis=-1)  # 获取每个预测框的类别下标# 结果堆叠,(num_boxes,位置信息4+包含物体概率1+类别置信度1+类别序号1)detections = np.concatenate([detections[:, :4], class_conf, class_pred], axis=-1)  # shape=(numbox,7)unique_class = np.unique(detections[:, -1])  # 取出包含的所有类别if len(unique_class) == 0:continuebest_box = []for c in unique_class:# 取出类别为c的预测结果cls_mask = detections[:, -1] == cdetection = detections[cls_mask]  # shape=(82,7)# 包含物体类别概率从高至低排列scores = detection[:, 4]arg_sort = np.argsort(scores)[::-1]  # 返回的是索引detection = detection[arg_sort]while len(detection) != 0:best_box.append(detection[0])if len(detection) == 1:break# 计算当前置信度最大的框和其它预测框的iouious = iou(best_box[-1], detection[1:])detection = detection[1:][ious < nms_thres]  # 小于nms_thres将被保留,每一轮至少减少一个output.append(best_box)boxes_loc = []conf_loc = []class_loc = []if len(output):for i in range(len(output)):pred = output[i]for i, det in enumerate(pred):if len(det):# 将框坐标调整回原始图像中det[0] = (det[0] - pad[0]) / ratiodet[2] = (det[2] - pad[0]) / ratiodet[1] = (det[1] - pad[1]) / ratiodet[3] = (det[3] - pad[1]) / ratioboxes_loc.append([det[0], det[1], det[2], det[3]])conf_loc.append(det[4])class_loc.append(det[5])return boxes_loc, conf_loc, class_loc
def plot_box(img, boxes, conf, clas_id, line_thickness=3, names=None):# 画位置框tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1c1, c2 = (int(boxes[0]), int(boxes[1])), (int(boxes[2]), int(boxes[3]))cv2.rectangle(img, c1, c2, [0, 0, 255], thickness=tl, lineType=cv2.LINE_AA)# 画类别信息框label = f'{names[int(clas_id)]} {conf:.2f}'tf = max(tl - 1, 1)  # label字体的线宽 font thicknesst_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]c2 = (c1[0] + t_size[0], c1[1] - t_size[1] - 3)cv2.rectangle(img, c1, c2, [255, 0, 0], -1, cv2.LINE_AA)cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
if __name__ == '__main__':ie = IECore()# net = ie.read_network(model=model_path)net = ie.read_network(model=model_xml, weights=model_bin)exec_net = ie.load_network(network=net, device_name="CPU")input_layer = next(iter(net.input_info))frame = cv2.imread("data/images/val/2025.06.20.14.21.57.780.jpg")img, ratio, (dw, dh) = letterbox(frame)blob = cv2.dnn.blobFromImage(np.ascontiguousarray(img), 1 / 255.0, (img.shape[0], img.shape[1]), swapRB=True,crop=False)infer_request_handle = exec_net.start_async(request_id=0, inputs={input_layer: blob})if infer_request_handle.wait(-1) == 0:res = infer_request_handle.output_blobs["output0"]outs = res.bufferboxes_loc, conf_loc, class_loc = non_max_suppression(outs, conf_thres=conf_thres, nms_thres=nms_thres,ratio=ratio, pad=(dw, dh))for i in range(len(boxes_loc)):boxes = boxes_loc[i]conf = conf_loc[i]clas_id = class_loc[i]plot_box(frame, boxes, conf, clas_id, line_thickness=3, names=names)cv2.imshow("result", frame)cv2.waitKey(0)cv2.destroyAllWindows()

6.C++版模型部署

使用LibTorch或ONNX Runtime或者openvino进行部署。如果有GPU的条件下,可以使用onnx+GPU,是一种不错的提升

本文以openvino为例:

  • 选好集成开发工具 VS2022/vscode/CLine…只要能编写C++代码即可,本文以VS2022为例。

  • OpenCV4.8+ 分别配置好环境变量

  • Openvino2024+

  • 以及转换好的IR模型

6.1 VS2022的环境配置:

新建项目,一般是选择一个控制台应用,打印C++的代码。为了测试自己的一些环境是否安装好。

在这里插入图片描述

测试这个main.cpp,看是否可以正常运行,然后开始配置环境。

6.2 项目环境配置

在这里插入图片描述

  • 1.如果没有找到解决方案,点击视图可以打开解决方案。
  • 2.对自己的项目名称进行鼠标右击,点击最下面的属性,就会弹出这个页面。

在这里插入图片描述

  • 1.打开VC++目录,打开右边的包含目录
  • 2.对其进行添加,opencv和openvino的include目录
F:\openvino_toolkit_windows_2024.3.0\runtime\include\openvino
F:\opencv48\opencv\build\include

在这里插入图片描述

  • 1.打开库目录
  • 2.对其配置对应的lib文件
F:\openvino_toolkit_windows_2024.3.0\runtime\lib
F:\opencv48\opencv\build\x64\vc16\lib

在这里插入图片描述

  • 1.打开C/C++下面的常规
  • 2.打开附加包含目录
F:\openvino_toolkit_windows_2024.3.0\runtime\include
F:\opencv48\opencv\build\include

在这里插入图片描述

  • 打开链接器的常规
  • 打开附加库目录
F:\openvino_toolkit_windows_2024.3.0\runtime\lib\intel64\Release
F:\opencv48\opencv\build\x64\vc16\lib

在这里插入图片描述

  • 1.打开链接器的输入
  • 2.打开添加依赖项
openvino.lib
opencv_world480.lib

6.3 代码部署

  • 代码头文件
  • openvino_yolov5n.h
#ifndef OPENVINO_YOLOV5N_H#define OPENVINO_YOLOV5N_H#include <openvino/openvino.hpp>#include <opencv2/opencv.hpp>// 常量定义const float SCORE_THRESHOLD = 0.1;const float NMS_THRESHOLD = 0.45;class OpenvinoModel{public:OpenvinoModel();ov::InferRequest init_model(const std::string& model_path, const std::string& weights_path);void infer(const ov::Tensor& data);std::vector<ov::Output<ov::Node>> get_outputs() const {if (model) {return model->outputs();}return std::vector<ov::Output<ov::Node>>();}std::string main_output_name;private:ov::Core core;std::shared_ptr<ov::Model> model;ov::CompiledModel complied_model;ov::InferRequest infer_request;};// 只声明函数,不实现std::vector<std::map<std::string, float>> nms_box(float* detectionResults, size_t detectionCount);//std::tuple<cv::Mat, int, int> resize_and_pad(const cv::Mat& image, const cv::Size& new_shape);std::tuple<cv::Mat, int, int, float> resize_and_pad(const cv::Mat& image, const cv::Size& new_shape);std::vector<std::map<std::string, float>> transform_boxes(const std::vector<std::map<std::string, float>>& detections,int delta_w,int delta_h,float ratio,int orig_width,int orig_height);#endif // OPENVINO_YOLOV5N_H

resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by project3.8.rc
// 新对象的下一组默认值#ifdef APSTUDIO_INVOKED#ifndef APSTUDIO_READONLY_SYMBOLS#define _APS_NEXT_RESOURCE_VALUE        101#define _APS_NEXT_COMMAND_VALUE         40001#define _APS_NEXT_CONTROL_VALUE         1001#define _APS_NEXT_SYMED_VALUE           101#endif#endif

源文件openvino_class_yolov5n.cpp和test_openvino.cpp

//openvino_class_yolov5n.cpp#include "openvino_yolov5n.h"#include <filesystem>#include <fstream> OpenvinoModel::OpenvinoModel(){core = ov::Core();//core.register_plugin("C:/openvino_windows_2025/runtime/bin/intel64/Releas/openvino_intel_gpu_plugin.dll", "GPU");}ov::InferRequest OpenvinoModel::init_model(const std::string& model_path, const std::string& weights_path){try {std::cout << "从: " << model_path << " 加载模型" << std::endl;// 加载模型model = core.read_model(model_path);// 保存第一个输出张量的名称main_output_name = model->outputs()[0].get_any_name();// 设置预处理ov::preprocess::PrePostProcessor ppp(model);// 输入设置 - 修改这部分auto& input = ppp.input();// 设置输入张量属性input.tensor().set_element_type(ov::element::f32).set_layout("NCHW")  // 直接使用 NCHW 布局.set_spatial_static_shape(640, 640);  // 设置固定的空间维度// 设置模型输入期望的布局input.model().set_layout("NCHW");// 构建预处理model = ppp.build();// 编译模型complied_model = core.compile_model(model, "CPU");std::cout << "模型编译成功。" << std::endl;// 创建推理请求infer_request = complied_model.create_infer_request();return infer_request;}catch (const ov::Exception& e) {std::cerr << "OpenVINO 错误: " << e.what() << std::endl;throw;}catch (const std::exception& e) {std::cerr << "错误: " << e.what() << std::endl;throw;}}void OpenvinoModel::infer(const ov::Tensor& data){infer_request.set_input_tensor(0, data);infer_request.infer();}std::vector<std::map<std::string, float>> nms_box(float* detectionResults, size_t detectionCount){const int NUM_CLASSES = 2;  // 明确指定类别数量const int DATA_PER_DETECTION = 5 + NUM_CLASSES;  // 7 = 4坐标 + 1置信度 + 2类别分数std::vector<cv::Rect> boxes;std::vector<int> classIds;std::vector<float> confidences;  // 现在存储综合分数for (size_t i = 0; i < detectionCount; ++i) {float* det = detectionResults + i * DATA_PER_DETECTION;float confidence = det[4];cv::Mat classesScores(1, NUM_CLASSES, CV_32F, det + 5);cv::Point maxLoc;double maxVal;cv::minMaxLoc(classesScores, nullptr, &maxVal, nullptr, &maxLoc);float classScore = static_cast<float>(maxVal);float final_score = confidence * classScore;  // 综合分数if (final_score >= SCORE_THRESHOLD) {float x = det[0];float y = det[1];float w = det[2];float h = det[3];// 调试时暂时禁用额外过滤float xmin = x - w / 2;float ymin = y - h / 2;boxes.emplace_back(xmin, ymin, w, h);confidences.push_back(final_score);classIds.push_back(maxLoc.x);// 调试输出/*std::cout << "Kept: score=" << final_score << " class=" << maxLoc.x<< " xywh=[" << x << "," << y << "," << w << "," << h << "]\n";*/}}// 自定义标签映射std::vector<std::string> custom_labels = { "mark", "pool" };std::vector<int> indexes;cv::dnn::NMSBoxes(boxes, confidences, SCORE_THRESHOLD, NMS_THRESHOLD, indexes);std::vector<std::map<std::string, float>> ans;for (int index : indexes){int original_class_id = classIds[index];// 动态映射到自定义标签int mappedClass = (original_class_id < custom_labels.size()) ? original_class_id : 0;  // 越界时默认第一个类别std::map<std::string, float> detection;detection["class_index"] = static_cast<float>(mappedClass);detection["confidence"] = confidences[index];detection["box_xmin"] = static_cast<float>(boxes[index].x);detection["box_ymin"] = static_cast<float>(boxes[index].y);detection["box_w"] = static_cast<float>(boxes[index].width);detection["box_h"] = static_cast<float>(boxes[index].height);// 添加原始类别ID用于调试(可选)detection["original_class_id"] = static_cast<float>(original_class_id);ans.push_back(detection);}return ans;}// 在nms_box函数后添加这个函数std::vector<std::map<std::string, float>> transform_boxes(const std::vector<std::map<std::string, float>>& detections,int delta_w,int delta_h, float ratio, int orig_width, int orig_height){std::vector<std::map<std::string, float>> transformed;for (const auto& det : detections) {// 计算原始图像上的坐标(去除填充)float xmin = det.at("box_xmin");float ymin = det.at("box_ymin");float width = det.at("box_w");float height = det.at("box_h");// 去除填充xmin = std::max(0.0f, xmin);ymin = std::max(0.0f, ymin);width = std::min(width, static_cast<float>(640 - delta_w) - xmin);height = std::min(height, static_cast<float>(640 - delta_h) - ymin);// 缩放回原始尺寸xmin = xmin / ratio;ymin = ymin / ratio;width = width / ratio;height = height / ratio;// 确保不超出原始图像边界xmin = std::clamp(xmin, 0.0f, static_cast<float>(orig_width));ymin = std::clamp(ymin, 0.0f, static_cast<float>(orig_height));width = std::clamp(width, 0.0f, static_cast<float>(orig_width) - xmin);height = std::clamp(height, 0.0f, static_cast<float>(orig_height) - ymin);// 创建新的检测结果std::map<std::string, float> new_det = det;new_det["box_xmin"] = xmin;new_det["box_ymin"] = ymin;new_det["box_w"] = width;new_det["box_h"] = height;transformed.push_back(new_det);}return transformed;}//std::tuple<cv::Mat, int, int> resize_and_pad(const cv::Mat& image, const cv::Size& new_shape)//{//    cv::Size old_size = image.size();//    float ratio = static_cast<float>(new_shape.width) / std::max(old_size.width, old_size.height);//    cv::Size new_size(static_cast<int>(old_size.width * ratio), static_cast<int>(old_size.height * ratio));//    cv::Mat resized_image;//    cv::resize(image, resized_image, new_size);//    int delta_w = new_shape.width - new_size.width;//    int delta_h = new_shape.height - new_size.height;//    cv::Scalar color(100, 100, 100);//    cv::Mat padded_image;//    cv::copyMakeBorder(resized_image, padded_image, 0, delta_h, 0, delta_w, cv::BORDER_CONSTANT, color);//    return std::make_tuple(padded_image, delta_w, delta_h);//}std::tuple<cv::Mat, int, int, float> resize_and_pad(const cv::Mat& image, const cv::Size& new_shape){cv::Size old_size = image.size();float ratio = static_cast<float>(new_shape.width) / std::max(old_size.width, old_size.height);cv::Size new_size(static_cast<int>(old_size.width * ratio), static_cast<int>(old_size.height * ratio));cv::Mat resized_image;cv::resize(image, resized_image, new_size);int delta_w = new_shape.width - new_size.width;int delta_h = new_shape.height - new_size.height;cv::Scalar color(100, 100, 100);cv::Mat padded_image;cv::copyMakeBorder(resized_image, padded_image, 0, delta_h, 0, delta_w, cv::BORDER_CONSTANT, color);return std::make_tuple(padded_image, delta_w, delta_h, ratio);  // 添加ratio到返回值}

test_openvino.cpp

#include <iostream>#include <string>#include <chrono>#include <thread>#include <filesystem>#include "openvino_yolov5n.h"#include <openvino/openvino.hpp>int main(){try {ov::Core core;OpenvinoModel model_base;ov::InferRequest infer_request = model_base.init_model("E:/best_Data/model.xml", "E:/best_Data/model.bin");std::string folder = "E:/TestData/intput";std::vector<std::string> jpg_files;// 创建输出目录if (!std::filesystem::exists("E:/TestData/output")) {
std::filesystem::create_directory("E:/TestData/output");}//处理所有图片类型for (const auto& entry : std::filesystem::directory_iterator(folder)){std::string ext = entry.path().extension().string();// 将扩展名转换为小写,方便比较std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);if (ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" || ext == ".png"){jpg_files.push_back(entry.path().string());}}// 在主循环开始之前,打印所有输出张量的信息auto outputs = model_base.get_outputs();  // 使用新添加的方法for (const auto& jpg : jpg_files){try {cv::Mat img = cv::imread(jpg);if (img.empty()){std::cerr << "Could not open or find the image: " << jpg << std::endl;continue;}// 保存一份原始图像的副本用于后续绘制cv::Mat display_img = img.clone();// 保存原始图像尺寸int original_width = img.cols;int original_height = img.rows;//cv::cvtColor(img, img, cv::COLOR_BGR2RGB);// 计时开始auto start = std::chrono::high_resolution_clock::now();cv::Mat img_resized;int dw, dh;float ratio;std::tie(img_resized, dw, dh, ratio) = resize_and_pad(img, cv::Size(640, 640));cv::Mat input_mat;cv::dnn::blobFromImage(img_resized,input_mat,1.0 / 255.0,cv::Size(640, 640),cv::Scalar(0, 0, 0),true,false,CV_32F);ov::Tensor input_tensor(ov::element::f32, { 1, 3, 640, 640 }, input_mat.ptr<float>());model_base.infer(input_tensor);ov::Tensor output_tensor = infer_request.get_tensor(model_base.main_output_name);float* detections = output_tensor.data<float>();size_t detectionCount = output_tensor.get_shape()[1];  // 检测框数量// NMS处理auto pre_detections = nms_box(detections, detectionCount);// 关键:坐标变换auto final_detections = transform_boxes(pre_detections,dw,dh,ratio,original_width,original_height);// 计时结束auto end = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::duration<float>>(end - start).count();std::cout << "Average inference time: " << duration*1000 << " ms" << std::endl;duration = 0.0; // 重置duration// 在原始图像上绘制检测结果for (const auto& detection : final_detections){float x = detection.at("box_xmin");float y = detection.at("box_ymin");float w = detection.at("box_w");float h = detection.at("box_h");float confidence = detection.at("confidence");int classId = static_cast<int>(detection.at("class_index"));// 将坐标转换回原图尺寸(修改转换逻辑)// 直接使用变换后的坐标float box_x = x;float box_y = y;float box_w = w;float box_h = h;// 确保边界框在图像范围内box_x = std::max(0.0f, std::min(box_x, static_cast<float>(original_width - 1)));box_y = std::max(0.0f, std::min(box_y, static_cast<float>(original_height - 1)));box_w = std::min(box_w, static_cast<float>(original_width - box_x));box_h = std::min(box_h, static_cast<float>(original_height - box_y));// 在display_img上绘制边界框cv::rectangle(display_img,cv::Point(static_cast<int>(box_x), static_cast<int>(box_y)),cv::Point(static_cast<int>(box_x + box_w), static_cast<int>(box_y + box_h)),cv::Scalar(0, 255, 0), 2);// 绘制类别标签和置信度std::string label = std::to_string(classId) + ": " + std::to_string(confidence).substr(0, 4);cv::putText(display_img, label,cv::Point(static_cast<int>(box_x), static_cast<int>(box_y - 5)),cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1);}// 保存结果图像(使用display_img而不是img)std::string output_path = "E:/TestData/output" + std::filesystem::path(jpg).filename().string();cv::imwrite(output_path, display_img);//std::cout << "Saved annotated image to: " << output_path << std::endl;}catch (const ov::Exception& e) {std::cerr << "OpenVINO error processing " << jpg << ": " << e.what() << std::endl;continue;}catch (const std::exception& e) {std::cerr << "Error processing " << jpg << ": " << e.what() << std::endl;continue;}}system("pause");return 0;}catch (const ov::Exception& e) {std::cerr << "OpenVINO initialization error: " << e.what() << std::endl;system("pause");return 1;}catch (const std::exception& e){std::cerr << "Error: " << e.what() << std::endl;system("pause");return 1;}}

需注意预处理和后处理需与Python保持一致(如归一化、NMS等)。

  • 结构目录如下:

在这里插入图片描述

在这里插入图片描述

6.4 CPU测试

测试前准备以下文件,程序都是在本地编译好的,直接可以运行,但是要确保文件路径一致。

文件的下载地址(https://download.csdn.net/download/qq_71729869/91347694)

在这里插入图片描述

在这里插入图片描述

6.4.1 I9 13代测试
模型FP32FP32置信度INT8INT8置信度
YOLOv5n11ms90%8ms85%
YOLOv5s21ms95%15ms90%

如图所示,这是YOLOv5s_FP32和YOLOv5s_INT8的测试图

在这里插入图片描述

在这里插入图片描述

如图所示,这是YOLOv5n_FP32和YOLOv5n_INT8的测试图

在这里插入图片描述

在这里插入图片描述

6.4.2 I7 11代测试
模型FP32FP32置信度INT8INT8置信度
YOLOv5n18ms90%12ms85%
YOLOv5s44ms95%20ms90%

如图所示,这是YOLOv5s_FP32和YOLOv5s_INT8的测试图

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

如图所示,这是YOLOv5n_FP32和YOLOv5n_INT8的测试图

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

6.4.3 I7 9代测试
模型FP32FP32置信度INT8INT8置信度
YOLOv5n35ms90%25ms85%
YOLOv5s88ms95%55ms90%

如图所示,这是YOLOv5s_FP32和YOLOv5s_INT8的测试图

在这里插入图片描述

在这里插入图片描述

如图所示,这是YOLOv5n_FP32和YOLOv5n_INT8的测试图

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

7.性能验证指标

7.1 yolov5s量化前后性能比较

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

量化前后置信度没有下降太多,但是量化之后的推理时间明显缩短一半。yolov5n也是同样的效果。

7.2 yolov5n量化前后性能比较

在这里插入图片描述

在这里插入图片描述

7.3 训练之后的指标图解释

  1. 混淆矩阵(Confusion Matrix)
  • 混淆矩阵用于展示模型预测结果与真实标签之间的对比情况。
  • 矩阵中的每个单元格表示在特定预测类别和真实类别组合下的样本数量或比例。
    在这里插入图片描述

行(Predicted) 表示预测类别,列(True) 表示真实类别。
对角线上的值(1.00, 1.00) 表示模型正确预测的比例,即准确率(Accuracy)。

  • mark类别的准确率为 1.00。
  • pool类别的准确率为 1.00。
  • 实际为background时,有 0.25 的概率被误判为mark。
  • 实际为background时,有 0.75 的概率被误判为pool。
  1. F1-置信度曲线(F1-Confidence Curve)
  • F1-Confidence 曲线 展示了不同置信度阈值下模型的 F1 分数变化情况。

  • F1 分数是精确率(Precision)和召回率(Recall)的调和平均值,综合衡量模型的性能。
    在这里插入图片描述

  • 横轴(Confidence) 表示置信度阈值,纵轴(F1) 表示 F1 分数。

  • 三条曲线 分别代表 [mark]、[pool] 和所有类别的 F1 分数变化。

  • 所有类别的 F1 分数在置信度为 0.662 时达到 1.00,表明此时模型性能最优。

  • 曲线越平缓且靠近顶部,说明模型在不同置信度下的性能越稳定。

  1. 标签统计图(Labels Statistics)
    在这里插入图片描述
  • 标签统计图 展示了数据集中不同类别的实例数量及其位置和尺寸分布。
  • 柱状图 显示[mark]和 [pool]类别的实例数量。
    • mark 类别有约 12000 个实例。
    • pool类别有约 11000 个实例。
  • 散点图 展示了目标在图像中的位置(x, y)和尺寸(width, height)分布。
    • x 和 y 的分布较为均匀,集中在图像的中间区域。
    • width 和 height 的分布较为集中,宽度主要在 0.1 左右,高度主要在 0.05 左右。
  1. 标签相关图(Labels Correlogram)
  • 标签相关图 进一步展示了目标位置和尺寸之间的相关性及分布情况。
  • 多个子图 分别展示了 x、y、width 和 height 之间的两两相关性及各自的一维分布。

在这里插入图片描述

  • 散点图 显示了变量之间的相关性。
    • x 和 y 之间存在一定的相关性,但不强。
    • width 和 height 之间的相关性较弱。
  • 直方图 展示了每个变量的一维分布情况。
    • x 和 y 的分布较为均匀。
    • width 和 height 的分布较为集中。
  1. 精确率-置信度曲线(Precision-Confidence Curve)
  • Precision-Confidence 曲线 展示了不同置信度阈值下模型的精确率变化情况。

  • 精确率表示模型正确预测为正类的比例。
    在这里插入图片描述

  • 横轴(Confidence) 表示置信度阈值,纵轴(Precision) 表示精确率。

  • 三条曲线 分别代表 markpool和所有类别的精确率变化。

  • 所有类别的精确率在置信度为 0.998 时达到 1.00,表明此时模型的精确率最优。

  • 曲线越平缓且靠近顶部,说明模型在不同置信度下的精确率越高。

  1. Precision-Recall Curve(PR_curve.png)
  • Precision-Recall 曲线 展示了模型在不同阈值下的精确率(Precision)和召回率(Recall)的变化情况。

  • 精确率表示模型正确预测为正类的比例,召回率表示实际为正类且被正确预测的比例。
    在这里插入图片描述

  • 三条曲线 分别代表 markpool和所有类别的 Precision-Recall 曲线。

  • mark 类别的 AP(Average Precision)为 0.995,表明该类别在各个阈值下的平均精确率为 0.995。

  • [pool]类别的 AP 为 0.991,同样表现优秀。

  • 所有类别的 mAP@0.5(Mean Average Precision at IoU=0.5)为 0.993,综合性能非常出色。

  1. Recall-Confidence Curve(R_curve.png)
  • Recall-Confidence 曲线 展示了模型在不同置信度阈值下的召回率变化情况。

  • 置信度越高,模型越确信其预测结果。
    在这里插入图片描述

  • 三条曲线 分别代表 markpool和所有类别的 Recall-Confidence 曲线。

  • 所有类别的召回率在置信度为 0.000 时达到 1.00,表明此时模型能够召回所有真实正例。

  • 随着置信度增加,召回率逐渐下降,但在较高置信度(如 0.8 左右)下仍能保持较高的召回率(约 0.9)。

  1. 训练结果图(results.png)
  • 训练结果图 展示了训练过程中各项指标的变化趋势。

  • 包括训练和验证集上的损失(Loss)、精确率(Precision)、召回率(Recall)以及 mAP(Mean Average Precision)等。
    在这里插入图片描述

  • 训练损失(train/box_loss, train/obj_loss, train/cls_loss)

    • 随着训练轮数增加,各项损失均迅速下降并趋于稳定,表明模型收敛良好。
  • 验证损失(val/box_loss, val/obj_loss, val/cls_loss)

    • 验证损失同样呈现下降趋势,但 val/obj_loss 在后期略有上升,可能需要关注是否存在过拟合现象。
  • 精确率(metrics/precision)和召回率(metrics/recall)

    • 精确率和召回率在训练初期迅速提升,并在后续训练中保持在高位(接近 1.0),表明模型在检测任务上的性能优秀。
  • mAP(metrics/mAP_0.5, metrics/mAP_0.5:0.95)

    • mAP@0.5 在训练过程中快速上升并稳定在 1.0,mAP@0.5:0.95 则稳定在约 0.6,反映了模型在不同 IoU 阈值下的综合性能。
  1. 训练批次可视化(train_batch0.jpg)
  • 训练批次可视化 展示了训练数据集中的一批样本及其标注信息。
    在这里插入图片描述

  • 每个图像中的红色框表示目标的真实边界框(Ground Truth),数字表示对应的类别标签。

  • 标注信息:通过观察标注框的位置和类别,可以验证数据集的标注质量。

  • 数据分布:了解训练数据的多样性和复杂性,有助于分析模型的泛化能力。

  1. 验证批次标签可视化(val_batch0_labels.jpg)
  • 验证批次标签可视化 展示了验证数据集中的一批样本及其标注信息。
    在这里插入图片描述

  • 同样使用红色框表示目标的真实边界框(Ground Truth),数字表示对应的类别标签。

  • 标注信息:验证数据集的标注质量同样重要,确保模型在未知数据上的表现可靠。

  • 数据分布:与训练数据集对比,可以分析验证数据集的代表性,评估模型的泛化性能。

8 总结

本文系统梳理了基于YOLOv5的目标检测完整流程,涵盖数据准备、模型训练、优化部署与性能评估四大阶段。数据环节强调使用LabelImg或X-AnyLabeling进行YOLO格式标注,并需按images/train、val与labels/train、val的规范结构组织数据集,支持自动与手动标注结合,提升效率。训练前通过conda创建Python3.9虚拟环境,安装PyTorch1.12及依赖,下载yolov5s预训练权重,以默认参数启动训练;后续可基于best.pt继续迭代并调节超参数。模型产出后,利用export.py将PyTorch模型转换为ONNX,再用OpenVINO生成FP32/FP16/IR或INT8量化模型,实现Python与C++双端部署,其中C++端通过VS2022集成OpenCV与OpenVINO完成推理,支持批量图片测试与实时显示。性能验证部分给出量化可减半推理时间且精度几乎无损;训练指标图展示了mark、pool两类别的高精确率、召回率及mAP@0.5≈0.993,验证了数据标注与模型训练的有效性,为工业落地提供了标准化、可复制的端到端方案。

http://www.dtcms.com/a/277868.html

相关文章:

  • 013_流式输出与实时响应
  • 【SSM】SpringBoot 实现邮件发送
  • Typecho博客新文章自动添加“New“标签的实现方案
  • 热点代码探测确定何时JITTest01
  • 16. JVM调优工具
  • 华为OD 处理器
  • 格密码--LWE,DLWE和ss-LWE
  • 【王树森推荐系统】重排04:DPP 多样性算法(上)
  • python学习打卡:DAY 45 Tensorboard使用介绍
  • 言语理解高频词之生僻成语
  • 驱动开发(3)|rk356x驱动GPIO基础应用之点亮led灯
  • idea docker插件连接docker失败
  • [RPA] 批量数据抓取指定商品名称信息
  • Pandas-数据清洗与处理
  • Spring高级特性——反射和动态代理的性能优化
  • SQL预编译:安全高效数据库操作的关键
  • 《1.5倍与2倍的扩容密码:Java容器的内存性能抉择》
  • 【牛客刷题】四个选项:高考选择题方案统计(并查集+动态规划)
  • 01.深入理解 Python 中的 if __name__ == “__main__“
  • TensorFlow深度学习实战(25)——变分自编码器详解与实现
  • 工作流执行路径的有效性
  • 零基础入门物联网-远程门禁开关:软件安装
  • 014_批处理与大规模任务
  • 【容器】资源平台初探 - K8s核心资源全解析:从Pod到StatefulSet
  • 板凳-------Mysql cookbook学习 (十一--------8)
  • Burp suite的下载安装基础用法(密码喷洒,密码爆破)
  • 算法入门--动态规划(C++)
  • Ribbon实战
  • 【枚举+差分】P6070 『MdOI R1』Decrease
  • RAG升级:Re-rank模型微调,实现极致检索精度