首页 > 其他分享 >目标检测mAP计算方法-简单易懂

目标检测mAP计算方法-简单易懂

时间:2023-08-09 21:35:53浏览次数:45  
标签:xml mAP img self json coco 易懂 id 计算方法

本次将整理一份map计算方法,主要分为三部分,第一部分简单了解原理,第二部分理解如何调用coco等相关库得到map,第三部分教会读者如何结合模型(任何可计算map的网络模型)调用而生成map,而本博客希望读者能掌握使用模型预测map,其重点也为第三部分:

 

第一部分介绍map原理,主要引用部分他人结果,

 

第二部分说明如何整理真实标签的数据及预测数据,调用pycocotools库实现map的计算,以下便是本博客的整理(附带转换coco json代码)

 

第三部分说明如何在模型中直接预测map,即结合模型预测+本博客代码样列,便可预测map,样列如下:

 

以下是根据模型运用Computer_map类主代码,详细,我将在第三部分展示细节代码。

1 def computer_main(data_root, model):
 2     '''
 3     data_root:任何文件夹,但必须保证每个图片与对应xml必须放在同一个文件夹中
 4     model:模型,用于预测
 5     '''
 6     C = Computer_map()
 7     img_root_lst = C.get_img_root_lst(data_root)  # 获得图片绝对路径与图片产生image_id映射关系
 8 
 9     # 在self.coco_json中保存categories,便于产生coco_json和predetect_json
10     categories = model.CLASSES  # 可以给txt路径读取,或直接给列表  #*********************得到classes,需要更改的地方***********##
11     C.get_categories(categories)
12 
13     # 产生coco_json格式
14     xml_root_lst = [name[:-3] + 'xml' for name in img_root_lst]
15     for xml_root in xml_root_lst: C.xml2cocojson(xml_root)  # 产生coco json 并保存到self.coco_json中
16 
17     # 产生预测的json
18     for img_path in img_root_lst:
19 
20         parse_result = predict(model, img_path)  ####**********************需要更改的地方***********************####
21 
22 
23         result, classes = parse_result['result'], parse_result['classes']
24         # restult 格式为列表[x1,y1,x2,y2,score,label],若无结果为空
25         img_name = C.get_strfile(img_path)
26         C.detect2json(result, img_name)
27     C.computer_map()  # 计算map

 

 

 

 

 

 

一.map原理:

定义内容均来自此网址:https://zhuanlan.zhihu.com/p/70667071

Accuracy:准确率

✔️ 准确率=预测正确的样本数/所有样本数,即预测正确的样本比例(包括预测正确的正样本和预测正确的负样本,不过在目标检测领域,没有预测正确的负样本这一说法,所以目标检测里面没有用Accuracy的)。

目标检测mAP计算方法-简单易懂_xml

Precision:查准率

✔️ recision表示某一类样本预测有多准。

✔️ Precision针对的是某一类样本,如果没有说明类别,那么Precision是毫无意义的(有些地方不说明类别,直接说Precision,是因为二分类问题通常说的Precision都是正样本的Precision)。

Recall:召回率

✔️ Recall和Precision一样,脱离类别是没有意义的。说道Recall,一定指的是某个类别的Recall。Recall表示某一类样本,预测正确的与所有Ground Truth的比例。

✍️ Recall计算的时候,分母是Ground Truth中某一类样本的数量,而Precision计算的时候,是预测出来的某一类样本数。

F1 Score:平衡F分数

F1分数,它被定义为查准率和召回率的调和平均数

目标检测mAP计算方法-简单易懂_json_02

AP: Average Precision

以Recall为横轴,Precision为纵轴,就可以画出一条PR曲线,PR曲线下的面积就定义为AP,即:

目标检测mAP计算方法-简单易懂_xml_03

PR曲线

由于计算积分相对困难,因此引入插值法,计算AP公式如下:

目标检测mAP计算方法-简单易懂_json格式_04

计算面积:

目标检测mAP计算方法-简单易懂_xml_05

原理:

 

 

 

 

 

二.代码-用于实现map:

 

