YOLOV5代码精读之val.py

一、main函数

def main(opt):
    check_requirements(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')
        if opt.save_hybrid:
            LOGGER.info('WARNING ⚠️ --save-hybrid will return high mAP from hybrid labels, not from predictions alone')
        run(**vars(opt))

    else:
        weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]
        opt.half = torch.cuda.is_available() and opt.device != 'cpu'  # FP16 for fastest results
        if opt.task == 'speed':  # speed benchmarks
            # python val.py --task speed --data coco.yaml --batch 1 --weights yolov5n.pt yolov5s.pt...
            opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
            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.pt yolov5s.pt...
            for opt.weights in weights:
                f = f'study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt'  # filename to save to
                x, y = list(range(256, 1536 + 128, 128)), []  # x axis (image sizes), y axis
                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


if __name__ == "__main__":
    opt = parse_opt()
    main(opt)

这段代码主要是运行YOLOv5模型的主函数 main,并根据用户的输入选项执行相应的任务。我们将逐步分解并详细解释这段代码。

1.1 依赖检查

check_requirements(exclude=('tensorboard', 'thop'))

这个函数用于检查程序所需的库和依赖项,确保环境中有必要的组件。如果某些指定的库(如 tensorboard 和 thop)存在,它们将被排除在外。

1.2 任务执行

if opt.task in ('train', 'val', 'test'):

根据用户输入的 task 值,决定执行的任务。如果任务为 train(训练),val(验证)或 test(测试),则进入相应的逻辑:

1.2.1 置信度阈值检查

if opt.conf_thres > 0.001:
    LOGGER.info(f'WARNING ⚠️ confidence threshold {opt.conf_thres} > 0.001 produces invalid results')

如果用户配置的置信度阈值大于 0.001,会发出警告信息。

1.2.2 混合结果保存警告

if opt.save_hybrid:
    LOGGER.info('WARNING ⚠️ --save-hybrid will return high mAP from hybrid labels, not from predictions alone')

如果用户选择保存混合结果,会提示用户混合标签可能产生更高的mAP(平均精确度)而非预测结果。

1.2.3 运行主函数

run(**vars(opt))

调用 run 函数,传递解析后的选项参数进行实际的任务执行。

1.3 其他任务处理 

如果 task 不是 trainval 或 test,则进行以下处理:

1.3.1 权重检查

weights = opt.weights if isinstance(opt.weights, list) else [opt.weights]

确保 weights 是一个列表,即使用户输入的是单个权重。

1.3.2 设置半精度

opt.half = torch.cuda.is_available() and opt.device != 'cpu'

根据是否可用的 CUDA 设备,决定是否使用半精度(FP16)以加快推理速度。

1.3.3 速度基准测试

        if opt.task == 'speed':  # speed benchmarks
            # python val.py --task speed --data coco.yaml --batch 1 --weights yolov5n.pt yolov5s.pt...
            opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
            for opt.weights in weights:
                run(**vars(opt), plots=False)

如果选定任务为 speed,则设置置信度阈值和 IoU 阈值,并运行基准测试。

这段代码是用来执行模型速度基准测试的。我们来逐步分解并详细解释这段代码。

  1. 条件判断 (if opt.task == 'speed':):

    • 这行代码检查命令行参数 opt.task 的值是否为 'speed'。如果是,则执行下方的代码块。这个条件通常是在执行速度基准测试时使用,即评估模型的推理速度。
  2. 注释:

    • 提供了如何运行这个脚本的示例命令,说明可以通过指定任务为 speed 来进行速度基准测试。提供了相关的参数说明,如数据集的路径和模型的权重。
  3. 设置阈值与参数:

    opt.conf_thres, opt.iou_thres, opt.save_json = 0.25, 0.45, False
    
    • 这行用于设置模型的推理参数:
      • opt.conf_thres 被设置为 0.25,这是置信度阈值,模型在进行分类时需要超过此值的结果才会被认为是可用的。
      • opt.iou_thres 被设置为 0.45,这是非极大值抑制(NMS)的IoU阈值,IoU值高于此阈值的边框将被认为是重叠的,会被抑制。
      • opt.save_json 被设置为 False,这意味着在进行速度测试时不会保存预测结果到JSON文件。
  4. 循环遍历模型权重:

    for opt.weights in weights:
    
    • 这行代码开始一个循环,遍历所有指定的模型权重(weights),每个权重文件都将用来进行速度测试。
  5. 运行模型:

    run(**vars(opt), plots=False)
    
    • 调用 run 函数,并使用当前的 opt 参数来执行速度测试。**vars(opt) 把 opt 中的所有参数以关键字参数的形式传入。
    • plots=False 表示在执行时不绘制可视化图像,通常在速度基准测试中为了专注于计算速度,这一步是必要的。

这段代码的主要功能是执行 YOLOv5 模型的速度基准测试通过设置合适的阈值和参数,它能够有效地评估模型在指定的输入上进行推理的速度。该部分代码从命令行参数确认任务类型为速度测试,并且会依次测试多个模型权重,记录它们的推理性能,而不关心可视化和结果的保存。这是在部署前评估模型高效性的关键步骤。

1.3.4 研究任务

 elif opt.task == 'study':  # speed vs mAP benchmarks
            # python val.py --task study --data coco.yaml --iou 0.7 --weights yolov5n.pt yolov5s.pt...
            for opt.weights in weights:
                f = f'study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt'  # filename to save to
                x, y = list(range(256, 1536 + 128, 128)), []  # x axis (image sizes), y axis
                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

如果选定任务为 study,则进行速度与 mAP 的比较。

  • 收集不同图片大小下的结果并保存到文件。
  • 使用 np.savetxt 保存结果到文本文件,并将多个结果文件压缩为一个 study.zip

这段代码是一个实验的主逻辑部分,主要用于性能分析,特别是通过改变不同的图像尺寸(imgsz)来研究模型的速度和准确性。以下是代码的逐步分解及详细解释:

  1. 任务设置

    # python val.py --task study --data coco.yaml --iou 0.7 --weights yolov5n.pt yolov5s.pt...
    

    这条注释是指令,用于在命令行中运行该脚本以进行“研究”任务,使用的数据集为coco.yaml,IoU阈值为0.7,模型权重提供了多个文件。

  2. 循环遍历权重

    for opt.weights in weights:
    

    这里使用for循环遍历不同的模型权重(如yolov5n.ptyolov5s.pt)。每次循环将opt.weights设置为当前权重。

  3. 生成文件名

    f = f'study_{Path(opt.data).stem}_{Path(opt.weights).stem}.txt'  # filename to save to
    

    通过Path库提取数据文件和权重文件的基本名称(不包含扩展名),并构造一个文件名,用于保存当前实验的结果。

  4. 设置图像尺寸和结果容器

    x, y = list(range(256, 1536 + 128, 128)), []  # x axis (image sizes), y axis
    
    • x是一个从256到1536的列表,步长为128,用于表示要测试的不同图像尺寸。
    • y是一个空列表,用于存储每个图像尺寸的实验结果和时间。
  5. 循环遍历图像尺寸

    for opt.imgsz in x:  # img-size
    

    对于每个图像尺寸,都会执行一轮新的实验。

  6. 日志记录

    LOGGER.info(f'\nRunning {f} --imgsz {opt.imgsz}...')
    

    在日志中记录当前实验的进程,显示正在运行的文件名和当前的图像尺寸。

  7. 运行实验

    r, _, t = run(**vars(opt), plots=False)
    

    调用run函数,使用当前的参数(包括图像尺寸、权重等)执行实验。返回值r是结果,t是时间。结果集中_是一个占位符,用于忽略其他返回值。

  8. 记录结果和时间

    y.append(r + t)  # results and times
    

    将当前的结果和时间添加到y列表中,组合在一起进行存储。

  9. 保存结果到文本文件

    np.savetxt(f, y, fmt='%10.4g')  # save
    

    将所有实验结果保存到指定的文本文件中。使用np.savetxt,格式化以保留四位有效数字。

  10. 压缩保存的文件

    os.system('zip -r study.zip study_*.txt')
    

    使用系统命令将所有以study_开头的文本文件压缩成一个名为study.zip的文件。

  11. 绘制结果图

    plot_val_study(x=x)  # plot
    

    调用plot_val_study函数,将结果进行可视化,x轴为图像尺寸,y轴为对应的性能指标。

这段代码的主要功能是进行模型性能评估,特别是探索不同图像尺寸对YOLOv5模型检测精度和速度的影响。通过记录实验结果,保存到文件,压缩结果,以及绘制性能图,帮助研究者更直观地分析模型在各种条件下的表现。整个逻辑清晰,强调了如何系统化地进行多轮实验以寻找最佳的参数设置。

1.4 总结

这段代码的主要功能是执行不同的任务(训练、验证、测试、速度基准测试和研究)以评估YOLOv5模型。它根据用户的输入选项进行适当的环境检查、任务执行和结果记录。此代码不仅确保模型的运行符合预期,还提供了警告以避免常见的错误配置(如置信度阈值过高等),并支持结果的高效管理与评估。

 二、 parse_opt 函数

