YOLOv5介绍
YOLOv5为兼顾速度与性能的目标检测算法。笔者将在近期更新一系列YOLOv5的代码导读博客。YOLOv5为2021.1.5日发布的4.0版本。
YOLOv5开源项目github网址
源代码导读汇总网址
本博客导读的代码为utils文件夹下的metrics.py
metrics.py
该文件通过获得到的预测结果与ground truth表现计算指标P、R、F1-score、AP、不同阈值下的mAP等。同时,该文件将上述指标进行了可视化,绘制了混淆矩阵以及P-R曲线。
相关导入模块及说明如下所示。
from pathlib import Path #调用路径操作模块
import matplotlib.pyplot as plt #matplotlib画图软件
import numpy as np #numpy矩阵处理模块
import torch #pytorch
from . import general #从当前文件所处的相对路径调用general.py
fitness函数 通过指标加权的形式返回适应度
def fitness(x):
# 以矩阵的加权组合作为模型的适应度
w = [0.0, 0.0, 0.1, 0.9] # 每个变量对应的权重 [P, R, mAP@0.5, mAP@0.5:0.95]
# (torch.tensor).sum(1) 每一行求和tensor为二维时返回一个以每一行求和为结果的行向量
return (x[:, :4] * w).sum(1)
ap_per_class 函数计算每一个类的AP指标
def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='precision-recall_curve.png', names=[]):
""" 计算平均精度(AP),并绘制P-R曲线
源代码来源: https://github.com/rafaelpadilla/Object-Detection-Metrics.
# Arguments(变量)
tp: True positives (nparray, nx1 or nx10). 真阳
conf: Objectness value from 0-1 (nparray). 目标的置信度取值0-1
pred_cls: Predicted object classes (nparray).预测目标类别
target_cls: True object classes (nparray). 真实目标类别
plot: Plot precision-recall curve at mAP@0.5 是否绘制P-R曲线 在mAP@0.5的情况下
save_dir: P-R曲线图的保存路径
# Returns(返回)
像faster-rcnn那种方式计算AP (这里涉及计算AP的两种不同方式 建议查询)
The average precision as computed in py-faster-rcnn.
"""
# 将目标进行排序
# np.argsort(-conf)函数返回一个索引数组 其中每一个数按照conf中元素从大到小 置为 0,1...n
i = np.argsort(-conf)
# tp conf pred_cls 三个矩阵均按照置信度从大到小进行排列
tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
# 找到各个独立的类别
# np.unique()会返回输入array中出现至少一次的变量 这里返回所有独立的类别
unique_classes = np.unique(target_cls)
# 创建P-R曲线 并 计算每一个类别的AP
px, py = np.linspace(0, 1, 1000), [] # for plotting
pr_score = 0.1 # 评估P和R的分数 参考论坛https://github.com/ultralytics/yolov3/issues/898
# 第一个为类别数目, 第二为IOU loss阈值的类别的 (i.e. 10 for mAP0.5...0.95)
s = [unique_classes.shape[0], tp.shape[1]]
#初始化 对每一个类别在每一个IOU阈值下面 计算P R AP参数
ap, p, r = np.zeros(s), np.zeros(s), np.zeros(s)
for ci, c in enumerate(unique_classes): # ci为类别对应索引 c为具体的类别
# i为一个包含True/False 的列表 代表 pred_cls array 各元素是否与 类别c 相同
i = pred_cls == c
n_l = (target_cls == c).sum() # ground truth中 类别c 的个数 all_results
n_p = i.sum() # 预测类别中为 类别c 的个数
if n_p == 0 or n_l == 0: #如果没有预测到 或者 ground truth没有标注 则略过类别c
continue
else:
"""
计算 FP(False Positive) 和 TP(Ture Positive)
tp[i] 会根据i中对应位置是否为False来决定是否删除这一位的内容,如下所示:
a = np.array([0,1,0,1]) i = np.array([True,False,False,True]) b = a[i]
则b为:[0 1]
而.cumsum(0)函数会 按照对象进行累加操作,如下所示:
a = np.array([0,1,0,1]) b = a.cumsum(0)
则b为:[0,1,1,2]
(FP + TP = all_detections 所以有 fp[i] = 1 - tp[i])
所以fpc为 类别c 按照置信度从大到小排列 截止到每一位的FP数目
tpc为 类别c 按照置信度从大到小排列 截止到每一位的TP数目
recall 和 precision 均按照元素从小到大排列
"""
fpc = (1 - tp[i]).cumsum(0)
tpc = tp[i].cumsum(0)
# 计算Recall
# Recall = TP / (TP + FN) = TP / all_results = TP / n_l
recall = tpc / (n_l + 1e-16) # 加一个1e-16的目的是防止n_l为0 时除不开
"""
np.interp() 函数第一个输入值为数值 第二第三个变量为一组x y坐标 返回结果为一个数值
这个数值为 找寻该数值左右两边的x值 并将两者对应的y值取平均 如果在左侧或右侧 则取 边界值
如果第一个输入为数组 则返回一个数组 其中每一个元素按照上述计算规则产生
"""
r[ci] = np.interp(-pr_score, -conf[i], recall[:, 0]) # pr_score 处的y值
# 计算Precision
# Precision = TP / TP + FP = TP / all_detections
precision = tpc / (tpc + fpc)
p[ci] = np.interp(-pr_score, -conf[i], precision[:, 0]) # pr_score 处的y值
# 从P-R曲线中计算AP
for j in range(tp.shape[1]): #这里对每一个IOU阈值 下的参数进行计算
ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j]) #取每一个阈值计算AP
if plot and (j == 0):
py.append(np.interp(px, mrec, mpre)) # mAP@0.5处的P
# 计算F1分数 P和R的调和平均值
f1 = 2 * p * r / (p + r + 1e-16)
if plot:
plot_pr_curve(px, py, ap, save_dir, names