Retinaface代码记录(六)(性能评估)

一、简介

这部分主要围绕如何评估一个人脸检测器性能展开记录。
代码地址:Retinaface代码地址

主要记录脚本:evaluation.py,位于./widerface_evaluate/下。

二、主要内容

一般来评价人脸检测器性能,常用的评价指标就是PR-精确度precision、召回率RecallAP,此次围绕widerface测试集人脸检测器Retinaface进行记录。
1、在进行AP计算之前,需要按照./widerface_evaluate/的Readme进行一些操作。首先需要利用test_widerface.py脚本生成我们的预测集,如下图所示,包含61个文件夹,每个文件夹里有以图片命名的txt,txt里面的内容是首行为图片name,第二行是预测人脸框的个数,下面便是人脸框坐标和置信度(x,y,w,h,score)。然后运行python3 setup.py build_ext --inplace,这个好像是为了把evaluation.py里的一个求iou的函数转化为了用C实现,提高速度。然后运行evaluation.py脚本就计算出了AP了,包括有Easy、Medium、Hard三个的。当然,也可以绘制出三种的PR图,如下图。下面详细来讲解下evaluation.py脚本和如何绘制PR图。
图1
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2、
下面我先插入下evaluation.py的代码,方便讲解和记录。
展开线路是围绕evaluation()进行。
首先,pred = get_preds(pred) ,这是把之前通过test_widerface.py得到的信息进行获取,存储到pred中,其具体格式如下面附录中的Ex1,获取方式主要利用了get_preds()、read_pred_file()等函数,主要是对一些文件txt的操作。
norm_score(pred)函数,利用norm_score()函数对pred中各个文件夹下图片的各个boxes的score进行归一化。
get_gt_boxes(gt_path),是获取官方所给的每个图片的boxes,便于我们进行对比,评估,下面就每个获取到的内容是什么,代表什么含义,进行详细解释,因为网上可搜到的这类资料很少,对于一些刚入门的可能不太友好。具体见附录中的Ex2。
然后 for setting_id in range(3):是针对easy,medium,hard三个测试集分别进行测试,for j in range(len(img_list))针对每个文件夹下每张图片进行计算PR和AP。
首先是,image_eval(),在了解这个函数之前,我们说下Precision和Recall的含义,Precision即预测正确的人脸个数占预测为人脸个数的比例,通俗点就是你找的人脸里有几个对的;Recall预测为正确的人脸个数占真实为人脸个数的比例,通俗点就是在对的人脸里,你找回了几个。更多介绍可自行谷歌。然后我们看此函数,在开头创建了三个array:pred_recall(全0)、recall_list(全0)、proposal_list(全1),shape分别为预测人脸框个数、真实人脸框个数、预测人脸框个数,然后通过这个iou进行筛选,大于设定iou_thresh的有以下两种情况:第一种,就是通过筛选但预测不是标注的‘合格人脸’,这时,proposal_list被标注为-1,recall_list被标注为-1,;第二种,就是通过筛选且预测为‘合格人脸’的,recall_list被标注为1。然后将recall_list为1的个数记录与pred_recall中,并返回pred_recall, proposal_list。
img_pr_info()函数,将上述得到的pred_recall, proposal_list,用于Precision和Recall的计算。对置信度进行了1000分划分,分别统各个划分点的P和R,pr_info[:,0]存放的是预测的人脸的总个数,是用的proposal_list中为1的进行统计计算的,那么在image_eval()函数中,置proposal_list为-1的意思就是从预测总人脸剔除了,既不算预测对,也不算预测错,相对于减小了计算precision的分母,即(TP+FP)预测人脸总数。而pr_info[t, 1]便是用recall_list中为1进行统计计算的,最后在dataset_pr_info()函数中,Precision通过pr_curve[i, 1] / pr_curve[i, 0]计算得到,即TP/(TP+FP);Recall通过pr_curve[i, 1] / count_face计算得到,即TP/真实人脸总数,即TP/(TP+FN)。有了这俩参数,把我代码里注释的关于plt(见下面代码)的代码取消注释,便可绘制P-R图,而AP的值,即P-R曲线下的面积。具体计算在voc_ap()函数中。
voc_ap()函数,类似于计算曲线积分线下面积的原始方法,即小矩形方法,底×高。底是Recall,高是Precision,然后进行简单的排序和去重操作,先把一些底即Recall值相同的找出来,避免两者相减为0,减少计算量,然后np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) 即底相减,然后乘以较大的底对应的高Precision,得到最终的AP。这部分更多的参考可见目标检测算法的评估指标:mAP定义及计算方式目标检测番外篇(2)_mAP

import os
...
from bbox import bbox_overlaps
...