def parse_opt():
    parser = argparse.ArgumentParser()
    parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path')
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5s.pt', help='model path(s)')
    parser.add_argument('--batch-size', type=int, default=32, help='batch size')
    parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='inference size (pixels)')
    parser.add_argument('--conf-thres', type=float, default=0.001, help='confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.6, help='NMS IoU threshold')
    parser.add_argument('--max-det', type=int, default=300, help='maximum detections per image')
    parser.add_argument('--task', default='val', 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')
    parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)')
    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')
    parser.add_argument('--save-txt', action='store_true', 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_true', help='save confidences in --save-txt labels')
    parser.add_argument('--save-json', action='store_true', help='save a COCO-JSON results file')
    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')
    parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
    opt = parser.parse_args()
    opt.data = check_yaml(opt.data)  # check YAML
    opt.save_json |= opt.data.endswith('coco.yaml')
    opt.save_txt |= opt.save_hybrid
    print_args(vars(opt))
    return opt

这段代码定义了一个parse_opt函数,主要用于解析命令行参数以及设置一些默认值。详细解释如下:

  1. 创建参数解析对象

    parser = argparse.ArgumentParser()
    

    通过argparse.ArgumentParser()创建一个命令行参数解析器对象,可以用来添加和处理命令行参数。

  2. 添加参数: 代码使用add_argument方法添加了多个命令行参数,下面逐一介绍:

    • --data:指定数据集的yaml文件路径,默认为ROOT / 'data/coco128.yaml'
    • --weights:指定模型权重路径,支持多个路径,默认为ROOT / 'yolov5s.pt'
    • --batch-size:设置批处理大小,默认为32。
    • --imgsz--img--img-size:设置推理图像的尺寸,默认为640像素。
    • --conf-thres:指定置信度阈值,默认为0.001。
    • --iou-thres:指定非极大抑制(NMS)IoU阈值,默认为0.6。
    • --max-det:每张图片最大检测数量,默认为300。
    • --task:指定任务类型(例如:训练、验证、测试、速度评测或学习),默认为'val'(验证)。
    • --device:指定设备(如CUDA设备),默认为空。
    • --workers:设置最大数据加载工作线程数,默认为8。
    • --single-cls:布尔值,标记是否将数据集视为单一类别。
    • --augment:布尔值,表示是否进行增强推理。
    • --verbose:布尔值,是否报告每个类的mAP。
    • --save-txt--save-hybrid--save-conf--save-json:用于保存结果的选项,包括保存到文本文件、混合结果和COCO格式JSON。
    • --project:指定保存结果的项目路径,默认为ROOT / 'runs/val'
    • --name:指定结果保存的名称,默认为'exp'。
    • --exist-ok:布尔值,表示是否允许使用已存在的项目名称而不进行增量处理。
    • --half:布尔值,是否使用FP16半精度推理。
    • --dnn:布尔值,使用OpenCV DNN进行ONNX推理。
  3. 解析参数

    opt = parser.parse_args()
    

    调用parse_args方法解析命令行传入的参数,并将其存储在opt对象中。

  4. 检查数据集配置文件

    opt.data = check_yaml(opt.data)  # check YAML
    

    调用check_yaml函数检查--data参数指定的YAML文件是否有效。

  5. 条件设置

    opt.save_json |= opt.data.endswith('coco.yaml')
    opt.save_txt |= opt.save_hybrid
    

    这两行代码使用位或赋值操作符,用于根据条件自动设置save_jsonsave_txt的值。

  6. 打印参数

    print_args(vars(opt))
    

    打印解析后的参数信息。

  7. 返回参数对象

    return opt
    

    返回包含所有解析参数的对象。

parse_opt函数的主要功能是从命令行提取并解析各种参数,这些参数用于配置YOLOv5模型的验证、训练或其他相关任务。通过设置默认值和类型,它确保程序在运行时可以灵活地调整行为,以满足用户的需求。这个函数是整个代码的基础部分,提供了所有必要的配置信息。

 三、run函数

def run(
        data,
        weights=None,  # model.pt path(s)
        batch_size=32,  # batch size
        imgsz=640,  # inference size (pixels)
        conf_thres=0.001,  # confidence threshold
        iou_thres=0.6,  # NMS IoU threshold
        max_det=300,  # maximum detections per image
        task='val',  # train, val, test, speed or study
        device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu
        workers=8,  # max dataloader workers (per RANK in DDP mode)
        single_cls=False,  # treat as single-class dataset
        augment=False,  # augmented inference
        verbose=False,  # verbose output
        save_txt=False,  # save results to *.txt
        save_hybrid=False,  # save label+prediction hybrid results to *.txt
        save_conf=False,  # save confidences in --save-txt labels
        save_json=False,  # save a COCO-JSON results file
        project=ROOT / 'runs/val',  # save to project/name
        name='exp',  # save to project/name
        exist_ok=False,  # existing project/name ok, do not increment
        half=True,  # use FP16 half-precision inference
        dnn=False,  # use OpenCV DNN for ONNX inference
        model=None,
        dataloader=None,
        save_dir=Path(''),
        plots=True,
        callbacks=Callbacks(),
        compute_loss=None,
):
    # Initialize/load model and set device
    training = model is not None
    if training:  # called by train.py
        device, pt, jit, engine = next(model.parameters()).device, True, False, False  # get model device, PyTorch model
        half &= device.type != 'cpu'  # half precision only supported on CUDA
        model.half() if half else model.float()
    else:  # called directly
        device = select_device(device, batch_size=batch_size)

        # Directories
        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

        # Load model
        model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
        stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
        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 (pt or jit):
                batch_size = 1  # export.py models default to batch-size 1
                LOGGER.info(f'Forcing --batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models')

        # Data
        data = check_dataset(data)  # check

    # 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 dataset
    nc = 1 if single_cls else int(data['nc'])  # number of classes
    iouv = torch.linspace(0.5, 0.95, 10, device=device)  # iou vector for mAP@0.5:0.95
    niou = iouv.numel()

    # Dataloader
    if not training:
        if pt 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 --weights and --data that are trained together.'
        model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz))  # warmup
        pad, rect = (0.0, False) if task == 'speed' else (0.5, pt)  # square inference for benchmarks
        task = task if task in ('train', 'val', 'test') else 'val'  # path to train/val/test images
        dataloader = create_dataloader(data[task],
                                       imgsz,
                                       batch_size,
                                       stride,
                                       single_cls,
                                       pad=pad,
                                       rect=rect,
                                       workers=workers,
                                       prefix=colorstr(f'{task}: '))[0]

    seen = 0
    confusion_matrix = ConfusionMatrix(nc=nc)
    names = model.names if hasattr(model, 'names') else model.module.names  # get class names
    if isinstance(names, (list, tuple)):  # old format
        names = dict(enumerate(names))
    class_map = coco80_to_coco91_class() if is_coco else list(range(1000))
    s = ('%22s' + '%11s' * 6) % ('Class', 'Images', 'Instances', 'P', 'R', 'mAP50', 'mAP50-95')
    tp, fp, p, r, f1, mp, mr, map50, ap50, map = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
    dt = Profile(), Profile(), Profile()  # profiling times
    loss = torch.zeros(3, device=device)
    jdict, stats, ap, ap_class = [], [], [], []
    callbacks.run('on_val_start')
    pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT)  # progress bar
    for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
        callbacks.run('on_val_batch_start')
        with dt[0]:
            if cuda:
                im = im.to(device, non_blocking=True)
                targets = targets.to(device)
            im = im.half() if half else im.float()  # uint8 to fp16/32
            im /= 255  # 0 - 255 to 0.0 - 1.0
            nb, _, height, width = im.shape  # batch size, channels, height, width

        # Inference
        with dt[1]:
            preds, train_out = model(im) if compute_loss else (model(im, augment=augment), None)

        # Loss
        if compute_loss:
            loss += compute_loss(train_out, targets)[1]  # box, obj, cls

        # NMS
        targets[:, 2:] *= torch.tensor((width, height, width, height), device=device)  # to pixels
        lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else []  # for autolabelling
        with dt[2]:
            preds = non_max_suppression(preds,
                                        conf_thres,
                                        iou_thres,
                                        labels=lb,
                                        multi_label=True,
                                        agnostic=single_cls,
                                        max_det=max_det)

        # Metrics
        for si, pred in enumerate(preds):
            labels = targets[targets[:, 0] == si, 1:]
            nl, npr = labels.shape[0], pred.shape[0]  # number of labels, predictions
            path, shape = Path(paths[si]), shapes[si][0]
            correct = torch.zeros(npr, niou, dtype=torch.bool, device=device)  # init
            seen += 1

            if npr == 0:
                if nl:
                    stats.append((correct, *torch.zeros((2, 0), device=device), labels[:, 0]))
                    if plots:
                        confusion_matrix.process_batch(detections=None, labels=labels[:, 0])
                continue

            # Predictions
            if single_cls:
                pred[:, 5] = 0
            predn = pred.clone()
            scale_boxes(im[si].shape[1:], predn[:, :4], shape, shapes[si][1])  # native-space pred

            # Evaluate
            if nl:
                tbox = xywh2xyxy(labels[:, 1:5])  # target boxes
                scale_boxes(im[si].shape[1:], tbox, shape, shapes[si][1])  # native-space labels
                labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # native-space labels
                correct = process_batch(predn, labelsn, iouv)
                if plots:
                    confusion_matrix.process_batch(predn, labelsn)
            stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0]))  # (correct, conf, pcls, tcls)

            # Save/log
            if save_txt:
                save_one_txt(predn, save_conf, shape, file=save_dir / 'labels' / f'{path.stem}.txt')
            if save_json:
                save_one_json(predn, jdict, path, class_map)  # append to COCO-JSON dictionary
            callbacks.run('on_val_image_end', pred, predn, path, names, im[si])

        # Plot images
        if plots and batch_i < 3:
            plot_images(im, targets, paths, save_dir / f'val_batch{batch_i}_labels.jpg', names)  # labels
            plot_images(im, output_to_target(preds), paths, save_dir / f'val_batch{batch_i}_pred.jpg', names)  # pred

        callbacks.run('on_val_batch_end', batch_i, im, targets, paths, shapes, preds)

    # Compute metrics
    stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)]  # to numpy
    if len(stats) and stats[0].any():
        tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
        ap50, ap = ap[:, 0], ap.mean(1)  # AP@0.5, AP@0.5:0.95
        mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
    nt = np.bincount(stats[3].astype(int), minlength=nc)  # number of targets per class

    # Print results
    pf = '%22s' + '%11i' * 2 + '%11.3g' * 4  # print format
    LOGGER.info(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
    if nt.sum() == 0:
        LOGGER.warning(f'WARNING ⚠️ no labels found in {task} set, can not compute metrics without labels')

    # Print results per class
    if (verbose or (nc < 50 and not training)) and nc > 1 and len(stats):
        for i, c in enumerate(ap_class):
            LOGGER.info(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))

    # Print speeds
    t = tuple(x.t / seen * 1E3 for x in dt)  # speeds per image
    if not training:
        shape = (batch_size, 3, imgsz, imgsz)
        LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {shape}' % t)

    # Plots
    if plots:
        confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
        callbacks.run('on_val_end', nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix)

    # Save JSON
    if save_json and len(jdict):
        w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else ''  # weights
        anno_json = str(Path(data.get('path', '../coco')) / 'annotations/instances_val2017.json')  # annotations json
        pred_json = str(save_dir / f"{w}_predictions.json")  # predictions json
        LOGGER.info(f'\nEvaluating pycocotools mAP... saving {pred_json}...')
        with open(pred_json, 'w') as f:
            json.dump(jdict, f)

        try:  # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
            check_requirements('pycocotools')
            from pycocotools.coco import COCO
            from pycocotools.cocoeval import COCOeval

            anno = COCO(anno_json)  # init annotations api
            pred = anno.loadRes(pred_json)  # init predictions api
            eval = COCOeval(anno, pred, 'bbox')
            if is_coco:
                eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.im_files]  # image IDs to evaluate
            eval.evaluate()
            eval.accumulate()
            eval.summarize()
            map, map50 = eval.stats[:2]  # update results (mAP@0.5:0.95, mAP@0.5)
        except Exception as e:
            LOGGER.info(f'pycocotools unable to run: {e}')

    # Return results
    model.float()  # for training
    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
    for i, c in enumerate(ap_class):
        maps[c] = ap[i]
    return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t

