首先介绍一下coco数据集
首先使用代码读取coco2017验证集的标注文件-instances_val2017.json
with open(annotate_in_file) as f: # annotate_in_file为coco数据标注文件-instances_val2017.json
dataset = json.load(f)
读取结果如下:标注文件一共分为5大类,info里面包含着数据集的各种描述信息;licenses为许可信息;images是所有图片的信息,images是一个list,每个元素对应一张图片的信息,list的下标没有任何实际意义;annotations也是一个list,每个元素对应一个框,list的下标也无实际意义。
每张图片的信息包括图片名和图片的尺寸、图片的id
每个边界框信息:包含边界框的坐标【边界框左上角横坐标x,边界框左上角纵坐标y,边界框的宽,边界框的高】;边界框的面积;边界框的类别id;边界框的id。
类别一共包含80个类别,但是类别的id不是从0-79的,而是从1-90中的80个数作为类别id,yolov5s预测的类别id是从0-79的,在做map验证时需要将预测类别id进行转化。
yolov5s前处理
为了方便多batch推理,以及评测各厂家的GPU性能,我们习惯将图片统一到固定尺寸,yolov5s的官方map结果使用的是640×640的图片大小。
yolov5s的官方map表现为map.50=56.8,其他参数为--conf 0.001 --iou 0.65。
coco数据集中有的图片尺寸大于640×640,有的图片小于640×640,需要采取一个统一的规则将所有图片的尺寸统一到640×640
1.首先将图片的最长边max_L变成640,记录r=640/max_L
2.接着将图片的高h和宽w缩放为:new_h = int(h*r) , new_w = int(w*r) ,使用cv2.resize函数将图像缩放到新尺寸
3.经过上一步,长边已经变成640,剩下的短边将其填充到640,在短边的两侧均与的填充像素,使得短边长度变成640,短边两侧填充的距离计算方法如下:d= (640 - min_L)/ 2。使用cv2.copyMakeBorder为图像填充像素
4.记录图像的原始尺寸(h0,w0), 图像长边缩放到640时,图像的尺寸(h,w),图像被填充到640×640时,图像周围的填充距离(dw,dh)
5.图片需要从 HWC 到 CHW, 从 BGR 到 RGB, 使用如下代码实现:img.transpose((2, 0, 1))[::-1]
6.图片还需要归一化。im /= 255
yolov5s后处理
图片经过模型推理会得到shapes为(1,25200,85)的矩阵,1为图片的batch,25200为边界框的个数,85为边界框的中心点坐标(x,y),(宽w、高h),80个类别的概率分数。
1.首先将图片预测得到框进行筛选,选择目标置信度大于0.001的框
2.将80个类别概率都乘上目标置信度,得到每个类别的综合置信度
3.将边界框的坐标从(center_x, center_y, width, height) to (x1, y1, x2, y2),即左上角的坐标和右下角的坐标
4.进一步筛选综合置信度大于0.001的框和类别。即使同一个框对应不同的类别,这也相当于两个框。
5.将筛选后的框的坐标,框的类别对应的综合置信度,框的类别的索引,进行拼接
6.将得到的矩阵按照综合置信度进行降序排序得到新的矩阵
7.将新矩阵的边界框坐标分别加上一个偏移量,即当前边界框对应的类别索引扩大7680倍的值
8.最后将所有的框送入torchvision.ops.nms,并设定iou_thres=0.65,函数返回剩余框的索引。在这些索引中,选择前300个框作为最后的预测框
9.最终我们得到一个矩阵,第一个维度小于300,代表预测到边界框的个数,第二个维度为6,包含边界框的坐标(x1, y1, x2, y2),边界框的类别综合置信度,边界框的类别索引
yolov5s 评价结果验证
首先官方的mAP值,是将预测结果重新缩放到原图上,再与coco val2017的标注文件做对比计算得到的。
我们需要将预测框映射回原图,然后将预测框结果保存为与标注文件一致的json格式
1.模型推理+后处理,得到的是640×640的预测框坐标,我们在前处理中有记录图像的原始尺寸(h0,w0), 图像resize之后的尺寸(h,w),图像填充到640需要填充的距离(dw,dh)。首先需要将预测框的坐标减去填充的距离,得到resize后图像上框的坐标。接着将横坐标除以 w/w0,纵坐标除以h/h0(其中w/w0=h/h0,具体请看前处理)。最后我们得到原图上预测得到的边界框的坐标。但是标注文件里的坐标为边界框左上角的坐标,边界框的宽和高,还需要将预测框的坐标转为左上角的坐标和框的宽和高。为了避免异常,还需要将框的坐标约束在原始图片的尺寸内
2.将每一个框的结果保存为json格式,用于与标注文件做对比计算。保存格式如下所示:
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)})
image_id为图像的文件名对应的整数,类别id需要将0-79的id转为1-90,bbox的坐标就是原图上预测框的左上角坐标和边界框的宽和高,score就是预测框的综合置信度。
将所有框的结果都添加到jdict这个字典当中。
3.最后使用pycocotool这个工具包计算coco评价指标,特别注意:需要指明eval.params.imgIds,即图片的id,实际上就是图片名的整数。
anno_json = str('instances_val2017.json') # annotations
pred_json = str("predictions.json") # predictions
LOGGER.info(f'\nEvaluating pycocotools mAP... saving {pred_json}...')
with open(pred_json, 'w') as f:
json.dump(jdict, f)
try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
check_requirements('pycocotools')
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
anno = COCO(anno_json) # init annotations api
pred = anno.loadRes(pred_json) # init predictions api
eval = COCOeval(anno, pred, 'bbox')
if is_coco:
eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.im_files] # image IDs to evaluate
eval.evaluate()
eval.accumulate()
eval.summarize()
map, map50 = eval.stats[:2] # update results (mAP@0.5:0.95, mAP@0.5)