计算目标检测和语义分割的PR

news2024/11/20 13:45:40

需求描述

  1. 实际工作中,相比于mAP项目更加关心的是特定阈值下的precision和recall结果;
  2. 由于本次的GT中除了目标框之外还存在多边形标注,为此,计算IoU的方式从框与框之间变成了mask之间
    本文的代码适用于MMDetection下的预测结果和COCO格式之间来计算PR结果,具体的实现过程如下:
  • 获取预测结果并保存到json文件中;
  • 解析预测结果和GT;
  • 根据image_id获取每张图的预测结果和GT;
  • 基于mask计算预测结果和GT之间的iou矩阵;
  • 根据iou矩阵得到对应的tp、fp和num_gt;
  • 迭代所有的图像得到所有的tp、fp和num_gt累加,根据公式计算precision和recall;

具体实现

获取预测结果

在MMDetection框架下,通常使用如下的命令来评估模型的结果:

bash tools/dist_test.sh configs/aaaa/gaotie_cascade_rcnn_r50_fpn_1x.py work_dirs/gaotie_cascade_rcnn_r50_fpn_1x/epoch_20.pth 8 --eval bbox

此时能获取到类似下图的mAP结果。
mAP)
而我们需要在某个过程把预测结果保存下,用于后续得到PR结果,具体可以在mmdet/datasets/coco.py的438行位置添加如下代码:

 try:
     import shutil
     cocoDt = cocoGt.loadRes(result_files[metric])
     shutil.copyfile(result_files[metric], "results.bbox.json")

这样我们就可以得到results.bbox.json文件,里面包含的是模型的预测结果,如下图所示。
在这里插入图片描述)

获取GT结果

由于标注时有两个格式:矩形框和多边形,因此在构建GT的coco格式文件时,对于矩形框会将其四个顶点作为多边形传入到segmentations字段,对于多边形会计算出外接矩形传入到bbox字段。
在这里插入图片描述)
为此,获取GT信息的脚本实现如下:

def construct_gt_results(gt_json_path):

    results = dict()
    bbox_results = dict()
    cocoGt = COCO(annotation_file=gt_json_path)
    # cat_ids = cocoGt.getCatIds()
    img_ids = cocoGt.getImgIds()
    for id in img_ids:
        anno_ids = cocoGt.getAnnIds(imgIds=[id])
        
        annotations = cocoGt.loadAnns(ids=anno_ids)
        for info in annotations:
            img_id = info["image_id"]
            if img_id not in results:
                results[img_id] = list()
                bbox_results[img_id] = list()
            bbox = info["bbox"]
            x1, y1, x2, y2 = bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]
            # results[img_id].append([x1, y1, x2, y2])
            # mask = _poly2mask(info["segmentation"], img_h=1544, img_w=2064)
            results[img_id].append(info["segmentation"])
            bbox_results[img_id].append([x1, y1, x2, y2])
    return results, img_ids, cocoGt, bbox_results

输入GT的json文件路径,返回所有图像的分割结果,image_id,COCO对象和目标框结果(用于后续的可视化结果)。

获取预测结果

模型预测出来的结果都是目标框的形式,与上面一样,将目标框的四个顶点作为多边形的分割结果。具体解析脚本如下:

def construct_det_results(det_json_path):

    results = dict()
    bbox_results = dict()
    scores  = dict()
    with open(det_json_path) as f:
        json_data = json.load(f)
    for info in json_data:
        img_id = info["image_id"]
        if img_id not in results:
            results[img_id] = list()
            scores[img_id] = list()
            bbox_results[img_id] = list()
        bbox = info["bbox"]
        x1, y1, x2, y2 = bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]
        segm = [[x1, y1, x2, y1, x2, y2, x1, y2]]
        # mask = _poly2mask(segm, img_h=1544, img_w=2064)
        score = info["score"]
        # results[img_id].append([x1, y1, x2, y2, score])
        results[img_id].append(segm)
        bbox_results[img_id].append([x1, y1, x2, y2])
        scores[img_id].append(score)
    return results, scores, bbox_results

输入的是预测结果的json文件路径,输出是所有图像分割结果、得分和目标框结果。

根据image_id计算单个图像的TP、FP结果

本步骤的具体内容如下:

  1. 根据置信度阈值对预测框进行筛选;
  2. 将所有的多边形转换为mask,用于后续计算IoU;
  3. 得到tp和fp;
  4. 可视化fp和fn结果;

