新闻  |   论坛  |   博客  |   在线研讨会
一览YOLOv5中的评价方式
计算机视觉工坊 | 2023-02-21 20:20:44    阅读:313   发布文章

前言
代码仓库地址:https://github.com/Oneflow-Inc/one-yolov5欢迎star one-yolov5项目 获取最新的动态。

源码解读:https://github.com/Oneflow-Inc/one-yolov5/blob/main/val.py 。文章里面的超链接可能被公众号吃掉,可以直接到我们的文档网站阅读获得更好的体验:https://start.oneflow.org/oneflow-yolo-doc/source_code_interpretation/val_py.htmlUltralytics YOLOv5 官方给的介绍:

Validate a model's accuracy on COCO val or test-dev datasets. Models are downloaded automatically from the latest YOLOv5 release. To show results by class use the --verbose flag. Note that pycocotools metrics may be ~1% better than the equivalent repo metrics, as is visible below, due to slight differences in mAP computation.
1.导入需要的包和基本配置
import argparse # 解析命令行参数模块
import json     # 字典列表和JSON字符串之间的相互解析模块
import os       # 与操作系统进行交互的模块 包含文件路径操作和解析
import sys      # sys系统模块 包含了与Python解释器和它的环境有关的函数
from pathlib import Path  # Path将str转换为Path对象 使字符串路径易于操作的模块

import numpy as np # NumPy(Numerical Python)是Python的一种开源的数值计算扩展
import oneflow as flow # OneFlow 深度学习框架
from tqdm import tqdm # 进度条模块
 
from models.common import DetectMultiBackend # 下面都是 one-yolov5 定义的模块,在本系列的其它文章都有涉及
from utils.callbacks import Callbacks
from utils.dataloaders import create_dataloader
from utils.general import (
    LOGGER,
    check_dataset,
    check_img_size,
    check_requirements,
    check_yaml,
    coco80_to_coco91_class,
    colorstr,
    increment_path,
    non_max_suppression,
    print_args,
    scale_coords,
    xywh2xyxy,
    xyxy2xywh,
)
from utils.metrics import ConfusionMatrix, ap_per_class, box_iou
from utils.oneflow_utils import select_device, time_sync
from utils.plots import output_to_target, plot_images, plot_val_study
2.opt参数详解

参数解析
datadataset.yaml path数据集配置文件地址 包含数据集的路径、类别个数、类名、下载地址等信息
weightsmodel weights path(s)模型的权重文件地址 weights/yolov5s
batch-sizebatch size计算样本的批次大小 默认32
imgszinference size (pixels)输入网络的图片分辨率    默认640
conf-thresconfidence thresholdobject置信度阈值 默认0.001
iou-thresNMS IoU threshold进行NMS时IOU的阈值 默认0.6
tasktrain, val, test, speed or study设置测试的类型 有train, val, test, speed or study几种 默认val
devicecuda device, i.e. 0 or 0,1,2,3 or cpu测试的设备
workersmax dataloader workers (per RANK in DDP mode)加载数据使用的 dataloader workers
single-clstreat as single-class dataset数据集是否只用一个类别 默认False
augmentaugmented inference测试是否使用TTA Test Time Augment 默认False
verbosereport mAP by class是否打印出每个类别的mAP 默认False
save-hybridsave label+prediction hybrid results to *.txt保存label+prediction 杂交结果到对应.txt 默认False
save-confsave confidences in --save-txt labels
save-jsonsave a COCO-JSON results file是否按照coco的json格式保存结果       默认False
projectsave to project/name测试保存的源文件 默认runs/val
namesave to project/name测试保存的文件地址名 默认exp  保存在runs/val/exp
exist-okexisting project/name ok, do not increment是否保存在当前文件,不新增 默认False
halfuse FP16 half-precision inference是否使用半精度推理 默认False
dnnuse OpenCV DNN for ONNX inference是否使用 OpenCV DNN 对 ONNX 模型推理

