Mask_RCNN icon indicating copy to clipboard operation
Mask_RCNN copied to clipboard

Map per class on custom dataset

Open tomklaver1995 opened this issue 4 years ago • 10 comments

Is it possible to produce the map per class on a custom dataset?

tomklaver1995 avatar Apr 21 '20 08:04 tomklaver1995

You can get it by add these lines to compute_matches function in util.py. Adding these lines helps in getting gt_match and pred_match for each class. These gt_match and pred_match are customized for each class, so precision and recall will be calculated for each class.

def compute_matches(gt_boxes, gt_class_ids, gt_masks, pred_boxes, pred_class_ids, pred_scores, pred_masks, class_id, iou_threshold=0.5, score_threshold=0.5): """Finds matches between prediction and ground truth instances.

Returns:
    gt_match: 1-D array. For each GT box it has the index of the matched
              predicted box.
    pred_match: 1-D array. For each predicted box, it has the index of
                the matched ground truth box.
    overlaps: [pred_boxes, gt_boxes] IoU overlaps.
"""
# Trim zero padding
# TODO: cleaner to do zero unpadding upstream


gt_boxes=gt_boxes[np.where(gt_class_ids==class_id)]
gt_masks=gt_masks[:,:,gt_class_ids==class_id]
pred_boxes=pred_boxes[np.where(pred_class_ids==class_id)]
pred_scores=pred_scores[np.where(pred_class_ids==class_id)]
pred_masks=pred_masks[:,:,pred_class_ids==class_id]
pred_class_ids=np.delete(pred_class_ids,np.where(pred_class_ids!=class_id))
gt_class_ids=np.delete(gt_class_ids,np.where(gt_class_ids!=class_id))














gt_boxes = trim_zeros(gt_boxes)
gt_masks = gt_masks[..., :gt_boxes.shape[0]]
pred_boxes = trim_zeros(pred_boxes)

mminakshi avatar Apr 22 '20 19:04 mminakshi

Thanks a lot!

tomklaver1995 avatar Apr 23 '20 06:04 tomklaver1995

Hi @tomklaver1995 and @mminakshi, I trained my mask-rcnn model with two classes and now I would like to calculate the average Mean Precision (mAP) of all classes, so I know that I need to calculate the mAP of each class and then make an average to obtain the general mAP . For that, I modified compute_matches as @mminakshi suggested and my code in utils.py was as follows:


def compute_matches(gt_boxes, gt_class_ids, gt_masks,
                    pred_boxes, pred_class_ids, pred_scores, pred_masks,class_id,
                    iou_threshold=0.5, score_threshold=0.0):
    gt_boxes=gt_boxes[np.where(gt_class_ids==class_id])]#comeca aqui
    gt_masks=gt_masks[:,:,gt_class_ids==class_id]
    pred_boxes=pred_boxes[np.where(pred_class_ids==class_id)]
    pred_scores=pred_scores[np.where(pred_class_ids==class_id)]
    pred_masks=pred_masks[:,:,pred_class_ids==class_id]
    pred_class_ids=np.delete(pred_class_ids,np.where(pred_class_ids!=class_id))
    gt_class_ids=np.delete(gt_class_ids,np.where(gt_class_ids!=class_id))#ate aqui
    gt_boxes = trim_zeros(gt_boxes)
    gt_masks = gt_masks[..., :gt_boxes.shape[0]]
    pred_boxes = trim_zeros(pred_boxes)
    indices = np.argsort(pred_scores)[::-1]
    overlaps = compute_overlaps_masks(pred_masks, gt_masks)
    match_count = 0
    pred_match = -1 * np.ones([pred_boxes.shape[0]])
    gt_match = -1 * np.ones([gt_boxes.shape[0]])
    for i in range(len(pred_boxes)):
        sorted_ixs = np.argsort(overlaps[i])[::-1]
        low_score_idx = np.where(overlaps[i, sorted_ixs] < score_threshold)[0]
        if low_score_idx.size > 0:
            sorted_ixs = sorted_ixs[:low_score_idx[0]]
        for j in sorted_ixs:
            if gt_match[j] > -1:
                continue
            iou = overlaps[i, j]
            if iou < iou_threshold:
                break
            if pred_class_ids[i] == gt_class_ids[j]:
                match_count += 1
                gt_match[j] = i
                pred_match[i] = j
                break

    return gt_match, pred_match, overlaps


