关于ap和map的计算

关于ap和map的计算

这可能是我目前看的最仔细的代码了

直接上代码了。

# -- coding: UTF-8 --
"""
#    关于ap,map的在目标检测中的细节,现在假设输入是preds和labels,
#    其中preds是模型的检测框的输出,其shape为(16, 30, 6)
#    16为batch_size, 30 为预测框的个数,6为对应的class和bbox信息,
#    [class_id, confidence_score, xmin, ymin, xmax, ymax],
#    label的shape(16,50,5)
#    其中16是batch_size, 50是每个图片上的“gt“框的个数,5为具体label信息,
#    label 是这种结构[class_id, xmin, ymin, xmax, ymax],
#    之所以”gt“是因为这50个框中可能有效的也就2,3个,我看SSD的输出是这样的,
#    我猜的可能是因为为了统一吧。
#    这个可以按batch进行计算,也可以理解为preds和labels就是所有的测试集的情况也行.对于最终的结果其实没
有什么影响.

"""

import numpy as np

def IOU(x, ys):
    xmin = np.maximum(x[0], ys[:,0])
    ymin = np.maximum(x[1], ys[:,1])
    xmax = np.minimum(x[2], ys[:,2])
    ymax = np.minimum(x[3], ys[:,3])

    iW = np.maximum(xmax-xmin+1, 0)
    iH = np.maximum(ymax-ymin+1, 0)
    intersect = iW*iH
    uni = (x[2]-x[0]+1)*(x[3]-x[1]+1) + (ys[:,2]-ys[:,0]+1)*(ys[:,3]-ys[:,1]+1)-intersect
    ious = intersect*1.0/uni
    # 把那些比较小的全部当成0来处理
    ious[uni<1e-12] = 0
    return ious

def get_records_counts(preds, labels, thresh):
    """
    因为有多个物体的类别,本质上就是要对于每一个类别的,得到一个[score, gt_label]的东西,其中[score, gt_label]是可以理解成二分类的得分和真实的label, 而在这里,
    主要把这两个得到的话,那么下面算ap或者roc都是比较自然的了,这里已经有的是score,就是上面的confidence_score, 而gt_label是要算IOU,然后和阈值比较才能得出的,
    如果其IOU大于阈值的话,我们就信为这个框是预测对了的,就给它打上“正”的label,否则就给其打上“负”,但>是有可能是一个物体有多个检测框,这种情况下只保留第一次的那个
    (这里感觉是有改进的空间的,第一次保留的那个未必就是最好的),然后其它的也全部打上“负”的label。
    此外,如果要算recall的话,还要知道有多少个是真实的正样本,所以还要一个东西来存每个类别的真实的正样
本的个数,这里真实的正样本的个数就是真正的gt框的个数。
    主要用两个字典来存,一个是Records一个是Counts,分别长成这样
    Records[class_id] = [[score, gt_label],[],...,[score, gt_label]]
    Counts[class_id] = count
"""
    global Records, Counts
    Records = {}
    Counts = {}

    for i in range(labels.shape[0]):  # 即对于batch中的每一个
        label = labels[i]   # 得`到batch中第i个label,其shape为(50, 5)
        pred = preds[i]  # 得到batch中第i个图像中的预测信息,其shape为(30, 6)
        # 过滤掉全是背景的,实际上就是过滤掉gt中那些无效的记录。
        if np.sum(label[:,0]>=0) <1:
            continue

        while (pred.shape[0]>0):   # 只要还有预测框就继续
            cid = int(pred[0, 0])  # 获得这个预测框的class_id
            indices = np.where(pred[:,0].astype(int)==cid)[0]  # 得到和上面这个label一样的所有的预测框
的index

            # 如果无效就不考虑了,并且要把这些给去掉
            if cid < 0:
                pred = np.delete(pred, indices, axis=0 )
                continue

            dets = pred[indices]  # 得到这些class_id都是cid的预测框

            pred = np.delete(pred, indices, axis=0)   # 用过了这些cid的之后也要删除掉
            dets = dets[dets[:,1].argsort()[::-1]]    # 将dets按照置信度从大到小排序

            # 申请一个中间的结果
            records = np.hstack((dets[:, 1][:,np.newaxis], np.zeros((dets.shape[0], 1))))
            # 即里面的元素是[[得分,label],[得分,label]]
            # 这个records就是上面Records的值的初始化


