【YOLOv3】源码(val.py)

概述

该文件类似于质量检查与评估团队,主要就是负责在建筑项目中的各个阶段进行质量检测和性能评估,从而确保最终的项目是符合要求的

注意这个文件会记录模型的性能数据,在YOLOv3的源码中train.py已经对其进行了调用,也是可以在模型训练完成后通过调用该文件从而判断整体性能如何

主要组成

  • 参数解析与初始化
    • 功能:解析命令行参数,设置验证配置
    • 质量检查团队制定详细的检测计划和资源分配
  • 模型与数据加载
    • 功能:加载模型和数据集,配置设备,初始化评估工具
    • 质量检查团队准备检测工具和资料,确保检测过程顺利进行
  • 推理与结果处理​​​​​​​
    • 功能:对图像进行推理,处理预测结果,匹配真实标签,计算评估指标
    • 质量检查团队对每个建筑部分进行实际检测和记录,确保每个部分符合设计标准
  • 指标计算与评估
    • ​​​​​​​
    • 功能:计算和评估模型的各项指标,生成报告和可视化图表
    • 质量检查团队总结和报告建筑质量评估结果,提供给项目经理和相关方参考
  • 结果保存与报告​​​​​​​
    • 功能:将检测结果保存为文本文件和JSON文件,生成报告
    • 质量检查团队记录和存档检测结果,便于后续分析和追踪

主要模块

参数解析与初始化

与train.py文件中的参数设置类似

def parse_opt():
    """
    解析命令行参数,配置模型推理或评估的相关设置。

    主要功能:
    - 通过命令行输入配置文件路径、模型权重路径、推理相关的参数等。
    - 返回解析后的参数对象 opt。

    返回值:
        opt: 包含所有命令行参数的命名空间对象。
    """
    # 创建 ArgumentParser 对象,用于管理命令行参数
    parser = argparse.ArgumentParser()

    # 添加参数:数据集路径
    parser.add_argument(
        '--data', 
        type=str, 
        default=ROOT / 'data/you.yaml', 
        help='dataset.yaml path'  # 数据集配置文件路径
    )

    # 添加参数:模型权重路径
    parser.add_argument(
        '--weights', 
        nargs='+', 
        type=str, 
        default=ROOT / 'runs/train/exp/weights/best.pt', 
        help='model.pt path(s)'  # 支持多个权重文件路径(使用空格分隔)
    )

    # 添加参数:批量大小
    parser.add_argument(
        '--batch-size', 
        type=int, 
        default=2, 
        help='batch size'  # 推理或验证的批量大小
    )

    # 添加参数:推理图片尺寸
    parser.add_argument(
        '--imgsz', '--img', '--img-size', 
        type=int, 
        default=416, 
        help='inference size (pixels)'  # 输入图像的分辨率大小
    )

    # 添加参数:置信度阈值
    parser.add_argument(
        '--conf-thres', 
        type=float, 
        default=0.5, 
        help='confidence threshold'  # 置信度分数阈值,低于此值的检测框将被丢弃
    )

    # 添加参数:IoU阈值
    parser.add_argument(
        '--iou-thres', 
        type=float, 
        default=0.6, 
        help='NMS IoU threshold'  # 非极大值抑制(NMS)的IoU阈值
    )

    # 添加参数:任务类型
    parser.add_argument(
        '--task', 
        default='test', 
        help='train, val, test, speed or study'  # 指定任务类型,如训练、验证、测试、速度评估或研究
    )

    # 添加参数:设备设置
    parser.add_argument(
        '--device', 
        default='', 
        help='cuda device, i.e. 0 or 0,1,2,3 or cpu'  # 指定设备(如GPU或CPU)
    )

    # 添加参数:单类别数据集
    parser.add_argument(
        '--single-cls', 
        action='store_true', 
        help='treat as single-class dataset'  # 将数据集视为单类别数据集
    )

    # 添加参数:增强推理
    parser.add_argument(
        '--augment', 
        action='store_true', 
        help='augmented inference'  # 是否在推理中使用增强技术
    )

    # 添加参数:详细信息
    parser.add_argument(
        '--verbose', 
        action='store_true', 
        help='report mAP by class'  # 是否按类别报告mAP
    )

    # 添加参数:保存文本结果
    parser.add_argument(
        '--save-txt', 
        action='store_false', 
        help='save results to *.txt'  # 是否将结果保存到文本文件中
    )

    # 添加参数:保存混合结果
    parser.add_argument(
        '--save-hybrid', 
        action='store_true', 
        help='save label+prediction hybrid results to *.txt'  # 是否保存标签和预测的混合结果
    )

    # 添加参数:保存置信度
    parser.add_argument(
        '--save-conf', 
        action='store_false', 
        help='save confidences in --save-txt labels'  # 是否在保存的标签中包含置信度
    )

    # 添加参数:保存JSON文件
    parser.add_argument(
        '--save-json', 
        action='store_false', 
        help='save a COCO-JSON results file'  # 是否将结果保存为COCO格式的JSON文件
    )

    # 添加参数:项目路径
    parser.add_argument(
        '--project', 
        default=ROOT / 'runs/val', 
        help='save to project/name'  # 保存结果的项目路径
    )

    # 添加参数:实验名称
    parser.add_argument(
        '--name', 
        default='exp', 
        help='save to project/name'  # 保存结果的实验名称
    )

    # 添加参数:覆盖现有项目
    parser.add_argument(
        '--exist-ok', 
        action='store_true', 
        help='existing project/name ok, do not increment'  # 允许覆盖现有项目
    )

    # 添加参数:半精度推理
    parser.add_argument(
        '--half', 
        action='store_true', 
        help='use FP16 half-precision inference'  # 是否使用FP16半精度进行推理
    )

    # 添加参数:OpenCV DNN支持
    parser.add_argument(
        '--dnn', 
        action='store_true', 
        help='use OpenCV DNN for ONNX inference'  # 是否使用OpenCV DNN进行ONNX推理
    )

    # 解析命令行参数
    opt = parser.parse_args()

    # 检查数据集配置文件路径是否为 YAML 格式
    opt.data = check_yaml(opt.data)

    # 如果数据集配置文件以 "coco.yaml" 结尾,则强制保存为 JSON
    opt.save_json |= opt.data.endswith('coco.yaml')

    # 如果启用了 save_hybrid,则自动启用 save_txt
    opt.save_txt |= opt.save_hybrid

    # 打印解析的参数
    print_args(FILE.stem, opt)

    # 返回解析后的参数对象
    return opt