def compute_ap(gt_boxes, gt_class_ids, gt_masks,
               pred_boxes, pred_class_ids, pred_scores, pred_masks,class_id,
               iou_threshold=0.5):
    gt_match, pred_match, overlaps = compute_matches(
        gt_boxes, gt_class_ids, gt_masks,
        pred_boxes, pred_class_ids, pred_scores, pred_masks,class_id,
        iou_threshold)
    precisions = np.cumsum(pred_match > -1) / (np.arange(len(pred_match)) + 1)
    recalls = np.cumsum(pred_match > -1).astype(np.float32) / len(gt_match)

    precisions = np.concatenate([[0], precisions, [0]])
    recalls = np.concatenate([[0], recalls, [1]])

    for i in range(len(precisions) - 2, -1, -1):
        precisions[i] = np.maximum(precisions[i], precisions[i + 1])

    indices = np.where(recalls[:-1] != recalls[1:])[0] + 1
    mAP = np.sum((recalls[indices] - recalls[indices - 1]) *
                 precisions[indices])

    return mAP, precisions, recalls, overlaps


To use it I did it as follows:

def compute_ap_per_class(name_class, dataset, model, cfg):
  for image_id in dataset.image_ids:
    image, image_meta, gt_class_id, gt_bbox, gt_mask = load_image_gt(dataset, cfg, image_id, use_mini_mask=False)
    scaled_image = mold_image(image, cfg)
    sample = expand_dims(scaled_image, 0)
    yhat = model.detect(sample, verbose=0)
    r = yhat[0]
    AP, _, _, _ = compute_ap(gt_bbox, gt_class_id, gt_mask, r["rois"], r["class_ids"], r["scores"], r['masks'], name_class)
    APs.append(AP)
  mAP = mean(APs)
  return mAP

def evaluate_model(dataset, model, cfg):
	mAP_apple = compute_ap_per_class(train_set.class_names[1], dataset, model, cfg)
	mAP_apple_damaged = compute_ap_per_class(train_set.class_names[2], dataset, model, cfg)
	print('mAP da classe apple ', mAP_apple)
	print('mAP da classe apple damaged ', mAP_apple_damaged) 
	mAP = (mAP_apple + mAP_apple_damaged) / 2
	return mAP

However I am getting the following error, on the line :


-->sorted_ixs = np.argsort(overlaps[i])[::-1]
TypeError: '<' not supported between instances of 'numpy.ndarray' and 'str'

Could someone please help me with this? I would be very, very grateful, this is an important job for my academic life, and unfortunately I can't understand where I'm going wrong. Thank you in advance for your attention.

WillianaLeite avatar Jun 29 '20 02:06 WillianaLeite

Hi! did you manage to find the solution?.

burhr2 avatar Aug 22 '20 15:08 burhr2

@Ab-Abdurrahman I did. The first comment fixes it for you :). Good luck

tomklaver1995 avatar Aug 24 '20 13:08 tomklaver1995

Thanks, @tomklaver1995

burhr2 avatar Aug 24 '20 13:08 burhr2

You can get it by add these lines to compute_matches function in util.py. Adding these lines helps in getting gt_match and pred_match for each class. These gt_match and pred_match are customized for each class, so precision and recall will be calculated for each class.

def compute_matches(gt_boxes, gt_class_ids, gt_masks, pred_boxes, pred_class_ids, pred_scores, pred_masks, class_id, iou_threshold=0.5, score_threshold=0.5): """Finds matches between prediction and ground truth instances.

Returns:
    gt_match: 1-D array. For each GT box it has the index of the matched
              predicted box.
    pred_match: 1-D array. For each predicted box, it has the index of
                the matched ground truth box.
    overlaps: [pred_boxes, gt_boxes] IoU overlaps.
"""
# Trim zero padding
# TODO: cleaner to do zero unpadding upstream


gt_boxes=gt_boxes[np.where(gt_class_ids==class_id)]
gt_masks=gt_masks[:,:,gt_class_ids==class_id]
pred_boxes=pred_boxes[np.where(pred_class_ids==class_id)]
pred_scores=pred_scores[np.where(pred_class_ids==class_id)]
pred_masks=pred_masks[:,:,pred_class_ids==class_id]
pred_class_ids=np.delete(pred_class_ids,np.where(pred_class_ids!=class_id))
gt_class_ids=np.delete(gt_class_ids,np.where(gt_class_ids!=class_id))














gt_boxes = trim_zeros(gt_boxes)
gt_masks = gt_masks[..., :gt_boxes.shape[0]]
pred_boxes = trim_zeros(pred_boxes)

class_id??? where does it come from?

luoolu avatar Feb 19 '21 01:02 luoolu

@mminakshi Hi, I tried your code, but nothing happened when I compute AP. Can you show that how to use this function? And I don't understand where is "class_id", does it come from the image when I load image data? Thanks for your help!

software-ai-life avatar Mar 18 '21 06:03 software-ai-life

@tomklaver1995 @mminakshi Hi, this only works if you have two classes? if I have 10 classes does the code change or is it just sending the name of each class to the function?

felipetobars avatar Nov 24 '21 17:11 felipetobars

请问你解决了这个问题吗?

LCC0001 avatar Sep 13 '22 03:09 LCC0001