将多边形转换为mask

    if img_id in det_results:
        # for dt in det_results[img_id]:
        for idx, score in enumerate(det_scores[img_id]):
            # score = dt[-1]
            if score > conf_thrs:
                mask = _poly2mask(det_results[img_id][idx], img_h=1544, img_w=2064)
                det_bboxes.append(mask)
                det_thrs_scores.append(score)
                plot_det_bboxes.append(det_tmp_bboxes[img_id][idx])
    if img_id in gt_results:     
        for segm in gt_results[img_id]:
            mask = _poly2mask(segm, img_h=1544, img_w=2064)   
            gt_bboxes.append(mask)
        plot_gt_bboxes = gt_tmp_bboxes[img_id]

通过_poly2mask函数可以将多边形转换为mask,_poly2mask函数的实现如下。

def _poly2mask(mask_ann, img_h, img_w):
    """Private function to convert masks represented with polygon to
    bitmaps.

    Args:
        mask_ann (list | dict): Polygon mask annotation input.
        img_h (int): The height of output mask.
        img_w (int): The width of output mask.

    Returns:
        numpy.ndarray: The decode bitmap mask of shape (img_h, img_w).
    """

    if isinstance(mask_ann, list):
        # polygon -- a single object might consist of multiple parts
        # we merge all parts into one mask rle code
        rles = maskUtils.frPyObjects(mask_ann, img_h, img_w)
        rle = maskUtils.merge(rles)
    elif isinstance(mask_ann['counts'], list):
        # uncompressed RLE
        rle = maskUtils.frPyObjects(mask_ann, img_h, img_w)
    else:
        # rle
        rle = mask_ann
    mask = maskUtils.decode(rle)
    return mask

计算单张图像的TP和FP

本文中使用tpfp_default函数实现该功能,具体实现如下:

def tpfp_default(det_bboxes,
                 gt_bboxes,
                 gt_bboxes_ignore=None,
                 det_thrs_scores=None,
                 iou_thr=0.5,
                 area_ranges=None):
    """Check if detected bboxes are true positive or false positive.

    Args:
        det_bbox (ndarray): Detected bboxes of this image, of shape (m, 5).
        gt_bboxes (ndarray): GT bboxes of this image, of shape (n, 4).
        gt_bboxes_ignore (ndarray): Ignored gt bboxes of this image,
            of shape (k, 4). Default: None
        iou_thr (float): IoU threshold to be considered as matched.
            Default: 0.5.
        area_ranges (list[tuple] | None): Range of bbox areas to be evaluated,
            in the format [(min1, max1), (min2, max2), ...]. Default: None.

    Returns:
        tuple[np.ndarray]: (tp, fp) whose elements are 0 and 1. The shape of
            each array is (num_scales, m).
    """
    # an indicator of ignored gts
    gt_ignore_inds = np.concatenate(
        (np.zeros(gt_bboxes.shape[0], dtype=np.bool),
         np.ones(gt_bboxes_ignore.shape[0], dtype=np.bool)))
    # stack gt_bboxes and gt_bboxes_ignore for convenience
    # gt_bboxes = np.vstack((gt_bboxes, gt_bboxes_ignore))

    num_dets = det_bboxes.shape[0]
    num_gts = gt_bboxes.shape[0]
    if area_ranges is None:
        area_ranges = [(None, None)]
    num_scales = len(area_ranges)
    # tp and fp are of shape (num_scales, num_gts), each row is tp or fp of
    # a certain scale
    tp = np.zeros((num_scales, num_dets), dtype=np.float32)
    fp = np.zeros((num_scales, num_dets), dtype=np.float32)

    # if there is no gt bboxes in this image, then all det bboxes
    # within area range are false positives
    if gt_bboxes.shape[0] == 0:
        if area_ranges == [(None, None)]:
            fp[...] = 1
        else:
            det_areas = (det_bboxes[:, 2] - det_bboxes[:, 0] + 1) * (
                det_bboxes[:, 3] - det_bboxes[:, 1] + 1)
            for i, (min_area, max_area) in enumerate(area_ranges):
                fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1
        return tp, fp

    # ious = bbox_overlaps(det_bboxes, gt_bboxes)
    # ious = mask_overlaps(det_bboxes, gt_bboxes)
    ious = mask_wraper(det_bboxes, gt_bboxes)
    # for each det, the max iou with all gts
    ious_max = ious.max(axis=1)
    # for each det, which gt overlaps most with it
    ious_argmax = ious.argmax(axis=1)
    # sort all dets in descending order by scores
    # sort_inds = np.argsort(-det_bboxes[:, -1])
    sort_inds = np.argsort(-det_thrs_scores)
    for k, (min_area, max_area) in enumerate(area_ranges):
        gt_covered = np.zeros(num_gts, dtype=bool)
        # if no area range is specified, gt_area_ignore is all False
        if min_area is None:
            gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool)
        else:
            gt_areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0] + 1) * (
                gt_bboxes[:, 3] - gt_bboxes[:, 1] + 1)
            gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area)
        for i in sort_inds:
            if ious_max[i] >= iou_thr:
                matched_gt = ious_argmax[i]     # 得到对应的GT索引
                if not (gt_ignore_inds[matched_gt]
                        or gt_area_ignore[matched_gt]):
                    if not gt_covered[matched_gt]:
                        gt_covered[matched_gt] = True   # GT占位
                        tp[k, i] = 1            
                    else:
                        fp[k, i] = 1
                # otherwise ignore this detected bbox, tp = 0, fp = 0
            elif min_area is None:
                fp[k, i] = 1
            else:
                bbox = det_bboxes[i, :4]
                area = (bbox[2] - bbox[0] + 1) * (bbox[3] - bbox[1] + 1)
                if area >= min_area and area < max_area:
                    fp[k, i] = 1
    return tp, fp