# 接下来要处理label的了
            label_indices = np.where(label[:,0].astype(int)==cid)[0]   # 在label的框s里面得到class_id>为cid的那些gt_bbox
            gts = label[label_indices, :]
            label = np.delete(label, label_indices, axis=0)  # 更新label,因为用过了的也不能再用了
            if gts.size > 0:   #如果gt中有对应的同样类别的物体
                # 因为要找对应的了,初始化为没有找到
                found = [False] * gts.shape[0]

                # 对于上面的pred中的每一个
                for j in range(dets.shape[0]):  #相当于是固定一个pred的框,让它和每个同类别的gt进行算IOU
                    ious = IOU(dets[j, 2:], gts[:,1:])
                    # 排名得到最大的
                    ovargmax = np.argmax(ious)  #得到的是最大的那个index.
                    ovmax = ious[ovargmax]
                    if ovmax > thresh:     # 即认为正.
                        if not found[ovargmax]:
                            records[j, -1] = 1    # 因为初始化的时候为0了,为了区别,这里用1,来标记tp
                            found[ovargmax] = True  # 并标记这个已经被用
                        else:
                            records[j, -1] = 2   # 预测重复的情况,同样这里用2作也区别,来标记fp
                    else:
                        records[j, -1] = 2   # 小于IOU阈值也认为

            else:   # 这时候没有对应的gt,说明也是检测错了。
                records[:, -1] = 2

            gt_count = gts.shape[0]

            update_dict(Records, Counts, cid, records, gt_count)


           """
        # 还有一种情况容易忘,就是万一当所有的预测框都用完了的时候,label里面的gt框还没有用完呢,这种>情况发生在预测的class可能在gt的label中
        #就没有出现,但是这时候这些gtbbox,仍然是算的啊
        while (label.shape[0]>0):
            cid = int(label[0,0])
            label_indices = np.where(label[:,0].astype(int) == cid)[0]
            label = np.delete(label, label_indices, axis=0)
            if cid<0:
                continue
            gt_count = label_indices.size
            update_dict(Records, Counts, cid, records, gt_count)

    return Records, Counts



def update_dict(Records, Counts, cid, records, gt_count):
    if cid not in Records:  ##即还没这个类别的记录.
        assert cid not in Counts
        Records[cid] = records
        Counts[cid] = gt_count
    else:
        Records[cid] = np.vstack((Records[cid], records))   # 得到更多的记录
        Counts[cid] += gt_count


def get_precision_recall(record, count):
    """这个是获得具体某一个类别的precision,recall"""
    record = np.delete(record, np.where(record[:,1].astype(int)==0)[0], axis=0)
    sorted_records = record[record[:,0].argsort()[::-1]]
    tp = np.cumsum(sorted_records[:, 1].astype(int)==1)
    fp = np.cumsum(sorted_records[:,1].astype(int)==2)
    # we can also get fpr: = fp*1.0/(total_pred_bbox-count)
    # and record.shape[0] = total_pred_bbox = real_true+real_false
    if count <= 0:
        recall = tp*0.0
    else:
        recall = tp/float(count)
    prec = tp.astype(float)/(tp+fp)
    return recall, prec


def get_ap(recall, prec, mode="original"):
    """得到单个类别的ap,这里采用原始的定义即p-r曲线下面的面积"""
    # 给recall补上01
    # 给pred补上00
    assert mode in ['original', 'voc07']
    if mode == "original":
        mrec = np.concatenate(([0.],recall,[1.]))
        mpre = np.concatenate(([0.],prec, [0.]))
        for i in range(mpre.size-1, 0,-1):  # 每一个都更新为recall右边的最大值。
            mpre[i-1] = np.maximum(mpre[i-1], mpre[i])
        # 然后看recall的点数,即要找recall发生改变的地方,
        j = np.where(mrec[1:] != mrec[:-1])[0]  #这个技术挺好的,错位比较.
        # 这些发生改变的地方,并不一定均,就像原始定义roc的时候一样
        ap = np.sum((mrec[j+1]-mrec[j])*(mpre[j+1]))
## 这些就是那个面积.    

    if mode == "voc07":
        ## use 11-point method
        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 += p /11.
    return ap


def main(preds, labels, thresh, mode="original"):
    # step 1
    assert preds.shape[0] == labels.shape[0]
    assert preds.shape[-1] == 6
    assert labels.shape[-1] == 5

    Dict_Records, Dict_Counts = get_records_counts(preds, labels, thresh)

    # get map
    aps = []
    for k, v in Dict_Records.items():
        recall, prec = get_precision_recall(v, Dict_Counts[k])
        ap = get_ap(recall, prec, mode)
        aps.append(ap)
        print "class %d, ap: %.3f" %(k, ap)

    meanap = np.mean(aps)
    print "map is %.4f"% meanap
    return meanap


然后 关于coco的计算方式稍微有些不同, coco关于IOU的阈值也做了变动,

 coco_map = []
    thresh = np.linspace(0.5,0.95,90)
    for i in list(thresh):
        coco_map.append(main(preds, L, i))
    print "coco-mAP", np.mean(coco_map)

原始的ap是算”p-r”曲线下面的面积, 到voc的时候是用max的方式,即找给定recall右边的最大的presicion,然后取平均,而coco又加了IOU阈值可以变动的方式。

Remark

其实关键就是Records 和Counts的获得.

打赏,谢谢~~

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,多谢支持~

打开微信扫一扫,即可进行扫码打赏哦