这段代码定义了一个名为 run 的函数,该函数用于执行目标检测模型(例如 YOLOv5)在验证、测试或训练数据集上的推理和评估。下面是对该函数的逐步分解和详细解释:

3.1. 函数参数

  • data: 数据集的配置。
  • weights: 模型权重文件路径。
  • batch_size: 每批次处理的样本数量。
  • imgsz: 输入图像的尺寸。
  • conf_thres: 置信度阈值,低于该值的检测将被忽略。
  • iou_thres: 非最大抑制(NMS)IoU阈值,控制不同检测框的重叠。
  • max_det: 每张图像的最大检测数量。
  • task: 指定任务类型,可能的值为 'train', 'val', 'test', 'speed' 或 'study'。
  • device: 指定使用的计算设备(如 CPU 或 GPU)。
  • workers: 用于数据加载的工作线程数量。
  • single_cls: 是否将数据集视为单类数据集。
  • augment: 是否使用数据增强。
  • verbose: 是否输出详细结果。
  • save_txt: 是否将结果保存为文本文件。
  • save_hybrid: 是否保存混合标签和预测结果。
  • save_conf: 是否保存置信度。
  • save_json: 是否保存为 COCO JSON 格式的结果。
  • projectnameexist_ok: 控制模型保存路径参数。
  • half: 是否使用半精度(FP16)推理。
  • dnn: 是否使用 OpenCV DNN 进行 ONNX 推理。
  • modeldataloadersave_dirplotscallbackscompute_loss: 额外参数,用于模型、数据加载器和回调函数的处理。

3.2. 模型初始化与设备配置

 # Initialize/load model and set device
    training = model is not None
    if training:  # called by train.py
        device, pt, jit, engine = next(model.parameters()).device, True, False, False  # get model device, PyTorch model
        half &= device.type != 'cpu'  # half precision only supported on CUDA
        model.half() if half else model.float()
    else:  # called directly
        device = select_device(device, batch_size=batch_size)

        # Directories
        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
  • 检查是否提供了模型,通过 model.parameters() 来获取模型的当前设备并设置为相应的类型。
  • 如果不是训练模式,选择使用的设备(CPU 或 GPU),并创建保存结果的目录。

这段代码主要负责初始化和加载模型,同时设置计算设备。以下是代码的逐步分解与详细解释:

  1. 培训模式判断

    training = model is not None
    

    这一行检查模型 model 是否已被赋值。如果 model 不是 None,意味着当前是执行训练过程,因此将 training 设置为 True

  2. 初始化在训练状态下的设备

    if training:  # called by train.py
    

    这段代码表示如果是在训练模式下,将执行以下初始化过程。

  3. 获取模型的设备信息

    device, pt, jit, engine = next(model.parameters()).device, True, False, False  # get model device, PyTorch model
    

    通过获取模型参数的设备信息,来确定模型是在哪个硬件上进行训练的(如 GPU 或 CPU)。这里将 pt 设置为 True,表示模型是一个 PyTorch 模型。

  4. 设置半精度浮点数模式

    half &= device.type != 'cpu'  # half precision only supported on CUDA
    

    只有当计算设备是 CUDA 时,才能使用半精度浮点数。half 的值可能用作后续判断,决定是否使用 half 精度。

  5. 模型转换为半精度或单精度

    model.half() if half else model.float()
    

    基于之前的判断,将模型转换为半精度(FP16)或单精度(FP32),以优化计算性能。

  6. 非训练状态下的设备选择

    else:  # called directly
        device = select_device(device, batch_size=batch_size)
    

    如果不在训练模式下,调用 select_device 函数来选择合适的计算设备,基于用户输入的设备参数。

  7. 目录设置与创建

    save_dir = increment_path(Path(project) / name, exist_ok=exist_ok)  # increment run
    

    使用 increment_path 函数来创建一个新的保存目录,目的是为了避免覆盖之前的实验结果。project 和 name 参数指定了保存路径的基础结构。

  8. 创建保存标签的目录

    (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir
    

    根据 save_txt 标志来决定是否创建一个专门用于保存标签的子目录,如果选择保存标签则创建 labels 目录,否则直接使用 save_dirmkdir 函数确保在需要时创建所有父目录。

代码的主要功能是初始化和设置深度学习模型的运行环境。它根据当前是否处于训练模式,来决定模型设备的选择、模型的精度设置、保存目录的创建等。这些操作为后续的模型训练或推理过程提供了必要的基础配置。

3.3 加载模型

       # Load model
        model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
        stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
        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 (pt or jit):
                batch_size = 1  # export.py models default to batch-size 1
                LOGGER.info(f'Forcing --batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models')
  • 使用 DetectMultiBackend 类加载模型,支持多种后端。
  • 检查输入图像的大小是否符合要求,支持 FP16 精度,确保 CUDA 支持。

  1. 加载模型

    model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
    

    这一行代码用于加载一个多后端检测模型(DetectMultiBackend),它可以根据提供的权重文件、设备类型、DNN选项、数据集信息以及半精度设置来初始化。weights参数指定了模型的权重文件,device指定了计算使用的设备(如CPU或GPU),dnn用于指示是否使用OpenCV DNN进行推断,data传递数据集信息,fp16指明是否使用半精度浮点数。

  2. 获取模型属性

    stride, pt, jit, engine = model.stride, model.pt, model.jit, model.engine
    

    该行代码从加载的模型中提取了一些重要的属性:

    • stride:模型的步幅设置,通常与输入图像的尺寸和网络结构有关。
    • pt:指示该模型是否为PyTorch模型。
    • jit:指示该模型是否为JIT(Just-In-Time)编译的模型。
    • engine:指示该模型是否被编译为TensorRT引擎。
  3. 检查图像大小

    imgsz = check_img_size(imgsz, s=stride)  # check image size
    

    这里调用了check_img_size函数来验证给定的图像尺寸是否符合模型要求。该函数会根据模型的步幅进行调整,以确保输入图像大小是合适的。

  4. 设置半精度(FP16)

    half = model.fp16  # FP16 supported on limited backends with CUDA
    

    从模型中获取其是否支持FP16(半精度浮点数)运算的设置。FP16可以提高某些硬件的计算速度和节省内存使用,但并不是所有情况都能使用。

  5. 设置批处理大小

    if engine:
        batch_size = model.batch_size
    else:
        device = model.device
        if not (pt or jit):
            batch_size = 1  # export.py models default to batch-size 1
            LOGGER.info(f'Forcing --batch-size 1 square inference (1,3,{imgsz},{imgsz}) for non-PyTorch models')
    

    在这段代码中,根据模型是否为TensorRT引擎来确定批处理的大小:

    • 如果模型是TensorRT引擎,则使用模型中定义的批处理大小。
    • 如果模型不是TensorRT引擎,需要确认当前设备并设置批处理大小为1,尤其在导出模型时(如使用export.py),确保其符合要求并输出相关的日志信息。

该代码片段的主要功能是加载一个多后端的YOLO检测模型并进行必要的设置,包括提取模型的属性、验证输入图像的尺寸、配置半精度运算和确定批处理的大小。这一过程中确保了所有设置能够适应不同的推断环境,比如使用不同的模型后端(如PyTorch、TensorRT等),从而为后续的推断过程奠定了基础。这部分代码是模型推断流程中的一个重要步骤,影响到性能和计算的正确性。

3.4. 数据加载器配置

 # Data
        data = check_dataset(data)  # check

    # 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 dataset
    nc = 1 if single_cls else int(data['nc'])  # number of classes
    iouv = torch.linspace(0.5, 0.95, 10, device=device)  # iou vector for mAP@0.5:0.95
    niou = iouv.numel()

    # Dataloader
    if not training:
        if pt 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 --weights and --data that are trained together.'
        model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz))  # warmup
        pad, rect = (0.0, False) if task == 'speed' else (0.5, pt)  # square inference for benchmarks
        task = task if task in ('train', 'val', 'test') else 'val'  # path to train/val/test images
        dataloader = create_dataloader(data[task],
                                       imgsz,
                                       batch_size,
                                       stride,
                                       single_cls,
                                       pad=pad,
                                       rect=rect,
                                       workers=workers,
                                       prefix=colorstr(f'{task}: '))[0]
  • 不在训练模式时,检查权重与数据集类的匹配性,并创建数据加载器。
  • 利用 create_dataloader 函数生成数据加载器以便于批量处理。

