Mask_RCNN
Mask_RCNN copied to clipboard
Map per class on custom dataset
Is it possible to produce the map per class on a custom dataset?
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)
Thanks a lot!
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.
Hi! did you manage to find the solution?.
@Ab-Abdurrahman I did. The first comment fixes it for you :). Good luck
Thanks, @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)
class_id??? where does it come from?
@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!
@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?
请问你解决了这个问题吗?