def get_gt_boxes(gt_dir):
    """ gt dir: (wider_face_val.mat, wider_easy_val.mat, wider_medium_val.mat, wider_hard_val.mat)"""

    gt_mat = loadmat(os.path.join(gt_dir, 'wider_face_val.mat'))
    hard_mat = loadmat(os.path.join(gt_dir, 'wider_hard_val.mat'))
    medium_mat = loadmat(os.path.join(gt_dir, 'wider_medium_val.mat'))
    easy_mat = loadmat(os.path.join(gt_dir, 'wider_easy_val.mat'))

    facebox_list = gt_mat['face_bbx_list']
    event_list = gt_mat['event_list']       #文件夹
    file_list = gt_mat['file_list']         #图片名称

    hard_gt_list = hard_mat['gt_list']
    medium_gt_list = medium_mat['gt_list']
    easy_gt_list = easy_mat['gt_list']

    return facebox_list, event_list, file_list, hard_gt_list, medium_gt_list, easy_gt_list


def get_gt_boxes_from_txt(gt_path, cache_dir):...

def read_pred_file(filepath):...

def get_preds(pred_dir):...
    
def norm_score(pred):...
  

def image_eval(pred, gt, ignore, iou_thresh):
    """ single image evaluation
    pred: Nx5
    gt: Nx4
    ignore:
    """
    # pred_info, gt_boxes, ignore, iou_thresh
    _pred = pred.copy()
    _gt = gt.copy()
    pred_recall = np.zeros(_pred.shape[0])
    recall_list = np.zeros(_gt.shape[0])
    proposal_list = np.ones(_pred.shape[0])

    _pred[:, 2] = _pred[:, 2] + _pred[:, 0]
    _pred[:, 3] = _pred[:, 3] + _pred[:, 1]
    _gt[:, 2] = _gt[:, 2] + _gt[:, 0]
    _gt[:, 3] = _gt[:, 3] + _gt[:, 1]

    overlaps = bbox_overlaps(_pred[:, :4], _gt)

    for h in range(_pred.shape[0]):

        gt_overlap = overlaps[h]
        max_overlap, max_idx = gt_overlap.max(), gt_overlap.argmax()
        if max_overlap >= iou_thresh:
            if ignore[max_idx] == 0:
                recall_list[max_idx] = -1
                proposal_list[h] = -1
            elif recall_list[max_idx] == 0:
                recall_list[max_idx] = 1

        r_keep_index = np.where(recall_list == 1)[0]
        pred_recall[h] = len(r_keep_index)
    return pred_recall, proposal_list


def img_pr_info(thresh_num, pred_info, proposal_list, pred_recall):
    pr_info = np.zeros((thresh_num, 2)).astype('float')
    for t in range(thresh_num):

        thresh = 1 - (t+1)/thresh_num
        r_index = np.where(pred_info[:, 4] >= thresh)[0]
        if len(r_index) == 0:
            pr_info[t, 0] = 0
            pr_info[t, 1] = 0
        else:
            r_index = r_index[-1]
            p_index = np.where(proposal_list[:r_index+1] == 1)[0]
            pr_info[t, 0] = len(p_index)             #TP+FP
            pr_info[t, 1] = pred_recall[r_index]     #TP
    return pr_info


def dataset_pr_info(thresh_num, pr_curve, count_face):
    _pr_curve = np.zeros((thresh_num, 2))
    for i in range(thresh_num):
        _pr_curve[i, 0] = pr_curve[i, 1] / pr_curve[i, 0]    #TP/(TP+FP)
        _pr_curve[i, 1] = pr_curve[i, 1] / count_face        #TP/(TP+FN)
    return _pr_curve


def voc_ap(rec, prec):

    # 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 evaluation(pred, gt_path, iou_thresh=0.5):
    pred = get_preds(pred)
    norm_score(pred)
    facebox_list, event_list, file_list, hard_gt_list, medium_gt_list, easy_gt_list = get_gt_boxes(gt_path)
    event_num = len(event_list)
    thresh_num = 1000
    settings = ['easy', 'medium', 'hard']
    setting_gts = [easy_gt_list, medium_gt_list, hard_gt_list]
    aps = []
    data_=list()
    for setting_id in range(3):
        # different setting
        gt_list = setting_gts[setting_id]
        count_face = 0
        pr_curve = np.zeros((thresh_num, 2)).astype('float')
        # [hard, medium, easy]
        pbar = tqdm.tqdm(range(event_num))   # 61
        for i in pbar:
            pbar.set_description('Processing {}'.format(settings[setting_id]))
            event_name = str(event_list[i][0][0])    # 每个文件夹
            img_list = file_list[i][0]
            pred_list = pred[event_name]
            sub_gt_list = gt_list[i][0]    # ???
            # img_pr_info_list = np.zeros((len(img_list), thresh_num, 2))
            gt_bbx_list = facebox_list[i][0]

            for j in range(len(img_list)):
                pred_info = pred_list[str(img_list[j][0][0])]   #file_list

                gt_boxes = gt_bbx_list[j][0].astype('float')
                keep_index = sub_gt_list[j][0]
                count_face += len(keep_index)

                if len(gt_boxes) == 0 or len(pred_info) == 0:
                    continue
                ignore = np.zeros(gt_boxes.shape[0])
                if len(keep_index) != 0:
                    ignore[keep_index-1] = 1
                pred_recall, proposal_list = image_eval(pred_info, gt_boxes, ignore, iou_thresh)

                _img_pr_info = img_pr_info(thresh_num, pred_info, proposal_list, pred_recall)

                pr_curve += _img_pr_info
        pr_curve = dataset_pr_info(thresh_num, pr_curve, count_face)

        propose = pr_curve[:, 0]
        recall = pr_curve[:, 1]

        data_.append(recall)
        data_.append(propose)

        
        ap = voc_ap(recall, propose)
        aps.append(ap)

    # import  matplotlib.pyplot as plt
    #     # y = recall*count_face
    #     # x = y/propose-y
    # plt.figure(figsize=(6,6))
    # plt.title("Precision Recall Curve")
    # plt.xlabel('Recall')
    # plt.ylabel('Precision')

    # plt.style.use('ggplot')
    # #plt.plot_date(data_[0],data_[1],color='r')
    # plt.xticks(rotation=45)

    # plt.plot(data_[0],data_[1],color='r',label = 'Easy')
    # plt.plot(data_[2],data_[3],color='g',label = 'Medium')
    # plt.plot(data_[4],data_[5],color='b',label = 'Hard')

    # plt.legend(loc=1)
    # plt.grid()

    # plt.show()

    print("==================== Results ====================")
    print("Easy   Val AP: {}".format(aps[0]))
    print("Medium Val AP: {}".format(aps[1]))
    print("Hard   Val AP: {}".format(aps[2]))
    print("=================================================")