本部分才是本博客重要内容,我将介绍2部分,第一部分如何使用有标记的真实数据产生coco json格式与如何使用模型预测结果产生预测json格式,第二部分如何使用代码计算map。

①.json格式

真实数据json格式实际是coco json 格式,主要是如下图:

目标检测mAP计算方法-简单易懂_xml_06

 

 其中images格式如下图:

目标检测mAP计算方法-简单易懂_xml_07

 

 

annotations格式如下:

目标检测mAP计算方法-简单易懂_xml_08

 

 categories格式为:

目标检测mAP计算方法-简单易懂_xml_09

 

 以上为真实数据转换为json的格式。

预测结果数据json格式转换,主要是如下图:

目标检测mAP计算方法-简单易懂_json_10

                                   

目标检测mAP计算方法-简单易懂_json格式_11

 

 以上右图是整体结构,实际为列表,左图是预测信息,保存为字典,其详细内容如下:

目标检测mAP计算方法-简单易懂_xml_12

 

 特别注意:image id 对应真实coco json图像的image-id,类别id也是对应真实coco json中的类别id。

 ②.实际代码,借助pycocotools 库中评估类别,具体代码如下图:

1 from pycocotools.coco import COCO
 2 from pycocotools.cocoeval import COCOeval
 3 
 4 if __name__ == "__main__":
 5     cocoGt = COCO('coco_json_format.json')        #标注文件的路径及文件名,json文件形式
 6     cocoDt = cocoGt.loadRes('predect_format.json')  #自己的生成的结果的路径及文件名,json文件形式
 7     cocoEval = COCOeval(cocoGt, cocoDt, "bbox")
 8     cocoEval.evaluate()
 9     cocoEval.accumulate()
10     cocoEval.summarize()

 

 

③结果展示:

目标检测mAP计算方法-简单易懂_json_13

 

 

 

二.代码-模型预测map:

 

使用模型实现代码的类:

 

模型预测map:

 

