YOLOv10-1.1部分代码阅读笔记-val.py
val.py
ultralytics\models\yolo\detect\val.py
目录
val.py
1.所需的库和模块
2.class DetectionValidator(BaseValidator):
1.所需的库和模块
# Ultralytics YOLO 🚀, AGPL-3.0 license
import os
from pathlib import Path
import numpy as np
import torch
from ultralytics.data import build_dataloader, build_yolo_dataset, converter
from ultralytics.engine.validator import BaseValidator
from ultralytics.utils import LOGGER, ops
from ultralytics.utils.checks import check_requirements
from ultralytics.utils.metrics import ConfusionMatrix, DetMetrics, box_iou
from ultralytics.utils.plotting import output_to_target, plot_images
2.class DetectionValidator(BaseValidator):
# 这段代码定义了一个名为 DetectionValidator 的类,用于目标检测任务的验证和评估。它继承自 BaseValidator ,并提供了对目标检测模型验证过程中的数据预处理、指标计算、结果可视化等功能的实现。
# 定义了一个名为 DetectionValidator 的类,继承自 BaseValidator 。这表明它会复用 BaseValidator 的一些基础功能,并在此基础上扩展目标检测相关的验证逻辑。
class DetectionValidator(BaseValidator):
# 扩展 BaseValidator 类的类,用于基于检测模型进行验证。
"""
A class extending the BaseValidator class for validation based on a detection model.
Example:
```python
from ultralytics.models.yolo.detect import DetectionValidator
args = dict(model='yolov8n.pt', data='coco8.yaml')
validator = DetectionValidator(args=args)
validator()
```
"""
# 这段代码是 DetectionValidator 类的初始化方法 __init__ ,用于设置类的基本属性和初始化一些关键变量。
# 定义了 DetectionValidator 类的初始化方法,接收以下参数 :
# 1.dataloader :数据加载器,用于加载验证数据集。
# 2.save_dir :保存验证结果的目录路径。
# 3.pbar :进度条工具,用于显示验证进度。
# 4.args :包含验证任务配置的参数对象。
# 5._callbacks :回调函数,用于在验证过程中执行自定义操作。
def __init__(self, dataloader=None, save_dir=None, pbar=None, args=None, _callbacks=None):
# 使用必要的变量和设置初始化检测模型。
"""Initialize detection model with necessary variables and settings."""
# 调用父类 BaseValidator 的初始化方法,将传入的参数传递给父类,完成基础的初始化工作。这确保了 DetectionValidator 继承了 BaseValidator 的通用功能。
super().__init__(dataloader, save_dir, pbar, args, _callbacks)
# 初始化 nt_per_class 属性,表示 每个类别的目标数量 。它在后续的验证过程中会被更新,用于统计每个类别的真实目标数量。
self.nt_per_class = None
# 初始化 is_coco 属性,默认值为 False 。该属性用于 标记当前验证任务是否使用 COCO 数据集 。如果使用 COCO 数据集,后续会启用一些特定的逻辑(如 COCO 格式的评估指标计算)。
self.is_coco = False
# 初始化 class_map 属性,表示 类别映射关系 。在目标检测任务中,某些数据集(如 COCO)可能需要将模型的输出类别映射到数据集的类别。例如,COCO 数据集有 80 个类别,但其类别索引与 COCO API 中的 91 个类别索引不完全一致,需要一个映射表来转换。
self.class_map = None
# 将 args.task 设置为 "detect" ,明确 当前任务是目标检测任务 。 args 是一个配置对象,存储了验证任务的参数,这里通过设置 task 属性,确保后续逻辑知道当前任务类型。
self.args.task = "detect"
# 实例化 DetMetrics 类,用于计算目标检测任务的评估指标(如 mAP、精确率、召回率等)。 DetMetrics 的初始化需要指定保存目录 save_dir 和绘图回调函数 on_plot 。
# class DetMetrics(SimpleClass):
# -> 用于处理目标检测任务中的性能评估。
# -> def __init__(self, save_dir=Path("."), plot=False, on_plot=None, names=()) -> None:
self.metrics = DetMetrics(save_dir=self.save_dir, on_plot=self.on_plot)
# 定义了一个 IoU 阈值向量 iouv ,用于计算不同 IoU 阈值下的 mAP 指标。 torch.linspace(0.5, 0.95, 10) 生成一个从 0.5 到 0.95 的等间距向量,包含 10 个值,即 [0.5, 0.55, 0.6, ..., 0.95] 。这是目标检测任务中常用的 IoU 阈值范围,用于计算 mAP@0.5:0.95。
self.iouv = torch.linspace(0.5, 0.95, 10) # IoU vector for mAP@0.5:0.95
# 计算 iouv 向量的长度(即元素数量),并将其存储在 niou 属性中。 numel() 是 PyTorch 的方法,用于获取张量中的元素总数。这里 niou 的值为 10,表示有 10 个 IoU 阈值。
self.niou = self.iouv.numel()
# 初始化 lb 属性,它是一个空列表,用于存储自动标注(autolabelling)相关的数据。自动标注是指在验证过程中,根据模型的预测结果生成新的标注数据,用于后续的训练或分析。
self.lb = [] # for autolabelling
# 这段代码是 DetectionValidator 类的初始化方法,主要功能包括。调用父类初始化方法,完成基础设置。初始化与目标检测任务相关的属性,如类别映射、评估指标计算工具、IoU 阈值向量等。设置默认值和任务类型,为后续的验证过程做好准备。通过这些初始化操作, DetectionValidator 类为后续的目标检测验证任务奠定了基础,确保了验证过程中数据处理、指标计算和结果保存等功能的正常运行。
# 这段代码定义了 DetectionValidator 类中的 preprocess 方法,其主要功能是对输入的批次数据( batch )进行预处理,以便为后续的目标检测模型验证做好准备。
# 定义了 preprocess 方法,接收一个批次数据 batch 作为输入。
# 1.batch :一个字典,包含了当前批次的图像、标签、边界框等信息。
def preprocess(self, batch):
# 对一批图像进行预处理,以进行 YOLO 训练。
"""Preprocesses batch of images for YOLO training."""
# 将批次中的图像数据( batch["img"] )移动到指定的设备(如 GPU)上。 non_blocking=True 表示在移动数据时不会阻塞当前线程,这在使用 GPU 时可以提高效率。
batch["img"] = batch["img"].to(self.device, non_blocking=True)
# 对图像数据进行归一化处理。
# 如果 self.args.half 为 True ,则将图像数据转换为半精度浮点数( half() );否则,转换为全精度浮点数( float() )。
# 然后将像素值除以 255,将像素值范围从 [0, 255] 归一化到 [0, 1] 。
batch["img"] = (batch["img"].half() if self.args.half else batch["img"].float()) / 255
# 将批次数据中的其他关键信息( batch_idx 、 cls 和 bboxes )也移动到指定设备上。这些信息分别表示 :
# batch_idx :每个目标在批次中的索引。
# cls :每个目标的类别标签。
# bboxes :每个目标的边界框坐标。
for k in ["batch_idx", "cls", "bboxes"]:
batch[k] = batch[k].to(self.device)
# 如果启用了 save_hybrid 模式(通过 self.args.save_hybrid 判断),则执行以下操作。 save_hybrid 通常用于生成混合数据集,结合模型的预测结果和真实标签,用于后续的自动标注或数据增强。
if self.args.save_hybrid:
# 获取图像的高度和宽度( height 和 width )。
height, width = batch["img"].shape[2:]
# 以及当前批次的图像数量( nb )。
nb = len(batch["img"])
# 将边界框坐标从归一化格式( [x_center, y_center, width, height] ,范围为 [0, 1] )转换为原始图像坐标( [x_center, y_center, width, height] ,范围为 [0, 图像尺寸] )。这里通过乘以 (width, height, width, height) 实现坐标转换。
bboxes = batch["bboxes"] * torch.tensor((width, height, width, height), device=self.device)
# 根据 batch_idx 将每个图像的类别标签和边界框组合起来,生成一个列表( self.lb ),用于自动标注。
self.lb = (
[
# 对于每个图像索引 i ,从 batch["cls"] 和 bboxes 中提取属于该图像的 类别标签 和 边界框 。
# 使用 torch.cat 将类别标签和边界框沿最后一维拼接( dim=-1 ),形成 [类别标签, 边界框坐标] 的格式。
torch.cat([batch["cls"][batch["batch_idx"] == i], bboxes[batch["batch_idx"] == i]], dim=-1)
for i in range(nb)
]
if self.args.save_hybrid
# 如果未启用 save_hybrid 模式,则 self.lb 为空列表。
else []
) # for autolabelling
# 返回预处理后的批次数据 batch ,以便后续使用。
return batch
# 这段代码实现了目标检测验证任务中的数据预处理功能,主要包括。将图像和标签数据移动到指定设备(如 GPU)。对图像数据进行归一化处理,将像素值范围从 [0, 255] 转换为 [0, 1] 。如果启用了 save_hybrid 模式,则将边界框坐标从归一化格式转换为原始图像坐标,并生成用于自动标注的数据列表 self.lb 。通过这些预处理步骤, preprocess 方法确保了数据格式和设备一致性,为后续的模型验证和指标计算提供了准备。
# 这段代码定义了 DetectionValidator 类中的 init_metrics 方法,其主要功能是初始化与验证过程相关的指标计算工具和属性。
# 定义了 init_metrics 方法,接收一个模型对象 1.model 作为输入。此方法的目的是根据模型和验证任务的配置,初始化与评估指标相关的属性和工具。
def init_metrics(self, model):
# 初始化 YOLO 的评估指标。
"""Initialize evaluation metrics for YOLO."""
# 从 self.data (通常是一个字典,存储数据集相关信息)中获取验证数据集的路径。 self.args.split 是一个参数,表示当前验证任务所使用的数据集分割(如 "val" 或 "test" )。如果未找到路径,则默认为空字符串。
val = self.data.get(self.args.split, "") # validation path
# 判断当前验证任务是否使用 COCO 数据集。
# 验证路径是一个字符串。 路径中包含 "coco" 。 路径以 "val2017.txt" 结尾(这是 COCO 数据集的标准验证集文件名)。如果满足这些条件,则将 self.is_coco 设置为 True ,否则为 False 。
self.is_coco = isinstance(val, str) and "coco" in val and val.endswith(f"{os.sep}val2017.txt") # is COCO
# 根据是否使用 COCO 数据集,初始化类别映射表 self.class_map 。
# 如果是 COCO 数据集,调用 converter.coco80_to_coco91_class() ,将 COCO 数据集的 80 个类别映射到 COCO API 所需的 91 个类别。
# 如果不是 COCO 数据集,使用默认的类别映射( list(range(1000)) ),表示类别索引直接对应模型输出。
self.class_map = converter.coco80_to_coco91_class() if self.is_coco else list(range(1000))
# 如果当前验证任务使用 COCO 数据集,则将 self.args.save_json 设置为 True 。这表示在验证结束后,会将预测结果保存为 JSON 文件,以便使用 COCO API 进行评估。
self.args.save_json |= self.is_coco # run on final val if training COCO
# 从模型对象中获取 类别名称列表 ,并将其存储在 self.names 中。这些类别名称通常用于可视化和日志记录。
self.names = model.names
# 计算 类别数量 ( nc ),即模型支持的类别总数。
self.nc = len(model.names)
# 将类别名称列表传递给 self.metrics 对象( DetMetrics 类的实例),以便在指标计算过程中使用类别名称。
self.metrics.names = self.names
# 将 self.args.plots 的值传递给 self.metrics.plot ,用于控制是否绘制指标相关的可视化图表。
self.metrics.plot = self.args.plots
# 初始化混淆矩阵对象 self.confusion_matrix ,用于计算分类指标。
# nc :类别数量。
# conf :置信度阈值(来自 self.args.conf ),用于决定哪些预测结果被视为有效。
# class ConfusionMatrix:
# -> 用于计算和处理混淆矩阵,特别是在目标检测和分类任务中。混淆矩阵是一个非常有用的工具,用于评估模型的性能,特别是分类的准确性和错误类型。
# -> def __init__(self, nc, conf=0.25, iou_thres=0.45, task="detect"):
self.confusion_matrix = ConfusionMatrix(nc=self.nc, conf=self.args.conf)
# 初始化 self.seen 为 0,用于记录验证过程中处理的图像总数。
self.seen = 0
# 初始化 self.jdict 为空列表,用于存储预测结果的 JSON 格式数据。这在使用 COCO 数据集时特别有用,因为 COCO API 需要 JSON 格式的预测结果。
self.jdict = []
# 初始化 self.stats 为一个字典,用于存储验证过程中的统计信息。
# tp :存储每个预测框是否为真阳性的布尔值。
# conf :存储预测框的置信度。
# pred_cls :存储预测框的类别。
# target_cls :存储目标框的真实类别。
self.stats = dict(tp=[], conf=[], pred_cls=[], target_cls=[])
# 这段代码实现了 DetectionValidator 类中与指标初始化相关的逻辑,主要包括以下功能。判断是否使用 COCO 数据集,并根据此决定是否启用 COCO 格式的评估。初始化类别映射表和类别名称列表。初始化混淆矩阵和统计信息存储结构。为后续的指标计算和结果保存做好准备。通过这些初始化操作, init_metrics 方法为验证过程中的指标计算、结果可视化和日志记录奠定了基础。
# 这段代码定义了 DetectionValidator 类中的 get_desc 方法,其主要功能是生成一个格式化的字符串,用于描述验证过程中输出的指标信息。
# 定义了 get_desc 方法,该方法不接收任何参数,返回一个格式化的字符串。
def get_desc(self):
# 返回一个格式化的字符串,总结 YOLO 模型的类指标。
"""Return a formatted string summarizing class metrics of YOLO model."""
# %22s" :定义了一个宽度为 22 个字符的字符串格式化占位符,用于对齐输出的类别名称。
# "%11s" * 6 :定义了 6 个宽度为 11 个字符的字符串格式化占位符,用于对齐后续的指标名称。
# ("Class", "Images", "Instances", "Box(P", "R", "mAP50", "mAP50-95)") :这些是需要格式化的指标名称,分别表示 :
# Class :类别名称。
# Images :处理的图像数量。
# Instances :目标实例的总数。
# Box(P) :边界框的精确率(Precision)。
# R :边界框的召回率(Recall)。
# mAP50 :在 IoU=0.5 时的平均精度(mean Average Precision)。
# mAP50-95 :在 IoU=0.5 到 IoU=0.95(步长为 0.05)范围内的平均精度。
return ("%22s" + "%11s" * 6) % ("Class", "Images", "Instances", "Box(P", "R", "mAP50", "mAP50-95)")
# 返回值 :
# 该方法返回一个格式化的字符串,用于在验证过程中输出指标时对齐列标题。例如 :
# Class Images Instances Box(P R mAP50 mAP50-95
# get_desc 方法的作用是生成一个固定格式的字符串,用于对齐和描述验证过程中的输出指标。这种格式化输出有助于清晰地展示验证结果,便于开发者和研究人员快速了解模型的性能表现。
# 这段代码定义了 DetectionValidator 类中的 postprocess 方法,其主要功能是对模型的预测结果进行后处理,特别是执行 非极大值抑制(Non-Maximum Suppression, NMS)。NMS 是目标检测任务中常用的步骤,用于去除冗余的检测框,保留最优的检测结果。
# 定义了 postprocess 方法,接收模型的预测结果 preds 作为输入。
# 1.preds :通常是一个张量,包含每个检测框的类别、置信度和坐标信息。
def postprocess(self, preds):
# 对预测输出应用非最大抑制。
"""Apply Non-maximum suppression to prediction outputs."""
# 调用 ops.non_max_suppression 函数,对预测结果执行非极大值抑制。 ops 是一个模块或工具类,提供了实现 NMS 的函数。
return ops.non_max_suppression(
# 将模型的预测结果传递给 NMS 函数。 preds 的格式通常是 [x1, y1, x2, y2, confidence, class] ,其中 (x1, y1) 和 (x2, y2) 是边界框的左上角和右下角坐标, confidence 是置信度, class 是类别索引。
preds,
# 设置置信度阈值( self.args.conf )。只有置信度高于此阈值的检测框才会被保留。例如,如果 self.args.conf = 0.25 ,则置信度低于 0.25 的检测框会被过滤掉。
self.args.conf,
# 设置 IoU(交并比)阈值( self.args.iou )。在 NMS 过程中,如果两个检测框的 IoU 大于此阈值,则认为它们是重叠的,保留置信度较高的框,移除置信度较低的框。例如, self.args.iou = 0.45 表示当两个框的 IoU 大于 0.45 时,其中一个会被抑制。
self.args.iou,
# 传递 self.lb ,这是在预处理阶段生成的标签信息(用于自动标注)。在某些情况下,NMS 可能会结合标签信息进行处理,例如在半监督学习或伪标签生成中。
labels=self.lb,
# 设置 multi_label=True ,表示每个边界框可以属于多个类别。这在多标签目标检测任务中很有用,允许一个检测框包含多个类别标签。
multi_label=True,
# 设置类别不可知(class-agnostic)模式。如果 self.args.single_cls=True ,则在 NMS 过程中忽略类别信息,只根据边界框的置信度和重叠程度进行抑制。如果为 False ,则会考虑类别信息,即不同类别的框即使重叠也不会互相抑制。
agnostic=self.args.single_cls,
# 设置每个图像中保留的最大检测框数量( self.args.max_det )。例如,如果 self.args.max_det = 300 ,则每个图像最多保留 300 个检测框,即使 NMS 后有更多的框满足条件,也会被截断到这个数量。
max_det=self.args.max_det,
# 返回经过 NMS 处理后的最终检测结果。
)
# postprocess 方法的作用是对模型的预测结果进行后处理,主要通过非极大值抑制(NMS)去除冗余的检测框。它根据以下参数配置 NMS 的行为。置信度阈值:过滤掉置信度较低的检测框。IoU 阈值:决定何时认为两个框重叠,并抑制其中一个。多标签模式:允许每个框属于多个类别。类别不可知模式:决定是否在 NMS 中考虑类别信息。最大检测框数量:限制每个图像中保留的检测框数量。通过这些配置, postprocess 方法确保了最终的检测结果既准确又高效,为后续的评估和可视化提供了高质量的输入。
# 这段代码定义了 DetectionValidator 类中的 _prepare_batch 方法,其主要功能是处理单个图像的标签信息(类别和边界框),并将这些标签从模型输入空间转换到原始图像空间。这个方法在验证过程中用于准备每个图像的真实标签数据,以便后续与模型的预测结果进行对比和评估。
# 定义了 _prepare_batch 方法,接收两个参数。
# 1.si :当前图像在批次中的索引。
# 2.batch :包含整个批次数据的字典,通常包括图像、类别标签、边界框等信息。
def _prepare_batch(self, si, batch):
# 准备一批图像和注释以供验证。
"""Prepares a batch of images and annotations for validation."""
# 通过 batch["batch_idx"] 找到当前图像索引 si 对应的 所有目标的索引 。 batch["batch_idx"] 是一个张量,记录了每个目标属于哪个图像。
idx = batch["batch_idx"] == si
# 提取 当前图像的所有类别标签 。batch["cls"][idx] :根据索引提取属于当前图像的类别标签。 .squeeze(-1) :去除多余的维度(如果类别标签是二维的,例如 [N, 1] ,则将其压缩为一维 [N] )。
cls = batch["cls"][idx].squeeze(-1)
# 提取当前图像的 所有边界框坐标 。 batch["bboxes"] 是一个张量,存储了每个目标的边界框信息,格式通常是 [x_center, y_center, width, height] 。
bbox = batch["bboxes"][idx]
# 提取当前图像的 原始尺寸 ( ori_shape ),通常是一个包含 [height, width] 的张量。
ori_shape = batch["ori_shape"][si]
# 获取当前图像在模型输入空间的尺寸( imgsz ),即 输入图像的高度和宽度 。 batch["img"].shape[2:] 表示提取张量的后两个维度(假设图像张量的形状为 [batch_size, channels, height, width] )。
imgsz = batch["img"].shape[2:]
# 提取当前图像的 缩放比例和填充信息 ( ratio_pad ),这些信息用于将边界框从模型输入空间映射回原始图像空间。
ratio_pad = batch["ratio_pad"][si]
# 如果当前图像存在目标(即类别标签列表 cls 不为空),则执行以下操作。
if len(cls):
# 将边界框从中心点格式( [x_center, y_center, width, height] )转换为角点格式( [x1, y1, x2, y2] ),使用 ops.xywh2xyxy 函数。
# 将边界框坐标从归一化格式(范围 [0, 1] )转换为模型输入空间的绝对坐标,通过乘以输入图像的尺寸 imgsz 。
# 注意, imgsz 的顺序是 [height, width] ,而边界框坐标需要 [width, height, width, height] ,因此通过索引 [[1, 0, 1, 0]] 调整顺序。
bbox = ops.xywh2xyxy(bbox) * torch.tensor(imgsz, device=self.device)[[1, 0, 1, 0]] # target boxes
# 将边界框从模型输入空间映射回原始图像空间。 ops.scale_boxes 函数根据输入图像的尺寸( imgsz )、原始图像尺寸( ori_shape )和缩放比例( ratio_pad )调整边界框坐标。
# def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None, padding=True, xywh=False):
# -> 用于将边界框从一个图像尺寸缩放到另一个图像尺寸,并考虑了填充(padding)和坐标格式(xyxy 或 xywh)。调用 clip_boxes 函数,将调整后的边界框限制在原始图像的边界内,并返回结果。
# -> return clip_boxes(boxes, img0_shape)
ops.scale_boxes(imgsz, bbox, ori_shape, ratio_pad=ratio_pad) # native-space labels
# 返回一个字典,包含处理后的类别标签、边界框坐标、原始图像尺寸、输入图像尺寸和缩放比例信息。这些信息将用于后续的评估和可视化。
return dict(cls=cls, bbox=bbox, ori_shape=ori_shape, imgsz=imgsz, ratio_pad=ratio_pad)
# _prepare_batch 方法的作用是。提取当前图像的类别标签和边界框信息。将边界框从中心点格式转换为角点格式。将边界框从模型输入空间映射回原始图像空间。返回处理后的标签信息,以便后续与模型的预测结果进行对比和评估。这个方法是目标检测验证过程中非常关键的一步,确保了真实标签的格式和空间与模型的预测结果一致,从而能够准确地计算评估指标(如 mAP、精确率、召回率等)。
# 这段代码定义了 DetectionValidator 类中的 _prepare_pred 方法,其主要功能是将模型的预测结果从模型输入空间(通常是缩放后的图像空间)转换到原始图像空间。这是目标检测任务中常见的步骤,因为模型的预测结果通常是基于输入图像的尺寸,而实际应用中需要将这些预测结果映射回原始图像的尺寸。
# 定义了 _prepare_pred 方法,接收两个参数。
# 1.pred :模型的预测结果,通常是一个张量,包含每个检测框的坐标、置信度和类别信息。
# 2.pbatch :一个字典,包含当前图像的相关信息,如原始尺寸、输入尺寸和缩放比例等。这些信息通常由 _prepare_batch 方法生成。
def _prepare_pred(self, pred, pbatch):
# 准备一批图像和注释以供验证。
"""Prepares a batch of images and annotations for validation."""
# 对 预测结果 pred 进行深拷贝,创建一个新的张量 predn 。这一步是为了避免直接修改原始预测结果,确保数据的安全性。
predn = pred.clone()
# 调用 ops.scale_boxes 函数,将预测的边界框从模型输入空间映射到原始图像空间。
# pbatch["imgsz"] :输入图像的尺寸(通常是缩放后的尺寸,如 [height, width] )。
# predn[:, :4] :提取预测结果中的边界框坐标部分(假设预测结果的格式为 [x1, y1, x2, y2, confidence, class] )。
# pbatch["ori_shape"] :原始图像的尺寸(如 [original_height, original_width] )。
# ratio_pad=pbatch["ratio_pad"] :缩放比例和填充信息,用于调整边界框坐标。
# ops.scale_boxes 函数的作用是根据输入图像的尺寸、原始图像的尺寸以及缩放比例,将边界框坐标从模型输入空间转换到原始图像空间。这一步确保了预测结果的坐标与原始图像一致,便于后续的评估和可视化。
ops.scale_boxes(
pbatch["imgsz"], predn[:, :4], pbatch["ori_shape"], ratio_pad=pbatch["ratio_pad"]
) # native-space pred
# 返回处理后的预测结果 predn ,其中边界框坐标已经转换到原始图像空间。
return predn
# _prepare_pred 方法的作用是将模型的预测结果从模型输入空间转换到原始图像空间。具体功能如下。深拷贝预测结果:确保原始数据不被修改。坐标转换:使用 ops.scale_boxes 函数,根据输入图像尺寸、原始图像尺寸和缩放比例,将边界框坐标从模型输入空间映射到原始图像空间。返回处理后的预测结果:返回转换后的预测结果,以便后续的评估和可视化。这个方法是目标检测验证过程中非常关键的一步,确保了预测结果的坐标与原始图像一致,从而能够准确地计算评估指标(如 mAP、精确率、召回率等),并生成正确的可视化结果。
# 这段代码定义了 DetectionValidator 类中的 update_metrics 方法,其主要功能是更新验证过程中的评估指标。该方法接收模型的预测结果 preds 和当前批次的真实标签 batch ,并根据这些信息计算和更新指标(如精确率、召回率、mAP 等)。
# 定义了 update_metrics 方法,接收两个参数。
# 1.preds :模型的预测结果,通常是一个列表,每个元素是一个张量,包含每个图像的检测框信息( [x1, y1, x2, y2, confidence, class] )。
# 2.batch :当前批次的真实标签数据,包含类别标签、边界框等信息。
def update_metrics(self, preds, batch):
# 指标。
"""Metrics."""
# 遍历当前批次的 每个图像的预测结果 。 si 是图像索引, pred 是对应图像的预测结果。
for si, pred in enumerate(preds):
# 将 self.seen (已处理的图像数量)加 1。
self.seen += 1
# 获取当前图像的 预测框数量 ( npr )。
npr = len(pred)
# 初始化一个字典 stat ,用于存储当前图像的统计信息。
stat = dict(
# 预测框的置信度。
conf=torch.zeros(0, device=self.device),
# 预测框的类别。
pred_cls=torch.zeros(0, device=self.device),
# 一个布尔张量,表示预测框是否为真阳性(True Positive),形状为 [npr, self.niou] ,对应每个预测框在不同 IoU 阈值下的结果。
tp=torch.zeros(npr, self.niou, dtype=torch.bool, device=self.device),
)
# 调用 _prepare_batch 方法,处理当前图像的真实标签数据,将其从模型输入空间转换到原始图像空间,并提取类别标签和边界框。
pbatch = self._prepare_batch(si, batch)
# 从 pbatch 中提取当前图像的 真实类别标签 cls 和 边界框 bbox 。
cls, bbox = pbatch.pop("cls"), pbatch.pop("bbox")
# 获取当前图像的 真实目标数量 ( nl )。
nl = len(cls)
# 将真实类别标签存储到 stat 字典中。
stat["target_cls"] = cls
# 如果当前图像没有预测框( npr == 0 ),则执行以下操作。
if npr == 0:
# 如果有真实目标( nl > 0 )。
if nl:
# 将当前图像的统计信息( stat )追加到全局统计字典 self.stats 中。
for k in self.stats.keys():
self.stats[k].append(stat[k])
# 如果启用了绘图( self.args.plots )。
if self.args.plots:
# 调用 self.confusion_matrix.process_batch 更新混淆矩阵,传入真实边界框和类别,但不传入预测框( detections=None )。
self.confusion_matrix.process_batch(detections=None, gt_bboxes=bbox, gt_cls=cls)
# 跳过当前图像的后续处理,继续处理下一个图像。
continue
# Predictions
# 如果启用了 单类别模式 ( self.args.single_cls )。
if self.args.single_cls:
# 则将所有预测框的类别设置为 0。
pred[:, 5] = 0
# 调用 _prepare_pred 方法,将当前图像的预测框从模型输入空间转换到原始图像空间。
predn = self._prepare_pred(pred, pbatch)
# 从处理后的预测框 predn 中提取 置信度 ( predn[:, 4] )和 类别 ( predn[:, 5] ),并存储到 stat 字典中。
stat["conf"] = predn[:, 4]
stat["pred_cls"] = predn[:, 5]
# Evaluate
# 如果当前图像有真实目标( nl > 0 )
if nl:
# 调用 _process_batch 方法,计算预测框与真实框的匹配情况,并更新 stat["tp"] (真阳性标志)。
stat["tp"] = self._process_batch(predn, bbox, cls)
# 如果启用了绘图( self.args.plots )。
if self.args.plots:
# 调用 self.confusion_matrix.process_batch 更新混淆矩阵,传入预测框、真实边界框和类别。
self.confusion_matrix.process_batch(predn, bbox, cls)
# 将当前图像的 统计信息 ( stat )追加到全局统计字典 self.stats 中。
for k in self.stats.keys():
self.stats[k].append(stat[k])
# Save
# 如果启用了 JSON 保存( self.args.save_json )。
if self.args.save_json:
# 调用 self.pred_to_json 方法,将预测结果保存为 JSON 格式。
self.pred_to_json(predn, batch["im_file"][si])
# 如果启用了 TXT 保存( self.args.save_txt )。
if self.args.save_txt:
file = self.save_dir / "labels" / f'{Path(batch["im_file"][si]).stem}.txt'
# 调用 self.save_one_txt 方法,将预测结果保存为 TXT 格式。
self.save_one_txt(predn, self.args.save_conf, pbatch["ori_shape"], file)
# pdate_metrics 方法的作用是。遍历当前批次的每个图像,提取其预测结果和真实标签。将预测框和真实框从模型输入空间转换到原始图像空间。计算预测框与真实框的匹配情况(如真阳性标志 tp )。更新全局统计信息(如置信度、类别、真阳性标志等)。根据配置保存预测结果(如 JSON 或 TXT 格式)。更新混淆矩阵(如果启用了绘图)。这个方法是验证过程中非常关键的一步,它确保了评估指标的准确计算,并为后续的可视化和结果保存提供了支持。
# 这段代码定义了 DetectionValidator 类中的 finalize_metrics 方法,其主要功能是在验证过程结束后,将一些关键的评估指标和统计信息汇总并传递给 self.metrics 对象。 self.metrics 通常是一个用于存储和处理评估指标的类实例(如 DetMetrics )。
# 定义了 finalize_metrics 方法。该方法接收可变参数 *args 和关键字参数 **kwargs ,但在这段代码中并未使用这些参数。这表明该方法的设计可能允许扩展,或者是为了兼容其他地方的调用。
def finalize_metrics(self, *args, **kwargs):
# 设置指标速度和混淆矩阵的最终值。
"""Set final values for metrics speed and confusion matrix."""
# 将 self.speed (通常是一个记录模型推理速度的属性)赋值给 self.metrics.speed 。这一步确保了推理速度信息被存储在 self.metrics 对象中,以便后续的报告或分析。
self.metrics.speed = self.speed
# 将 self.confusion_matrix (混淆矩阵对象)赋值给 self.metrics.confusion_matrix 。混淆矩阵是分类任务中常用的工具,用于评估模型的分类性能。在这里,它可能用于记录目标检测任务中的类别分类情况。
self.metrics.confusion_matrix = self.confusion_matrix
# finalize_metrics 方法的作用是在验证过程结束后,将一些重要的统计信息(如推理速度和混淆矩阵)汇总并传递给 self.metrics 对象。这些信息通常用于生成最终的评估报告、可视化结果或进一步分析模型性能。尽管这段代码非常简洁,但它在验证流程中扮演着关键角色,确保了所有重要的评估指标和统计信息被正确汇总,为后续的分析和报告提供了基础。
# 这段代码定义了 DetectionValidator 类中的 get_stats 方法,其主要功能是汇总和处理验证过程中的统计信息,并计算最终的评估指标(如 mAP、精确率、召回率等)。
# 定义了 get_stats 方法,该方法不接收任何参数,但会返回一个包含评估结果的字典。
def get_stats(self):
# 返回指标统计数据和结果字典。
"""Returns metrics statistics and results dictionary."""
# self.stats :这是一个字典,存储了验证过程中每个图像的统计信息,例如置信度( conf )、预测类别( pred_cls )、真阳性标志( tp )等。
# torch.cat(v, 0) :将每个统计项(如 conf 或 tp )的列表合并为一个张量。 dim=0 表示沿着第一个维度(即图像维度)进行拼接。
# .cpu().numpy() :将合并后的张量移动到 CPU,并转换为 NumPy 数组,以便后续处理。
# stats :最终生成的字典,包含所有统计信息的 NumPy 数组版本。
stats = {k: torch.cat(v, 0).cpu().numpy() for k, v in self.stats.items()} # to numpy
# 检查统计信息是否有效。
# len(stats) :确保统计信息不为空。
# stats["tp"].any() :确保至少有一个预测框被标记为真阳性( tp 中至少有一个 True 值)。
if len(stats) and stats["tp"].any():
# 调用 self.metrics.process 方法,将统计信息传递给评估指标计算模块( self.metrics )。 **stats 表示将字典中的键值对作为关键字参数传递。
self.metrics.process(**stats)
# self.nt_per_class 存储每个类别的目标数量,用于后续分析。
# np.bincount(..., minlength=self.nc) :计算每个类别的 目标数量 。 minlength=self.nc 确保结果数组的长度至少为 self.nc (类别总数)。
self.nt_per_class = np.bincount(
# stats["target_cls"] :获取所有真实目标的类别标签。
# .astype(int) :确保类别标签是整数类型。
stats["target_cls"].astype(int), minlength=self.nc
) # number of targets per class
# 返回 self.metrics.results_dict ,这是一个包含最终评估结果的字典,例如 : mAP@0.5 。 mAP@0.5:0.95 。每个类别的精确率和召回率等。
# def results_dict(self): -> 用于返回性能指标的字典。返回一个字典,键为 keys 列表加上 "fitness" ,值为平均性能指标加上综合性能指标。 -> return dict(zip(self.keys + ["fitness"], self.mean_results() + [self.fitness]))
return self.metrics.results_dict
# get_stats 方法的作用是。将验证过程中收集的统计信息(如置信度、预测类别、真阳性标志等)汇总并转换为 NumPy 数组。调用评估指标计算模块( self.metrics.process ),计算最终的评估指标(如 mAP、精确率、召回率等)。统计每个类别的目标数量。返回包含最终评估结果的字典。这个方法是验证流程的最后一步,确保了所有统计信息被正确处理,并生成了可用于报告和分析的评估结果。
# 这段代码定义了 DetectionValidator 类中的 print_results 方法,其主要功能是将验证过程中的评估结果打印到日志中,并根据配置生成混淆矩阵的可视化图表。
# 定义了 print_results 方法,用于打印验证结果并生成可视化图表。
def print_results(self):
# 打印每个类别的训练/验证集指标。
"""Prints training/validation set metrics per class."""
# 定义了一个格式化字符串 pf ,用于格式化打印结果。
# %22s :宽度为 22 个字符的字符串格式化占位符,用于对齐类别名称。
# %11i :宽度为 11 个字符的整数格式化占位符,用于对齐图像数量和目标实例总数。
# %11.3g :宽度为 11 个字符的浮点数格式化占位符,用于对齐评估指标(如 mAP、精确率、召回率等),保留 3 位小数。
# len(self.metrics.keys) :根据评估指标的数量动态调整格式化占位符的数量。
pf = "%22s" + "%11i" * 2 + "%11.3g" * len(self.metrics.keys) # print format
# 使用 LOGGER.info 打印整体验证结果。
# "all" :表示这是整体结果。
# self.seen :已处理的图像总数。
# self.nt_per_class.sum() :所有类别的目标实例总数。
# *self.metrics.mean_results() :解包整体评估指标(如 mAP@0.5、mAP@0.5:0.95 等)。
LOGGER.info(pf % ("all", self.seen, self.nt_per_class.sum(), *self.metrics.mean_results()))
# 如果目标实例总数为 0(即没有真实标签)。
if self.nt_per_class.sum() == 0:
# 则打印警告信息,提示无法计算评估指标。
LOGGER.warning(f"WARNING ⚠️ no labels found in {self.args.task} set, can not compute metrics without labels") # 警告⚠️在 {self.args.task} 集合中未找到标签,无法计算没有标签的指标。
# Print results per class
# 如果满足以下条件,则打印每个类别的验证结果。
# self.args.verbose :启用了详细模式。
# not self.training :当前不是训练模式(即处于验证或测试模式)。
# self.nc > 1 :类别数量大于 1(即多类别任务)。
# len(self.stats) :统计信息不为空。
if self.args.verbose and not self.training and self.nc > 1 and len(self.stats):
# 遍历每个类别,打印其验证结果。
for i, c in enumerate(self.metrics.ap_class_index):
# self.names[c] :当前类别的名称。
# self.seen :已处理的图像总数。
# self.nt_per_class[c] :当前类别的目标实例数量。
# *self.metrics.class_result(i) :解包当前类别的评估指标(如精确率、召回率、mAP 等)。
LOGGER.info(pf % (self.names[c], self.seen, self.nt_per_class[c], *self.metrics.class_result(i)))
# 如果启用了绘图功能( self.args.plots ),则生成混淆矩阵的可视化图表。
if self.args.plots:
# 绘制混淆矩阵图表,分别生成归一化和非归一化的版本。
# normalize=True :绘制归一化后的混淆矩阵(每个类别的值范围为 [0, 1] )。
# normalize=False :绘制原始的混淆矩阵(值为整数,表示目标数量)。
for normalize in True, False:
self.confusion_matrix.plot(
# save_dir=self.save_dir :指定保存图表的目录。
# names=self.names.values() :类别名称,用于图表的标签。
# on_plot=self.on_plot :回调函数,用于在绘图时执行自定义操作。
save_dir=self.save_dir, names=self.names.values(), normalize=normalize, on_plot=self.on_plot
)
# print_results 方法的作用是。打印整体验证结果,包括图像数量、目标实例总数和整体评估指标(如 mAP)。如果没有找到真实标签,打印警告信息。如果启用了详细模式,打印每个类别的验证结果。如果启用了绘图功能,生成混淆矩阵的可视化图表(归一化和非归一化版本)。这个方法是验证流程的最后一步,确保了验证结果的可视化和日志记录,便于开发者和研究人员分析模型性能。
# 这段代码定义了 DetectionValidator 类中的 _process_batch 方法,其主要功能是处理单个图像的预测结果和真实标签,计算预测框与真实框之间的匹配情况(如 IoU 和类别匹配)。这是目标检测评估中的关键步骤,用于确定哪些预测框是真阳性(True Positive, TP)、假阳性(False Positive, FP)或假阴性(False Negative, FN)。
# 定义了 _process_batch 方法,接收以下参数 :
# 1.detections :当前图像的预测结果,格式为 [x1, y1, x2, y2, confidence, class] 。
# 2.gt_bboxes :当前图像的真实边界框,格式为 [x1, y1, x2, y2] 。
# 3.gt_cls :当前图像的真实类别标签。
def _process_batch(self, detections, gt_bboxes, gt_cls):
# 返回正确的预测矩阵。
# 参数:
# 检测 (torch.Tensor):表示检测的形状为 [N, 6] 的张量。每个检测的格式为:x1、y1、x2、y2、conf、class。
# 标签 (torch.Tensor):表示标签的形状为 [M, 5] 的张量。每个标签的格式为:class、x1、y1、x2、y2。
# 返回:
# (torch.Tensor):10 个 IoU 级别的正确预测矩阵,形状为 [N, 10]。
"""
Return correct prediction matrix.
Args:
detections (torch.Tensor): Tensor of shape [N, 6] representing detections.
Each detection is of the format: x1, y1, x2, y2, conf, class.
labels (torch.Tensor): Tensor of shape [M, 5] representing labels.
Each label is of the format: class, x1, y1, x2, y2.
Returns:
(torch.Tensor): Correct prediction matrix of shape [N, 10] for 10 IoU levels.
"""
# 计算真实边界框( gt_bboxes )与预测边界框( detections[:, :4] )之间的交并比(IoU)。
# def box_iou(box1, box2, eps=1e-7): -> 用于计算两个边界框集合之间的交并比(Intersection over Union, IoU)。计算IoU。 -> return inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps)
iou = box_iou(gt_bboxes, detections[:, :4])
# 调用 self.match_predictions 方法,传入以下参数 :
# detections[:, 5] :预测框的类别标签。
# gt_cls :真实框的类别标签。
# iou :预测框与真实框之间的 IoU 矩阵。
# self.match_predictions 方法的作用是根据 IoU 和类别匹配情况,确定每个预测框是否为真阳性(TP)、假阳性(FP)或假阴性(FN)。该方法通常会返回一个布尔张量,表示每个预测框在不同 IoU 阈值下的匹配情况。
return self.match_predictions(detections[:, 5], gt_cls, iou)
# _process_batch 方法的作用是。计算预测框与真实框之间的 IoU 矩阵。根据 IoU 和类别匹配情况,调用 self.match_predictions 方法,确定每个预测框的匹配状态(TP、FP 或 FN)。返回匹配结果,用于后续的评估指标计算(如 mAP、精确率、召回率等)。这个方法是目标检测评估流程中的核心步骤,确保了预测结果与真实标签之间的准确匹配,从而为评估模型性能提供了基础。
# 这段代码定义了 DetectionValidator 类中的 build_dataset 方法,其主要功能是构建用于目标检测任务的数据集。这个方法通过调用外部函数 build_yolo_dataset 来创建数据集,并返回一个数据集对象。
# 定义了 build_dataset 方法,接收以下参数 :
# 1.img_path :图像路径或数据集路径,用于指定数据集的来源。
# 2.mode :数据集的模式,默认为 "val" ,表示验证集。其他可能的值包括 "train" 或 "test" 。
# 3.batch :批次大小,用于指定数据加载时的批量处理数量。如果为 None ,则可能使用默认值。
def build_dataset(self, img_path, mode="val", batch=None):
# 构建 YOLO 数据集。
"""
Build YOLO Dataset.
Args:
img_path (str): Path to the folder containing images.
mode (str): `train` mode or `val` mode, users are able to customize different augmentations for each mode.
batch (int, optional): Size of batches, this is for `rect`. Defaults to None.
"""
# 调用 build_yolo_dataset 函数,构建目标检测数据集。这个函数可能是一个外部定义的工具函数,用于创建适用于 YOLO 模型的数据集。
# self.args :包含数据集配置和验证参数的对象。
# img_path :图像路径或数据集路径。
# batch :批次大小。
# self.data :一个包含数据集相关信息(如类别、路径等)的字典或对象。
# mode=mode :数据集模式(如 "val" 或 "train" )。
# stride=self.stride :模型的步幅(stride),用于调整图像的尺寸和处理方式。
# def build_yolo_dataset(cfg, img_path, batch, data, mode="train", rect=False, stride=32):
# -> 根据给定的配置和参数构建一个用于 YOLO 模型训练或验证的 YOLODataset 实例。返回一个 YOLODataset 实例。
# -> return YOLODataset(img_path=img_path, imgsz=cfg.imgsz, batch_size=batch, augment=mode == "train", hyp=cfg, rect=cfg.rect or rect, cache=cfg.cache or None, single_cls=cfg.single_cls or False, stride=int(stride),
# pad=0.0 if mode == "train" else 0.5, prefix=colorstr(f"{mode}: "), task=cfg.task, classes=cfg.classes, data=data, fraction=cfg.fraction if mode == "train" else 1.0,)
return build_yolo_dataset(self.args, img_path, batch, self.data, mode=mode, stride=self.stride)
# 方法的作用 :
# build_dataset 方法的作用是封装数据集的构建过程,使其能够根据指定的路径、模式和批次大小生成一个适用于目标检测任务的数据集对象。这个方法通常用于在验证或测试阶段加载数据集。
# build_dataset 方法通过调用 build_yolo_dataset 函数,构建并返回一个目标检测数据集对象。它的主要功能包括。参数封装:将数据集路径、模式、批次大小等参数传递给数据集构建函数。数据集创建:生成适用于目标检测任务的数据集对象,便于后续的数据加载和验证。灵活性:支持不同的数据集模式(如验证集或测试集),并允许指定批次大小。这个方法是验证流程中的一个重要环节,确保了数据集的正确加载和处理,为后续的模型验证提供了基础。
# 这段代码定义了 DetectionValidator 类中的 get_dataloader 方法,其主要功能是根据指定的数据集路径和批次大小,构建并返回一个数据加载器( DataLoader )。这个方法通常用于在验证或测试阶段加载数据集。
# 定义了 get_dataloader 方法,接收以下参数 :
# 1.dataset_path :数据集的路径,用于指定验证或测试数据的位置。
# 2.batch_size :数据加载器的批次大小,即每次加载的图像数量。
def get_dataloader(self, dataset_path, batch_size):
# 构造并返回数据加载器。
"""Construct and return dataloader."""
# 调用 self.build_dataset 方法,构建验证数据集对象。 build_dataset 方法会根据指定的路径、模式( mode="val" )和批次大小,生成一个适用于目标检测任务的数据集对象。
dataset = self.build_dataset(dataset_path, batch=batch_size, mode="val")
# 调用 build_dataloader 函数,根据数据集对象 dataset 和其他参数构建 数据加载器 ( DataLoader )。
# dataset :由 self.build_dataset 构建的数据集对象。
# batch_size :每个批次加载的图像数量。
# self.args.workers :数据加载器使用的线程数(或进程数),用于加速数据加载。
# shuffle=False :在验证阶段,通常不需要打乱数据顺序,因此设置为 False 。
# rank=-1 :通常用于分布式训练中的进程编号。在单机验证中, rank 通常设置为 -1 ,表示非分布式模式。
# 最终,该方法返回一个构建好的数据加载器对象,用于在验证或测试阶段加载数据。
# def build_dataloader(dataset, batch, workers, shuffle=True, rank=-1):
# -> 根据给定的参数构建一个用于训练或验证的数据加载器( DataLoader )。这个函数可以返回一个 InfiniteDataLoader 或 DataLoader ,具体取决于是否需要无限迭代数据集。这个函数特别适用于分布式训练和多进程数据加载的场景。返回一个 InfiniteDataLoader 实例。
# -> return InfiniteDataLoader(dataset=dataset, batch_size=batch, shuffle=shuffle and sampler is None, num_workers=nw, sampler=sampler, pin_memory=PIN_MEMORY, collate_fn=getattr(dataset, "collate_fn", None), worker_init_fn=seed_worker, generator=generator,)
return build_dataloader(dataset, batch_size, self.args.workers, shuffle=False, rank=-1) # return dataloader
# 方法的作用 :
# get_dataloader 方法的作用是封装数据加载器的构建过程,使其能够根据指定的数据集路径和批次大小生成一个适用于目标检测验证的数据加载器。这个方法通常用于在验证或测试阶段加载数据集。
# get_dataloader 方法通过以下步骤构建并返回一个数据加载器。构建数据集:调用 self.build_dataset 方法,根据指定路径和模式构建数据集对象。构建数据加载器:调用 build_dataloader 函数,根据数据集对象、批次大小、线程数等参数构建数据加载器。返回数据加载器:返回构建好的数据加载器对象,用于后续的验证或测试。这个方法是验证流程中的一个重要环节,确保了数据能够高效地加载到模型中,为验证过程提供支持。
# 这段代码定义了 DetectionValidator 类中的 plot_val_samples 方法,其主要功能是可视化验证过程中的样本图像,并将带有标签的图像保存到指定路径。这个方法通常用于调试或展示验证数据集的标注情况。
# 定义了 plot_val_samples 方法,接收以下参数 :
# 1.batch :当前批次的数据,通常是一个字典,包含图像、类别标签、边界框等信息。
# 2.ni :当前批次的索引,用于生成保存文件的名称。
def plot_val_samples(self, batch, ni):
# 绘制验证图像样本。
"""Plot validation image samples."""
# 调用 plot_images 函数,绘制并保存带有标签的验证图像。
# def plot_images(images, batch_idx, cls, bboxes=np.zeros(0, dtype=np.float32), confs=None, masks=np.zeros(0, dtype=np.uint8), kpts=np.zeros((0, 51), dtype=np.float32), paths=None, fname="images.jpg", names=None, on_plot=None, max_subplots=16, save=True, conf_thres=0.25,):
# -> 用于绘制图像网格,并在每个图像上标注边界框、类别标签、关键点和掩码。该函数被 @threaded 装饰器修饰,可以根据 threaded 参数决定是否在新线程中运行。如果 save 参数为 False ,则不保存图像,而是将 Annotator 对象中的图像转换为NumPy数组并返回。
# -> return np.asarray(annotator.im)
plot_images(
# 当前批次的图像数据,通常是一个张量,形状为 [batch_size, channels, height, width] 。
batch["img"],
# 每个目标框所属的图像索引,用于将目标框绘制到正确的图像上。
batch["batch_idx"],
# 目标框的类别标签,通过 .squeeze(-1) 去除多余的维度(如果类别标签是二维的,例如 [N, 1] ,则将其压缩为一维 [N] )。
batch["cls"].squeeze(-1),
# 目标框的坐标,格式通常为 [x1, y1, x2, y2] 。
batch["bboxes"],
# 图像的文件路径列表,用于在保存时记录原始图像来源。
paths=batch["im_file"],
# 保存可视化图像的文件名。文件名包含验证批次索引 ni ,格式为 val_batch{ni}_labels.jpg 。
fname=self.save_dir / f"val_batch{ni}_labels.jpg",
# 类别名称列表,用于在图像上显示类别标签。
names=self.names,
# 回调函数,用于在绘图过程中执行自定义操作(例如调整绘图样式或添加额外信息)。
on_plot=self.on_plot,
)
# 方法的作用 :
# plot_val_samples 方法的作用是。可视化验证数据:将验证批次中的图像及其标注(类别标签和边界框)绘制出来。保存可视化结果:将绘制好的图像保存到指定的目录( self.save_dir ),文件名包含批次索引,便于区分不同批次的可视化结果。调试和展示:帮助开发者检查验证数据的标注是否正确,以及模型输入数据的格式是否符合预期。
# plot_val_samples 方法通过调用 plot_images 函数,实现了验证样本的可视化和保存。它在目标检测任务中非常有用,尤其是在调试阶段,可以帮助开发者快速发现数据标注问题或数据预处理中的错误。此外,可视化结果也可以用于展示验证数据集的标注情况,便于进一步分析和优化模型性能。
# 这段代码定义了 DetectionValidator 类中的 plot_predictions 方法,其主要功能是可视化模型的预测结果,并将带有预测框的图像保存到指定路径。这个方法通常用于展示模型在验证集上的表现,帮助开发者直观地评估模型的性能。
# 定义了 plot_predictions 方法,接收以下参数 :
# 1.batch :当前批次的数据,包含图像、类别标签、边界框等信息。
# 2.preds :模型的预测结果,通常是一个张量,包含每个检测框的坐标、置信度和类别信息。
# 3.ni :当前批次的索引,用于生成保存文件的名称。
def plot_predictions(self, batch, preds, ni):
# 在输入图像上绘制预测边界框并保存结果。
"""Plots predicted bounding boxes on input images and saves the result."""
# 调用 plot_images 函数,绘制并保存带有预测框的图像。
plot_images(
# 当前批次的图像数据,通常是一个张量,形状为 [batch_size, channels, height, width] 。
batch["img"],
# output_to_target 是一个函数,用于将模型的预测结果( preds )转换为可视化所需的格式。
# preds 是模型的预测结果,格式通常为 [x1, y1, x2, y2, confidence, class] 。
# max_det=self.args.max_det :限制每个图像中保留的最大检测框数量。
# * :表示解包函数返回的多个值(例如,目标框的类别、坐标等),作为 plot_images 的参数。
# def output_to_target(output, max_det=300):
# -> 将模型的输出转换为一个目标格式,以便于后续处理。返回四个值 : targets[:, 0] 图像索引。 targets[:, 1] 类别。 targets[:, 2:-1] 边界框坐标(转换为XYWH格式)。 targets[:, -1] 置信度。
# -> return targets[:, 0], targets[:, 1], targets[:, 2:-1], targets[:, -1]
*output_to_target(preds, max_det=self.args.max_det),
# 图像的文件路径列表,用于在保存时记录原始图像来源。
paths=batch["im_file"],
# 保存可视化图像的文件名。文件名包含验证批次索引 ni ,格式为 val_batch{ni}_pred.jpg ,表示这是预测结果的可视化。
fname=self.save_dir / f"val_batch{ni}_pred.jpg",
# 类别名称列表,用于在图像上显示类别标签。
names=self.names,
# 回调函数,用于在绘图过程中执行自定义操作(例如调整绘图样式或添加额外信息)。
on_plot=self.on_plot,
) # pred
# 方法的作用 :
# plot_predictions 方法的作用是。将预测结果转换为可视化格式:通过调用 output_to_target 函数,将模型的预测结果转换为适合绘图的格式。绘制带有预测框的图像:使用 plot_images 函数,将预测框绘制到图像上。保存可视化结果:将绘制好的图像保存到指定的目录( self.save_dir ),文件名包含批次索引,便于区分不同批次的可视化结果。直观展示模型性能:帮助开发者直观地评估模型的预测效果,例如检测框的准确性、类别预测的正确性等。
# plot_predictions 方法通过调用 plot_images 函数,实现了模型预测结果的可视化和保存。它在目标检测任务中非常有用,尤其是在调试和评估阶段,可以帮助开发者快速发现模型的问题(如误检、漏检等),并直观地展示模型的性能。
# 这段代码定义了 DetectionValidator 类中的 save_one_txt 方法,其主要功能是将单个图像的预测结果保存为一个文本文件(通常是 .txt 格式)。这种格式常用于目标检测任务,尤其是在保存预测结果以便后续分析或与其他工具(如 COCO API)兼容时。
# 定义了 save_one_txt 方法,接收以下参数 :
# 1.predn :模型的预测结果,格式为 [x1, y1, x2, y2, confidence, class] ,其中坐标已转换为原始图像空间。
# 2.save_conf :布尔值,表示是否保存预测框的置信度。
# 3.shape :原始图像的尺寸,格式为 [height, width] 。
# 4.file :保存预测结果的文件路径。
def save_one_txt(self, predn, save_conf, shape, file):
# 将 YOLO 检测结果以特定格式保存到标准化坐标中的 txt 文件中。
"""Save YOLO detections to a txt file in normalized coordinates in a specific format."""
# 定义归一化增益 gn ,用于将边界框坐标从绝对坐标转换为归一化坐标(范围 [0, 1] )。 shape 是 [height, width] , gn 的值为 [width, height, width, height] ,与边界框坐标 [x1, y1, x2, y2] 对应。
gn = torch.tensor(shape)[[1, 0, 1, 0]] # normalization gain whwh
# 遍历预测结果 predn ,提取每个预测框的信息。
# xyxy :边界框坐标 [x1, y1, x2, y2] 。
# conf :预测框的置信度。
# cls :预测框的类别。
for *xyxy, conf, cls in predn.tolist():
# ops.xyxy2xywh(torch.tensor(xyxy).view(1, 4)) :将边界框坐标从 [x1, y1, x2, y2] 转换为 [x_center, y_center, width, height] 。
# / gn :将边界框坐标归一化到 [0, 1] 范围。
# .view(-1).tolist() :将张量展平并转换为列表。
xywh = (ops.xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
# 根据是否保存置信度( save_conf ),生成保存到文件的行格式。
# 如果 save_conf=True ,则保存类别、归一化边界框坐标和置信度。
# 如果 save_conf=False ,则只保存类别和归一化边界框坐标。
line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
# 将预测结果写入指定文件。
with open(file, "a") as f:
# 使用 "%g " 格式化每个值( %g 表示浮点数或整数,自动去除尾部的零)。
# 使用 rstrip() 去除多余的空格。
# 每个预测框占一行。
f.write(("%g " * len(line)).rstrip() % line + "\n")
# 方法的作用 :
# save_one_txt 方法的作用是。将预测结果转换为归一化坐标:将边界框坐标从绝对坐标转换为归一化坐标(范围 [0, 1] )。生成保存格式:根据配置决定是否保存置信度,并将预测结果格式化为一行文本。保存到文件:将预测结果追加到指定的 .txt 文件中。
# save_one_txt 方法通过将预测结果保存为文本文件,提供了与目标检测任务中常用格式的兼容性。这种格式便于后续分析、评估或与其他工具(如 COCO API)集成。
# 这段代码定义了 DetectionValidator 类中的 pred_to_json 方法,其主要功能是将模型的预测结果转换为 JSON 格式,并将其存储到一个列表中( self.jdict )。这种格式通常用于与标准的目标检测评估工具(如 COCO API)兼容,便于后续计算评估指标(如 mAP)。
# 定义了 pred_to_json 方法,接收以下参数 :
# 1.predn :模型的预测结果,格式为 [x1, y1, x2, y2, confidence, class] ,其中坐标已转换为原始图像空间。
# 2.filename :当前图像的文件名,用于提取图像 ID。
def pred_to_json(self, predn, filename):
# 将 YOLO 预测序列化为 COCO json 格式。
"""Serialize YOLO predictions to COCO json format."""
# 使用 Path 对象提取文件名的“stem”部分,即去掉扩展名后的文件名。例如,对于文件名 "image001.jpg" , stem 的值为 "image001" 。
stem = Path(filename).stem
# 根据文件名的 stem 部分生成图像 ID 。
# 如果 stem 是纯数字( stem.isnumeric() 为 True ),则将其转换为整数。
# 否则,直接使用 stem 作为图像 ID(例如,对于非数字文件名)。
image_id = int(stem) if stem.isnumeric() else stem
# 将预测框的坐标从 [x1, y1, x2, y2] 格式转换为 [x_center, y_center, width, height] 格式,使用 ops.xyxy2xywh 函数。
box = ops.xyxy2xywh(predn[:, :4]) # xywh
# 将边界框的中心点坐标( [x_center, y_center] )转换为左上角坐标( [x1, y1] ),以符合 COCO 格式的要求。
# box[:, :2] 表示中心点坐标 [x_center, y_center] 。
# box[:, 2:] / 2 表示宽度和高度的一半 [width/2, height/2] 。
# 通过减法操作,将中心点坐标转换为左上角坐标。
box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
# 遍历每个预测框的详细信息( p )和对应的边界框坐标( b )。
# predn.tolist() :将预测结果转换为列表格式。
# box.tolist() :将边界框坐标转换为列表格式。
for p, b in zip(predn.tolist(), box.tolist()):
# 将每个预测框的信息以字典格式添加到 self.jdict 列表中。
self.jdict.append(
{
# 当前图像的 ID。
"image_id": image_id,
# 预测框的类别 ID,通过 self.class_map 将模型输出的类别索引映射到目标数据集的类别 ID(例如,COCO 数据集的类别 ID)。
"category_id": self.class_map[int(p[5])],
# 预测框的边界坐标 [x1, y1, width, height] ,坐标值保留 3 位小数。
"bbox": [round(x, 3) for x in b],
# 预测框的置信度,保留 5 位小数。
"score": round(p[4], 5),
}
)
# 方法的作用 :
# pred_to_json 方法的作用是。将预测结果转换为 COCO 格式:将预测框的坐标从 [x1, y1, x2, y2] 转换为 [x1, y1, width, height] ,并确保坐标为左上角格式。映射类别 ID:将模型输出的类别索引转换为目标数据集的类别 ID。生成 JSON 格式的预测信息:将每个预测框的信息存储为一个字典,并将其添加到 self.jdict 列表中。支持后续评估:生成的 JSON 格式数据可以用于与 COCO API 等工具进行评估。
# pred_to_json 方法通过将预测结果转换为 JSON 格式,确保了与目标检测评估工具的兼容性。这种格式便于后续计算评估指标(如 mAP),并支持与其他工具(如 COCO API)的集成。
# 这段代码定义了 DetectionValidator 类中的 eval_json 方法,其主要功能是使用 pycocotools 库对目标检测模型的预测结果进行评估。该方法特别适用于 COCO 数据集,计算评估指标(如 mAP@0.5 和 mAP@0.5:0.95)。
# 定义了 eval_json 方法,接收一个字典作为输入。
# 1.stats :该字典用于存储评估结果。
def eval_json(self, stats):
# 以 JSON 格式评估 YOLO 输出并返回性能统计数据。
"""Evaluates YOLO output in JSON format and returns performance statistics."""
# 检查是否满足以下条件。
# self.args.save_json :是否启用了 JSON 结果保存。
# self.is_coco :是否使用 COCO 数据集。
# len(self.jdict) :是否有预测结果( self.jdict 是一个存储预测结果的列表)。
# 如果满足这些条件,则执行 COCO 评估。
if self.args.save_json and self.is_coco and len(self.jdict):
# 定义 COCO 数据集的 标注文件路径 ( anno_json )和 预测结果文件路径 ( pred_json )。
# anno_json :COCO 数据集的标准标注文件路径。
anno_json = self.data["path"] / "annotations/instances_val2017.json" # annotations
# pred_json :保存模型预测结果的 JSON 文件路径。
pred_json = self.save_dir / "predictions.json" # predictions
# 打印日志信息,说明正在使用指定的标注文件和预测文件进行评估。
LOGGER.info(f"\nEvaluating pycocotools mAP using {pred_json} and {anno_json}...") # 使用 {pred_json} 和 {anno_json} 评估 pycocotools mAP……
# 尝试检查是否安装了 pycocotools 库(版本 >= 2.0.6)。如果未安装,则会抛出异常。
try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
check_requirements("pycocotools>=2.0.6")
# 导入 pycocotools 的相关模块。
# COCO 用于加载 COCO 数据集的标注文件。
from pycocotools.coco import COCO # noqa
# COCOeval 用于评估预测结果。
from pycocotools.cocoeval import COCOeval # noqa
# 检查标注文件和预测文件是否存在。如果文件不存在,则抛出异常。
for x in anno_json, pred_json:
assert x.is_file(), f"{x} file not found" # 未找到 {x} 文件。
# 初始化 COCO API 。
# 使用 COCO 加载标注文件 。
anno = COCO(str(anno_json)) # init annotations api
# 使用 loadRes 方法 加载预测文件 (必须传递字符串路径)。
pred = anno.loadRes(str(pred_json)) # init predictions api (must pass string, not Path)
# 初始化 评估对象 COCOeval ,指定评估任务为边界框检测( "bbox" )。
eval = COCOeval(anno, pred, "bbox")
# 如果使用 COCO 数据集。
if self.is_coco:
# 设置评估的图像 ID 列表。
# self.dataloader.dataset.im_files :包含验证集图像路径的列表。
# 使用 Path(x).stem 提取文件名(去掉扩展名),并将其转换为整数(图像 ID)。
eval.params.imgIds = [int(Path(x).stem) for x in self.dataloader.dataset.im_files] # images to eval
# 执行评估流程。
# 计算每个图像的评估指标。
eval.evaluate()
# 汇总评估结果。
eval.accumulate()
# 打印评估结果(如 mAP@0.5 和 mAP@0.5:0.95)。
eval.summarize()
# 将 评估结果 ( eval.stats )更新到 stats 字典中。
# eval.stats[0] :mAP@0.5:0.95。
# eval.stats[1] :mAP@0.5。
stats[self.metrics.keys[-1]], stats[self.metrics.keys[-2]] = eval.stats[:2] # update mAP50-95 and mAP50
# 捕获任何异常,并打印警告信息。这可能是由于 pycocotools 未安装、文件路径错误或评估过程中出现问题。
except Exception as e:
LOGGER.warning(f"pycocotools unable to run: {e}") # pycocotools 无法运行:{e} 。
# 返回更新后的 stats 字典,包含评估结果。
return stats
# 方法的作用 :
# eval_json 方法的作用是。检查条件:确保启用了 JSON 结果保存、使用了 COCO 数据集,并且有预测结果。加载标注和预测文件:使用 pycocotools 加载 COCO 数据集的标注文件和模型的预测结果。执行评估:使用 COCOeval 计算评估指标(如 mAP@0.5 和 mAP@0.5:0.95)。更新评估结果:将评估指标更新到 stats 字典中。处理异常:捕获并记录评估过程中可能出现的错误。
# eval_json 方法通过调用 pycocotools 的评估工具,实现了对目标检测模型的预测结果进行标准化评估。它特别适用于 COCO 数据集,能够计算常用的评估指标(如 mAP),并支持将评估结果整合到验证流程中。
# DetectionValidator 类是一个用于目标检测任务的验证工具,旨在高效地评估目标检测模型的性能。它通过封装数据预处理、指标计算、结果可视化和标准化评估等功能,为验证过程提供了一站式的解决方案。该类支持多种数据集格式(如 COCO),并能够计算关键指标(如 mAP、精确率、召回率)以量化模型性能。此外,它还提供了丰富的可视化功能(如绘制预测框和混淆矩阵)以及灵活的配置选项(如保存预测结果为 JSON 或 TXT 格式),便于开发者进行调试、分析和展示模型的验证结果。