if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument('-p', '--pred', default="./widerface_evaluate/widerface_txt/")
    parser.add_argument('-g', '--gt', default='./widerface_evaluate/ground_truth/')
    args = parser.parse_args()

    evaluation(args.pred, args.gt)

附录:
Ex1:
在这里插入图片描述
Ex2:
在这里插入图片描述
facebox_list记录了每个文件夹下,每个图片所包含人脸的人脸框(x,y,w,h),是一个np.ndarray。
在这里插入图片描述
event_list记录了61个文件夹的名称,也是一个np.ndarray。
在这里插入图片描述
file_list记录了61个文件夹下每张图片的名称,如图。也是一个np.ndarray.
在这里插入图片描述
至于hard_gt_list, medium_gt_list, easy_gt_list三个,他们的作用就是标注出一张图片中真正称得上‘合格人脸’的人脸,根据easy,medium,hard意思可知,easy标注的是一些比较好识别的人脸,而hard除了标注出easy和medium的,还要标注出一些相对较难识别的,比如侧脸的,模糊的,遮挡的等等。具体是给出facebox_list中标注人脸框的下标,这样可以对应且方便找出。
上述是列举的easy的数据格式。

三、结束语

到这里差不多就结束了。上面的记录可能存在手写体、书写格式不合理翻上翻下的情况、解释一大段文字等等缺点,但仍希望每位读者可以静心阅读,从中获取你想要的东西。有哪里不太清楚的或者错误的,可以直接私信或者留言!

  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
好的,关于 Pytorch-RetinaFace代码讲解,我可以给您提供以下信息: 1. RetinaFace 模型介绍 RetinaFace 是一个用于人脸检测和人脸识别的深度学习模型,其核心思想是在多个尺度下进行特征提取,并利用类似于 Anchor 的方式进行边框预测。RetinaFace 模型的核心网络结构为 ResNet-50,通过 ResNet-50 提取特征,再分别在不同的特征图上进行预测,从而提高检测的准确率和速度。 2. Pytorch-RetinaFace 代码结构 Pytorch-RetinaFace代码结构主要包含以下几个文件: - data/ 目录:包含了数据集相关的代码和数据集文件; - models/ 目录:包含了 RetinaFace 模型的代码和模型文件; - layers/ 目录:包含了 RetinaFace 模型中用到的自定义层代码; - utils/ 目录:包含了一些工具类和函数; - train.py:训练脚本; - test.py:测试脚本; - demo.py:演示脚本。 3. RetinaFace 模型训练 RetinaFace 模型的训练主要包含以下几个步骤: - 数据集准备:将数据集按照指定格式进行划分和预处理; - 模型构建:使用 Pytorch 搭建 RetinaFace 模型,并定义损失函数和优化器; - 模型训练:使用训练集对模型进行训练,并在验证集上进行验证和调参; - 模型保存:将训练好的模型保存到指定的路径。 4. RetinaFace 模型测试 RetinaFace 模型的测试主要包含以下几个步骤: - 加载模型:使用 Pytorch 加载训练好的模型; - 图像预处理:将待检测的图像进行预处理,包括大小调整和归一化等; - 特征提取:使用 ResNet-50 提取图像的特征; - 预测边框:在不同的特征图上进行边框预测,并进行 NMS 处理; - 绘制结果:将预测出的边框和置信度绘制在原图上。 以上就是关于 Pytorch-RetinaFace 代码的讲解,希望能够对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值