过程是先获取预测框和GT框之间的IoU矩阵,然后按照置信度排序,将每个预测框分配给GT框得到tp和fp结果。

计算mask的IoU

IoU的定义都是一样的,计算公式如下:
在这里插入图片描述
基于mask计算IoU的实验也非常简单,代码如下:

def mask_overlaps(bboxes1, bboxes2, mode='iou'):

    assert mode in ['iou', 'iof']

    bboxes1 = bboxes1.astype(np.bool_)
    bboxes2 = bboxes2.astype(np.bool_)
    
    intersection = np.logical_and(bboxes1, bboxes2)
    union = np.logical_or(bboxes1, bboxes2)

    intersection_area = np.sum(intersection)
    union_area = np.sum(union)

    iou = intersection_area / union_area
    return iou

而计算预测框和GT之间的IoU矩阵实现如下:

def mask_wraper(bboxes1, bboxes2, mode='iou'):
    rows = bboxes1.shape[0]     # gt
    cols = bboxes2.shape[0]     # det
    ious = np.zeros((rows, cols), dtype=np.float32)
    if rows * cols == 0:
        return ious
    for i in range(rows):
        for j in range(cols):
            iou = mask_overlaps(bboxes1[i], bboxes2[j])
            ious[i, j] = iou
    return ious

至此,通过上述过程就能获取到单张图像的tp和fp结果。

可视化FP和FN结果

