""" The FDF dataset expands bound boxes differently from what is used for CSE. """ import numpy as np def quadratic_bounding_box(x0, y0, width, height, imshape): # We assume that we can create a image that is quadratic without # minimizing any of the sides assert width <= min(imshape[:2]) assert height <= min(imshape[:2]) min_side = min(height, width) if height != width: side_diff = abs(height - width) # Want to extend the shortest side if min_side == height: # Vertical side height += side_diff if height > imshape[0]: # Take full frame, and shrink width y0 = 0 height = imshape[0] side_diff = abs(height - width) width -= side_diff x0 += side_diff // 2 else: y0 -= side_diff // 2 y0 = max(0, y0) else: # Horizontal side width += side_diff if width > imshape[1]: # Take full frame width, and shrink height x0 = 0 width = imshape[1] side_diff = abs(height - width) height -= side_diff y0 += side_diff // 2 else: x0 -= side_diff // 2 x0 = max(0, x0) # Check that bbox goes outside image x1 = x0 + width y1 = y0 + height if imshape[1] < x1: diff = x1 - imshape[1] x0 -= diff if imshape[0] < y1: diff = y1 - imshape[0] y0 -= diff assert x0 >= 0, "Bounding box outside image." assert y0 >= 0, "Bounding box outside image." assert x0 + width <= imshape[1], "Bounding box outside image." assert y0 + height <= imshape[0], "Bounding box outside image." return x0, y0, width, height def expand_bounding_box(bbox, percentage, imshape): orig_bbox = bbox.copy() x0, y0, x1, y1 = bbox width = x1 - x0 height = y1 - y0 x0, y0, width, height = quadratic_bounding_box( x0, y0, width, height, imshape) expanding_factor = int(max(height, width) * percentage) possible_max_expansion = [(imshape[0] - width) // 2, (imshape[1] - height) // 2, expanding_factor] expanding_factor = min(possible_max_expansion) # Expand height if expanding_factor > 0: y0 = y0 - expanding_factor y0 = max(0, y0) height += expanding_factor * 2 if height > imshape[0]: y0 -= (imshape[0] - height) height = imshape[0] if height + y0 > imshape[0]: y0 -= (height + y0 - imshape[0]) # Expand width x0 = x0 - expanding_factor x0 = max(0, x0) width += expanding_factor * 2 if width > imshape[1]: x0 -= (imshape[1] - width) width = imshape[1] if width + x0 > imshape[1]: x0 -= (width + x0 - imshape[1]) y1 = y0 + height x1 = x0 + width assert y0 >= 0, "Y0 is minus" assert height <= imshape[0], "Height is larger than image." assert x0 + width <= imshape[1] assert y0 + height <= imshape[0] assert width == height, "HEIGHT IS NOT EQUAL WIDTH!!" assert x0 >= 0, "Y0 is minus" assert width <= imshape[1], "Height is larger than image." # Check that original bbox is within new x0_o, y0_o, x1_o, y1_o = orig_bbox assert x0 <= x0_o, f"New bbox is outisde of original. O:{x0_o}, N: {x0}" assert x1 >= x1_o, f"New bbox is outisde of original. O:{x1_o}, N: {x1}" assert y0 <= y0_o, f"New bbox is outisde of original. O:{y0_o}, N: {y0}" assert y1 >= y1_o, f"New bbox is outisde of original. O:{y1_o}, N: {y1}" x0, y0, width, height = [int(_) for _ in [x0, y0, width, height]] x1 = x0 + width y1 = y0 + height return np.array([x0, y0, x1, y1]) def is_keypoint_within_bbox(x0, y0, x1, y1, keypoint): keypoint = keypoint[:, :3] # only nose + eyes are relevant kp_X = keypoint[0, :] kp_Y = keypoint[1, :] within_X = np.all(kp_X >= x0) and np.all(kp_X <= x1) within_Y = np.all(kp_Y >= y0) and np.all(kp_Y <= y1) return within_X and within_Y def expand_bbox_simple(bbox, percentage): x0, y0, x1, y1 = bbox.astype(float) width = x1 - x0 height = y1 - y0 x_c = int(x0) + width // 2 y_c = int(y0) + height // 2 avg_size = max(width, height) new_width = avg_size * (1 + percentage) x0 = x_c - new_width // 2 y0 = y_c - new_width // 2 x1 = x_c + new_width // 2 y1 = y_c + new_width // 2 return np.array([x0, y0, x1, y1]).astype(int) def pad_image(im, bbox, pad_value): x0, y0, x1, y1 = bbox if x0 < 0: pad_im = np.zeros((im.shape[0], abs(x0), im.shape[2]), dtype=np.uint8) + pad_value im = np.concatenate((pad_im, im), axis=1) x1 += abs(x0) x0 = 0 if y0 < 0: pad_im = np.zeros((abs(y0), im.shape[1], im.shape[2]), dtype=np.uint8) + pad_value im = np.concatenate((pad_im, im), axis=0) y1 += abs(y0) y0 = 0 if x1 >= im.shape[1]: pad_im = np.zeros( (im.shape[0], x1 - im.shape[1] + 1, im.shape[2]), dtype=np.uint8) + pad_value im = np.concatenate((im, pad_im), axis=1) if y1 >= im.shape[0]: pad_im = np.zeros( (y1 - im.shape[0] + 1, im.shape[1], im.shape[2]), dtype=np.uint8) + pad_value im = np.concatenate((im, pad_im), axis=0) return im[y0:y1, x0:x1] def clip_box(bbox, im): bbox[0] = max(0, bbox[0]) bbox[1] = max(0, bbox[1]) bbox[2] = min(im.shape[1] - 1, bbox[2]) bbox[3] = min(im.shape[0] - 1, bbox[3]) return bbox def cut_face(im, bbox, simple_expand=False, pad_value=0, pad_im=True): outside_im = (bbox < 0).any() or bbox[2] > im.shape[1] or bbox[3] > im.shape[0] if simple_expand or (outside_im and pad_im): return pad_image(im, bbox, pad_value) bbox = clip_box(bbox, im) x0, y0, x1, y1 = bbox return im[y0:y1, x0:x1] def expand_bbox( bbox_ltrb, imshape, simple_expand, default_to_simple=False, expansion_factor=0.35): assert bbox_ltrb.shape == (4,), f"BBox shape was: {bbox_ltrb.shape}" bbox = bbox_ltrb.astype(float) # FDF256 uses simple expand with ratio 0.4 if simple_expand: return expand_bbox_simple(bbox, 0.4) try: return expand_bounding_box(bbox, expansion_factor, imshape) except AssertionError: return expand_bbox_simple(bbox, expansion_factor * 2)