[Python] mAP evaluation code

mAP concept reference  https://blog.csdn.net/u013066730/article/details/82707559

import os
import numpy as np
from os import mkdir
import xml.etree.ElementTree as ET

from easydict import EasyDict as edict

cfgs = edict()
cfgs.EVALUATE_DIR = r"E:\data\yolov3\test\evaluate"
cfgs.VERSION = r"cls_dir"
cfgs.USE_07_METRIC = False

NAME_LABEL_MAP = {"person": 0, "car": 1}


def write_voc_results_file(all_boxes, test_imgid_list, det_save_dir):
    for cls, cls_id in NAME_LABEL_MAP.items():
        if cls == 'back_ground':
            continue
        print("Writing {} VOC resutls file".format(cls))

        if not os.path.exists(det_save_dir):
            mkdir(det_save_dir)
        det_save_path = os.path.join(det_save_dir, "det_" + cls + ".txt")
        with open(det_save_path, 'wt') as f:
            for index, img_name in enumerate(test_imgid_list):
                this_img_detections = np.array(all_boxes[index])
                if this_img_detections.shape[0] == 0:
                    continue
                this_cls_detections = this_img_detections[this_img_detections[:, 0] == cls_id]
                if this_cls_detections.shape[0] == 0:
                    continue  # this cls has none detections in this img
                for a_det in this_cls_detections:
                    f.write('{:s} {:.3f} {:.1f} {:.1f} {:.1f} {:.1f}\n'.
                            format(img_name, a_det[1],
                                   a_det[2], a_det[3],
                                   a_det[4], a_det[5]))  # that is [img_name, score, xmin, ymin, xmax, ymax]


def parse_rec(filename):
    """ Parse a PASCAL VOC xml file """
    tree = ET.parse(filename)
    objects = []
    for obj in tree.findall('object'):
        obj_struct = {}
        obj_struct['name'] = obj.find('name').text  #####################这里可能需要调整
        obj_struct['pose'] = obj.find('pose').text
        obj_struct['truncated'] = int(obj.find('truncated').text)
        obj_struct['difficult'] = int(obj.find('difficult').text)
        bbox = obj.find('bndbox')
        obj_struct['bbox'] = [int(bbox.find('xmin').text),
                              int(bbox.find('ymin').text),
                              int(bbox.find('xmax').text),
                              int(bbox.find('ymax').text)]
        objects.append(obj_struct)
    return objects


def voc_ap(rec, prec, use_07_metric=False):
    if use_07_metric:
        # 11 point metric
        ap = 0.
        for t in np.arange(0., 1.1, 0.1):
            if np.sum(rec >= t) == 0:
                p = 0
            else:
                p = np.max(prec[rec >= t])
            ap = ap + p / 11.
    else:
        # correct AP calculation
        # first append sentinel values at the end
        mrec = np.concatenate(([0.], rec, [1.]))
        mpre = np.concatenate(([0.], prec, [0.]))

        # compute the precision envelope
        for i in range(mpre.size - 1, 0, -1):
            mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

        # to calculate area under PR curve, look for points
        # where X axis (recall) changes value
        i = np.where(mrec[1:] != mrec[:-1])[0]

        # and sum (\Delta recall) * prec
        ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
    return ap


def voc_eval(detpath, annopath, test_imgid_list, cls_name, ovthresh=0.5,
             use_07_metric=False, use_diff=False):
    # 1. parse xml to get gtboxes
    # read list of images
    imagenames = test_imgid_list

    recs = {}
    for i, imagename in enumerate(imagenames):
        recs[imagename] = parse_rec(os.path.join(annopath, imagename + '.xml'))
        # if i % 100 == 0:
        #   print('Reading annotation for {:d}/{:d}'.format(
        #     i + 1, len(imagenames)))

    # 2. get gtboxes for this class.
    class_recs = {}
    num_pos = 0
    # if cls_name == 'person':
    #   print ("aaa")
    for imagename in imagenames:
        R = [obj for obj in recs[imagename] if obj['name'] == cls_name]
        bbox = np.array([x['bbox'] for x in R])
        if use_diff:
            difficult = np.array([False for x in R]).astype(np.bool)
        else:
            difficult = np.array([x['difficult'] for x in R]).astype(np.bool)
        det = [False] * len(R)
        num_pos = num_pos + sum(~difficult)  # ignored the diffcult boxes
        class_recs[imagename] = {'bbox': bbox,
                                 'difficult': difficult,
                                 'det': det}  # det means that gtboxes has already been detected

    # 3. read the detection file
    detfile = os.path.join(detpath, "det_" + cls_name + ".txt")
    with open(detfile, 'r') as f:
        lines = f.readlines()
    # for a line. that is [img_name, confidence, xmin, ymin, xmax, ymax]
    splitlines = [x.strip().split(' ') for x in lines]  # a list that include a list
    image_ids = [x[0] for x in splitlines]  # img_id is img_name
    confidence = np.array([float(x[1]) for x in splitlines])
    BB = np.array([[float(z) for z in x[2:]] for x in splitlines])
    nd = len(image_ids)  # num of detections. That, a line is a det_box.
    tp = np.zeros(nd)
    fp = np.zeros(nd)

    if BB.shape[0] > 0:
        # sort by confidence
        sorted_ind = np.argsort(-confidence)
        sorted_scores = np.sort(-confidence)
        BB = BB[sorted_ind, :]
        image_ids = [image_ids[x] for x in sorted_ind]  # reorder the img_name

        # go down dets and mark TPs and FPs
        for d in range(nd):
            R = class_recs[image_ids[d]]  # img_id is img_name
            bb = BB[d, :].astype(float)
            ovmax = -np.inf
            BBGT = R['bbox'].astype(float)

            if BBGT.size > 0:
                # compute overlaps
                # intersection
                ixmin = np.maximum(BBGT[:, 0], bb[0])
                iymin = np.maximum(BBGT[:, 1], bb[1])
                ixmax = np.minimum(BBGT[:, 2], bb[2])
                iymax = np.minimum(BBGT[:, 3], bb[3])
                iw = np.maximum(ixmax - ixmin + 1., 0.)
                ih = np.maximum(iymax - iymin + 1., 0.)
                inters = iw * ih

                # union
                uni = ((bb[2] - bb[0] + 1.) * (bb[3] - bb[1] + 1.) +
                       (BBGT[:, 2] - BBGT[:, 0] + 1.) *
                       (BBGT[:, 3] - BBGT[:, 1] + 1.) - inters)

                overlaps = inters / uni
                ovmax = np.max(overlaps)
                jmax = np.argmax(overlaps)

            if ovmax > ovthresh:
                if not R['difficult'][jmax]:
                    if not R['det'][jmax]:
                        tp[d] = 1.
                        R['det'][jmax] = 1
                    else:
                        fp[d] = 1.
            else:
                fp[d] = 1.

    # 4. get recall, precison and AP
    fp = np.cumsum(fp)
    tp = np.cumsum(tp)
    rec = tp / float(num_pos)
    # avoid divide by zero in case the first detection matches a difficult
    # ground truth
    prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
    ap = voc_ap(rec, prec, use_07_metric)

    return rec, prec, ap