此外,我们需要分析模型的badcase,因此,可以将FP和FN的结果可视化出来,我这里是直接将存在问题的图像所有预测框和GT框都画出来了。

    if VIS and (fp > 0 or tp < gt):
        img_data, path = draw_bbox(img_id=img_id, cocoGt=cocoGt, det_bboxes=plot_det_bboxes, gt_bboxes=plot_gt_bboxes)
        if fp > 0:
            save_dir = os.path.join(VIS_ROOT, "tmp/FP/")
            os.makedirs(save_dir, exist_ok=True)
            cv2.imwrite(os.path.join(save_dir, os.path.basename(path)+".jpg"), img_data, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
        if tp < gt:
            save_dir = os.path.join(VIS_ROOT, "tmp/FN/")
            os.makedirs(save_dir, exist_ok=True)
            cv2.imwrite(os.path.join(save_dir, os.path.basename(path)+".jpg"), img_data,
                        [int(cv2.IMWRITE_JPEG_QUALITY), 30])

画框的实现如下:

def draw_bbox(img_id, cocoGt, det_bboxes, gt_bboxes):
    path = cocoGt.loadImgs(ids=[img_id])[0]["file_name"]
    img_path = os.path.join(IMG_ROOT, path)
    img_data = cv2.imread(img_path)
    for box in det_bboxes:
        # color_mask = (0, 0, 255)
        # color_mask = np.array([0, 0, 255], dtype=np.int8)
        # bbox_mask = box.astype(np.bool)
        cv2.rectangle(img_data, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (0, 0, 255), 3)
        # img_data[bbox_mask] = img_data[bbox_mask] * 0.5 + color_mask * 0.5
    for box in gt_bboxes:
        # color_mask = np.array([0, 255, 0], dtype=np.int8)
        # bbox_mask = box.astype(np.bool)

        # img_data[bbox_mask] = img_data[bbox_mask] * 0.5 + color_mask * 0.5
        cv2.rectangle(img_data, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (0, 255, 0), 3)
    
    return img_data, path

至此,我们实现了单张图像的所有业务逻辑。

多线程计算所有图像结果

通过multiprocessing启动一个进程池来加速结果计算。

def eval_multiprocessing(img_ids):
    from multiprocessing import Pool
    pool = Pool(processes=16)

    results = pool.map(eval_pr, img_ids)
    # 关闭进程池,表示不再接受新的任务
    pool.close()

    # 等待所有任务完成
    pool.join()
    return np.sum(np.array(results), axis=0)

计算PR结果

返回所有图像的TP和FP结果之后,就可以计算precision和recall值了。

gt, tp, fp = eval_multiprocessing(img_ids)
eps = np.finfo(np.float32).eps
recalls = tp / np.maximum(gt, eps)
precisions = tp / np.maximum((tp + fp), eps)

print("conf_thrs:{:.3f} iou_thrs:{:.3f}, gt:{:d}, TP={:d}, FP={:d}, P={:.3f}, R={:.3f}".format(conf_thrs, iou_thrs, gt, tp, fp, precisions, recalls))

最后,也附上整个实现代码,方便后续复现或者参考。

from multiprocessing import Pool
import os
import numpy as np
import json
from pycocotools.coco import COCO
import cv2
from pycocotools import mask as maskUtils

def bbox_overlaps(bboxes1, bboxes2, mode='iou'):
    """Calculate the ious between each bbox of bboxes1 and bboxes2.

    Args:
        bboxes1(ndarray): shape (n, 4)
        bboxes2(ndarray): shape (k, 4)
        mode(str): iou (intersection over union) or iof (intersection
            over foreground)

    Returns:
        ious(ndarray): shape (n, k)
    """

    assert mode in ['iou', 'iof']

    bboxes1 = bboxes1.astype(np.float32)
    bboxes2 = bboxes2.astype(np.float32)
    rows = bboxes1.shape[0]
    cols = bboxes2.shape[0]
    ious = np.zeros((rows, cols), dtype=np.float32)
    if rows * cols == 0:
        return ious
    exchange = False
    if bboxes1.shape[0] > bboxes2.shape[0]:
        bboxes1, bboxes2 = bboxes2, bboxes1
        ious = np.zeros((cols, rows), dtype=np.float32)
        exchange = True
    area1 = (bboxes1[:, 2] - bboxes1[:, 0] + 1) * (bboxes1[:, 3] - bboxes1[:, 1] + 1)
    area2 = (bboxes2[:, 2] - bboxes2[:, 0] + 1) * (bboxes2[:, 3] - bboxes2[:, 1] + 1)
    for i in range(bboxes1.shape[0]):
        x_start = np.maximum(bboxes1[i, 0], bboxes2[:, 0])
        y_start = np.maximum(bboxes1[i, 1], bboxes2[:, 1])
        x_end = np.minimum(bboxes1[i, 2], bboxes2[:, 2])
        y_end = np.minimum(bboxes1[i, 3], bboxes2[:, 3])
        overlap = np.maximum(x_end - x_start + 1, 0) * np.maximum(y_end - y_start + 1, 0)
        if mode == 'iou':
            union = area1[i] + area2 - overlap
        else:
            union = area1[i] if not exchange else area2
        ious[i, :] = overlap / union
    if exchange:
        ious = ious.T
    return ious

def mask_wraper(bboxes1, bboxes2, mode='iou'):
    rows = bboxes1.shape[0]     # gt
    cols = bboxes2.shape[0]     # det
    ious = np.zeros((rows, cols), dtype=np.float32)
    if rows * cols == 0:
        return ious
    for i in range(rows):
        for j in range(cols):
            iou = mask_overlaps(bboxes1[i], bboxes2[j])
            ious[i, j] = iou
    return ious

def mask_overlaps(bboxes1, bboxes2, mode='iou'):

    assert mode in ['iou', 'iof']

    bboxes1 = bboxes1.astype(np.bool_)
    bboxes2 = bboxes2.astype(np.bool_)
    
    intersection = np.logical_and(bboxes1, bboxes2)
    union = np.logical_or(bboxes1, bboxes2)

    intersection_area = np.sum(intersection)
    union_area = np.sum(union)

    iou = intersection_area / union_area
    return iou


def tpfp_default(det_bboxes,
                 gt_bboxes,
                 gt_bboxes_ignore=None,
                 det_thrs_scores=None,
                 iou_thr=0.5,
                 area_ranges=None):
    """Check if detected bboxes are true positive or false positive.

    Args:
        det_bbox (ndarray): Detected bboxes of this image, of shape (m, 5).
        gt_bboxes (ndarray): GT bboxes of this image, of shape (n, 4).
        gt_bboxes_ignore (ndarray): Ignored gt bboxes of this image,
            of shape (k, 4). Default: None
        iou_thr (float): IoU threshold to be considered as matched.
            Default: 0.5.
        area_ranges (list[tuple] | None): Range of bbox areas to be evaluated,
            in the format [(min1, max1), (min2, max2), ...]. Default: None.

    Returns:
        tuple[np.ndarray]: (tp, fp) whose elements are 0 and 1. The shape of
            each array is (num_scales, m).
    """
    # an indicator of ignored gts
    gt_ignore_inds = np.concatenate(
        (np.zeros(gt_bboxes.shape[0], dtype=np.bool),
         np.ones(gt_bboxes_ignore.shape[0], dtype=np.bool)))
    # stack gt_bboxes and gt_bboxes_ignore for convenience
    # gt_bboxes = np.vstack((gt_bboxes, gt_bboxes_ignore))

    num_dets = det_bboxes.shape[0]
    num_gts = gt_bboxes.shape[0]
    if area_ranges is None:
        area_ranges = [(None, None)]
    num_scales = len(area_ranges)
    # tp and fp are of shape (num_scales, num_gts), each row is tp or fp of
    # a certain scale
    tp = np.zeros((num_scales, num_dets), dtype=np.float32)
    fp = np.zeros((num_scales, num_dets), dtype=np.float32)

    # if there is no gt bboxes in this image, then all det bboxes
    # within area range are false positives
    if gt_bboxes.shape[0] == 0:
        if area_ranges == [(None, None)]:
            fp[...] = 1
        else:
            det_areas = (det_bboxes[:, 2] - det_bboxes[:, 0] + 1) * (
                det_bboxes[:, 3] - det_bboxes[:, 1] + 1)
            for i, (min_area, max_area) in enumerate(area_ranges):
                fp[i, (det_areas >= min_area) & (det_areas < max_area)] = 1
        return tp, fp

    # ious = bbox_overlaps(det_bboxes, gt_bboxes)
    # ious = mask_overlaps(det_bboxes, gt_bboxes)
    ious = mask_wraper(det_bboxes, gt_bboxes)
    # for each det, the max iou with all gts
    ious_max = ious.max(axis=1)
    # for each det, which gt overlaps most with it
    ious_argmax = ious.argmax(axis=1)
    # sort all dets in descending order by scores
    # sort_inds = np.argsort(-det_bboxes[:, -1])
    sort_inds = np.argsort(-det_thrs_scores)
    for k, (min_area, max_area) in enumerate(area_ranges):
        gt_covered = np.zeros(num_gts, dtype=bool)
        # if no area range is specified, gt_area_ignore is all False
        if min_area is None:
            gt_area_ignore = np.zeros_like(gt_ignore_inds, dtype=bool)
        else:
            gt_areas = (gt_bboxes[:, 2] - gt_bboxes[:, 0] + 1) * (
                gt_bboxes[:, 3] - gt_bboxes[:, 1] + 1)
            gt_area_ignore = (gt_areas < min_area) | (gt_areas >= max_area)
        for i in sort_inds:
            if ious_max[i] >= iou_thr:
                matched_gt = ious_argmax[i]     # 得到对应的GT索引
                if not (gt_ignore_inds[matched_gt]
                        or gt_area_ignore[matched_gt]):
                    if not gt_covered[matched_gt]:
                        gt_covered[matched_gt] = True   # GT占位
                        tp[k, i] = 1            
                    else:
                        fp[k, i] = 1
                # otherwise ignore this detected bbox, tp = 0, fp = 0
            elif min_area is None:
                fp[k, i] = 1
            else:
                bbox = det_bboxes[i, :4]
                area = (bbox[2] - bbox[0] + 1) * (bbox[3] - bbox[1] + 1)
                if area >= min_area and area < max_area:
                    fp[k, i] = 1
    return tp, fp


def _poly2mask(mask_ann, img_h, img_w):
    """Private function to convert masks represented with polygon to
    bitmaps.

    Args:
        mask_ann (list | dict): Polygon mask annotation input.
        img_h (int): The height of output mask.
        img_w (int): The width of output mask.

    Returns:
        numpy.ndarray: The decode bitmap mask of shape (img_h, img_w).
    """

    if isinstance(mask_ann, list):
        # polygon -- a single object might consist of multiple parts
        # we merge all parts into one mask rle code
        rles = maskUtils.frPyObjects(mask_ann, img_h, img_w)
        rle = maskUtils.merge(rles)
    elif isinstance(mask_ann['counts'], list):
        # uncompressed RLE
        rle = maskUtils.frPyObjects(mask_ann, img_h, img_w)
    else:
        # rle
        rle = mask_ann
    mask = maskUtils.decode(rle)
    return mask



def construct_det_results(det_json_path):

    results = dict()
    bbox_results = dict()
    scores  = dict()
    with open(det_json_path) as f:
        json_data = json.load(f)
    for info in json_data:
        img_id = info["image_id"]
        if img_id not in results:
            results[img_id] = list()
            scores[img_id] = list()
            bbox_results[img_id] = list()
        bbox = info["bbox"]
        x1, y1, x2, y2 = bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]
        segm = [[x1, y1, x2, y1, x2, y2, x1, y2]]
        # mask = _poly2mask(segm, img_h=1544, img_w=2064)
        score = info["score"]
        # results[img_id].append([x1, y1, x2, y2, score])
        results[img_id].append(segm)
        bbox_results[img_id].append([x1, y1, x2, y2])
        scores[img_id].append(score)
    return results, scores, bbox_results
    
    
