首先介紹一下coco數據集
首(shou)先使用(yong)代(dai)碼讀取coco2017驗證集(ji)的標注文件(jian)-instances_val2017.json
with open(annotate_in_file) as f: # annotate_in_file為coco數據標注文件-instances_val2017.json
dataset = json.load(f)?
讀取結果如下(xia):
標(biao)注文件(jian)一(yi)共(gong)分為(wei)5大類,info里面包含著數據集的(de)各種描述信(xin)息;licenses為(wei)許(xu)可信(xin)息;images是所有(you)圖片的(de)信(xin)息,images是一(yi)個(ge)list,每(mei)個(ge)元素對(dui)應一(yi)張圖片的(de)信(xin)息,list的(de)下(xia)標(biao)沒有(you)任(ren)何實際(ji)意義;annotations也是一(yi)個(ge)list,每(mei)個(ge)元素對(dui)應一(yi)個(ge)框,list的(de)下(xia)標(biao)也無實際(ji)意義。
每張圖(tu)片(pian)的(de)(de)信息包括圖(tu)片(pian)名和圖(tu)片(pian)的(de)(de)尺寸、圖(tu)片(pian)的(de)(de)id

每個邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)信息:包含(han)邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)的坐(zuo)標(biao)【邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)左上(shang)角橫坐(zuo)標(biao)x,邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)左上(shang)角縱坐(zuo)標(biao)y,邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)的寬,邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)的高】;邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)的面積;邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)的類別id;邊(bian)(bian)(bian)(bian)(bian)界框(kuang)(kuang)(kuang)的id。

類別(bie)一共包含80個(ge)類別(bie),但是(shi)(shi)類別(bie)的id不是(shi)(shi)從(cong)0-79的,而是(shi)(shi)從(cong)1-90中的80個(ge)數作(zuo)為類別(bie)id,yolov5s預測的類別(bie)id是(shi)(shi)從(cong)0-79的,在做map驗證時需(xu)要將預測類別(bie)id進(jin)行轉(zhuan)化。

yolov5s前處理
為了(le)方便多batch推(tui)理,以(yi)及評測各廠家的GPU性能,我們習慣將圖片統一(yi)到固(gu)定尺寸,yolov5s的官方map結果使用的是640×640的圖片大小。
yolov5s的官方map表(biao)現為map.50=56.8,其他參數為--conf 0.001 --iou 0.65。