模型与数据加载

主要作用是负责加载模型和数据集,配备训练设备,初始化混淆矩阵和评价指标。类似于质量检查团队准备检测工具和资料,确保一切准备就绪以进行有效的质量评估

@torch.no_grad()
def run(data,
        weights=None,  # 模型权重文件路径
        batch_size=32,  # 批量大小
        imgsz=640,  # 输入图片大小(像素)
        conf_thres=0.001,  # 置信度阈值
        iou_thres=0.6,  # 非极大值抑制(NMS)的IoU阈值
        task='val',  # 任务类型:'train', 'val', 'test', 'speed' 或 'study'
        device='',  # 设备选择,例如 'cuda:0' 或 'cpu'
        single_cls=False,  # 是否将数据集视为单类别
        augment=False,  # 是否在推理过程中应用数据增强
        verbose=False,  # 是否输出详细信息
        save_txt=False,  # 是否保存预测结果到 *.txt 文件
        save_hybrid=False,  # 是否保存标签和预测的混合结果到 *.txt 文件
        save_conf=False,  # 是否保存置信度到结果文件中
        save_json=False,  # 是否保存预测结果为 COCO 格式的 JSON 文件
        project=ROOT / 'runs/val',  # 保存结果的目录
        name='exp',  # 结果保存的实验名称
        exist_ok=False,  # 是否允许覆盖已存在的目录
        half=True,  # 是否使用 FP16 半精度推理
        dnn=False,  # 是否使用 OpenCV 的 DNN 模式进行 ONNX 推理
        model=None,  # 预加载的模型,如果为 None 则加载新的模型
        dataloader=None,  # 预加载的数据加载器,如果为 None 则重新创建
        save_dir=Path(''),  # 保存结果的路径
        plots=True,  # 是否绘制结果图
        callbacks=Callbacks(),  # 回调函数,用于自定义过程
        compute_loss=None,  # 自定义损失函数
        ):
    """
    YOLOv3 验证函数,执行模型推理和性能评估。

    参数:
        data: 数据集配置文件路径或解析后的字典
        weights: 模型权重路径
        batch_size: 批量大小
        imgsz: 输入图像大小(像素)
        conf_thres: 置信度阈值
        iou_thres: IoU 阈值
        task: 任务类型('val', 'train', 'test'等)
        device: 设备选择
        single_cls: 是否单类别数据集
        augment: 是否启用数据增强
        verbose: 是否输出详细日志
        save_txt: 是否保存预测结果到文本文件
        save_hybrid: 是否保存混合结果
        save_conf: 是否保存置信度
        save_json: 是否保存结果为 COCO 格式的 JSON 文件
        project: 保存结果的主目录
        name: 保存结果的子目录
        exist_ok: 是否允许覆盖已存在的目录
        half: 是否使用 FP16 半精度推理
        dnn: 是否启用 OpenCV DNN 推理
        model: 预加载模型(若无则重新加载)
        dataloader: 数据加载器(若无则重新创建)
        save_dir: 保存结果路径
        plots: 是否绘制结果图
        callbacks: 回调函数
        compute_loss: 自定义损失函数
    """

    # 判断是否为训练阶段
    training = model is not None  # 如果已加载模型,则为训练阶段,否则为推理阶段
    if training:
        # 设置设备为模型参数所在的设备
        device, pt = next(model.parameters()).device, True
        half &= device.type != 'cpu'  # 如果不是 CPU,则启用半精度推理
        model.half() if half else model.float()  # 设置模型为 FP16 或 FP32
    else:
        # 如果未提供模型,则选择设备并加载模型权重
        device = select_device(device, batch_size=batch_size)  # 自动选择 GPU 或 CPU
        save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # 创建保存目录
        (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # 创建保存结果的目录
        model = DetectMultiBackend(weights, device=device, dnn=dnn)  # 加载模型
        stride, pt = model.stride, model.pt  # 获取模型步长和类型(是否为 PyTorch)
        imgsz = check_img_size(imgsz, s=stride)  # 检查并调整输入图片大小以适应模型的步长
        half &= pt and device.type != 'cpu'  # 如果是 PyTorch 且不是 CPU,则启用半精度
        if pt:
            model.model.half() if half else model.model.float()  # 设置模型精度
        else:
            half = False
            batch_size = 1  # 非 PyTorch 模型强制设置 batch_size 为 1
            device = torch.device('cpu')  # 强制使用 CPU
            LOGGER.info(f'强制使用 --batch-size 1 和输入形状 (1,3,{imgsz},{imgsz}) 对于非 PyTorch 后端')
        data = check_dataset(data)  # 加载和检查数据集配置

    # 设置模型为评估模式
    model.eval()
    is_coco = isinstance(data.get('val'), str) and data['val'].endswith('coco/val2017.txt')  # 检查是否为 COCO 数据集
    nc = 1 if single_cls else int(data['nc'])  # 获取类别数,如果是单类别则设置为 1
    iouv = torch.linspace(0.5, 0.95, 10).to(device)  # IoU 阈值从 0.5 到 0.95,分成 10 段
    niou = iouv.numel()  # IoU 阈值的数量

    # 创建数据加载器
    if not training:  # 如果不是训练阶段
        if pt and device.type != 'cpu':  # 如果是 PyTorch 且不是 CPU
            model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.model.parameters())))  # 运行一次前向传播以初始化模型
        pad = 0.0 if task == 'speed' else 0.5  # 填充策略
        task = task if task in ('train', 'val', 'test') else 'val'  # 确保任务类型有效
        dataloader = create_dataloader(data[task], imgsz, batch_size, stride, single_cls,
                                       pad=pad, rect=pt, prefix=colorstr(f'{task}: '))[0]  # 创建数据加载器

    # 初始化变量
    seen = 0  # 已处理的图像数量
    confusion_matrix = ConfusionMatrix(nc=nc)  # 初始化混淆矩阵
    names = {k: v for k, v in enumerate(model.names if hasattr(model, 'names') else model.module.names)}  # 获取类别名称
    class_map = coco80_to_coco91_class() if is_coco else list(range(1000))  # 如果是 COCO 数据集,则获取类别映射
    s = ('%20s' + '%11s' * 6) % ('Class', 'Images', 'Labels', 'P', 'R', 'mAP@.5', 'mAP@.5:.95')  # 打印表头格式
    dt, p, r, f1, mp, mr, map50, map = [0.0, 0.0, 0.0], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0  # 初始化指标
    loss = torch.zeros(3, device=device)  # 初始化损失
    jdict, stats, ap, ap_class = [], [], [], []  # 初始化 JSON 结果字典和统计变量
    pbar = tqdm(dataloader, desc=s, ncols=NCOLS, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}')  # 进度条

