def get_boxes_from_image_dataframe(image_data, image_size): if not image_data.empty: # get the labels label_ids_global = torch.tensor(list(image_data["classid"]), dtype=torch.long) difficult_flag = torch.tensor(list(image_data["difficult"] == 1), dtype=torch.bool) # get the boxes boxes = image_data[["lx", "ty", "rx", "by"]].to_numpy() # renorm boxes using the image size boxes[:, 0] *= image_size.w boxes[:, 2] *= image_size.w boxes[:, 1] *= image_size.h boxes[:, 3] *= image_size.h boxes = torch.FloatTensor(boxes) boxes = BoxList(boxes, image_size=image_size, mode="xyxy") else: boxes = BoxList.create_empty(image_size) label_ids_global = torch.tensor([], dtype=torch.long) difficult_flag = torch.tensor([], dtype=torch.bool) boxes.add_field("labels", label_ids_global) boxes.add_field("difficult", difficult_flag) boxes.add_field("labels_original", label_ids_global) boxes.add_field("difficult_original", difficult_flag) return boxes
def save_cropped_boxes(dataset, tgt_image_path, extension=".jpg", num_random_crops_per_image=0): # crop all the boxes db = {"cids":[], "cluster":[], "gtbboxid":[], "classid":[], "imageid":[], "difficult":[], "type":[], "size":[], "bbox":[]} for image_id in tqdm(dataset.image_ids): img = dataset._get_dataset_image_by_id(image_id) boxes = dataset.get_image_annotation_for_imageid(image_id) assert boxes.has_field("labels"), "GT boxes need a field 'labels'" # remove all fields except "labels" and "difficult" for f in boxes.fields(): if f not in ["labels", "difficult"]: boxes.remove_field(f) if not boxes.has_field("difficult"): boxes.add_field("difficult", torch.zeros(len(boxes), dtype=torch.bool)) num_gt_boxes = len(boxes) im_size = FeatureMapSize(img=img) assert im_size == boxes.image_size eval_scale = dataset.get_eval_scale() # sample random boxes if needed if num_random_crops_per_image > 0: boxes_random = torch.rand(num_random_crops_per_image, 4) x1 = torch.min(boxes_random[:, 0], boxes_random[:, 2]) * im_size.w x2 = torch.max(boxes_random[:, 0], boxes_random[:, 2]) * im_size.w y1 = torch.min(boxes_random[:, 1], boxes_random[:, 3]) * im_size.h y2 = torch.max(boxes_random[:, 1], boxes_random[:, 3]) * im_size.h boxes_random = torch.stack([x1, y1, x2, y2], 1).floor() # crop boxes that are too small min_size = 10.0 / eval_scale * max(im_size.w, im_size.h) mask_bad_boxes = (boxes_random[:,0] + min_size > boxes_random[:,2]) | (boxes_random[:,1] + min_size > boxes_random[:,3]) good_boxes = torch.nonzero(~mask_bad_boxes).view(-1) boxes_random = boxes_random[good_boxes] boxes_random = BoxList(boxes_random, im_size, mode="xyxy") boxes_random.add_field("labels", torch.full([len(boxes_random)], -1, dtype=torch.long)) boxes_random.add_field("difficult", torch.zeros(len(boxes_random), dtype=torch.bool)) boxes = cat_boxlist([boxes, boxes_random]) if boxes is not None: for i_box in range(len(boxes)): # box format: left, top, right, bottom box = boxes[i_box].bbox_xyxy.view(-1) box = [b.item() for b in box] cropped_img = img.crop(box) if i_box < num_gt_boxes: lbl = boxes[i_box].get_field("labels").item() dif_flag = boxes[i_box].get_field("difficult").item() box_id = i_box box_type = "GT" else: lbl = -1 dif_flag = 0 box_id = i_box box_type = "RN" # create the file name to be used with cirtorch.datasets.datahelpers.cid2filename and their dataloader cid = "box{box_id:05d}_lbl{label:05d}_dif{dif:01d}_im{image_id:05d}{box_type}".format(box_id=box_id, image_id = image_id, label = lbl, dif = dif_flag, box_type=box_type) file_name = cid2filename(cid, prefix=tgt_image_path) # save the image image_path, _ = os.path.split(file_name) mkdir(image_path) if extension: cropped_img.save("{}{}".format(file_name, extension)) else: # cirtorch uses files with empty extension for training for some reason, need to support that cropped_img.save("{}".format(file_name), format="jpeg") # add to the db structure db["cids"].append(cid) db["cluster"].append(lbl) # use labels as clusters not to sample negatives from the same object db["classid"].append(lbl) db["gtbboxid"].append(box_id) db["imageid"].append(image_id) db["difficult"].append(dif_flag) if i_box < num_gt_boxes: db["type"].append("gtproposal") else: db["type"].append("randomcrop") db["size"].append(cropped_img.size) db["bbox"].append(box) # format (x1,y1,x2,y2) return db
def evaluate_detections(self, all_boxes, output_dir, mAP_iou_threshold=0.5): predictions = [] gt_boxes = [] roidb = self.roidb for i_image, roi in enumerate(roidb): image_size = FeatureMapSize(w=roi["width"], h=roi["height"]) if roi["boxes"].size > 0: roi_gt_boxes = BoxList(roi["boxes"], image_size, mode="xyxy") else: roi_gt_boxes = BoxList.create_empty(image_size) roi_gt_boxes.add_field( "labels", torch.as_tensor(roi["gt_classes"], dtype=torch.int32)) roi_gt_boxes.add_field( "difficult", torch.as_tensor(roi["gt_ishard"], dtype=torch.int32)) gt_boxes.append(roi_gt_boxes) roi_detections = [] for i_class, class_boxes in enumerate(all_boxes): assert len(class_boxes) == len(roidb), \ "Number of detection for class {0} image{1} ({2}) inconsistent with the length of roidb ({3})".format(i_class, i_image, len(class_boxes), len(roidb)) boxes = class_boxes[i_image] if len(boxes) > 0: assert boxes.shape[ 1] == 5, "Detections should be of shape (:,5), but are {0} for class {1}, image {2}".format( boxes.shape, i_class, i_image) bbox = BoxList(boxes[:, :4], image_size, mode="xyxy") scores = boxes[:, -1] bbox.add_field( "scores", torch.as_tensor(scores, dtype=torch.float32)) bbox.add_field( "labels", torch.full(scores.shape, i_class, dtype=torch.int32)) roi_detections.append(bbox) if roi_detections: roi_detections = cat_boxlist(roi_detections) else: roi_detections = BoxList.create_empty(image_size) roi_detections.add_field( "scores", torch.zeros((0, ), dtype=torch.float32)) roi_detections.add_field("labels", torch.zeros((0, ), dtype=torch.int32)) predictions.append(roi_detections) if False: self.visualize_detections(i_image, gt=roi_gt_boxes, dets=roi_detections) ap_data = do_voc_evaluation(predictions, gt_boxes, iou_thresh=mAP_iou_threshold, use_07_metric=False) print("mAP@{:0.2f}: {:0.4f}".format(mAP_iou_threshold, ap_data["map"])) print("mAPw@{:0.2f}: {:0.4f}".format(mAP_iou_threshold, ap_data["map_weighted"])) print("recall@{:0.2f}: {:0.4f}".format(mAP_iou_threshold, ap_data["recall"])) return ap_data['map']