如有错误,恳请指出。


在解析yolov5整个工程项目的时候要,已经对其detect.py脚本和val.py脚本进行分别的解析。其中,个人觉得detect脚本写得过于冗杂,所以分别为每个任务(图片推理,视频推理,摄像头推理)单独的写了个简单易懂的推理脚本。

在之前在解析完val.py脚本之后,一直想同样地对其进行简化,现在用这篇博客来记录简化过程以及出现的问题。

文章目录

  • 1. yolo的txt标注文件转coco的json标注文件
    • 1.1 标注格式
    • 1.2 coco字段说明
    • 1.3 yolo转coco脚本
  • 2. 按coco格式获取预测结果的json文件
  • 3. 使用coco API评估结果
  • 4. val脚本简化

1. yolo的txt标注文件转coco的json标注文件

1.1 标注格式

一般来说,现有的标注格式就是xml格式,yolo的txt格式还有coco的json标注特殊,我们使用yolov5项目来说标注文件就是一堆txt文件,文件名是对应的图像名,如下所示:

然后每个txt文件中,就存储着当前图像的标注信息,分别对于的是:类别,归一化后中心点的x坐标,归一化后中心点的y坐标,归一化后的目标框宽度w,归一化后的目标框高度h(此处归一化指的是除以图片宽和高)

0 0.17 0.36678200692041524 0.07 0.09688581314878893
0 0.35625 0.20415224913494812 0.0525 0.08304498269896193
0 0.6375000000000001 0.3788927335640139 0.065 0.10726643598615918
0 0.65 0.19896193771626297 0.03 0.04498269896193772
0 0.6725 0.29584775086505194 0.03 0.04498269896193772
1 0.79 0.32525951557093424 0.07 0.08996539792387544
1 0.91125 0.19377162629757785 0.0625 0.07612456747404844

但是,对于coco的标注格式来说,顺序是:左上角的x坐标,左上角的y坐标,目标框的宽度w,目标框的高度h


所以,对于yolo格式的标注文件,不仅仅要依次的读取每个图像的标注txt信息,还需要对其中的信息进行转换。

下面,需要对coco的json标注格式进行一个简要的说明

1.2 coco字段说明

对于这部分内容,基本是来源于网上资料的,详细可以查看参考资料1,2

不同于voc还有yolo,一张照片对应着一个xml文件或者是一个txt文件,coco是直接将所有图片以及对应的box信息写在了一个json文件里。通常整个coco目录长这样:

coco
|______annotations # 存放标注信息
|        |__train.json
|        |__val.json
|        |__test.json
|______trainset # 存放训练集图像
|______valset   # 存放验证集图像
|______testset  # 存放测试集图像

一个标准的json文件包含如下信息:

{"info": info,"images": [image],"annotations": [annotation],"licenses": [license],"categories": [categories]
}info{"description": "COCO 2017 Dataset",   # 数据集描述"url": "http://cocodataset.org",     # 下载地址"version": "1.0",         # 版本"year": 2017,             # 年份"contributor": "COCO Consortium",   # 提供者"date_created": "2017/09/01"       # 数据创建日期}
image{"file_name": "000000397133.jpg", # 图片名"id": 397133      # 图片的ID编号(每张图片ID是唯一的)"height": 427,         # 高"width": 640,      # 宽"license": 4,"coco_url":  "http://images.cocodataset.org/val2017/000000397133.jpg",# 网路地址路径"date_captured": "2013-11-14 17:02:52", # 数据获取日期"flickr_url": "http://farm7.staticflickr.com/6116/6255196340_da26cf2c9e_z.jpg",# flickr网路地址}
license{"url": "http://creativecommons.org/licenses/by-nc-sa/2.0/","id": 1,"name": "Attribution-NonCommercial-ShareAlike License"}
categories{"supercategory": "person", # 主类别"id": 1, # 类对应的id (0 默认为背景)"name": "person" # 子类别
}
annotations{"id":          # 指的是这个annotation的一个id"image_id":      # 等同于前面image字段里面的id。"category_id": # 类别id"segmentation":  # 左上-右上-右下-坐下 依次四个点坐标"area":           # 标注区域面积"bbox":            # 标注框,x,y为标注框的左上角坐标。"iscrowd":      # 决定是RLE格式还是polygon格式
}

1.3 yolo转coco脚本

接下来就直接进行转换,代码是我基于参考资料4的基础上修改而来的。

参考代码:

import os
import json
import random
import time
from PIL import Image
import csvcoco_format_save_path = './coco'  # 要生成的标准coco格式标签所在文件夹
yolo_format_classes_path = 'annotations.csv'  # 类别文件,用csv文件表示,一行一个类
yolo_format_annotation_path = '../dataset/mask/labels/val'  # yolo格式标签所在文件夹
img_pathDir = '../dataset/mask/images/val'  # 图片所在文件夹# 类别设置
categories = []
class_names = ['with_mask', 'without_mask', 'mask_weared_incorrect']
for label in class_names:categories.append({'id': class_names.index(label), 'name': label, 'supercategory': ""})write_json_context = dict()  # 写入.json文件的大字典
write_json_context['licenses'] = [{'name': "", 'id': 0, 'url': ""}]
write_json_context['info'] = {'contributor': "", 'date_created': "", 'description': "", 'url': "", 'version': "", 'year': ""}
write_json_context['categories'] = categories
write_json_context['images'] = []
write_json_context['annotations'] = []# 接下来的代码主要添加'images'和'annotations'的key值
imageFileList = os.listdir(img_pathDir)
# 遍历该文件夹下的所有文件,并将所有文件名添加到列表中
img_id = 0  # 图片编号
anno_id = 0     # 标注标号
for i, imageFile in enumerate(imageFileList):if '_' not in imageFile:img_id += 1imagePath = os.path.join(img_pathDir, imageFile)  # 获取图片的绝对路径image = Image.open(imagePath)  # 读取图片W, H = image.size  # 获取图片的高度宽度img_context = {}  # 使用一个字典存储该图片信息# img_name=os.path.basename(imagePath)img_context['id'] = img_id  # 每张图像的唯一ID索引img_context['width'] = Wimg_context['height'] = Himg_context['file_name'] = imageFileimg_context['license'] = 0img_context['flickr_url'] = ""img_context['color_url'] = ""img_context['date_captured'] = ""write_json_context['images'].append(img_context)  # 将该图片信息添加到'image'列表中txtFile = imageFile.split('.')[0] + '.txt'  # 获取该图片获取的txt文件with open(os.path.join(yolo_format_annotation_path, txtFile), 'r') as fr:lines = fr.readlines()  # 读取txt文件的每一行数据,lines2是一个列表,包含了一个图片的所有标注信息for j, line in enumerate(lines):anno_id += 1  # 标注的id从1开始bbox_dict = {}  # 将每一个bounding box信息存储在该字典中class_id, x, y, w, h = line.strip().split(' ')  # 获取每一个标注框的详细信息class_id, x, y, w, h = int(class_id), float(x), float(y), float(w), float(h)  # 将字符串类型转为可计算的int和float类型# 坐标转换xmin = (x - w / 2) * Wymin = (y - h / 2) * Hxmax = (x + w / 2) * Wymax = (y + h / 2) * Hw = w * Wh = h * Hheight, width = abs(ymax - ymin), abs(xmax - xmin)# bounding box的坐标信息bbox_dict['id'] = anno_id               # 每个标注信息的索引bbox_dict['image_id'] = img_id          # 当前图像的ID索引bbox_dict['category_id'] = class_id     # 类别信息bbox_dict['segmentation'] = [[xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax]]bbox_dict['area'] = height * widthbbox_dict['bbox'] = [xmin, ymin, w, h]  # 注意目标类别要加一bbox_dict['iscrowd'] = 0bbox_dict['attributes'] = ""write_json_context['annotations'].append(bbox_dict)  # 将每一个由字典存储的bounding box信息添加到'annotations'列表中name = os.path.join(coco_format_save_path, "annotations" + '.json')
with open(name, 'w') as fw:  # 将字典信息写入.json文件中json.dump(write_json_context, fw, indent=4, ensure_ascii=False)

运行结果:

{"images": [{"id": 1,"width": 400,"height": 267,"file_name": "maksssksksss98.png","license": 0,"flickr_url": "","color_url": "","date_captured": ""},......
"annotations": [{"id": 1,"image_id": 1,"category_id": 0,"segmentation": [[196.00000000000003,43.0,236.00000000000003,43.0,236.00000000000003,91.0,196.00000000000003,91.0]],"area": 1920.0,"bbox": [196.00000000000003,43.0,40.0,48.0],"iscrowd": 0,"attributes": ""},{"id": 2,"image_id": 1,"category_id": 0,"segmentation": [[41.0,73.0,65.0,73.0,65.0,95.0,41.0,95.0]],"area": 528.0,"bbox": [41.0,73.0,24.0,22.000000000000004],"iscrowd": 0,"attributes": ""},......
}

这样,就可以将全部的标注txt文件,转化成一个json文件的标注信息


2. 按coco格式获取预测结果的json文件

基于以上的操作,现在已经得到了coco格式的json标注文件。根据API的调用,现在还需要将预测信息整合在一个json文件中,对于每副图像需要获取其所有预测框的类别,边界框的4个坐标,置信度。将所有结果保留为一个列表,输入如下所示:

[{"image_id": "maksssksksss363","category_id": 0,"bbox": [342.638,86.238,36.37,39.355],"score": 0.91578},{"image_id": "maksssksksss363","category_id": 0,"bbox": [327.98,21.8,38.32,41.232],"score": 0.9059},......
]

这个预测文件在原本的val.py脚本中,设置--save-json参数基于可以输出

def parse_opt():parser = argparse.ArgumentParser()parser.add_argument('--save-json', default=True, action='store_true', help='save a COCO-JSON results file')......def run(...):# Save JSONif save_jsonand len(jdict):w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else ''  # weightsanno_json = str(Path(data.get('path', '../coco')) / 'annotations/instances_val2017.json')  # annotations jsonpred_json = str(save_dir / f"{w}_predictions.json")  # predictions jsonprint(f'\nEvaluating pycocotools mAP... saving {pred_json}...')# 保存val的所有预测结果在jdict字典中,然后保存名称为:best_predictions.jsonwith open(pred_json, 'w') as f:json.dump(jdict, f, indent=4, ensure_ascii=False)

输入路径如下所示:


对于jdict字典中的每一个内容,是通过save_one_json函数来保存设置的:

# 将预测信息保存到coco格式的json字典
def save_one_json(predn, jdict, path, class_map):# Save one JSON result {"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}# 获取图片idimage_id = int(path.stem) if path.stem.isnumeric() else path.stem# 获取预测框 并将xyxy转为xywh格式box = xyxy2xywh(predn[:, :4])  # xywh# 之前的的xyxy格式是左上角右下角坐标  xywh是中心的坐标和宽高# 而coco的json格式的框坐标是xywh(左上角坐标 + 宽高)# 所以这行代码是将中心点坐标 -> 左上角坐标box[:, :2] -= box[:, 2:] / 2  # xy center to top-left corner# image_id: 图片id 即属于哪张图片# category_id: 类别 coco91class()从索引0~79映射到索引0~90# bbox: 预测框坐标# score: 预测得分for p, b in zip(predn.tolist(), box.tolist()):jdict.append({'image_id': image_id,'category_id': class_map[int(p[5])],'bbox': [round(x, 3) for x in b],'score': round(p[4], 5)})

那么,现在有了对val数据集的标注信息json文件,也有了val数据集的预测信息json文件,就可以使用pycocotools.cocoeval工具包来进行map的判断,这样就不需要像yolov5那样写了一大堆复杂的评价函数。


3. 使用coco API评估结果

使用coco api评估当前数据集的map结果非常简单,只需要将coco格式的标注json文件和coco格式的预测json文件同时传入COCOeval函数中即可,代码如下:

from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOevalif __name__ == '__main__':anno_json = r'./test/anno_json.json'pred_json = r'./test/pred_json.json'anno = COCO(anno_json)        # init annotations apipred = anno.loadRes(pred_json)  # init predictions apieval = COCOeval(anno, pred, 'bbox')eval.evaluate()eval.accumulate()eval.summarize()map, map50 = eval.stats[:2]  # update results (mAP@0.5:0.95, mAP@0.5)print(eval.stats)

这时候如果直接传入刚刚的两个json文件,是会报错的,错误信息是:AssertionError: Results do not correspond to current coco set。参考资料6.

出现这个问题的原因有两个:

  1. 图像id和标注的id数量不对应。也就是说出现了一些没有标注的图像信息,在image列表中出现,但是却没有在annotations中出现,也就是有点图像没有目标没有标注。
  2. image_id 类型出现错误,image_id 必须为 int类型,不能是字符串

随后,我检查了一下txt标注文件,发现所有的图像都有目标,都有标注,也就排除了第一个问题。(假如是因为第一个问题,需要把标注信息为空的图像进行删除,这个操作其实挺不合理的)。那么,就是第二个问题了。

然后,我们的预测json文件中,image_id 是图像的文件名。image_id 必须为 int类型,不能是字符串。为什么会出现这个错误?原因是在save_one_json()函数主要注意image_id = int(path.stem) if path.stem.isnumeric() else path.stem这一句出现的了问题,因为我们传入的 path.stem 本身就是一个字符串。

path.stem是指验证集图片名,如host0000001.jpg
那么path.stem为host0000001,则取数字部分:path.stem[5:] #为0000001

由于本身就是字符串,所以判断后的image_id 传入还是字符串,导致了这个错误。同样的,在标注信息的json文件中,也出现了这个错误。

  • annotations.json的错误:
"annotations": [{"id": 1,"image_id": "maksssksksss98",   # 错误,需要是int类型,和image信息相匹配"category_id": 0,"segmentation": [[196.00000000000003,43.0,236.00000000000003,43.0,236.00000000000003,91.0,196.00000000000003,91.0]],"area": 1920.0,"bbox": [196.00000000000003,43.0,40.0,48.0],"iscrowd": 0,"attributes": ""},
  • best_preditions.json的错误:
{"image_id": "maksssksksss363",  # 错误,需要是int类型,和image信息相匹配"category_id": 0,"bbox": [342.638,86.238,36.37,39.355],"score": 0.91578},

那么,现在知道了错误的原因,就需要将问题改正。对于这些字符串,我们需要和annotations.json字典中的images信息来进行匹配,在对应的地方转为id,而不是图像名。比如:

 "images": [{"id": 1,"width": 400,"height": 267,"file_name": "maksssksksss98.png","license": 0,"flickr_url": "","color_url": "","date_captured": ""},

也就是说,将原本image_idmaksssksksss98的内容,改为1,因为匹配的是id是1。基于这一点,下面就写了一个修正脚本:

'''
修正脚本:对预测的json文件还有标注的json文件的id信息根据标注文件的image来命名
'''import json
import os
from collections import OrderedDict# 获取标注文件图像id与图像名字的字典
def get_name2id_map(image_dict):name2id_dict = OrderedDict()for image in image_dict:file_name = image['file_name'].split('.')[0]    # maksssksksss98.png -> maksssksksss98id = image['id']name2id_dict[file_name] = idreturn name2id_dictif __name__ == '__main__':anno_json = r'./coco/annotations.json'pred_json = r'../runs/val/mask/best_predictions.json'with open(pred_json, 'r') as fr:pred_dict = json.load(fr)with open(anno_json, 'r') as fr:anno_dict = json.load(fr)name2id_dict = get_name2id_map(anno_dict['images'])# 对标注文件annotations的image_id进行更改for annotations in anno_dict['annotations']:image_id = annotations['image_id']annotations['image_id'] = int(name2id_dict[image_id])# 对预测文件的image_id同样进行更改for predictions in pred_dict:image_id = predictions['image_id']predictions['image_id'] = int(name2id_dict[image_id])# 分别保存更改后的标注文件和预测文件with open('anno_json.json', 'w') as fw:json.dump(anno_dict, fw, indent=4, ensure_ascii=False)with open('pred_json.json', 'w') as fw:json.dump(pred_dict, fw, indent=4, ensure_ascii=False)

输出两个修正后的json文件:

现在重新查看修正后的标注信息:

# pred_json.json
{"image_id": 112, # 这里需要修改为图像的的ID索引"category_id": 0,"bbox": [342.638,86.238,36.37,39.355],"score": 0.91578},...# anno_json.json
"annotations": [{"id": 1,"image_id": 1,  # 由于图像的读取顺序是固定的,所以这里的image_id其实也就是id"category_id": 0,"segmentation": [[196.00000000000003,43.0,236.00000000000003,43.0,236.00000000000003,91.0,196.00000000000003,91.0]],"area": 1920.0,"bbox": [196.00000000000003,43.0,40.0,48.0],"iscrowd": 0,"attributes": ""},

经过如此修正之后,就可以正常的调用coco的api了。

  • COCO API评估代码:
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOevalif __name__ == '__main__':anno_json = r'./test/anno_json.json'pred_json = r'./test/pred_json.json'anno = COCO(anno_json)  # init annotations apipred = anno.loadRes(pred_json)  # init predictions apieval = COCOeval(anno, pred, 'bbox')eval.evaluate()eval.accumulate()eval.summarize()map, map50 = eval.stats[:2]  # update results (mAP@0.5:0.95, mAP@0.5)print(eval.stats)

输出信息:

loading annotations into memory...
Done (t=0.00s)
creating index...
index created!
Loading and preparing results...
DONE (t=0.01s)
creating index...
index created!
Running per image evaluation...
Evaluate annotation type *bbox*
DONE (t=0.45s).
Accumulating evaluation results...
DONE (t=0.05s).Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.494Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.764Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.545Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.392Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.680Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.853Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.269Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.565Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.591Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.503Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.755Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.868Process finished with exit code 0

普通执行val.py函数的预测信息:

(yolov5) [fs@localhost yolov5-6.0]$ python val.py
val: data=./dataset/mask/mask.yaml, weights=./runs/train/mask/weights/best.pt, batch_size=32, imgsz=640, conf_thres=0.001, iou_thres=0.6, task=val, device=cpu, single_cls=False, augment=False, verbose=False, save_txt=False, save_hybrid=False, save_conf=False, save_json=True, project=runs/val, name=exp, exist_ok=False, half=False
YOLOv5 												

YOLOv5的Tricks | 【Trick15】使用COCO API评估模型在自己数据集的结果相关推荐

  1. 利用COCO API测试自己数据集训练的YOLOv3模型的mAP(VOC格式数据集)

    目录 工具 前言 生成标注集的json文件 数据集准备 将voc注解格式数据集的注解转换成txt注解格式 自定义数据集的注解转换成coco的注解格式 生成结果集的json文件 安装darknet 获取 ...

  2. YOLOv5的Tricks | 【Trick14】YOLOv5的val.py脚本的解析

    如有问题,恳请指出. 这篇可能是这个系列最后的一篇了,最后把yolov5的验证过程大致的再介绍介绍,基本上把yolov5的全部内容就稍微过了一遍了,也是我自己对这个项目学习的结束.(补充一下,这里我介 ...

  3. Winddows 10 安装 COCO API(pycocotools)

    为了玩 Yolo-V3 花了好几天了,好不容易编写好了模型,也通过了测试,现在想做一些调整然后继续训练,还是采用COCO数据集. 但是 PyTorch 加载 COCO 数据集需要安装 COCO API ...

  4. 自制 COCO api 直接读取类 COCO 的标注数据的压缩文件

    第6章 COCO API 的使用 COCO 数据库是由微软发布的一个大型图像数据集,该数据集专为对象检测.分割.人体关键点检测.语义分割和字幕生成而设计.如果你要了解 COCO 数据库的一些细节,你可 ...

  5. 评价对象检测模型的数字度量:F1分数以及它们如何帮助评估模型的表现

    来源:DeepHub IBMA本文约2000字,建议阅读7分钟 本文为你介绍评价对象检测模型的数字度量. 介绍 使用精度和召回率评估目标检测模型可以为模型在不同置信度下的表现提供有价值的见解.类似地, ...

  6. 评估模型如何建立_建立和评估分类ML模型

    评估模型如何建立 There are different types of problems in machine learning. Some might fall under regression ...

  7. 深度学习笔记--pytorch从梯度下降到反向传播BP到线性回归实现,以及API调用和手写数据集的实现

    梯度下降和反向传播 目标 知道什么是梯度下降 知道什么是反向传播 1. 梯度是什么? 梯度:是一个向量,导数+变化最快的方向(学习的前进方向) 回顾机器学习 收集数据 x x x ,构建机器学习模型 ...

  8. 基于YOLOv5的舰船检测与识别系统(Python+清新界面+数据集)

    摘要:基于YOLOv5的舰船检测与识别系统用于识别包括渔船.游轮等多种海上船只类型,检测船舰目标并进行识别计数,以提供海洋船只的自动化监测和管理.本文详细介绍船舰类型识别系统,在介绍算法原理的同时,给 ...

  9. 基于YOLOv5的停车位检测系统(清新UI+深度学习+训练数据集)

    摘要:基于YOLOv5的停车位检测系统用于露天停车场车位检测,应用深度学习技术检测停车位是否占用,以辅助停车场对车位进行智能化管理.在介绍算法原理的同时,给出Python的实现代码.训练数据集以及Py ...

最新文章

  1. 11/1787, 哈工大小学妹的比赛上分经验,附战友招募
  2. 搞定Linux只要半年
  3. 首次提出“智能经济形态”,与实体经济深度融合
  4. [日常工作]WorkStation 使用端口转发的方式使用宿主机IP地址提供服务
  5. 网站title实现切换
  6. Windows 8 各版本功能区别一览表
  7. Newton Method in Maching Learning
  8. 学习《css世界》笔记之content自动添加开启闭合符号
  9. linux mysql安装_LINUX 安装 MYSQL
  10. ios 使用webview 查找_iOS开发WKWebView与JS的交互
  11. JavaScript Demo - so cool
  12. Netflix推出《DOTA2》系列动画 3月25日上线
  13. c++该转java吗_java多线程,静态方法加锁后,调用该方法会影响其它方法吗?
  14. C++ 4 C++变量及作用域
  15. Netron 可视化Pytorh模型架构
  16. CSS-盒子模型,标准盒子模型,IE 盒子模型,盒模型之间的转换
  17. QLabel setText 标红 加粗
  18. 团队作业1——团队展示
  19. from .cv2 import * ImportError: libGL.so.1: cannot open shared object file: No such file or direc
  20. PYNQ yocto运行python

热门文章

  1. web网页设计期末课程大作业 基于HTML+CSS+JavaScript制作八大菜系介绍舌尖上的美食5页
  2. 翻翻棋 博弈论
  3. Unity Shader - 故障艺术之 - Glitch Art - Split RGB (分离通道颜色的故障效果)
  4. 《用友ERP-U8(V8.72)模拟实战----财务、供应链和生产制造》一1.1 用友ERP-U8(V8.72)系统介绍...
  5. 易语言输入法注入dll到游戏进程
  6. 1009MySQL数据库InnoDB存储引擎Log漫游
  7. 2023第三届中国数字化人才国际峰会
  8. Scratch版文本编辑器案例实现+源码
  9. day19-java
  10. 共享打印机机显示一个感叹号怎么处理