推理与结果处理

在每个批次中,对图像进行推理(预测过程),然后应用非极大值抑制(NMS,主要是用于过滤重复的检测结果,确保每个问题区域只可以被记录一次),处理检测结果与真实标签的匹配,计算评估指标,最后根据需要保存结果。

类似于质量检查团队对每个建筑部分进行实际检测和记录,确保每个部分符合设计标准

代码主要逻辑

  • 预处理​​​​​​​
    • 将输入图像转换为设备可用的格式,并归一化到 [0,1] 范围。
    • 如果是训练阶段,还需将标签调整为像素坐标。
  • 推理阶段
    • ​​​​​​​forword方法,生成预测结果
  • 损失计算
    • ​​​​​​​在训练阶段计算损失,用于后续优化(验证阶段通常不进行)​​​​​​​
  • 非极大值抑制(NMS)
    • ​​​​​​​对模型输出进行 NMS,去除冗余预测框
  • 评估阶段​​​​​​​
    • 统计预测框与标签的匹配情况,计算各项指标(例如 P、R、mAP 等)
    • 如果启用绘图,则绘制预测和标签结果
  • 保存结果
    • 根据配置保存预测结果到文本文件或 JSON 文件
    • 调用回调函数以便执行自定义逻辑​​​​​​​
for batch_i, (im, targets, paths, shapes) in enumerate(pbar):  
    """
    遍历数据加载器中的每个批次,进行推理、损失计算、NMS(非极大值抑制)以及评估指标的计算。
    参数:
        batch_i: 当前批次索引
        im: 当前批次的图像张量
        targets: 当前批次的标签张量
        paths: 当前批次的图像路径
        shapes: 当前批次的原始图像尺寸和缩放信息
    """

    t1 = time_sync()  # 记录当前时间,用于计时

    # 如果是 PyTorch 模型
    if pt:
        im = im.to(device, non_blocking=True)  # 将图像张量加载到设备上(GPU 或 CPU)
        targets = targets.to(device)  # 将标签加载到设备上

    # 将图像转换为 FP16 或 FP32 浮点类型
    im = im.half() if half else im.float()
    im /= 255  # 将像素值归一化到 [0, 1] 范围
    nb, _, height, width = im.shape  # 获取批次大小和图像的高度、宽度
    t2 = time_sync()  # 记录时间
    dt[0] += t2 - t1  # 记录数据加载和预处理的时间

    # 推理
    out, train_out = model(im) if training else model(im, augment=augment, val=True)  
    """
    如果在训练阶段,调用模型进行标准推理;否则调用模型进行验证推理。
    augment 参数用于是否启用数据增强。
    train_out 为训练阶段的输出,out 为推理结果。
    """
    dt[1] += time_sync() - t2  # 记录推理时间

    # 损失计算(仅在训练阶段进行)
    if compute_loss:
        loss += compute_loss([x.float() for x in train_out], targets)[1]  # 计算损失并累积

    # NMS(非极大值抑制)处理
    targets[:, 2:] *= torch.Tensor([width, height, width, height]).to(device)  
    """
    将目标标签的坐标从相对坐标(归一化)转换为像素坐标。
    width 和 height 分别是图像的宽度和高度。
    """
    lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else []  
    """
    如果 save_hybrid 为 True,则根据目标标签生成与 NMS 配合的辅助标签。
    """
    t3 = time_sync()  # 记录时间
    out = non_max_suppression(out, conf_thres, iou_thres, labels=lb, multi_label=True, agnostic=single_cls)  
    """
    对模型的输出进行 NMS 操作,去除冗余预测框。
    参数:
        conf_thres: 置信度阈值
        iou_thres: IoU 阈值
        labels: 传入的辅助标签(如果有)
        multi_label: 是否支持多标签
        agnostic: 是否类别无关
    """
    dt[2] += time_sync() - t3  # 记录 NMS 时间

    # 评估指标计算
    for si, pred in enumerate(out):  
        """
        遍历当前批次的每张图像的预测结果。
        si: 当前图像在批次中的索引
        pred: 当前图像的预测结果
        """
        labels = targets[targets[:, 0] == si, 1:]  # 获取当前图像的标签
        nl = len(labels)  # 获取标签数量
        tcls = labels[:, 0].tolist() if nl else []  # 提取标签类别
        path, shape = Path(paths[si]), shapes[si][0]  # 获取图像路径和原始形状
        seen += 1  # 统计已处理的图像数量

        # 如果没有预测结果
        if len(pred) == 0:
            if nl:  # 如果标签数量不为 0,则统计错误样本
                stats.append((torch.zeros(0, niou, dtype=torch.bool), torch.Tensor(), torch.Tensor(), tcls))
            continue

        # 如果是单类别数据集,则将所有预测的类别设为 0
        if single_cls:
            pred[:, 5] = 0

        # 将预测框的坐标转换为原始图像的坐标
        predn = pred.clone()  # 克隆预测结果
        scale_coords(im[si].shape[1:], predn[:, :4], shape, shapes[si][1])  # 坐标缩放到原始图像尺寸

        # 如果有标签,则计算精确匹配
        if nl:
            tbox = xywh2xyxy(labels[:, 1:5])  # 将标签的坐标从 (x, y, w, h) 转换为 (x1, y1, x2, y2)
            scale_coords(im[si].shape[1:], tbox, shape, shapes[si][1])  # 缩放标签坐标到原始图像尺寸
            labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # 合并标签类别和坐标
            correct = process_batch(predn, labelsn, iouv)  # 计算预测框与标签框的匹配情况
            if plots:  # 如果启用绘图,则更新混淆矩阵
                confusion_matrix.process_batch(predn, labelsn)
        else:
            # 如果没有标签,则所有预测框均为错误预测
            correct = torch.zeros(pred.shape[0], niou, dtype=torch.bool)

        # 更新评估统计信息
        stats.append((correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls))

        # 保存预测结果到文本文件(如果启用)
        if save_txt:
            save_one_txt(predn, save_conf, shape, file=save_dir / 'labels' / (path.stem + '.txt'))

        # 保存预测结果为 COCO 格式 JSON 文件(如果启用)
        if save_json:
            save_one_json(predn, jdict, path, class_map)

        # 调用回调函数
        callbacks.run('on_val_image_end', pred, predn, path, names, im[si])

    # 如果启用绘图,并且当前批次的索引小于 3,则绘制标签和预测结果
    if plots and batch_i < 3:
        # 绘制标签结果
        f = save_dir / f'val_batch{batch_i}_labels.jpg'
        Thread(target=plot_images, args=(im, targets, paths, f, names), daemon=True).start()

        # 绘制预测结果
        f = save_dir / f'val_batch{batch_i}_pred.jpg'
        Thread(target=plot_images, args=(im, output_to_target(out), paths, f, names), daemon=True).start()

