MiVOLO / mivolo /data /misc.py
admin
sync
319d3b5
import argparse
import ast
import re
from typing import List, Optional, Tuple, Union
import cv2
import numpy as np
import torch
import torchvision.transforms.functional as F
from scipy.optimize import linear_sum_assignment
from timm.data import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD
CROP_ROUND_RATE = 0.1
MIN_PERSON_CROP_NONZERO = 0.5
def aggregate_votes_winsorized(ages, max_age_dist=6):
# Replace any annotation that is more than a max_age_dist away from the median
# with the median + max_age_dist if higher or max_age_dist - max_age_dist if below
median = np.median(ages)
ages = np.clip(ages, median - max_age_dist, median + max_age_dist)
return np.mean(ages)
def cropout_black_parts(img, tol=0.3):
# Create a binary mask of zero pixels
zero_pixels_mask = np.all(img == 0, axis=2)
# Calculate the threshold for zero pixels in rows and columns
threshold = img.shape[0] - img.shape[0] * tol
# Calculate row sums and column sums of zero pixels mask
row_sums = np.sum(zero_pixels_mask, axis=1)
col_sums = np.sum(zero_pixels_mask, axis=0)
# Find the first and last rows with zero pixel sums above the threshold
start_row = np.argmin(row_sums > threshold)
end_row = img.shape[0] - np.argmin(row_sums[::-1] > threshold)
# Find the first and last columns with zero pixel sums above the threshold
start_col = np.argmin(col_sums > threshold)
end_col = img.shape[1] - np.argmin(col_sums[::-1] > threshold)
# Crop the image
cropped_img = img[start_row:end_row, start_col:end_col, :]
area = cropped_img.shape[0] * cropped_img.shape[1]
area_orig = img.shape[0] * img.shape[1]
return cropped_img, area / area_orig
def natural_key(string_):
"""See http://www.codinghorror.com/blog/archives/001018.html"""
return [int(s) if s.isdigit() else s for s in re.split(r"(\d+)", string_.lower())]
def add_bool_arg(parser, name, default=False, help=""):
dest_name = name.replace("-", "_")
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument("--" + name, dest=dest_name, action="store_true", help=help)
group.add_argument("--no-" + name, dest=dest_name, action="store_false", help=help)
parser.set_defaults(**{dest_name: default})
def cumulative_score(pred_ages, gt_ages, L, tol=1e-6):
n = pred_ages.shape[0]
num_correct = torch.sum(torch.abs(pred_ages - gt_ages) <= L + tol)
cs_score = num_correct / n
return cs_score
def cumulative_error(pred_ages, gt_ages, L, tol=1e-6):
n = pred_ages.shape[0]
num_correct = torch.sum(torch.abs(pred_ages - gt_ages) >= L + tol)
cs_score = num_correct / n
return cs_score
class ParseKwargs(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
kw = {}
for value in values:
key, value = value.split("=")
try:
kw[key] = ast.literal_eval(value)
except ValueError:
kw[key] = str(value) # fallback to string (avoid need to escape on command line)
setattr(namespace, self.dest, kw)
def box_iou(box1, box2, over_second=False):
"""
Return intersection-over-union (Jaccard index) of boxes.
If over_second == True, return mean(intersection-over-union, (inter / area2))
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
Arguments:
box1 (Tensor[N, 4])
box2 (Tensor[M, 4])
Returns:
iou (Tensor[N, M]): the NxM matrix containing the pairwise
IoU values for every element in boxes1 and boxes2
"""
def box_area(box):
# box = 4xn
return (box[2] - box[0]) * (box[3] - box[1])
area1 = box_area(box1.T)
area2 = box_area(box2.T)
# inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
iou = inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter)
if over_second:
return (inter / area2 + iou) / 2 # mean(inter / area2, iou)
else:
return iou
def split_batch(bs: int, dev: int) -> Tuple[int, int]:
full_bs = (bs // dev) * dev
part_bs = bs - full_bs
return full_bs, part_bs
def assign_faces(
persons_bboxes: List[torch.tensor], faces_bboxes: List[torch.tensor], iou_thresh: float = 0.0001
) -> Tuple[List[Optional[int]], List[int]]:
"""
Assign person to each face if it is possible.
Return:
- assigned_faces List[Optional[int]]: mapping of face_ind to person_ind
( assigned_faces[face_ind] = person_ind ). person_ind can be None
- unassigned_persons_inds List[int]: persons indexes without any assigned face
"""
assigned_faces: List[Optional[int]] = [None for _ in range(len(faces_bboxes))]
unassigned_persons_inds: List[int] = [p_ind for p_ind in range(len(persons_bboxes))]
if len(persons_bboxes) == 0 or len(faces_bboxes) == 0:
return assigned_faces, unassigned_persons_inds
cost_matrix = box_iou(torch.stack(persons_bboxes), torch.stack(faces_bboxes), over_second=True).cpu().numpy()
persons_indexes, face_indexes = [], []
if len(cost_matrix) > 0:
persons_indexes, face_indexes = linear_sum_assignment(cost_matrix, maximize=True)
matched_persons = set()
for person_idx, face_idx in zip(persons_indexes, face_indexes):
ciou = cost_matrix[person_idx][face_idx]
if ciou > iou_thresh:
if person_idx in matched_persons:
# Person can not be assigned twice, in reality this should not happen
continue
assigned_faces[face_idx] = person_idx
matched_persons.add(person_idx)
unassigned_persons_inds = [p_ind for p_ind in range(len(persons_bboxes)) if p_ind not in matched_persons]
return assigned_faces, unassigned_persons_inds
def class_letterbox(im, new_shape=(640, 640), color=(0, 0, 0), scaleup=True):
# Resize and pad image while meeting stride-multiple constraints
shape = im.shape[:2] # current shape [height, width]
if isinstance(new_shape, int):
new_shape = (new_shape, new_shape)
if im.shape[0] == new_shape[0] and im.shape[1] == new_shape[1]:
return im
# Scale ratio (new / old)
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
if not scaleup: # only scale down, do not scale up (for better val mAP)
r = min(r, 1.0)
# Compute padding
# ratio = r, r # width, height ratios
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
dw /= 2 # divide padding into 2 sides
dh /= 2
if shape[::-1] != new_unpad: # resize
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
return im
def prepare_classification_images(
img_list: List[Optional[np.ndarray]],
target_size: int = 224,
mean=IMAGENET_DEFAULT_MEAN,
std=IMAGENET_DEFAULT_STD,
device=None,
) -> torch.tensor:
prepared_images: List[torch.tensor] = []
for img in img_list:
if img is None:
img = torch.zeros((3, target_size, target_size), dtype=torch.float32)
img = F.normalize(img, mean=mean, std=std)
img = img.unsqueeze(0)
prepared_images.append(img)
continue
img = class_letterbox(img, new_shape=(target_size, target_size))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img / 255.0
img = (img - mean) / std
img = img.astype(dtype=np.float32)
img = img.transpose((2, 0, 1))
img = np.ascontiguousarray(img)
img = torch.from_numpy(img)
img = img.unsqueeze(0)
prepared_images.append(img)
prepared_input = torch.concat(prepared_images)
if device:
prepared_input = prepared_input.to(device)
return prepared_input
def IOU(bb1: Union[tuple, list], bb2: Union[tuple, list], norm_second_bbox: bool = False) -> float:
# expects [ymin, xmin, ymax, xmax], doesnt matter absolute or relative
assert bb1[1] < bb1[3]
assert bb1[0] < bb1[2]
assert bb2[1] < bb2[3]
assert bb2[0] < bb2[2]
# determine the coordinates of the intersection rectangle
x_left = max(bb1[1], bb2[1])
y_top = max(bb1[0], bb2[0])
x_right = min(bb1[3], bb2[3])
y_bottom = min(bb1[2], bb2[2])
if x_right < x_left or y_bottom < y_top:
return 0.0
# The intersection of two axis-aligned bounding boxes is always an
# axis-aligned bounding box
intersection_area = (x_right - x_left) * (y_bottom - y_top)
# compute the area of both AABBs
bb1_area = (bb1[3] - bb1[1]) * (bb1[2] - bb1[0])
bb2_area = (bb2[3] - bb2[1]) * (bb2[2] - bb2[0])
if not norm_second_bbox:
# compute the intersection over union by taking the intersection
# area and dividing it by the sum of prediction + ground-truth
# areas - the interesection area
iou = intersection_area / float(bb1_area + bb2_area - intersection_area)
else:
# for cases when we search if second bbox is inside first one
iou = intersection_area / float(bb2_area)
assert iou >= 0.0
assert iou <= 1.01
return iou