这段代码是YOLOv5模型验证(validation)流程中的一部分,主要负责数据验证和模型配置。下面是对代码的逐步分解和详细解释:

  1. 数据检查

    data = check_dataset(data)  # check
    
    • 功能:调用check_dataset函数,以确认输入的数据集是否有效。这个函数通常会检查数据集的路径、格式以及图像和标签是否匹配,确保后续处理时数据的整洁性和一致性。
  2. 模型配置

    model.eval()  # 切换到评估模式
    cuda = device.type != 'cpu'  # 检查设备类型,如果是GPU则为True
    
    • 功能
      • model.eval() 将模型设置为评估模式,此模式下不进行梯度计算,这样能够减少内存占用和加速计算。
      • cuda 是一个布尔值,指示当前的设备是否为CUDA(即GPU),以便后续操作中决定是否使用GPU加速。
  3. 检查COCO数据集

    is_coco = isinstance(data.get('val'), str) and data['val'].endswith(f'coco{os.sep}val2017.txt')  # COCO数据集
    
    • 功能:确定当前使用的数据集是否为COCO格式。如果验证集的路径以coco/.../val2017.txt结尾,则is_coco为真。这通常影响后续评估的类别映射和指标计算。
  4. 类的数量判断

    nc = 1 if single_cls else int(data['nc'])  # number of classes
    
    • 功能:确定模型的类别数量。如果数据集被标识为单类,则类别数量为1;否则,从数据集中读取类别数。在物体检测中,类别数量对于计算各种指标至关重要。
  5. 计算IoU阈值

    iouv = torch.linspace(0.5, 0.95, 10, device=device)  # iou vector for mAP@0.5:0.95
    niou = iouv.numel()
    
    • 功能:生成一个从0.5到0.95的线性间隔的IoU(Intersection over Union)阈值张量。这用于计算mAP(mean Average Precision)指标,niou表示该张量的元素数量。
  6. 数据加载器配置

    if not training:
    
    • 功能:判断是否处于训练状态,只有在验证或测试状态下才执行数据加载和验证。
  7. 权重与数据集一致性检查

    if pt 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} classes). Pass correct combination of --weights and --data that are trained together.'
    
    • 功能:确保加载的模型权重与指定的数据集具有相同的类数。如果不一致,则抛出异常。这是为了避免错误的评估结果。
  8. 模型热身

    model.warmup(imgsz=(1 if pt else batch_size, 3, imgsz, imgsz))  # warmup
    
    • 功能:对模型进行“热身”,通过一次前向传播来调整模型的计算和内存配置,提升运行效率。
  9. 推理模式选择

    pad, rect = (0.0, False) if task == 'speed' else (0.5, pt)  # square inference for benchmarks
    task = task if task in ('train', 'val', 'test') else 'val'  # path to train/val/test images
    
    • 功能
      • 根据任务类型选择填充方式。例如,如果是速度测试,则不进行填充(pad为0),这将影响图像推理的方式(是使用方形推理还是矩形推理)。
      • 确保任务字符串是有效的训练、验证或测试类型,默认设为验证。
  10. 数据加载器创建

    dataloader = create_dataloader(data[task],
                                   imgsz,
                                   batch_size,
                                   stride,
                                   single_cls,
                                   pad=pad,
                                   rect=rect,
                                   workers=workers,
                                   prefix=colorstr(f'{task}: '))[0]
    
    • 功能:调用create_dataloader函数,加载指定任务的数据集,设置图像大小、批量大小、步幅、单类标识、填充方式等参数,最终返回一个迭代器用于数据遍历。

这段代码的主要功能是检查和准备YOLOv5模型的验证过程。它确认数据集的有效性和格式,设置模型为评估模式,确定类别的数量以及IoU计算参数,并创建一个数据加载器以便后续的模型推理和验证。这部分代码确保在进行物体检测评估时,所有必要的准备工作都已完成,从而能准确高效地运行后续的评估流程。

3.5. 推理与指标计算

3.5.1 初始化验证过程中的各类统计指标和状态信息

seen = 0
    confusion_matrix = ConfusionMatrix(nc=nc)
    names = model.names if hasattr(model, 'names') else model.module.names  # get class names
    if isinstance(names, (list, tuple)):  # old format
        names = dict(enumerate(names))
    class_map = coco80_to_coco91_class() if is_coco else list(range(1000))
    s = ('%22s' + '%11s' * 6) % ('Class', 'Images', 'Instances', 'P', 'R', 'mAP50', 'mAP50-95')
    tp, fp, p, r, f1, mp, mr, map50, ap50, map = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
    dt = Profile(), Profile(), Profile()  # profiling times
    loss = torch.zeros(3, device=device)
    jdict, stats, ap, ap_class = [], [], [], []
    callbacks.run('on_val_start')

这段代码的逐步分解和详细解释如下:

  1. seen = 0

    • 初始化一个计数器seen为0,用于记录已经处理过的图像数量。
  2. confusion_matrix = ConfusionMatrix(nc=nc)

    • 创建一个ConfusionMatrix对象,用于保存和计算混淆矩阵。参数nc表示类别数量,配置了用于评估模型的混淆矩阵。
  3. names = model.names if hasattr(model, 'names') else model.module.names

    • 获取模型的类别名称。首先检查模型是否有属性names,如果有,直接使用它;如果没有,则使用模块的names属性。这种方式用于兼容旧版本的模型。
  4. if isinstance(names, (list, tuple)):

    • 检查names是否是列表或元组,这是一种旧格式。如果是,则使用dict(enumerate(names))将其转换为字典,字典的键是类别的索引,值是类别的名字。
  5. class_map = coco80_to_coco91_class() if is_coco else list(range(1000))

    • 根据是否使用COCO数据集,生成类别映射。如果是COCO数据集,调用coco80_to_coco91_class()函数转换类别。否则,生成一个范围从0到999的列表作为类别ID。
  6. s = ('%22s' + '%11s' * 6) % ('Class', 'Images', 'Instances', 'P', 'R', 'mAP50', 'mAP50-95')

    • 创建一个格式化字符串s,用于打印评估结果的表头。表头包含类名、图像数量、实例数量、精确率(P)、召回率(R)、mAP@50、mAP@50-95等指标。
  7. tp, fp, p, r, f1, mp, mr, map50, ap50, map = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0

    • 初始化各种评估指标。tp(真正例)、fp(假正例)、p(精确率)、r(召回率)、f1(F1分数)、mp(平均精确率)、mr(平均召回率)、map50(mAP@0.5)、ap50(mAP@0.5:0.95)、map(mAP)都被初始化为0.0。
  8. dt = Profile(), Profile(), Profile()

    • 创建三个Profile对象dt用于性能分析。这些对象将用于记录和分析处理时间。
  9. loss = torch.zeros(3, device=device)

    • 为损失初始化一个张量loss,大小为3,并将其放在指定的设备上(CPU或GPU)。
  10. jdict, stats, ap, ap_class = [], [], [], []

    • 初始化四个空列表,其中jdict用于保存JSON格式的结果,stats用于保存统计信息,ap用于保存每个类别的平均精确率,ap_class用于保存类别类的列表。
  11. callbacks.run('on_val_start')

    • 调用回调函数,表明验证过程开始,这在整个流程中通常用于执行一些定制化操作。

这段代码主要功能是初始化验证过程中的各类统计指标和状态信息。它准备了混淆矩阵、模型的类别名称、类别映射以及评估指标等,为后续的评估过程打下了基础。这里的设置将帮助在验证阶段有效地跟踪模型的性能,同时为模型的验证输出做准备。

3.5.2 数据处理

  pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT)  # progress bar
    for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
        callbacks.run('on_val_batch_start')
        with dt[0]:
            if cuda:
                im = im.to(device, non_blocking=True)
                targets = targets.to(device)
            im = im.half() if half else im.float()  # uint8 to fp16/32
            im /= 255  # 0 - 255 to 0.0 - 1.0
            nb, _, height, width = im.shape  # batch size, channels, height, width