1 class Computer_map():
  2     '''
  3     主代码样列
  4     def computer_main(data_root, model):#data_root:任何文件夹,但必须保证每个图片与对应xml必须放在同一个文件夹中,model:模型,用于预测
  5         C = Computer_map()
  6         img_root_lst = C.get_img_root_lst(data_root)  # 获得图片绝对路径与图片产生image_id映射关系
  7 
  8         # 在self.coco_json中保存categories,便于产生coco_json和predetect_json
  9         categories = model.CLASSES  # 可以给txt路径读取,或直接给列表  #*********************得到classes,需要更改的地方***********##
 10         C.get_categories(categories)
 11 
 12         # 产生coco_json格式
 13         xml_root_lst = [name[:-3] + 'xml' for name in img_root_lst]
 14         for xml_root in xml_root_lst: C.xml2cocojson(xml_root)  # 产生coco json 并保存到self.coco_json中
 15 
 16         # 产生预测的json
 17         for img_path in img_root_lst:
 18 
 19             parse_result = predict(model, img_path)  ####**********************需要更改的地方***********************####
 20 
 21             result, classes = parse_result['result'], parse_result['classes']
 22             # restult 格式为列表[x1,y1,x2,y2,score,label],若无结果为空
 23             img_name = C.get_strfile(img_path)
 24             C.detect2json(result, img_name)
 25         C.computer_map()  # 计算map
 26 
 27     '''
 28 
 29     def __init__(self):
 30         self.img_format = ['png', 'jpg', 'JPG', 'PNG', 'bmp', 'jpeg']
 31         self.coco_json = {'images': [], 'type': 'instances', 'annotations': [], 'categories': []}
 32         self.predetect_json = []  # 保存字典
 33         self.image_id = 10000000  # 图像的id,每增加一张图片便+1
 34         self.anation_id = 10000000
 35         self.imgname_map_id = {}  # 图片名字映射id
 36 
 37     def read_txt(self, file_path):
 38         with open(file_path, 'r') as f:
 39             content = f.read().splitlines()
 40         return content
 41 
 42     def get_categories(self, categories):
 43         '''
 44         categories:为字符串,指绝对路径;为列表,指类本身
 45         return:将categories存入coco json中
 46         '''
 47         if isinstance(categories, str):
 48             categories = self.read_txt(categories)
 49         elif isinstance(categories, list or tuple):
 50             categories = list(categories)
 51 
 52         category_json = [{"supercategory": cat, "id": i + 1, "name": cat} for i, cat in enumerate(categories)]
 53         self.coco_json['categories'] = category_json
 54 
 55     def computer_map(self, coco_json_path=None, predetect_json_path=None):
 56         from pycocotools.coco import COCO
 57         from pycocotools.cocoeval import COCOeval
 58         from collections import defaultdict
 59         import time
 60         import json
 61         from pycocotools import mask as maskUtils
 62         import numpy as np
 63         # 继承修改coco json文件
 64         class COCO_modify(COCO):
 65             def __init__(self, coco_json_data=None):
 66                 """
 67                 Constructor of Microsoft COCO helper class for reading and visualizing annotations.
 68                 :param annotation_file (str): location of annotation file
 69                 :param image_folder (str): location to the folder that hosts images.
 70                 :return:
 71                 """
 72                 # load dataset
 73                 self.dataset, self.anns, self.cats, self.imgs = dict(), dict(), dict(), dict()
 74                 self.imgToAnns, self.catToImgs = defaultdict(list), defaultdict(list)
 75                 if coco_json_data is not None:
 76                     print('loading annotations into memory...')
 77                     tic = time.time()
 78                     if isinstance(coco_json_data, str):
 79                         with open(coco_json_data, 'r') as f:
 80                             dataset = json.load(f)
 81                         assert type(dataset) == dict, 'annotation file format {} not supported'.format(type(dataset))
 82                         print('Done (t={:0.2f}s)'.format(time.time() - tic))
 83                     else:
 84                         dataset = coco_json_data
 85                     self.dataset = dataset
 86                     self.createIndex()
 87 
 88             def loadRes(self, predetect_json_data):
 89                 import copy
 90                 """
 91                 Load result file and return a result api object.
 92                 :param   resFile (str)     : file name of result file
 93                 :return: res (obj)         : result api object
 94                 """
 95                 res = COCO_modify()
 96                 res.dataset['images'] = [img for img in self.dataset['images']]
 97 
 98                 print('Loading and preparing results...')
 99                 tic = time.time()
