# Copyright (c) 2018-present, Baidu, Inc. # # 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. ############################################################################## """Utility functions.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function import math import distutils.util import numpy as np import cv2 from pathlib import Path def print_arguments(args): """Print argparse's arguments. Usage: .. code-block:: python parser = argparse.ArgumentParser() parser.add_argument("name", default="Jonh", type=str, help="User name.") args = parser.parse_args() print_arguments(args) :param args: Input argparse.Namespace for printing. :type args: argparse.Namespace """ print("----------- Configuration Arguments -----------") for arg, value in sorted(vars(args).iteritems()): print("%s: %s" % (arg, value)) print("------------------------------------------------") def add_arguments(argname, type, default, help, argparser, **kwargs): """Add argparse's argument. Usage: .. code-block:: python parser = argparse.ArgumentParser() add_argument("name", str, "Jonh", "User name.", parser) args = parser.parse_args() """ type = distutils.util.strtobool if type == bool else type argparser.add_argument( "--" + argname, default=default, type=type, help=help + ' Default: %(default)s.', **kwargs) def get_max_preds(batch_heatmaps): """Get predictions from score maps. heatmaps: numpy.ndarray([batch_size, num_joints, height, width]) """ assert isinstance(batch_heatmaps, np.ndarray), \ 'batch_heatmaps should be numpy.ndarray' assert batch_heatmaps.ndim == 4, 'batch_images should be 4-ndim' batch_size = batch_heatmaps.shape[0] num_joints = batch_heatmaps.shape[1] width = batch_heatmaps.shape[3] heatmaps_reshaped = batch_heatmaps.reshape((batch_size, num_joints, -1)) idx = np.argmax(heatmaps_reshaped, 2) maxvals = np.amax(heatmaps_reshaped, 2) maxvals = maxvals.reshape((batch_size, num_joints, 1)) idx = idx.reshape((batch_size, num_joints, 1)) preds = np.tile(idx, (1, 1, 2)).astype(np.float32) preds[:, :, 0] = (preds[:, :, 0]) % width preds[:, :, 1] = np.floor((preds[:, :, 1]) / width) pred_mask = np.tile(np.greater(maxvals, 0.0), (1, 1, 2)) pred_mask = pred_mask.astype(np.float32) preds *= pred_mask return preds, maxvals def affine_transform(pt, t): new_pt = np.array([pt[0], pt[1], 1.]).T new_pt = np.dot(t, new_pt) return new_pt[:2] def get_3rd_point(a, b): direct = a - b return b + np.array([-direct[1], direct[0]], dtype=np.float32) def get_dir(src_point, rot_rad): sn, cs = np.sin(rot_rad), np.cos(rot_rad) src_result = [0, 0] src_result[0] = src_point[0] * cs - src_point[1] * sn src_result[1] = src_point[0] * sn + src_point[1] * cs return src_result def crop(img, center, scale, output_size, rot=0): trans = get_affine_transform(center, scale, rot, output_size) dst_img = cv2.warpAffine(img, trans, (int(output_size[0]), int(output_size[1])), flags=cv2.INTER_LINEAR) return dst_img def get_affine_transform(center, scale, rot, output_size, shift=np.array([0, 0], dtype=np.float32), inv=0): if not isinstance(scale, np.ndarray) and not isinstance(scale, list): print(scale) scale = np.array([scale, scale]) scale_tmp = scale * 200.0 src_w = scale_tmp[0] dst_w = output_size[0] dst_h = output_size[1] rot_rad = np.pi * rot / 180 src_dir = get_dir([0, src_w * -0.5], rot_rad) dst_dir = np.array([0, dst_w * -0.5], np.float32) src = np.zeros((3, 2), dtype=np.float32) dst = np.zeros((3, 2), dtype=np.float32) src[0, :] = center + scale_tmp * shift src[1, :] = center + src_dir + scale_tmp * shift dst[0, :] = [dst_w * 0.5, dst_h * 0.5] dst[1, :] = np.array([dst_w * 0.5, dst_h * 0.5]) + dst_dir src[2:, :] = get_3rd_point(src[0, :], src[1, :]) dst[2:, :] = get_3rd_point(dst[0, :], dst[1, :]) if inv: trans = cv2.getAffineTransform(np.float32(dst), np.float32(src)) else: trans = cv2.getAffineTransform(np.float32(src), np.float32(dst)) return trans def transform_preds(coords, center, scale, output_size): target_coords = np.zeros(coords.shape) trans = get_affine_transform(center, scale, 0, output_size, inv=1) for p in range(coords.shape[0]): target_coords[p, 0:2] = affine_transform(coords[p, 0:2], trans) return target_coords def get_final_preds(args, batch_heatmaps, center, scale): coords, maxvals = get_max_preds(batch_heatmaps) heatmap_height = batch_heatmaps.shape[2] heatmap_width = batch_heatmaps.shape[3] # Post-processing if args.post_process: for n in range(coords.shape[0]): for p in range(coords.shape[1]): hm = batch_heatmaps[n][p] px = int(math.floor(coords[n][p][0] + 0.5)) py = int(math.floor(coords[n][p][1] + 0.5)) if 1 < px < heatmap_width-1 and 1 < py < heatmap_height-1: diff = np.array([hm[py][px+1] - hm[py][px-1], hm[py+1][px]-hm[py-1][px]]) coords[n][p] += np.sign(diff) * .25 preds = coords.copy() # Transform back for i in range(coords.shape[0]): preds[i] = transform_preds(coords[i], center[i], scale[i], [heatmap_width, heatmap_height]) return preds, maxvals def calc_dists(preds, target, normalize): preds = preds.astype(np.float32) target = target.astype(np.float32) dists = np.zeros((preds.shape[1], preds.shape[0])) for n in range(preds.shape[0]): for c in range(preds.shape[1]): if target[n, c, 0] > 1 and target[n, c, 1] > 1: normed_preds = preds[n, c, :] / normalize[n] normed_targets = target[n, c, :] / normalize[n] dists[c, n] = np.linalg.norm(normed_preds - normed_targets) else: dists[c, n] = -1 return dists def dist_acc(dists, thr=0.5): """Return percentage below threshold while ignoring values with a -1. """ dist_cal = np.not_equal(dists, -1) num_dist_cal = dist_cal.sum() if num_dist_cal > 0: return np.less(dists[dist_cal], thr).sum() * 1.0 / num_dist_cal else: return -1 def accuracy(output, target, hm_type='gaussian', thr=0.5): """ Calculate accuracy according to PCK, but uses ground truth heatmap rather than x,y locations First value to be returned is average accuracy across 'idxs', followed by individual accuracies """ idx = list(range(output.shape[1])) norm = 1.0 if hm_type == 'gaussian': pred, _ = get_max_preds(output) target, _ = get_max_preds(target) h = output.shape[2] w = output.shape[3] norm = np.ones((pred.shape[0], 2)) * np.array([h, w]) / 10 dists = calc_dists(pred, target, norm) acc = np.zeros((len(idx) + 1)) avg_acc = 0 cnt = 0 for i in range(len(idx)): acc[i + 1] = dist_acc(dists[idx[i]]) if acc[i + 1] >= 0: avg_acc = avg_acc + acc[i + 1] cnt += 1 avg_acc = avg_acc / cnt if cnt != 0 else 0 if cnt != 0: acc[0] = avg_acc return acc, avg_acc, cnt, pred def save_batch_heatmaps(batch_image, batch_heatmaps, file_name, normalize=True): """ :param batch_image: [batch_size, channel, height, width] :param batch_heatmaps: ['batch_size, num_joints, height, width] :param file_name: saved file name """ if normalize: min = np.array(batch_image.min(), dtype=np.float) max = np.array(batch_image.max(), dtype=np.float) batch_image = np.add(batch_image, -min) batch_image = np.divide(batch_image, max - min + 1e-5) batch_size, num_joints, \ heatmap_height, heatmap_width = batch_heatmaps.shape grid_image = np.zeros((batch_size*heatmap_height, (num_joints+1)*heatmap_width, 3), dtype=np.uint8) preds, maxvals = get_max_preds(batch_heatmaps) for i in range(batch_size): image = batch_image[i] * 255 image = image.clip(0, 255).astype(np.uint8) image = image.transpose(1, 2, 0) heatmaps = batch_heatmaps[i] * 255 heatmaps = heatmaps.clip(0, 255).astype(np.uint8) resized_image = cv2.resize(image, (int(heatmap_width), int(heatmap_height))) height_begin = heatmap_height * i height_end = heatmap_height * (i + 1) for j in range(num_joints): cv2.circle(resized_image, (int(preds[i][j][0]), int(preds[i][j][1])), 1, [0, 0, 255], 1) heatmap = heatmaps[j, :, :] colored_heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET) masked_image = colored_heatmap*0.7 + resized_image*0.3 cv2.circle(masked_image, (int(preds[i][j][0]), int(preds[i][j][1])), 1, [0, 0, 255], 1) width_begin = heatmap_width * (j+1) width_end = heatmap_width * (j+2) grid_image[height_begin:height_end, width_begin:width_end, :] = \ masked_image grid_image[height_begin:height_end, 0:heatmap_width, :] = resized_image cv2.imwrite(file_name, grid_image) def save_predict_results(batch_image, batch_heatmaps, file_ids, fold_name, normalize=True): """ :param batch_image: [batch_size, channel, height, width] :param batch_heatmaps: ['batch_size, num_joints, height, width] :param fold_name: saved files in this folder """ save_dir = Path('./{}'.format(fold_name)) try: save_dir.mkdir() except OSError: pass if normalize: min = np.array(batch_image.min(), dtype=np.float) max = np.array(batch_image.max(), dtype=np.float) batch_image = np.add(batch_image, -min) batch_image = np.divide(batch_image, max - min + 1e-5) batch_size, num_joints, \ heatmap_height, heatmap_width = batch_heatmaps.shape # (32, 16, 2), (32, 16, 1)) preds, maxvals = get_max_preds(batch_heatmaps) # Blue icolor = (255, 137, 0) ocolor = (138, 255, 0) for i in range(batch_size): image = batch_image[i] * 255 image = image.clip(0, 255).astype(np.uint8) image = image.transpose(1, 2, 0) image = cv2.resize(image, (384, 384)) file_id = file_ids[i] imgname = save_dir.joinpath('rendered_{}.png'.format(str(file_id).zfill(7))) for j in range(num_joints): x, y = preds[i][j] cv2.circle(image, (int(x * 4), int(y * 4)), 3, icolor, -1, 16) cv2.circle(image, (int(x * 4), int(y * 4)), 6, ocolor, 1, 16) cv2.imwrite(str(imgname), image) # Clean format output def print_name_value(name_value, full_arch_name): names = name_value.keys() values = name_value.values() num_values = len(name_value) results = [] for value in values: results.append('| {:.3f}'.format(value)) print( '| Arch ' + ' '.join(['| {}'.format(name) for name in names]) + ' |' ) print('|---' * (num_values+1) + '|') print('| ' + 'SIMPLEBASE RESNET50 ' + ' '.join(results) + ' |') class AverageMeter(object): """Computes and stores the average and current value. """ def __init__(self): self.reset() def reset(self): self.val = 0 self.avg = 0 self.sum = 0 self.count = 0 def update(self, val, n=1): self.val = val self.sum += val * n self.count += n self.avg = self.sum / self.count if self.count != 0 else 0