garmentiq.landmark.detection.utils
Pre- and post-processing for the landmark detection model.
Covers the affine transforms that crop and scale an image to the model input size, and the decoding that turns predicted heatmaps back into image-space coordinates with confidence scores.
1"""Pre- and post-processing for the landmark detection model. 2 3Covers the affine transforms that crop and scale an image to the model input size, and 4the decoding that turns predicted heatmaps back into image-space coordinates with 5confidence scores. 6""" 7import math 8import numpy as np 9import cv2 10from PIL import Image 11import torchvision.transforms as transforms 12from typing import Union 13 14 15def get_max_preds(batch_heatmaps): 16 """ 17 get predictions from score maps 18 heatmaps: numpy.ndarray([batch_size, num_joints, height, width]) 19 20 Args: 21 batch_heatmaps (numpy.ndarray): Heatmaps generated by the model. 22 Shape: [batch_size, num_joints, height, width]. 23 24 Returns: 25 tuple: 26 - preds (numpy.ndarray): Predicted coordinates. 27 - maxvals (numpy.ndarray): Maximum values (confidence scores) for each prediction. 28 """ 29 assert isinstance( 30 batch_heatmaps, np.ndarray 31 ), "batch_heatmaps should be numpy.ndarray" 32 assert batch_heatmaps.ndim == 4, "batch_images should be 4-ndim" 33 34 batch_size = batch_heatmaps.shape[0] 35 num_joints = batch_heatmaps.shape[1] 36 width = batch_heatmaps.shape[3] 37 heatmaps_reshaped = batch_heatmaps.reshape((batch_size, num_joints, -1)) 38 idx = np.argmax(heatmaps_reshaped, 2) 39 maxvals = np.amax(heatmaps_reshaped, 2) 40 41 maxvals = maxvals.reshape((batch_size, num_joints, 1)) 42 idx = idx.reshape((batch_size, num_joints, 1)) 43 44 preds = np.tile(idx, (1, 1, 2)).astype(np.float32) 45 46 preds[:, :, 0] = (preds[:, :, 0]) % width 47 preds[:, :, 1] = np.floor((preds[:, :, 1]) / width) 48 49 pred_mask = np.tile(np.greater(maxvals, 0.0), (1, 1, 2)) 50 pred_mask = pred_mask.astype(np.float32) 51 52 preds *= pred_mask 53 return preds, maxvals 54 55 56def get_final_preds(output, height=96, width=72): 57 """ 58 Transforms raw heatmap outputs into final landmark coordinates. 59 60 Applies post-processing (e.g., quarter offset for sub-pixel accuracy) 61 to refine the landmark predictions from heatmaps. 62 63 Args: 64 output (numpy.ndarray): Raw heatmap output from the model. 65 height (int): Height of the heatmap. Defaults to 96. 66 width (int): Width of the heatmap. Defaults to 72. 67 68 Returns: 69 tuple: 70 - coords (numpy.ndarray): Final predicted coordinates. 71 - maxvals (numpy.ndarray): Confidence scores for the predictions. 72 """ 73 heatmap_height = height 74 heatmap_width = width 75 76 batch_heatmaps = output 77 coords, maxvals = get_max_preds(batch_heatmaps) 78 # post-processing 79 for n in range(coords.shape[0]): 80 for p in range(coords.shape[1]): 81 hm = batch_heatmaps[n][p] 82 px = int(math.floor(coords[n][p][0] + 0.5)) 83 py = int(math.floor(coords[n][p][1] + 0.5)) 84 if 1 < px < heatmap_width - 1 and 1 < py < heatmap_height - 1: 85 diff = np.array( 86 [hm[py][px + 1] - hm[py][px - 1], hm[py + 1][px] - hm[py - 1][px]] 87 ) 88 coords[n][p] += np.sign(diff) * 0.25 89 90 return coords, maxvals 91 92 93def flip_back(output_flipped, matched_parts, heatmap_wid): 94 """ 95 Flips the output (coordinates or heatmaps) horizontally for test-time augmentation. 96 97 Args: 98 output_flipped (numpy.ndarray): The output (heatmaps or coordinates) that has been flipped. 99 matched_parts (list): A list of tuples indicating which joint pairs are left-right symmetric. 100 heatmap_wid (int): The width of the heatmap (used for coordinate flipping). 101 102 Returns: 103 numpy.ndarray: The flipped output with joints correctly reordered. 104 """ 105 if output_flipped.ndim == 4: 106 output_flipped = output_flipped[:, :, :, ::-1] 107 for pair in matched_parts: 108 tmp = output_flipped[:, pair[0], :, :].copy() 109 output_flipped[:, pair[0], :, :] = output_flipped[:, pair[1], :, :] 110 output_flipped[:, pair[1], :, :] = tmp 111 elif output_flipped.ndim == 3: 112 output_flipped[:, :, 0] = heatmap_wid - output_flipped[:, :, 0] 113 for pair in matched_parts: 114 tmp = output_flipped[:, pair[0], :].copy() 115 output_flipped[:, pair[0], :] = output_flipped[:, pair[1], :] 116 output_flipped[:, pair[1], :] = tmp 117 else: 118 raise NotImplementedError( 119 "output_flipped should be [batch_size, num_joints, height, width], " 120 "or [batch_size, num_joints, coord_dim" 121 ) 122 123 return output_flipped 124 125 126def fliplr_joints(joints, joints_vis, width, matched_parts): 127 """ 128 Flips joint coordinates horizontally and reorders them based on matched parts. 129 130 Args: 131 joints (numpy.ndarray): Array of joint coordinates. 132 joints_vis (numpy.ndarray): Array indicating visibility of joints. 133 width (int): Width of the image or feature map. 134 matched_parts (list): A list of tuples indicating which joint pairs are left-right symmetric. 135 136 Returns: 137 tuple: 138 - joints (numpy.ndarray): Flipped and reordered joint coordinates. 139 - joints_vis (numpy.ndarray): Corresponding joint visibility. 140 """ 141 # Flip horizontal 142 joints[:, 0] = width - joints[:, 0] - 1 143 144 # Change left-right parts 145 for pair in matched_parts: 146 joints[pair[0], :], joints[pair[1], :] = ( 147 joints[pair[1], :], 148 joints[pair[0], :].copy(), 149 ) 150 joints_vis[pair[0], :], joints_vis[pair[1], :] = ( 151 joints_vis[pair[1], :], 152 joints_vis[pair[0], :].copy(), 153 ) 154 155 return joints * joints_vis, joints_vis 156 157 158def transform_preds(coords, center, scale, output_size: list[int, int] = [72, 96]): 159 """ 160 Transforms predicted coordinates from heatmap space back to original image space. 161 162 Args: 163 coords (numpy.ndarray): Predicted coordinates in heatmap space. 164 center (numpy.ndarray): Center of the original image (or cropped region). 165 scale (numpy.ndarray): Scale factor applied during preprocessing. 166 output_size (list[int, int], optional): The size of the output image after transformation. Defaults to [72, 96]. 167 168 Returns: 169 numpy.ndarray: Transformed coordinates in original image space. 170 """ 171 target_coords = np.zeros(coords.shape) 172 trans = get_affine_transform(center, scale, 0, output_size, inv=1) 173 for p in range(coords.shape[0]): 174 target_coords[p, 0:2] = affine_transform(coords[p, 0:2], trans) 175 return target_coords 176 177 178def get_affine_transform( 179 center, scale, rot, output_size, shift=np.array([0, 0], dtype=np.float32), inv=0 180): 181 """ 182 Calculates the 2x3 affine transformation matrix for image cropping and resizing. 183 184 Args: 185 center (numpy.ndarray): Center of the original image or region of interest. 186 scale (numpy.ndarray): Scale factor for the transformation. 187 rot (float): Rotation angle in degrees. 188 output_size (list): Target output size [width, height]. 189 shift (numpy.ndarray, optional): Shift applied to the center. Defaults to [0, 0]. 190 inv (int, optional): If 1, returns the inverse transformation matrix. Defaults to 0. 191 192 Returns: 193 numpy.ndarray: The 2x3 affine transformation matrix. 194 """ 195 if not isinstance(scale, np.ndarray) and not isinstance(scale, list): 196 print(scale) 197 scale = np.array([scale, scale]) 198 199 scale_tmp = scale * 200.0 200 src_w = scale_tmp[0] 201 dst_w = output_size[0] 202 dst_h = output_size[1] 203 204 rot_rad = np.pi * rot / 180 205 src_dir = get_dir([0, src_w * -0.5], rot_rad) 206 dst_dir = np.array([0, dst_w * -0.5], np.float32) 207 208 src = np.zeros((3, 2), dtype=np.float32) 209 dst = np.zeros((3, 2), dtype=np.float32) 210 src[0, :] = center + scale_tmp * shift 211 src[1, :] = center + src_dir + scale_tmp * shift 212 dst[0, :] = [dst_w * 0.5, dst_h * 0.5] 213 dst[1, :] = np.array([dst_w * 0.5, dst_h * 0.5]) + dst_dir 214 215 src[2:, :] = get_3rd_point(src[0, :], src[1, :]) 216 dst[2:, :] = get_3rd_point(dst[0, :], dst[1, :]) 217 218 if inv: 219 trans = cv2.getAffineTransform(np.float32(dst), np.float32(src)) 220 else: 221 trans = cv2.getAffineTransform(np.float32(src), np.float32(dst)) 222 223 return trans 224 225 226def affine_transform(pt, t): 227 """ 228 Applies an affine transformation matrix to a 2D point. 229 230 Args: 231 pt (tuple or list): The 2D point (x, y) to transform. 232 t (numpy.ndarray): The 2x3 affine transformation matrix. 233 234 Returns: 235 numpy.ndarray: The transformed 2D point. 236 """ 237 new_pt = np.array([pt[0], pt[1], 1.0]).T 238 new_pt = np.dot(t, new_pt) 239 return new_pt[:2] 240 241 242def get_3rd_point(a, b): 243 """ 244 Calculates a third point to form a right-angled triangle with two given points. 245 Used for creating a 3-point basis for affine transformations. 246 247 Args: 248 a (numpy.ndarray): First point. 249 b (numpy.ndarray): Second point. 250 251 Returns: 252 numpy.ndarray: The calculated third point. 253 """ 254 direct = a - b 255 return b + np.array([-direct[1], direct[0]], dtype=np.float32) 256 257 258def get_dir(src_point, rot_rad): 259 """ 260 Calculates the direction vector after rotation. 261 262 Args: 263 src_point (list): Source point [x, y]. 264 rot_rad (float): Rotation angle in radians. 265 266 Returns: 267 list: The rotated direction vector. 268 """ 269 sn, cs = np.sin(rot_rad), np.cos(rot_rad) 270 271 src_result = [0, 0] 272 src_result[0] = src_point[0] * cs - src_point[1] * sn 273 src_result[1] = src_point[0] * sn + src_point[1] * cs 274 275 return src_result 276 277 278def crop(img, center, scale, output_size, rot=0): 279 """ 280 Crops and resizes an image using an affine transformation. 281 282 Args: 283 img (numpy.ndarray): The input image. 284 center (numpy.ndarray): The center of the crop region. 285 scale (numpy.ndarray): The scale factor for the crop. 286 output_size (tuple): The target output size (width, height). 287 rot (int, optional): Rotation angle in degrees. Defaults to 0. 288 289 Returns: 290 numpy.ndarray: The cropped and transformed image. 291 """ 292 trans = get_affine_transform(center, scale, rot, output_size) 293 294 dst_img = cv2.warpAffine( 295 img, trans, (int(output_size[0]), int(output_size[1])), flags=cv2.INTER_LINEAR 296 ) 297 298 return dst_img 299 300 301def input_image_transform( 302 img_input: Union[str, np.ndarray], 303 scale_std: float = 200.0, 304 resize_dim: list[int] = [288, 384], 305 normalize_mean: list[float] = [0.485, 0.456, 0.406], 306 normalize_std: list[float] = [0.229, 0.224, 0.225], 307): 308 """ 309 Preprocesses an input image for landmark detection. 310 311 This function takes an image (either path or NumPy array), applies an affine 312 transformation (scaling, centering), resizes it, converts it to a PyTorch tensor, 313 and normalizes it. 314 315 Args: 316 img_input (Union[str, np.ndarray]): Path to the image file or a NumPy array of the image. 317 scale_std (float, optional): Standard scale for image transformation. Defaults to 200.0. 318 resize_dim (list[int], optional): Target dimensions [width, height] for the transformed image. 319 Defaults to [288, 384]. 320 normalize_mean (list[float], optional): Mean values for image normalization (RGB channels). 321 Defaults to [0.485, 0.456, 0.406]. 322 normalize_std (list[float], optional): Standard deviation values for image normalization (RGB channels). 323 Defaults to [0.229, 0.224, 0.225]. 324 325 Raises: 326 ValueError: If `img_input` is neither a file path nor a NumPy array. 327 328 Returns: 329 tuple: 330 - input_tensor (torch.Tensor): The preprocessed image as a PyTorch tensor, ready for model input. 331 - image_np (numpy.ndarray): The original image as a NumPy array (RGB). 332 - center (numpy.ndarray): The center of the original image used for transformation. 333 - scale (numpy.ndarray): The scale factor used for transformation. 334 """ 335 if isinstance(img_input, str): 336 img = Image.open(img_input).convert("RGB") 337 elif isinstance(img_input, np.ndarray): 338 img = Image.fromarray(img_input.astype(np.uint8)) 339 else: 340 raise ValueError("img_input must be a file path or a NumPy array.") 341 342 image_np = np.array(img) 343 344 h, w = image_np.shape[:2] 345 center = np.array([w / 2, h / 2], dtype=np.float32) 346 scale = np.array([w / scale_std, h / scale_std], dtype=np.float32) 347 image_size = np.array(resize_dim) 348 rotation = 0 349 350 trans = get_affine_transform(center, scale, rotation, image_size) 351 warped_image = cv2.warpAffine( 352 image_np, 353 trans, 354 (int(image_size[0]), int(image_size[1])), 355 flags=cv2.INTER_LINEAR, 356 ) 357 358 to_tensor = transforms.ToTensor() 359 normalize = transforms.Normalize(normalize_mean, normalize_std) 360 input_tensor = normalize(to_tensor(warped_image)).unsqueeze(0) 361 362 return input_tensor, image_np, center, scale
16def get_max_preds(batch_heatmaps): 17 """ 18 get predictions from score maps 19 heatmaps: numpy.ndarray([batch_size, num_joints, height, width]) 20 21 Args: 22 batch_heatmaps (numpy.ndarray): Heatmaps generated by the model. 23 Shape: [batch_size, num_joints, height, width]. 24 25 Returns: 26 tuple: 27 - preds (numpy.ndarray): Predicted coordinates. 28 - maxvals (numpy.ndarray): Maximum values (confidence scores) for each prediction. 29 """ 30 assert isinstance( 31 batch_heatmaps, np.ndarray 32 ), "batch_heatmaps should be numpy.ndarray" 33 assert batch_heatmaps.ndim == 4, "batch_images should be 4-ndim" 34 35 batch_size = batch_heatmaps.shape[0] 36 num_joints = batch_heatmaps.shape[1] 37 width = batch_heatmaps.shape[3] 38 heatmaps_reshaped = batch_heatmaps.reshape((batch_size, num_joints, -1)) 39 idx = np.argmax(heatmaps_reshaped, 2) 40 maxvals = np.amax(heatmaps_reshaped, 2) 41 42 maxvals = maxvals.reshape((batch_size, num_joints, 1)) 43 idx = idx.reshape((batch_size, num_joints, 1)) 44 45 preds = np.tile(idx, (1, 1, 2)).astype(np.float32) 46 47 preds[:, :, 0] = (preds[:, :, 0]) % width 48 preds[:, :, 1] = np.floor((preds[:, :, 1]) / width) 49 50 pred_mask = np.tile(np.greater(maxvals, 0.0), (1, 1, 2)) 51 pred_mask = pred_mask.astype(np.float32) 52 53 preds *= pred_mask 54 return preds, maxvals
get predictions from score maps heatmaps: numpy.ndarray([batch_size, num_joints, height, width])
Arguments:
- batch_heatmaps (numpy.ndarray): Heatmaps generated by the model. Shape: [batch_size, num_joints, height, width].
Returns:
tuple: - preds (numpy.ndarray): Predicted coordinates. - maxvals (numpy.ndarray): Maximum values (confidence scores) for each prediction.
57def get_final_preds(output, height=96, width=72): 58 """ 59 Transforms raw heatmap outputs into final landmark coordinates. 60 61 Applies post-processing (e.g., quarter offset for sub-pixel accuracy) 62 to refine the landmark predictions from heatmaps. 63 64 Args: 65 output (numpy.ndarray): Raw heatmap output from the model. 66 height (int): Height of the heatmap. Defaults to 96. 67 width (int): Width of the heatmap. Defaults to 72. 68 69 Returns: 70 tuple: 71 - coords (numpy.ndarray): Final predicted coordinates. 72 - maxvals (numpy.ndarray): Confidence scores for the predictions. 73 """ 74 heatmap_height = height 75 heatmap_width = width 76 77 batch_heatmaps = output 78 coords, maxvals = get_max_preds(batch_heatmaps) 79 # post-processing 80 for n in range(coords.shape[0]): 81 for p in range(coords.shape[1]): 82 hm = batch_heatmaps[n][p] 83 px = int(math.floor(coords[n][p][0] + 0.5)) 84 py = int(math.floor(coords[n][p][1] + 0.5)) 85 if 1 < px < heatmap_width - 1 and 1 < py < heatmap_height - 1: 86 diff = np.array( 87 [hm[py][px + 1] - hm[py][px - 1], hm[py + 1][px] - hm[py - 1][px]] 88 ) 89 coords[n][p] += np.sign(diff) * 0.25 90 91 return coords, maxvals
Transforms raw heatmap outputs into final landmark coordinates.
Applies post-processing (e.g., quarter offset for sub-pixel accuracy) to refine the landmark predictions from heatmaps.
Arguments:
- output (numpy.ndarray): Raw heatmap output from the model.
- height (int): Height of the heatmap. Defaults to 96.
- width (int): Width of the heatmap. Defaults to 72.
Returns:
tuple: - coords (numpy.ndarray): Final predicted coordinates. - maxvals (numpy.ndarray): Confidence scores for the predictions.
94def flip_back(output_flipped, matched_parts, heatmap_wid): 95 """ 96 Flips the output (coordinates or heatmaps) horizontally for test-time augmentation. 97 98 Args: 99 output_flipped (numpy.ndarray): The output (heatmaps or coordinates) that has been flipped. 100 matched_parts (list): A list of tuples indicating which joint pairs are left-right symmetric. 101 heatmap_wid (int): The width of the heatmap (used for coordinate flipping). 102 103 Returns: 104 numpy.ndarray: The flipped output with joints correctly reordered. 105 """ 106 if output_flipped.ndim == 4: 107 output_flipped = output_flipped[:, :, :, ::-1] 108 for pair in matched_parts: 109 tmp = output_flipped[:, pair[0], :, :].copy() 110 output_flipped[:, pair[0], :, :] = output_flipped[:, pair[1], :, :] 111 output_flipped[:, pair[1], :, :] = tmp 112 elif output_flipped.ndim == 3: 113 output_flipped[:, :, 0] = heatmap_wid - output_flipped[:, :, 0] 114 for pair in matched_parts: 115 tmp = output_flipped[:, pair[0], :].copy() 116 output_flipped[:, pair[0], :] = output_flipped[:, pair[1], :] 117 output_flipped[:, pair[1], :] = tmp 118 else: 119 raise NotImplementedError( 120 "output_flipped should be [batch_size, num_joints, height, width], " 121 "or [batch_size, num_joints, coord_dim" 122 ) 123 124 return output_flipped
Flips the output (coordinates or heatmaps) horizontally for test-time augmentation.
Arguments:
- output_flipped (numpy.ndarray): The output (heatmaps or coordinates) that has been flipped.
- matched_parts (list): A list of tuples indicating which joint pairs are left-right symmetric.
- heatmap_wid (int): The width of the heatmap (used for coordinate flipping).
Returns:
numpy.ndarray: The flipped output with joints correctly reordered.
127def fliplr_joints(joints, joints_vis, width, matched_parts): 128 """ 129 Flips joint coordinates horizontally and reorders them based on matched parts. 130 131 Args: 132 joints (numpy.ndarray): Array of joint coordinates. 133 joints_vis (numpy.ndarray): Array indicating visibility of joints. 134 width (int): Width of the image or feature map. 135 matched_parts (list): A list of tuples indicating which joint pairs are left-right symmetric. 136 137 Returns: 138 tuple: 139 - joints (numpy.ndarray): Flipped and reordered joint coordinates. 140 - joints_vis (numpy.ndarray): Corresponding joint visibility. 141 """ 142 # Flip horizontal 143 joints[:, 0] = width - joints[:, 0] - 1 144 145 # Change left-right parts 146 for pair in matched_parts: 147 joints[pair[0], :], joints[pair[1], :] = ( 148 joints[pair[1], :], 149 joints[pair[0], :].copy(), 150 ) 151 joints_vis[pair[0], :], joints_vis[pair[1], :] = ( 152 joints_vis[pair[1], :], 153 joints_vis[pair[0], :].copy(), 154 ) 155 156 return joints * joints_vis, joints_vis
Flips joint coordinates horizontally and reorders them based on matched parts.
Arguments:
- joints (numpy.ndarray): Array of joint coordinates.
- joints_vis (numpy.ndarray): Array indicating visibility of joints.
- width (int): Width of the image or feature map.
- matched_parts (list): A list of tuples indicating which joint pairs are left-right symmetric.
Returns:
tuple: - joints (numpy.ndarray): Flipped and reordered joint coordinates. - joints_vis (numpy.ndarray): Corresponding joint visibility.
159def transform_preds(coords, center, scale, output_size: list[int, int] = [72, 96]): 160 """ 161 Transforms predicted coordinates from heatmap space back to original image space. 162 163 Args: 164 coords (numpy.ndarray): Predicted coordinates in heatmap space. 165 center (numpy.ndarray): Center of the original image (or cropped region). 166 scale (numpy.ndarray): Scale factor applied during preprocessing. 167 output_size (list[int, int], optional): The size of the output image after transformation. Defaults to [72, 96]. 168 169 Returns: 170 numpy.ndarray: Transformed coordinates in original image space. 171 """ 172 target_coords = np.zeros(coords.shape) 173 trans = get_affine_transform(center, scale, 0, output_size, inv=1) 174 for p in range(coords.shape[0]): 175 target_coords[p, 0:2] = affine_transform(coords[p, 0:2], trans) 176 return target_coords
Transforms predicted coordinates from heatmap space back to original image space.
Arguments:
- coords (numpy.ndarray): Predicted coordinates in heatmap space.
- center (numpy.ndarray): Center of the original image (or cropped region).
- scale (numpy.ndarray): Scale factor applied during preprocessing.
- output_size (list[int, int], optional): The size of the output image after transformation. Defaults to [72, 96].
Returns:
numpy.ndarray: Transformed coordinates in original image space.
179def get_affine_transform( 180 center, scale, rot, output_size, shift=np.array([0, 0], dtype=np.float32), inv=0 181): 182 """ 183 Calculates the 2x3 affine transformation matrix for image cropping and resizing. 184 185 Args: 186 center (numpy.ndarray): Center of the original image or region of interest. 187 scale (numpy.ndarray): Scale factor for the transformation. 188 rot (float): Rotation angle in degrees. 189 output_size (list): Target output size [width, height]. 190 shift (numpy.ndarray, optional): Shift applied to the center. Defaults to [0, 0]. 191 inv (int, optional): If 1, returns the inverse transformation matrix. Defaults to 0. 192 193 Returns: 194 numpy.ndarray: The 2x3 affine transformation matrix. 195 """ 196 if not isinstance(scale, np.ndarray) and not isinstance(scale, list): 197 print(scale) 198 scale = np.array([scale, scale]) 199 200 scale_tmp = scale * 200.0 201 src_w = scale_tmp[0] 202 dst_w = output_size[0] 203 dst_h = output_size[1] 204 205 rot_rad = np.pi * rot / 180 206 src_dir = get_dir([0, src_w * -0.5], rot_rad) 207 dst_dir = np.array([0, dst_w * -0.5], np.float32) 208 209 src = np.zeros((3, 2), dtype=np.float32) 210 dst = np.zeros((3, 2), dtype=np.float32) 211 src[0, :] = center + scale_tmp * shift 212 src[1, :] = center + src_dir + scale_tmp * shift 213 dst[0, :] = [dst_w * 0.5, dst_h * 0.5] 214 dst[1, :] = np.array([dst_w * 0.5, dst_h * 0.5]) + dst_dir 215 216 src[2:, :] = get_3rd_point(src[0, :], src[1, :]) 217 dst[2:, :] = get_3rd_point(dst[0, :], dst[1, :]) 218 219 if inv: 220 trans = cv2.getAffineTransform(np.float32(dst), np.float32(src)) 221 else: 222 trans = cv2.getAffineTransform(np.float32(src), np.float32(dst)) 223 224 return trans
Calculates the 2x3 affine transformation matrix for image cropping and resizing.
Arguments:
- center (numpy.ndarray): Center of the original image or region of interest.
- scale (numpy.ndarray): Scale factor for the transformation.
- rot (float): Rotation angle in degrees.
- output_size (list): Target output size [width, height].
- shift (numpy.ndarray, optional): Shift applied to the center. Defaults to [0, 0].
- inv (int, optional): If 1, returns the inverse transformation matrix. Defaults to 0.
Returns:
numpy.ndarray: The 2x3 affine transformation matrix.
227def affine_transform(pt, t): 228 """ 229 Applies an affine transformation matrix to a 2D point. 230 231 Args: 232 pt (tuple or list): The 2D point (x, y) to transform. 233 t (numpy.ndarray): The 2x3 affine transformation matrix. 234 235 Returns: 236 numpy.ndarray: The transformed 2D point. 237 """ 238 new_pt = np.array([pt[0], pt[1], 1.0]).T 239 new_pt = np.dot(t, new_pt) 240 return new_pt[:2]
Applies an affine transformation matrix to a 2D point.
Arguments:
- pt (tuple or list): The 2D point (x, y) to transform.
- t (numpy.ndarray): The 2x3 affine transformation matrix.
Returns:
numpy.ndarray: The transformed 2D point.
243def get_3rd_point(a, b): 244 """ 245 Calculates a third point to form a right-angled triangle with two given points. 246 Used for creating a 3-point basis for affine transformations. 247 248 Args: 249 a (numpy.ndarray): First point. 250 b (numpy.ndarray): Second point. 251 252 Returns: 253 numpy.ndarray: The calculated third point. 254 """ 255 direct = a - b 256 return b + np.array([-direct[1], direct[0]], dtype=np.float32)
Calculates a third point to form a right-angled triangle with two given points. Used for creating a 3-point basis for affine transformations.
Arguments:
- a (numpy.ndarray): First point.
- b (numpy.ndarray): Second point.
Returns:
numpy.ndarray: The calculated third point.
259def get_dir(src_point, rot_rad): 260 """ 261 Calculates the direction vector after rotation. 262 263 Args: 264 src_point (list): Source point [x, y]. 265 rot_rad (float): Rotation angle in radians. 266 267 Returns: 268 list: The rotated direction vector. 269 """ 270 sn, cs = np.sin(rot_rad), np.cos(rot_rad) 271 272 src_result = [0, 0] 273 src_result[0] = src_point[0] * cs - src_point[1] * sn 274 src_result[1] = src_point[0] * sn + src_point[1] * cs 275 276 return src_result
Calculates the direction vector after rotation.
Arguments:
- src_point (list): Source point [x, y].
- rot_rad (float): Rotation angle in radians.
Returns:
list: The rotated direction vector.
279def crop(img, center, scale, output_size, rot=0): 280 """ 281 Crops and resizes an image using an affine transformation. 282 283 Args: 284 img (numpy.ndarray): The input image. 285 center (numpy.ndarray): The center of the crop region. 286 scale (numpy.ndarray): The scale factor for the crop. 287 output_size (tuple): The target output size (width, height). 288 rot (int, optional): Rotation angle in degrees. Defaults to 0. 289 290 Returns: 291 numpy.ndarray: The cropped and transformed image. 292 """ 293 trans = get_affine_transform(center, scale, rot, output_size) 294 295 dst_img = cv2.warpAffine( 296 img, trans, (int(output_size[0]), int(output_size[1])), flags=cv2.INTER_LINEAR 297 ) 298 299 return dst_img
Crops and resizes an image using an affine transformation.
Arguments:
- img (numpy.ndarray): The input image.
- center (numpy.ndarray): The center of the crop region.
- scale (numpy.ndarray): The scale factor for the crop.
- output_size (tuple): The target output size (width, height).
- rot (int, optional): Rotation angle in degrees. Defaults to 0.
Returns:
numpy.ndarray: The cropped and transformed image.
302def input_image_transform( 303 img_input: Union[str, np.ndarray], 304 scale_std: float = 200.0, 305 resize_dim: list[int] = [288, 384], 306 normalize_mean: list[float] = [0.485, 0.456, 0.406], 307 normalize_std: list[float] = [0.229, 0.224, 0.225], 308): 309 """ 310 Preprocesses an input image for landmark detection. 311 312 This function takes an image (either path or NumPy array), applies an affine 313 transformation (scaling, centering), resizes it, converts it to a PyTorch tensor, 314 and normalizes it. 315 316 Args: 317 img_input (Union[str, np.ndarray]): Path to the image file or a NumPy array of the image. 318 scale_std (float, optional): Standard scale for image transformation. Defaults to 200.0. 319 resize_dim (list[int], optional): Target dimensions [width, height] for the transformed image. 320 Defaults to [288, 384]. 321 normalize_mean (list[float], optional): Mean values for image normalization (RGB channels). 322 Defaults to [0.485, 0.456, 0.406]. 323 normalize_std (list[float], optional): Standard deviation values for image normalization (RGB channels). 324 Defaults to [0.229, 0.224, 0.225]. 325 326 Raises: 327 ValueError: If `img_input` is neither a file path nor a NumPy array. 328 329 Returns: 330 tuple: 331 - input_tensor (torch.Tensor): The preprocessed image as a PyTorch tensor, ready for model input. 332 - image_np (numpy.ndarray): The original image as a NumPy array (RGB). 333 - center (numpy.ndarray): The center of the original image used for transformation. 334 - scale (numpy.ndarray): The scale factor used for transformation. 335 """ 336 if isinstance(img_input, str): 337 img = Image.open(img_input).convert("RGB") 338 elif isinstance(img_input, np.ndarray): 339 img = Image.fromarray(img_input.astype(np.uint8)) 340 else: 341 raise ValueError("img_input must be a file path or a NumPy array.") 342 343 image_np = np.array(img) 344 345 h, w = image_np.shape[:2] 346 center = np.array([w / 2, h / 2], dtype=np.float32) 347 scale = np.array([w / scale_std, h / scale_std], dtype=np.float32) 348 image_size = np.array(resize_dim) 349 rotation = 0 350 351 trans = get_affine_transform(center, scale, rotation, image_size) 352 warped_image = cv2.warpAffine( 353 image_np, 354 trans, 355 (int(image_size[0]), int(image_size[1])), 356 flags=cv2.INTER_LINEAR, 357 ) 358 359 to_tensor = transforms.ToTensor() 360 normalize = transforms.Normalize(normalize_mean, normalize_std) 361 input_tensor = normalize(to_tensor(warped_image)).unsqueeze(0) 362 363 return input_tensor, image_np, center, scale
Preprocesses an input image for landmark detection.
This function takes an image (either path or NumPy array), applies an affine transformation (scaling, centering), resizes it, converts it to a PyTorch tensor, and normalizes it.
Arguments:
- img_input (Union[str, np.ndarray]): Path to the image file or a NumPy array of the image.
- scale_std (float, optional): Standard scale for image transformation. Defaults to 200.0.
- resize_dim (list[int], optional): Target dimensions [width, height] for the transformed image. Defaults to [288, 384].
- normalize_mean (list[float], optional): Mean values for image normalization (RGB channels). Defaults to [0.485, 0.456, 0.406].
- normalize_std (list[float], optional): Standard deviation values for image normalization (RGB channels). Defaults to [0.229, 0.224, 0.225].
Raises:
- ValueError: If
img_inputis neither a file path nor a NumPy array.
Returns:
tuple: - input_tensor (torch.Tensor): The preprocessed image as a PyTorch tensor, ready for model input. - image_np (numpy.ndarray): The original image as a NumPy array (RGB). - center (numpy.ndarray): The center of the original image used for transformation. - scale (numpy.ndarray): The scale factor used for transformation.