def construct_gt_results(gt_json_path):

    results = dict()
    bbox_results = dict()
    cocoGt = COCO(annotation_file=gt_json_path)
    # cat_ids = cocoGt.getCatIds()
    img_ids = cocoGt.getImgIds()
    for id in img_ids:
        anno_ids = cocoGt.getAnnIds(imgIds=[id])
        
        annotations = cocoGt.loadAnns(ids=anno_ids)
        for info in annotations:
            img_id = info["image_id"]
            if img_id not in results:
                results[img_id] = list()
                bbox_results[img_id] = list()
            bbox = info["bbox"]
            x1, y1, x2, y2 = bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]
            # results[img_id].append([x1, y1, x2, y2])
            # mask = _poly2mask(info["segmentation"], img_h=1544, img_w=2064)
            results[img_id].append(info["segmentation"])
            bbox_results[img_id].append([x1, y1, x2, y2])
    return results, img_ids, cocoGt, bbox_results



def draw_bbox(img_id, cocoGt, det_bboxes, gt_bboxes):
    path = cocoGt.loadImgs(ids=[img_id])[0]["file_name"]
    img_path = os.path.join(IMG_ROOT, path)
    img_data = cv2.imread(img_path)
    for box in det_bboxes:
        # color_mask = (0, 0, 255)
        # color_mask = np.array([0, 0, 255], dtype=np.int8)
        # bbox_mask = box.astype(np.bool)
        cv2.rectangle(img_data, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (0, 0, 255), 3)
        # img_data[bbox_mask] = img_data[bbox_mask] * 0.5 + color_mask * 0.5
    for box in gt_bboxes:
        # color_mask = np.array([0, 255, 0], dtype=np.int8)
        # bbox_mask = box.astype(np.bool)

        # img_data[bbox_mask] = img_data[bbox_mask] * 0.5 + color_mask * 0.5
        cv2.rectangle(img_data, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (0, 255, 0), 3)
    
    return img_data, path

