YOLOv10-1.1部分代码阅读笔记-metrics.py
metrics.py
ultralytics\utils\metrics.py
目录
2.def bbox_ioa(box1, box2, iou=False, eps=1e-7):
3.def box_iou(box1, box2, eps=1e-7):
4.def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
5.def mask_iou(mask1, mask2, eps=1e-7):
6.def kpt_iou(kpt1, kpt2, area, sigma, eps=1e-7):
7.def _get_covariance_matrix(boxes):
8.def probiou(obb1, obb2, CIoU=False, eps=1e-7):
9.def batch_probiou(obb1, obb2, eps=1e-7):
13.def plot_pr_curve(px, py, ap, save_dir=Path("pr_curve.png"), names=(), on_plot=None):
15.def compute_ap(recall, precision):
18.class DetMetrics(SimpleClass):
19.class SegmentMetrics(SimpleClass):
20.class PoseMetrics(SegmentMetrics):
21.class ClassifyMetrics(SimpleClass):
22.class OBBMetrics(SimpleClass):
1.所需的库和模块
# Ultralytics YOLO 🚀, AGPL-3.0 license
"""Model validation metrics."""
import math
import warnings
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import torch
from ultralytics.utils import LOGGER, SimpleClass, TryExcept, plt_settings
# 这段代码定义了一个名为 OKS_SIGMA 的常量,用于目标检测任务中的关键点检测,特别是在计算对象关键点相似性(Object Keypoint Similarity, OKS)时。OKS是一种用于评估关键点检测性能的指标,类似于IoU(Intersection over Union)在边界框检测中的作用。
# OKS_SIGMA的用途 :在关键点检测任务中,OKS用于评估预测的关键点与真实关键点之间的相似性。
OKS_SIGMA = (
# 定义了一个NumPy数组,包含17个元素。这些元素表示不同关键点的sigma值,sigma值用于计算OKS。
np.array([0.26, 0.25, 0.25, 0.35, 0.35, 0.79, 0.79, 0.72, 0.72, 0.62, 0.62, 1.07, 1.07, 0.87, 0.87, 0.89, 0.89])
# 将数组中的每个元素除以10.0,进行归一化处理。归一化后的sigma值通常在0到1之间,表示关键点的相对重要性或标准差。
/ 10.0
)
# OKS_SIGMA 是一个预定义的常量,用于关键点检测任务中计算OKS。这些sigma值表示不同关键点的相对重要性或标准差,通过归一化处理,确保它们在0到1之间。OKS指标用于评估预测的关键点与真实关键点之间的相似性,是关键点检测任务中常用的性能评估指标。
2.def bbox_ioa(box1, box2, iou=False, eps=1e-7):
# 这段代码定义了一个名为 bbox_ioa 的函数,用于计算两个边界框集合之间的交并比(Intersection over Area, IOA)。IOA是交并比(Intersection over Union, IoU)的一个变体,它计算的是交集面积与第二个边界框面积的比值。这个函数可以用于目标检测任务中,特别是在NMS(Non-Maximum Suppression)和数据增强等步骤中。
# 定义了 bbox_ioa 函数,接受四个参数。
# 1.box1 :第一个边界框集合,形状为 [n, 4] ,其中 n 是边界框的数量,每个边界框表示为 [x1, y1, x2, y2] 。
# 2.box2 :第二个边界框集合,形状为 [m, 4] ,其中 m 是边界框的数量,每个边界框表示为 [x1, y1, x2, y2] 。
# 3.iou :布尔值,如果为 True ,则计算IoU;如果为 False ,则计算IOA。默认为 False 。
# 4.eps :一个小的常数,用于避免除以零的错误。默认为 1e-7 。
def bbox_ioa(box1, box2, iou=False, eps=1e-7):
# 给定 box1 和 box2,计算 box2 区域的交集。框采用 x1y1x2y2 格式。
# 参数:
# box1 (np.ndarray):表示 n 个边界框的形状为 (n, 4) 的 numpy 数组。
# box2 (np.ndarray):表示 m 个边界框的形状为 (m, 4) 的 numpy 数组。
# iou (bool):如果为 True,则计算标准 IoU,否则返回 inter_area/box2_area。
# eps (float,可选):一个较小的值,以避免被零除。默认为 1e-7。
# 返回:
# (np.ndarray):表示 box2 区域交集的形状为 (n, m) 的 numpy 数组。
"""
Calculate the intersection over box2 area given box1 and box2. Boxes are in x1y1x2y2 format.
Args:
box1 (np.ndarray): A numpy array of shape (n, 4) representing n bounding boxes.
box2 (np.ndarray): A numpy array of shape (m, 4) representing m bounding boxes.
iou (bool): Calculate the standard IoU if True else return inter_area/box2_area.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-7.
Returns:
(np.ndarray): A numpy array of shape (n, m) representing the intersection over box2 area.
"""
# Get the coordinates of bounding boxes
# 提取边界框坐标。将输入的边界框集合 box1 和 box2 的坐标提取出来,转置操作 T 将形状从 [n, 4] 和 [m, 4] 转换为 [4, n] 和 [4, m] ,以便后续计算。
b1_x1, b1_y1, b1_x2, b1_y2 = box1.T
b2_x1, b2_y1, b2_x2, b2_y2 = box2.T
# Intersection area 计算交集面积。
# 计算两个边界框集合的交集面积。
# np.minimum(b1_x2[:, None], b2_x2) :计算每个边界框对的最小右下角x坐标。
# np.maximum(b1_x1[:, None], b2_x1) :计算每个边界框对的最大左上角x坐标。
# np.minimum(b1_y2[:, None], b2_y2) :计算每个边界框对的最小右下角y坐标。
# np.maximum(b1_y1[:, None], b2_y1) :计算每个边界框对的最大左上角y坐标。
# clip(0) :确保交集面积不为负数。
# * :将x方向和y方向的交集长度相乘,得到交集面积。
# b1_x2[:, None] 和 b2_x2 :将 b1_x2 的形状从 [n] 转换为 [n, 1] ,以便与 b2_x2 的形状 [m] 进行广播操作,得到一个形状为 [n, m] 的张量,表示每个边界框对的右下角x坐标。
# 示例 :
# 假设我们有两个边界框集合 :
# b1_x2 = np.array([10, 20, 30]) # 第一个集合的右下角 x 坐标
# b2_x2 = np.array([15, 25, 35]) # 第二个集合的右下角 x 坐标
# 执行 np.minimum(b1_x2[:, None], b2_x2) 的结果如下 :
# b1_x2[:, None] = np.array([[10], [20], [30]])
# b2_x2 = np.array([15, 25, 35])
# np.minimum(b1_x2[:, None], b2_x2) = np.array([
# [10, 10, 10], # 10 和 [15, 25, 35] 的最小值
# [15, 20, 20], # 20 和 [15, 25, 35] 的最小值
# [15, 25, 30] # 30 和 [15, 25, 35] 的最小值
# ])
# 这行代码通过广播操作和元素-wise操作,计算两个边界框集合的交集面积。这种方法在目标检测任务中非常有用,特别是在NMS和数据增强等步骤中。通过计算交集面积,可以有效地评估预测边界框与真实边界框之间的匹配程度。
inter_area = (np.minimum(b1_x2[:, None], b2_x2) - np.maximum(b1_x1[:, None], b2_x1)).clip(0) * (
np.minimum(b1_y2[:, None], b2_y2) - np.maximum(b1_y1[:, None], b2_y1)
).clip(0)
# Box2 area
# 计算边界框面积。计算第二个边界框集合 box2 的面积。
area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1)
# 如果 iou 为 True ,则计算IoU。
if iou:
# 计算第一个边界框集合 box1 的面积。
box1_area = (b1_x2 - b1_x1) * (b1_y2 - b1_y1)
# 将 box2 的面积和 box1 的面积相加,再减去交集面积,得到并集面积。
area = area + box1_area[:, None] - inter_area
# Intersection over box2 area
# 返回交并比(IOA或IoU)。为了避免除以零的错误,加上一个小的常数 eps 。
return inter_area / (area + eps)
# bbox_ioa 函数的主要作用是计算两个边界框集合之间的交并比(IOA或IoU)。提取边界框的坐标。计算交集面积。计算边界框的面积。根据 iou 参数决定计算IOA还是IoU。返回交并比,避免除以零的错误。这个函数在目标检测任务中非常有用,特别是在NMS和数据增强等步骤中。通过计算交并比,可以有效地评估预测边界框与真实边界框之间的匹配程度。
3.def box_iou(box1, box2, eps=1e-7):
# 这段代码定义了一个名为 box_iou 的函数,用于计算两个边界框集合之间的交并比(Intersection over Union, IoU)。IoU是目标检测任务中常用的指标,用于评估预测边界框与真实边界框之间的匹配程度。
# 定义了 box_iou 函数,接受三个参数。
# 1.box1 :第一个边界框集合,形状为 [n, 4] ,其中 n 是边界框的数量,每个边界框表示为 [x1, y1, x2, y2] 。
# 2.box2 :第二个边界框集合,形状为 [m, 4] ,其中 m 是边界框的数量,每个边界框表示为 [x1, y1, x2, y2] 。
# 3.eps :一个小的常数,用于避免除以零的错误。默认为 1e-7 。
def box_iou(box1, box2, eps=1e-7):
# 计算框的交并比 (IoU)。两组框均应为 (x1, y1, x2, y2) 格式。基于 https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
# 参数:
# box1 (torch.Tensor):表示 N 个边界框的形状为 (N, 4) 的张量。
# box2 (torch.Tensor):表示 M 个边界框的形状为 (M, 4) 的张量。
# eps (float,可选):一个较小的值,以避免被零除。默认为 1e-7。
# 返回:
# (torch.Tensor):一个 NxM 张量,包含 box1 和 box2 中每个元素的成对 IoU 值。
"""
Calculate intersection-over-union (IoU) of boxes. Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
Based on https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
Args:
box1 (torch.Tensor): A tensor of shape (N, 4) representing N bounding boxes.
box2 (torch.Tensor): A tensor of shape (M, 4) representing M bounding boxes.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-7.
Returns:
(torch.Tensor): An NxM tensor containing the pairwise IoU values for every element in box1 and box2.
"""
# inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
# 将两个边界框集合的坐标提取出来,并进行广播操作,以便计算交集面积。
# box1.unsqueeze(1) :在 box1 的第二个维度上添加一个新轴,将形状从 [n, 4] 转换为 [n, 1, 4] 。
# box2.unsqueeze(0) :在 box2 的第一个维度上添加一个新轴,将形状从 [m, 4] 转换为 [1, m, 4] 。
# .chunk(2, 2) :将每个边界框的坐标分成两部分, [x1, y1] 和 [x2, y2] 。
(a1, a2), (b1, b2) = box1.unsqueeze(1).chunk(2, 2), box2.unsqueeze(0).chunk(2, 2)
# 计算两个边界框集合的交集面积。
# torch.min(a2, b2) :计算每个边界框对的最小右下角坐标。
# torch.max(a1, b1) :计算每个边界框对的最大左上角坐标。
# torch.min(a2, b2) - torch.max(a1, b1) :计算交集区域的宽度和高度。
# .clamp_(0) :确保交集区域的宽度和高度不为负数。
# .prod(2) :将宽度和高度相乘,得到交集面积。
inter = (torch.min(a2, b2) - torch.max(a1, b1)).clamp_(0).prod(2)
# IoU = inter / (area1 + area2 - inter)
# 计算IoU。
# (a2 - a1).prod(2) :计算第一个边界框集合的面积。
# (b2 - b1).prod(2) :计算第二个边界框集合的面积。
# ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter) :计算并集面积。
# inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps) :计算IoU,加上一个小的常数 eps ,避免除以零的错误。
return inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps)
# box_iou 函数的主要作用是计算两个边界框集合之间的交并比(IoU)。提取边界框的坐标并进行广播操作。计算交集面积。计算并集面积。计算IoU,避免除以零的错误。这个函数在目标检测任务中非常有用,特别是在NMS(Non-Maximum Suppression)和数据增强等步骤中。通过计算IoU,可以有效地评估预测边界框与真实边界框之间的匹配程度。
4.def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
# 这段代码定义了一个名为 bbox_iou 的函数,用于计算两个边界框之间的IoU(交并比)以及其变体(GIoU、DIoU、CIoU)。
# 定义 bbox_iou 函数,它接受以下参数 :
# 1.box1 和 2.box2 :两个边界框的坐标,可以是形状为 (b, 4) 的张量。
# 3.xywh :布尔值,表示边界框的坐标格式是否为 (x, y, w, h) 。如果为 False ,则坐标格式为 (x1, y1, x2, y2) 。
# 4.GIoU 、 5.DIoU 、 6.CIoU :布尔值,表示是否计算GIoU、DIoU或CIoU。
# 7.eps :一个很小的值,用于防止除零错误。
def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
# 计算 box1(1, 4) 和 box2(n, 4) 的并集交集 (IoU)。
# 参数:
# box1 (torch.Tensor):表示形状为 (1, 4) 的单个边界框的张量。
# box2 (torch.Tensor):表示形状为 (n, 4) 的 n 个边界框的张量。
"""
Calculate Intersection over Union (IoU) of box1(1, 4) to box2(n, 4).
Args:
box1 (torch.Tensor): A tensor representing a single bounding box with shape (1, 4).
box2 (torch.Tensor): A tensor representing n bounding boxes with shape (n, 4).
xywh (bool, optional): If True, input boxes are in (x, y, w, h) format. If False, input boxes are in
(x1, y1, x2, y2) format. Defaults to True.
GIoU (bool, optional): If True, calculate Generalized IoU. Defaults to False.
DIoU (bool, optional): If True, calculate Distance IoU. Defaults to False.
CIoU (bool, optional): If True, calculate Complete IoU. Defaults to False.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-7.
Returns:
(torch.Tensor): IoU, GIoU, DIoU, or CIoU values depending on the specified flags.
"""
# Get the coordinates of bounding boxes 获取边界框的坐标。
# 如果 xywh 为 True ,则将边界框的坐标从 (x, y, w, h) 转换为 (x1, y1, x2, y2) 。
if xywh: # transform from xywh to xyxy
# box1.chunk(4, -1) 和 box2.chunk(4, -1) 将 box1 和 box2 按最后一个维度分成4个部分,分别表示 x 、 y 、 w 和 h 。
(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
# w1 / 2 和 h1 / 2 计算宽度和高度的一半。
w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
# b1_x1, b1_x2, b1_y1, b1_y2 和 b2_x1, b2_x2, b2_y1, b2_y2 计算转换后的边界框坐标。
b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
# 如果 xywh 为 False ,则直接使用 (x1, y1, x2, y2) 格式的坐标。
else: # x1, y1, x2, y2 = box1
# box1.chunk(4, -1) 和 box2.chunk(4, -1) 将 box1 和 box2 按最后一个维度分成4个部分,分别表示 x1 、 y1 、 x2 和 y2 。
b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
# w1, h1 和 w2, h2 计算 宽度 和 高度 。
w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
# Intersection area
# 计算两个边界框的交集面积 inter 。
# b1_x2.minimum(b2_x2) 和 b1_y2.minimum(b2_y2) 计算两个边界框在x和y方向上的最小值。 b1_x1.maximum(b2_x1) 和 b1_y1.maximum(b2_y1) 计算两个边界框在x和y方向上的最大值。 .clamp_(0) 确保交集面积不为负。
inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp_(0) * (
b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)
).clamp_(0)
# Union Area
# 计算两个边界框的并集面积 union 。 w1 * h1 和 w2 * h2 计算两个边界框的面积。 + eps 添加一个小值以防止除零错误。
union = w1 * h1 + w2 * h2 - inter + eps
# IoU
# 计算IoU。
iou = inter / union
# 如果需要计算CIoU、DIoU或GIoU。
if CIoU or DIoU or GIoU:
# 计算包含两个边界框的最小凸包的 宽度 和 高度 。
cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width
ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height
if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
# 计算 凸包的对角线长度的平方 。
c2 = cw.pow(2) + ch.pow(2) + eps # convex diagonal squared
# 计算两个边界框中心点之间的距离的平方。
rho2 = (
(b2_x1 + b2_x2 - b1_x1 - b1_x2).pow(2) + (b2_y1 + b2_y2 - b1_y1 - b1_y2).pow(2)
) / 4 # center dist**2
if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
v = (4 / math.pi**2) * ((w2 / h2).atan() - (w1 / h1).atan()).pow(2)
with torch.no_grad():
alpha = v / (v - iou + (1 + eps))
# 计算CIoU,考虑了边界框的宽高比。
return iou - (rho2 / c2 + v * alpha) # CIoU
# 计算DIoU,考虑了两个边界框中心点之间的距离。
return iou - rho2 / c2 # DIoU
c_area = cw * ch + eps # convex area
# 计算GIoU,考虑了包含两个边界框的最小凸包的面积。
return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf
# 返回IoU。
return iou # IoU
# bbox_iou 函数用于计算两个边界框之间的IoU以及其变体(GIoU、DIoU、CIoU)。 通过考虑不同的因素(如边界框的宽高比、中心点之间的距离、最小凸包的面积),这些变体可以更全面地评估边界框的重叠情况。 这个函数在目标检测任务中非常重要,因为它提供了评估模型性能的指标。
# def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
# -> 用于计算两个边界框之间的IoU(交并比)以及其变体(GIoU、DIoU、CIoU)。计算CIoU,考虑了边界框的宽高比。计算DIoU,考虑了两个边界框中心点之间的距离。计算GIoU,考虑了包含两个边界框的最小凸包的面积。计算IoU。
# -> return iou - (rho2 / c2 + v * alpha) # CIoU
# -> return iou - rho2 / c2 # DIoU
# -> return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf
# -> return iou # IoU
5.def mask_iou(mask1, mask2, eps=1e-7):
# 这段代码定义了一个名为 mask_iou 的函数,用于计算两个掩码集合之间的交并比(Intersection over Union, IoU)。IoU是目标检测任务中常用的指标,用于评估预测掩码与真实掩码之间的匹配程度。
# 定义了 mask_iou 函数,接受三个参数。
# 1.mask1 :第一个掩码集合,形状为 [n, m] ,其中 n 是掩码的数量, m 是每个掩码的像素数量。
# 2.mask2 :第二个掩码集合,形状为 [p, m] ,其中 p 是掩码的数量, m 是每个掩码的像素数量。
# 3.eps :一个小的常数,用于避免除以零的错误。默认为 1e-7 。
def mask_iou(mask1, mask2, eps=1e-7):
# 计算 mask IoU。
# 参数:
# mask1 (torch.Tensor):形状为 (N, n) 的张量,其中 N 是真实对象的数量,n 是图像宽度和高度的乘积。
# mask2 (torch.Tensor):形状为 (M, n) 的张量,其中 M 是预测对象的数量,n 是图像宽度和高度的乘积。
# eps (float,可选):一个较小的值,以避免被零除。默认为 1e-7。
# 返回:
# (torch.Tensor):表示 mask IoU 的形状为 (N, M) 的张量。
"""
Calculate masks IoU.
Args:
mask1 (torch.Tensor): A tensor of shape (N, n) where N is the number of ground truth objects and n is the
product of image width and height.
mask2 (torch.Tensor): A tensor of shape (M, n) where M is the number of predicted objects and n is the
product of image width and height.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-7.
Returns:
(torch.Tensor): A tensor of shape (N, M) representing masks IoU.
"""
# 计算两个掩码集合的交集面积。
# torch.matmul(mask1, mask2.T) :计算 mask1 和 mask2 的矩阵乘法,结果是一个形状为 [n, p] 的张量,表示每个掩码对的交集像素数量。
# .clamp_(0) :确保交集面积不为负数。
intersection = torch.matmul(mask1, mask2.T).clamp_(0)
# 计算两个掩码集合的并集面积。
# mask1.sum(1) :计算 mask1 中每个掩码的像素数量,结果是一个形状为 [n] 的张量。
# mask2.sum(1) :计算 mask2 中每个掩码的像素数量,结果是一个形状为 [p] 的张量。
# mask1.sum(1)[:, None] :将 mask1.sum(1) 的形状从 [n] 转换为 [n, 1] 。
# mask2.sum(1)[None] :将 mask2.sum(1) 的形状从 [p] 转换为 [1, p] 。
# (mask1.sum(1)[:, None] + mask2.sum(1)[None]) :计算两个掩码集合的总面积。
# union = (mask1.sum(1)[:, None] + mask2.sum(1)[None]) - intersection :计算并集面积,即总面积减去交集面积。
union = (mask1.sum(1)[:, None] + mask2.sum(1)[None]) - intersection # (area1 + area2) - intersection
# 计算IoU。计算交集面积与并集面积的比值,加上一个小的常数 eps ,避免除以零的错误。
return intersection / (union + eps)
# mask_iou 函数的主要作用是计算两个掩码集合之间的交并比(IoU)。计算交集面积。计算并集面积。计算IoU,避免除以零的错误。这个函数在目标检测任务中非常有用,特别是在实例分割和语义分割等任务中。通过计算IoU,可以有效地评估预测掩码与真实掩码之间的匹配程度。
6.def kpt_iou(kpt1, kpt2, area, sigma, eps=1e-7):
# 这段代码定义了一个名为 kpt_iou 的函数,用于计算两个关键点集合之间的对象关键点相似性(Object Keypoint Similarity, OKS)。OKS是目标检测任务中用于评估关键点检测性能的指标,类似于IoU在边界框检测中的作用。
# 定义了 kpt_iou 函数,接受五个参数。
# 1.kpt1 :第一个关键点集合,形状为 [N, 17, 3] ,其中 N 是关键点的数量,17是关键点的数量,3表示每个关键点的 (x, y, v) ,其中 v 表示关键点的可见性。
# 2.kpt2 :第二个关键点集合,形状为 [M, 17, 3] ,其中 M 是关键点的数量,17是关键点的数量,3表示每个关键点的 (x, y, v) 。
# 3.area :边界框的面积,形状为 [N] ,表示每个关键点集合的边界框面积。
# 4.sigma :关键点的标准差,形状为 [17] ,表示每个关键点的标准差。
# 5.eps :一个小的常数,用于避免除以零的错误。默认为 1e-7 。
def kpt_iou(kpt1, kpt2, area, sigma, eps=1e-7):
# 计算对象关键点相似度 (OKS)。
# 参数:
# kpt1 (torch.Tensor):表示地面实况关键点的形状为 (N, 17, 3) 的张量。
# kpt2 (torch.Tensor):表示预测关键点的形状为 (M, 17, 3) 的张量。
# area (torch.Tensor):表示地面实况区域的形状为 (N,) 的张量。
# sigma (列表):包含 17 个表示关键点尺度的值的列表。
# eps (浮点数,可选):一个较小的值,以避免被零除。默认为 1e-7。
# 返回:
# (torch.Tensor):表示关键点相似度的形状为 (N, M) 的张量。
"""
Calculate Object Keypoint Similarity (OKS).
Args:
kpt1 (torch.Tensor): A tensor of shape (N, 17, 3) representing ground truth keypoints.
kpt2 (torch.Tensor): A tensor of shape (M, 17, 3) representing predicted keypoints.
area (torch.Tensor): A tensor of shape (N,) representing areas from ground truth.
sigma (list): A list containing 17 values representing keypoint scales.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-7.
Returns:
(torch.Tensor): A tensor of shape (N, M) representing keypoint similarities.
"""
# 计算两个关键点集合之间的欧氏距离平方。
# kpt1[:, None, :, 0] :将 kpt1 的形状从 [N, 17, 3] 转换为 [N, 1, 17] ,提取x坐标。
# kpt2[..., 0] :提取 kpt2 的x坐标,形状为 [M, 17] 。
# kpt1[:, None, :, 1] :将 kpt1 的形状从 [N, 17, 3] 转换为 [N, 1, 17] ,提取y坐标。
# kpt2[..., 1] :提取 kpt2 的y坐标,形状为 [M, 17] 。
# d :计算每个关键点对的欧氏距离平方,结果形状为 [N, M, 17] 。
d = (kpt1[:, None, :, 0] - kpt2[..., 0]).pow(2) + (kpt1[:, None, :, 1] - kpt2[..., 1]).pow(2) # (N, M, 17)
# 将标准差 sigma 转换为与 kpt1 相同设备和数据类型的张量,形状为 [17] 。
sigma = torch.tensor(sigma, device=kpt1.device, dtype=kpt1.dtype) # (17, )
# 计算关键点的可见性掩码,形状为 [N, 17] ,表示每个关键点是否可见(1表示可见,0表示不可见)。
kpt_mask = kpt1[..., 2] != 0 # (N, 17)
# 计算OKS的指数部分。
# d / (2 * sigma).pow(2) :计算每个关键点对的欧氏距离平方除以两倍标准差的平方。
# area[:, None, None] :将 area 的形状从 [N] 转换为 [N, 1, 1] 。
# e :计算每个关键点对的指数部分,结果形状为 [N, M, 17] 。
e = d / (2 * sigma).pow(2) / (area[:, None, None] + eps) / 2 # from cocoeval
# e = d / ((area[None, :, None] + eps) * sigma) ** 2 / 2 # from formula
# 计算OKS。
# (-e).exp() :计算指数部分的负指数,结果形状为 [N, M, 17] 。
# kpt_mask[:, None] :将 kpt_mask 的形状从 [N, 17] 转换为 [N, 1, 17] 。
# (-e).exp() * kpt_mask[:, None] :将指数部分与可见性掩码相乘,结果形状为 [N, M, 17] 。
# .sum(-1) :对每个关键点对的OKS求和,结果形状为 [N, M] 。
# kpt_mask.sum(-1)[:, None] :计算每个关键点集合的可见关键点数量,结果形状为 [N, 1] 。
# kpt_mask.sum(-1)[:, None] + eps :避免除以零的错误。
# (((-e).exp() * kpt_mask[:, None]).sum(-1) / (kpt_mask.sum(-1)[:, None] + eps)) :计算最终的OKS,结果形状为 [N, M] 。
return ((-e).exp() * kpt_mask[:, None]).sum(-1) / (kpt_mask.sum(-1)[:, None] + eps)
# kpt_iou 函数的主要作用是计算两个关键点集合之间的对象关键点相似性(OKS)。计算关键点之间的欧氏距离平方。计算标准差。计算关键点的可见性掩码。计算OKS的指数部分。计算最终的OKS,避免除以零的错误。这个函数在关键点检测任务中非常有用,特别是在评估预测关键点与真实关键点之间的匹配程度时。通过计算OKS,可以有效地评估关键点检测的性能。
7.def _get_covariance_matrix(boxes):
# 这段代码定义了一个名为 _get_covariance_matrix 的函数,用于计算旋转边界框的协方差矩阵元素。协方差矩阵在概率交并比(ProbIoU)的计算中起着重要作用,因为它描述了旋转边界框的形状和方向。
# 定义了 _get_covariance_matrix 函数,它接受一个参数。
# 1.boxes :表示旋转边界框的张量。通常, boxes 包含每个框的中心坐标 (x, y) 、宽度 w 、高度 h 和旋转角度 theta 。
def _get_covariance_matrix(boxes):
# 从 obbs 生成协方差矩阵。
# 参数:
# boxes (torch.Tensor):表示旋转边界框的形状为 (N, 5) 的张量,具有 xywhr 格式。
"""
Generating covariance matrix from obbs.
Args:
boxes (torch.Tensor): A tensor of shape (N, 5) representing rotated bounding boxes, with xywhr format.
Returns:
(torch.Tensor): Covariance metrixs corresponding to original rotated bounding boxes.
"""
# Gaussian bounding boxes, ignore the center points (the first two columns) because they are not needed here. 高斯边界框,忽略中心点(前两列),因为这里不需要它们。
# 计算宽度和高度的平方,然后除以 12。这是因为在统计学中,对于一个均匀分布的矩形,其方差可以通过宽度和高度的平方除以 12 来计算。
# 将计算得到的方差与旋转角度 theta 拼接在一起,形成一个新的张量 gbbs 。
gbbs = torch.cat((boxes[:, 2:4].pow(2) / 12, boxes[:, 4:]), dim=-1)
# 将 gbbs 分割成三个单独的张量 a 、 b 和 c ,分别表示协方差矩阵的 元素 和 旋转角度 。
a, b, c = gbbs.split(1, dim=-1)
# 分别计算旋转角度 theta 的余弦值和正弦值。
cos = c.cos()
sin = c.sin()
# 分别计算余弦值和正弦值的平方。
cos2 = cos.pow(2)
sin2 = sin.pow(2)
# 计算协方差矩阵元素。
# a * cos2 + b * sin2 计算协方差矩阵的第一个元素,表示旋转后的宽度方差。
# a * sin2 + b * cos2 计算协方差矩阵的第二个元素,表示旋转后的高度方差。
# (a - b) * cos * sin 计算协方差矩阵的第三个元素,表示旋转后的协方差项。
return a * cos2 + b * sin2, a * sin2 + b * cos2, (a - b) * cos * sin
# _get_covariance_matrix 函数通过计算旋转边界框的协方差矩阵元素,为概率交并比(ProbIoU)的计算提供了必要的信息。协方差矩阵描述了旋转边界框的形状和方向,使得 ProbIoU 能够更准确地衡量两个旋转边界框之间的重叠程度。这种计算方法在旋转目标检测任务中非常有用,能够帮助模型更好地学习旋转边界框的相对位置和大小,从而提高旋转目标检测的准确性。
8.def probiou(obb1, obb2, CIoU=False, eps=1e-7):
# 这段代码定义了一个名为 probiou 的函数,用于计算两个旋转边界框(oriented bounding boxes,简称 OBBs)之间的概率交并比(Probability Intersection over Union,简称 ProbIoU)。ProbIoU 是一种用于旋转目标检测任务的 IoU 计算方法,能够更好地处理旋转边界框的重叠问题。
# 定义了 probiou 函数,它接受以下参数 :
# 1.obb1 和 2.obb2 :两个旋转边界框的张量,通常包含每个框的中心坐标 (x, y) 、宽度 w 、高度 h 和旋转角度。
# 3.CIoU :一个布尔值,表示是否计算完整交并比(Complete Intersection over Union),默认为 False 。
# 4.eps :一个很小的数值,用于防止除零错误,默认为 1e-7 。
def probiou(obb1, obb2, CIoU=False, eps=1e-7):
# 计算定向边界框之间的概率 IoU,https://arxiv.org/pdf/2106.06072v1.pdf。
# 参数:
# obb1 (torch.Tensor):一个形状为 (N, 5) 的张量,表示真实 obbs,采用 xywhr 格式。
# obb2 (torch.Tensor):一个形状为 (N, 5) 的张量,表示预测 obbs,采用 xywhr 格式。
# eps (float,可选):一个较小的值,以避免被零除。默认为 1e-7。
# 返回:
# (torch.Tensor):一个形状为 (N, ) 的张量,表示 obb 相似性。
"""
Calculate the prob IoU between oriented bounding boxes, https://arxiv.org/pdf/2106.06072v1.pdf.
Args:
obb1 (torch.Tensor): A tensor of shape (N, 5) representing ground truth obbs, with xywhr format.
obb2 (torch.Tensor): A tensor of shape (N, 5) representing predicted obbs, with xywhr format.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-7.
Returns:
(torch.Tensor): A tensor of shape (N, ) representing obb similarities.
"""
# 分别将 obb1 和 obb2 的中心坐标 (x, y) 分割成两个单独的张量 x1 、 y1 和 x2 、 y2 。
x1, y1 = obb1[..., :2].split(1, dim=-1)
x2, y2 = obb2[..., :2].split(1, dim=-1)
# 分别计算两个旋转边界框的协方差矩阵的元素 a 、 b 和 c 。这些元素用于后续的概率交并比计算。
a1, b1, c1 = _get_covariance_matrix(obb1)
a2, b2, c2 = _get_covariance_matrix(obb2)
# 这部分代码计算了概率交并比(ProbIoU)。
# t1 、 t2 和 t3 是计算概率交并比的中间步骤,涉及到协方差矩阵的元素和边界框中心坐标的差值。
t1 = (
((a1 + a2) * (y1 - y2).pow(2) + (b1 + b2) * (x1 - x2).pow(2)) / ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2) + eps)
) * 0.25
t2 = (((c1 + c2) * (x2 - x1) * (y1 - y2)) / ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2) + eps)) * 0.5
t3 = (
((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2))
/ (4 * ((a1 * b1 - c1.pow(2)).clamp_(0) * (a2 * b2 - c2.pow(2)).clamp_(0)).sqrt() + eps)
+ eps
).log() * 0.5
# 计算边界距离 bd ,并使用 clamp 函数限制其范围,以防止数值不稳定。
bd = (t1 + t2 + t3).clamp(eps, 100.0)
# 计算汉明距离 hd 。
hd = (1.0 - (-bd).exp() + eps).sqrt()
# 最终计算概率交并比 iou 。
iou = 1 - hd
# 如果 CIoU 为 True ,则计算完整交并比(CIoU)。
if CIoU: # only include the wh aspect ratio part
# 分别将 obb1 和 obb2 的 宽度 和 高度 分割出来。
w1, h1 = obb1[..., 2:4].split(1, dim=-1)
w2, h2 = obb2[..., 2:4].split(1, dim=-1)
# 计算宽高比的惩罚项。
v = (4 / math.pi**2) * ((w2 / h2).atan() - (w1 / h1).atan()).pow(2)
with torch.no_grad():
# 计算惩罚系数 alpha 。
alpha = v / (v - iou + (1 + eps))
# 返回完整交并比(CIoU)。
return iou - v * alpha # CIoU
# 如果 CIoU 为 False ,则直接返回概率交并比 iou 。
return iou
# probiou 函数通过计算旋转边界框之间的概率交并比(ProbIoU),提供了一种更准确的旋转边界框重叠度量方法。当 CIoU 参数为 True 时,还可以计算完整交并比(CIoU),进一步考虑了宽高比的影响。这种损失函数在旋转目标检测任务中非常有用,能够帮助模型更好地学习旋转边界框的相对位置和大小,从而提高旋转目标检测的准确性。
9.def batch_probiou(obb1, obb2, eps=1e-7):
# 这段代码定义了一个名为 batch_probiou 的函数,用于计算两个定向边界框(OBB)集合之间的概率交并比(ProbIoU)。ProbIoU是一种用于评估定向边界框检测性能的指标,类似于IoU在边界框检测中的作用。
# 定义了 batch_probiou 函数,接受三个参数。
# 1.obb1 :第一个定向边界框集合,形状为 [N, 5] ,其中 N 是边界框的数量,每个边界框表示为 [x, y, w, h, theta] 。
# 2.obb2 :第二个定向边界框集合,形状为 [M, 5] ,其中 M 是边界框的数量,每个边界框表示为 [x, y, w, h, theta] 。
# 3.eps :一个小的常数,用于避免除以零的错误。默认为 1e-7 。
def batch_probiou(obb1, obb2, eps=1e-7):
# 计算定向边界框之间的概率 IoU,https://arxiv.org/pdf/2106.06072v1.pdf。
# 参数:
# obb1 (torch.Tensor | np.ndarray):表示真实 obbs 的形状为 (N, 5) 的张量,采用 xywhr 格式。
# obb2 (torch.Tensor | np.ndarray):表示预测 obbs 的形状为 (M, 5) 的张量,采用 xywhr 格式。
# eps (float,可选):一个较小的值,以避免被零除。默认为 1e-7。
# 返回:
# (torch.Tensor):表示 obb 相似性的形状为 (N, M) 的张量。
"""
Calculate the prob IoU between oriented bounding boxes, https://arxiv.org/pdf/2106.06072v1.pdf.
Args:
obb1 (torch.Tensor | np.ndarray): A tensor of shape (N, 5) representing ground truth obbs, with xywhr format.
obb2 (torch.Tensor | np.ndarray): A tensor of shape (M, 5) representing predicted obbs, with xywhr format.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-7.
Returns:
(torch.Tensor): A tensor of shape (N, M) representing obb similarities.
"""
# 将输入的 定向边界框集 合转换为PyTorch张量,如果它们是NumPy数组。
obb1 = torch.from_numpy(obb1) if isinstance(obb1, np.ndarray) else obb1
obb2 = torch.from_numpy(obb2) if isinstance(obb2, np.ndarray) else obb2
# 提取两个定向边界框集合的 中心点坐标 。
# obb1[..., :2].split(1, dim=-1) :将 obb1 的中心点坐标 (x, y) 分开,结果为两个形状为 [N, 1] 的张量。
x1, y1 = obb1[..., :2].split(1, dim=-1)
# obb2[..., :2].split(1, dim=-1) :将 obb2 的中心点坐标 (x, y) 分开,结果为两个形状为 [M, 1] 的张量。
# x.squeeze(-1)[None] :将形状从 [M, 1] 转换为 [1, M] ,以便进行广播操作。
x2, y2 = (x.squeeze(-1)[None] for x in obb2[..., :2].split(1, dim=-1))
# 计算两个定向边界框集合的 协方差矩阵 。
# _get_covariance_matrix(obb1) :计算 obb1 的协方差矩阵,结果为三个形状为 [N, 1] 的张量 (a1, b1, c1) 。
a1, b1, c1 = _get_covariance_matrix(obb1)
# _get_covariance_matrix(obb2) :计算 obb2 的协方差矩阵,结果为三个形状为 [M, 1] 的张量 (a2, b2, c2) 。
# x.squeeze(-1)[None] :将形状从 [M, 1] 转换为 [1, M] ,以便进行广播操作。
a2, b2, c2 = (x.squeeze(-1)[None] for x in _get_covariance_matrix(obb2))
# 计算 概率交并比 的各个部分。
# t1 :计算第一个部分,涉及 中心点坐标的差异 和 协方差矩阵的和 。
t1 = (
((a1 + a2) * (y1 - y2).pow(2) + (b1 + b2) * (x1 - x2).pow(2)) / ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2) + eps)
) * 0.25
# t2 :计算第二个部分,涉及 中心点坐标的差异 和 协方差矩阵的和 。
t2 = (((c1 + c2) * (x2 - x1) * (y1 - y2)) / ((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2) + eps)) * 0.5
# t3 :计算第三个部分,涉及 协方差矩阵的和和差 。
t3 = (
((a1 + a2) * (b1 + b2) - (c1 + c2).pow(2))
/ (4 * ((a1 * b1 - c1.pow(2)).clamp_(0) * (a2 * b2 - c2.pow(2)).clamp_(0)).sqrt() + eps)
+ eps
).log() * 0.5
# 计算最终的ProbIoU。
# 将三个部分相加,结果形状为 [N, M] ,并使用 clamp 函数限制在 [eps, 100.0] 范围内。
bd = (t1 + t2 + t3).clamp(eps, 100.0)
# 计算 1.0 - (-bd).exp() ,结果形状为 [N, M] ,并使用 sqrt 函数计算平方根。
hd = (1.0 - (-bd).exp() + eps).sqrt()
# 返回最终的ProbIoU,结果形状为 [N, M] 。
return 1 - hd
# batch_probiou 函数的主要作用是计算两个定向边界框集合之间的概率交并比(ProbIoU)。转换输入为PyTorch张量。提取中心点坐标。计算协方差矩阵。计算概率交并比的各个部分。计算最终的ProbIoU,避免除以零的错误。这个函数在定向边界框检测任务中非常有用,特别是在评估预测定向边界框与真实定向边界框之间的匹配程度时。通过计算ProbIoU,可以有效地评估定向边界框检测的性能。
10.def smooth_BCE(eps=0.1):
# 这段代码定义了一个名为 smooth_BCE 的函数,用于生成平滑的二元交叉熵(Binary Cross-Entropy, BCE)损失的标签。平滑的BCE损失通过在真实标签中引入小的噪声,可以提高模型的泛化能力,减少过拟合。
# 定义了 smooth_BCE 函数,接受一个参数。
# 1.eps :一个小的常数,用于控制标签平滑的程度。默认为 0.1 。
def smooth_BCE(eps=0.1):
# 计算平滑的正负二元交叉熵目标。
# 此函数根据给定的 epsilon 值计算正负标签平滑 BCE 目标。
"""
Computes smoothed positive and negative Binary Cross-Entropy targets.
This function calculates positive and negative label smoothing BCE targets based on a given epsilon value.
For implementation details, refer to https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441.
Args:
eps (float, optional): The epsilon value for label smoothing. Defaults to 0.1.
Returns:
(tuple): A tuple containing the positive and negative label smoothing BCE targets.
"""
# 计算平滑的BCE损失的标签。
# 1.0 - 0.5 * eps :计算正类标签的平滑值。例如,如果 eps=0.1 ,则正类标签的平滑值为 0.95 。
# 0.5 * eps :计算负类标签的平滑值。例如,如果 eps=0.1 ,则负类标签的平滑值为 0.05 。
return 1.0 - 0.5 * eps, 0.5 * eps
# smooth_BCE 函数的主要作用是生成平滑的BCE损失的标签。计算正类标签的平滑值。计算负类标签的平滑值。通过引入小的噪声,平滑的BCE损失可以提高模型的泛化能力,减少过拟合。这个函数在二分类任务中非常有用,特别是在训练深度学习模型时。通过使用平滑的标签,可以使得模型在训练过程中更加稳定,避免对训练数据的过度拟合。
11.class ConfusionMatrix:
# 这段代码定义了一个名为 ConfusionMatrix 的类,用于计算和处理混淆矩阵,特别是在目标检测和分类任务中。混淆矩阵是一个非常有用的工具,用于评估模型的性能,特别是分类的准确性和错误类型。
class ConfusionMatrix:
# 用于计算和更新对象检测和分类任务的混淆矩阵的类。
"""
A class for calculating and updating a confusion matrix for object detection and classification tasks.
Attributes:
task (str): The type of task, either 'detect' or 'classify'.
matrix (np.ndarray): The confusion matrix, with dimensions depending on the task.
nc (int): The number of classes.
conf (float): The confidence threshold for detections.
iou_thres (float): The Intersection over Union threshold.
"""
# 这段代码是 ConfusionMatrix 类的初始化方法 __init__ 的定义,用于初始化混淆矩阵的相关属性。
# 定义了 ConfusionMatrix 类的初始化方法 __init__ ,接受四个参数。
# 1.nc :类别数量(number of classes)。
# 2.conf :置信度阈值,用于过滤低置信度的检测结果。默认为 0.25 。
# 3.iou_thres :IoU阈值,用于确定检测结果是否为真阳性(true positive)。默认为 0.45 。
# 4.task :任务类型,可以是 "detect" (目标检测)或 "classify" (分类)。默认为 "detect" 。
def __init__(self, nc, conf=0.25, iou_thres=0.45, task="detect"):
# 初始化 YOLO 模型的属性。
"""Initialize attributes for the YOLO model."""
# 将任务类型 task 赋值给类的属性 self.task 。
self.task = task
# 根据任务类型初始化混淆矩阵 self.matrix 。如果任务类型为 "detect" ,则初始化一个形状为 (nc + 1, nc + 1) 的零矩阵。额外的一行和一列用于表示背景类(false positives 和 false negatives)。 如果任务类型为 "classify" ,则初始化一个形状为 (nc, nc) 的零矩阵。
self.matrix = np.zeros((nc + 1, nc + 1)) if self.task == "detect" else np.zeros((nc, nc))
# 将类别数量 nc 赋值给类的属性 self.nc 。
self.nc = nc # number of classes
# 设置置信度阈值 self.conf 。如果 conf 为 None 或 0.001 ,则使用默认值 0.25 。 否则,使用传入的 conf 值。
self.conf = 0.25 if conf in (None, 0.001) else conf # apply 0.25 if default val conf is passed
# 将IoU阈值 iou_thres 赋值给类的属性 self.iou_thres 。
self.iou_thres = iou_thres
# ConfusionMatrix 类的初始化方法 __init__ 的主要作用是初始化混淆矩阵的相关属性,包括任务类型、混淆矩阵、类别数量、置信度阈值和IoU阈值。这些属性在后续的方法中用于计算和更新混淆矩阵,评估模型的性能。通过这些初始化步骤,类的实例可以灵活地处理不同任务类型(目标检测或分类)的混淆矩阵计算。
# 这段代码定义了 ConfusionMatrix 类中的 process_cls_preds 方法,用于更新分类任务的混淆矩阵。这个方法接受预测结果和真实标签,然后根据这些信息更新混淆矩阵。
# 定义了 process_cls_preds 方法,接受两个参数。
# 1.preds :预测的类别标签,形状为 [N, min(nc, 5)] ,其中 N 是样本数量, nc 是类别数量。每个样本的预测结果是一个类别标签。
# 2.targets :真实类别标签,形状为 [N, 1] ,每个样本的真实类别标签。
def process_cls_preds(self, preds, targets):
# 更新分类任务的混淆矩阵。
# 参数:
# preds (Array[N, min(nc,5)]):预测的类标签。
# target (Array[N, 1]):真实类标签。
"""
Update confusion matrix for classification task.
Args:
preds (Array[N, min(nc,5)]): Predicted class labels.
targets (Array[N, 1]): Ground truth class labels.
"""
# 将预测结果和真实标签拼接成一个张量,并提取 每个样本的预测类别标签 和 真实类别标签 。
# torch.cat(preds)[:, 0] :将预测结果拼接成一个一维张量,并提取每个样本的预测类别标签。
# torch.cat(targets) :将真实标签拼接成一个一维张量。
preds, targets = torch.cat(preds)[:, 0], torch.cat(targets)
# 遍历每个样本的 预测类别标签 p 和 真实类别标签 t ,并更新混淆矩阵 self.matrix 。
# preds.cpu().numpy() :将预测类别标签转换为NumPy数组,并移动到CPU。
# targets.cpu().numpy() :将真实类别标签转换为NumPy数组,并移动到CPU。
for p, t in zip(preds.cpu().numpy(), targets.cpu().numpy()):
# 在混淆矩阵中,将预测类别 p 和真实类别 t 对应的单元格值加1。
self.matrix[p][t] += 1
# process_cls_preds 方法的主要作用是更新分类任务的混淆矩阵。拼接预测结果和真实标签,提取每个样本的预测类别标签和真实类别标签。遍历每个样本的预测类别标签和真实类别标签,更新混淆矩阵。通过这些步骤,混淆矩阵可以记录每个类别的预测和真实标签的匹配情况,从而评估模型的分类性能。这个方法在分类任务中非常有用,特别是在评估模型的准确性和错误类型时。
# 这段代码定义了 ConfusionMatrix 类中的 process_batch 方法,用于更新目标检测任务的混淆矩阵。这个方法接受检测结果、真实边界框和真实类别标签,然后根据这些信息更新混淆矩阵。
# 定义了 process_batch 方法,接受三个参数。
# 1.detections :检测结果,形状为 [N, 6] 或 [N, 7] ,其中 N 是检测结果的数量。每行包含 (x1, y1, x2, y2, conf, class) ,如果是定向边界框(OBB),则包含一个额外的 angle 维度。
# 2.gt_bboxes :真实边界框,形状为 [M, 4] 或 [M, 5] ,其中 M 是真实边界框的数量。每行包含 (x1, y1, x2, y2) ,如果是定向边界框(OBB),则包含一个额外的 angle 维度。
# 3.gt_cls :真实类别标签,形状为 [M] ,每个元素表示一个真实边界框的类别标签。
def process_batch(self, detections, gt_bboxes, gt_cls):
# 更新对象检测任务的混淆矩阵。
# 参数:
# detections (Array[N, 6] | Array[N, 7]):检测到的边界框及其相关信息。每行应包含 (x1, y1, x2, y2, conf, class) 或附加元素 `angle`(如果是 obb)。
# gt_bboxes (Array[M, 4]| Array[N, 5]):具有 xyxy/xyxyr 格式的地面实况边界框。
# gt_cls (Array[M]):类标签。
"""
Update confusion matrix for object detection task.
Args:
detections (Array[N, 6] | Array[N, 7]): Detected bounding boxes and their associated information.
Each row should contain (x1, y1, x2, y2, conf, class)
or with an additional element `angle` when it's obb.
gt_bboxes (Array[M, 4]| Array[N, 5]): Ground truth bounding boxes with xyxy/xyxyr format.
gt_cls (Array[M]): The class labels.
"""
# 这段代码处理了两种特殊情况,真实标签为空和检测结果为空。
# 检查 真实类别标签 gt_cls 是否为空。如果 gt_cls 的形状的第一个维度为0,表示没有真实标签。
if gt_cls.shape[0] == 0: # Check if labels is empty
# 如果 检测结果 detections 不为空,则继续处理。
if detections is not None:
# 过滤掉置信度低于 self.conf 的检测结果。 detections[:, 4] 表示 检测结果的置信度 , self.conf 是 置信度阈值 。
detections = detections[detections[:, 4] > self.conf]
# 提取 检测结果的类别标签 , detections[:, 5] 表示 检测结果的类别标签 , int() 确保类别标签为整数。
detection_classes = detections[:, 5].int()
# 遍历 每个检测结果的类别标签 dc ,在混淆矩阵中将 预测类别 dc 和 背景类别 self.nc 对应的单元格值加1,表示 假阳性 (false positives)。
for dc in detection_classes:
self.matrix[dc, self.nc] += 1 # false positives
# 返回,结束方法的执行。
return
# 检查 检测结果 detections 是否为空。如果 detections 为空,则继续处理。
if detections is None:
# 提取 真实类别标签 , gt_cls.int() 确保类别标签为整数。
gt_classes = gt_cls.int()
# 遍历 每个真实类别标签 gc ,在混淆矩阵中将 背景类别 self.nc 和 真实类别 gc 对应的单元格值加1,表示 背景假阴性 (false negatives)。
for gc in gt_classes:
self.matrix[self.nc, gc] += 1 # background FN
# 返回,结束方法的执行。
return
# 这段代码的主要作用是处理两种特殊情况,真实标签为空 :如果真实标签为空,但检测结果不为空,则将检测结果视为假阳性(false positives),更新混淆矩阵。检测结果为空 :如果检测结果为空,但真实标签不为空,则将真实标签视为背景假阴性(false negatives),更新混淆矩阵。通过这些步骤,混淆矩阵可以正确记录在特殊情况下模型的预测和真实标签的匹配情况,从而更全面地评估模型的性能。
# 这段代码处理检测结果和真实边界框,计算它们之间的IoU(交并比),并根据检测结果是否为定向边界框(OBB)选择合适的IoU计算方法。
# 过滤掉 置信度 低于 self.conf 的检测结果。 detections[:, 4] 表示检测结果的置信度, self.conf 是置信度阈值。这一步确保只有高置信度的检测结果被用于后续的计算。
detections = detections[detections[:, 4] > self.conf]
# 提取 真实类别标签 ,并确保它们为整数类型。 gt_cls.int() 将真实类别标签转换为整数。
gt_classes = gt_cls.int()
# 提取 检测结果的类别标签 ,并确保它们为整数类型。 detections[:, 5] 表示 检测结果的类别标签 , int() 确保类别标签为整数。
detection_classes = detections[:, 5].int()
# 判断检测结果和真实边界框是否为定向边界框(OBB)。
# detections.shape[1] == 7 :检测结果包含7个元素,即 (x1, y1, x2, y2, conf, class, angle) 。
# gt_bboxes.shape[1] == 5 :真实边界框包含5个元素,即 (x1, y1, x2, y2, angle) 。
is_obb = detections.shape[1] == 7 and gt_bboxes.shape[1] == 5 # with additional `angle` dimension
# 根据是否为定向边界框(OBB),选择合适的IoU计算方法
iou = (
# 如果是OBB,使用 batch_probiou 函数计算IoU。 torch.cat([detections[:, :4], detections[:, -1:]], dim=-1) 将检测结果的前4个元素(边界框坐标)和最后一个元素(角度)拼接在一起,形成形状为 [N, 5] 的张量。
batch_probiou(gt_bboxes, torch.cat([detections[:, :4], detections[:, -1:]], dim=-1))
if is_obb
# 如果不是OBB,使用 box_iou 函数计算IoU。 detections[:, :4] 提取检测结果的前4个元素(边界框坐标),形成形状为 [N, 4] 的张量。
else box_iou(gt_bboxes, detections[:, :4])
)
# 这段代码过滤掉低置信度的检测结果。提取真实类别标签和检测结果的类别标签。判断检测结果和真实边界框是否为定向边界框(OBB)。根据是否为OBB,选择合适的IoU计算方法,并计算检测结果和真实边界框之间的IoU。通过这些步骤,可以准确地计算检测结果和真实边界框之间的匹配程度,为后续的混淆矩阵更新提供基础。
# 这段代码用于处理计算得到的IoU(交并比)矩阵,找出满足IoU阈值条件的检测结果与真实边界框的匹配对,并确保每个检测结果和每个真实边界框只匹配一次。
# 使用 torch.where 函数找出IoU矩阵中大于IoU阈值 self.iou_thres 的元素的索引。 x 是一个包含两个张量的元组,分别表示满足条件的行索引和列索引。
x = torch.where(iou > self.iou_thres)
# 检查是否有满足IoU阈值的匹配对。如果 x[0].shape[0] 大于0,表示存在匹配对。
if x[0].shape[0]:
# torch.stack(tensors, dim=0, *, out=None) -> Tensor
# torch.stack 是 PyTorch 中的一个函数,用于将一系列张量(tensors)沿着一个新的维度连接起来,从而创建一个新的张量。这个新张量是由输入张量堆叠而成的,堆叠的维度由参数 dim 指定。
# 参数 :
# tensors :必须。一个张量序列,可以是列表、元组或任何可迭代的张量对象集合。所有张量必须具有相同的形状。
# dim :可选。指定沿着哪个维度堆叠张量,默认为0。这个维度将被添加到结果张量的维度中。
# out :可选。用于存储输出结果的张量。它必须具有与要创建的堆叠张量相同的形状和类型。
# 返回值 :
# 返回一个新的张量,它是输入张量沿着指定维度 dim 堆叠的结果。
# 描述 :
# torch.stack 函数与 torch.cat 函数类似,但它们的主要区别在于堆叠的维度。 torch.cat 是在已存在的维度上连接张量,而 torch.stack 是创建一个新的维度来堆叠张量。
# 如果存在匹配对,构建一个 包含匹配对信息的数组 matches 。
# torch.stack(x, 1) :将行索引和列索引堆叠成一个形状为 [num_matches, 2] 的张量。
# iou[x[0], x[1]][:, None] :提取匹配对的IoU值,形状为 [num_matches, 1] 。
# torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1) :将行索引、列索引和IoU值拼接成一个形状为 [num_matches, 3] 的张量。
# .cpu().numpy() :将张量移动到CPU并转换为NumPy数组。
matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()
# 如果存在多个匹配对,进行进一步处理,确保每个检测结果和每个真实边界框只匹配一次。
if x[0].shape[0] > 1:
# 按IoU值降序排序匹配对,确保IoU值最高的匹配对优先处理。
matches = matches[matches[:, 2].argsort()[::-1]]
# 确保每个检测结果只匹配一个真实边界框。 np.unique(matches[:, 1], return_index=True)[1] 返回每个 检测结果 第一次出现的索引,从而去除重复的检测结果。
matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
# 再次按IoU值降序排序匹配对,确保IoU值最高的匹配对优先处理。
matches = matches[matches[:, 2].argsort()[::-1]]
# 确保每个真实边界框只匹配一个检测结果。 np.unique(matches[:, 0], return_index=True)[1] 返回每个 真实边界框 第一次出现的索引,从而去除重复的真实边界框。
matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
# 如果没有匹配对,初始化一个形状为 [0, 3] 的空数组 matches 。
else:
matches = np.zeros((0, 3))
# 这段代码找出满足IoU阈值的检测结果与真实边界框的匹配对。确保每个检测结果和每个真实边界框只匹配一次。按IoU值降序排序匹配对,确保IoU值最高的匹配对优先处理。通过这些步骤,可以准确地确定检测结果与真实边界框之间的最佳匹配,为后续的混淆矩阵更新提供基础。
# 这段代码用于根据匹配结果更新混淆矩阵,记录正确检测、假阳性和假阴性的数量。
# 检查 matches 数组是否为空。如果 matches 的形状的第一个维度大于0,表示存在匹配对。
n = matches.shape[0] > 0
# 将 matches 数组转置并转换为整数类型,提取匹配对的索引。 m0 表示真实边界框的索引, m1 表示检测结果的索引。
m0, m1, _ = matches.transpose().astype(int)
# 遍历每个真实类别标签 gc ,更新混淆矩阵
for i, gc in enumerate(gt_classes):
# m0 == i :找出与当前真实边界框索引 i 匹配的检测结果索引。
j = m0 == i
# sum(j) == 1 :确保只有一个匹配的检测结果。
# self.matrix[detection_classes[m1[j]], gc] += 1 :如果存在匹配的检测结果,更新混淆矩阵,记录为 正确检测 (true positives)。
if n and sum(j) == 1:
self.matrix[detection_classes[m1[j]], gc] += 1 # correct
# 如果没有匹配的检测结果,更新混淆矩阵,记录为 背景假阴性 (false negatives)。
else:
self.matrix[self.nc, gc] += 1 # true background
# 检查 matches 数组是否不为空。如果 matches 的形状的第一个维度大于0,表示存在匹配对,进入后续处理。
if n:
# 遍历每个 检测结果的类别标签 dc , i 是检测结果的索引, dc 是检测结果的类别标签。
for i, dc in enumerate(detection_classes):
# 检查当前检测结果的索引 i 是否在匹配对的检测结果索引 m1 中。如果 m1 中没有 i ,表示该检测结果没有匹配的真实边界框。
if not any(m1 == i):
# 如果检测结果没有匹配的真实边界框,更新混淆矩阵,将 预测类别 dc 和 背景类别 self.nc 对应的单元格值加1,表示 背景假阳性 (false positives)。
self.matrix[dc, self.nc] += 1 # predicted background
# 这段代码的主要作用是处理未匹配的检测结果,将它们记录为背景假阳性(false positives),更新混淆矩阵。检查是否存在匹配对。遍历每个检测结果的类别标签。检查检测结果是否未匹配。更新混淆矩阵,记录背景假阳性的数量。通过这些步骤,混淆矩阵可以准确记录检测结果与真实标签的匹配情况,从而评估模型的目标检测性能。这个方法在目标检测任务中非常有用,特别是在评估模型的准确性和错误类型时。
# process_batch 方法的主要作用是更新目标检测任务的混淆矩阵。处理空标签和空检测的情况。过滤低置信度的检测结果。计算检测结果和真实边界框之间的IoU。匹配检测结果和真实边界框。更新混淆矩阵,记录正确检测、假阳性和假阴性的数量。通过这些步骤,混淆矩阵可以记录每个类别的预测和真实标签的匹配情况,从而评估模型的目标检测性能。这个方法在目标检测任务中非常有用,特别是在评估模型的准确性和错误类型时。
# 这段代码定义了 ConfusionMatrix 类中的一个方法 matrix ,用于返回当前的混淆矩阵。这个方法非常简单,但非常有用,因为它提供了一个直接的方式来访问和使用混淆矩阵的数据。
# 定义了 matrix 方法,这个方法没有接受额外的参数,仅使用类的实例 self 。
def matrix(self):
# 返回混淆矩阵。
"""Returns the confusion matrix."""
# 这行代码返回类实例中的混淆矩阵 self.matrix 。这个矩阵是一个二维数组,其中的每个元素 self.matrix[i][j] 表示预测为类别 i 而实际为类别 j 的样本数量。
return self.matrix
# matrix 方法的主要作用是提供一个接口来获取混淆矩阵的当前状态。这个方法在评估模型性能时非常有用,因为它允许用户直接访问混淆矩阵的数据,从而可以进行进一步的分析和可视化。例如,用户可以使用这个矩阵来计算各种性能指标,如准确率、召回率、F1分数等,或者将其可视化以直观地理解模型的性能。
# 这个方法的简单性和直接性使其成为 ConfusionMatrix 类的一个重要组成部分,为用户提供了一个方便的方式来获取和使用混淆矩阵的数据。
# process_batch 方法中没有直接计算真阴性(True Negatives, TN)的值。在目标检测任务中,真阴性通常指的是模型正确地识别了背景区域,即没有在背景区域检测到目标。然而,在混淆矩阵的上下文中,真阴性通常不被显式计算,原因如下 :
# 背景类的处理 :
# 在目标检测任务中,背景区域通常被视为一个特殊的类别。所有未被检测到的区域默认被认为是背景。
# 因此,真阴性(TN)的数量通常非常大,因为背景区域通常占图像的大部分。
# 混淆矩阵的结构 :
# 混淆矩阵的主要关注点是记录模型对每个类别的预测和真实标签的匹配情况。
# 真阳性(TP)、假阳性(FP)和假阴性(FN)是评估模型性能的关键指标。
# 真阴性(TN)虽然重要,但在目标检测任务中通常不被显式记录,因为背景区域的处理方式与目标区域不同。
# 计算TN的复杂性 :
# 计算真阴性(TN)需要明确地记录模型在背景区域的预测情况,这在实际操作中非常复杂,因为背景区域的数量通常非常大。
# 通常,背景区域的处理是通过非最大抑制(NMS)和置信度阈值来控制的,而不是通过显式记录每个背景区域的预测结果。
# 总结 :
# 在目标检测任务中, process_batch 方法主要关注真阳性(TP)、假阳性(FP)和假阴性(FN)的计算。真阴性(TN)通常不被显式记录,因为背景区域的处理方式与目标区域不同,且背景区域的数量通常非常大。通过记录TP、FP和FN,可以有效地评估模型的性能,特别是检测的准确性和完整性。
# 这段代码定义了 ConfusionMatrix 类中的 tp_fp 方法,用于计算并返回真阳性(true positives, TP)和假阳性(false positives, FP)的数量。这个方法在评估分类和目标检测模型的性能时非常有用。
# 定义了 tp_fp 方法,这个方法没有接受额外的参数,仅使用类的实例 self 。
def tp_fp(self):
# 返回 真阳性 和 假阳性 。
"""Returns true positives and false positives."""
# 计算 混淆矩阵的对角线元素 ,这些元素表示 每个类别的真阳性数量 。对角线元素 self.matrix[i][i] 表示预测为类别 i 且实际也为类别 i 的样本数量。
tp = self.matrix.diagonal() # true positives
# 计算每个类别的假阳性数量。 self.matrix.sum(1) :计算混淆矩阵每一行的和,表示每个预测类别的总数量。 self.matrix.sum(1) - tp :从每个预测类别的总数量中减去真阳性数量,得到假阳性数量。
fp = self.matrix.sum(1) - tp # false positives
# 计算每个类别的 假阴性数量 (注释掉的部分)。 self.matrix.sum(0) :计算混淆矩阵每一列的和,表示每个真实类别的总数量。 self.matrix.sum(0) - tp :从每个真实类别的总数量中减去真阳性数量,得到假阴性数量。
# fn = self.matrix.sum(0) - tp # false negatives (missed detections)
# 根据任务类型返回结果。如果任务类型为 "detect" (目标检测),则返回去除背景类的真阳性和假阳性数量。背景类通常在混淆矩阵的最后一行和最后一列。 如果任务类型为其他(如分类),则返回完整的真阳性和假阳性数量。
return (tp[:-1], fp[:-1]) if self.task == "detect" else (tp, fp) # remove background class if task=detect
# tp_fp 方法的主要作用是计算并返回真阳性和假阳性的数量。计算混淆矩阵的对角线元素,得到真阳性数量。计算每个类别的假阳性数量。根据任务类型返回适当的真阳性和假阳性数量。通过这些步骤,可以评估模型的性能,特别是分类的准确性和错误类型。这个方法在分类和目标检测任务中非常有用,特别是在评估模型的准确性和错误类型时。
# 示例 :
# 假设我们有以下混淆矩阵 :
# [[10 2 0 0]
# [ 1 15 1 0]
# [ 0 1 20 0]
# [ 0 0 0 0]]
# tp :对角线元素 [10, 15, 20] ,表示每个类别的真阳性数量。
# fp :每一行的总和减去对角线元素 [2, 2, 1] ,表示每个类别的假阳性数量。
# 这段代码定义了 ConfusionMatrix 类中的 plot 方法,用于绘制并保存混淆矩阵的热图。这个方法使用 seaborn 库来生成热图,并提供了多种配置选项,如归一化、保存路径、类别名称等。
# 一个自定义装饰器,用于捕获并处理绘图过程中可能出现的异常,确保程序的健壮性。
# class TryExcept(contextlib.ContextDecorator):
# -> 用于捕获在 with 语句块中发生的异常,并根据提供的参数决定是否打印错误信息。
# -> def __init__(self, msg="", verbose=True):
@TryExcept("WARNING ⚠️ ConfusionMatrix plot failure") # 警告⚠️ConfusionMatrix 绘图失败。
# 另一个自定义装饰器,用于设置绘图的全局配置,如字体大小等。
# def plt_settings(rcparams=None, backend="Agg"):
# -> 用于设置Matplotlib的全局配置参数(rcparams)和后端(backend),并在函数执行后恢复原始设置。这个装饰器在绘图函数中非常有用,特别是当需要临时修改绘图配置而不影响全局设置时。返回装饰器工厂。返回装饰器 decorator 。
# -> return decorator
@plt_settings()
# plot 方法接受以下参数 :
# 1.normalize :布尔值,表示是否对混淆矩阵进行归一化。默认为 True 。
# 2.save_dir :字符串,表示保存热图的目录路径。默认为空字符串。
# 3.names :元组,包含类别的名称,用于热图的标签。默认为空元组。
# 4.on_plot :可选的回调函数,用于在热图生成后执行某些操作,如保存路径和数据。默认为 None 。
def plot(self, normalize=True, save_dir="", names=(), on_plot=None):
# 使用 seaborn 绘制混淆矩阵并将其保存到文件中。
"""
Plot the confusion matrix using seaborn and save it to a file.
Args:
normalize (bool): Whether to normalize the confusion matrix.
save_dir (str): Directory where the plot will be saved.
names (tuple): Names of classes, used as labels on the plot.
on_plot (func): An optional callback to pass plots path and data when they are rendered.
"""
import seaborn as sn
# 如果 normalize 为 True ,则对混淆矩阵的每一列进行归一化,使其和为1。 self.matrix.sum(0).reshape(1, -1) + 1e-9 用于避免除以零的错误。
array = self.matrix / ((self.matrix.sum(0).reshape(1, -1) + 1e-9) if normalize else 1) # normalize columns
# 将小于0.005的值设置为 np.nan ,这样在热图中这些值不会被标注,避免显示为0.00。
array[array < 0.005] = np.nan # don't annotate (would appear as 0.00)
# 创建一个图形和轴对象,设置图形大小为12x9英寸,并启用紧凑布局。
fig, ax = plt.subplots(1, 1, figsize=(12, 9), tight_layout=True)
# nc 类别数量。 nn 类别名称的数量。
nc, nn = self.nc, len(names) # number of classes, names
# 根据类别数量调整字体大小,如果类别数量少于50,则使用较大的字体。
sn.set(font_scale=1.0 if nc < 50 else 0.8) # for label size
# 如果类别名称数量在1到99之间且与类别数量相等,则使用类别名称作为标签。
labels = (0 < nn < 99) and (nn == nc) # apply names to ticklabels
# 如果 labels 为 True ,则使用类别名称和“background”作为标签;否则,使用默认标签。
ticklabels = (list(names) + ["background"]) if labels else "auto"
# 一个上下文管理器,用于捕获和处理警告信息。
with warnings.catch_warnings():
# 设置警告过滤器,忽略所有警告信息。这特别用于忽略由于热图数据中存在全NaN切片而引发的 RuntimeWarning: All-NaN slice encountered 警告。
warnings.simplefilter("ignore") # suppress empty matrix RuntimeWarning: All-NaN slice encountered
# 使用 seaborn 的 heatmap 函数绘制热图。
sn.heatmap(
# 输入的混淆矩阵数据。
array,
# matplotlib轴对象,用于绘制热图。
ax=ax,
# 如果类别数量少于30,则在热图的每个单元格中显示数值。
annot=nc < 30,
# 设置标注的字体大小为8。
annot_kws={"size": 8},
# 使用蓝色调色板。
cmap="Blues",
# 如果归一化,则显示两位小数;否则,显示整数。
fmt=".2f" if normalize else ".0f",
# 确保每个单元格为正方形。
square=True,
# 设置颜色映射的最小值为0。
vmin=0.0,
# 设置x轴和y轴的标签。
xticklabels=ticklabels,
yticklabels=ticklabels,
# 设置热图的背景颜色为白色。
).set_facecolor((1, 1, 1))
# 设置热图的标题,如果归一化,则在标题中添加“Normalized”。
title = "Confusion Matrix" + " Normalized" * normalize
# 设置x轴标签为“True”,y轴标签为“Predicted”。
ax.set_xlabel("True")
ax.set_ylabel("Predicted")
ax.set_title(title)
# 生成保存路径。
plot_fname = Path(save_dir) / f'{title.lower().replace(" ", "_")}.png'
# 保存热图为PNG文件,分辨率为250 DPI。
fig.savefig(plot_fname, dpi=250)
# 关闭图形对象,释放资源。
plt.close(fig)
# 如果提供了回调函数 on_plot ,则调用该函数,传入保存的文件路径。
if on_plot:
on_plot(plot_fname)
# plot 方法的主要作用是绘制并保存混淆矩阵的热图。通过这个方法,可以直观地评估模型的性能,特别是分类的准确性和错误类型。这个方法在分类和目标检测任务中非常有用,特别是在评估模型的准确性和错误类型时。通过提供多种配置选项,如归一化、保存路径、类别名称等,这个方法可以灵活地适应不同的需求。
# 这段代码定义了 ConfusionMatrix 类中的 print 方法,用于将混淆矩阵打印到控制台。这个方法在调试和评估模型性能时非常有用,因为它提供了一种直观的方式来查看模型的预测和真实标签的匹配情况。
# 定义了 print 方法,这个方法没有接受额外的参数,仅使用类的实例 self 。
def print(self):
# 将混淆矩阵打印到控制台。
"""Print the confusion matrix to the console."""
# 遍历混淆矩阵的每一行。 self.nc 是类别数量,加上1表示背景类。
for i in range(self.nc + 1):
# 将每一行的元素转换为字符串并用空格连接,然后使用 LOGGER.info 打印到控制台。 LOGGER 是一个日志记录器,用于输出信息。
LOGGER.info(" ".join(map(str, self.matrix[i])))
# print 方法的主要作用是将混淆矩阵打印到控制台。遍历混淆矩阵的每一行。将每一行的元素转换为字符串并用空格连接。使用日志记录器打印每一行的内容。通过这些步骤,可以直观地查看模型的预测和真实标签的匹配情况,从而评估模型的性能。这个方法在调试和评估模型时非常有用,特别是在分类和目标检测任务中。
12.def smooth(y, f=0.05):
# 这段代码定义了一个名为 smooth 的函数,用于对一维数据进行平滑处理。平滑处理通常用于减少数据中的噪声,使数据更加平滑。这个函数使用了一个简单的箱式滤波器(box filter)来实现平滑。
# 定义了 smooth 函数,接受两个参数。
# 1.y :一维数据数组,需要进行平滑处理。
# 2.f :滤波器的分数,用于确定滤波器的大小。默认值为 0.05 。
def smooth(y, f=0.05):
# 分数为 f 的箱式滤波器。
"""Box filter of fraction f."""
# 计算滤波器的大小 nf ,确保其为奇数。
# len(y) * f * 2 :计算滤波器的大小,乘以2是为了确保滤波器的大小足够大。
# round(...) :四舍五入到最近的整数。
# // 2 + 1 :确保滤波器的大小为奇数,因为箱式滤波器的中心需要对称。
nf = round(len(y) * f * 2) // 2 + 1 # number of filter elements (must be odd)
# 创建一个填充数组 p ,其长度为滤波器大小的一半,用于在数据的两端进行填充。填充数组的值为1,确保填充部分的值与数据的首尾值相同。
p = np.ones(nf // 2) # ones padding
# 将数据 y 进行填充。
# p * y[0] :在数据的开头填充 p 个 y[0] 。
# y :原始数据。
# p * y[-1] :在数据的末尾填充 p 个 y[-1] 。
# np.concatenate(..., 0) :将这些部分连接成一个一维数组 yp 。
yp = np.concatenate((p * y[0], y, p * y[-1]), 0) # y padded
# np.convolve(in1, in2, mode='full', method='auto')
# np.convolve 是 NumPy 库中的一个函数,用于执行一维卷积。卷积是一种数学运算,它将两个函数(信号)结合起来产生第三个函数,这个函数表达了一个函数的形状如何被另一个函数修改。在信号处理和图像处理中,卷积是一种常见的操作,用于平滑数据、滤波、边缘检测等。
# 参数说明 :
# in1 :第一个输入数组。
# in2 :第二个输入数组,通常称为卷积核或滤波器。
# mode :字符串,指定输出的类型。可以是以下几种之一 :
# 'full' :返回完整的卷积结果,输出长度为 len(in1) + len(in2) - 1 。
# 'valid' :返回只有完全包含两个输入数组部分的输出,输出长度为 len(in1) + len(in2) - 2 ,如果 in1 和 in2 长度相等,则输出长度为 len(in1) - 1 。
# 'same' :返回输出结果,长度与 in1 相同。
# method :字符串,指定计算卷积的方法。可以是以下几种之一 :
# 'auto' :自动选择最快的方法。
# 'direct' :直接计算(直接方法)。
# 'fft' :使用快速傅里叶变换(FFT)计算卷积。
# 返回值 :
# 函数返回一个新的数组,该数组是输入数组 in1 和 in2 的卷积结果。
# 使用 np.convolve 函数应用箱式滤波器。
# np.ones(nf) / nf :创建一个长度为 nf 的滤波器,其值为 1/nf ,表示每个元素的权重相等。
# mode="valid" :卷积模式为 "valid" ,表示只返回完全重叠部分的卷积结果,确保输出数组的长度与输入数组 y 的长度相同。
return np.convolve(yp, np.ones(nf) / nf, mode="valid") # y-smoothed
# smooth 函数的主要作用是对一维数据进行平滑处理,使用箱式滤波器减少数据中的噪声。计算滤波器的大小,确保其为奇数。创建填充数组,用于在数据的两端进行填充。将数据进行填充,确保滤波器可以完全覆盖数据的首尾部分。使用 np.convolve 函数应用箱式滤波器,返回平滑后的数据。通过这些步骤,可以有效地减少数据中的噪声,使数据更加平滑。这个函数在数据预处理和信号处理中非常有用,特别是在需要平滑时间序列数据或一维信号时。
13.def plot_pr_curve(px, py, ap, save_dir=Path("pr_curve.png"), names=(), on_plot=None):
# 这段代码定义了一个名为 plot_pr_curve 的函数,用于绘制精确率-召回率(Precision-Recall, PR)曲线。这个函数使用 matplotlib 库来生成图形,并提供了多种配置选项,如保存路径、类别名称等。
# 使用 plt_settings 装饰器,设置绘图的全局配置,如字体大小等。这个装饰器确保在绘图过程中使用一致的配置,并在绘图完成后恢复原始设置。
# def plt_settings(rcparams=None, backend="Agg"):
# -> 用于设置Matplotlib的全局配置参数(rcparams)和后端(backend),并在函数执行后恢复原始设置。这个装饰器在绘图函数中非常有用,特别是当需要临时修改绘图配置而不影响全局设置时。返回装饰器工厂。返回装饰器 decorator 。
# -> return decorator
@plt_settings()
# 定义了 plot_pr_curve 函数,接受以下参数 :
# 1.px :召回率(recall)数组。
# 2.py :精确率(precision)数组,形状为 (num_classes, num_points) 。
# 3.ap :平均精确率(AP)数组,形状为 (num_classes, 1) 。
# 4.save_dir :保存路径,类型为 Path 。默认为 "pr_curve.png" 。
# 5.names :类别名称列表。默认为空元组。
# 6.on_plot :可选的回调函数,用于在绘图完成后执行某些操作。默认为 None 。
def plot_pr_curve(px, py, ap, save_dir=Path("pr_curve.png"), names=(), on_plot=None):
# 绘制精确度-召回率曲线。
"""Plots a precision-recall curve."""
# 创建一个图形和轴对象,设置图形大小为 9x6 英寸,并启用紧凑布局。
fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
# 将 py 数组堆叠成形状为 (num_points, num_classes) 的数组。
py = np.stack(py, axis=1)
# 如果类别数量在 1 到 20 之间,则为每个类别绘制 PR 曲线,并在图例中显示类别名称和 AP 值。
if 0 < len(names) < 21: # display per-class legend if < 21 classes
for i, y in enumerate(py.T):
ax.plot(px, y, linewidth=1, label=f"{names[i]} {ap[i, 0]:.3f}") # plot(recall, precision)
# 如果类别数量超过 20,则绘制所有类别的 PR 曲线,颜色为灰色。
else:
ax.plot(px, py, linewidth=1, color="grey") # plot(recall, precision)
# 绘制所有类别的平均 PR 曲线,颜色为蓝色,并在图例中显示所有类别的平均 AP 值。
ax.plot(px, py.mean(1), linewidth=3, color="blue", label="all classes %.3f mAP@0.5" % ap[:, 0].mean())
# 设置 x 轴标签为 "Recall"。
ax.set_xlabel("Recall")
# 设置 y 轴标签为 "Precision"。
ax.set_ylabel("Precision")
# 设置 x 轴和 y 轴的范围为 0 到 1。
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
# 设置图例的位置。
ax.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
# 设置图形的标题。
ax.set_title("Precision-Recall Curve")
# 保存图形到指定路径,分辨率为 250 DPI。
fig.savefig(save_dir, dpi=250)
# 关闭图形对象,释放资源。
plt.close(fig)
# 如果提供了回调函数 on_plot ,则调用该函数,传入保存的文件路径。
if on_plot:
on_plot(save_dir)
# plot_pr_curve 函数的主要作用是绘制精确率-召回率曲线,评估模型的性能。创建图形和轴对象。绘制每个类别的 PR 曲线。绘制所有类别的平均 PR 曲线。设置图形的属性,如标签、范围和图例。保存图形到指定路径。调用回调函数(如果提供)。通过这些步骤,可以直观地评估模型的性能,特别是在分类和目标检测任务中。这个函数在评估模型的精确率和召回率时非常有用。
14.def plot_mc_curve(px, py, save_dir=Path("mc_curve.png"), names=(), xlabel="Confidence", ylabel="Metric", on_plot=None):
# 这段代码定义了一个名为 plot_mc_curve 的函数,用于绘制指标-置信度(Metric-Confidence, MC)曲线。这个函数使用 matplotlib 库来生成图形,并提供了多种配置选项,如保存路径、类别名称等。
# 使用 plt_settings 装饰器,设置绘图的全局配置,如字体大小等。这个装饰器确保在绘图过程中使用一致的配置,并在绘图完成后恢复原始设置。
@plt_settings()
# 定义了 plot_mc_curve 函数,接受以下参数 :
# 1.px :置信度(confidence)数组。
# 2.py :指标(metric)数组,形状为 (num_classes, num_points) 。
# 3.save_dir :保存路径,类型为 Path 。默认为 "mc_curve.png" 。
# 4.names :类别名称列表。默认为空元组。
# 5.xlabel :x 轴标签。默认为 "Confidence" 。
# 6.ylabel :y 轴标签。默认为 "Metric" 。
# 7.on_plot :可选的回调函数,用于在绘图完成后执行某些操作。默认为 None 。
def plot_mc_curve(px, py, save_dir=Path("mc_curve.png"), names=(), xlabel="Confidence", ylabel="Metric", on_plot=None):
# 绘制度量置信度曲线。
"""Plots a metric-confidence curve."""
# 创建一个图形和轴对象,设置图形大小为 9x6 英寸,并启用紧凑布局。
fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
# 如果类别数量在 1 到 20 之间,则为每个类别绘制 MC 曲线,并在图例中显示类别名称。
if 0 < len(names) < 21: # display per-class legend if < 21 classes
for i, y in enumerate(py):
ax.plot(px, y, linewidth=1, label=f"{names[i]}") # plot(confidence, metric)
# 如果类别数量超过 20,则绘制所有类别的 MC 曲线,颜色为灰色。
else:
ax.plot(px, py.T, linewidth=1, color="grey") # plot(confidence, metric)
# 使用 smooth 函数对所有类别的平均指标进行平滑处理。
y = smooth(py.mean(0), 0.05)
# 绘制平滑后的平均 MC 曲线,颜色为蓝色,并在图例中显示最大指标值及其对应的置信度。
ax.plot(px, y, linewidth=3, color="blue", label=f"all classes {y.max():.2f} at {px[y.argmax()]:.3f}")
# 设置 x 轴标签为 xlabel 。
ax.set_xlabel(xlabel)
# 设置 y 轴标签为 ylabel 。
ax.set_ylabel(ylabel)
# 设置 x 轴和 y 轴的范围为 0 到 1。
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
# 设置图例的位置。
ax.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
# 设置图形的标题。
ax.set_title(f"{ylabel}-Confidence Curve")
# 保存图形到指定路径,分辨率为 250 DPI。
fig.savefig(save_dir, dpi=250)
# 关闭图形对象,释放资源。
plt.close(fig)
# 如果提供了回调函数 on_plot ,则调用该函数,传入保存的文件路径。
if on_plot:
on_plot(save_dir)
# plot_mc_curve 函数的主要作用是绘制指标-置信度曲线,评估模型的性能。创建图形和轴对象。绘制每个类别的 MC 曲线。绘制所有类别的平均 MC 曲线。设置图形的属性,如标签、范围和图例。保存图形到指定路径。调用回调函数(如果提供)。通过这些步骤,可以直观地评估模型的性能,特别是在分类和目标检测任务中。这个函数在评估模型的指标和置信度时非常有用。
15.def compute_ap(recall, precision):
# 这段代码定义了一个名为 compute_ap 的函数,用于计算平均精确率(Average Precision, AP),这是评估分类和目标检测模型性能的一个重要指标。AP 是精确率-召回率曲线下的面积。
# 定义了 compute_ap 函数,接受两个参数。
# 1.recall :召回率(recall)列表。
# 2.precision :精确率(precision)列表。
def compute_ap(recall, precision):
# 根据召回率和准确率曲线计算平均准确率 (AP)。
"""
Compute the average precision (AP) given the recall and precision curves.
Args:
recall (list): The recall curve.
precision (list): The precision curve.
Returns:
(float): Average precision.
(np.ndarray): Precision envelope curve.
(np.ndarray): Modified recall curve with sentinel values added at the beginning and end.
"""
# Append sentinel values to beginning and end
# 在召回率曲线的开始和结束处添加哨兵值 [0.0] 和 [1.0] 。
mrec = np.concatenate(([0.0], recall, [1.0]))
# 在精确率曲线的开始和结束处添加哨兵值 [1.0] 和 [0.0] 。这些哨兵值确保曲线在边界处的行为是正确的。
mpre = np.concatenate(([1.0], precision, [0.0]))
# Compute the precision envelope
# np.maximum.accumulate(array, axis=None, dtype=None)
# np.maximum.accumulate() 是 NumPy 库中的一个函数,它沿着指定的轴计算输入数组元素的最大累积值。这个函数返回一个新的数组,其中每个元素是输入数组中直到该位置的元素的最大值。
# 参数说明 :
# array :输入的数组。
# axis :沿哪个轴计算最大累积值。如果为 None ,则计算扁平化后的数组的最大累积值。
# dtype :输出数组的数据类型。如果为 None ,则数据类型与输入数组相同。
# 返回值 :
# 返回一个新的数组,其中每个元素是输入数组中直到该位置的元素的最大值。
# 使用 np.flip 将精确率曲线翻转。 使用 np.maximum.accumulate 计算累积最大值,生成精确率包络曲线。 再次使用 np.flip 将曲线翻转回原始顺序。
mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
# Integrate area under curve
# 定义了一个变量 method ,用于选择计算 AP 的方法。这里选择 "interp" ,表示使用 101 点插值方法。另一种方法是 "continuous" ,表示使用连续方法。
method = "interp" # methods: 'continuous', 'interp'
# 101 点插值方法。
if method == "interp":
# 生成 101 个等间距的点,范围从 0 到 1。这是 COCO 标准中使用的方法。
x = np.linspace(0, 1, 101) # 101-point interp (COCO)
# np.trapz(y, x=None, dx=1.0, axis=-1)
# np.trapz 是 NumPy 库中的一个函数,用于使用梯形规则计算数值积分。这个函数可以近似计算函数的定积分,即曲线下的面积,通过将区间划分为多个小梯形并计算它们的面积之和。
# 参数说明 :
# y :待积分的函数值数组。
# x :对应于 y 中的函数值的点,如果为 None ,则假定 y 中的点均匀分布在 [0, n] 区间内,其中 n 是 y 的长度。
# dx : x 轴上每对相邻点之间的间隔,默认为 1.0 。
# axis :沿哪个轴进行积分,默认为 -1 ,即最后一个轴。
# 返回值 :
# 返回沿指定轴的积分结果。
# 插值和积分。
# np.interp(x, mrec, mpre) :在这些等间距的点上进行线性插值,生成对应的精确率值。
# np.trapz(np.interp(x, mrec, mpre), x) :使用梯形法则( np.trapz )计算插值曲线下的面积,即 AP。
ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate
# 连续方法。
else: # 'continuous'
# 找到召回率变化的点。找到召回率曲线中发生变化的点的索引。这些点表示召回率从一个值变化到另一个值。
i = np.where(mrec[1:] != mrec[:-1])[0] # points where x-axis (recall) changes
# 计算面积。计算这些点之间的面积。对于每个召回率变化的区间,计算该区间的宽度( mrec[i + 1] - mrec[i] )和对应的高度( mpre[i + 1] ),然后求和得到总面积,即 AP。
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve
# 返回计算得到的 平均精确率 ap 、 精确率包络曲线 mpre 和 修改后的召回率曲线 mrec 。
return ap, mpre, mrec
# compute_ap 函数的主要作用是计算平均精确率(AP),这是评估分类和目标检测模型性能的一个重要指标。在召回率和精确率曲线的开始和结束处添加哨兵值。计算精确率包络曲线。使用 101 点插值方法或连续方法计算精确率-召回率曲线下的面积。返回平均精确率、精确率包络曲线和修改后的召回率曲线。通过这些步骤,可以有效地评估模型的性能,特别是在分类和目标检测任务中。这个函数在评估模型的精确率和召回率时非常有用。
16.def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, on_plot=None, save_dir=Path(), names=(), eps=1e-16, prefix=""):
# 这段代码定义了一个名为 ap_per_class 的函数,用于计算每个类别在目标检测任务中的平均精度(AP)、精确度(Precision)、召回率(Recall)等指标,并可绘制相关的曲线。
# 定义了 ap_per_class 函数,接受以下参数 :
# 1.tp :二元数组,表示检测是否正确( True 表示正确, False 表示不正确)。 tp 变量是一个二维数组,其形状为(检测数,IoU阈值数)。
# 2.conf :检测的置信度数组。
# 3.pred_cls :检测的预测类别数组。
# 4.target_cls :检测的真实类别数组。
# 5.plot :布尔值,表示是否绘制 PR 曲线。默认为 False 。
# 6.on_plot :可选的回调函数,用于在绘图完成后执行某些操作。默认为 None 。
# 7.save_dir :保存路径,类型为 Path 。默认为空路径。
# 8.names :类别名称字典。默认为空元组。
# 9.eps :一个小的值,用于避免除以零。默认为 1e-16 。
# 10.prefix :保存文件时的前缀字符串。默认为空字符串。
def ap_per_class(
tp, conf, pred_cls, target_cls, plot=False, on_plot=None, save_dir=Path(), names=(), eps=1e-16, prefix=""
):
# 计算对象检测评估中每个类别的平均精度。
# (tuple):由六个数组和一个唯一类数组组成的 tuple,其中:
# tp (np.ndarray):每个类的最大 F1 度量给出的阈值处的真阳性计数。形状:(nc,)。
# fp (np.ndarray):每个类的最大 F1 度量给出的阈值处的假阳性计数。形状:(nc,)。
# p (np.ndarray):每个类的最大 F1 度量给出的阈值处的精度值。形状:(nc,)。
# r (np.ndarray):每个类的最大 F1 度量给出的阈值处的召回率值。形状:(nc,)。
# f1 (np.ndarray):每个类的最大 F1 度量给出的阈值处的 F1 得分值。形状:(nc,)。
# ap (np.ndarray):不同 IoU 阈值下每个类的平均精度。形状:(nc, 10)。
# unique_classes (np.ndarray):具有数据的唯一类的数组。形状:(nc,)。
# p_curve (np.ndarray):每个类的精度曲线。形状:(nc, 1000)。
# r_curve (np.ndarray):每个类的召回率曲线。形状:(nc, 1000)。
# f1_curve (np.ndarray):每个类的 F1 分数曲线。形状:(nc, 1000)。
# x (np.ndarray):曲线的 X 轴值。形状:(1000,)。
# prec_values:每个类的 mAP@0.5 精度值。形状:(nc, 1000)。
"""
Computes the average precision per class for object detection evaluation.
Args:
tp (np.ndarray): Binary array indicating whether the detection is correct (True) or not (False).
conf (np.ndarray): Array of confidence scores of the detections.
pred_cls (np.ndarray): Array of predicted classes of the detections.
target_cls (np.ndarray): Array of true classes of the detections.
plot (bool, optional): Whether to plot PR curves or not. Defaults to False.
on_plot (func, optional): A callback to pass plots path and data when they are rendered. Defaults to None.
save_dir (Path, optional): Directory to save the PR curves. Defaults to an empty path.
names (tuple, optional): Tuple of class names to plot PR curves. Defaults to an empty tuple.
eps (float, optional): A small value to avoid division by zero. Defaults to 1e-16.
prefix (str, optional): A prefix string for saving the plot files. Defaults to an empty string.
Returns:
(tuple): A tuple of six arrays and one array of unique classes, where:
tp (np.ndarray): True positive counts at threshold given by max F1 metric for each class.Shape: (nc,).
fp (np.ndarray): False positive counts at threshold given by max F1 metric for each class. Shape: (nc,).
p (np.ndarray): Precision values at threshold given by max F1 metric for each class. Shape: (nc,).
r (np.ndarray): Recall values at threshold given by max F1 metric for each class. Shape: (nc,).
f1 (np.ndarray): F1-score values at threshold given by max F1 metric for each class. Shape: (nc,).
ap (np.ndarray): Average precision for each class at different IoU thresholds. Shape: (nc, 10).
unique_classes (np.ndarray): An array of unique classes that have data. Shape: (nc,).
p_curve (np.ndarray): Precision curves for each class. Shape: (nc, 1000).
r_curve (np.ndarray): Recall curves for each class. Shape: (nc, 1000).
f1_curve (np.ndarray): F1-score curves for each class. Shape: (nc, 1000).
x (np.ndarray): X-axis values for the curves. Shape: (1000,).
prec_values: Precision values at mAP@0.5 for each class. Shape: (nc, 1000).
"""
# 这段代码首先对预测结果按照置信度进行排序,并计算一些基本的类别信息和初始化一些变量。
# Sort by objectness
# 使用 np.argsort 函数对 conf 数组进行降序排序,返回排序后的索引数组 i 。这样做的目的是将置信度高的预测放在前面,以便后续计算精度和召回率时按照置信度顺序进行。
i = np.argsort(-conf)
# 根据上一步得到的索引数组 i ,对 tp 、 conf 和 pred_cls 数组进行重新排序。这样 tp 、 conf 和 pred_cls 数组中的元素顺序将与置信度的降序顺序一致。
tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
# Find unique classes
# 使用 np.unique 函数找出 target_cls 数组中的唯一类别,并返回这些唯一类别以及它们各自的出现次数。 unique_classes 是一个 包含所有唯一类别的数组 , nt 是一个与 unique_classes 长度相同的数组,表示 每个类别的目标数量 。
unique_classes, nt = np.unique(target_cls, return_counts=True)
# 计算 unique_classes 数组的长度,即不同类别的数量,赋值给变量 nc 。这同时也可以理解为 检测到的类别数量 。
nc = unique_classes.shape[0] # number of classes, number of detections
# Create Precision-Recall curve and compute AP for each class
# 使用 np.linspace 函数生成一个从0到1的等差数列,包含1000个点,赋值给变量 x 。这个数列将用于 后续插值计算精度和召回率曲线 。同时,初始化一个空列表 prec_values ,用于 存储后续计算得到的精度值 。
x, prec_values = np.linspace(0, 1, 1000), []
# 这几行代码主要完成了对预测结果的置信度排序,计算了目标类别的一些基本信息(如类别数量和每个类别的目标数量),并初始化了用于后续计算和绘图的变量。这些操作为后续计算每个类别的平均精度(AP)等指标奠定了基础。
# 这段代码初始化了用于存储每个类别平均精度(AP)、精度曲线(p_curve)和召回率曲线(r_curve)的数组,并通过循环计算每个类别的相关指标。
# Average precision, precision and recall curves
# 初始化三个二维数组 ap 、 p_curve 和 r_curve ,它们的形状分别为 (nc, tp.shape[1]) 、 (nc, 1000) 和 (nc, 1000) 。 ap 用于存储 每个类别在不同IoU阈值下的平均精度 , p_curve 和 r_curve 分别用于存储 每个类别的精度曲线 和 召回率曲线 。
ap, p_curve, r_curve = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000))
# 对 unique_classes 中的每个类别进行循环, ci 是类别的索引, c 是类别的值。
for ci, c in enumerate(unique_classes):
# 创建一个布尔数组 i ,表示 预测类别 pred_cls 中等于当前类别 c 的位置。
i = pred_cls == c
# n_l 表示当前类别 c 的 目标数量 ,从 nt 数组中通过索引 ci 获取。
n_l = nt[ci] # number of labels
# n_p 表示当前类别 c 的 预测数量 ,通过计算布尔数组 i 的和得到。
n_p = i.sum() # number of predictions
# 如果当前类别的 预测数量 或 目标数量 为0,则跳过当前循环,不进行后续计算。
if n_p == 0 or n_l == 0:
continue
# Accumulate FPs and TPs
# 计算当前类别的 假正例 (False Positive)累计数量 fpc 和 真正例 (True Positive)累计数量 tpc 。通过 tp[i] 获取当前类别 c 的真正例情况,然后计算其补数的累计和得到 fpc ,计算其自身的累计和得到 tpc 。
fpc = (1 - tp[i]).cumsum(0)
tpc = tp[i].cumsum(0)
# Recall
# 计算当前类别的 召回率曲线 recall ,通过 tpc 除以目标数量 n_l (加上一个小常数 eps 防止除以0)。
recall = tpc / (n_l + eps) # recall curve
# np.interp(x, xp, fp, left=None, right=None, period=None)
# np.interp 是 NumPy 库中的一个函数,用于一维线性插值。给定一组数据点 x 和相应的值 xp ,以及一个新的查询点 x , np.interp 函数会找到 xp 中 x 值所在的区间,并使用线性插值来估计 x 对应的值。
# 参数说明 :
# x :查询点,即你想要插值的点。
# xp :数据点,一个一维数组,包含数据点的横坐标。
# fp : xp 对应的值,一个一维数组,包含数据点的纵坐标。
# left :可选参数,如果 x 中的值小于 xp 中的最小值,则使用这个值作为插值结果。
# right :可选参数,如果 x 中的值大于 xp 中的最大值,则使用这个值作为插值结果。
# period :可选参数,表示周期性,如果指定, xp 将被视为周期性的。
# 返回值 :
# 插值结果,一个与 x 形状相同的数组。
# 然后使用 np.interp 函数对召回率曲线进行插值,得到在 x (从1到0的等差数列)对应的召回率值,存储到 r_curve[ci] 中。这里使用负的 x 和 conf[i] 是因为 conf[i] 是降序的。
r_curve[ci] = np.interp(-x, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases
# Precision
# 计算当前类别的 精度曲线 precision ,通过 tpc 除以 tpc 和 fpc 的和得到。
precision = tpc / (tpc + fpc) # precision curve
# 然后同样使用 np.interp 函数对精度曲线进行插值,得到在 x 对应的精度值,存储到 p_curve[ci] 中。插值的 left 参数设置为1,表示当插值点在最左侧时,精度值取1。
p_curve[ci] = np.interp(-x, -conf[i], precision[:, 0], left=1) # p at pr_score
# AP from recall-precision curve
# 对每个IoU阈值进行循环,调用 compute_ap 函数计算当前类别在该IoU阈值下的平均精度 ap[ci, j] ,以及对应的精度和召回率曲线 mpre 和 mrec 。
for j in range(tp.shape[1]):
ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])
# 如果 plot 为 True 且当前是第一个IoU阈值(通常为0.5),则将插值得到的精度值 np.interp(x, mrec, mpre) 添加到 prec_values 列表中,用于后续绘制PR曲线。
if plot and j == 0:
prec_values.append(np.interp(x, mrec, mpre)) # precision at mAP@0.5
# 将 prec_values 列表转换为形状为(类别数 nc ,1000)的数组。
prec_values = np.array(prec_values) # (nc, 1000)
# 这段代码通过遍历每个类别,计算了每个类别的假正例和真正例累计数,进而得到了召回率和精度曲线,并通过插值操作使得这些曲线可以在统一的 x 值上进行比较。同时,计算了每个类别在不同IoU阈值下的平均精度,并在需要绘制曲线时收集了相关的精度值。这些计算结果为后续的性能评估和可视化提供了基础数据。
# 这段代码计算了F1分数曲线,筛选出有数据的类别名称,根据条件绘制相关的性能曲线,并计算一些关键的性能指标,最后返回一系列结果。
# Compute F1 (harmonic mean of precision and recall)
# 计算F1分数曲线。F1分数是精确度(precision)和召回率(recall)的调和平均数。这里加上一个小值eps是为了防止分母为零的情况。
f1_curve = 2 * p_curve * r_curve / (p_curve + r_curve + eps)
# 从 names 字典中筛选出在 unique_classes 中存在的类别的名称,生成一个列表。这一步确保只考虑有实际检测数据的类别。
names = [v for k, v in names.items() if k in unique_classes] # list: only classes that have data
# 将筛选后的类别名称列表转换为字典,键为类别索引,值为类别名称。这便于在绘图等操作中通过索引快速访问类别名称。
names = dict(enumerate(names)) # to dict
# 如果 plot 参数为 True ,则执行以下绘图操作。
if plot:
# 绘制精确度-召回率(PR)曲线,并保存到指定目录。 prec_values 是在不同召回率下插值得到的精确度值, ap 是每个类别的平均精度, names 提供了类别名称, on_plot 是一个可选的回调函数,用于在绘图时执行额外的操作。
plot_pr_curve(x, prec_values, ap, save_dir / f"{prefix}PR_curve.png", names, on_plot=on_plot)
# 绘制F1分数曲线,并保存到指定目录。 f1_curve 是在不同阈值下计算得到的F1分数, ylabel="F1" 指定了y轴的标签。
plot_mc_curve(x, f1_curve, save_dir / f"{prefix}F1_curve.png", names, ylabel="F1", on_plot=on_plot)
# 绘制精确度曲线,并保存到指定目录。 p_curve 是在不同阈值下插值得到的精确度值。
plot_mc_curve(x, p_curve, save_dir / f"{prefix}P_curve.png", names, ylabel="Precision", on_plot=on_plot)
# 绘制召回率曲线,并保存到指定目录。 r_curve 是在不同阈值下插值得到的召回率值。
plot_mc_curve(x, r_curve, save_dir / f"{prefix}R_curve.png", names, ylabel="Recall", on_plot=on_plot)
# 计算F1分数曲线在所有类别上的平均值,然后对其进行平滑处理(使用 smooth 函数,平滑参数为0.1),最后找到平滑后F1分数最大值的索引 i 。
i = smooth(f1_curve.mean(0), 0.1).argmax() # max F1 index
# 根据上一步得到的索引 i ,从 精确度曲线 p_curve 、 召回率曲线 r_curve 和 F1分数曲线 f1_curve 中提取 在最大F1分数处 的 精确度 p 、 召回率 r 和 F1分数 f1 值。
p, r, f1 = p_curve[:, i], r_curve[:, i], f1_curve[:, i] # max-F1 precision, recall, F1 values
# 计算真正例的数量 tp ,通过召回率 r 乘以每个类别的目标数量 nt ,然后四舍五入得到。
tp = (r * nt).round() # true positives
# 计算假正例的数量 fp ,通过真正例数量 tp 除以精确度 p (加上一个小值 eps 防止除以0),再减去真正例数量 tp ,最后四舍五入得到。
fp = (tp / (p + eps) - tp).round() # false positives
# 返回计算得到的 真正例数量 tp 、 假正例数量 fp 、 精确度 p 、 召回率 r 、 F1分数 f1 、 每个类别的平均精度 ap 、 唯一类别数组 unique_classes (转换为整数类型)、 精确度曲线 p_curve 、 召回率曲线 r_curve 、 F1分数曲线 f1_curve 、 x值数组 x 和 插值得到的精确度值 prec_values 。
return tp, fp, p, r, f1, ap, unique_classes.astype(int), p_curve, r_curve, f1_curve, x, prec_values
# 这段代码不仅计算了F1分数曲线,还根据条件绘制了精确度-召回率曲线、F1分数曲线、精确度曲线和召回率曲线。此外,还计算了在最大F1分数处的精确度、召回率和F1分数,以及相应的真正例和假正例数量。这些结果为评估目标检测模型的性能提供了全面的指标。
# 这段代码是一个用于目标检测评估的函数,它通过计算每个类别的真正例、假正例、精确度、召回率、F1分数和平均精度等指标,全面评估检测模型的性能。同时,它还提供了绘制相关曲线的功能,以便直观地展示模型在不同阈值下的表现。这些指标和曲线对于理解模型的优势和不足、指导模型优化具有重要意义。
17.class Metric(SimpleClass):
# 这段代码定义了一个名为 Metric 的类,该类继承自 SimpleClass ,用于存储和计算目标检测任务中的各种性能指标。
# 定义了一个名为 Metric 的类,继承自 SimpleClass 。
# class SimpleClass:
# -> 该类实现了几个特殊方法,用于自定义对象的字符串表示、属性访问等行为。
# -> def __str__(self):
class Metric(SimpleClass):
# 用于计算 YOLOv8 模型评估指标的类。
# 方法:
# ap50():所有类的 IoU 阈值为 0.5 时的 AP。返回:AP 分数列表。形状:(nc,) 或 []。
# ap():所有类的 IoU 阈值从 0.5 到 0.95 时的 AP。返回:AP 分数列表。形状:(nc,) 或 []。
# mp():所有类的平均准确率。返回:浮点数。
# mr():所有类别的平均召回率。返回:浮点数。
# map50():所有类别在 IoU 阈值为 0.5 时的平均 AP。返回:浮点数。
# map75():所有类别在 IoU 阈值为 0.75 时的平均 AP。返回:浮点数。
# map():所有类别在 IoU 阈值从 0.5 到 0.95 时的平均 AP。返回:浮点数。
# mean_results():结果的平均值,返回 mp、mr、map50、map。
# class_result(i):类别感知结果,返回 p[i]、r[i]、ap50[i]、ap[i]。
# maps():每个类别的 mAP。返回:mAP 分数数组,形状:(nc,)。
# fitness():将适应度模型作为指标的加权组合。返回:浮点数。
# update(results):使用新的评估结果更新指标属性。
"""
Class for computing evaluation metrics for YOLOv8 model.
Attributes:
p (list): Precision for each class. Shape: (nc,).
r (list): Recall for each class. Shape: (nc,).
f1 (list): F1 score for each class. Shape: (nc,).
all_ap (list): AP scores for all classes and all IoU thresholds. Shape: (nc, 10).
ap_class_index (list): Index of class for each AP score. Shape: (nc,).
nc (int): Number of classes.
Methods:
ap50(): AP at IoU threshold of 0.5 for all classes. Returns: List of AP scores. Shape: (nc,) or [].
ap(): AP at IoU thresholds from 0.5 to 0.95 for all classes. Returns: List of AP scores. Shape: (nc,) or [].
mp(): Mean precision of all classes. Returns: Float.
mr(): Mean recall of all classes. Returns: Float.
map50(): Mean AP at IoU threshold of 0.5 for all classes. Returns: Float.
map75(): Mean AP at IoU threshold of 0.75 for all classes. Returns: Float.
map(): Mean AP at IoU thresholds from 0.5 to 0.95 for all classes. Returns: Float.
mean_results(): Mean of results, returns mp, mr, map50, map.
class_result(i): Class-aware result, returns p[i], r[i], ap50[i], ap[i].
maps(): mAP of each class. Returns: Array of mAP scores, shape: (nc,).
fitness(): Model fitness as a weighted combination of metrics. Returns: Float.
update(results): Update metric attributes with new evaluation results.
"""
# 定义了 Metric 类的初始化方法。
def __init__(self) -> None:
# 初始化 Metric 实例,用于计算 YOLOv8 模型的评估指标。
"""Initializes a Metric instance for computing evaluation metrics for the YOLOv8 model."""
# 初始化类的属性。
# 存储每个类别的精确度(precision)。
self.p = [] # (nc, )
# 存储每个类别的召回率(recall)。
self.r = [] # (nc, )
# 存储每个类别的F1分数。
self.f1 = [] # (nc, )
# 存储每个类别在不同IoU阈值下的平均精度(AP)。
self.all_ap = [] # (nc, 10)
# 存储每个类别的索引。
self.ap_class_index = [] # (nc, )
# 存储类别总数。
self.nc = 0
# 定义了一个属性 ap50 ,用于获取IoU阈值为0.5时的平均精度。
# property(fget=None, fset=None, fdel=None, doc=None)
# 在 Python 中, property 是一个内置函数,用于创建属性访问器(getter)、修改器(setter)和删除器(deleter)。 property 函数通常用于将类的方法转换为属性,使得这些方法可以像访问普通属性一样被访问。
# 参数 :
# fget :一个函数,用于获取属性值。如果没有提供,则属性将只写。
# fset :一个函数,用于设置属性值。如果没有提供,则属性将只读。
# fdel :一个函数,用于删除属性。如果没有提供,则属性将不能被删除。
# doc :属性的文档字符串。
# 返回值 :
# 返回一个属性对象,该对象可以用于定义管理属性访问的方法。
# 用法示例 :
# @property
# property 函数是 Python 中实现 getter/setter/ deleter 的标准方式,它允许在访问属性时执行额外的逻辑,例如验证或更新其他属性。
@property
def ap50(self):
# 返回所有类别在 IoU 阈值为 0.5 时的平均精度 (AP)。
# 返回:
# (np.ndarray, list):形状为 (nc,) 的数组,每个类别包含 AP50 值,如果不可用,则返回空列表。
"""
Returns the Average Precision (AP) at an IoU threshold of 0.5 for all classes.
Returns:
(np.ndarray, list): Array of shape (nc,) with AP50 values per class, or an empty list if not available.
"""
# 如果 self.all_ap 不为空,则返回第一列(IoU阈值为0.5的AP值),否则返回空列表。
return self.all_ap[:, 0] if len(self.all_ap) else []
# 定义了一个属性 ap ,用于获取每个类别的平均精度。
@property
def ap(self):
# 返回所有类别的 IoU 阈值为 0.5-0.95 的平均精度 (AP)。
# 返回:
# (np.ndarray, list):形状为 (nc,) 的数组,每个类别的 AP50-95 值,如果不可用,则返回空列表。
"""
Returns the Average Precision (AP) at an IoU threshold of 0.5-0.95 for all classes.
Returns:
(np.ndarray, list): Array of shape (nc,) with AP50-95 values per class, or an empty list if not available.
"""
# 如果 self.all_ap 不为空,则计算每个类别的平均AP值,否则返回空列表。
return self.all_ap.mean(1) if len(self.all_ap) else []
# 定义了一个属性 mp ,用于获取所有类别的平均精确度。
@property
def mp(self):
# 返回所有类别的平均精度。
"""
Returns the Mean Precision of all classes.
Returns:
(float): The mean precision of all classes.
"""
# 如果 self.p 不为空,则计算所有类别的平均精确度,否则返回0.0。
return self.p.mean() if len(self.p) else 0.0
# 定义了一个属性 mr ,用于获取所有类别的平均召回率。
@property
def mr(self):
# 返回所有类别的平均召回率。
"""
Returns the Mean Recall of all classes.
Returns:
(float): The mean recall of all classes.
"""
# 如果 self.r 不为空,则计算所有类别的平均召回率,否则返回0.0。
return self.r.mean() if len(self.r) else 0.0
# 定义了一个属性 map50 ,用于获取所有类别的平均AP(IoU阈值为0.5)。
@property
def map50(self):
# 返回 IoU 阈值为 0.5 时的平均精度 (mAP)。
"""
Returns the mean Average Precision (mAP) at an IoU threshold of 0.5.
Returns:
(float): The mAP at an IoU threshold of 0.5.
"""
# 如果 self.all_ap 不为空,则计算所有类别的平均AP(IoU阈值为0.5),否则返回0.0。
return self.all_ap[:, 0].mean() if len(self.all_ap) else 0.0
# 定义了一个属性 map75 ,用于获取所有类别的平均AP(IoU阈值为0.75)。
@property
def map75(self):
# 返回 IoU 阈值为 0.75 时的平均精度 (mAP)。
"""
Returns the mean Average Precision (mAP) at an IoU threshold of 0.75.
Returns:
(float): The mAP at an IoU threshold of 0.75.
"""
# 如果 self.all_ap 不为空,则计算所有类别的平均AP(IoU阈值为0.75),否则返回0.0。
return self.all_ap[:, 5].mean() if len(self.all_ap) else 0.0
# 定义了一个属性 map ,用于获取所有类别的平均AP(所有IoU阈值)。
@property
def map(self):
# 返回 IoU 阈值为 0.5 - 0.95 时的平均精度 (mAP),步长为 0.05。
"""
Returns the mean Average Precision (mAP) over IoU thresholds of 0.5 - 0.95 in steps of 0.05.
Returns:
(float): The mAP over IoU thresholds of 0.5 - 0.95 in steps of 0.05.
"""
# 如果 self.all_ap 不为空,则计算所有类别的平均AP(所有IoU阈值),否则返回0.0。
return self.all_ap.mean() if len(self.all_ap) else 0.0
# 定义了一个方法 mean_results ,用于返回所有类别的平均精确度、召回率、mAP@0.5和mAP。
def mean_results(self):
# 结果的平均值,返回 mp、mr、map50、map。
"""Mean of results, return mp, mr, map50, map."""
# 返回一个列表,包含所有类别的 平均精确度 、 召回率 、 mAP@0.5 和 mAP 。
return [self.mp, self.mr, self.map50, self.map]
# 定义了一个方法 class_result ,用于返回第 i 个类别的精确度、召回率、AP@0.5和AP。
def class_result(self, i):
# 类别感知结果,返回 p[i]、r[i]、ap50[i]、ap[i]。
"""Class-aware result, return p[i], r[i], ap50[i], ap[i]."""
# 返回第 i 个类别的精确度、召回率、AP@0.5和AP。
return self.p[i], self.r[i], self.ap50[i], self.ap[i]
# 定义了一个属性 maps ,用于返回每个类别的mAP。
@property
def maps(self):
# 每个类别的平均精度 (mAP) 。
"""MAP of each class."""
# 初始化一个长度为 self.nc 的数组 maps ,并将其所有元素设置为 self.map 。
maps = np.zeros(self.nc) + self.map
# 然后,根据 self.ap_class_index 更新每个类别的mAP值。
for i, c in enumerate(self.ap_class_index):
maps[c] = self.ap[i]
return maps
# 定义了一个方法 fitness ,用于计算模型的综合性能指标。
def fitness(self):
# 模型适应度作为指标的加权组合。
"""Model fitness as a weighted combination of metrics."""
# 定义权重 w ,并计算加权和,返回模型的综合性能指标。
w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95]
return (np.array(self.mean_results()) * w).sum()
# 定义了一个方法 update ,用于更新类的属性。
def update(self, results):
# 使用一组新结果更新模型的评估指标。
# 参数:
# results(元组):包含以下评估指标的元组:
# - p(列表):每个类的准确率。形状:(nc,)。
# - r(列表):每个类的召回率。形状:(nc,)。
# - f1(列表):每个类的 F1 分数。形状:(nc,)。
# - all_ap(列表):所有类和所有 IoU 阈值的 AP 分数。形状:(nc, 10)。
# - ap_class_index(列表):每个 AP 分数的类索引。形状:(nc,)。
# 副作用:
# 根据 `results` 元组中提供的值更新类属性 `self.p`、`self.r`、`self.f1`、`self.all_ap` 和 `self.ap_class_index`。
"""
Updates the evaluation metrics of the model with a new set of results.
Args:
results (tuple): A tuple containing the following evaluation metrics:
- p (list): Precision for each class. Shape: (nc,).
- r (list): Recall for each class. Shape: (nc,).
- f1 (list): F1 score for each class. Shape: (nc,).
- all_ap (list): AP scores for all classes and all IoU thresholds. Shape: (nc, 10).
- ap_class_index (list): Index of class for each AP score. Shape: (nc,).
Side Effects:
Updates the class attributes `self.p`, `self.r`, `self.f1`, `self.all_ap`, and `self.ap_class_index` based
on the values provided in the `results` tuple.
"""
# 将输入的 results 元组中的值分别赋值给类的属性。
(
self.p,
self.r,
self.f1,
self.all_ap,
self.ap_class_index,
self.p_curve,
self.r_curve,
self.f1_curve,
self.px,
self.prec_values,
) = results
# 定义了一个属性 curves ,用于返回性能曲线。
@property
def curves(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个空列表(这里可以扩展为返回实际的曲线数据)。
return []
# 定义了一个属性 curves_results ,用于返回性能曲线的结果。
@property
def curves_results(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个列表,包含四个子列表,每个子列表包含 x值 、 y值 和 对应的标签 ,用于绘制不同的性能曲线。
return [
[self.px, self.prec_values, "Recall", "Precision"],
[self.px, self.f1_curve, "Confidence", "F1"],
[self.px, self.p_curve, "Confidence", "Precision"],
[self.px, self.r_curve, "Confidence", "Recall"],
]
# Metric 类继承自 SimpleClass ,提供了丰富的属性和方法,用于存储和计算目标检测任务中的各种性能指标,如精确度、召回率、F1分数、平均精度(AP)等。 通过属性装饰器 @property ,类提供了多个只读属性,方便用户获取特定的性能指标。 update 方法允许用户更新类的属性,方便在不同的评估阶段更新性能指标。 curves_results 属性提供了绘制性能曲线所需的数据,方便用户进行可视化分析。 这个类的设计使得性能评估更加模块化和易于管理,特别是在复杂的机器学习项目中。
18.class DetMetrics(SimpleClass):
# 这段代码定义了一个名为 DetMetrics 的类,该类继承自 SimpleClass ,用于处理目标检测任务中的性能评估。
class DetMetrics(SimpleClass):
# 此类是用于计算检测指标(例如对象检测模型的精度、召回率和平均精度 (mAP))的实用程序类。
# 方法:
# process(tp, conf, pred_cls, target_cls):使用最新一批预测更新度量结果。
# keys:返回用于访问计算的检测指标的键列表。
# mean_results:返回计算的检测指标的平均值列表。
# class_result(i):返回特定类的计算检测指标的值列表。
# maps:返回不同 IoU 阈值的平均精度 (mAP) 值字典。
# fitness:根据计算的检测指标计算适应度分数。
# ap_class_index:返回按平均精度 (AP) 值排序的类索引列表。
# results_dict:返回将检测指标键映射到其计算值的字典。
# curves:TODO
# curves_results:TODO
"""
This class is a utility class for computing detection metrics such as precision, recall, and mean average precision
(mAP) of an object detection model.
Args:
save_dir (Path): A path to the directory where the output plots will be saved. Defaults to current directory.
plot (bool): A flag that indicates whether to plot precision-recall curves for each class. Defaults to False.
on_plot (func): An optional callback to pass plots path and data when they are rendered. Defaults to None.
names (tuple of str): A tuple of strings that represents the names of the classes. Defaults to an empty tuple.
Attributes:
save_dir (Path): A path to the directory where the output plots will be saved.
plot (bool): A flag that indicates whether to plot the precision-recall curves for each class.
on_plot (func): An optional callback to pass plots path and data when they are rendered.
names (tuple of str): A tuple of strings that represents the names of the classes.
box (Metric): An instance of the Metric class for storing the results of the detection metrics.
speed (dict): A dictionary for storing the execution time of different parts of the detection process.
Methods:
process(tp, conf, pred_cls, target_cls): Updates the metric results with the latest batch of predictions.
keys: Returns a list of keys for accessing the computed detection metrics.
mean_results: Returns a list of mean values for the computed detection metrics.
class_result(i): Returns a list of values for the computed detection metrics for a specific class.
maps: Returns a dictionary of mean average precision (mAP) values for different IoU thresholds.
fitness: Computes the fitness score based on the computed detection metrics.
ap_class_index: Returns a list of class indices sorted by their average precision (AP) values.
results_dict: Returns a dictionary that maps detection metric keys to their computed values.
curves: TODO
curves_results: TODO
"""
# 这段代码是 DetMetrics 类的初始化方法 __init__ 的定义,用于设置类的初始状态和属性。
# 定义了 DetMetrics 类的初始化方法 __init__ ,接受以下参数 :
# 1.save_dir :一个 Path 对象,表示保存图像和结果的目录,默认为当前目录( Path(".") )。
# 2.plot :一个布尔值,表示是否绘制性能曲线,默认为 False 。
# 3.on_plot :一个可调用对象(如函数),在绘制曲线时会被调用,用于执行额外的操作,默认为 None 。
# 4.names :一个元组,包含类别名称,默认为空元组 () 。
def __init__(self, save_dir=Path("."), plot=False, on_plot=None, names=()) -> None:
# 使用保存目录、绘图标志、回调函数和类名初始化 DetMetrics 实例。
"""Initialize a DetMetrics instance with a save directory, plot flag, callback function, and class names."""
# 将传入的 save_dir 参数值赋给实例变量 self.save_dir ,用于 存储结果和图像的目录 。
self.save_dir = save_dir
# 将传入的 plot 参数值赋给实例变量 self.plot ,用于 控制是否绘制性能曲线 。
self.plot = plot
# 将传入的 on_plot 参数值赋给实例变量 self.on_plot ,用于 在绘制曲线时执行额外的操作 。
self.on_plot = on_plot
# 将传入的 names 参数值赋给实例变量 self.names ,用于 存储类别名称 。
self.names = names
# 创建一个 Metric 类的实例,并赋给实例变量 self.box 。 Metric 类用于存储和计算性能指标,如精确度、召回率、F1分数、平均精度(AP)等。
self.box = Metric()
# 初始化一个字典 self.speed ,用于存储不同阶段的处理时间。字典的键包括 "preprocess" 、 "inference" 、 "loss" 和 "postprocess" ,初始值均为0.0。这些键分别代表 预处理 、 推理 、 损失计算 和 后处理阶段 的处理时间。
self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0}
# 将实例变量 self.task 设置为字符串 "detect" ,表示当前任务是目标检测。
self.task = "detect"
# __init__ 方法用于初始化 DetMetrics 类的实例,设置保存目录、绘图选项、类别名称等属性。 创建了一个 Metric 类的实例,用于后续的性能指标计算。 初始化了一个字典来记录不同处理阶段的耗时,方便性能分析。 设置了任务类型为 "detect" ,明确该类用于目标检测任务。 这个初始化方法为类的其他方法提供了必要的上下文和初始状态,使得类的实例能够正确地处理和评估目标检测任务的性能。
# 这段代码定义了 DetMetrics 类中的 process 方法,用于处理目标检测任务中的性能评估数据。
# 定义了 process 方法,接受以下参数 :
# 1.tp :每个预测的真正例(True Positive)情况,其形状为(检测数,IoU阈值数)。
# 2.conf :每个预测的置信度。
# 3.pred_cls :每个预测的类别。
# 4.target_cls :每个目标的真实类别。
def process(self, tp, conf, pred_cls, target_cls):
# 处理对象检测的预测结果并更新指标。
"""Process predicted results for object detection and update metrics."""
# 调用 ap_per_class 函数,传入 tp 、 conf 、 pred_cls 和 target_cls ,并设置绘图选项、保存目录、类别名称和绘图回调函数。 ap_per_class 函数返回多个结果,这里通过切片 [2:] 获取从第三个元素开始的结果,这些结果通常包括精确度、召回率、F1分数、AP等性能指标。
results = ap_per_class(
tp,
conf,
pred_cls,
target_cls,
plot=self.plot,
save_dir=self.save_dir,
names=self.names,
on_plot=self.on_plot,
)[2:]
# 将 Metric 对象 self.box 的类别总数 nc 设置为 类别名称列表 self.names 的长度。这一步确保 Metric 对象知道有多少个类别,以便正确地处理和存储性能指标。
self.box.nc = len(self.names)
# 调用 Metric 对象 self.box 的 update 方法,传入从 ap_per_class 函数获取的性能指标结果。 update 方法将这些结果更新到 Metric 对象的内部状态中,以便后续可以方便地访问和使用这些性能指标。
self.box.update(results)
# process 方法是 DetMetrics 类中的一个关键方法,用于处理目标检测任务中的性能评估数据。 通过调用 ap_per_class 函数,方法计算了各种性能指标,并将这些指标更新到 Metric 对象中。 方法还设置了 Metric 对象的类别总数,确保其内部状态与当前任务的类别数量一致。 这个方法的设计使得 DetMetrics 类能够灵活地处理和评估目标检测任务的性能,提供了一个简洁的接口来更新和管理性能指标。
# 定义了一个属性 keys ,用于返回性能指标的键名。
@property
def keys(self):
# 返回用于访问特定指标的键列表。
"""Returns a list of keys for accessing specific metrics."""
# 返回一个列表,包含性能指标的键名。
return ["metrics/precision(B)", "metrics/recall(B)", "metrics/mAP50(B)", "metrics/mAP50-95(B)"]
# 定义了一个方法 mean_results ,用于返回所有类别的平均性能指标。
def mean_results(self):
# 计算检测到的对象的平均值并返回精度、召回率、mAP50 和 mAP50-95。
"""Calculate mean of detected objects & return precision, recall, mAP50, and mAP50-95."""
# 调用 Metric 对象的 mean_results 方法,返回平均性能指标。
return self.box.mean_results()
# 定义了一个方法 class_result ,用于返回第 i 个类别的性能指标。
def class_result(self, i):
# 返回评估对象检测模型在特定类别上的性能的结果。
"""Return the result of evaluating the performance of an object detection model on a specific class."""
# 调用 Metric 对象的 class_result 方法,返回第 i 个类别的性能指标。
return self.box.class_result(i)
# 定义了一个属性 maps ,用于返回每个类别的mAP。
@property
def maps(self):
# 返回每个类别的平均精度 (mAP) 分数。
"""Returns mean Average Precision (mAP) scores per class."""
# 调用 Metric 对象的 maps 属性,返回每个类别的mAP。
return self.box.maps
# 定义了一个属性 fitness ,用于返回模型的综合性能指标。
@property
def fitness(self):
# 返回边界框对象的适应度。
"""Returns the fitness of box object."""
# 调用 Metric 对象的 fitness 方法,返回模型的综合性能指标。
return self.box.fitness()
# 定义了一个属性 ap_class_index ,用于返回每个类别的索引。
@property
def ap_class_index(self):
# 返回每个类别的平均精度指数。
"""Returns the average precision index per class."""
# 调用 Metric 对象的 ap_class_index 属性,返回每个类别的索引。
return self.box.ap_class_index
# 定义了一个属性 results_dict ,用于返回性能指标的字典。
@property
def results_dict(self):
# 返回计算的性能指标和统计数据的字典。
"""Returns dictionary of computed performance metrics and statistics."""
# 返回一个字典,键为 keys 列表加上 "fitness" ,值为平均性能指标加上综合性能指标。
return dict(zip(self.keys + ["fitness"], self.mean_results() + [self.fitness]))
# 定义了一个属性 curves ,用于返回性能曲线的名称。
@property
def curves(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个列表,包含性能曲线的名称。
return ["Precision-Recall(B)", "F1-Confidence(B)", "Precision-Confidence(B)", "Recall-Confidence(B)"]
# 定义了一个属性 curves_results ,用于返回性能曲线的结果。
@property
def curves_results(self):
# 返回计算的性能指标和统计数据的字典。
"""Returns dictionary of computed performance metrics and statistics."""
# 调用 Metric 对象的 curves_results 属性,返回性能曲线的结果。
return self.box.curves_results
# DetMetrics 类继承自 SimpleClass ,提供了一个完整的框架来处理目标检测任务中的性能评估。 通过 process 方法,类可以处理检测结果并更新性能指标。 类提供了多个属性和方法,用于获取和返回各种性能指标,如精确度、召回率、mAP等。 results_dict 属性提供了一个字典,方便用户获取和使用性能指标。 curves 和 curves_results 属性提供了绘制性能曲线所需的数据,方便用户进行可视化分析。 这个类的设计使得性能评估更加模块化和易于管理,特别是在复杂的机器学习项目中。
19.class SegmentMetrics(SimpleClass):
# 这段代码定义了一个名为 SegmentMetrics 的类,该类继承自 SimpleClass ,用于处理语义分割任务中的性能评估。
class SegmentMetrics(SimpleClass):
# 计算并汇总给定类集的检测和分割指标。
# 方法:
# process(tp_m、tp_b、conf、pred_cls、target_cls):处理给定预测集的指标。
# mean_results():返回所有类别的检测和分割指标的平均值。
# class_result(i):返回类“i”的检测和分割指标。
# map:返回 IoU 阈值的平均平均精度 (mAP) 分数,范围从 0.50 到 0.95。
# fitness:返回适应度分数,即指标的单一加权组合。
# ap_class_index:返回用于计算平均精度 (AP) 的类索引列表。
# results_dict:返回包含所有检测和分割指标以及适应度分数的字典。
"""
Calculates and aggregates detection and segmentation metrics over a given set of classes.
Args:
save_dir (Path): Path to the directory where the output plots should be saved. Default is the current directory.
plot (bool): Whether to save the detection and segmentation plots. Default is False.
on_plot (func): An optional callback to pass plots path and data when they are rendered. Defaults to None.
names (list): List of class names. Default is an empty list.
Attributes:
save_dir (Path): Path to the directory where the output plots should be saved.
plot (bool): Whether to save the detection and segmentation plots.
on_plot (func): An optional callback to pass plots path and data when they are rendered.
names (list): List of class names.
box (Metric): An instance of the Metric class to calculate box detection metrics.
seg (Metric): An instance of the Metric class to calculate mask segmentation metrics.
speed (dict): Dictionary to store the time taken in different phases of inference.
Methods:
process(tp_m, tp_b, conf, pred_cls, target_cls): Processes metrics over the given set of predictions.
mean_results(): Returns the mean of the detection and segmentation metrics over all the classes.
class_result(i): Returns the detection and segmentation metrics of class `i`.
maps: Returns the mean Average Precision (mAP) scores for IoU thresholds ranging from 0.50 to 0.95.
fitness: Returns the fitness scores, which are a single weighted combination of metrics.
ap_class_index: Returns the list of indices of classes used to compute Average Precision (AP).
results_dict: Returns the dictionary containing all the detection and segmentation metrics and fitness score.
"""
# 这段代码是 SegmentMetrics 类的初始化方法 __init__ 的定义,用于设置类的初始状态和属性。
# 定义了 SegmentMetrics 类的初始化方法 __init__ ,接受以下参数 :
# 1.save_dir :一个 Path 对象,表示保存图像和结果的目录,默认为当前目录( Path(".") )。
# 2.plot :一个布尔值,表示是否绘制性能曲线,默认为 False 。
# 3.on_plot :一个可调用对象(如函数),在绘制曲线时会被调用,用于执行额外的操作,默认为 None 。
# 4.names :一个元组,包含类别名称,默认为空元组 () 。
def __init__(self, save_dir=Path("."), plot=False, on_plot=None, names=()) -> None:
# 使用保存目录、绘图标志、回调函数和类名初始化 SegmentMetrics 实例。
"""Initialize a SegmentMetrics instance with a save directory, plot flag, callback function, and class names."""
# 将传入的 save_dir 参数值赋给实例变量 self.save_dir ,用于存储结果和图像的目录。
self.save_dir = save_dir
# 将传入的 plot 参数值赋给实例变量 self.plot ,用于控制是否绘制性能曲线。
self.plot = plot
# 将传入的 on_plot 参数值赋给实例变量 self.on_plot ,用于在绘制曲线时执行额外的操作。
self.on_plot = on_plot
# 将传入的 names 参数值赋给实例变量 self.names ,用于存储类别名称。
self.names = names
# 创建一个 Metric 类的实例,并赋给实例变量 self.box 。 Metric 类用于存储和计算边界框(box)的性能指标,如精确度、召回率、F1分数、平均精度(AP)等。
self.box = Metric()
# 创建另一个 Metric 类的实例,并赋给实例变量 self.seg 。这个实例用于存储和计算分割掩码(seg)的性能指标。
self.seg = Metric()
# 初始化一个字典 self.speed ,用于存储不同阶段的处理时间。字典的键包括 "preprocess" 、 "inference" 、 "loss" 和 "postprocess" ,初始值均为0.0。这些键分别代表预处理、推理、损失计算和后处理阶段的处理时间。
self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0}
# 将实例变量 self.task 设置为字符串 "segment" ,表示当前任务是语义分割。
self.task = "segment"
# __init__ 方法用于初始化 SegmentMetrics 类的实例,设置保存目录、绘图选项、类别名称等属性。 创建了两个 Metric 类的实例,分别用于处理边界框和分割掩码的性能指标。 初始化了一个字典来记录不同处理阶段的耗时,方便性能分析。 设置了任务类型为 "segment" ,明确该类用于语义分割任务。 这个初始化方法为类的其他方法提供了必要的上下文和初始状态,使得类的实例能够正确地处理和评估语义分割任务的性能。
# 这段代码定义了 SegmentMetrics 类中的 process 方法,用于处理语义分割任务中的性能评估数据。该方法分别计算边界框(box)和分割掩码(mask)的性能指标,并更新相应的 Metric 对象。
# 定义了 process 方法,接受以下参数 :
# 1.tp :一个二维数组,表示每个预测的边界框的真正例(True Positive)情况,其形状为(检测数,IoU阈值数)。
# 2.tp_m :一个二维数组,表示每个预测的分割掩码的真正例情况,其形状为(检测数,IoU阈值数)。
# 3.conf :一个一维数组,表示每个预测的置信度。
# 4.pred_cls :一个一维数组,表示每个预测的类别。
# 5.target_cls :一个一维数组,表示每个目标的真实类别。
def process(self, tp, tp_m, conf, pred_cls, target_cls):
# 处理给定预测集的检测和分割指标。
"""
Processes the detection and segmentation metrics over the given set of predictions.
Args:
tp (list): List of True Positive boxes.
tp_m (list): List of True Positive masks.
conf (list): List of confidence scores.
pred_cls (list): List of predicted classes.
target_cls (list): List of target classes.
"""
# 调用 ap_per_class 函数计算 分割掩码的性能指标 。传入 tp_m 、 conf 、 pred_cls 和 target_cls ,并设置绘图选项、保存目录、类别名称和前缀为 "Mask" 。 ap_per_class 函数返回多个结果,这里通过切片 [2:] 获取从第三个元素开始的结果,这些结果通常包括精确度、召回率、F1分数、AP等性能指标。
results_mask = ap_per_class(
tp_m,
conf,
pred_cls,
target_cls,
plot=self.plot,
on_plot=self.on_plot,
save_dir=self.save_dir,
names=self.names,
prefix="Mask",
)[2:]
# 将 Metric 对象 self.seg 的类别总数 nc 设置为类别名称列表 self.names 的长度。这一步确保 Metric 对象知道有多少个类别,以便正确地处理和存储性能指标。
self.seg.nc = len(self.names)
# 调用 Metric 对象 self.seg 的 update 方法,传入从 ap_per_class 函数获取的分割掩码的性能指标结果。 update 方法将这些结果更新到 Metric 对象的内部状态中,以便后续可以方便地访问和使用这些性能指标。
self.seg.update(results_mask)
# 调用 ap_per_class 函数计算 边界框的性能指标 。传入 tp 、 conf 、 pred_cls 和 target_cls ,并设置绘图选项、保存目录、类别名称和前缀为 "Box" 。同样通过切片 [2:] 获取从第三个元素开始的结果。
results_box = ap_per_class(
tp,
conf,
pred_cls,
target_cls,
plot=self.plot,
on_plot=self.on_plot,
save_dir=self.save_dir,
names=self.names,
prefix="Box",
)[2:]
# 将 Metric 对象 self.box 的类别总数 nc 设置为类别名称列表 self.names 的长度。
self.box.nc = len(self.names)
# 调用 Metric 对象 self.box 的 update 方法,传入从 ap_per_class 函数获取的边界框的性能指标结果。 update 方法将这些结果更新到 Metric 对象的内部状态中。
self.box.update(results_box)
# process 方法是 SegmentMetrics 类中的一个关键方法,用于处理语义分割任务中的性能评估数据。 通过调用 ap_per_class 函数,方法分别计算了边界框和分割掩码的性能指标,并将这些指标更新到相应的 Metric 对象中。 方法还设置了 Metric 对象的类别总数,确保其内部状态与当前任务的类别数量一致。 这个方法的设计使得 SegmentMetrics 类能够灵活地处理和评估语义分割任务的性能,提供了一个简洁的接口来更新和管理性能指标。
# 定义了一个属性 keys ,用于返回性能指标的键名。
@property
def keys(self):
# 返回用于访问指标的键列表。
"""Returns a list of keys for accessing metrics."""
# 返回一个列表,包含边界框和分割掩码的性能指标键名。
return [
"metrics/precision(B)",
"metrics/recall(B)",
"metrics/mAP50(B)",
"metrics/mAP50-95(B)",
"metrics/precision(M)",
"metrics/recall(M)",
"metrics/mAP50(M)",
"metrics/mAP50-95(M)",
]
# 定义了一个方法 mean_results ,用于返回所有类别的平均性能指标。
def mean_results(self):
# 返回边界框和分割结果的平均指标。
"""Return the mean metrics for bounding box and segmentation results."""
# 返回边界框和分割掩码的平均性能指标。
return self.box.mean_results() + self.seg.mean_results()
# 定义了一个方法 class_result ,用于返回第 i 个类别的性能指标。
def class_result(self, i):
# 返回指定类别索引的分类结果。
"""Returns classification results for a specified class index."""
# 返回第 i 个类别的边界框和分割掩码的性能指标。
return self.box.class_result(i) + self.seg.class_result(i)
# 定义了一个属性 maps ,用于返回每个类别的mAP。
@property
def maps(self):
# 返回对象检测和语义分割模型的 mAP 分数。
"""Returns mAP scores for object detection and semantic segmentation models."""
# 返回边界框和分割掩码的mAP。
return self.box.maps + self.seg.maps
# 定义了一个属性 fitness ,用于返回模型的综合性能指标。
@property
def fitness(self):
# 获取分割和边界框模型的适应度分数。
"""Get the fitness score for both segmentation and bounding box models."""
# 返回分割掩码和边界框的综合性能指标。
return self.seg.fitness() + self.box.fitness()
# 定义了一个属性 ap_class_index ,用于返回每个类别的索引。
@property
def ap_class_index(self):
# 边界框和掩膜具有相同的 ap_class_index。
"""Boxes and masks have the same ap_class_index."""
# 返回边界框的类别索引。
return self.box.ap_class_index
# 定义了一个属性 results_dict ,用于返回性能指标的字典。
@property
def results_dict(self):
# 返回对象检测模型的结果以供评估。
"""Returns results of object detection model for evaluation."""
# 返回一个字典,键为 keys 列表加上 "fitness" ,值为平均性能指标加上综合性能指标。
return dict(zip(self.keys + ["fitness"], self.mean_results() + [self.fitness]))
# 定义了一个属性 curves ,用于返回性能曲线的名称。
@property
def curves(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个列表,包含边界框和分割掩码的性能曲线名称。
return [
"Precision-Recall(B)",
"F1-Confidence(B)",
"Precision-Confidence(B)",
"Recall-Confidence(B)",
"Precision-Recall(M)",
"F1-Confidence(M)",
"Precision-Confidence(M)",
"Recall-Confidence(M)",
]
# 定义了一个属性 curves_results ,用于返回性能曲线的结果。
@property
def curves_results(self):
# 返回计算的性能指标和统计数据的字典。
"""Returns dictionary of computed performance metrics and statistics."""
# 返回边界框和分割掩码的性能曲线结果。
return self.box.curves_results + self.seg.curves_results
# SegmentMetrics 类继承自 SimpleClass ,提供了一个完整的框架来处理语义分割任务中的性能评估。 通过 process 方法,类可以处理检测结果并更新边界框和分割掩码的性能指标。 类提供了多个属性和方法,用于获取和返回各种性能指标,如精确度、召回率、mAP等。 results_dict 属性提供了一个字典,方便用户获取和使用性能指标。 curves 和 curves_results 属性提供了绘制性能曲线所需的数据,方便用户进行可视化分析。 这个类的设计使得性能评估更加模块化和易于管理,特别是在复杂的机器学习项目中。
20.class PoseMetrics(SegmentMetrics):
# 这段代码定义了一个名为 PoseMetrics 的类,该类继承自 SegmentMetrics ,用于处理姿态估计任务中的性能评估。
class PoseMetrics(SegmentMetrics):
# 计算并汇总给定类集的检测和姿势指标。
# 方法:
# process(tp_m、tp_b、conf、pred_cls、target_cls):处理给定预测集的指标。
# mean_results():返回所有类别的检测和分割指标的平均值。
# class_result(i):返回类“i”的检测和分割指标。
# map:返回 IoU 阈值的平均平均精度 (mAP) 分数,范围从 0.50 到 0.95。
# fitness:返回适应度分数,即指标的单一加权组合。
# ap_class_index:返回用于计算平均精度 (AP) 的类索引列表。
# results_dict:返回包含所有检测和分割指标以及适应度分数的字典。
"""
Calculates and aggregates detection and pose metrics over a given set of classes.
Args:
save_dir (Path): Path to the directory where the output plots should be saved. Default is the current directory.
plot (bool): Whether to save the detection and segmentation plots. Default is False.
on_plot (func): An optional callback to pass plots path and data when they are rendered. Defaults to None.
names (list): List of class names. Default is an empty list.
Attributes:
save_dir (Path): Path to the directory where the output plots should be saved.
plot (bool): Whether to save the detection and segmentation plots.
on_plot (func): An optional callback to pass plots path and data when they are rendered.
names (list): List of class names.
box (Metric): An instance of the Metric class to calculate box detection metrics.
pose (Metric): An instance of the Metric class to calculate mask segmentation metrics.
speed (dict): Dictionary to store the time taken in different phases of inference.
Methods:
process(tp_m, tp_b, conf, pred_cls, target_cls): Processes metrics over the given set of predictions.
mean_results(): Returns the mean of the detection and segmentation metrics over all the classes.
class_result(i): Returns the detection and segmentation metrics of class `i`.
maps: Returns the mean Average Precision (mAP) scores for IoU thresholds ranging from 0.50 to 0.95.
fitness: Returns the fitness scores, which are a single weighted combination of metrics.
ap_class_index: Returns the list of indices of classes used to compute Average Precision (AP).
results_dict: Returns the dictionary containing all the detection and segmentation metrics and fitness score.
"""
# 定义了 PoseMetrics 类的初始化方法 __init__ ,接受以下参数 :
# 1.save_dir :一个 Path 对象,表示保存图像和结果的目录,默认为当前目录。
# 2.plot :一个布尔值,表示是否绘制性能曲线,默认为 False 。
# 3.on_plot :一个可调用对象(如函数),在绘制曲线时会被调用,用于执行额外的操作,默认为 None 。
# 4.names :一个元组,包含类别名称,默认为空元组。
def __init__(self, save_dir=Path("."), plot=False, on_plot=None, names=()) -> None:
# 使用目录路径、类名和绘图选项初始化 PoseMetrics 类。
"""Initialize the PoseMetrics class with directory path, class names, and plotting options."""
# 调用父类 SegmentMetrics 的初始化方法,传入 save_dir 、 plot 和 names 参数。这里漏掉了 on_plot 参数,应该是 super().__init__(save_dir, plot, on_plot, names) 。
super().__init__(save_dir, plot, names)
# 再次初始化类的属性,存储传入的参数值。这一步实际上是多余的,因为父类的初始化方法已经完成了这些操作。
self.save_dir = save_dir
self.plot = plot
self.on_plot = on_plot
self.names = names
# 创建两个 Metric 类的实例,分别用于存储和计算边界框(box)和姿态(pose)的性能指标。
self.box = Metric()
self.pose = Metric()
# 初始化一个字典 self.speed ,用于存储不同阶段的处理时间。
self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0}
# 将实例变量 self.task 设置为字符串 "pose" ,表示当前任务是姿态估计。
self.task = "pose"
# 定义了一个方法 process ,用于处理姿态估计任务中的性能评估数据。参数包括 :
# 1.tp :一个二维数组,表示每个预测的边界框的真正例情况。
# 2.tp_p :一个二维数组,表示每个预测的姿态的真正例情况。
# 3.conf :一个一维数组,表示每个预测的置信度。
# 4.pred_cls :一个一维数组,表示每个预测的类别。
# 5.target_cls :一个一维数组,表示每个目标的真实类别。
def process(self, tp, tp_p, conf, pred_cls, target_cls):
# 处理给定预测集的检测和姿势指标。
"""
Processes the detection and pose metrics over the given set of predictions.
Args:
tp (list): List of True Positive boxes.
tp_p (list): List of True Positive keypoints.
conf (list): List of confidence scores.
pred_cls (list): List of predicted classes.
target_cls (list): List of target classes.
"""
# 调用 ap_per_class 函数计算姿态的性能指标,并获取从第三个元素开始的结果。
results_pose = ap_per_class(
tp_p,
conf,
pred_cls,
target_cls,
plot=self.plot,
on_plot=self.on_plot,
save_dir=self.save_dir,
names=self.names,
prefix="Pose",
)[2:]
# 设置 Metric 对象 self.pose 的 类别总数 ,并 更新其性能指标 。
self.pose.nc = len(self.names)
self.pose.update(results_pose)
# 调用 ap_per_class 函数计算边界框的性能指标,并获取从第三个元素开始的结果。
results_box = ap_per_class(
tp,
conf,
pred_cls,
target_cls,
plot=self.plot,
on_plot=self.on_plot,
save_dir=self.save_dir,
names=self.names,
prefix="Box",
)[2:]
# 设置 Metric 对象 self.box 的类别总数,并更新其性能指标。
self.box.nc = len(self.names)
self.box.update(results_box)
# 定义了一个属性 keys ,用于返回性能指标的键名。
@property
def keys(self):
# 返回评估指标键列表。
"""Returns list of evaluation metric keys."""
# 返回一个列表,包含边界框和姿态的性能指标键名。
return [
"metrics/precision(B)",
"metrics/recall(B)",
"metrics/mAP50(B)",
"metrics/mAP50-95(B)",
"metrics/precision(P)",
"metrics/recall(P)",
"metrics/mAP50(P)",
"metrics/mAP50-95(P)",
]
# 定义了一个方法 mean_results ,用于返回所有类别的平均性能指标。
def mean_results(self):
# 返回边界框和姿势的平均结果。
"""Return the mean results of box and pose."""
# 返回边界框和姿态的平均性能指标。
return self.box.mean_results() + self.pose.mean_results()
# 定义了一个方法 class_result ,用于返回第 i 个类别的性能指标。
def class_result(self, i):
# 返回特定类别 i 的类别检测结果。
"""Return the class-wise detection results for a specific class i."""
# 返回第 i 个类别的边界框和姿态的性能指标。
return self.box.class_result(i) + self.pose.class_result(i)
# 定义了一个属性 maps ,用于返回每个类别的mAP。
@property
def maps(self):
# 返回每个类别的框和姿势检测的平均精度 (mAP)。
"""Returns the mean average precision (mAP) per class for both box and pose detections."""
# 返回边界框和姿态的mAP。
return self.box.maps + self.pose.maps
# 定义了一个属性 fitness ,用于返回模型的综合性能指标。
@property
def fitness(self):
# 使用‘目标’和‘预测’输入计算分类指标和速度。
"""Computes classification metrics and speed using the `targets` and `pred` inputs."""
# 返回姿态和边界框的综合性能指标。
return self.pose.fitness() + self.box.fitness()
# 定义了一个属性 curves ,用于返回性能曲线的名称。
@property
def curves(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个列表,包含边界框和姿态的性能曲线名称。
return [
"Precision-Recall(B)",
"F1-Confidence(B)",
"Precision-Confidence(B)",
"Recall-Confidence(B)",
"Precision-Recall(P)",
"F1-Confidence(P)",
"Precision-Confidence(P)",
"Recall-Confidence(P)",
]
# 定义了一个属性 curves_results ,用于返回性能曲线的结果。
@property
def curves_results(self):
# 返回计算的性能指标和统计数据的字典。
"""Returns dictionary of computed performance metrics and statistics."""
# 返回边界框和姿态的性能曲线结果。
return self.box.curves_results + self.pose.curves_results
# PoseMetrics 类继承自 SegmentMetrics ,提供了一个完整的框架来处理姿态估计任务中的性能评估。 通过 process 方法,类可以处理姿态估计结果并更新性能指标。 类提供了多个属性和方法,用于获取和返回各种性能指标,如精确度、召回率、mAP等。 results_dict 属性提供了一个字典,方便用户获取和使用性能指标。 curves 和 curves_results 属性提供了绘制性能曲线所需的数据,方便用户进行可视化分析。 这个类的设计使得性能评估更加模块化和易于管理,特别是在复杂的机器学习项目中。
21.class ClassifyMetrics(SimpleClass):
# 这段代码定义了一个名为 ClassifyMetrics 的类,该类继承自 SimpleClass ,用于处理分类任务中的性能评估。
class ClassifyMetrics(SimpleClass):
# 用于计算分类指标的类,包括 top-1 和 top-5 准确度。
# 方法:
# process(targets, pred):处理目标和预测以计算分类指标。
"""
Class for computing classification metrics including top-1 and top-5 accuracy.
Attributes:
top1 (float): The top-1 accuracy.
top5 (float): The top-5 accuracy.
speed (Dict[str, float]): A dictionary containing the time taken for each step in the pipeline.
Properties:
fitness (float): The fitness of the model, which is equal to top-5 accuracy.
results_dict (Dict[str, Union[float, str]]): A dictionary containing the classification metrics and fitness.
keys (List[str]): A list of keys for the results_dict.
Methods:
process(targets, pred): Processes the targets and predictions to compute classification metrics.
"""
# 定义了 ClassifyMetrics 类的初始化方法 __init__ ,不接受任何参数。
def __init__(self) -> None:
# 初始化 ClassifyMetrics 实例。
"""Initialize a ClassifyMetrics instance."""
# 初始化两个实例变量 self.top1 和 self.top5 ,分别用于存储Top-1和Top-5准确率,初始值为0。
self.top1 = 0
self.top5 = 0
# 初始化一个字典 self.speed ,用于存储不同阶段的处理时间。字典的键包括 "preprocess" 、 "inference" 、 "loss" 和 "postprocess" ,初始值均为0.0。这些键分别代表预处理、推理、损失计算和后处理阶段的处理时间。
self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0}
# 将实例变量 self.task 设置为字符串 "classify" ,表示当前任务是分类。
self.task = "classify"
# 定义了一个方法 process ,用于处理分类任务中的性能评估数据。参数包括 :
# 1.targets :一个列表或张量,表示真实标签。
# 2.pred :一个列表或张量,表示模型的预测结果。
def process(self, targets, pred):
# 目标类别和预测类别。
"""Target classes and predicted classes."""
# 使用 torch.cat 函数将 pred 和 targets 列表中的张量连接成一个大的张量。
pred, targets = torch.cat(pred), torch.cat(targets)
# 计算每个预测是否正确。 targets[:, None] 将 targets 张量扩展为二维张量,然后与 pred 张量进行比较,生成一个布尔张量,表示每个预测是否正确。将布尔张量转换为浮点张量,True为1.0,False为0.0。
# 这行代码是 ClassifyMetrics 类中 process 方法的一部分,用于计算分类任务中每个预测是否正确。
# targets[:, None] : targets 是一个一维张量,表示真实标签。 [:, None] 是一个索引操作,用于在 targets 张量中插入一个新维度。具体来说,它将 targets 从一维张量(形状为 (n,) )转换为二维张量(形状为 (n, 1) )。
# 例如 :如果 targets 是 [1, 2, 3] ,那么 targets[:, None] 将变为 [[1], [2], [3]] 。
# targets[:, None] == pred : pred 是一个二维张量,表示模型的预测结果,形状为 (n, k) ,其中 n 是样本数量, k 是类别数量。 targets[:, None] == pred 进行逐元素比较,生成一个布尔张量,形状与 pred 相同。
# 例如 :如果 targets 是 [1, 2, 3] , pred 是 [[0.1, 0.2, 0.7], [0.3, 0.6, 0.1], [0.2, 0.1, 0.7]] ,那么 targets[:, None] == pred 将生成 [[False, False, True], [False, True, False], [False, False, True]] 。
# .float() :将布尔张量转换为浮点张量, True 变为 1.0 , False 变为 0.0 。
# 例如 :布尔张量 [[False, False, True], [False, True, False], [False, False, True]] 将变为 [[0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] 。
# 这行代码的目的是生成一个浮点张量 correct ,其中每个元素表示模型的预测是否正确。具体来说, correct[i, j] 为 1.0 表示第 i 个样本的第 j 个预测类别是正确的,为 0.0 表示不正确。 这个张量 correct 将用于后续计算Top-1和Top-5准确率。
correct = (targets[:, None] == pred).float()
# 计算Top-1和Top-5准确率。 correct[:, 0] 表示每个预测的Top-1准确率, correct.max(1).values 表示每个预测的Top-5准确率。将这两个准确率堆叠成一个二维张量 acc 。
# 这行代码是 ClassifyMetrics 类中 process 方法的一部分,用于计算分类任务中的Top-1和Top-5准确率。
# correct[:, 0] : correct 是一个二维浮点张量,形状为 (n, k) ,其中 n 是样本数量, k 是类别数量。 correct[:, 0] 提取 correct 张量的第一列,表示每个样本的Top-1预测是否正确。这列的每个元素为 1.0 表示Top-1预测正确,为 0.0 表示不正确。
# correct.max(1).values : correct.max(1) 对 correct 张量的每一行(即每个样本)进行最大值操作,返回一个包含最大值的张量和一个包含最大值索引的张量。 correct.max(1).values 提取最大值张量,表示每个样本的Top-5预测是否正确。这列的每个元素为 1.0 表示Top-5预测中至少有一个正确,为 0.0 表示不正确。
# torch.stack((correct[:, 0], correct.max(1).values), dim=1) : torch.stack 函数将两个张量沿着指定维度( dim=1 )堆叠起来,生成一个新的二维张量。 correct[:, 0] 和 correct.max(1).values 分别表示Top-1和Top-5预测的正确性,堆叠后的张量 acc 的形状为 (n, 2) ,其中第一列是Top-1准确率,第二列是Top-5准确率。
# 这行代码生成了一个二维张量 acc ,其中每一行包含两个元素,分别表示该样本的Top-1和Top-5预测的正确性。 acc 张量的形状为 (n, 2) ,其中 n 是样本数量。 这个张量 acc 将用于后续计算Top-1和Top-5准确率的平均值。
acc = torch.stack((correct[:, 0], correct.max(1).values), dim=1) # (top1, top5) accuracy
# 计算Top-1和Top-5准确率的平均值,并将结果转换为列表,分别赋值给 self.top1 和 self.top5 。
# 这行代码是 ClassifyMetrics 类中 process 方法的一部分,用于计算并更新Top-1和Top-5准确率的平均值。
# acc.mean(0) : acc 是一个二维张量,形状为 (n, 2) ,其中 n 是样本数量,每一行包含两个元素,分别表示该样本的Top-1和Top-5预测的正确性。 acc.mean(0) 沿着第0维(即每一列)计算平均值,返回一个形状为 (2,) 的一维张量,其中第一个元素是Top-1准确率的平均值,第二个元素是Top-5准确率的平均值。
# .tolist() : tolist() 方法将张量转换为Python列表。 例如,如果 acc.mean(0) 的结果是 torch.tensor([0.7, 0.9]) ,那么 tolist() 将返回 [0.7, 0.9] 。
# self.top1, self.top5 = ... : 将转换后的列表中的两个值分别赋值给 self.top1 和 self.top5 。 self.top1 存储Top-1准确率的平均值, self.top5 存储Top-5准确率的平均值。
# 这行代码计算了Top-1和Top-5准确率的平均值,并将结果存储在 self.top1 和 self.top5 中。 acc.mean(0) 计算每一列的平均值, tolist() 将结果转换为列表,以便赋值给实例变量。 这样, self.top1 和 self.top5 分别表示整个数据集的Top-1和Top-5准确率。
# 例如,假设 acc 张量为 :
# acc = torch.tensor([
# [0.0, 1.0],
# [1.0, 1.0],
# [1.0, 1.0]
# ])
# 那么 :acc.mean(0) 的结果为 torch.tensor([0.6667, 1.0]) 。 acc.mean(0).tolist() 的结果为 [0.6667, 1.0] 。 self.top1 将被赋值为 0.6667 , self.top5 将被赋值为 1.0 这表示整个数据集的Top-1准确率为66.67%,Top-5准确率为100%。
self.top1, self.top5 = acc.mean(0).tolist()
# 定义了一个属性 fitness ,用于返回模型的综合性能指标。
@property
def fitness(self):
# 返回 top-1 和 top-5 准确度的平均值作为适应度分数。
"""Returns mean of top-1 and top-5 accuracies as fitness score."""
# 返回Top-1和Top-5准确率的平均值作为综合性能指标。
return (self.top1 + self.top5) / 2
# 定义了一个属性 results_dict ,用于返回性能指标的字典。
@property
def results_dict(self):
# 返回包含模型性能指标和适应度得分的字典。
"""Returns a dictionary with model's performance metrics and fitness score."""
# 返回一个字典,键为 keys 列表加上 "fitness" ,值为Top-1准确率、Top-5准确率和综合性能指标。
return dict(zip(self.keys + ["fitness"], [self.top1, self.top5, self.fitness]))
# 定义了一个属性 keys ,用于返回性能指标的键名。
@property
def keys(self):
# 返回 results_dict 属性的键列表。
"""Returns a list of keys for the results_dict property."""
# 返回一个列表,包含Top-1和Top-5准确率的键名。
return ["metrics/accuracy_top1", "metrics/accuracy_top5"]
# 定义了一个属性 curves ,用于返回性能曲线的名称。
@property
def curves(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个空列表,表示分类任务没有性能曲线。
return []
# 定义了一个属性 curves_results ,用于返回性能曲线的结果。
@property
def curves_results(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个空列表,表示分类任务没有性能曲线结果。
return []
# ClassifyMetrics 类继承自 SimpleClass ,提供了一个完整的框架来处理分类任务中的性能评估。 通过 process 方法,类可以处理分类结果并更新Top-1和Top-5准确率。 类提供了多个属性和方法,用于获取和返回各种性能指标,如Top-1和Top-5准确率。 results_dict 属性提供了一个字典,方便用户获取和使用性能指标。 curves 和 curves_results 属性返回空列表,因为分类任务通常不涉及性能曲线的绘制。 这个类的设计使得性能评估更加模块化和易于管理,特别是在复杂的机器学习项目中。
22.class OBBMetrics(SimpleClass):
# 这段代码定义了一个名为 OBBMetrics 的类,该类继承自 SimpleClass ,用于处理定向边界框(Oriented Bounding Box, OBB)检测任务中的性能评估。
class OBBMetrics(SimpleClass):
# 定义了 OBBMetrics 类的初始化方法 __init__ ,接受以下参数 :
# 1.save_dir :一个 Path 对象,表示保存图像和结果的目录,默认为当前目录。
# 2.plot :一个布尔值,表示是否绘制性能曲线,默认为 False 。
# 3.on_plot :一个可调用对象(如函数),在绘制曲线时会被调用,用于执行额外的操作,默认为 None 。
# 4.names :一个元组,包含类别名称,默认为空元组。
def __init__(self, save_dir=Path("."), plot=False, on_plot=None, names=()) -> None:
# 初始化类的属性,存储传入的参数值。
self.save_dir = save_dir
self.plot = plot
self.on_plot = on_plot
self.names = names
# 创建一个 Metric 类的实例,并赋给实例变量 self.box 。 Metric 类用于存储和计算定向边界框的性能指标,如精确度、召回率、F1分数、平均精度(AP)等。
self.box = Metric()
# 初始化一个字典 self.speed ,用于存储不同阶段的处理时间。字典的键包括 "preprocess" 、 "inference" 、 "loss" 和 "postprocess" ,初始值均为0.0。这些键分别代表预处理、推理、损失计算和后处理阶段的处理时间。
self.speed = {"preprocess": 0.0, "inference": 0.0, "loss": 0.0, "postprocess": 0.0}
# 定义了一个方法 process ,用于处理定向边界框检测结果并更新性能指标。参数包括 :
# 1.tp :一个二维数组,表示每个预测的真正例(True Positive)情况,其形状为(检测数,IoU阈值数)。
# 2.conf :一个一维数组,表示每个预测的置信度。
# 3.pred_cls :一个一维数组,表示每个预测的类别。
# 4.target_cls :一个一维数组,表示每个目标的真实类别。
def process(self, tp, conf, pred_cls, target_cls):
# 处理对象检测的预测结果并更新指标。
"""Process predicted results for object detection and update metrics."""
# 调用 ap_per_class 函数计算定向边界框的性能指标,并获取从第三个元素开始的结果。
results = ap_per_class(
tp,
conf,
pred_cls,
target_cls,
plot=self.plot,
save_dir=self.save_dir,
names=self.names,
on_plot=self.on_plot,
)[2:]
# 将 Metric 对象 self.box 的类别总数 nc 设置为类别名称列表 self.names 的长度。
self.box.nc = len(self.names)
# 调用 Metric 对象 self.box 的 update 方法,传入从 ap_per_class 函数获取的性能指标结果。 update 方法将这些结果更新到 Metric 对象的内部状态中,以便后续可以方便地访问和使用这些性能指标。
self.box.update(results)
# 定义了一个属性 keys ,用于返回性能指标的键名。
@property
def keys(self):
# 返回用于访问特定指标的键列表。
"""Returns a list of keys for accessing specific metrics."""
# 返回一个列表,包含定向边界框的性能指标键名。
return ["metrics/precision(B)", "metrics/recall(B)", "metrics/mAP50(B)", "metrics/mAP50-95(B)"]
# 定义了一个方法 mean_results ,用于返回所有类别的平均性能指标。
def mean_results(self):
# 计算检测到的对象的平均值并返回精度、召回率、mAP50 和 mAP50-95。
"""Calculate mean of detected objects & return precision, recall, mAP50, and mAP50-95."""
# 调用 Metric 对象 self.box 的 mean_results 方法,返回平均性能指标。
return self.box.mean_results()
# 定义了一个方法 class_result ,用于返回第 i 个类别的性能指标。
def class_result(self, i):
# 返回评估对象检测模型在特定类别上的性能的结果。
"""Return the result of evaluating the performance of an object detection model on a specific class."""
# 调用 Metric 对象 self.box 的 class_result 方法,返回第 i 个类别的性能指标。
return self.box.class_result(i)
# 定义了一个属性 maps ,用于返回每个类别的mAP。
@property
def maps(self):
# 返回每个类别的平均精度 (mAP) 分数。
"""Returns mean Average Precision (mAP) scores per class."""
# 调用 Metric 对象 self.box 的 maps 属性,返回每个类别的mAP。
return self.box.maps
# 定义了一个属性 fitness ,用于返回模型的综合性能指标。
@property
def fitness(self):
# 返回边界框对象的适应度。
"""Returns the fitness of box object."""
# 调用 Metric 对象 self.box 的 fitness 方法,返回模型的综合性能指标。
return self.box.fitness()
# 定义了一个属性 ap_class_index ,用于返回每个类别的索引。
@property
def ap_class_index(self):
# 返回每个类别的平均精度指数。
"""Returns the average precision index per class."""
# 调用 Metric 对象 self.box 的 ap_class_index 属性,返回每个类别的索引。
return self.box.ap_class_index
# 定义了一个属性 results_dict ,用于返回性能指标的字典。
@property
def results_dict(self):
# 返回计算的性能指标和统计数据的字典。
"""Returns dictionary of computed performance metrics and statistics."""
# 返回一个字典,键为 keys 列表加上 "fitness" ,值为平均性能指标加上综合性能指标。
return dict(zip(self.keys + ["fitness"], self.mean_results() + [self.fitness]))
# 定义了一个属性 curves ,用于返回性能曲线的名称。
@property
def curves(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个空列表,表示定向边界框检测任务没有性能曲线。
return []
# 定义了一个属性 curves_results ,用于返回性能曲线的结果。
@property
def curves_results(self):
# 返回用于访问特定指标曲线的曲线列表。
"""Returns a list of curves for accessing specific metrics curves."""
# 返回一个空列表,表示定向边界框检测任务没有性能曲线结果。
return []
# OBBMetrics 类继承自 SimpleClass ,提供了一个完整的框架来处理定向边界框检测任务中的性能评估。 通过 process 方法,类可以处理检测结果并更新性能指标。 类提供了多个属性和方法,用于获取和返回各种性能指标,如精确度、召回率、mAP等。 results_dict 属性提供了一个字典,方便用户获取和使用性能指标。 curves 和 curves_results 属性返回空列表,因为定向边界框检测任务通常不涉及性能曲线的绘制。 这个类的设计使得性能评估更加模块化和易于管理,特别是在复杂的机器学习项目中。
原文地址:https://blog.csdn.net/m0_58169876/article/details/145068202
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!