pipe_utils.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. # Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import time
  15. import os
  16. import ast
  17. import argparse
  18. import glob
  19. import yaml
  20. import copy
  21. import numpy as np
  22. from python.keypoint_preprocess import EvalAffine, TopDownEvalAffine, expand_crop
  23. def argsparser():
  24. parser = argparse.ArgumentParser(description=__doc__)
  25. parser.add_argument(
  26. "--config",
  27. type=str,
  28. default=None,
  29. help=("Path of configure"),
  30. required=True)
  31. parser.add_argument(
  32. "--image_file", type=str, default=None, help="Path of image file.")
  33. parser.add_argument(
  34. "--image_dir",
  35. type=str,
  36. default=None,
  37. help="Dir of image file, `image_file` has a higher priority.")
  38. parser.add_argument(
  39. "--video_file",
  40. type=str,
  41. default=None,
  42. help="Path of video file, `video_file` or `camera_id` has a highest priority."
  43. )
  44. parser.add_argument(
  45. "--video_dir",
  46. type=str,
  47. default=None,
  48. help="Dir of video file, `video_file` has a higher priority.")
  49. parser.add_argument(
  50. "--model_dir", nargs='*', help="set model dir in pipeline")
  51. parser.add_argument(
  52. "--camera_id",
  53. type=int,
  54. default=-1,
  55. help="device id of camera to predict.")
  56. parser.add_argument(
  57. "--enable_attr",
  58. type=ast.literal_eval,
  59. default=False,
  60. help="Whether use attribute recognition.")
  61. parser.add_argument(
  62. "--enable_action",
  63. type=ast.literal_eval,
  64. default=False,
  65. help="Whether use action recognition.")
  66. parser.add_argument(
  67. "--output_dir",
  68. type=str,
  69. default="output",
  70. help="Directory of output visualization files.")
  71. parser.add_argument(
  72. "--run_mode",
  73. type=str,
  74. default='paddle',
  75. help="mode of running(paddle/trt_fp32/trt_fp16/trt_int8)")
  76. parser.add_argument(
  77. "--device",
  78. type=str,
  79. default='cpu',
  80. help="Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU."
  81. )
  82. parser.add_argument(
  83. "--enable_mkldnn",
  84. type=ast.literal_eval,
  85. default=False,
  86. help="Whether use mkldnn with CPU.")
  87. parser.add_argument(
  88. "--cpu_threads", type=int, default=1, help="Num of threads with CPU.")
  89. parser.add_argument(
  90. "--trt_min_shape", type=int, default=1, help="min_shape for TensorRT.")
  91. parser.add_argument(
  92. "--trt_max_shape",
  93. type=int,
  94. default=1280,
  95. help="max_shape for TensorRT.")
  96. parser.add_argument(
  97. "--trt_opt_shape",
  98. type=int,
  99. default=640,
  100. help="opt_shape for TensorRT.")
  101. parser.add_argument(
  102. "--trt_calib_mode",
  103. type=bool,
  104. default=False,
  105. help="If the model is produced by TRT offline quantitative "
  106. "calibration, trt_calib_mode need to set True.")
  107. parser.add_argument(
  108. "--do_entrance_counting",
  109. action='store_true',
  110. help="Whether counting the numbers of identifiers entering "
  111. "or getting out from the entrance. Note that only support one-class"
  112. "counting, multi-class counting is coming soon.")
  113. parser.add_argument(
  114. "--secs_interval",
  115. type=int,
  116. default=2,
  117. help="The seconds interval to count after tracking")
  118. parser.add_argument(
  119. "--draw_center_traj",
  120. action='store_true',
  121. help="Whether drawing the trajectory of center")
  122. return parser
  123. class Times(object):
  124. def __init__(self):
  125. self.time = 0.
  126. # start time
  127. self.st = 0.
  128. # end time
  129. self.et = 0.
  130. def start(self):
  131. self.st = time.time()
  132. def end(self, repeats=1, accumulative=True):
  133. self.et = time.time()
  134. if accumulative:
  135. self.time += (self.et - self.st) / repeats
  136. else:
  137. self.time = (self.et - self.st) / repeats
  138. def reset(self):
  139. self.time = 0.
  140. self.st = 0.
  141. self.et = 0.
  142. def value(self):
  143. return round(self.time, 4)
  144. class PipeTimer(Times):
  145. def __init__(self):
  146. super(PipeTimer, self).__init__()
  147. self.total_time = Times()
  148. self.module_time = {
  149. 'det': Times(),
  150. 'mot': Times(),
  151. 'attr': Times(),
  152. 'kpt': Times(),
  153. 'action': Times(),
  154. 'reid': Times()
  155. }
  156. self.img_num = 0
  157. def get_total_time(self):
  158. total_time = self.total_time.value()
  159. total_time = round(total_time, 4)
  160. average_latency = total_time / max(1, self.img_num)
  161. qps = 0
  162. if total_time > 0:
  163. qps = 1 / average_latency
  164. return total_time, average_latency, qps
  165. def info(self):
  166. total_time, average_latency, qps = self.get_total_time()
  167. print("------------------ Inference Time Info ----------------------")
  168. print("total_time(ms): {}, img_num: {}".format(total_time * 1000,
  169. self.img_num))
  170. for k, v in self.module_time.items():
  171. v_time = round(v.value(), 4)
  172. if v_time > 0:
  173. print("{} time(ms): {}".format(k, v_time * 1000))
  174. print("average latency time(ms): {:.2f}, QPS: {:2f}".format(
  175. average_latency * 1000, qps))
  176. return qps
  177. def report(self, average=False):
  178. dic = {}
  179. dic['total'] = round(self.total_time.value() / max(1, self.img_num),
  180. 4) if average else self.total_time.value()
  181. dic['det'] = round(self.module_time['det'].value() /
  182. max(1, self.img_num),
  183. 4) if average else self.module_time['det'].value()
  184. dic['mot'] = round(self.module_time['mot'].value() /
  185. max(1, self.img_num),
  186. 4) if average else self.module_time['mot'].value()
  187. dic['attr'] = round(self.module_time['attr'].value() /
  188. max(1, self.img_num),
  189. 4) if average else self.module_time['attr'].value()
  190. dic['kpt'] = round(self.module_time['kpt'].value() /
  191. max(1, self.img_num),
  192. 4) if average else self.module_time['kpt'].value()
  193. dic['action'] = round(
  194. self.module_time['action'].value() / max(1, self.img_num),
  195. 4) if average else self.module_time['action'].value()
  196. dic['img_num'] = self.img_num
  197. return dic
  198. def merge_model_dir(args, model_dir):
  199. # set --model_dir DET=ppyoloe/ to overwrite the model_dir in config file
  200. task_set = ['DET', 'ATTR', 'MOT', 'KPT', 'ACTION', 'REID']
  201. if not model_dir:
  202. return args
  203. for md in model_dir:
  204. md = md.strip()
  205. k, v = md.split('=', 1)
  206. k_upper = k.upper()
  207. assert k_upper in task_set, 'Illegal type of task, expect task are: {}, but received {}'.format(
  208. task_set, k)
  209. args[k_upper].update({'model_dir': v})
  210. return args
  211. def merge_cfg(args):
  212. with open(args.config) as f:
  213. pred_config = yaml.safe_load(f)
  214. def merge(cfg, arg):
  215. merge_cfg = copy.deepcopy(cfg)
  216. for k, v in cfg.items():
  217. if k in arg:
  218. merge_cfg[k] = arg[k]
  219. else:
  220. if isinstance(v, dict):
  221. merge_cfg[k] = merge(v, arg)
  222. return merge_cfg
  223. args_dict = vars(args)
  224. model_dir = args_dict.pop('model_dir')
  225. pred_config = merge_model_dir(pred_config, model_dir)
  226. pred_config = merge(pred_config, args_dict)
  227. return pred_config
  228. def print_arguments(cfg):
  229. print('----------- Running Arguments -----------')
  230. buffer = yaml.dump(cfg)
  231. print(buffer)
  232. print('------------------------------------------')
  233. def get_test_images(infer_dir, infer_img):
  234. """
  235. Get image path list in TEST mode
  236. """
  237. assert infer_img is not None or infer_dir is not None, \
  238. "--infer_img or --infer_dir should be set"
  239. assert infer_img is None or os.path.isfile(infer_img), \
  240. "{} is not a file".format(infer_img)
  241. assert infer_dir is None or os.path.isdir(infer_dir), \
  242. "{} is not a directory".format(infer_dir)
  243. # infer_img has a higher priority
  244. if infer_img and os.path.isfile(infer_img):
  245. return [infer_img]
  246. images = set()
  247. infer_dir = os.path.abspath(infer_dir)
  248. assert os.path.isdir(infer_dir), \
  249. "infer_dir {} is not a directory".format(infer_dir)
  250. exts = ['jpg', 'jpeg', 'png', 'bmp']
  251. exts += [ext.upper() for ext in exts]
  252. for ext in exts:
  253. images.update(glob.glob('{}/*.{}'.format(infer_dir, ext)))
  254. images = list(images)
  255. assert len(images) > 0, "no image found in {}".format(infer_dir)
  256. print("Found {} inference images in total.".format(len(images)))
  257. return images
  258. def crop_image_with_det(batch_input, det_res, thresh=0.3):
  259. boxes = det_res['boxes']
  260. score = det_res['boxes'][:, 1]
  261. boxes_num = det_res['boxes_num']
  262. start_idx = 0
  263. crop_res = []
  264. for b_id, input in enumerate(batch_input):
  265. boxes_num_i = boxes_num[b_id]
  266. if boxes_num_i == 0:
  267. continue
  268. boxes_i = boxes[start_idx:start_idx + boxes_num_i, :]
  269. score_i = score[start_idx:start_idx + boxes_num_i]
  270. res = []
  271. for box, s in zip(boxes_i, score_i):
  272. if s > thresh:
  273. crop_image, new_box, ori_box = expand_crop(input, box)
  274. if crop_image is not None:
  275. res.append(crop_image)
  276. crop_res.append(res)
  277. return crop_res
  278. def normal_crop(image, rect):
  279. imgh, imgw, c = image.shape
  280. label, conf, xmin, ymin, xmax, ymax = [int(x) for x in rect.tolist()]
  281. org_rect = [xmin, ymin, xmax, ymax]
  282. if label != 0:
  283. return None, None, None
  284. xmin = max(0, xmin)
  285. ymin = max(0, ymin)
  286. xmax = min(imgw, xmax)
  287. ymax = min(imgh, ymax)
  288. return image[ymin:ymax, xmin:xmax, :], [xmin, ymin, xmax, ymax], org_rect
  289. def crop_image_with_mot(input, mot_res, expand=True):
  290. res = mot_res['boxes']
  291. crop_res = []
  292. new_bboxes = []
  293. ori_bboxes = []
  294. for box in res:
  295. if expand:
  296. crop_image, new_bbox, ori_bbox = expand_crop(input, box[1:])
  297. else:
  298. crop_image, new_bbox, ori_bbox = normal_crop(input, box[1:])
  299. if crop_image is not None:
  300. crop_res.append(crop_image)
  301. new_bboxes.append(new_bbox)
  302. ori_bboxes.append(ori_bbox)
  303. return crop_res, new_bboxes, ori_bboxes
  304. def parse_mot_res(input):
  305. mot_res = []
  306. boxes, scores, ids = input[0]
  307. for box, score, i in zip(boxes[0], scores[0], ids[0]):
  308. xmin, ymin, w, h = box
  309. res = [i, 0, score, xmin, ymin, xmin + w, ymin + h]
  310. mot_res.append(res)
  311. return {'boxes': np.array(mot_res)}
  312. def refine_keypoint_coordinary(kpts, bbox, coord_size):
  313. """
  314. This function is used to adjust coordinate values to a fixed scale.
  315. """
  316. tl = bbox[:, 0:2]
  317. wh = bbox[:, 2:] - tl
  318. tl = np.expand_dims(np.transpose(tl, (1, 0)), (2, 3))
  319. wh = np.expand_dims(np.transpose(wh, (1, 0)), (2, 3))
  320. target_w, target_h = coord_size
  321. res = (kpts - tl) / wh * np.expand_dims(
  322. np.array([[target_w], [target_h]]), (2, 3))
  323. return res
  324. def parse_mot_keypoint(input, coord_size):
  325. parsed_skeleton_with_mot = {}
  326. ids = []
  327. skeleton = []
  328. for tracker_id, kpt_seq in input:
  329. ids.append(tracker_id)
  330. kpts = np.array(kpt_seq.kpts, dtype=np.float32)[:, :, :2]
  331. kpts = np.expand_dims(np.transpose(kpts, [2, 0, 1]),
  332. -1) #T, K, C -> C, T, K, 1
  333. bbox = np.array(kpt_seq.bboxes, dtype=np.float32)
  334. skeleton.append(refine_keypoint_coordinary(kpts, bbox, coord_size))
  335. parsed_skeleton_with_mot["mot_id"] = ids
  336. parsed_skeleton_with_mot["skeleton"] = skeleton
  337. return parsed_skeleton_with_mot