3.main函数
根据解析的opt参数,调用run函数
def main(opt):
    #  检测requirements文件中需要的包是否安装好了
    check_requirements(requirements=ROOT / "requirements.txt", exclude=("tensorboard""thop"))
    
    if opt.task in ("train""val""test"):  # run normally
        if opt.conf_thres > 0.001:  # 更多请见 https://github.com/ultralytics/yolov5/issues/1466
            LOGGER.info(f"WARNING: confidence threshold {opt.conf_thres} > 0.001 produces invalid results")
        run(**vars(opt))

    else:
        weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
        opt.half = True  # FP16 for fastest results
        if opt.task == "speed":  # speed benchmarks
            # python val.py --task speed --data coco.yaml
            #                --batch 1 --weights yolov5n/ yolov5s/ ...
            opt.conf_thres, opt.iou_thres, opt.save_json = 0.250.45False
            for opt.weights in weights:
                run(**vars(opt), plots=False)

        elif opt.task == "study":  # speed vs mAP benchmarks
            # python val.py --task study --data coco.yaml
            #                --iou 0.7 --weights yolov5n/ yolov5s/...
            for opt.weights in weights:
                f = f"study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt"
                x, y = (
                    list(range(2561536 + 128128)),
                    [],
                )  # x axis (image sizes), y axis
                # "study": 模型在各个尺度下的指标并可视化,
                # 上面list(range(256, 1536 + 128, 128)),代表 img-size 的各个尺度, 具体代码如下:
                for opt.imgsz in x:  # img-size
                    LOGGER.info(f"\nRunning {f} --imgsz {opt.imgsz}...")
                    r, _, t = run(**vars(opt), plots=False)
                    y.append(r + t)  # results and times
                np.savetxt(f, y, fmt="%10.4g")  # save
            os.system("zip -r study.zip study_*.txt")
            # 可视化各个指标
            plot_val_study(x=x)  # plot
3. run函数
https://github.com/Oneflow-Inc/one-yolov5/blob/bf8c66e011fcf5b8885068074ffc6b56c113a20c/val.py#L112-L383
3.1 载入参数
# 不参与反向传播
@flow.no_grad() 
def run(
    data, # 数据集配置文件地址 包含数据集的路径、类别个数、类名、下载地址等信息 train.py时传入data_dict
    weights=None,  # 模型的权重文件地址 运行train.py=None 运行test.py=默认weights/yolov5s
    batch_size=32,  # 前向传播的批次大小 运行test.py传入默认32 运行train.py则传入batch_size // WORLD_SIZE * 2
    imgsz=640,  # 输入网络的图片分辨率 运行test.py传入默认640 运行train.py则传入imgsz_test
    conf_thres=0.001,  # object置信度阈值 默认0.001
    iou_thres=0.6,  # 进行NMS时IOU的阈值 默认0.6
    task="val",  # 设置测试的类型 有train, val, test, speed or study几种 默认val
    device="",  # 执行 val.py 所在的设备 cuda device, i.e. 0 or 0,1,2,3 or cpu
    workers=8,  # dataloader中的最大 worker 数(线程个数)
    single_cls=False,  # 数据集是否只有一个类别 默认False
    augment=False,  # 测试时增强,详细请看我们的教程:https://start.oneflow.org/oneflow-yolo-doc/tutorials/03_chapter/TTA.html
    verbose=False,  # 是否打印出每个类别的mAP 运行test.py传入默认Fasle 运行train.py则传入nc < 50 and final_epoch
    save_txt=False,  # 是否以txt文件的形式保存模型预测框的坐标 默认True
    save_hybrid=False,  # 是否save label+prediction hybrid results to *.txt  默认False
    save_conf=False,  # 是否保存预测每个目标的置信度到预测txt文件中 默认True
    save_json=False,  # 是否按照coco的json格式保存预测框,并且使用cocoapi做评估(需要同样coco的json格式的标签),
                      #运行test.py传入默认Fasle 运行train.py则传入is_coco and final_epoch(一般也是False)
    project=ROOT / "runs/val",  # 验证结果保存的根目录 默认是 runs/val
    name="exp",   # 验证结果保存的目录 默认是exp  最终: runs/val/exp
    exist_ok=False,  # 如果文件存在就increment name,不存在就新建  默认False(默认文件都是不存在的)
    half=True,    # 使用 FP16 的半精度推理
    dnn=False,    # 在 ONNX 推理时使用 OpenCV DNN 后段端
    model=None,   # 如果执行val.py就为None 如果执行train.py就会传入( model=attempt_load(f, device).half() )
    dataloader=None,   # 数据加载器 如果执行val.py就为None 如果执行train.py就会传入testloader
    save_dir=Path("")# 文件保存路径 如果执行val.py就为‘’ , 如果执行train.py就会传入save_dir(runs/train/expn)
    plots=True,  # 是否可视化 运行val.py传入,默认True 
    callbacks=Callbacks()
    compute_loss=None, # 损失函数 运行val.py传入默认None 运行train.py则传入compute_loss(train)
)
:

3.2 Initialize/load model and set device(初始化/加载模型以及设置设备)
  if training:  # 通过 train.py 调用的run函数
        device, of, engine = (
            next(model.parameters()).device,
            True,
            False,
        )  # get model device, OneFlow model
        half &= device.type != "cpu"  # half precision only supported on CUDA
        model.half() if half else model.float()
    else:  # 直接通过 val.py 调用 run 函数
        device = select_device(device, batch_size=batch_size)

        # Directories  生成 save_dir 文件路径  run/val/expn
        save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run
        (save_dir / "labels" if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

        # 加载模型 只在运行 val.py 才需要自己加载model
        model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
        
        stride, of, engine = model.stride, model.of, model.engine
        # 检测输入图片的分辨率 imgsz 是否能被 stride 整除 
        imgsz = check_img_size(imgsz, s=stride)  # check image size
        half = model.fp16  # FP16 supported on limited backends with CUDA
        if engine:
            batch_size = model.batch_size
        else:
            device = model.device
            if not of:
                batch_size = 1  # export.py models default to batch-size 1
                LOGGER.info(f"Forcing --batch-size 1 inference (1,3,{imgsz},{imgsz}) for non-OneFlow models")
        
        # Data
        data = check_dataset(data)  # check
3.3 Configure
# 配置
model.eval() # 启动模型验证模式
cuda = device.type != "cpu"
is_coco = isinstance(data.get("val"), str) and data["val"].endswith(f"coco{os.sep}val2017.txt")  # 通过 COCO 数据集的文件夹组织结构判断当前数据集是否为 COCO 数据集
nc = 1 if single_cls else int(data["nc"])  # number of classes
# 设置iou阈值 从0.5-0.95取10个(0.05间隔)   iou vector for mAP@0.5:0.95
# iouv: [0.50000, 0.55000, 0.60000, 0.65000, 0.70000, 0.75000, 0.80000, 0.85000, 0.90000, 0.95000]
iouv = flow.linspace(0.50.9510, device=device)  # iou vector for mAP@0.5:0.95
niou = iouv.numel() # 示例 mAP@0.5:0.95 iou阈值个数=10个,计算 mAP 的详细教程可以在 https://start.oneflow.org/oneflow-yolo-doc/tutorials/05_chapter/map_analysis.html 这里查看
3.4 Dataloader
通过 train.py 调用 run 函数会传入一个 Dataloader,而通过 val.py 需要加载测试数据集
# Dataloader
# 如果不是训练(执行val.py脚本调用run函数)就调用create_dataloader生成dataloader
# 如果是训练(执行train.py调用run函数)就不需要生成dataloader 可以直接从参数中传过来testloader
if not training: # 加载val数据集
    if of and not single_cls:  # check --weights are trained on --data
        ncm = model.model.nc
        assert ncm == nc, (
            f"{weights} ({ncm} classes) trained on different --data than what you passed ({nc} " f"classes). Pass correct combination of" f" --weights and --data that are trained together."
        )
    model.warmup(imgsz=(1 if of else batch_size, 3, imgsz, imgsz))  # warmup
    pad = 0.0 if task in ("speed""benchmark"else 0.5
    rect = False if task == "benchmark" else of  # square inference for benchmarks
    task = task if task in ("train""val""test"else "val"  # path to train/val/test images
    # 创建dataloader 这里的rect默认为True 矩形推理用于测试集 在不影响mAP的情况下可以大大提升推理速度
    dataloader = create_dataloader(
        data[task],
        imgsz,
        batch_size,
        stride,
        single_cls,
        pad=pad,
        rect=rect,
        workers=workers,
        prefix=colorstr(f"{task}: "),
    )[0]
3.5 初始化
# 初始化验证的图片的数量
seen = 0
# 初始化混淆矩阵
confusion_matrix = ConfusionMatrix(nc=nc)

#  获取数据集所有目标类别的类名
names = dict(enumerate(model.names if hasattr(model, "names"else model.module.names))

# coco80_to_coco91_class :  converts 80-index (val2014) to 91-index (paper) 
# https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
class_map = coco80_to_coco91_class() if is_coco else list(range(1000))
# 设置进度条模块显示信息
s = ("%20s" + "%11s" * 6) % (
    "Class",
    "Images",
    "Labels",
    "P",
    "R",
    "mAP@.5",
    "mAP@.5:.95",
)
# 初始化时间 dt[t0(预处理的时间), t1(推理的时间), t2(后处理的时间)] 和 p, r, f1, mp, mr, map50, map指标
dt, p, r, f1, mp, mr, map50, map = (
    [0.00.00.0],
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
)
#  初始化验证集的损失
loss = flow.zeros(3, device=device)
#  初始化 json 文件中的字典, 统计信息, ap, ap_class 
jdict, stats, ap, ap_class = [], [], [], []
callbacks.run("on_val_start")
# 初始化 tqdm 进度条模块
pbar = tqdm(dataloader, desc=s, bar_format="{l_bar}{bar:10}{r_bar}{bar:-10b}")

示例输出

val: data=data/coco.yaml, weights=['yolov5x'], batch_size=32, imgsz=640, conf_thres=0.001, iou_thres=0.6, task=val, 
    device=, workers=8, single_cls=False, augment=False, verbose=False, save_txt=False, save_hybrid=False
    save_conf=False, save_json=True, project=runs/val, name=exp, exist_ok=False, half=True, dnn=False
YOLOv5 

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客