yolov5 调用cocotools 评价自己的模型和数据集(AP低的问题已解决)

1.使用方法

“前排提示,精度低的问题已经解决,方法可用”

yolov5 的数据集是.txt格式,coco数据集是.json格式,因此需要转换
安装cocotools

  • 代码1:
import os
import cv2
'''
data:2022.4.6
check:Y
function:可将yolo格式的数据集转换为coco格式的(1), 生成annos.txt

需要准备:
labels:yolo格式的标签,是txt格式,名字为图片名
images:原始标签对应的图片,需要有序号
'''


# 原始标签路径
originLabelsDir = 'C:/My/Py_CODE/train/yolo_hostdata/valid/labels'
# 转换后的文件保存路径
saveDir = 'C:/My/Py_CODE/train/coco/annos1.txt'
# 原始标签对应的图片路径
originImagesDir = 'C:/My/Py_CODE/train/yolo_hostdata/valid/images'

txtFileList = os.listdir(originLabelsDir)
with open(saveDir, 'w') as fw:
    for txtFile in txtFileList:
        with open(os.path.join(originLabelsDir, txtFile), 'r') as fr:
            labelList = fr.readlines()
            for label in labelList:
                label = label.strip().split()
                x = float(label[1])
                y = float(label[2])
                w = float(label[3])
                h = float(label[4])

                # convert x,y,w,h to x1,y1,x2,y2
                imagePath = os.path.join(originImagesDir,
                                         txtFile.replace('txt', 'jpg'))
                image = cv2.imread(imagePath)
                H, W, _ = image.shape
                x1 = (x - w / 2) * W
                y1 = (y - h / 2) * H
                x2 = (x + w / 2) * W
                y2 = (y + h / 2) * H
                # 为了与coco标签方式对,标签序号从1开始计算
                fw.write(txtFile.replace('txt', 'jpg') + ' {} {} {} {} {}\n'.format(int(label[0]) + 1, x1, y1, x2, y2))

        print('{} done'.format(txtFile))
  • 代码2
import json
import os
import cv2

#-------------------可用-----------------------------------
'''
data:2022.4.6
check:Y
function:可将yolo格式的数据集转换为coco格式的(2)

需要准备:
classes.txt:一行就是一个类,不需要数字,只要类名称
annos.txt:由上一个.py文件生成
images:与annos.txt对应的图片,需要有序号

生成.json文件,在annotations文件下
'''

# ------------用os提取images文件夹中的图片名称,并且将BBox都读进去------------
# 根路径,里面包含images(图片文件夹),annos.txt(bbox标注),classes.txt(类别标签),
# 以及annotations文件夹(如果没有则会自动创建,用于保存最后的json)
root_path = 'C:/My/Py_CODE/train/coco'
# 用于创建训练集或验证集
phase = 'val'  # 需要修正,保存后的json文件名

# dataset用于保存所有数据的图片信息和标注信息
dataset = {'categories': [], 'annotations': [], 'images': []}

# 打开类别标签
with open(os.path.join(root_path, 'classes.txt')) as f:
    classes = f.read().strip().split()

# 建立类别标签和数字id的对应关系
for i, cls in enumerate(classes, 1):
    dataset['categories'].append({'id': i, 'name': cls, 'supercategory': 'mark'})

# 读取images文件夹的图片名称
indexes = os.listdir(os.path.join(root_path, './valid/images'))

# 统计处理图片的数量
global count
count = 0

# 读取Bbox信息
with open(os.path.join(root_path, './valid/annos.txt')) as tr:
    annos = tr.readlines()

    # ---------------接着将,以上数据转换为COCO所需要的格式---------------
    for k, index in enumerate(indexes):
        count += 1
        # 用opencv读取图片,得到图像的宽和高
        im = cv2.imread(os.path.join(root_path, './valid/images/') + index)
        height, width, _ = im.shape

        # 添加图像的信息到dataset中
        dataset['images'].append({'file_name': index.replace("\\", "/"),
                                  'id': int(index[5:10]),  # 提取文件名 里的数字标号 必须是int类型,不能是str
                                  'width': width,
                                  'height': height})

        for ii, anno in enumerate(annos):
            parts = anno.strip().split()

            # 如果图像的名称和标记的名称对上,则添加标记
            if parts[0] == index:
                # 类别
                cls_id = parts[1]
                # x_min
                x1 = float(parts[2])
                # y_min
                y1 = float(parts[3])
                # x_max
                x2 = float(parts[4])
                # y_max
                y2 = float(parts[5])
                width = max(0, x2 - x1)
                height = max(0, y2 - y1)
                dataset['annotations'].append({
                    'area': width * height,
                    'bbox': [x1, y1, width, height],
                    'category_id': int(cls_id),
                    'id': ii,
                    'image_id': int(index[5:10]),	# 提取文件名里的数字标号  必须是int类型,不能是str
                    'iscrowd': 0,
                    # mask, 矩形是从左上角点按顺时针的四个顶点
                    'segmentation': [[x1, y1, x2, y1, x2, y2, x1, y2]]
                })

        print('{} images handled'.format(count))