这段代码的功能是为数据验证过程设置进度条,并处理验证数据集中的每一个批次数据。下面对代码进行逐步分解并详细解释:

  1. 创建进度条:

    pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT)  # progress bar
    
    • tqdm 是一个用于创建进度条的Python库,上面的代码创建了一个进度条 pbar,它会监视 dataloader 中的数据加载进度。
    • desc=s 为进度条添加描述信息。
    • bar_format=TQDM_BAR_FORMAT 指定了进度条的格式。
  2. 遍历数据集:

    for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
    
    • 使用 enumerate 函数遍历 pbar,对于每个批次返回批次索引 batch_i 及其内容 (im, targets, paths, shapes)
    • im 是输入图像,targets 是目标标签,paths 是图像路径,shapes 是图像的原始尺寸。
  3. 回调函数:

    callbacks.run('on_val_batch_start')
    
    • 在处理每个批次开始时执行回调函数 on_val_batch_start。这可能用于记录或监控:例如,开始批量验证时的操作。
  4. 设备和数据类型转换:

    with dt[0]:
        if cuda:
            im = im.to(device, non_blocking=True)
            targets = targets.to(device)
    
    • 在 dt[0] 的上下文下记录处理时间。
    • 如果使用CUDA(GPU),则将图像 im 和目标 targets 移动到指定的 device(一般为GPU)。non_blocking=True 使得数据的转移不阻塞CPU和GPU的计算。
  5. 数据类型转换:

    im = im.half() if half else im.float()  # uint8 to fp16/32
    
    • 根据 half 变量,决定是否将图像数据转换为半精度浮点(fp16)或单精度浮点(fp32)。这样可以减少内存使用和加速计算。
  6. 归一化图像数据:

    im /= 255  # 0 - 255 to 0.0 - 1.0
    
    • 将图像 im 的像素值从 [0, 255] 的范围归一化到 [0.0, 1.0] 的范围,以提高模型的训练和推理性能。
  7. 获取图像的形状信息:

    nb, _, height, width = im.shape  # batch size, channels, height, width
    
    • 获取当前批次中图像的详细维度信息。nb 是批量大小,_ 是通道数(通常为3,如果是RGB图像),height 和 width 是图像的高度和宽度。

这段代码的主要功能是通过创建一个进度条来跟踪验证数据集的处理情况,并对每个批次的图像进行必要的数据预处理,确保数据能够顺利输入到YOLOv5模型进行验证。这包括数据类型的转换、将数据移动到合适的设备(GPU或CPU),以及对图像进行归一化处理以提高计算效率。整体上,这段代码有助于管理和优化模型验证过程中图像数据的处理。

 3.5.3 前向推理

 # Inference
        with dt[1]:
            preds, train_out = model(im) if compute_loss else (model(im, augment=augment), None)

 这段代码是深度学习模型推理过程中的一部分,主要负责对输入图像进行推理,生成预测结果。我们逐步分析这段代码:

  1. 上下文管理器 with dt[1]:

    • 这一行使用了上下文管理器,dt[1] 是一个 Profile 对象,用于性能分析。在执行这一块代码时,Profile 会监控这段代码的运行时间,从而帮助开发者优化性能。
  2. 推理过程

    • preds, train_out = model(im) if compute_loss else (model(im, augment=augment), None):这一行使用了条件表达式(即三元运算符)。推理的具体过程如下:
      • model(im): 如果 compute_loss 为 True,即需要计算损失,那么直接调用模型 model 对输入 im 进行推理,并返回预测结果 preds 和模型的输出 train_out(这通常用于训练过程中的损失计算)。
      • model(im, augment=augment): 如果 compute_loss 为 False,那么调用模型进行推理时,同时传入 augment 参数。这个参数用于指定是否在推理时应用数据增强技术,以提高模型在测试阶段的鲁棒性。此时返回的结果只有 preds,而 train_out 为 None

这段代码的主要功能是进行模型的推理操作。它在推理过程中能够根据是否需要计算损失,选择不同的推理方式,并同时监控这段代码的执行性能。最终,模型会对输入的图像 im 生成预测结果 preds,这些结果可以用于后续的处理和评价。

 3.5.4 计算损失函数

        # Loss
        if compute_loss:
            loss += compute_loss(train_out, targets)[1]  # box, obj, cls

代码段的功能是计算模型在当前批次的损失值(loss),主要用于训练过程中的性能评估。以下是逐步分解和详细解释:

  1. if compute_loss:
    这一行检查变量 compute_loss 的值。compute_loss 是一个布尔值,通常由外部函数决定是否在当前训练阶段计算损失。如果其值为 True,则程序将计算损失;如果为 False,则跳过损失计算。

  2. loss += compute_loss(train_out, targets)[1]:

    • compute_loss(train_out, targets): 调用计算损失的函数 compute_loss,这个函数接收两个参数:
      • train_out: 这是模型的输出,通常包括预测的边界框、目标物体的置信度和类别概率等信息。
      • targets: 这是实际的目标标签,包含真实的边界框和类别信息。它通常在训练时提供给模型,用于计算损失。
    • [1]compute_loss 函数返回一个含有多个损失分量的元组(例如:边界框损失、物体置信度损失、类别损失)。这一行中的 [1] 表示我们只获取这些损失分量中的第二个,即对应于“物体置信度”的损失。这是因为在训练过程中,人们通常会关注不同损失的贡献,而这段代码显然是将“物体置信度”的损失累加到总损失中。
    • loss +=:这表示将当前批次计算得到的物体置信度损失累加到 loss 变量上,以形成当前训练迭代的总损失值。

这段代码的主要功能是在训练迭代期间计算并累计模型在当前批次的物体置信度损失。通过检查 compute_loss 变量,该代码决定是否需要进行损失计算;如果需要,则调用 compute_loss 函数,并从返回结果中提取物体置信度损失,最终更新总损失值。这样做有助于在训练过程中动态衡量模型的性能,并为后续的参数更新提供依据。

 3.5.5 nms过滤

# NMS
        targets[:, 2:] *= torch.tensor((width, height, width, height), device=device)  # to pixels
        lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else []  # for autolabelling
        with dt[2]:
            preds = non_max_suppression(preds,
                                        conf_thres,
                                        iou_thres,
                                        labels=lb,
                                        multi_label=True,
                                        agnostic=single_cls,
                                        max_det=max_det)

这段代码的主要功能是对目标检测模型的输出进行非极大值抑制(NMS),以过滤出最终的检测结果。以下是对每一行代码的逐步分解和详细解释:

  1. 目标框坐标转换为像素:

    targets[:, 2:] *= torch.tensor((width, height, width, height), device=device)  # to pixels
    

    这行代码的目的是将targets变量中的目标框坐标(通常是归一化的坐标)转换为像素坐标。

    • targets是一个包含所有目标框信息的张量,其中第二列及之后的列代表目标框的坐标(通常是[中心x, 中心y, 宽度, 高度])。
    • torch.tensor((width, height, width, height), device=device)创建了一个张量,其值为图像的宽度和高度,这样可以将目标框的坐标乘以图像的尺寸以获得实际的像素坐标。
  2. 构造标签列表:

    lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_hybrid else []  # for autolabelling
    

    这行代码用于生成每个图像的标签列表,用于后续的NMS过程。

    • nb是当前批次的图像数量,对于每个图像i,使用targets中与当前图像相对应的目标框信息(目标框的类别和坐标)构成一个列表。
    • 如果save_hybrid为真,则生成包括标签的列表lb;如果为假,则lb为空列表。
  3. 调用非极大值抑制 (NMS):

    with dt[2]:
        preds = non_max_suppression(preds,
                                    conf_thres,
                                    iou_thres,
                                    labels=lb,
                                    multi_label=True,
                                    agnostic=single_cls,
                                    max_det=max_det)
    

    这一段代码在上下文管理器dt[2]中执行,通常用于性能评估或计时。

    • non_max_suppression是一个函数,用于执行NMS操作,减少重叠的检测框,保留最有可能的目标框。
    • preds是网络返回的原始检测结果,包含框坐标、置信度和类别信息。
    • conf_thres是置信度阈值,低于此值的预测结果将被丢弃。
    • iou_thres是交并比(IoU)阈值,用于决定两个框之间是否重叠。
    • labels=lb提供了当前图像的标签(如果需要),用于支持autolabelling。
    • multi_label=True表示支持多标签检测。
    • agnostic=single_cls表示是否在NMS过程中忽略类别信息,适用于单类问题。
    • max_det=max_det限制每张图像返回的最大检测框数量。

这段代码的主要功能是将目标检测模型的输出后处理为最终的检测结果,具体而言,通过将目标框的坐标转换为像素格式,然后利用非极大值抑制(NMS)算法,过滤掉重叠过多、不可靠的检测结果。通过对目标检测的输出进行这种处理,能够提高检测的精度和可靠性,确保最终返回的结果集中每个检测框都是高质量的。

 3.5.6 评估目标检测模型的预测结果

 # Metrics
        for si, pred in enumerate(preds):
            labels = targets[targets[:, 0] == si, 1:]
            nl, npr = labels.shape[0], pred.shape[0]  # number of labels, predictions
            path, shape = Path(paths[si]), shapes[si][0]
            correct = torch.zeros(npr, niou, dtype=torch.bool, device=device)  # init
            seen += 1

            if npr == 0:
                if nl:
                    stats.append((correct, *torch.zeros((2, 0), device=device), labels[:, 0]))
                    if plots:
                        confusion_matrix.process_batch(detections=None, labels=labels[:, 0])
                continue

            # Predictions
            if single_cls:
                pred[:, 5] = 0
            predn = pred.clone()
            scale_boxes(im[si].shape[1:], predn[:, :4], shape, shapes[si][1])  # native-space pred

            # Evaluate
            if nl:
                tbox = xywh2xyxy(labels[:, 1:5])  # target boxes
                scale_boxes(im[si].shape[1:], tbox, shape, shapes[si][1])  # native-space labels
                labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # native-space labels
                correct = process_batch(predn, labelsn, iouv)
                if plots:
                    confusion_matrix.process_batch(predn, labelsn)
            stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0]))  # (correct, conf, pcls, tcls)

            # Save/log
            if save_txt:
                save_one_txt(predn, save_conf, shape, file=save_dir / 'labels' / f'{path.stem}.txt')
            if save_json:
                save_one_json(predn, jdict, path, class_map)  # append to COCO-JSON dictionary
            callbacks.run('on_val_image_end', pred, predn, path, names, im[si])

        # Plot images
        if plots and batch_i < 3:
            plot_images(im, targets, paths, save_dir / f'val_batch{batch_i}_labels.jpg', names)  # labels
            plot_images(im, output_to_target(preds), paths, save_dir / f'val_batch{batch_i}_pred.jpg', names)  # pred

        callbacks.run('on_val_batch_end', batch_i, im, targets, paths, shapes, preds)

