YOLOv8可视化并统计每张图的TP,FP,FN,检测准确率、误检率、漏检统计
YOLOv8可视化并统计每张图的TP,FP,FN,检测准确率、误检率、漏检统计
YOLOv8是一种流行的目标检测框架,能够快速准确地识别图像中的目标。在进行目标检测时,我们通常会关注三个关键指标:真正例(True Positives,TP)、假正例(False Positives,FP)和假负例(False Negatives,FN)。本文将详细介绍如何使用YOLOv8可视化这些指标,并统计每张图片的检测准确率、误检率和漏检率。
一、相关名词解释
在目标检测中,TP、FP和FN的定义稍微复杂一些,因为目标检测不仅要考虑分类是否正确,还要考虑定位是否准确。以下是这些概念的解释和示例:
真正例(True Positives,TP):指检测到的目标与实际目标之间的匹配。这意味着检测到的目标在位置和类别上都与实际目标匹配。
假正例(False Positives,FP):指模型错误地将负例(非目标)样本预测为正例(目标)。在目标检测中,FP 是指检测到的目标与实际无目标区域之间的匹配。
假负例(False Negatives,FN):指模型未能检测到实际存在的目标。在目标检测中,FN 是指未检测到的实际目标。
举个例子:
假设我们有一张图像,其中包含一只猫和一只狗。我们的目标检测模型会尝试检测图像中的动物,并且根据预测结果计算 TP、FP 和 FN。
TP(真正例):如果模型正确地检测到了图像中的猫和狗,并且对它们进行了正确的分类和定位,那么这就是一个 TP。
FP(假正例):如果模型在图像中的某些区域错误地检测到了动物(例如,将一只猫误认为狗),或者在图像中检测到了不存在的动物,那么这就是一个 FP。
FN(假负例):如果模型未能检测到图像中的某些动物(例如,漏掉了图像中的狗),那么这就是一个 FN。
例如,如果我们的模型在一张图像中正确检测到了一只猫和一只狗,并且没有检测到不存在的动物,那么:
- TP = 2(图像中的猫狗都被检测出来,并且类别位置正确)
- FP = 0(模型未将不存在的动物检测为目标)
- FN = 0(模型未漏掉任何实际存在的目标)
二、新建Python文件
YOLOv8没有单独运行的Python文件,以下是自己新加的detect.py
,来获取每张图的TP、FP、FN:
import os
import cv2
import tqdm
import shutil
import numpy as np
def xywh2xyxy(box):
box[:, 0] = box[:, 0] - box[:, 2] / 2
box[:, 1] = box[:, 1] - box[:, 3] / 2
box[:, 2] = box[:, 0] + box[:, 2]
box[:, 3] = box[:, 1] + box[:, 3]
return box
def iou(box1, box2):
x11, y11, x12, y12 = np.split(box1, 4, axis=1)
x21, y21, x22, y22 = np.split(box2, 4, axis=1)
xa = np.maximum(x11, np.transpose(x21))
xb = np.minimum(x12, np.transpose(x22))
ya = np.maximum(y11, np.transpose(y21))
yb = np.minimum(y12, np.transpose(y22))
area_inter = np.maximum(0, (xb - xa + 1)) * np.maximum(0, (yb - ya + 1))
area_1 = (x12 - x11 + 1) * (y12 - y11 + 1)
area_2 = (x22 - x21 + 1) * (y22 - y21 + 1)
area_union = area_1 + np.transpose(area_2) - area_inter
iou = area_inter / area_union
return iou
def draw_box(img, box, color):
cv2.rectangle(img, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), color, thickness=5)
return img
if __name__ == '__main__':
postfixes = ['jpg', 'JPG'] # 定义要处理的后缀列表
img_path = '/home/wuyapeng/yolov8/data/images'
label_path = '/home/wuyapeng/yolov8/data/labels'
predict_path = 'runs/detect/predict20/labels'
save_path = 'vis'
classes = ['car', 'person']
detect_color, missing_color, error_color = (0, 255, 0), (0, 0, 255), (0, 255, 0)#(b,g,r)
iou_threshold = 0.45
if os.path.exists(save_path):
shutil.rmtree(save_path)
os.makedirs(save_path, exist_ok=True)
all_right_num, all_missing_num, all_error_num = 0, 0, 0
with open('result.txt', 'w') as f_w:
for filename in tqdm.tqdm(os.listdir(img_path)):
postfix = filename.split('.')[-1] # 获取文件名后缀
if postfix.lower() in postfixes: # 判断后缀是否在指定列表中
image = cv2.imread(os.path.join(img_path, filename))
if image is None:
print(f'image:{os.path.join(img_path, filename)} not found.', file=f_w)
continue
h, w = image.shape[:2]
path = filename[:-4] # 去除文件后缀
try:
with open(f'{predict_path}/{path}.txt') as f:
pred = np.array(list(map(lambda x:np.array(x.strip().split(), dtype=np.float32), f.readlines())))
pred[:, 1:5] = xywh2xyxy(pred[:, 1:5])
pred[:, [1, 3]] *= w
pred[:, [2, 4]] *= h
pred = list(pred)
except:
pred = []
try:
with open(f'{label_path}/{path}.txt') as f:
label = np.array(list(map(lambda x:np.array(x.strip().split(), dtype=np.float32), f.readlines())))
label[:, 1:] = xywh2xyxy(label[:, 1:])
label[:, [1, 3]] *= w
label[:, [2, 4]] *= h
except:
print(f'label path:{label_path}/{path}.txt (not found or no target).', file=f_w)
right_num, missing_num, error_num = 0, 0, 0
label_id, pred_id = list(range(label.shape[0])), [] if len(pred) == 0 else list(range(len(pred)))
for i in range(label.shape[0]):
if len(pred) == 0: break
ious = iou(label[i:i+1, 1:], np.array(pred)[:, 1:5])[0]
ious_argsort = ious.argsort()[::-1]
missing = True
for j in ious_argsort:
if ious[j] < iou_threshold: break
if label[i, 0] == pred[j][0]:
image = draw_box(image, pred[j][1:5], detect_color)
pred.pop(j)
missing = False
right_num += 1
break
if missing:
image = draw_box(image, label[i][1:5], missing_color)
missing_num += 1
if len(pred):
for j in range(len(pred)):
image = draw_box(image, pred[j][1:5], error_color)
error_num += 1
all_right_num, all_missing_num, all_error_num = all_right_num + right_num, all_missing_num + missing_num, all_error_num + error_num
cv2.imwrite(f'{save_path}/{path}.{postfix}', image)
print(f'name:{path} right:{right_num} missing:{missing_num} error:{error_num}', file=f_w)
print(f'all_result: right:{all_right_num} missing:{all_missing_num} error:{all_error_num}', file=f_w)
注意:detect.py需要修改的地方
postfixes = ['jpg', 'JPG']
:定义要处理的后缀列表,这个需要根据检测图片的格式进行修改。img_path = '/home/wuyapeng/yolov8/data/images'
:这是需要检测的图片路径。label_path = '/home/wuyapeng/yolov8/data/labels'
:这是需要检测的图片标签的路径。predict_path = 'runs/detect/predict20/labels'
:这个是需要用你自己训练好的模型/官方预训练模型去检测你的图片img_path,会生成一个检测后的标签txt文件,注意save_txt需要设置成True。save_path = 'vis'
:这是保存路径。classes = ['one', 'two']
:这是自己的类别,可以根据实际情况进行修改。
三、具体步骤(以下是我用YOLOv8运行的案例)
首先运行YOLOv8的命令来生成模型pt文件检测的图片标签txt文件(注意save_txt=True必须加上去):
yolo task=detect mode=predict model=/home/wuyapeng/yolov8/yuanv8-2cls.pt source=/home/wuyapeng/yolov8/data/images conf=0.45 save_txt=True device=0 --line_width=5
然后会生成一个检测后的图片和标签,将生成后的标签labels的路径添加到detect.py
文件中。
最后直接运行:
python detect.py
会生成一个vis
文件夹(可视化正确检测、漏检和误检的检测图)和result.txt
文件(会记录每张图片的正确检测、漏检、误检的数量,及总量)。
(注意opencv画图是BGR,所以TP=right=绿色,FN=miss=红色(漏检),FP=error=蓝色(误检))