指标计算与评估

计算和评估模型的各项指标,然后保存成可视化图表

# 计算评估指标
stats = [np.concatenate(x, 0) for x in zip(*stats)]  # 将统计信息中的每一项(如正确预测、置信度、类别等)按批次合并
if len(stats) and stats[0].any():  # 如果统计信息非空,且有预测结果
    p, r, ap, f1, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)  
    """
    调用 `ap_per_class` 计算各类的评估指标,包括精度 (p)、召回率 (r)、平均精度 (AP)、F1 值 (f1) 和 AP 对应的类别 (ap_class)。
    参数:
        stats: 合并后的统计信息
        plot: 是否绘制指标图
        save_dir: 保存结果的目录
        names: 类别名称
    """
    ap50, ap = ap[:, 0], ap.mean(1)  # 提取 AP@0.5 和平均 AP(所有 IoU 阈值的平均值)
    mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()  
    """
    计算整体的评估指标:
        mp: 平均精度
        mr: 平均召回率
        map50: 平均 AP@0.5
        map: 平均 AP(所有 IoU 阈值的平均值)
    """
    nt = np.bincount(stats[3].astype(np.int64), minlength=nc)  # 统计每类的目标数量
else:
    nt = torch.zeros(1)  # 如果没有统计信息,目标数量为 0