这段代码主要用于评估目标检测模型的预测结果,并计算相关的评估指标。以下是对该代码的逐步分解和解释:

  1. 循环评估每个预测

    for si, pred in enumerate(preds):
    

    在每次迭代中,pred 是模型针对当前图像的预测结果,si 是当前图像的索引。

  2. 提取标签和初始化

    labels = targets[targets[:, 0] == si, 1:]
    nl, npr = labels.shape[0], pred.shape[0]  # number of labels, predictions
    path, shape = Path(paths[si]), shapes[si][0]
    correct = torch.zeros(npr, niou, dtype=torch.bool, device=device)  # init
    seen += 1
    
    • 从目标(targets)中提取当前图像的标签。
    • nl 表示标签的数量,npr 表示预测的数量。
    • correct 初始化一个布尔张量,用于存储每个预测是否正确。
  3. 处理没有预测结果的情况

    if npr == 0:
        if nl:
            stats.append((correct, *torch.zeros((2, 0), device=device), labels[:, 0]))
            if plots:
                confusion_matrix.process_batch(detections=None, labels=labels[:, 0])
        continue
    
    • 如果没有预测结果 (npr == 0),但存在标签,则记录此信息到 stats 以便后续的统计分析。
  4. 处理单类情况

    if single_cls:
        pred[:, 5] = 0
    
    • 如果只处理单类目标检测,将每个预测的类别索引设为0。
  5. 缩放预测结果

    predn = pred.clone()
    scale_boxes(im[si].shape[1:], predn[:, :4], shape, shapes[si][1])  # native-space pred
    
    • 克隆预测结果并将其转换为原始图像的坐标空间。
  6. 计算评价指标

    if nl:
        tbox = xywh2xyxy(labels[:, 1:5])  # target boxes
        scale_boxes(im[si].shape[1:], tbox, shape, shapes[si][1])  # native-space labels
        labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # native-space labels
        correct = process_batch(predn, labelsn, iouv)
        if plots:
            confusion_matrix.process_batch(predn, labelsn)
    

    如果有标签,首先将其转换为合适的坐标格式并缩放,然后通过调用 process_batch 方法计算正确预测的情况。

  7. 记录结果

    stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0]))  # (correct, conf, pcls, tcls)
    

    将当前图像的正确预测、置信度、类别和真实标签记录到统计信息中。

  8. 保存结果

    if save_txt:
        save_one_txt(predn, save_conf, shape, file=save_dir / 'labels' / f'{path.stem}.txt')
    if save_json:
        save_one_json(predn, jdict, path, class_map)  # append to COCO-JSON dictionary
    callbacks.run('on_val_image_end', pred, predn, path, names, im[si])
    

    如果设置了保存选项,则将预测结果保存为文本或 JSON 格式并调用回调函数。

  9. 可视化

    if plots and batch_i < 3:
        plot_images(im, targets, paths, save_dir / f'val_batch{batch_i}_labels.jpg', names)  # labels
        plot_images(im, output_to_target(preds), paths, save_dir / f'val_batch{batch_i}_pred.jpg', names)  # pred
    callbacks.run('on_val_batch_end', batch_i, im, targets, paths, shapes, preds)
    

    如果启用了可视化并且当前批次小于3,则绘制标签和预测的图像。

这段代码的主要功能是评估目标检测模型的性能。在模型输出的预测与真实标签对比的过程中,它:

  • 计算并存储每个预测的准确性。
  • 进行必要的坐标缩放和格式转换,以适应原始图像的空间。
  • 对于没有预测的情况进行特殊处理。
  • 将结果保存到指定格式(文本或 JSON)中,并可视化部分数据以便验证结果。

整体而言,这段代码是目标检测任务中非常关键的一部分,负责生成评估信息和结果,以便后续的分析和优化。

3.6. 计算最终指标

    # Compute metrics
    stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)]  # to numpy
    if len(stats) and stats[0].any():
        tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
        ap50, ap = ap[:, 0], ap.mean(1)  # AP@0.5, AP@0.5:0.95
        mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
    nt = np.bincount(stats[3].astype(int), minlength=nc)  # number of targets per class
  • 计算并输出各类预测的平均精度(mAP),召回率,精确度等指标。
  • 显示每个类的检测结果,提供详细的分析信息。

这段代码片段的主要目的是计算目标检测模型的性能指标。我们逐步分解并详细解释每一行代码的功能:

  1. 统计数据组合和转换

    stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)]  # to numpy
    
    • zip(*stats):该操作将stats中的每个元素解包并按列组合,使得每列数据都聚集在一起。
    • torch.cat(x, 0):对每列的张量进行拼接,结果是一个按行组合的张量。
    • .cpu().numpy():将拼接后的张量从GPU移动到CPU,并转换为NumPy数组,以便后续处理。
  2. 检查统计数据是否有效

    if len(stats) and stats[0].any():
    
    • 这行代码检查stats的长度是否大于0,并且第一个元素(通常表示准确预测的数量)是否存在有效值(即非零)。如果都满足条件,则继续处理。
  3. 计算每类的指标

    tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
    
    • 通过调用ap_per_class函数计算每个类别的指标,包括:
      • tp(真正例数量)
      • fp(假正例数量)
      • p(精确度)
      • r(召回率)
      • f1(F1分数)
      • ap(平均精确度)
      • ap_class(跟踪每个类的平均精确度)
  4. 计算AP@0.5和AP@0.5:0.95

    ap50, ap = ap[:, 0], ap.mean(1)  # AP@0.5, AP@0.5:0.95
    
    • ap[:, 0]:提取平均精确度中AP@0.5的值。
    • ap.mean(1):计算AP@0.5:0.95,即在多个IoU阈值下的平均值。
  5. 计算各类的平均值

    mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
    
    • 计算所有类别的精确度、召回率、AP@0.5和总体平均精确度(map)的平均值。
  6. 统计每个类别的目标数量

    nt = np.bincount(stats[3].astype(int), minlength=nc)  # number of targets per class
    
    • stats[3]:通常表示实际标注的类别标签。
    • np.bincount:统计每个类别的目标数量,minlength=nc确保输出数组至少有nc个元素(对应于所有类别),如果某个类别没有实际目标,则返回0。

这段代码的主要功能是计算检测模型在验证阶段的性能指标。它组织了模型在预测中生成的统计数据,并利用这些数据计算出真正例、假正例、精确度、召回率、F1分数和平均精确度(AP)。最终,这些指标帮助评估模型的性能,并进行后续分析(例如,按类别的表现)。通过分析这些指标,开发者能够了解模型的优缺点,以及在特定类别上的表现如何,为进一步的模型优化提供有用的参考。

 3.7 打印结果

 # Print results
    pf = '%22s' + '%11i' * 2 + '%11.3g' * 4  # print format
    LOGGER.info(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
    if nt.sum() == 0:
        LOGGER.warning(f'WARNING ⚠️ no labels found in {task} set, can not compute metrics without labels')

    # Print results per class
    if (verbose or (nc < 50 and not training)) and nc > 1 and len(stats):
        for i, c in enumerate(ap_class):
            LOGGER.info(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))

    # Print speeds
    t = tuple(x.t / seen * 1E3 for x in dt)  # speeds per image
    if not training:
        shape = (batch_size, 3, imgsz, imgsz)
        LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {shape}' % t)

这段代码的目的是打印出模型验证的结果以及相关的性能指标。以下是逐步分解并详细解释的过程:

  1. 打印结果格式定义

    pf = '%22s' + '%11i' * 2 + '%11.3g' * 4  # print format
    

    这里定义了一个打印格式字符串 pf。这个格式包含:

    • %22s: 一个字符串,占22个字符的宽度。
    • %11i * 2: 两个整数,占11个字符的宽度。
    • %11.3g * 4: 四个浮点数,显示为科学计数法或普通数字,保留3位有效数字。
  2. 记录总体结果

    LOGGER.info(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
    

    通过 LOGGER.info 将格式化后的结果信息打印出来:

    • 'all': 字符串显示为类别名称。
    • seen: 已处理的图像数量。
    • nt.sum(): 各类别目标的总数量。
    • mp: 平均精度 (Mean Precision)。
    • mr: 平均召回率 (Mean Recall)。
    • map50: 在IoU阈值为0.5时的平均精度 (Mean Average Precision at IoU=0.5)。
    • map: 在所有IoU阈值下的平均精度。
  3. 检查标签

    if nt.sum() == 0:
        LOGGER.warning(f'WARNING ⚠️ no labels found in {task} set, can not compute metrics without labels')
    

    如果没有任何标签 (nt.sum() == 0) 被找到,将发出警告。这个条件检查确保在计算指标时有可用的标签。

  4. 按类别打印结果

    if (verbose or (nc < 50 and not training)) and nc > 1 and len(stats):
        for i, c in enumerate(ap_class):
            LOGGER.info(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))
    

    如果 verbose 为真,或者类别数量 (nc) 小于50并且不是在训练中,并且有统计数据 (len(stats)),则打印每个类别的详细结果:

    • names[c]: 类别名称。
    • seen: 该类别的已处理图像数量。
    • nt[c]: 该类别的目标数量。
    • p[i]: 该类别的精度。
    • r[i]: 该类别的召回率。
    • ap50[i]: 该类别在IoU=0.5时的平均精度。
    • ap[i]: 该类别在所有IoU阈值下的平均精度。
  5. 打印推理速度

    t = tuple(x.t / seen * 1E3 for x in dt)  # speeds per image
    if not training:
        shape = (batch_size, 3, imgsz, imgsz)
        LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {shape}' % t)
    

    首先计算每个图像处理的速度,将时间转换为毫秒(1E3 是乘以1000),然后如果当前不是训练阶段,将打印处理速度信息:

    • shape = (batch_size, 3, imgsz, imgsz): 表示输入图像的形状,batch_size 是每批数量,3 是通道数(RGB),imgsz 是图像的高度和宽度。