def do_python_eval(test_imgid_list, test_annotation_path):
    AP_list = []
    # import matplotlib.pyplot as plt
    # import matplotlib.colors as colors
    # color_list = colors.cnames.keys()[::6]

    for cls, index in NAME_LABEL_MAP.items():
        if cls == 'back_ground':
            continue
        recall, precision, AP = voc_eval(detpath=os.path.join(cfgs.EVALUATE_DIR, cfgs.VERSION),
                                         test_imgid_list=test_imgid_list,
                                         cls_name=cls,
                                         annopath=test_annotation_path,
                                         use_07_metric=cfgs.USE_07_METRIC)
        AP_list += [AP]
        print("cls : {}|| Recall: {} || Precison: {}|| AP: {}".format(cls, recall[-1], precision[-1], AP))
        # plt.plot(recall, precision, label=cls, color=color_list[index])
        # plt.legend(loc='upper right')
        # print(10*"__")
    # plt.show()
    # plt.savefig(cfgs.VERSION+'.jpg')
    print("mAP is : {}".format(np.mean(AP_list)))


def voc_evaluate_detections(all_boxes, test_annotation_path, test_imgid_list):
    '''
    all_boxes:形状为(n,m,6)n表示有多少张图像,m表示一张图像有多少个检测物体,6表示 [category, score, xmin, ymin, xmax, ymax]
    test_imgid_list:表示评估图片的图片名列表,其形状为(n,)
    test_annotation_path:标签xml所在的文件夹
    :param all_boxes: is a list. each item reprensent the detections of a img.The detections is a array. shape is [-1, 6]. [category, score, xmin, ymin, xmax, ymax].Note that: if none detections in this img. that the detetions is : []
    :return:
    '''
    test_imgid_list = [item.split('.')[0] for item in test_imgid_list]
    write_voc_results_file(all_boxes, test_imgid_list=test_imgid_list,
                           det_save_dir=os.path.join(cfgs.EVALUATE_DIR, cfgs.VERSION))
    do_python_eval(test_imgid_list, test_annotation_path=test_annotation_path)


test_annotation_path = r"E:\data\yolov3\annotations"
import pickle
with open(r"E:\data\yolov3\test\evaluate\all_boxes.pkl", 'rb') as opened_file:  # 读出文件
    all_boxes = pickle.load(opened_file)

test_imgid_list = []
with open(r'E:\data\yolov3\test\evaluate\test_imgid_list.txt') as f:
    lines = f.readlines()
    for line in lines:
        test_imgid_list.append(line.split("\n")[0])
voc_evaluate_detections(all_boxes, test_annotation_path, test_imgid_list)

The main parameters

The first part of the parameters

This is mainly for storing temporary files, you can set it up by yourself, it does n’t matter if you set it up

cfgs.EVALUATE_DIR = r"E:\data\yolov3\test\evaluate"
cfgs.VERSION = r"cls_dir"

The second part of the parameters

Here is the folder where you store the xml file. The specific xml format is similar to voc.

test_annotation_path = r"E:\data\yolov3\annotations"

The third part of the parameters

with open(r"E:\data\yolov3\test\evaluate\all_boxes.pkl", 'rb') as opened_file

This all_boxes.pkl holds a bunch of arrays, the specific shape is (n, m, 6), where n represents how many images, m represents how many detection objects an image has, 6 represents [category, score, xmin , ymin, xmax, ymax].

The fourth part of the parameters

with open(r'E:\data\yolov3\test\evaluate\test_imgid_list.txt') as f

It is mainly the test_imgid_list.txt file, which mainly saves the name of the image, without .png and other suffixes, and without the path, it is simply

00001

00002

00003

Wait.

note

The third part parameters and the fourth part parameters need one-to-one correspondence

Published 190 original articles · praised 497 · 2.60 million views +

Guess you like

Origin blog.csdn.net/u013066730/article/details/105203825