def eval_pr(img_id):
    tp, fp, gt = 0, 0, 0
    gt_bboxes, gt_ignore = [], []
    det_bboxes = list()
    gt_bboxes = list()
    det_thrs_scores = list()
    
    plot_det_bboxes = list()
    plot_gt_bboxes  = list()
    
    if img_id in det_results:
        # for dt in det_results[img_id]:
        for idx, score in enumerate(det_scores[img_id]):
            # score = dt[-1]
            if score > conf_thrs:
                mask = _poly2mask(det_results[img_id][idx], img_h=1544, img_w=2064)
                det_bboxes.append(mask)
                det_thrs_scores.append(score)
                plot_det_bboxes.append(det_tmp_bboxes[img_id][idx])
    if img_id in gt_results:     
        for segm in gt_results[img_id]:
            mask = _poly2mask(segm, img_h=1544, img_w=2064)   
            gt_bboxes.append(mask)
        plot_gt_bboxes = gt_tmp_bboxes[img_id]
            
    det_bboxes = np.array(det_bboxes)
    gt_bboxes = np.array(gt_bboxes)
    det_thrs_scores = np.array(det_thrs_scores)
    gt_ignore = np.array(gt_ignore).reshape(-1, 4)
    
    if len(gt_bboxes) > 0:
        if len(det_bboxes) == 0:
            tp, fp = 0, 0 
        else:
            tp, fp = tpfp_default(det_bboxes, gt_bboxes, gt_ignore, det_thrs_scores, iou_thrs)
            tp, fp = np.sum(tp == 1), np.sum(fp == 1)
        gt = len(gt_bboxes)
        
    else:
        fp = len(det_bboxes)
        
        
    if VIS and (fp > 0 or tp < gt):
        img_data, path = draw_bbox(img_id=img_id, cocoGt=cocoGt, det_bboxes=plot_det_bboxes, gt_bboxes=plot_gt_bboxes)
        if fp > 0:
            save_dir = os.path.join(VIS_ROOT, "tmp/FP/")
            os.makedirs(save_dir, exist_ok=True)
            cv2.imwrite(os.path.join(save_dir, os.path.basename(path)+".jpg"), img_data, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
        if tp < gt:
            save_dir = os.path.join(VIS_ROOT, "tmp/FN/")
            os.makedirs(save_dir, exist_ok=True)
            cv2.imwrite(os.path.join(save_dir, os.path.basename(path)+".jpg"), img_data,
                        [int(cv2.IMWRITE_JPEG_QUALITY), 30])
    return gt, tp, fp

    
def eval_multiprocessing(img_ids):
    from multiprocessing import Pool
    pool = Pool(processes=16)

    results = pool.map(eval_pr, img_ids)
    # 关闭进程池,表示不再接受新的任务
    pool.close()

    # 等待所有任务完成
    pool.join()
    return np.sum(np.array(results), axis=0)



if __name__ == '__main__':
    VIS = 1
    IMG_ROOT = "gaotie_data"
    VIS_ROOT = 'badcase-vis-test-2/'

    conf_thrs = 0.5
    iou_thrs  = 0.001
    det_json_path = "results.bbox.json"
    gt_json_path  = "datasets/gaotie_test_data/annotations/test5_seg_removed.json"
    det_results, det_scores, det_tmp_bboxes = construct_det_results(det_json_path)
    gt_results, img_ids, cocoGt, gt_tmp_bboxes  = construct_gt_results(gt_json_path)

    gt, tp, fp = eval_multiprocessing(img_ids)
    eps = np.finfo(np.float32).eps
    recalls = tp / np.maximum(gt, eps)
    precisions = tp / np.maximum((tp + fp), eps)
    
    print("conf_thrs:{:.3f} iou_thrs:{:.3f}, gt:{:d}, TP={:d}, FP={:d}, P={:.3f}, R={:.3f}".format(conf_thrs, iou_thrs, gt, tp, fp, precisions, recalls))
    

总结

本文针对目标检测任务中GT存在多边形情况下给出了如下计算数据集的PR结果,基于mask来计算IoU,与语义分割计算IoU的思路一致,最后也给出了所有的实现代码作为参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1297908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

wireshark过滤包小技巧

1、过滤包含某个字符串的数据包&#xff1a; 或者&#xff1a; 2、过滤包含某一连续十六进制的数据包&#xff1a; 或者&#xff1a; 3、过滤精确到位数位置 或者&#xff1a;

1-Tornado的介绍

1 tornado的介绍 **Tornado**是一个用Python编写的可扩展的、无阻塞的**Web应用程序框架**和**Web服务器**。 它是由FriendFeed开发使用的&#xff1b;该公司于2009年被Facebook收购&#xff0c;而Tornado很快就开源了龙卷风以其高性能着称。它的设计允许处理大量并发连接&…

实时动作识别学习笔记

目录 yowo v2 yowof 判断是在干什么,不能获取细节信息 yowo v2 https://github.com/yjh0410/YOWOv2/blob/master/README_CN.md ModelClipmAPFPSweightYOWOv2-Nano1612.640ckptYOWOv2-Tiny

2024年度AI投资策略报告:乘AI之风,破明日之浪

今天分享的AI系列深度研究报告&#xff1a;《2024年度AI投资策略报告&#xff1a;乘AI之风&#xff0c;破明日之浪》。 &#xff08;报告出品方&#xff1a;万联证券&#xff09; 报告共计&#xff1a;25页 1 需求复苏&#xff0c;政策指引热点驱动AI 赋能助推行业发展 1.1 …

【车载开发系列】Visio工具使用小技巧

【车载开发系列】Visio工具使用小技巧 【车载开发系列】Visio工具使用小技巧 【车载开发系列】Visio工具使用小技巧一. Word中编辑Visio技巧二. Word中插入visio图形的问题三. 总结 一. Word中编辑Visio技巧 本节主要介绍了Microsoft Word中编辑Visio图形的具体方法。 在 Word…

PyQt6 QTimeEdit时间控件

​锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计39条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话…

Echarts图表title使用富文本

rich中有配置的话&#xff08;如a&#xff09;使用该样式&#xff0c;没有配置样式的话&#xff08;如b&#xff09;使用外层textstyle的样式&#xff0c;textstyle没有样式的话使用默认样式 const option1 {tooltip: {trigger: "item",},title: {text: ["{a|1…

线程及实现方式

一、线程 线程是一个基本的CPU执行单元&#xff0c;也是程序执行流的最小单位。引入线程之后&#xff0c;不仅是进程之间可以并发&#xff0c;进程内的各线程之间也可以并发&#xff0c;从而进一步提升了系统的并发度&#xff0c;使得一个进程内也可以并发处理各种任务&#x…

Linux就该这么学(第一天)

1.1 准备您的工具 所谓“工欲善其事&#xff0c;必先利其器”&#xff0c;在本章学习过程中&#xff0c;读者需要搭建出为今后练习而使用的红帽RHEL 7系统环境。您不需要为了练习实验而特意再购买一台新电脑&#xff0c;下文会讲解如何通过虚拟机软件来模拟出仿真系统。虚拟机…

使用 Tailwind CSS 完成导航栏效果

使用 Tailwind CSS 完成导航栏效果 本文将向您介绍如何使用 Tailwind CSS 创建一个漂亮的导航栏。通过逐步演示和示例代码&#xff0c;您将学习如何使用 Tailwind CSS 的类来设计和定制导航栏的样式。 准备工作 在开始之前&#xff0c;请确保已经安装了 Tailwind CSS。如果没…

异步回调模式

异步回调 所谓异步回调&#xff0c;本质上就是多线程中线程的通信&#xff0c;如今很多业务系统中&#xff0c;某个业务或者功能调用多个外部接口&#xff0c;通常这种调用就是异步的调用。如何得到这些异步调用的结果自然也就很重要了。 Callable、Future、FutureTask publi…

Python中利用遗传算法探索迷宫出路

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 当处理迷宫问题时&#xff0c;遗传算法提供了一种创新的解决方案。本文将深入探讨如何运用Python和遗传算法来解决迷宫问题。迷宫问题是一个经典的寻路问题&#xff0c;寻找从起点到终点的最佳路径。遗传算法是一…

半导体划片机助力氧化铝陶瓷片切割:科技与工艺的完美结合

在当今半导体制造领域&#xff0c;氧化铝陶瓷片作为一种高性能、高可靠性的材料&#xff0c;被广泛应用于各种电子设备中。而半导体划片机的出现&#xff0c;则为氧化铝陶瓷片的切割提供了新的解决方案&#xff0c;实现了科技与工艺的完美结合。 氧化铝陶瓷片是一种以氧化铝为基…

基于ssm端游账号销售管理系统论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对端游账号销售信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

单片机语言--C51语言的数据类型以及存储类型以及一些基本运算

C51语言 本文主要涉及C51语言的一些基本知识&#xff0c;比如C51语言的数据类型以及存储类型以及一些基本运算。 文章目录 C51语言一、 C51与标准C的比较二、 C51语言中的数据类型与存储类型2.1、C51的扩展数据类型2.2、数据存储类型 三、 C51的基本运算3.1 算术运算符3.2 逻辑…

使用Draw.io制作泳道图

使用Draw.io制作泳道图 一、横向泳道图1. 有标题泳道图2. 无标题泳道图3. 横纵向扩展泳道 二、纵向泳道图三、横纵交错地泳道图想做这样的图具体步骤1. 拖拽一个带标题的横向泳道图2. 拖拽一个带标题的单一图&#xff0c;并且把它放进Lane1中3. 其他注意 四、下载文件说明 一、…

ThinkPHP生活用品商城系统

有需要请加文章底部Q哦 可远程调试 ThinkPHP生活用品商城系统 一 介绍 此生活用品商城系统基于ThinkPHP框架开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统分为用户和管理员。(附带配套设计文档) 技术栈&#xff1a;ThinkPHPmysqlbootstrapphpstudyvscode 二 …

SAP UI5 walkthrough step7 JSON Model

这个章节&#xff0c;帮助我们理解MVC架构中的M 我们将会在APP中新增一个输入框&#xff0c;并将输入的值绑定到model&#xff0c;然后将其作为描述&#xff0c;直接显示在输入框的右边 首先修改App.controllers.js webapp/controller/App.controller.js sap.ui.define([&…

.NET开源且好用的权限工作流管理系统

前言 系统权限管理、工作流是企业应用开发中很常见的功能&#xff0c;虽说开发起来难度不大&#xff0c;但是假如从零开始开发一个完整的权限管理和工作流平台的话也是比较耗费时间的。今天推荐一款.NET开源且好用的权限工作流管理系统&#xff08;值得借鉴参考和使用&#xf…

【Python】翻译包translate

在Python里&#xff0c;可以用translate包完成语言的翻译转化 import translatetrantranslate.Translator(from_lang"ZH",to_lang"JA") strtran.translate("今天的天气怎么样")print(str)