这段代码的主要功能是:

  • 打印模型的整体性能指标,包括处理的图像数量、目标数量、平均精度和召回率。
  • 在未找到标签时发出警告。
  • 按类别详细列出精度和召回率等指标。
  • 计算并打印图像处理的速度,以便了解模型的推理性能。这些信息对于了解模型在验证集上的表现以及潜在的性能瓶颈都非常关键。

 3.8 可视化结果

# Plots
    if plots:
        confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
        callbacks.run('on_val_end', nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix)

这段代码的作用是在模型验证后生成一些可视化结果,并运行相关的回调函数。代码分解如下:

  1. if plots:
    这一行检查变量 plots 的值。如果 plots 为真(True),则执行后面的内容。plots 通常用于控制是否生成和保存可视化图表。

  2. confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
    这一行调用了 confusion_matrix 对象的 plot 方法,目的在于生成并保存混淆矩阵的可视化。

    • save_dir=save_dir:定义保存图像的目录,save_dir 变量指定了文件的保存路径。
    • names=list(names.values()):传递类名列表,names 是一个字典,names.values() 获取所有的类名。

    混淆矩阵 是评估分类模型性能的标准工具,显示模型在不同类别上的预测情况。它可以帮助识别哪些类别容易被混淆,从而为改进模型提供方向。

  3. callbacks.run('on_val_end', nt, tp, fp, p, r, f1, ap, ap50, ap_class, confusion_matrix)
    这一行调用了一个回调函数 callbacks.run(),传递了一些评估指标和混淆矩阵。

    • 'on_val_end':这是回调的名称,表示验证过程结束时触发该回调。
    • nt:表示每个类别目标的数量(number of targets)。
    • tpfpprf1apap50ap_class:这些都是来自模型评估的一些指标,如真正例数量(True Positives)、假正例数量(False Positives)、准确率(Precision)、召回率(Recall)、F1分数、平均精度(Average Precision)等。
    • confusion_matrix:混淆矩阵对象,展示模型在分类任务中的预测表现。

这段代码的主要功能是在模型验证结束后,生成可视化的混淆矩阵并运行结束时的回调处理。通过可视化混淆矩阵,可以更直观地了解模型在不同类别上的分类表现,而回调函数则用于处理验证结束后的各种操作,例如记录结果、更新状态等。这些步骤是模型评估过程的重要组成部分,有助于分析和改进模型的性能。

 3.9 保存JSON格式

# Save JSON
    if save_json and len(jdict):
        w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else ''  # weights
        anno_json = str(Path(data.get('path', '../coco')) / 'annotations/instances_val2017.json')  # annotations json
        pred_json = str(save_dir / f"{w}_predictions.json")  # predictions json
        LOGGER.info(f'\nEvaluating pycocotools mAP... saving {pred_json}...')
        with open(pred_json, 'w') as f:
            json.dump(jdict, f)

        try:  # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
            check_requirements('pycocotools')
            from pycocotools.coco import COCO
            from pycocotools.cocoeval import COCOeval

            anno = COCO(anno_json)  # init annotations api
            pred = anno.loadRes(pred_json)  # init predictions api
            eval = COCOeval(anno, pred, 'bbox')
            if is_coco:
                eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.im_files]  # image IDs to evaluate
            eval.evaluate()
            eval.accumulate()
            eval.summarize()
            map, map50 = eval.stats[:2]  # update results (mAP@0.5:0.95, mAP@0.5)
        except Exception as e:
            LOGGER.info(f'pycocotools unable to run: {e}')

代码主要负责保存模型检测结果为JSON格式,并使用pycocotools对结果进行评估,计算平均精度(mAP)。

  1. 条件判断

    if save_json and len(jdict):
    

    这里检查是否需要保存结果为JSON格式(save_jsonTrue),并且jdict(用于存储预测结果的字典)不为空。

  2. 准备权重与路径

    w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else ''
    
    • weights表示使用的模型权重路径。根据权重是否为列表,提取第一个权重的文件名(不带扩展名)作为w
    • 如果weights为空,则w将为空字符串。
  3. 设置注解与预测JSON路径

    anno_json = str(Path(data.get('path', '../coco')) / 'annotations/instances_val2017.json')
    pred_json = str(save_dir / f"{w}_predictions.json")
    
    • anno_json为注解文件的路径,这里默认指向COCO格式的注解文件。
    • pred_json为保存预测结果的文件路径,文件名包含模型权重的名称。
  4. 日志记录

    LOGGER.info(f'\nEvaluating pycocotools mAP... saving {pred_json}...')
    

    在日志中记录将要评估和保存的预测结果路径。

  5. 保存JSON文件

    with open(pred_json, 'w') as f:
        json.dump(jdict, f)
    
    • 使用json.dumpjdict内容写入到指定的JSON文件pred_json中。
  6. 使用pycocotools进行评估

    try:
        check_requirements('pycocotools')
        from pycocotools.coco import COCO
        from pycocotools.cocoeval import COCOeval
    
    • try块中,首先检查是否安装了pycocotools库。
    • 导入COCOCOCOeval类,以便进行评估。
  7. 初始化注解与预测

    anno = COCO(anno_json)  # init annotations api
    pred = anno.loadRes(pred_json)  # init predictions api
    
    • 初始化COCO对象,用于处理注解数据。
    • 加载预测结果,将其转换为可以评估的格式。
  8. 评估设置

    eval = COCOeval(anno, pred, 'bbox')
    if is_coco:
        eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.im_files]  # image IDs to evaluate
    
    • 创建COCOeval对象,指定评估的类型为边界框('bbox')。
    • 如果数据集为COCO格式,设置将要评估的图片ID。
  9. 评估过程

    eval.evaluate()
    eval.accumulate()
    eval.summarize()
    map, map50 = eval.stats[:2]
    
    • 调用评估方法,计算结果并累加。
    • 最后,调用summary以输出评估结果,并提取mAP和mAP@0.5的值。
  10. 异常处理

except Exception as e:
    LOGGER.info(f'pycocotools unable to run: {e}')
  • 如果在评估过程中发生异常,将错误信息记录到日志中。

本段代码实现了将模型的预测结果保存为JSON文件,并利用pycocotools库对预测结果进行评估,计算mAP(平均精度)等指标。这对于模型性能评估和结果记录非常重要,尤其是在目标检测任务中,能够有效地验证模型的检测效果。

3.10. 返回结果

 model.float()  # for training
    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
    for i, c in enumerate(ap_class):
        maps[c] = ap[i]
    return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t
  • 返回 mAP、速度以及其他指标,供后续处理或评估使用。

这段代码的目的是在评估阶段处理模型的结果、记录输出的日志,并最终返回评估的指标和计算时间。我们逐步分解和解释这段代码:

  1. model.float():

    • 这行代码的作用是将模型设置为浮点数精度(float)。在训练阶段,一般会使用浮点数来提高计算的精度。这行代码主要是为了确保在后续的处理程序中,模型使用的浮点数精度是正确的。
  2. if not training::

    • 这个条件判断用于检查当前是否处于训练阶段。如果不是训练阶段,接下来的代码块将被执行。这通常是用来进行模型评估或者推理。
  3. s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '':

    • 在这一行,程序首先通过save_dir.glob('labels/*.txt')查找在保存目录下的所有标签文件(以.txt结尾)。然后计算找到的标签文件的数量。如果设置了save_txt,则会生成一条字符串s,内容是保存的标签数目及其保存位置;如果没有保存标签,则s为空字符串。
  4. LOGGER.info(...):

    • 这里使用LOGGER.info来记录信息,显示结果被保存到哪个目录,并且如果有标签文件,则会显示相应的数量。这条信息将在控制台打印,以便用户了解模型评估的结果。
  5. maps = np.zeros(nc) + map:

    • 这一行创建一个新的 NumPy 数组maps,其大小为类别数量nc,并将初始值设为零。然后将先前计算的map(均值平均精度)加到这个数组中。这是用来保存每个类别的AP值。
  6. for i, c in enumerate(ap_class)::

    • 这个循环遍历每个类别的数组ap_classi是索引,c是类别编号。
  7. maps[c] = ap[i]:

    • 在循环中,将每个类别的AP值存储到maps数组中,通过查找类别c并将相应的AP值赋给它。
  8. return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t:

    • 最后,函数返回若干结果:
      • mp: 平均精度(mean precision)
      • mr: 平均召回率(mean recall)
      • map50: 在IoU为0.5时的均值平均精度
      • map: 整体的均值平均精度
      • *(loss.cpu() / len(dataloader)).tolist(): 将损失(loss)转换为CPU上的numpy数组,并返回其平均值。
      • maps: 各类别的AP值数组
      • t: 函数执行的时间统计(通常是各个阶段的执行时间)