# 保存结果的文件夹
folder = os.path.join(root_path, './valid/annotations')
if not os.path.exists(folder):
    os.makedirs(folder)
json_name = os.path.join(root_path, './valid/annotations/{}.json'.format(phase))
with open(json_name, 'w') as f:
    json.dump(dataset, f, ensure_ascii=False, indent=1)
  • 代码3
'''
data:2022.4.6
check:Y
function:载入coco格式的数据集
'''
import json

json_path = r'instances_val2017.json'
json_labels = json.load(open(json_path, "r"))


# load coco data
print(json_labels)
  • 代码4
import os
from pycocotools.coco import COCO
from PIL import Image, ImageDraw
import matplotlib.pyplot as plt
'''
data:2022.4.6
check:Y
function:绘制标注的数据集和对应图片的位置是否正确
绘制前三张,如果yoyo转换标注的json数据集没有问题,则也能在图上画出框

'''
json_path = 'C:/My/Py_CODE/train/coco/valid/annotations/train.json'
# json_path = r'predictions.json'
img_path = 'C:/My/Py_CODE/train/coco/valid/images'  #json对应的图片

# load coco data
coco = COCO(annotation_file=json_path)

# get all image index info
ids = list(sorted(coco.imgs.keys()))
print("number of images: {}".format(len(ids)))

# get all coco class labels
coco_classes = dict([(v["id"], v["name"]) for k, v in coco.cats.items()])

# 遍历前三张图像
for img_id in ids[:3]:
    # 获取对应图像id的所有annotations idx信息
    ann_ids = coco.getAnnIds(imgIds=img_id)

    # 根据annotations idx信息获取所有标注信息
    targets = coco.loadAnns(ann_ids)

    # get image file name
    path = coco.loadImgs(img_id)[0]['file_name']

    # read image
    img = Image.open(os.path.join(img_path, path)).convert('RGB')
    draw = ImageDraw.Draw(img)
    # draw box to image
    for target in targets:
        x, y, w, h = target["bbox"]
        x1, y1, x2, y2 = x, y, int(x + w), int(y + h)
        draw.rectangle((x1, y1, x2, y2))
        draw.text((x1, y1), coco_classes[target["category_id"]])

    # show image
    plt.imshow(img)
    plt.show()

  • 代码5
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
'''
data:2022.4.6
check:Y
function:验证标注的数据集和预测的数据集
如果格式不对,则会报错
如果无误,会返回检测结果
'''

# accumulate predictions from all images
# 载入coco2017验证集标注文件
# coco_true = COCO(annotation_file='coco/train.json')
coco_true = COCO(annotation_file=r'instances_val2017.json')  # 标准数据集(真值)
# coco_pre = coco_true.loadRes('predictions.json')
coco_pre = coco_true.loadRes('predict_results.json')  # 预测数据集(预测值)
# 载入网络在coco2017验证集上预测的结果
coco_evaluator = COCOeval(cocoGt=coco_true, cocoDt=coco_pre, iouType="bbox")    #计算bbox值
coco_evaluator.evaluate()
coco_evaluator.accumulate()
coco_evaluator.summarize()

以上5个代码是yolo数据集到coco数据集标签,和使用cocotools的全过程代码

2.yolov5代码修改

直接对val.py修改

'--save-json' 添加 default=True
parser.add_argument('--save-json', default=True, action='store_true', help='save a COCO-JSON results file')

注释下句
# opt.save_json |= opt.data.endswith('coco.yaml')
修改save_one_json()函数
       # Save/log
       if save_txt:
            save_one_txt(predn, save_conf, shape, file=save_dir / 'labels' / (path.stem + '.txt'))
       if save_json:
            save_one_json(predn, jdict, path, class_map)  # append to COCO-JSON dictionary
       if wandb_logger and wandb_logger.wandb_run:
            wandb_logger.val_one_image(pred, predn, path, names, img[si])  

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}
    image_id = int(path.stem) if path.stem.isnumeric() else path.stem
    box = xyxy2xywh(predn[:, :4])  # xywh
    box[:, :2] -= box[:, 2:] / 2  # xy center to top-left corner
    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)})