# 打印整体结果
pf = '%20s' + '%11i' * 2 + '%11.3g' * 4  # 格式化输出字符串
print(pf % ('all', seen, nt.sum(), mp, mr, map50, map))  
"""
打印评估指标:
    all: 表示所有类别的整体结果
    seen: 总共处理的图像数
    nt.sum(): 总目标数量
    mp: 平均精度
    mr: 平均召回率
    map50: 平均 AP@0.5
    map: 平均 AP
"""

# 打印每个类别的评估结果
if (verbose or (nc < 50 and not training)) and nc > 1 and len(stats):  
    """
    如果启用了 verbose 模式,或者类别数少于 50 且不在训练模式中,则逐类打印结果。
    """
    for i, c in enumerate(ap_class):  
        """
        遍历每个类别的评估结果:
            i: 当前类别的索引
            c: 类别编号
        """
        print(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))  
        """
        打印该类别的评估指标:
            names[c]: 类别名称
            nt[c]: 当前类别的目标数量
            p[i]: 精度
            r[i]: 召回率
            ap50[i]: AP@0.5
            ap[i]: 平均 AP
        """

# 打印处理速度
t = tuple(x / seen * 1E3 for x in dt)  # 计算每张图像的平均处理时间(单位:毫秒)
if not training:
    shape = (batch_size, 3, imgsz, imgsz)  # 输入图像的形状
    print(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {shape}' % t)  
    """
    打印速度:
        pre-process: 预处理时间
        inference: 推理时间
        NMS: 非极大值抑制时间
        shape: 输入图像的形状
    """

# 绘制混淆矩阵
if plots:
    confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))  # 绘制混淆矩阵并保存到指定目录
    callbacks.run('on_val_end')  # 运行验证结束时的回调函数