这段代码的主要功能是处理模型评估的后续工作,包括记录模型评估结果、保存标签文件的数量、汇总每个类别的平均精度(AP)并返回多项评估指标。这在机器学习工作流程中是非常关键的,尤其是在目标检测等任务中。通过记录这些信息,用户能够清楚地了解模型性能,并据此进行进一步的分析和优化。

3.11 总结

该函数 run 的主要功能是执行模型的推理,评估和结果记录。它支持来自 YOLOv5 系列模型的各种输入配置,并根据不同的任务(如验证、测试等)灵活处理数据。此函数不仅计算模型在给定数据集上的性能指标(如 mAP、召回率等),还能根据用户的需求保存结果,并支持多种推理模式(例如通过不同数据增强和精度设置)。

四、使用说明

# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
"""
Validate a trained YOLOv5 detection model on a detection dataset

Usage:
    $ python val.py --weights yolov5s.pt --data coco128.yaml --img 640

Usage - formats:
    $ python val.py --weights yolov5s.pt                 # PyTorch
                              yolov5s.torchscript        # TorchScript
                              yolov5s.onnx               # ONNX Runtime or OpenCV DNN with --dnn
                              yolov5s_openvino_model     # OpenVINO
                              yolov5s.engine             # TensorRT
                              yolov5s.mlmodel            # CoreML (macOS-only)
                              yolov5s_saved_model        # TensorFlow SavedModel
                              yolov5s.pb                 # TensorFlow GraphDef
                              yolov5s.tflite             # TensorFlow Lite
                              yolov5s_edgetpu.tflite     # TensorFlow Edge TPU
                              yolov5s_paddle_model       # PaddlePaddle
"""

这段代码的主要功能是用于验证训练好的YOLOv5目标检测模型在某个检测数据集上的表现。下面我将逐步分解并详细解释这段代码。

  1. 注释部分:

    • 开头的注释 # YOLOv5 🚀 by Ultralytics, GPL-3.0 license 表示这段代码是由Ultralytics开发的YOLOv5模型,并且该代码遵循GPL-3.0许可证。
    • 接下来的一系列三引号 """ 开头和结束之间的内容是代码的文档说明,主要用于介绍这个脚本的功能和用法。
  2. 功能说明:

    • Validate a trained YOLOv5 detection model on a detection dataset 这一行明确了脚本的功能:验证已训练的YOLOv5检测模型在检测数据集上的性能。
  3. 使用示例:

    • Usage: 部分提供了一个基本的命令行示例,说明如何运行这个验证脚本:
      $ python val.py --weights yolov5s.pt --data coco128.yaml --img 640
      

      CopyInsert

      • --weights yolov5s.pt 指定了要使用的模型权重文件。
      • --data coco128.yaml 指定了数据集的配置文件。
      • --img 640 指定输入图像的大小为640x640像素。
  4. 其他格式说明:

    • Usage - formats: 这一部分列出了可以进行验证的多种模型格式:
      • yolov5s.pt: PyTorch格式的模型。
      • yolov5s.torchscript: TorchScript格式的模型。
      • yolov5s.onnx: ONNX格式的模型,可以通过ONNX Runtime或OpenCV DNN进行推理。
      • yolov5s_openvino_model: OpenVINO格式的模型。
      • yolov5s.engine: TensorRT格式的模型。
      • yolov5s.mlmodel: CoreML格式的模型(仅限于macOS)。
      • yolov5s_saved_model: TensorFlow的SavedModel格式。
      • yolov5s.pb: TensorFlow的GraphDef格式。
      • yolov5s.tflite: TensorFlow Lite格式的模型。
      • yolov5s_edgetpu.tflite: TensorFlow Edge TPU格式的模型。
      • yolov5s_paddle_model: PaddlePaddle格式的模型。

这段代码是YOLOv5目标检测模型验证脚本的开头部分,主要负责定义命令行参数,并提示用户如何正确使用该脚本进行模型验证。它支持多种模型格式,适用于不同的推理环境和设备,为用户提供了灵活性。此外,用户可以通过示例理解如何输入必要的参数来运行验证过程。这为使用YOLOv5进行目标检测的开发者提供了一个具体的操作指南。

 五、依赖库

mport argparse
import json
import os
import sys
from pathlib import Path

import numpy as np
import torch
from tqdm import tqdm

FILE = Path(__file__).resolve()
ROOT = FILE.parents[0]  # YOLOv5 root directory
if str(ROOT) not in sys.path:
    sys.path.append(str(ROOT))  # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative

from models.common import DetectMultiBackend
from utils.callbacks import Callbacks
from utils.dataloaders import create_dataloader
from utils.general import (LOGGER, TQDM_BAR_FORMAT, Profile, check_dataset, check_img_size, check_requirements,
                           check_yaml, coco80_to_coco91_class, colorstr, increment_path, non_max_suppression,
                           print_args, scale_boxes, xywh2xyxy, xyxy2xywh)
from utils.metrics import ConfusionMatrix, ap_per_class, box_iou
from utils.plots import output_to_target, plot_images, plot_val_study
from utils.torch_utils import select_device, smart_inference_mode
  1. 导入必要的库:

    import argparse
    import json
    import os
    import sys
    from pathlib import Path
    import numpy as np
    import torch
    from tqdm import tqdm
    

    CopyInsert

    • argparse:用于解析命令行参数。
    • json:用于处理JSON格式的数据。
    • ossys:提供与操作系统及系统路径的交互功能。
    • Path:来自pathlib模块,用于路径操作。
    • numpy:用于数值计算和数组处理。
    • torch:PyTorch深度学习框架。
    • tqdm:用于显示进度条。
  2. 定义文件和根目录路径:

    FILE = Path(__file__).resolve()
    ROOT = FILE.parents[0]  # YOLOv5 root directory
    if str(ROOT) not in sys.path:
        sys.path.append(str(ROOT))  # add ROOT to PATH
    ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative
    
    • FILE:获取当前脚本的绝对路径。
    • ROOT:定义YOLOv5的根目录,确保当前脚本可以找到相关模块。
    • 检查并将ROOT添加到系统路径中,以便后面的模块可以被导入。
  3. 导入YOLOv5相关的模块:

    from models.common import DetectMultiBackend
    from utils.callbacks import Callbacks
    from utils.dataloaders import create_dataloader
    from utils.general import (LOGGER, TQDM_BAR_FORMAT, Profile, check_dataset, check_img_size, check_requirements,
                               check_yaml, coco80_to_coco91_class, colorstr, increment_path, non_max_suppression,
                               print_args, scale_boxes, xywh2xyxy, xyxy2xywh)
    from utils.metrics import ConfusionMatrix, ap_per_class, box_iou
    from utils.plots import output_to_target, plot_images, plot_val_study
    from utils.torch_utils import select_device, smart_inference_mode
    
    • 以上代码从modelsutils模块中导入了一系列功能。
    • DetectMultiBackend:用于加载多种格式的模型。
    • Callbacks:用于处理训练和验证过程中的回调功能。
    • create_dataloader:用于创建数据加载器。
    • 各种辅助函数和类如check_datasetcheck_img_size等用于检查和处理数据集、图像大小等。
    • ConfusionMatrixap_per_class等用于计算模型的评估指标。
    • plot_images等用于可视化结果。

这段代码的主要功能是为YOLOv5目标检测模型的验证过程设定必要的环境和工具,包括:

  • 设置环境路径以方便导入YOLO模型相关模块。
  • 导入数据加载、模型检测和结果评估等所需的工具和函数,确保后续的代码可以有效地加载数据、进行推理、计算指标并可视化结果。

通过这一部分代码,整个模型验证流程的基础架构已经为后续步骤做好了准备。

 

PaddlePaddle是一个开源的深度学习平台,可以用于构建和训练深度学习模型。如果你想使用PaddlePaddle,可以通过源码编译的方式来安装。首先,你需要在Git Bash中执行以下两条命令来将PaddlePaddle的源码克隆到本地,并进入Paddle目录: ``` git clone https://github.com/PaddlePaddle/Paddle.git cd Paddle ``` 接下来,你可以根据自己的需求进行编译。如果你使用的是Windows系统,可以使用源码编译来安装符合你需求的PaddlePaddle版本。具体的编译步骤可以参考官方文档中的Windows下源码编译部分\[2\]。 如果你想在docker镜像中编译PaddlePaddle,可以使用以下命令启动docker镜像并进行编译。如果你需要编译CPU版本,可以使用以下命令: ``` sudo docker run --name paddle-test -v $PWD:/paddle --network=host -it hub.baidubce.com/paddlepaddle/paddle:latest-dev /bin/bash ``` 如果你需要编译GPU版本,可以使用以下命令: ``` sudo nvidia-docker run --name paddle-test -v $PWD:/paddle --network=host -it hub.baidubce.com/paddlepaddle/paddle:latest-dev /bin/bash ``` 以上是关于使用源码编译PaddlePaddle的一些基本步骤和命令。你可以根据自己的需求和操作系统选择适合的方式来安装PaddlePaddle。 #### 引用[.reference_title] - *1* *2* *3* [《PaddlePaddle从入门到炼丹》一——新版本PaddlePaddle的安装](https://blog.csdn.net/qq_33200967/article/details/83052060)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值