coco數據集中有(you)的(de)圖(tu)片(pian)尺寸大于640×640,有(you)的(de)圖(tu)片(pian)小(xiao)于640×640,需要采取一個(ge)統(tong)一的(de)規則將(jiang)所(suo)有(you)圖(tu)片(pian)的(de)尺寸統(tong)一到640×640
1.首先將(jiang)圖片的最長(chang)邊(bian)max_L變成640,記(ji)錄r=640/max_L
2.接(jie)著將(jiang)圖片(pian)的高(gao)h和寬w縮放(fang)(fang)為:new_h = int(h*r) , new_w = int(w*r) ,使(shi)用cv2.resize函數將(jiang)圖像縮放(fang)(fang)到新尺寸
3.經(jing)過上一步,長邊已經(jing)變(bian)成640,剩下的短邊將其填(tian)充到640,在短邊的兩(liang)側均(jun)與(yu)的填(tian)充像素,使得短邊長度(du)變(bian)成640,短邊兩(liang)側填(tian)充的距(ju)離計算方法(fa)如下:d= (640 - min_L)/ 2。使用cv2.copyMakeBorder為圖像填(tian)充像素
4.記錄圖像(xiang)(xiang)的原始尺寸(h0,w0), 圖像(xiang)(xiang)長邊(bian)縮放到640時(shi)(shi),圖像(xiang)(xiang)的尺寸(h,w),圖像(xiang)(xiang)被填充到640×640時(shi)(shi),圖像(xiang)(xiang)周圍(wei)的填充距離(dw,dh)
5.圖片需(xu)要從(cong) HWC 到 CHW, 從(cong) BGR 到 RGB, 使用如下(xia)代碼實現:img.transpose((2, 0, 1))[::-1]
6.圖片還需要歸一化。im /= 255
yolov5s后處理
圖(tu)片經(jing)過模型(xing)推理會得到shapes為(wei)(1,25200,85)的(de)(de)矩陣,1為(wei)圖(tu)片的(de)(de)batch,25200為(wei)邊界(jie)框(kuang)的(de)(de)個(ge)數,85為(wei)邊界(jie)框(kuang)的(de)(de)中心(xin)點(dian)坐標(biao)(x,y),(寬(kuan)w、高(gao)h),80個(ge)類別的(de)(de)概率分數。
1.首先將圖片預測得(de)到框(kuang)進(jin)行(xing)篩選,選擇目標置信度大于0.001的框(kuang)
2.將80個類別(bie)概率都乘(cheng)上目標置信度,得到每個類別(bie)的綜合(he)置信度
3.將邊界框的坐標(biao)從(cong)(center_x, center_y, width, height) to (x1, y1, x2, y2),即(ji)左上(shang)角的坐標(biao)和右下角的坐標(biao)
4.進一步篩選綜(zong)合置信(xin)度(du)大于0.001的(de)框和類別。即使同(tong)一個框對應不同(tong)的(de)類別,這也相(xiang)當于兩個框。
5.將篩選后(hou)的(de)框的(de)坐標,框的(de)類別對應的(de)綜合(he)置信度,框的(de)類別的(de)索引,進行拼接
6.將得到的矩陣(zhen)按(an)照綜合置信度進行降(jiang)序排序得到新的矩陣(zhen)
7.將新矩(ju)陣的邊(bian)(bian)界框坐標分別加(jia)上一個偏移量(liang),即(ji)當前邊(bian)(bian)界框對應的類(lei)別索引(yin)擴大7680倍的值
8.最后將(jiang)所有的框(kuang)送入torchvision.ops.nms,并設定iou_thres=0.65,函(han)數返回(hui)剩余框(kuang)的索(suo)引。在這些索(suo)引中,選擇前300個框(kuang)作為最后的預測框(kuang)
9.最終我們得到一個(ge)(ge)矩陣(zhen),第一個(ge)(ge)維度(du)小于300,代表預測到邊(bian)界框的(de)個(ge)(ge)數(shu),第二個(ge)(ge)維度(du)為6,包含邊(bian)界框的(de)坐標(x1, y1, x2, y2),邊(bian)界框的(de)類別綜(zong)合(he)置信度(du),邊(bian)界框的(de)類別索引(yin)
yolov5s 評價結果驗證
首先官(guan)方的mAP值,是將預(yu)測(ce)結果重新縮放到原(yuan)圖上,再(zai)與coco val2017的標注文件做對比計算(suan)得(de)到的。
我(wo)們需要將預(yu)測框(kuang)映射回(hui)原圖,然(ran)后將預(yu)測框(kuang)結(jie)果保存為與標注文件一(yi)致的json格(ge)式
1.模型推理(li)(li)+后(hou)處(chu)理(li)(li),得到(dao)(dao)(dao)的(de)(de)(de)(de)(de)(de)是640×640的(de)(de)(de)(de)(de)(de)預(yu)測(ce)(ce)(ce)框(kuang)(kuang)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao),我們在(zai)(zai)前處(chu)理(li)(li)中有記錄圖像的(de)(de)(de)(de)(de)(de)原始(shi)尺寸(h0,w0), 圖像resize之(zhi)后(hou)的(de)(de)(de)(de)(de)(de)尺寸(h,w),圖像填(tian)充(chong)(chong)到(dao)(dao)(dao)640需(xu)(xu)要(yao)(yao)填(tian)充(chong)(chong)的(de)(de)(de)(de)(de)(de)距離(dw,dh)。首(shou)先需(xu)(xu)要(yao)(yao)將(jiang)預(yu)測(ce)(ce)(ce)框(kuang)(kuang)的(de)(de)(de)(de)(de)(de)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)減去(qu)填(tian)充(chong)(chong)的(de)(de)(de)(de)(de)(de)距離,得到(dao)(dao)(dao)resize后(hou)圖像上(shang)(shang)框(kuang)(kuang)的(de)(de)(de)(de)(de)(de)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)。接(jie)著將(jiang)橫(heng)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)除以 w/w0,縱坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)除以h/h0(其中w/w0=h/h0,具(ju)體請看前處(chu)理(li)(li))。最后(hou)我們得到(dao)(dao)(dao)原圖上(shang)(shang)預(yu)測(ce)(ce)(ce)得到(dao)(dao)(dao)的(de)(de)(de)(de)(de)(de)邊(bian)界框(kuang)(kuang)的(de)(de)(de)(de)(de)(de)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)。但是標(biao)(biao)(biao)注文件里的(de)(de)(de)(de)(de)(de)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)為(wei)(wei)邊(bian)界框(kuang)(kuang)左上(shang)(shang)角的(de)(de)(de)(de)(de)(de)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao),邊(bian)界框(kuang)(kuang)的(de)(de)(de)(de)(de)(de)寬(kuan)和高,還需(xu)(xu)要(yao)(yao)將(jiang)預(yu)測(ce)(ce)(ce)框(kuang)(kuang)的(de)(de)(de)(de)(de)(de)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)轉為(wei)(wei)左上(shang)(shang)角的(de)(de)(de)(de)(de)(de)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)和框(kuang)(kuang)的(de)(de)(de)(de)(de)(de)寬(kuan)和高。為(wei)(wei)了避免異(yi)常,還需(xu)(xu)要(yao)(yao)將(jiang)框(kuang)(kuang)的(de)(de)(de)(de)(de)(de)坐(zuo)(zuo)(zuo)標(biao)(biao)(biao)約(yue)束在(zai)(zai)原始(shi)圖片的(de)(de)(de)(de)(de)(de)尺寸內(nei)
2.將(jiang)每一個框的結果保存為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為圖像的(de)(de)文件名對應的(de)(de)整數,類別id需要(yao)將(jiang)0-79的(de)(de)id轉為1-90,bbox的(de)(de)坐標(biao)就是原圖上預測框的(de)(de)左(zuo)上角坐標(biao)和邊界框的(de)(de)寬和高,score就是預測框的(de)(de)綜(zong)合置信度(du)。
將所有框的結果(guo)都添加到jdict這個字典當中(zhong)。
3.最(zui)后使用pycocotool這個工(gong)具包(bao)計算coco評價指標(biao),特別注(zhu)意:需(xu)要指明eval.params.imgIds,即(ji)圖(tu)片(pian)的id,實際上就是圖(tu)片(pian)名的整數。
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: # //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 = COCO
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)