# 保存 JSON 文件
if save_json and len(jdict):  # 如果启用了 JSON 保存并且有预测结果
    w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else ''  
    """
    获取权重文件的名称(不带路径和后缀)。
    """
    anno_json = str(Path(data.get('path', '../coco')) / 'annotations/instances_val2017.json')  
    """
    定义 COCO 数据集的注释文件路径。
    """
    pred_json = str(save_dir / f"{w}_predictions.json")  # 定义预测结果保存路径
    LOGGER.info(f'\nEvaluating pycocotools mAP... saving {pred_json}...')  
    """
    日志记录,提示保存预测结果到 JSON 文件。
    """
    with open(pred_json, 'w') as f:
        json.dump(jdict, f)  # 将预测结果保存为 JSON 文件

    # 使用 pycocotools 进行评估
    try:
        check_requirements(['pycocotools'])  # 检查是否安装了 pycocotools
        from pycocotools.coco import COCO
        from pycocotools.cocoeval import COCOeval

        anno = COCO(anno_json)  # 加载 COCO 注释文件
        pred = anno.loadRes(pred_json)  # 加载预测结果
        eval = COCOeval(anno, pred, 'bbox')  # 创建评估器
        if is_coco:  # 如果是 COCO 数据集,设置图像 ID
            eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.img_files]
        eval.evaluate()  # 执行评估
        eval.accumulate()  # 累计结果
        eval.summarize()  # 打印评估摘要
        map, map50 = eval.stats[:2]  # 获取 mAP 和 mAP@0.5
    except Exception as e:
        LOGGER.info(f'pycocotools unable to run: {e}')  # 如果出现异常,记录日志

# 返回结果
model.float()  # 将模型切换为 FP32 精度
if not training:
    s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''  
    """
    如果启用了保存文本标签,统计保存的标签数量。
    """
    LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}{s}")  # 记录结果保存路径
maps = np.zeros(nc) + map  # 初始化每类的 mAP 为整体 mAP
for i, c in enumerate(ap_class):  # 遍历每个类别
    maps[c] = ap[i]  # 更新每类的 mAP
return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t  
"""
返回最终结果:
    mp: 平均精度
    mr: 平均召回率
    map50: 平均 AP@0.5
    map: 平均 AP
    loss: 损失值
    maps: 每类的 mAP
    t: 每阶段的时间消耗
"""

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值