save_one_json()函数主要注意image_id = int(path.stem) if path.stem.isnumeric() else path.stem这一参数,要和数据集的对应上(需要检测),内容是图片名,必须是数字,因此可能需要截取图片名的字符段。
经测试 image_id 必须为 int类型,不能是字符串

image_id = int(path.stem[5:])  #这个视情况而定

path.stem是指验证集图片名,如host0000001.jpg  那么path.stem为host0000001,则取数字部分:path.stem[5:]  #为0000001,这里需要和标的验证集里.txt转.json时相同。

如果没有对应上,那么会报以下错误:
pycocotools unable to run: Results do not correspond to current coco set

    # Save JSON
    if save_json and len(jdict):
        w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else ''  # weights
        anno_json = str(Path(data.get('path', '../mydata')) / 'labels/coco_val/annotations/train.json')  # annotations json
        pred_json = str(save_dir / f"{w}predictions.json")  # predictions json
        print(f'\nEvaluating pycocotools mAP... saving {pred_json}...')
        with open(pred_json, 'w') as f:
            json.dump(jdict, f)

anno_json 是指验证集json的路径
pred_json 是指模型预测生成的路径,这个不需要改

需要改
json.dump(jdict, f)
为
json.dump(jdict, f, ensure_ascii=False, indent=1)

json.dump是json文件写入函数,加上以上命令是为了生成的json文件是多行的,区别如下:

不加
在这里插入图片描述
加入:
在这里插入图片描述
这样就方便自查格式了

3.运行示例

修改后,全流程下来:
运行val.py
在这里插入图片描述
自动cocotools评估:

在这里插入图片描述

4.结论

与yolo自身评价的mAP@50相比,用cocotools获得的评价值很低,不知道为什么,有知道的可以分享一下


上面那个精度低的问题解决了,经过测试分析原来是 json格式里的关键字:‘category_id’ 出的问题,它的意思是 “一张图片里的类别标号”

原因简单来说就是:
假设图片里两个目标类别,如果用我的yolo(txt)转coco(json)代码,那么它生成的json文件(真值)里category_id分别为 1,2,原因是在我的第一个代码里:

 # 为了与coco标签方式对,标签序号从1开始计算
                fw.write(txtFile.replace('txt', 'jpg') + ' {} {} {} {} {}\n'.format(int(label[0]) + 1, x1, y1, x2, y2))

他是从1开始标记类别号的。

而val.py里,按照我的方法修改后,它预测的时候是从0开始标记类别号的,这样就错位了,它生成的json文件(预测值)里category_id分别为 0,1

所以完全对不上,精度就很低了

修改&原理(低AP原因)

方法一:
category_id以 0 开始标记

从制作json数据集的第一个代码开始改:

 # 为了与coco标签方式对,标签序号从0开始计算
                fw.write(txtFile.replace('txt', 'jpg') + ' {} {} {} {} {}\n'.format(int(label[0]), x1, y1, x2, y2))

代码 int(label[0]) 去掉 +1

val.py其余地方不用改,按照 2.yolov5代码修改 的方法修改就行

方法二:
category_id以1 开始标记类别号

不用修改制作数据集的代码,仍以1开始标记类别号

在原来的修改基础上,再修改val.py

原代码:
model.eval()
is_coco = isinstance(data.get('val'), str) and data['val'].endswith('coco/val2017.txt')  # COCO dataset
nc = 1 if single_cls else int(data['nc'])  # number of classes

修改为:
model.eval()
is_coco = True
nc = 1 if single_cls else int(data['nc'])  # number of classes

原理:
原来的代码:

is_coco = isinstance(data.get('val'), str) and data['val'].endswith('coco/val2017.txt')  # COCO dataset

bool(is_coco ) = False
因为他不是标准的coco数据集

那么就会造成下面语句:

class_map = coco80_to_coco91_class() if is_coco else list(range(1000))

class_map = list(range(1000)) 显然是从0~999取值

而save_one_json()函数里
‘category_id’: class_map[int(p[5])]
所以也是从0开始的

如果修改
is_coco = True

那么:

class_map = coco80_to_coco91_class()

coco80_to_coco91_class()的作用是从80类映射到91类的coco索引,他是从1开始取值的,而且有的值还没有。为了符合标注coco数据集的映射,如下图所示
在这里插入图片描述
所以还是建议将 is_coco = False,如果用自己的数据集,不需要将 “80类映射到91类” 此方法二不建议使用

最后所得评价结果是:
在这里插入图片描述
这个结果就很舒服,,,,,,,
哈哈哈哈哈哈哈哈

  • 14
    点赞
  • 111
    收藏
    觉得还不错? 一键收藏
  • 47
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 47
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值