# Copyright 2020 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ """metrics utils""" import numpy as np from src.config import ConfigYOLOV3ResNet18 def calc_iou(bbox_pred, bbox_ground): """Calculate iou of predicted bbox and ground truth.""" x1 = bbox_pred[0] y1 = bbox_pred[1] width1 = bbox_pred[2] - bbox_pred[0] height1 = bbox_pred[3] - bbox_pred[1] x2 = bbox_ground[0] y2 = bbox_ground[1] width2 = bbox_ground[2] - bbox_ground[0] height2 = bbox_ground[3] - bbox_ground[1] endx = max(x1 + width1, x2 + width2) startx = min(x1, x2) width = width1 + width2 - (endx - startx) endy = max(y1 + height1, y2 + height2) starty = min(y1, y2) height = height1 + height2 - (endy - starty) if width <= 0 or height <= 0: iou = 0 else: area = width * height area1 = width1 * height1 area2 = width2 * height2 iou = area * 1. / (area1 + area2 - area) return iou def apply_nms(all_boxes, all_scores, thres, max_boxes): """Apply NMS to bboxes.""" x1 = all_boxes[:, 0] y1 = all_boxes[:, 1] x2 = all_boxes[:, 2] y2 = all_boxes[:, 3] areas = (x2 - x1 + 1) * (y2 - y1 + 1) order = all_scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) if len(keep) >= max_boxes: break xx1 = np.maximum(x1[i], x1[order[1:]]) yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) w = np.maximum(0.0, xx2 - xx1 + 1) h = np.maximum(0.0, yy2 - yy1 + 1) inter = w * h ovr = inter / (areas[i] + areas[order[1:]] - inter) inds = np.where(ovr <= thres)[0] order = order[inds + 1] return keep def metrics(pred_data): """Calculate precision and recall of predicted bboxes.""" config = ConfigYOLOV3ResNet18() num_classes = config.num_classes count_corrects = [1e-6 for _ in range(num_classes)] count_grounds = [1e-6 for _ in range(num_classes)] count_preds = [1e-6 for _ in range(num_classes)] for i, sample in enumerate(pred_data): gt_anno = sample["annotation"] box_scores = sample['box_scores'] boxes = sample['boxes'] mask = box_scores >= config.obj_threshold boxes_ = [] scores_ = [] classes_ = [] max_boxes = config.nms_max_num for c in range(num_classes): class_boxes = np.reshape(boxes, [-1, 4])[np.reshape(mask[:, c], [-1])] class_box_scores = np.reshape(box_scores[:, c], [-1])[np.reshape(mask[:, c], [-1])] nms_index = apply_nms(class_boxes, class_box_scores, config.nms_threshold, max_boxes) class_boxes = class_boxes[nms_index] class_box_scores = class_box_scores[nms_index] classes = np.ones_like(class_box_scores, 'int32') * c boxes_.append(class_boxes) scores_.append(class_box_scores) classes_.append(classes) boxes = np.concatenate(boxes_, axis=0) classes = np.concatenate(classes_, axis=0) # metric count_correct = [1e-6 for _ in range(num_classes)] count_ground = [1e-6 for _ in range(num_classes)] count_pred = [1e-6 for _ in range(num_classes)] for anno in gt_anno: count_ground[anno[4]] += 1 for box_index, box in enumerate(boxes): bbox_pred = [box[1], box[0], box[3], box[2]] count_pred[classes[box_index]] += 1 for anno in gt_anno: class_ground = anno[4] if classes[box_index] == class_ground: iou = calc_iou(bbox_pred, anno) if iou >= 0.5: count_correct[class_ground] += 1 break count_corrects = [count_corrects[i] + count_correct[i] for i in range(num_classes)] count_preds = [count_preds[i] + count_pred[i] for i in range(num_classes)] count_grounds = [count_grounds[i] + count_ground[i] for i in range(num_classes)] precision = np.array([count_corrects[ix] / count_preds[ix] for ix in range(num_classes)]) recall = np.array([count_corrects[ix] / count_grounds[ix] for ix in range(num_classes)]) return precision, recall