100 
101                 if isinstance(predetect_json_data, str):
102                     with open(predetect_json_data, 'r') as f:
103                         anns = json.load(f)
104 
105                     print('Done (t={:0.2f}s)'.format(time.time() - tic))
106                 else:
107                     anns = predetect_json_data
108 
109                 assert type(anns) == list, 'results in not an array of objects'
110                 annsImgIds = [ann['image_id'] for ann in anns]
111                 assert set(annsImgIds) == (set(annsImgIds) & set(self.getImgIds())), \
112                     'Results do not correspond to current coco set'
113                 if 'caption' in anns[0]:
114                     imgIds = set([img['id'] for img in res.dataset['images']]) & set([ann['image_id'] for ann in anns])
115                     res.dataset['images'] = [img for img in res.dataset['images'] if img['id'] in imgIds]
116                     for id, ann in enumerate(anns):
117                         ann['id'] = id + 1
118                 elif 'bbox' in anns[0] and not anns[0]['bbox'] == []:
119                     res.dataset['categories'] = copy.deepcopy(self.dataset['categories'])
120                     for id, ann in enumerate(anns):
121                         bb = ann['bbox']
122                         x1, x2, y1, y2 = [bb[0], bb[0] + bb[2], bb[1], bb[1] + bb[3]]
123                         if not 'segmentation' in ann:
124                             ann['segmentation'] = [[x1, y1, x1, y2, x2, y2, x2, y1]]
125                         ann['area'] = bb[2] * bb[3]
126                         ann['id'] = id + 1
127                         ann['iscrowd'] = 0
128                 elif 'segmentation' in anns[0]:
129                     res.dataset['categories'] = copy.deepcopy(self.dataset['categories'])
130                     for id, ann in enumerate(anns):
131                         # now only support compressed RLE format as segmentation results
132                         ann['area'] = maskUtils.area(ann['segmentation'])
133                         if not 'bbox' in ann:
134                             ann['bbox'] = maskUtils.toBbox(ann['segmentation'])
135                         ann['id'] = id + 1
136                         ann['iscrowd'] = 0
137                 elif 'keypoints' in anns[0]:
138                     res.dataset['categories'] = copy.deepcopy(self.dataset['categories'])
139                     for id, ann in enumerate(anns):
140                         s = ann['keypoints']
141                         x = s[0::3]
142                         y = s[1::3]
143                         x0, x1, y0, y1 = np.min(x), np.max(x), np.min(y), np.max(y)
144                         ann['area'] = (x1 - x0) * (y1 - y0)
145                         ann['id'] = id + 1
146                         ann['bbox'] = [x0, y0, x1 - x0, y1 - y0]
147                 print('DONE (t={:0.2f}s)'.format(time.time() - tic))
148 
149                 res.dataset['annotations'] = anns
150                 res.createIndex()
151                 return res
152 
153         coco_json_data = coco_json_path if coco_json_path is not None else self.coco_json
154         cocoGt = COCO_modify(coco_json_data)  # 标注文件的路径及文件名,json文件形式
155         predetect_json_data = predetect_json_path if predetect_json_path is not None else self.predetect_json
156         cocoDt = cocoGt.loadRes(predetect_json_data)  # 自己的生成的结果的路径及文件名,json文件形式
157 
158         cocoEval = COCOeval(cocoGt, cocoDt, "bbox")
159         cocoEval.evaluate()
160         cocoEval.accumulate()
161         cocoEval.summarize()
162 
163     def get_img_root_lst(self, root_data):
164         import os
165         img_root_lst = []
166         for dir, file, names in os.walk(root_data):
167             img_lst = [os.path.join(dir, name) for name in names if name[-3:] in self.img_format]
168             img_root_lst = img_root_lst + img_lst
169             for na in img_lst:  # 图片名字映射image_id
170                 self.image_id += 1
171                 self.imgname_map_id[self.get_strfile(na)] = self.image_id
172         return img_root_lst  # 得到图片绝对路径
173 
174     def get_strfile(self, file_str, pos=-1):
175         '''
176         得到file_str / or \\ 的最后一个名称
177         '''
178         endstr_f_filestr = file_str.split('\\')[pos] if '\\' in file_str else file_str.split('/')[pos]
179         return endstr_f_filestr
180 
181     def read_xml(self, xml_root):
182         '''
183         :param xml_root: .xml文件
184         :return: dict('cat':['cat1',...],'bboxes':[[x1,y1,x2,y2],...],'whd':[w ,h,d])
185         '''
186 
187         import xml.etree.ElementTree as ET
188         import os
189 
190         dict_info = {'cat': [], 'bboxes': [], 'box_wh': [], 'whd': []}
191         if os.path.splitext(xml_root)[-1] == '.xml':
192             tree = ET.parse(xml_root)  # ET是一个xml文件解析库,ET.parse()打开xml文件。parse--"解析"
193             root = tree.getroot()  # 获取根节点
194             whd = root.find('size')
195             whd = [int(whd.find('width').text), int(whd.find('height').text), int(whd.find('depth').text)]
196             xml_filename = root.find('filename').text
197             dict_info['whd'] = whd
198             dict_info['xml_filename'] = xml_filename
199             for obj in root.findall('object'):  # 找到根节点下所有“object”节点
200                 cat = str(obj.find('name').text)  # 找到object节点下name子节点的值(字符串)
201                 bbox = obj.find('bndbox')
202                 x1, y1, x2, y2 = [int(bbox.find('xmin').text),
203                                   int(bbox.find('ymin').text),
204                                   int(bbox.find('xmax').text),
205                                   int(bbox.find('ymax').text)]
206                 b_w = x2 - x1 + 1
207                 b_h = y2 - y1 + 1
208 
209                 dict_info['cat'].append(cat)
210                 dict_info['bboxes'].append([x1, y1, x2, y2])
211                 dict_info['box_wh'].append([b_w, b_h])
212 
213         else:
214             print('[inexistence]:{} suffix is not xml '.format(xml_root))
215         return dict_info
216 
217     def xml2cocojson(self, xml_root):
218         '''
219         处理1个xml,将其真实json保存到self.coco_json中
220         '''
221         assert len(self.coco_json['categories']) > 0, 'self.coco_json[categories] must exist v'
222         categories = [cat_info['name'] for cat_info in  self.coco_json['categories']]
223         xml_info = self.read_xml(xml_root)
224         if len(xml_info['cat']) > 0:
225             xml_filename = xml_info['xml_filename']
226             xml_name = self.get_strfile(xml_root)
227             img_name = xml_name[:-3] + xml_filename[-3:]
228             # 转为coco json时候,若add_file为True则在coco json文件的file_name增加文件夹名称+图片名字
229 
230             image_id = self.imgname_map_id[img_name]
231             w, h, d = xml_info['whd']
232             # 构建json文件字典
233             image_json = {'file_name': img_name, 'height': h, 'width': w, 'id': image_id}
234             ann_json = []
235             for i, category in enumerate(xml_info['cat']):
236                 # 表示有box存在,可以添加images信息
237 
238                 category_id = categories.index(category) + 1  # 给出box对应标签索引为类
239                 self.anation_id = self.anation_id + 1
240                 xmin, ymin, xmax, ymax = xml_info['bboxes'][i]
241 
242                 o_width, o_height = xml_info['box_wh'][i]
243 
244                 if (xmax <= xmin) or (ymax <= ymin):
245                     print('code:[{}] will be abandon due to  {} min of box w or h more than max '.format(category,
246                                                                                                          xml_root))  # 打印错误的box
247                 else:
248                     ann = {'area': o_width * o_height, 'iscrowd': 0, 'image_id': image_id,
249                            'bbox': [xmin, ymin, o_width, o_height],
250                            'category_id': category_id, 'id': self.anation_id, 'ignore': 0,
251                            'segmentation': []}
252                     ann_json.append(ann)
253 
254             if len(ann_json) > 0:  # 证明存在 annotation
255                 for ann in ann_json:  self.coco_json['annotations'].append(ann)
256                 self.coco_json['images'].append(image_json)
257 
258     def detect2json(self, predetect_result, img_name,score_thr=-1):
259         '''
260         predetect_result:为列表,每个列表中包含[x1, y1, x2, y2, score, label]
261         img_name: 图片的名字
262         '''
263         if len(predetect_result) > 0:
264             categories = [cat_info['name'] for cat_info in  self.coco_json['categories']]
265             for result in predetect_result:
266                 x1, y1, x2, y2, score, label = result
267                 if score>score_thr:
268                     w, h = int(x2 - x1), int(y2 - y1)
269                     x1, y1 = int(x1), int(y1)
270                     img_name_new = self.get_strfile(img_name)
271                     image_id = self.imgname_map_id[img_name_new]
272                     category_id = list(categories).index(label) + 1
273                     detect_json = {
274                         "area": w * h,
275                         "iscrowd": 0,
276                         "image_id": image_id,
277                         "bbox": [
278                             x1,
279                             y1,
280                             w,
281                             h
282                         ],
283                         "category_id": category_id,
284                         "id": image_id,
285                         "ignore": 0,
286                         "segmentation": [],
287                         "score": score
288                     }
289                     self.predetect_json.append(detect_json)
290 
291     def write_json(self,out_dir):
292         import os
293         import json
294         coco_json_path=os.path.join(out_dir,'coco_json_data.json')
295         with open(coco_json_path, 'w') as f:
296             json.dump(self.coco_json, f, indent=4)  # indent表示间隔长度
297         predetect_json_path=os.path.join(out_dir,'predetect_json_data.json')
298         with open(predetect_json_path, 'w') as f:
299             json.dump(self.predetect_json, f, indent=4)  # indent表示间隔长度

 

结果展示:左图为mmdet2.19模型结果,右图为yolov5模型结果

 

目标检测mAP计算方法-简单易懂_xml_14

目标检测mAP计算方法-简单易懂_json_15

 

 

 

 

 

 

 

 

 

 

 

 

附带xml转换coco json代码:

 

 xml2cocojson

 

 

 

 

 

标签:xml,mAP,img,self,json,coco,易懂,id,计算方法
From: https://blog.51cto.com/u_16162011/7025598

相关文章

  • mapreduce 去重的问题怎么解决
    mapreduce去重的问题怎么解决? john 89 tom 100 mary 100 mary 200 tom 20———–我刚学mapreduce,正在练习,上面这个我计算了很久也不对,就是对第一列去重,去重后应该是3如果用mapreduce计算成功后,part-00000 的文件内容 是:请问下,这个mapreduce怎么写啊?ma......
  • lVU__gMAp
    title:'戏剧的真实与欺骗的幻象:从舞台到荧幕'date:2023-08-0513:33:44tags:[]published:truehideInList:falsefeature:isTop:false第一幕:戏剧与欺骗的界线当我站在灯火辉煌的舞台上,我是在演戏,还是在骗人呢?我曾思考过这个问题,也曾试图寻找答案。戏剧和欺骗,这两......
  • 字符设备驱动-11.mmap机制
    1引入mmap应用程序和驱动程序之间传递数据时,可以通过read、write函数进行,用户态和内核态的数据交互一般用copy_from_user,copy_to_user。这种方式在数据量比较小时没什么问题;但是数据量比较大时效率就太低了。比如更新LCD显示时,如果每次都让APP传递一帧数据给内核,假设......
  • 由put方法深入了解HashMap
    正文put方法publicVput(Kkey,Vvalue){returnputVal(hash(key),key,value,false,true);}当进入put方法中时,首先将键值赋给key和value,再通过key计算出相对应的hash值。再将数据传到putVal方法中。变量名及方法名代表的意义:key:键的数据value:值的数据......
  • 使用C#配合modbus协议的16进制代码生成crc16校验码的计算方法
    前言在网上也是查看了很多关于crc16校验的文章,但是好像都是对于有基础的人看的,我当时拿起直接使用,发现行不通,这对于零基础的不是很友好,所以决定贡献一篇,哈哈哈哈~~~publicuintCalcCRC16(stringhexCommand){byte[]pBuf=HexStringToByteArray(......
  • 01-[Linux][regmap]regmap模块介绍
    1、什么是regmap?Linux引入regmap是为了统一管理内核的i2c,spi等总线,将i2c、spi驱动做了一次重构,把I/O读写的重复逻辑在regmap中实现。只需初始化时指定总线类型、寄存器位宽等关键参数,即可通过regmap模型接口来操作器件寄存器。将i2c、spi、mmio、irq等抽象出统一接口regmap_read......
  • Java遍历集合(List,Map)
    遍历ListpublicvoiditeratorList(){List<String>list=newArrayList<>();list.add("a");list.add("b");//方法1使用iterator遍历Iterator<String>iterator=list.iterator();w......
  • java-vector-tile | 使用java生成Mapbox矢量图块规范的矢量图块
    https://github.com/ElectronicChartCentre/java-vector-tile/tree/master/src/main/java/no/ecc/vectortile使用java生成mapbox-gl可读的vectortile......
  • Java Map初始化赋值 Map初始化和Map赋值
    JavaMap初始化赋值原文链接:https://www.python100.com/html/105098.html一、Map初始化Map是Java中的一种数据结构,用于存储键值对。初始化Map有两种主要方法。第一种方法使用put方法手动为Map添加键值对;第二种方法使用静态代码块初始化Map。1.put方法手动添加键值对put方法......
  • C# byte[]与Bitmap互转
    首先先观察一下本地bmp图像结构(参考:https://blog.csdn.net/qq_37872392/article/details/124710600): 可以看到bmp图像结构除了纯图像像素点位信息,还有一块未用空间(OffSet)。所以如果需要得到图像所有数据进行转换,则可以使用网上提供的大部分方式:bitmap转byte[]:publicbyte[......