Spaces:
Build error
Build error
Upload 62 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- app.py +72 -0
- app.yml +11 -0
- pipeline/__init__.py +13 -0
- pipeline/cfg_utils.py +224 -0
- pipeline/config/infer_cfg_pphuman.yml +63 -0
- pipeline/config/tracker_config.yml +28 -0
- pipeline/datacollector.py +132 -0
- pipeline/download.py +340 -0
- pipeline/pipe_utils.py +263 -0
- pipeline/pipeline.py +898 -0
- pipeline/pphuman/action_infer.py +693 -0
- pipeline/pphuman/action_utils.py +114 -0
- pipeline/pphuman/attr_infer.py +348 -0
- pipeline/pphuman/mtmct.py +381 -0
- pipeline/pphuman/reid.py +204 -0
- pipeline/pphuman/video_action_infer.py +310 -0
- pipeline/pphuman/video_action_preprocess.py +545 -0
- pptracking/python/det_infer.py +594 -0
- pptracking/python/mot/__init__.py +25 -0
- pptracking/python/mot/matching/__init__.py +21 -0
- pptracking/python/mot/matching/deepsort_matching.py +382 -0
- pptracking/python/mot/matching/jde_matching.py +163 -0
- pptracking/python/mot/matching/ocsort_matching.py +129 -0
- pptracking/python/mot/motion/__init__.py +17 -0
- pptracking/python/mot/motion/kalman_filter.py +267 -0
- pptracking/python/mot/mtmct/__init__.py +24 -0
- pptracking/python/mot/mtmct/camera_utils.py +288 -0
- pptracking/python/mot/mtmct/postprocess.py +386 -0
- pptracking/python/mot/mtmct/utils.py +604 -0
- pptracking/python/mot/mtmct/zone.py +412 -0
- pptracking/python/mot/tracker/__init__.py +25 -0
- pptracking/python/mot/tracker/base_jde_tracker.py +286 -0
- pptracking/python/mot/tracker/base_sde_tracker.py +153 -0
- pptracking/python/mot/tracker/deepsort_tracker.py +185 -0
- pptracking/python/mot/tracker/jde_tracker.py +343 -0
- pptracking/python/mot/tracker/ocsort_tracker.py +366 -0
- pptracking/python/mot/utils.py +437 -0
- pptracking/python/mot/visualize.py +379 -0
- pptracking/python/mot_jde_infer.py +508 -0
- pptracking/python/mot_sde_infer.py +882 -0
- pptracking/python/mot_utils.py +349 -0
- pptracking/python/mtmct_cfg.yml +17 -0
- pptracking/python/picodet_postprocess.py +227 -0
- pptracking/python/preprocess.py +286 -0
- pptracking/python/tracker_config.yml +43 -0
- python/README.md +104 -0
- python/benchmark_utils.py +291 -0
- python/det_keypoint_unite_infer.py +377 -0
- python/det_keypoint_unite_utils.py +141 -0
- python/infer.py +1035 -0
app.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import base64
|
| 3 |
+
from io import BytesIO
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import numpy as np
|
| 6 |
+
import os
|
| 7 |
+
from pipeline.pipeline import pp_humanv2
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
# UGC: Define the inference fn() for your models
|
| 11 |
+
def model_inference(input_date, avtivity_list):
|
| 12 |
+
|
| 13 |
+
if isinstance(input_date, str):
|
| 14 |
+
if os.path.splitext(input_date)[-1] not in ['.avi','.mp4']:
|
| 15 |
+
return None
|
| 16 |
+
|
| 17 |
+
if 'do_entrance_counting'in avtivity_list or 'draw_center_traj' in avtivity_list:
|
| 18 |
+
if 'MOT' not in avtivity_list:
|
| 19 |
+
avtivity_list.append('MOT')
|
| 20 |
+
|
| 21 |
+
result = pp_humanv2(input_date, avtivity_list)
|
| 22 |
+
|
| 23 |
+
return result
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def clear_all():
|
| 27 |
+
return None, None, None
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
with gr.Blocks() as demo:
|
| 31 |
+
gr.Markdown("PP-Human Pipeline")
|
| 32 |
+
|
| 33 |
+
with gr.Tabs():
|
| 34 |
+
|
| 35 |
+
with gr.TabItem("image"):
|
| 36 |
+
|
| 37 |
+
img_in = gr.Image(value="https://paddledet.bj.bcebos.com/modelcenter/images/PP-Human/human_attr.jpg",label="Input")
|
| 38 |
+
img_out = gr.Image(label="Output")
|
| 39 |
+
|
| 40 |
+
img_avtivity_list = gr.CheckboxGroup(["ATTR"])
|
| 41 |
+
img_button1 = gr.Button("Submit")
|
| 42 |
+
img_button2 = gr.Button("Clear")
|
| 43 |
+
|
| 44 |
+
with gr.TabItem("video"):
|
| 45 |
+
|
| 46 |
+
video_in = gr.Video(value="https://paddledet.bj.bcebos.com/modelcenter/images/PP-Human/human_attr.mp4",label="Input only support .mp4 or .avi")
|
| 47 |
+
video_out = gr.Video(label="Output")
|
| 48 |
+
|
| 49 |
+
video_avtivity_list = gr.CheckboxGroup(["MOT","ATTR","VIDEO_ACTION","SKELETON_ACTION","ID_BASED_DETACTION","ID_BASED_CLSACTION","REID",\
|
| 50 |
+
"do_entrance_counting","draw_center_traj"],label="Task Choice (note: only one task should be checked)")
|
| 51 |
+
video_button1 = gr.Button("Submit")
|
| 52 |
+
video_button2 = gr.Button("Clear")
|
| 53 |
+
|
| 54 |
+
img_button1.click(
|
| 55 |
+
fn=model_inference,
|
| 56 |
+
inputs=[img_in, img_avtivity_list],
|
| 57 |
+
outputs=img_out)
|
| 58 |
+
img_button2.click(
|
| 59 |
+
fn=clear_all,
|
| 60 |
+
inputs=None,
|
| 61 |
+
outputs=[img_in, img_out, img_avtivity_list])
|
| 62 |
+
|
| 63 |
+
video_button1.click(
|
| 64 |
+
fn=model_inference,
|
| 65 |
+
inputs=[video_in, video_avtivity_list],
|
| 66 |
+
outputs=video_out)
|
| 67 |
+
video_button2.click(
|
| 68 |
+
fn=clear_all,
|
| 69 |
+
inputs=None,
|
| 70 |
+
outputs=[video_in, video_out, video_avtivity_list])
|
| 71 |
+
|
| 72 |
+
demo.launch()
|
app.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
【PP-HumanV2-App-YAML】
|
| 2 |
+
|
| 3 |
+
APP_Info:
|
| 4 |
+
title: PP-HumanV2-App
|
| 5 |
+
colorFrom: blue
|
| 6 |
+
colorTo: yellow
|
| 7 |
+
sdk: gradio
|
| 8 |
+
sdk_version: 3.9
|
| 9 |
+
app_file: app.py
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
device: cpu
|
pipeline/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
pipeline/cfg_utils.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ast
|
| 2 |
+
import yaml
|
| 3 |
+
import copy
|
| 4 |
+
import argparse
|
| 5 |
+
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class ArgsParser(ArgumentParser):
|
| 9 |
+
def __init__(self):
|
| 10 |
+
super(ArgsParser, self).__init__(
|
| 11 |
+
formatter_class=RawDescriptionHelpFormatter)
|
| 12 |
+
self.add_argument(
|
| 13 |
+
"-o", "--opt", nargs='*', help="set configuration options")
|
| 14 |
+
|
| 15 |
+
def parse_args(self, argv=None):
|
| 16 |
+
args = super(ArgsParser, self).parse_args(argv)
|
| 17 |
+
assert args.config is not None, \
|
| 18 |
+
"Please specify --config=configure_file_path."
|
| 19 |
+
args.opt = self._parse_opt(args.opt)
|
| 20 |
+
return args
|
| 21 |
+
|
| 22 |
+
def _parse_opt(self, opts):
|
| 23 |
+
config = {}
|
| 24 |
+
if not opts:
|
| 25 |
+
return config
|
| 26 |
+
for s in opts:
|
| 27 |
+
s = s.strip()
|
| 28 |
+
k, v = s.split('=', 1)
|
| 29 |
+
if '.' not in k:
|
| 30 |
+
config[k] = yaml.load(v, Loader=yaml.Loader)
|
| 31 |
+
else:
|
| 32 |
+
keys = k.split('.')
|
| 33 |
+
if keys[0] not in config:
|
| 34 |
+
config[keys[0]] = {}
|
| 35 |
+
cur = config[keys[0]]
|
| 36 |
+
for idx, key in enumerate(keys[1:]):
|
| 37 |
+
if idx == len(keys) - 2:
|
| 38 |
+
cur[key] = yaml.load(v, Loader=yaml.Loader)
|
| 39 |
+
else:
|
| 40 |
+
cur[key] = {}
|
| 41 |
+
cur = cur[key]
|
| 42 |
+
return config
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def argsparser():
|
| 46 |
+
parser = ArgsParser()
|
| 47 |
+
|
| 48 |
+
parser.add_argument(
|
| 49 |
+
"--config",
|
| 50 |
+
type=str,
|
| 51 |
+
default='pipeline/config/infer_cfg_pphuman.yml',
|
| 52 |
+
help=("Path of configure"))
|
| 53 |
+
parser.add_argument(
|
| 54 |
+
"--image_file", type=str, default=None, help="Path of image file.")
|
| 55 |
+
parser.add_argument(
|
| 56 |
+
"--image_dir",
|
| 57 |
+
type=str,
|
| 58 |
+
default=None,
|
| 59 |
+
help="Dir of image file, `image_file` has a higher priority.")
|
| 60 |
+
parser.add_argument(
|
| 61 |
+
"--video_file",
|
| 62 |
+
type=str,
|
| 63 |
+
default=None,
|
| 64 |
+
help="Path of video file, `video_file` or `camera_id` has a highest priority."
|
| 65 |
+
)
|
| 66 |
+
parser.add_argument(
|
| 67 |
+
"--video_dir",
|
| 68 |
+
type=str,
|
| 69 |
+
default=None,
|
| 70 |
+
help="Dir of video file, `video_file` has a higher priority.")
|
| 71 |
+
parser.add_argument(
|
| 72 |
+
"--rtsp",
|
| 73 |
+
type=str,
|
| 74 |
+
nargs='+',
|
| 75 |
+
default=None,
|
| 76 |
+
help="list of rtsp inputs, for one or multiple rtsp input.")
|
| 77 |
+
parser.add_argument(
|
| 78 |
+
"--camera_id",
|
| 79 |
+
type=int,
|
| 80 |
+
default=-1,
|
| 81 |
+
help="device id of camera to predict.")
|
| 82 |
+
parser.add_argument(
|
| 83 |
+
"--output_dir",
|
| 84 |
+
type=str,
|
| 85 |
+
default="output",
|
| 86 |
+
help="Directory of output visualization files.")
|
| 87 |
+
parser.add_argument(
|
| 88 |
+
"--pushurl",
|
| 89 |
+
type=str,
|
| 90 |
+
default="",
|
| 91 |
+
help="url of output visualization stream.")
|
| 92 |
+
parser.add_argument(
|
| 93 |
+
"--run_mode",
|
| 94 |
+
type=str,
|
| 95 |
+
default='paddle',
|
| 96 |
+
help="mode of running(paddle/trt_fp32/trt_fp16/trt_int8)")
|
| 97 |
+
parser.add_argument(
|
| 98 |
+
"--device",
|
| 99 |
+
type=str,
|
| 100 |
+
default='cpu',
|
| 101 |
+
help="Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU."
|
| 102 |
+
)
|
| 103 |
+
parser.add_argument(
|
| 104 |
+
"--enable_mkldnn",
|
| 105 |
+
type=ast.literal_eval,
|
| 106 |
+
default=False,
|
| 107 |
+
help="Whether use mkldnn with CPU.")
|
| 108 |
+
parser.add_argument(
|
| 109 |
+
"--cpu_threads", type=int, default=1, help="Num of threads with CPU.")
|
| 110 |
+
parser.add_argument(
|
| 111 |
+
"--trt_min_shape", type=int, default=1, help="min_shape for TensorRT.")
|
| 112 |
+
parser.add_argument(
|
| 113 |
+
"--trt_max_shape",
|
| 114 |
+
type=int,
|
| 115 |
+
default=1280,
|
| 116 |
+
help="max_shape for TensorRT.")
|
| 117 |
+
parser.add_argument(
|
| 118 |
+
"--trt_opt_shape",
|
| 119 |
+
type=int,
|
| 120 |
+
default=640,
|
| 121 |
+
help="opt_shape for TensorRT.")
|
| 122 |
+
parser.add_argument(
|
| 123 |
+
"--trt_calib_mode",
|
| 124 |
+
type=bool,
|
| 125 |
+
default=False,
|
| 126 |
+
help="If the model is produced by TRT offline quantitative "
|
| 127 |
+
"calibration, trt_calib_mode need to set True.")
|
| 128 |
+
parser.add_argument(
|
| 129 |
+
"--do_entrance_counting",
|
| 130 |
+
type=bool,
|
| 131 |
+
default=False,
|
| 132 |
+
help="Whether counting the numbers of identifiers entering "
|
| 133 |
+
"or getting out from the entrance. Note that only support single-class MOT."
|
| 134 |
+
)
|
| 135 |
+
parser.add_argument(
|
| 136 |
+
"--do_break_in_counting",
|
| 137 |
+
type=bool,
|
| 138 |
+
default=False,
|
| 139 |
+
help="Whether counting the numbers of identifiers break in "
|
| 140 |
+
"the area. Note that only support single-class MOT and "
|
| 141 |
+
"the video should be taken by a static camera.")
|
| 142 |
+
parser.add_argument(
|
| 143 |
+
"--illegal_parking_time",
|
| 144 |
+
type=int,
|
| 145 |
+
default=-1,
|
| 146 |
+
help="illegal parking time which units are seconds, default is -1 which means not recognition illegal parking"
|
| 147 |
+
)
|
| 148 |
+
parser.add_argument(
|
| 149 |
+
"--region_type",
|
| 150 |
+
type=str,
|
| 151 |
+
default='horizontal',
|
| 152 |
+
help="Area type for entrance counting or break in counting, 'horizontal' and "
|
| 153 |
+
"'vertical' used when do entrance counting. 'custom' used when do break in counting. "
|
| 154 |
+
"Note that only support single-class MOT, and the video should be taken by a static camera."
|
| 155 |
+
)
|
| 156 |
+
parser.add_argument(
|
| 157 |
+
'--region_polygon',
|
| 158 |
+
nargs='+',
|
| 159 |
+
type=int,
|
| 160 |
+
default=[],
|
| 161 |
+
help="Clockwise point coords (x0,y0,x1,y1...) of polygon of area when "
|
| 162 |
+
"do_break_in_counting. Note that only support single-class MOT and "
|
| 163 |
+
"the video should be taken by a static camera.")
|
| 164 |
+
parser.add_argument(
|
| 165 |
+
"--secs_interval",
|
| 166 |
+
type=int,
|
| 167 |
+
default=2,
|
| 168 |
+
help="The seconds interval to count after tracking")
|
| 169 |
+
parser.add_argument(
|
| 170 |
+
"--draw_center_traj",
|
| 171 |
+
type=bool,
|
| 172 |
+
default=False,
|
| 173 |
+
help="Whether drawing the trajectory of center")
|
| 174 |
+
parser.add_argument('--avtivity_list', nargs='+', type=str)
|
| 175 |
+
|
| 176 |
+
return parser
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def merge_cfg(args):
|
| 180 |
+
# load config
|
| 181 |
+
with open(args.config) as f:
|
| 182 |
+
pred_config = yaml.safe_load(f)
|
| 183 |
+
|
| 184 |
+
def merge(cfg, arg):
|
| 185 |
+
# update cfg from arg directly
|
| 186 |
+
merge_cfg = copy.deepcopy(cfg)
|
| 187 |
+
for k, v in cfg.items():
|
| 188 |
+
if k in arg:
|
| 189 |
+
merge_cfg[k] = arg[k]
|
| 190 |
+
else:
|
| 191 |
+
if isinstance(v, dict):
|
| 192 |
+
merge_cfg[k] = merge(v, arg)
|
| 193 |
+
|
| 194 |
+
return merge_cfg
|
| 195 |
+
|
| 196 |
+
def merge_opt(cfg, arg):
|
| 197 |
+
merge_cfg = copy.deepcopy(cfg)
|
| 198 |
+
# merge opt
|
| 199 |
+
if 'opt' in arg.keys() and arg['opt']:
|
| 200 |
+
for name, value in arg['opt'].items(
|
| 201 |
+
): # example: {'MOT': {'batch_size': 3}}
|
| 202 |
+
if name not in merge_cfg.keys():
|
| 203 |
+
print("No", name, "in config file!")
|
| 204 |
+
continue
|
| 205 |
+
for sub_k, sub_v in value.items():
|
| 206 |
+
if sub_k not in merge_cfg[name].keys():
|
| 207 |
+
print("No", sub_k, "in config file of", name, "!")
|
| 208 |
+
continue
|
| 209 |
+
merge_cfg[name][sub_k] = sub_v
|
| 210 |
+
|
| 211 |
+
return merge_cfg
|
| 212 |
+
|
| 213 |
+
args_dict = vars(args)
|
| 214 |
+
pred_config = merge(pred_config, args_dict)
|
| 215 |
+
pred_config = merge_opt(pred_config, args_dict)
|
| 216 |
+
|
| 217 |
+
return pred_config
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def print_arguments(cfg):
|
| 221 |
+
print('----------- Running Arguments -----------')
|
| 222 |
+
buffer = yaml.dump(cfg)
|
| 223 |
+
print(buffer)
|
| 224 |
+
print('------------------------------------------')
|
pipeline/config/infer_cfg_pphuman.yml
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
crop_thresh: 0.5
|
| 2 |
+
attr_thresh: 0.5
|
| 3 |
+
kpt_thresh: 0.2
|
| 4 |
+
visual: True
|
| 5 |
+
warmup_frame: 50
|
| 6 |
+
|
| 7 |
+
DET:
|
| 8 |
+
model_dir: https://bj.bcebos.com/v1/paddledet/models/pipeline/mot_ppyoloe_l_36e_pipeline.zip
|
| 9 |
+
batch_size: 1
|
| 10 |
+
|
| 11 |
+
MOT:
|
| 12 |
+
model_dir: https://bj.bcebos.com/v1/paddledet/models/pipeline/mot_ppyoloe_l_36e_pipeline.zip
|
| 13 |
+
tracker_config: pipeline/config/tracker_config.yml
|
| 14 |
+
batch_size: 1
|
| 15 |
+
skip_frame_num: -1 # preferably no more than 3
|
| 16 |
+
enable: False
|
| 17 |
+
|
| 18 |
+
KPT:
|
| 19 |
+
model_dir: https://bj.bcebos.com/v1/paddledet/models/pipeline/dark_hrnet_w32_256x192.zip
|
| 20 |
+
batch_size: 8
|
| 21 |
+
|
| 22 |
+
ATTR:
|
| 23 |
+
model_dir: https://bj.bcebos.com/v1/paddledet/models/pipeline/PPLCNet_x1_0_person_attribute_945_infer.zip
|
| 24 |
+
batch_size: 8
|
| 25 |
+
enable: False
|
| 26 |
+
|
| 27 |
+
VIDEO_ACTION:
|
| 28 |
+
model_dir: https://videotag.bj.bcebos.com/PaddleVideo-release2.3/ppTSM_fight.zip
|
| 29 |
+
batch_size: 1
|
| 30 |
+
frame_len: 8
|
| 31 |
+
sample_freq: 7
|
| 32 |
+
short_size: 340
|
| 33 |
+
target_size: 320
|
| 34 |
+
enable: False
|
| 35 |
+
|
| 36 |
+
SKELETON_ACTION:
|
| 37 |
+
model_dir: https://bj.bcebos.com/v1/paddledet/models/pipeline/STGCN.zip
|
| 38 |
+
batch_size: 1
|
| 39 |
+
max_frames: 50
|
| 40 |
+
display_frames: 80
|
| 41 |
+
coord_size: [384, 512]
|
| 42 |
+
enable: False
|
| 43 |
+
|
| 44 |
+
ID_BASED_DETACTION:
|
| 45 |
+
model_dir: https://bj.bcebos.com/v1/paddledet/models/pipeline/ppyoloe_crn_s_80e_smoking_visdrone.zip
|
| 46 |
+
batch_size: 8
|
| 47 |
+
threshold: 0.6
|
| 48 |
+
display_frames: 80
|
| 49 |
+
skip_frame_num: 2
|
| 50 |
+
enable: False
|
| 51 |
+
|
| 52 |
+
ID_BASED_CLSACTION:
|
| 53 |
+
model_dir: https://bj.bcebos.com/v1/paddledet/models/pipeline/PPHGNet_tiny_calling_halfbody.zip
|
| 54 |
+
batch_size: 8
|
| 55 |
+
threshold: 0.8
|
| 56 |
+
display_frames: 80
|
| 57 |
+
skip_frame_num: 2
|
| 58 |
+
enable: False
|
| 59 |
+
|
| 60 |
+
REID:
|
| 61 |
+
model_dir: https://bj.bcebos.com/v1/paddledet/models/pipeline/reid_model.zip
|
| 62 |
+
batch_size: 16
|
| 63 |
+
enable: False
|
pipeline/config/tracker_config.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# config of tracker for MOT SDE Detector, use 'JDETracker' as default.
|
| 2 |
+
# The tracker of MOT JDE Detector (such as FairMOT) is exported together with the model.
|
| 3 |
+
# Here 'min_box_area' and 'vertical_ratio' are set for pedestrian, you can modify for other objects tracking.
|
| 4 |
+
|
| 5 |
+
type: OCSORTTracker # choose one tracker in ['JDETracker', 'OCSORTTracker']
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
# BYTETracker
|
| 9 |
+
JDETracker:
|
| 10 |
+
use_byte: True
|
| 11 |
+
det_thresh: 0.3
|
| 12 |
+
conf_thres: 0.6
|
| 13 |
+
low_conf_thres: 0.1
|
| 14 |
+
match_thres: 0.9
|
| 15 |
+
min_box_area: 0
|
| 16 |
+
vertical_ratio: 0 # 1.6 for pedestrian
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
OCSORTTracker:
|
| 20 |
+
det_thresh: 0.4
|
| 21 |
+
max_age: 30
|
| 22 |
+
min_hits: 3
|
| 23 |
+
iou_threshold: 0.3
|
| 24 |
+
delta_t: 3
|
| 25 |
+
inertia: 0.2
|
| 26 |
+
vertical_ratio: 0
|
| 27 |
+
min_box_area: 0
|
| 28 |
+
use_byte: False
|
pipeline/datacollector.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import copy
|
| 17 |
+
from collections import Counter
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class Result(object):
|
| 21 |
+
def __init__(self):
|
| 22 |
+
self.res_dict = {
|
| 23 |
+
'det': dict(),
|
| 24 |
+
'mot': dict(),
|
| 25 |
+
'attr': dict(),
|
| 26 |
+
'kpt': dict(),
|
| 27 |
+
'video_action': dict(),
|
| 28 |
+
'skeleton_action': dict(),
|
| 29 |
+
'reid': dict(),
|
| 30 |
+
'det_action': dict(),
|
| 31 |
+
'cls_action': dict(),
|
| 32 |
+
'vehicleplate': dict(),
|
| 33 |
+
'vehicle_attr': dict()
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
def update(self, res, name):
|
| 37 |
+
self.res_dict[name].update(res)
|
| 38 |
+
|
| 39 |
+
def get(self, name):
|
| 40 |
+
if name in self.res_dict and len(self.res_dict[name]) > 0:
|
| 41 |
+
return self.res_dict[name]
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
def clear(self, name):
|
| 45 |
+
self.res_dict[name].clear()
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class DataCollector(object):
|
| 49 |
+
"""
|
| 50 |
+
DataCollector of Pipeline, collect results in every frames and assign it to each track ids.
|
| 51 |
+
mainly used in mtmct.
|
| 52 |
+
|
| 53 |
+
data struct:
|
| 54 |
+
collector:
|
| 55 |
+
- [id1]: (all results of N frames)
|
| 56 |
+
- frames(list of int): Nx[int]
|
| 57 |
+
- rects(list of rect): Nx[rect(conf, xmin, ymin, xmax, ymax)]
|
| 58 |
+
- features(list of array(256,)): Nx[array(256,)]
|
| 59 |
+
- qualities(list of float): Nx[float]
|
| 60 |
+
- attrs(list of attr): refer to attrs for details
|
| 61 |
+
- kpts(list of kpts): refer to kpts for details
|
| 62 |
+
- skeleton_action(list of skeleton_action): refer to skeleton_action for details
|
| 63 |
+
...
|
| 64 |
+
- [idN]
|
| 65 |
+
"""
|
| 66 |
+
|
| 67 |
+
def __init__(self):
|
| 68 |
+
#id, frame, rect, score, label, attrs, kpts, skeleton_action
|
| 69 |
+
self.mots = {
|
| 70 |
+
"frames": [],
|
| 71 |
+
"rects": [],
|
| 72 |
+
"attrs": [],
|
| 73 |
+
"kpts": [],
|
| 74 |
+
"features": [],
|
| 75 |
+
"qualities": [],
|
| 76 |
+
"skeleton_action": [],
|
| 77 |
+
"vehicleplate": []
|
| 78 |
+
}
|
| 79 |
+
self.collector = {}
|
| 80 |
+
|
| 81 |
+
def append(self, frameid, Result):
|
| 82 |
+
mot_res = Result.get('mot')
|
| 83 |
+
attr_res = Result.get('attr')
|
| 84 |
+
kpt_res = Result.get('kpt')
|
| 85 |
+
skeleton_action_res = Result.get('skeleton_action')
|
| 86 |
+
reid_res = Result.get('reid')
|
| 87 |
+
vehicleplate_res = Result.get('vehicleplate')
|
| 88 |
+
|
| 89 |
+
rects = []
|
| 90 |
+
if reid_res is not None:
|
| 91 |
+
rects = reid_res['rects']
|
| 92 |
+
elif mot_res is not None:
|
| 93 |
+
rects = mot_res['boxes']
|
| 94 |
+
|
| 95 |
+
for idx, mot_item in enumerate(rects):
|
| 96 |
+
ids = int(mot_item[0])
|
| 97 |
+
if ids not in self.collector:
|
| 98 |
+
self.collector[ids] = copy.deepcopy(self.mots)
|
| 99 |
+
self.collector[ids]["frames"].append(frameid)
|
| 100 |
+
self.collector[ids]["rects"].append([mot_item[2:]])
|
| 101 |
+
if attr_res:
|
| 102 |
+
self.collector[ids]["attrs"].append(attr_res['output'][idx])
|
| 103 |
+
if kpt_res:
|
| 104 |
+
self.collector[ids]["kpts"].append([
|
| 105 |
+
kpt_res['keypoint'][0][idx], kpt_res['keypoint'][1][idx]
|
| 106 |
+
])
|
| 107 |
+
if skeleton_action_res and (idx + 1) in skeleton_action_res:
|
| 108 |
+
self.collector[ids]["skeleton_action"].append(
|
| 109 |
+
skeleton_action_res[idx + 1])
|
| 110 |
+
else:
|
| 111 |
+
# action model generate result per X frames, Not available every frames
|
| 112 |
+
self.collector[ids]["skeleton_action"].append(None)
|
| 113 |
+
if reid_res:
|
| 114 |
+
self.collector[ids]["features"].append(reid_res['features'][
|
| 115 |
+
idx])
|
| 116 |
+
self.collector[ids]["qualities"].append(reid_res['qualities'][
|
| 117 |
+
idx])
|
| 118 |
+
if vehicleplate_res and vehicleplate_res['plate'][idx] != "":
|
| 119 |
+
self.collector[ids]["vehicleplate"].append(vehicleplate_res[
|
| 120 |
+
'plate'][idx])
|
| 121 |
+
|
| 122 |
+
def get_res(self):
|
| 123 |
+
return self.collector
|
| 124 |
+
|
| 125 |
+
def get_carlp(self, trackid):
|
| 126 |
+
lps = self.collector[trackid]["vehicleplate"]
|
| 127 |
+
counter = Counter(lps)
|
| 128 |
+
carlp = counter.most_common()
|
| 129 |
+
if len(carlp) > 0:
|
| 130 |
+
return carlp[0][0]
|
| 131 |
+
else:
|
| 132 |
+
return None
|
pipeline/download.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import os, sys
|
| 16 |
+
import os.path as osp
|
| 17 |
+
import hashlib
|
| 18 |
+
import requests
|
| 19 |
+
import shutil
|
| 20 |
+
import tqdm
|
| 21 |
+
import time
|
| 22 |
+
import tarfile
|
| 23 |
+
import zipfile
|
| 24 |
+
from paddle.utils.download import _get_unique_endpoints
|
| 25 |
+
|
| 26 |
+
PPDET_WEIGHTS_DOWNLOAD_URL_PREFIX = 'https://paddledet.bj.bcebos.com/'
|
| 27 |
+
|
| 28 |
+
DOWNLOAD_RETRY_LIMIT = 3
|
| 29 |
+
|
| 30 |
+
WEIGHTS_HOME = osp.expanduser("~/.cache/paddle/infer_weights")
|
| 31 |
+
|
| 32 |
+
MODEL_URL_MD5_DICT = {
|
| 33 |
+
'https://bj.bcebos.com/v1/paddledet/models/pipeline/ch_PP-OCRv3_det_infer.tar.gz':
|
| 34 |
+
'1b8eae0f098635699bd4e8bccf3067a7',
|
| 35 |
+
'https://bj.bcebos.com/v1/paddledet/models/pipeline/ch_PP-OCRv3_rec_infer.tar.gz':
|
| 36 |
+
'64fa0e0701efd93c7db52a9b685b3de6',
|
| 37 |
+
"https://bj.bcebos.com/v1/paddledet/models/pipeline/mot_ppyoloe_l_36e_ppvehicle.zip":
|
| 38 |
+
"3859d1a26e0c498285c2374b1a347013",
|
| 39 |
+
"https://bj.bcebos.com/v1/paddledet/models/pipeline/mot_ppyoloe_s_36e_ppvehicle.zip":
|
| 40 |
+
"4ed58b546be2a76d8ccbb138f64874ac",
|
| 41 |
+
"https://bj.bcebos.com/v1/paddledet/models/pipeline/dark_hrnet_w32_256x192.zip":
|
| 42 |
+
"a20d5f6ca087bff0e9f2b18df45a36f2",
|
| 43 |
+
"https://bj.bcebos.com/v1/paddledet/models/pipeline/PPLCNet_x1_0_person_attribute_945_infer.zip":
|
| 44 |
+
"1dfb161bf12bbc1365b2ed6866674483",
|
| 45 |
+
"https://videotag.bj.bcebos.com/PaddleVideo-release2.3/ppTSM_fight.zip":
|
| 46 |
+
"5d4609142501258608bf0a1445eedaba",
|
| 47 |
+
"https://bj.bcebos.com/v1/paddledet/models/pipeline/STGCN.zip":
|
| 48 |
+
"cf1c3c4bae90b975accb954d13129ea4",
|
| 49 |
+
"https://bj.bcebos.com/v1/paddledet/models/pipeline/ppyoloe_crn_s_80e_smoking_visdrone.zip":
|
| 50 |
+
"4cd12ae55be8f0eb2b90c08ac3b48218",
|
| 51 |
+
"https://bj.bcebos.com/v1/paddledet/models/pipeline/PPHGNet_tiny_calling_halfbody.zip":
|
| 52 |
+
"cf86b87ace97540dace6ef08e62b584a",
|
| 53 |
+
"https://bj.bcebos.com/v1/paddledet/models/pipeline/reid_model.zip":
|
| 54 |
+
"fdc4dac38393b8e2b5921c1e1fdd5315"
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def is_url(path):
|
| 59 |
+
"""
|
| 60 |
+
Whether path is URL.
|
| 61 |
+
Args:
|
| 62 |
+
path (string): URL string or not.
|
| 63 |
+
"""
|
| 64 |
+
return path.startswith('http://') \
|
| 65 |
+
or path.startswith('https://') \
|
| 66 |
+
or path.startswith('ppdet://')
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def parse_url(url):
|
| 70 |
+
url = url.replace("ppdet://", PPDET_WEIGHTS_DOWNLOAD_URL_PREFIX)
|
| 71 |
+
return url
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def map_path(url, root_dir, path_depth=1):
|
| 75 |
+
# parse path after download to decompress under root_dir
|
| 76 |
+
assert path_depth > 0, "path_depth should be a positive integer"
|
| 77 |
+
dirname = url
|
| 78 |
+
for _ in range(path_depth):
|
| 79 |
+
dirname = osp.dirname(dirname)
|
| 80 |
+
fpath = osp.relpath(url, dirname)
|
| 81 |
+
|
| 82 |
+
zip_formats = ['.zip', '.tar', '.gz']
|
| 83 |
+
for zip_format in zip_formats:
|
| 84 |
+
fpath = fpath.replace(zip_format, '')
|
| 85 |
+
return osp.join(root_dir, fpath)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _md5check(fullname, md5sum=None):
|
| 89 |
+
if md5sum is None:
|
| 90 |
+
return True
|
| 91 |
+
|
| 92 |
+
md5 = hashlib.md5()
|
| 93 |
+
with open(fullname, 'rb') as f:
|
| 94 |
+
for chunk in iter(lambda: f.read(4096), b""):
|
| 95 |
+
md5.update(chunk)
|
| 96 |
+
calc_md5sum = md5.hexdigest()
|
| 97 |
+
|
| 98 |
+
if calc_md5sum != md5sum:
|
| 99 |
+
return False
|
| 100 |
+
return True
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def _check_exist_file_md5(filename, md5sum, url):
|
| 104 |
+
return _md5check(filename, md5sum)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def _download(url, path, md5sum=None):
|
| 108 |
+
"""
|
| 109 |
+
Download from url, save to path.
|
| 110 |
+
url (str): download url
|
| 111 |
+
path (str): download to given path
|
| 112 |
+
"""
|
| 113 |
+
if not osp.exists(path):
|
| 114 |
+
os.makedirs(path)
|
| 115 |
+
|
| 116 |
+
fname = osp.split(url)[-1]
|
| 117 |
+
fullname = osp.join(path, fname)
|
| 118 |
+
retry_cnt = 0
|
| 119 |
+
while not (osp.exists(fullname) and _check_exist_file_md5(fullname, md5sum,
|
| 120 |
+
url)):
|
| 121 |
+
if retry_cnt < DOWNLOAD_RETRY_LIMIT:
|
| 122 |
+
retry_cnt += 1
|
| 123 |
+
else:
|
| 124 |
+
raise RuntimeError("Download from {} failed. "
|
| 125 |
+
"Retry limit reached".format(url))
|
| 126 |
+
|
| 127 |
+
# NOTE: windows path join may incur \, which is invalid in url
|
| 128 |
+
if sys.platform == "win32":
|
| 129 |
+
url = url.replace('\\', '/')
|
| 130 |
+
|
| 131 |
+
req = requests.get(url, stream=True)
|
| 132 |
+
if req.status_code != 200:
|
| 133 |
+
raise RuntimeError("Downloading from {} failed with code "
|
| 134 |
+
"{}!".format(url, req.status_code))
|
| 135 |
+
|
| 136 |
+
# For protecting download interupted, download to
|
| 137 |
+
# tmp_fullname firstly, move tmp_fullname to fullname
|
| 138 |
+
# after download finished
|
| 139 |
+
tmp_fullname = fullname + "_tmp"
|
| 140 |
+
total_size = req.headers.get('content-length')
|
| 141 |
+
with open(tmp_fullname, 'wb') as f:
|
| 142 |
+
if total_size:
|
| 143 |
+
for chunk in tqdm.tqdm(
|
| 144 |
+
req.iter_content(chunk_size=1024),
|
| 145 |
+
total=(int(total_size) + 1023) // 1024,
|
| 146 |
+
unit='KB'):
|
| 147 |
+
f.write(chunk)
|
| 148 |
+
else:
|
| 149 |
+
for chunk in req.iter_content(chunk_size=1024):
|
| 150 |
+
if chunk:
|
| 151 |
+
f.write(chunk)
|
| 152 |
+
shutil.move(tmp_fullname, fullname)
|
| 153 |
+
return fullname
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _download_dist(url, path, md5sum=None):
|
| 157 |
+
env = os.environ
|
| 158 |
+
if 'PADDLE_TRAINERS_NUM' in env and 'PADDLE_TRAINER_ID' in env:
|
| 159 |
+
trainer_id = int(env['PADDLE_TRAINER_ID'])
|
| 160 |
+
num_trainers = int(env['PADDLE_TRAINERS_NUM'])
|
| 161 |
+
if num_trainers <= 1:
|
| 162 |
+
return _download(url, path, md5sum)
|
| 163 |
+
else:
|
| 164 |
+
fname = osp.split(url)[-1]
|
| 165 |
+
fullname = osp.join(path, fname)
|
| 166 |
+
lock_path = fullname + '.download.lock'
|
| 167 |
+
|
| 168 |
+
if not osp.isdir(path):
|
| 169 |
+
os.makedirs(path)
|
| 170 |
+
|
| 171 |
+
if not osp.exists(fullname):
|
| 172 |
+
from paddle.distributed import ParallelEnv
|
| 173 |
+
unique_endpoints = _get_unique_endpoints(ParallelEnv()
|
| 174 |
+
.trainer_endpoints[:])
|
| 175 |
+
with open(lock_path, 'w'): # touch
|
| 176 |
+
os.utime(lock_path, None)
|
| 177 |
+
if ParallelEnv().current_endpoint in unique_endpoints:
|
| 178 |
+
_download(url, path, md5sum)
|
| 179 |
+
os.remove(lock_path)
|
| 180 |
+
else:
|
| 181 |
+
while os.path.exists(lock_path):
|
| 182 |
+
time.sleep(0.5)
|
| 183 |
+
return fullname
|
| 184 |
+
else:
|
| 185 |
+
return _download(url, path, md5sum)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _move_and_merge_tree(src, dst):
|
| 189 |
+
"""
|
| 190 |
+
Move src directory to dst, if dst is already exists,
|
| 191 |
+
merge src to dst
|
| 192 |
+
"""
|
| 193 |
+
if not osp.exists(dst):
|
| 194 |
+
shutil.move(src, dst)
|
| 195 |
+
elif osp.isfile(src):
|
| 196 |
+
shutil.move(src, dst)
|
| 197 |
+
else:
|
| 198 |
+
for fp in os.listdir(src):
|
| 199 |
+
src_fp = osp.join(src, fp)
|
| 200 |
+
dst_fp = osp.join(dst, fp)
|
| 201 |
+
if osp.isdir(src_fp):
|
| 202 |
+
if osp.isdir(dst_fp):
|
| 203 |
+
_move_and_merge_tree(src_fp, dst_fp)
|
| 204 |
+
else:
|
| 205 |
+
shutil.move(src_fp, dst_fp)
|
| 206 |
+
elif osp.isfile(src_fp) and \
|
| 207 |
+
not osp.isfile(dst_fp):
|
| 208 |
+
shutil.move(src_fp, dst_fp)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def _decompress(fname):
|
| 212 |
+
"""
|
| 213 |
+
Decompress for zip and tar file
|
| 214 |
+
"""
|
| 215 |
+
|
| 216 |
+
# For protecting decompressing interupted,
|
| 217 |
+
# decompress to fpath_tmp directory firstly, if decompress
|
| 218 |
+
# successed, move decompress files to fpath and delete
|
| 219 |
+
# fpath_tmp and remove download compress file.
|
| 220 |
+
fpath = osp.split(fname)[0]
|
| 221 |
+
fpath_tmp = osp.join(fpath, 'tmp')
|
| 222 |
+
if osp.isdir(fpath_tmp):
|
| 223 |
+
shutil.rmtree(fpath_tmp)
|
| 224 |
+
os.makedirs(fpath_tmp)
|
| 225 |
+
|
| 226 |
+
if fname.find('tar') >= 0:
|
| 227 |
+
with tarfile.open(fname) as tf:
|
| 228 |
+
tf.extractall(path=fpath_tmp)
|
| 229 |
+
elif fname.find('zip') >= 0:
|
| 230 |
+
with zipfile.ZipFile(fname) as zf:
|
| 231 |
+
zf.extractall(path=fpath_tmp)
|
| 232 |
+
elif fname.find('.txt') >= 0:
|
| 233 |
+
return
|
| 234 |
+
else:
|
| 235 |
+
raise TypeError("Unsupport compress file type {}".format(fname))
|
| 236 |
+
|
| 237 |
+
for f in os.listdir(fpath_tmp):
|
| 238 |
+
src_dir = osp.join(fpath_tmp, f)
|
| 239 |
+
dst_dir = osp.join(fpath, f)
|
| 240 |
+
_move_and_merge_tree(src_dir, dst_dir)
|
| 241 |
+
|
| 242 |
+
shutil.rmtree(fpath_tmp)
|
| 243 |
+
os.remove(fname)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def _decompress_dist(fname):
|
| 247 |
+
env = os.environ
|
| 248 |
+
if 'PADDLE_TRAINERS_NUM' in env and 'PADDLE_TRAINER_ID' in env:
|
| 249 |
+
trainer_id = int(env['PADDLE_TRAINER_ID'])
|
| 250 |
+
num_trainers = int(env['PADDLE_TRAINERS_NUM'])
|
| 251 |
+
if num_trainers <= 1:
|
| 252 |
+
_decompress(fname)
|
| 253 |
+
else:
|
| 254 |
+
lock_path = fname + '.decompress.lock'
|
| 255 |
+
from paddle.distributed import ParallelEnv
|
| 256 |
+
unique_endpoints = _get_unique_endpoints(ParallelEnv()
|
| 257 |
+
.trainer_endpoints[:])
|
| 258 |
+
# NOTE(dkp): _decompress_dist always performed after
|
| 259 |
+
# _download_dist, in _download_dist sub-trainers is waiting
|
| 260 |
+
# for download lock file release with sleeping, if decompress
|
| 261 |
+
# prograss is very fast and finished with in the sleeping gap
|
| 262 |
+
# time, e.g in tiny dataset such as coco_ce, spine_coco, main
|
| 263 |
+
# trainer may finish decompress and release lock file, so we
|
| 264 |
+
# only craete lock file in main trainer and all sub-trainer
|
| 265 |
+
# wait 1s for main trainer to create lock file, for 1s is
|
| 266 |
+
# twice as sleeping gap, this waiting time can keep all
|
| 267 |
+
# trainer pipeline in order
|
| 268 |
+
# **change this if you have more elegent methods**
|
| 269 |
+
if ParallelEnv().current_endpoint in unique_endpoints:
|
| 270 |
+
with open(lock_path, 'w'): # touch
|
| 271 |
+
os.utime(lock_path, None)
|
| 272 |
+
_decompress(fname)
|
| 273 |
+
os.remove(lock_path)
|
| 274 |
+
else:
|
| 275 |
+
time.sleep(1)
|
| 276 |
+
while os.path.exists(lock_path):
|
| 277 |
+
time.sleep(0.5)
|
| 278 |
+
else:
|
| 279 |
+
_decompress(fname)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def get_path(url, root_dir=WEIGHTS_HOME, md5sum=None, check_exist=True):
|
| 283 |
+
""" Download from given url to root_dir.
|
| 284 |
+
if file or directory specified by url is exists under
|
| 285 |
+
root_dir, return the path directly, otherwise download
|
| 286 |
+
from url and decompress it, return the path.
|
| 287 |
+
url (str): download url
|
| 288 |
+
root_dir (str): root dir for downloading
|
| 289 |
+
md5sum (str): md5 sum of download package
|
| 290 |
+
"""
|
| 291 |
+
# parse path after download to decompress under root_dir
|
| 292 |
+
fullpath = map_path(url, root_dir)
|
| 293 |
+
|
| 294 |
+
# For same zip file, decompressed directory name different
|
| 295 |
+
# from zip file name, rename by following map
|
| 296 |
+
decompress_name_map = {"ppTSM_fight": "ppTSM", }
|
| 297 |
+
for k, v in decompress_name_map.items():
|
| 298 |
+
if fullpath.find(k) >= 0:
|
| 299 |
+
fullpath = osp.join(osp.split(fullpath)[0], v)
|
| 300 |
+
|
| 301 |
+
if osp.exists(fullpath) and check_exist:
|
| 302 |
+
if not osp.isfile(fullpath) or \
|
| 303 |
+
_check_exist_file_md5(fullpath, md5sum, url):
|
| 304 |
+
return fullpath, True
|
| 305 |
+
else:
|
| 306 |
+
os.remove(fullpath)
|
| 307 |
+
|
| 308 |
+
fullname = _download_dist(url, root_dir, md5sum)
|
| 309 |
+
|
| 310 |
+
# new weights format which postfix is 'pdparams' not
|
| 311 |
+
# need to decompress
|
| 312 |
+
if osp.splitext(fullname)[-1] not in ['.pdparams', '.yml']:
|
| 313 |
+
_decompress_dist(fullname)
|
| 314 |
+
|
| 315 |
+
return fullpath, False
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def get_weights_path(url):
|
| 319 |
+
"""Get weights path from WEIGHTS_HOME, if not exists,
|
| 320 |
+
download it from url.
|
| 321 |
+
"""
|
| 322 |
+
url = parse_url(url)
|
| 323 |
+
md5sum = None
|
| 324 |
+
if url in MODEL_URL_MD5_DICT.keys():
|
| 325 |
+
md5sum = MODEL_URL_MD5_DICT[url]
|
| 326 |
+
path, _ = get_path(url, WEIGHTS_HOME, md5sum)
|
| 327 |
+
return path
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def auto_download_model(model_path):
|
| 331 |
+
# auto download
|
| 332 |
+
if is_url(model_path):
|
| 333 |
+
weight = get_weights_path(model_path)
|
| 334 |
+
return weight
|
| 335 |
+
return None
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
if __name__ == "__main__":
|
| 339 |
+
model_path = "https://bj.bcebos.com/v1/paddledet/models/pipeline/mot_ppyoloe_l_36e_pipeline.zip"
|
| 340 |
+
auto_download_model(model_path)
|
pipeline/pipe_utils.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import time
|
| 16 |
+
import os
|
| 17 |
+
import ast
|
| 18 |
+
import glob
|
| 19 |
+
import yaml
|
| 20 |
+
import copy
|
| 21 |
+
import numpy as np
|
| 22 |
+
import subprocess as sp
|
| 23 |
+
|
| 24 |
+
from python.keypoint_preprocess import EvalAffine, TopDownEvalAffine, expand_crop
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class Times(object):
|
| 28 |
+
def __init__(self):
|
| 29 |
+
self.time = 0.
|
| 30 |
+
# start time
|
| 31 |
+
self.st = 0.
|
| 32 |
+
# end time
|
| 33 |
+
self.et = 0.
|
| 34 |
+
|
| 35 |
+
def start(self):
|
| 36 |
+
self.st = time.time()
|
| 37 |
+
|
| 38 |
+
def end(self, repeats=1, accumulative=True):
|
| 39 |
+
self.et = time.time()
|
| 40 |
+
if accumulative:
|
| 41 |
+
self.time += (self.et - self.st) / repeats
|
| 42 |
+
else:
|
| 43 |
+
self.time = (self.et - self.st) / repeats
|
| 44 |
+
|
| 45 |
+
def reset(self):
|
| 46 |
+
self.time = 0.
|
| 47 |
+
self.st = 0.
|
| 48 |
+
self.et = 0.
|
| 49 |
+
|
| 50 |
+
def value(self):
|
| 51 |
+
return round(self.time, 4)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class PipeTimer(Times):
|
| 55 |
+
def __init__(self):
|
| 56 |
+
super(PipeTimer, self).__init__()
|
| 57 |
+
self.total_time = Times()
|
| 58 |
+
self.module_time = {
|
| 59 |
+
'det': Times(),
|
| 60 |
+
'mot': Times(),
|
| 61 |
+
'attr': Times(),
|
| 62 |
+
'kpt': Times(),
|
| 63 |
+
'video_action': Times(),
|
| 64 |
+
'skeleton_action': Times(),
|
| 65 |
+
'reid': Times(),
|
| 66 |
+
'det_action': Times(),
|
| 67 |
+
'cls_action': Times(),
|
| 68 |
+
'vehicle_attr': Times(),
|
| 69 |
+
'vehicleplate': Times()
|
| 70 |
+
}
|
| 71 |
+
self.img_num = 0
|
| 72 |
+
self.track_num = 0
|
| 73 |
+
|
| 74 |
+
def get_total_time(self):
|
| 75 |
+
total_time = self.total_time.value()
|
| 76 |
+
total_time = round(total_time, 4)
|
| 77 |
+
average_latency = total_time / max(1, self.img_num)
|
| 78 |
+
qps = 0
|
| 79 |
+
if total_time > 0:
|
| 80 |
+
qps = 1 / average_latency
|
| 81 |
+
return total_time, average_latency, qps
|
| 82 |
+
|
| 83 |
+
def info(self):
|
| 84 |
+
total_time, average_latency, qps = self.get_total_time()
|
| 85 |
+
print("------------------ Inference Time Info ----------------------")
|
| 86 |
+
print("total_time(ms): {}, img_num: {}".format(total_time * 1000,
|
| 87 |
+
self.img_num))
|
| 88 |
+
|
| 89 |
+
for k, v in self.module_time.items():
|
| 90 |
+
v_time = round(v.value(), 4)
|
| 91 |
+
if v_time > 0 and k in ['det', 'mot', 'video_action']:
|
| 92 |
+
print("{} time(ms): {}; per frame average time(ms): {}".format(
|
| 93 |
+
k, v_time * 1000, v_time * 1000 / self.img_num))
|
| 94 |
+
elif v_time > 0:
|
| 95 |
+
print("{} time(ms): {}; per trackid average time(ms): {}".
|
| 96 |
+
format(k, v_time * 1000, v_time * 1000 / self.track_num))
|
| 97 |
+
|
| 98 |
+
print("average latency time(ms): {:.2f}, QPS: {:2f}".format(
|
| 99 |
+
average_latency * 1000, qps))
|
| 100 |
+
return qps
|
| 101 |
+
|
| 102 |
+
def report(self, average=False):
|
| 103 |
+
dic = {}
|
| 104 |
+
dic['total'] = round(self.total_time.value() / max(1, self.img_num),
|
| 105 |
+
4) if average else self.total_time.value()
|
| 106 |
+
dic['det'] = round(self.module_time['det'].value() /
|
| 107 |
+
max(1, self.img_num),
|
| 108 |
+
4) if average else self.module_time['det'].value()
|
| 109 |
+
dic['mot'] = round(self.module_time['mot'].value() /
|
| 110 |
+
max(1, self.img_num),
|
| 111 |
+
4) if average else self.module_time['mot'].value()
|
| 112 |
+
dic['attr'] = round(self.module_time['attr'].value() /
|
| 113 |
+
max(1, self.img_num),
|
| 114 |
+
4) if average else self.module_time['attr'].value()
|
| 115 |
+
dic['kpt'] = round(self.module_time['kpt'].value() /
|
| 116 |
+
max(1, self.img_num),
|
| 117 |
+
4) if average else self.module_time['kpt'].value()
|
| 118 |
+
dic['video_action'] = self.module_time['video_action'].value()
|
| 119 |
+
dic['skeleton_action'] = round(
|
| 120 |
+
self.module_time['skeleton_action'].value() / max(1, self.img_num),
|
| 121 |
+
4) if average else self.module_time['skeleton_action'].value()
|
| 122 |
+
|
| 123 |
+
dic['img_num'] = self.img_num
|
| 124 |
+
return dic
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class PushStream(object):
|
| 128 |
+
def __init__(self, pushurl="rtsp://127.0.0.1:8554/"):
|
| 129 |
+
self.command = ""
|
| 130 |
+
# 自行设置
|
| 131 |
+
self.pushurl = pushurl
|
| 132 |
+
|
| 133 |
+
def initcmd(self, fps, width, height):
|
| 134 |
+
self.command = [
|
| 135 |
+
'ffmpeg', '-y', '-f', 'rawvideo', '-vcodec', 'rawvideo',
|
| 136 |
+
'-pix_fmt', 'bgr24', '-s', "{}x{}".format(width, height), '-r',
|
| 137 |
+
str(fps), '-i', '-', '-pix_fmt', 'yuv420p', '-f', 'rtsp',
|
| 138 |
+
self.pushurl
|
| 139 |
+
]
|
| 140 |
+
self.pipe = sp.Popen(self.command, stdin=sp.PIPE)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def get_test_images(infer_dir, infer_img):
|
| 144 |
+
"""
|
| 145 |
+
Get image path list in TEST mode
|
| 146 |
+
"""
|
| 147 |
+
assert infer_img is not None or infer_dir is not None, \
|
| 148 |
+
"--infer_img or --infer_dir should be set"
|
| 149 |
+
assert infer_img is None or os.path.isfile(infer_img), \
|
| 150 |
+
"{} is not a file".format(infer_img)
|
| 151 |
+
assert infer_dir is None or os.path.isdir(infer_dir), \
|
| 152 |
+
"{} is not a directory".format(infer_dir)
|
| 153 |
+
|
| 154 |
+
# infer_img has a higher priority
|
| 155 |
+
if infer_img and os.path.isfile(infer_img):
|
| 156 |
+
return [infer_img]
|
| 157 |
+
|
| 158 |
+
images = set()
|
| 159 |
+
infer_dir = os.path.abspath(infer_dir)
|
| 160 |
+
assert os.path.isdir(infer_dir), \
|
| 161 |
+
"infer_dir {} is not a directory".format(infer_dir)
|
| 162 |
+
exts = ['jpg', 'jpeg', 'png', 'bmp']
|
| 163 |
+
exts += [ext.upper() for ext in exts]
|
| 164 |
+
for ext in exts:
|
| 165 |
+
images.update(glob.glob('{}/*.{}'.format(infer_dir, ext)))
|
| 166 |
+
images = list(images)
|
| 167 |
+
|
| 168 |
+
assert len(images) > 0, "no image found in {}".format(infer_dir)
|
| 169 |
+
print("Found {} inference images in total.".format(len(images)))
|
| 170 |
+
|
| 171 |
+
return images
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def crop_image_with_det(batch_input, det_res, thresh=0.3):
|
| 175 |
+
boxes = det_res['boxes']
|
| 176 |
+
score = det_res['boxes'][:, 1]
|
| 177 |
+
boxes_num = det_res['boxes_num']
|
| 178 |
+
start_idx = 0
|
| 179 |
+
crop_res = []
|
| 180 |
+
for b_id, input in enumerate(batch_input):
|
| 181 |
+
boxes_num_i = boxes_num[b_id]
|
| 182 |
+
if boxes_num_i == 0:
|
| 183 |
+
continue
|
| 184 |
+
boxes_i = boxes[start_idx:start_idx + boxes_num_i, :]
|
| 185 |
+
score_i = score[start_idx:start_idx + boxes_num_i]
|
| 186 |
+
res = []
|
| 187 |
+
for box, s in zip(boxes_i, score_i):
|
| 188 |
+
if s > thresh:
|
| 189 |
+
crop_image, new_box, ori_box = expand_crop(input, box)
|
| 190 |
+
if crop_image is not None:
|
| 191 |
+
res.append(crop_image)
|
| 192 |
+
crop_res.append(res)
|
| 193 |
+
return crop_res
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def normal_crop(image, rect):
|
| 197 |
+
imgh, imgw, c = image.shape
|
| 198 |
+
label, conf, xmin, ymin, xmax, ymax = [int(x) for x in rect.tolist()]
|
| 199 |
+
org_rect = [xmin, ymin, xmax, ymax]
|
| 200 |
+
if label != 0:
|
| 201 |
+
return None, None, None
|
| 202 |
+
xmin = max(0, xmin)
|
| 203 |
+
ymin = max(0, ymin)
|
| 204 |
+
xmax = min(imgw, xmax)
|
| 205 |
+
ymax = min(imgh, ymax)
|
| 206 |
+
return image[ymin:ymax, xmin:xmax, :], [xmin, ymin, xmax, ymax], org_rect
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def crop_image_with_mot(input, mot_res, expand=True):
|
| 210 |
+
res = mot_res['boxes']
|
| 211 |
+
crop_res = []
|
| 212 |
+
new_bboxes = []
|
| 213 |
+
ori_bboxes = []
|
| 214 |
+
for box in res:
|
| 215 |
+
if expand:
|
| 216 |
+
crop_image, new_bbox, ori_bbox = expand_crop(input, box[1:])
|
| 217 |
+
else:
|
| 218 |
+
crop_image, new_bbox, ori_bbox = normal_crop(input, box[1:])
|
| 219 |
+
if crop_image is not None:
|
| 220 |
+
crop_res.append(crop_image)
|
| 221 |
+
new_bboxes.append(new_bbox)
|
| 222 |
+
ori_bboxes.append(ori_bbox)
|
| 223 |
+
return crop_res, new_bboxes, ori_bboxes
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def parse_mot_res(input):
|
| 227 |
+
mot_res = []
|
| 228 |
+
boxes, scores, ids = input[0]
|
| 229 |
+
for box, score, i in zip(boxes[0], scores[0], ids[0]):
|
| 230 |
+
xmin, ymin, w, h = box
|
| 231 |
+
res = [i, 0, score, xmin, ymin, xmin + w, ymin + h]
|
| 232 |
+
mot_res.append(res)
|
| 233 |
+
return {'boxes': np.array(mot_res)}
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def refine_keypoint_coordinary(kpts, bbox, coord_size):
|
| 237 |
+
"""
|
| 238 |
+
This function is used to adjust coordinate values to a fixed scale.
|
| 239 |
+
"""
|
| 240 |
+
tl = bbox[:, 0:2]
|
| 241 |
+
wh = bbox[:, 2:] - tl
|
| 242 |
+
tl = np.expand_dims(np.transpose(tl, (1, 0)), (2, 3))
|
| 243 |
+
wh = np.expand_dims(np.transpose(wh, (1, 0)), (2, 3))
|
| 244 |
+
target_w, target_h = coord_size
|
| 245 |
+
res = (kpts - tl) / wh * np.expand_dims(
|
| 246 |
+
np.array([[target_w], [target_h]]), (2, 3))
|
| 247 |
+
return res
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def parse_mot_keypoint(input, coord_size):
|
| 251 |
+
parsed_skeleton_with_mot = {}
|
| 252 |
+
ids = []
|
| 253 |
+
skeleton = []
|
| 254 |
+
for tracker_id, kpt_seq in input:
|
| 255 |
+
ids.append(tracker_id)
|
| 256 |
+
kpts = np.array(kpt_seq.kpts, dtype=np.float32)[:, :, :2]
|
| 257 |
+
kpts = np.expand_dims(np.transpose(kpts, [2, 0, 1]),
|
| 258 |
+
-1) #T, K, C -> C, T, K, 1
|
| 259 |
+
bbox = np.array(kpt_seq.bboxes, dtype=np.float32)
|
| 260 |
+
skeleton.append(refine_keypoint_coordinary(kpts, bbox, coord_size))
|
| 261 |
+
parsed_skeleton_with_mot["mot_id"] = ids
|
| 262 |
+
parsed_skeleton_with_mot["skeleton"] = skeleton
|
| 263 |
+
return parsed_skeleton_with_mot
|
pipeline/pipeline.py
ADDED
|
@@ -0,0 +1,898 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import yaml
|
| 17 |
+
import glob
|
| 18 |
+
import cv2
|
| 19 |
+
import numpy as np
|
| 20 |
+
import math
|
| 21 |
+
import paddle
|
| 22 |
+
import sys
|
| 23 |
+
import copy
|
| 24 |
+
from collections import defaultdict
|
| 25 |
+
from pipeline.datacollector import DataCollector, Result
|
| 26 |
+
|
| 27 |
+
# add deploy path of PadleDetection to sys.path
|
| 28 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'] * 2)))
|
| 29 |
+
sys.path.insert(0, parent_path)
|
| 30 |
+
|
| 31 |
+
from pipeline.cfg_utils import argsparser, merge_cfg
|
| 32 |
+
from pipeline.pipe_utils import get_test_images, crop_image_with_det, crop_image_with_mot, parse_mot_res, parse_mot_keypoint
|
| 33 |
+
from pipeline.pipe_utils import PushStream, PipeTimer
|
| 34 |
+
|
| 35 |
+
from python.infer import Detector, DetectorPicoDet
|
| 36 |
+
from python.keypoint_infer import KeyPointDetector
|
| 37 |
+
from python.keypoint_postprocess import translate_to_ori_images
|
| 38 |
+
from python.preprocess import decode_image, ShortSizeScale
|
| 39 |
+
from python.visualize import visualize_box_mask, visualize_attr, visualize_pose, visualize_action, visualize_vehicleplate
|
| 40 |
+
|
| 41 |
+
from pptracking.python.mot_sde_infer import SDE_Detector
|
| 42 |
+
from pptracking.python.mot.visualize import plot_tracking_dict
|
| 43 |
+
from pptracking.python.mot.utils import flow_statistic, update_object_info
|
| 44 |
+
|
| 45 |
+
from pipeline.pphuman.attr_infer import AttrDetector
|
| 46 |
+
from pipeline.pphuman.video_action_infer import VideoActionRecognizer
|
| 47 |
+
from pipeline.pphuman.action_infer import SkeletonActionRecognizer, DetActionRecognizer, ClsActionRecognizer
|
| 48 |
+
from pipeline.pphuman.action_utils import KeyPointBuff, ActionVisualHelper
|
| 49 |
+
from pipeline.pphuman.reid import ReID
|
| 50 |
+
from pipeline.pphuman.mtmct import mtmct_process
|
| 51 |
+
|
| 52 |
+
from pipeline.download import auto_download_model
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class Pipeline(object):
|
| 56 |
+
"""
|
| 57 |
+
Pipeline
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
args (argparse.Namespace): arguments in pipeline, which contains environment and runtime settings
|
| 61 |
+
cfg (dict): config of models in pipeline
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
def __init__(self, args, cfg):
|
| 65 |
+
self.multi_camera = False
|
| 66 |
+
reid_cfg = cfg.get('REID', False)
|
| 67 |
+
self.enable_mtmct = reid_cfg['enable'] if reid_cfg else False
|
| 68 |
+
self.is_video = False
|
| 69 |
+
self.output_dir = args.output_dir
|
| 70 |
+
self.vis_result = cfg['visual']
|
| 71 |
+
self.input = self._parse_input(args.image_file, args.video_file)
|
| 72 |
+
|
| 73 |
+
self.predictor = PipePredictor(args, cfg, self.is_video)
|
| 74 |
+
if self.is_video:
|
| 75 |
+
self.predictor.set_file_name(self.input)
|
| 76 |
+
|
| 77 |
+
def _parse_input(self, image, video_file):
|
| 78 |
+
|
| 79 |
+
# parse input as is_video and multi_camera
|
| 80 |
+
|
| 81 |
+
if image is not None:
|
| 82 |
+
|
| 83 |
+
input = image
|
| 84 |
+
self.is_video = False
|
| 85 |
+
self.multi_camera = False
|
| 86 |
+
|
| 87 |
+
elif video_file is not None:
|
| 88 |
+
input = video_file
|
| 89 |
+
self.is_video = True
|
| 90 |
+
else:
|
| 91 |
+
raise ValueError(
|
| 92 |
+
"Illegal Input, please set one of ['video_file', 'camera_id', 'image_file', 'image_dir']"
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
return input
|
| 96 |
+
|
| 97 |
+
def run_multithreads(self):
|
| 98 |
+
|
| 99 |
+
out = self.predictor.run(self.input)
|
| 100 |
+
return out
|
| 101 |
+
|
| 102 |
+
def run(self):
|
| 103 |
+
out = self.predictor.run(self.input)
|
| 104 |
+
return out
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def get_model_dir_with_list(cfg, args):
|
| 108 |
+
activate_list = args.avtivity_list
|
| 109 |
+
"""
|
| 110 |
+
Auto download inference model if the model_path is a url link.
|
| 111 |
+
Otherwise it will use the model_path directly.
|
| 112 |
+
"""
|
| 113 |
+
for key in cfg.keys():
|
| 114 |
+
if type(cfg[key]) == dict and ((key in activate_list) or
|
| 115 |
+
"enable" not in cfg[key].keys()):
|
| 116 |
+
if "model_dir" in cfg[key].keys():
|
| 117 |
+
model_dir = cfg[key]["model_dir"]
|
| 118 |
+
downloaded_model_dir = auto_download_model(model_dir)
|
| 119 |
+
if downloaded_model_dir:
|
| 120 |
+
model_dir = downloaded_model_dir
|
| 121 |
+
cfg[key]["model_dir"] = model_dir
|
| 122 |
+
print(key, " model dir: ", model_dir)
|
| 123 |
+
elif key == "VEHICLE_PLATE":
|
| 124 |
+
det_model_dir = cfg[key]["det_model_dir"]
|
| 125 |
+
downloaded_det_model_dir = auto_download_model(det_model_dir)
|
| 126 |
+
if downloaded_det_model_dir:
|
| 127 |
+
det_model_dir = downloaded_det_model_dir
|
| 128 |
+
cfg[key]["det_model_dir"] = det_model_dir
|
| 129 |
+
print("det_model_dir model dir: ", det_model_dir)
|
| 130 |
+
|
| 131 |
+
rec_model_dir = cfg[key]["rec_model_dir"]
|
| 132 |
+
downloaded_rec_model_dir = auto_download_model(rec_model_dir)
|
| 133 |
+
if downloaded_rec_model_dir:
|
| 134 |
+
rec_model_dir = downloaded_rec_model_dir
|
| 135 |
+
cfg[key]["rec_model_dir"] = rec_model_dir
|
| 136 |
+
print("rec_model_dir model dir: ", rec_model_dir)
|
| 137 |
+
|
| 138 |
+
if (key == 'ID_BASED_DETACTION' and (key in activate_list)) or (key == 'SKELETON_ACTION' and (key in activate_list))or (key == 'ATTR' and (key in activate_list)) or (key =='ID_BASED_CLSACTION' and (key in activate_list)) or (key=='REID' and (key in activate_list)):
|
| 139 |
+
model_dir = cfg['MOT']["model_dir"]
|
| 140 |
+
downloaded_model_dir = auto_download_model(model_dir)
|
| 141 |
+
if downloaded_model_dir:
|
| 142 |
+
model_dir = downloaded_model_dir
|
| 143 |
+
cfg['MOT']["model_dir"] = model_dir
|
| 144 |
+
print("mot_model_dir model_dir: ", model_dir)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def get_model_dir(cfg):
|
| 148 |
+
"""
|
| 149 |
+
Auto download inference model if the model_path is a url link.
|
| 150 |
+
Otherwise it will use the model_path directly.
|
| 151 |
+
"""
|
| 152 |
+
for key in cfg.keys():
|
| 153 |
+
if type(cfg[key]) == dict and \
|
| 154 |
+
("enable" in cfg[key].keys() and cfg[key]['enable']
|
| 155 |
+
or "enable" not in cfg[key].keys()):
|
| 156 |
+
|
| 157 |
+
if "model_dir" in cfg[key].keys():
|
| 158 |
+
model_dir = cfg[key]["model_dir"]
|
| 159 |
+
downloaded_model_dir = auto_download_model(model_dir)
|
| 160 |
+
if downloaded_model_dir:
|
| 161 |
+
model_dir = downloaded_model_dir
|
| 162 |
+
cfg[key]["model_dir"] = model_dir
|
| 163 |
+
print(key, " model dir: ", model_dir)
|
| 164 |
+
elif key == "VEHICLE_PLATE":
|
| 165 |
+
det_model_dir = cfg[key]["det_model_dir"]
|
| 166 |
+
downloaded_det_model_dir = auto_download_model(det_model_dir)
|
| 167 |
+
if downloaded_det_model_dir:
|
| 168 |
+
det_model_dir = downloaded_det_model_dir
|
| 169 |
+
cfg[key]["det_model_dir"] = det_model_dir
|
| 170 |
+
print("det_model_dir model dir: ", det_model_dir)
|
| 171 |
+
|
| 172 |
+
rec_model_dir = cfg[key]["rec_model_dir"]
|
| 173 |
+
downloaded_rec_model_dir = auto_download_model(rec_model_dir)
|
| 174 |
+
if downloaded_rec_model_dir:
|
| 175 |
+
rec_model_dir = downloaded_rec_model_dir
|
| 176 |
+
cfg[key]["rec_model_dir"] = rec_model_dir
|
| 177 |
+
print("rec_model_dir model dir: ", rec_model_dir)
|
| 178 |
+
|
| 179 |
+
elif key == "MOT": # for idbased and skeletonbased actions
|
| 180 |
+
model_dir = cfg[key]["model_dir"]
|
| 181 |
+
downloaded_model_dir = auto_download_model(model_dir)
|
| 182 |
+
if downloaded_model_dir:
|
| 183 |
+
model_dir = downloaded_model_dir
|
| 184 |
+
cfg[key]["model_dir"] = model_dir
|
| 185 |
+
print("mot_model_dir model_dir: ", model_dir)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class PipePredictor(object):
|
| 189 |
+
"""
|
| 190 |
+
Predictor in single camera
|
| 191 |
+
|
| 192 |
+
The pipeline for image input:
|
| 193 |
+
|
| 194 |
+
1. Detection
|
| 195 |
+
2. Detection -> Attribute
|
| 196 |
+
|
| 197 |
+
The pipeline for video input:
|
| 198 |
+
|
| 199 |
+
1. Tracking
|
| 200 |
+
2. Tracking -> Attribute
|
| 201 |
+
3. Tracking -> KeyPoint -> SkeletonAction Recognition
|
| 202 |
+
4. VideoAction Recognition
|
| 203 |
+
|
| 204 |
+
Args:
|
| 205 |
+
args (argparse.Namespace): arguments in pipeline, which contains environment and runtime settings
|
| 206 |
+
cfg (dict): config of models in pipeline
|
| 207 |
+
is_video (bool): whether the input is video, default as False
|
| 208 |
+
multi_camera (bool): whether to use multi camera in pipeline,
|
| 209 |
+
default as False
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
def __init__(self, args, cfg, is_video=True, multi_camera=False):
|
| 213 |
+
# general module for pphuman and ppvehicle
|
| 214 |
+
activate_list = args.avtivity_list
|
| 215 |
+
self.with_mot = True if 'MOT' in activate_list else False
|
| 216 |
+
self.with_human_attr = True if 'ATTR' in activate_list else False
|
| 217 |
+
if self.with_mot:
|
| 218 |
+
print('Multi-Object Tracking enabled')
|
| 219 |
+
if self.with_human_attr:
|
| 220 |
+
print('Human Attribute Recognition enabled')
|
| 221 |
+
|
| 222 |
+
# only for pphuman
|
| 223 |
+
self.with_skeleton_action = True if 'SKELETON_ACTION' in activate_list else False
|
| 224 |
+
|
| 225 |
+
self.with_video_action = True if 'VIDEO_ACTION' in activate_list else False
|
| 226 |
+
|
| 227 |
+
self.with_idbased_detaction = True if 'ID_BASED_DETACTION' in activate_list else False
|
| 228 |
+
|
| 229 |
+
self.with_idbased_clsaction = True if 'ID_BASED_CLSACTION' in activate_list else False
|
| 230 |
+
|
| 231 |
+
self.with_mtmct = True if 'REID' in activate_list else False
|
| 232 |
+
|
| 233 |
+
if self.with_skeleton_action:
|
| 234 |
+
print('SkeletonAction Recognition enabled')
|
| 235 |
+
if self.with_video_action:
|
| 236 |
+
print('VideoAction Recognition enabled')
|
| 237 |
+
if self.with_idbased_detaction:
|
| 238 |
+
print('IDBASED Detection Action Recognition enabled')
|
| 239 |
+
if self.with_idbased_clsaction:
|
| 240 |
+
print('IDBASED Classification Action Recognition enabled')
|
| 241 |
+
if self.with_mtmct:
|
| 242 |
+
print("MTMCT enabled")
|
| 243 |
+
|
| 244 |
+
self.modebase = {
|
| 245 |
+
"framebased": False,
|
| 246 |
+
"videobased": False,
|
| 247 |
+
"idbased": False,
|
| 248 |
+
"skeletonbased": False
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
self.basemode = {
|
| 252 |
+
"MOT": "idbased",
|
| 253 |
+
"ATTR": "idbased",
|
| 254 |
+
"VIDEO_ACTION": "videobased",
|
| 255 |
+
"SKELETON_ACTION": "skeletonbased",
|
| 256 |
+
"ID_BASED_DETACTION": "idbased",
|
| 257 |
+
"ID_BASED_CLSACTION": "idbased",
|
| 258 |
+
"REID": "idbased",
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
self.is_video = is_video
|
| 262 |
+
self.multi_camera = multi_camera
|
| 263 |
+
self.cfg = cfg
|
| 264 |
+
|
| 265 |
+
self.output_dir = args.output_dir
|
| 266 |
+
self.draw_center_traj = True if 'draw_center_traj' in activate_list else False
|
| 267 |
+
self.secs_interval = args.secs_interval
|
| 268 |
+
self.do_entrance_counting = True if 'do_entrance_counting' in activate_list else False
|
| 269 |
+
self.do_break_in_counting = args.do_break_in_counting
|
| 270 |
+
self.region_type = args.region_type
|
| 271 |
+
self.region_polygon = args.region_polygon
|
| 272 |
+
self.illegal_parking_time = args.illegal_parking_time
|
| 273 |
+
|
| 274 |
+
self.warmup_frame = self.cfg['warmup_frame']
|
| 275 |
+
self.pipeline_res = Result()
|
| 276 |
+
self.pipe_timer = PipeTimer()
|
| 277 |
+
self.file_name = None
|
| 278 |
+
self.collector = DataCollector()
|
| 279 |
+
|
| 280 |
+
self.pushurl = args.pushurl
|
| 281 |
+
|
| 282 |
+
# auto download inference model
|
| 283 |
+
get_model_dir_with_list(self.cfg, args)
|
| 284 |
+
|
| 285 |
+
if self.with_human_attr:
|
| 286 |
+
attr_cfg = self.cfg['ATTR']
|
| 287 |
+
basemode = self.basemode['ATTR']
|
| 288 |
+
self.modebase[basemode] = True
|
| 289 |
+
self.attr_predictor = AttrDetector.init_with_cfg(args, attr_cfg)
|
| 290 |
+
|
| 291 |
+
if not is_video:
|
| 292 |
+
det_cfg = self.cfg['DET']
|
| 293 |
+
model_dir = det_cfg['model_dir']
|
| 294 |
+
batch_size = det_cfg['batch_size']
|
| 295 |
+
self.det_predictor = Detector(
|
| 296 |
+
model_dir, args.device, args.run_mode, batch_size,
|
| 297 |
+
args.trt_min_shape, args.trt_max_shape, args.trt_opt_shape,
|
| 298 |
+
args.trt_calib_mode, args.cpu_threads, args.enable_mkldnn)
|
| 299 |
+
else:
|
| 300 |
+
if self.with_idbased_detaction:
|
| 301 |
+
idbased_detaction_cfg = self.cfg['ID_BASED_DETACTION']
|
| 302 |
+
basemode = self.basemode['ID_BASED_DETACTION']
|
| 303 |
+
self.modebase[basemode] = True
|
| 304 |
+
|
| 305 |
+
self.det_action_predictor = DetActionRecognizer.init_with_cfg(
|
| 306 |
+
args, idbased_detaction_cfg)
|
| 307 |
+
self.det_action_visual_helper = ActionVisualHelper(1)
|
| 308 |
+
|
| 309 |
+
if self.with_idbased_clsaction:
|
| 310 |
+
idbased_clsaction_cfg = self.cfg['ID_BASED_CLSACTION']
|
| 311 |
+
basemode = self.basemode['ID_BASED_CLSACTION']
|
| 312 |
+
self.modebase[basemode] = True
|
| 313 |
+
|
| 314 |
+
self.cls_action_predictor = ClsActionRecognizer.init_with_cfg(
|
| 315 |
+
args, idbased_clsaction_cfg)
|
| 316 |
+
self.cls_action_visual_helper = ActionVisualHelper(1)
|
| 317 |
+
|
| 318 |
+
if self.with_skeleton_action:
|
| 319 |
+
skeleton_action_cfg = self.cfg['SKELETON_ACTION']
|
| 320 |
+
display_frames = skeleton_action_cfg['display_frames']
|
| 321 |
+
self.coord_size = skeleton_action_cfg['coord_size']
|
| 322 |
+
basemode = self.basemode['SKELETON_ACTION']
|
| 323 |
+
self.modebase[basemode] = True
|
| 324 |
+
skeleton_action_frames = skeleton_action_cfg['max_frames']
|
| 325 |
+
|
| 326 |
+
self.skeleton_action_predictor = SkeletonActionRecognizer.init_with_cfg(
|
| 327 |
+
args, skeleton_action_cfg)
|
| 328 |
+
self.skeleton_action_visual_helper = ActionVisualHelper(
|
| 329 |
+
display_frames)
|
| 330 |
+
|
| 331 |
+
kpt_cfg = self.cfg['KPT']
|
| 332 |
+
kpt_model_dir = kpt_cfg['model_dir']
|
| 333 |
+
kpt_batch_size = kpt_cfg['batch_size']
|
| 334 |
+
self.kpt_predictor = KeyPointDetector(
|
| 335 |
+
kpt_model_dir,
|
| 336 |
+
args.device,
|
| 337 |
+
args.run_mode,
|
| 338 |
+
kpt_batch_size,
|
| 339 |
+
args.trt_min_shape,
|
| 340 |
+
args.trt_max_shape,
|
| 341 |
+
args.trt_opt_shape,
|
| 342 |
+
args.trt_calib_mode,
|
| 343 |
+
args.cpu_threads,
|
| 344 |
+
args.enable_mkldnn,
|
| 345 |
+
use_dark=False)
|
| 346 |
+
self.kpt_buff = KeyPointBuff(skeleton_action_frames)
|
| 347 |
+
|
| 348 |
+
if self.with_mtmct:
|
| 349 |
+
reid_cfg = self.cfg['REID']
|
| 350 |
+
basemode = self.basemode['REID']
|
| 351 |
+
self.modebase[basemode] = True
|
| 352 |
+
self.reid_predictor = ReID.init_with_cfg(args, reid_cfg)
|
| 353 |
+
|
| 354 |
+
if self.with_mot or self.modebase["idbased"] or self.modebase[
|
| 355 |
+
"skeletonbased"]:
|
| 356 |
+
mot_cfg = self.cfg['MOT']
|
| 357 |
+
model_dir = mot_cfg['model_dir']
|
| 358 |
+
tracker_config = mot_cfg['tracker_config']
|
| 359 |
+
batch_size = mot_cfg['batch_size']
|
| 360 |
+
skip_frame_num = mot_cfg.get('skip_frame_num', -1)
|
| 361 |
+
basemode = self.basemode['MOT']
|
| 362 |
+
self.modebase[basemode] = True
|
| 363 |
+
self.mot_predictor = SDE_Detector(
|
| 364 |
+
model_dir,
|
| 365 |
+
tracker_config,
|
| 366 |
+
args.device,
|
| 367 |
+
args.run_mode,
|
| 368 |
+
batch_size,
|
| 369 |
+
args.trt_min_shape,
|
| 370 |
+
args.trt_max_shape,
|
| 371 |
+
args.trt_opt_shape,
|
| 372 |
+
args.trt_calib_mode,
|
| 373 |
+
args.cpu_threads,
|
| 374 |
+
args.enable_mkldnn,
|
| 375 |
+
skip_frame_num=skip_frame_num,
|
| 376 |
+
draw_center_traj=self.draw_center_traj,
|
| 377 |
+
secs_interval=self.secs_interval,
|
| 378 |
+
do_entrance_counting=self.do_entrance_counting,
|
| 379 |
+
do_break_in_counting=self.do_break_in_counting,
|
| 380 |
+
region_type=self.region_type,
|
| 381 |
+
region_polygon=self.region_polygon)
|
| 382 |
+
|
| 383 |
+
if self.with_video_action:
|
| 384 |
+
video_action_cfg = self.cfg['VIDEO_ACTION']
|
| 385 |
+
basemode = self.basemode['VIDEO_ACTION']
|
| 386 |
+
self.modebase[basemode] = True
|
| 387 |
+
self.video_action_predictor = VideoActionRecognizer.init_with_cfg(
|
| 388 |
+
args, video_action_cfg)
|
| 389 |
+
|
| 390 |
+
def set_file_name(self, path):
|
| 391 |
+
if path is not None:
|
| 392 |
+
if "." in path:
|
| 393 |
+
self.file_name = path.split(".")[-2]
|
| 394 |
+
else:
|
| 395 |
+
# use camera id
|
| 396 |
+
self.file_name = None
|
| 397 |
+
|
| 398 |
+
def get_result(self):
|
| 399 |
+
return self.collector.get_res()
|
| 400 |
+
|
| 401 |
+
def run(self, input, thread_idx=0):
|
| 402 |
+
|
| 403 |
+
if self.is_video:
|
| 404 |
+
out = self.predict_video(input, thread_idx=thread_idx)
|
| 405 |
+
return out
|
| 406 |
+
else:
|
| 407 |
+
out = self.predict_image(input)
|
| 408 |
+
return out
|
| 409 |
+
|
| 410 |
+
def predict_image(self, input):
|
| 411 |
+
# det
|
| 412 |
+
# det -> attr
|
| 413 |
+
batch_input = [decode_image(input, {})[0]]
|
| 414 |
+
batch_input[0] = cv2.cvtColor(batch_input[0], cv2.COLOR_BGR2RGB)
|
| 415 |
+
# det output format: class, score, xmin, ymin, xmax, ymax
|
| 416 |
+
det_res = self.det_predictor.predict_image(batch_input, visual=False)
|
| 417 |
+
det_res = self.det_predictor.filter_box(det_res,
|
| 418 |
+
self.cfg['crop_thresh'])
|
| 419 |
+
|
| 420 |
+
self.pipeline_res.update(det_res, 'det')
|
| 421 |
+
|
| 422 |
+
if self.with_human_attr:
|
| 423 |
+
crop_inputs = crop_image_with_det(batch_input, det_res)
|
| 424 |
+
attr_res_list = []
|
| 425 |
+
|
| 426 |
+
for crop_input in crop_inputs:
|
| 427 |
+
attr_res = self.attr_predictor.predict_image(
|
| 428 |
+
crop_input, visual=False)
|
| 429 |
+
attr_res_list.extend(attr_res['output'])
|
| 430 |
+
|
| 431 |
+
attr_res = {'output': attr_res_list}
|
| 432 |
+
self.pipeline_res.update(attr_res, 'attr')
|
| 433 |
+
|
| 434 |
+
return self.visualize_image(batch_input, self.pipeline_res)
|
| 435 |
+
|
| 436 |
+
def predict_video(self, video_file, thread_idx=0):
|
| 437 |
+
# mot
|
| 438 |
+
# mot -> attr
|
| 439 |
+
# mot -> pose -> action
|
| 440 |
+
capture = cv2.VideoCapture(video_file)
|
| 441 |
+
|
| 442 |
+
# Get Video info : resolution, fps, frame count
|
| 443 |
+
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 444 |
+
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 445 |
+
fps = int(capture.get(cv2.CAP_PROP_FPS))
|
| 446 |
+
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 447 |
+
print("video fps: %d, frame_count: %d" % (fps, frame_count))
|
| 448 |
+
|
| 449 |
+
video_out_name = 'output' if self.file_name is None else self.file_name
|
| 450 |
+
out_path = video_out_name + "_output.mp4"
|
| 451 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 452 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
|
| 453 |
+
|
| 454 |
+
frame_id = 0
|
| 455 |
+
|
| 456 |
+
entrance, records, center_traj = None, None, None
|
| 457 |
+
if self.draw_center_traj:
|
| 458 |
+
center_traj = [{}]
|
| 459 |
+
id_set = set()
|
| 460 |
+
interval_id_set = set()
|
| 461 |
+
in_id_list = list()
|
| 462 |
+
out_id_list = list()
|
| 463 |
+
prev_center = dict()
|
| 464 |
+
records = list()
|
| 465 |
+
if self.do_entrance_counting or self.do_break_in_counting or self.illegal_parking_time != -1:
|
| 466 |
+
if self.region_type == 'horizontal':
|
| 467 |
+
entrance = [0, height / 2., width, height / 2.]
|
| 468 |
+
elif self.region_type == 'vertical':
|
| 469 |
+
entrance = [width / 2, 0., width / 2, height]
|
| 470 |
+
elif self.region_type == 'custom':
|
| 471 |
+
entrance = []
|
| 472 |
+
assert len(
|
| 473 |
+
self.region_polygon
|
| 474 |
+
) % 2 == 0, "region_polygon should be pairs of coords points when do break_in counting."
|
| 475 |
+
assert len(
|
| 476 |
+
self.region_polygon
|
| 477 |
+
) > 6, 'region_type is custom, region_polygon should be at least 3 pairs of point coords.'
|
| 478 |
+
|
| 479 |
+
for i in range(0, len(self.region_polygon), 2):
|
| 480 |
+
entrance.append(
|
| 481 |
+
[self.region_polygon[i], self.region_polygon[i + 1]])
|
| 482 |
+
entrance.append([width, height])
|
| 483 |
+
else:
|
| 484 |
+
raise ValueError("region_type:{} unsupported.".format(
|
| 485 |
+
self.region_type))
|
| 486 |
+
|
| 487 |
+
video_fps = fps
|
| 488 |
+
|
| 489 |
+
video_action_imgs = []
|
| 490 |
+
|
| 491 |
+
if self.with_video_action:
|
| 492 |
+
short_size = self.cfg["VIDEO_ACTION"]["short_size"]
|
| 493 |
+
scale = ShortSizeScale(short_size)
|
| 494 |
+
|
| 495 |
+
object_in_region_info = {
|
| 496 |
+
} # store info for vehicle parking in region
|
| 497 |
+
illegal_parking_dict = None
|
| 498 |
+
|
| 499 |
+
while (1):
|
| 500 |
+
if frame_id % 10 == 0:
|
| 501 |
+
print('Thread: {}; frame id: {}'.format(thread_idx, frame_id))
|
| 502 |
+
|
| 503 |
+
ret, frame = capture.read()
|
| 504 |
+
if not ret:
|
| 505 |
+
break
|
| 506 |
+
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 507 |
+
if frame_id > self.warmup_frame:
|
| 508 |
+
self.pipe_timer.total_time.start()
|
| 509 |
+
|
| 510 |
+
if self.modebase["idbased"] or self.modebase["skeletonbased"]:
|
| 511 |
+
if frame_id > self.warmup_frame:
|
| 512 |
+
self.pipe_timer.module_time['mot'].start()
|
| 513 |
+
|
| 514 |
+
mot_skip_frame_num = self.mot_predictor.skip_frame_num
|
| 515 |
+
reuse_det_result = False
|
| 516 |
+
if mot_skip_frame_num > 1 and frame_id > 0 and frame_id % mot_skip_frame_num > 0:
|
| 517 |
+
reuse_det_result = True
|
| 518 |
+
res = self.mot_predictor.predict_image(
|
| 519 |
+
[copy.deepcopy(frame_rgb)],
|
| 520 |
+
visual=False,
|
| 521 |
+
reuse_det_result=reuse_det_result)
|
| 522 |
+
|
| 523 |
+
# mot output format: id, class, score, xmin, ymin, xmax, ymax
|
| 524 |
+
mot_res = parse_mot_res(res)
|
| 525 |
+
if frame_id > self.warmup_frame:
|
| 526 |
+
self.pipe_timer.module_time['mot'].end()
|
| 527 |
+
self.pipe_timer.track_num += len(mot_res['boxes'])
|
| 528 |
+
|
| 529 |
+
if frame_id % 10 == 0:
|
| 530 |
+
print("Thread: {}; trackid number: {}".format(
|
| 531 |
+
thread_idx, len(mot_res['boxes'])))
|
| 532 |
+
|
| 533 |
+
# flow_statistic only support single class MOT
|
| 534 |
+
boxes, scores, ids = res[0] # batch size = 1 in MOT
|
| 535 |
+
mot_result = (frame_id + 1, boxes[0], scores[0],
|
| 536 |
+
ids[0]) # single class
|
| 537 |
+
statistic = flow_statistic(
|
| 538 |
+
mot_result,
|
| 539 |
+
self.secs_interval,
|
| 540 |
+
self.do_entrance_counting,
|
| 541 |
+
self.do_break_in_counting,
|
| 542 |
+
self.region_type,
|
| 543 |
+
video_fps,
|
| 544 |
+
entrance,
|
| 545 |
+
id_set,
|
| 546 |
+
interval_id_set,
|
| 547 |
+
in_id_list,
|
| 548 |
+
out_id_list,
|
| 549 |
+
prev_center,
|
| 550 |
+
records,
|
| 551 |
+
ids2names=self.mot_predictor.pred_config.labels)
|
| 552 |
+
records = statistic['records']
|
| 553 |
+
|
| 554 |
+
if self.illegal_parking_time != -1:
|
| 555 |
+
object_in_region_info, illegal_parking_dict = update_object_info(
|
| 556 |
+
object_in_region_info, mot_result, self.region_type,
|
| 557 |
+
entrance, video_fps, self.illegal_parking_time)
|
| 558 |
+
if len(illegal_parking_dict) != 0:
|
| 559 |
+
# build relationship between id and plate
|
| 560 |
+
for key, value in illegal_parking_dict.items():
|
| 561 |
+
plate = self.collector.get_carlp(key)
|
| 562 |
+
illegal_parking_dict[key]['plate'] = plate
|
| 563 |
+
|
| 564 |
+
# nothing detected
|
| 565 |
+
if len(mot_res['boxes']) == 0:
|
| 566 |
+
frame_id += 1
|
| 567 |
+
if frame_id > self.warmup_frame:
|
| 568 |
+
self.pipe_timer.img_num += 1
|
| 569 |
+
self.pipe_timer.total_time.end()
|
| 570 |
+
if self.cfg['visual']:
|
| 571 |
+
_, _, fps = self.pipe_timer.get_total_time()
|
| 572 |
+
im = self.visualize_video(frame, mot_res, frame_id,
|
| 573 |
+
fps, entrance, records,
|
| 574 |
+
center_traj) # visualize
|
| 575 |
+
if len(self.pushurl) > 0:
|
| 576 |
+
pushstream.pipe.stdin.write(im.tobytes())
|
| 577 |
+
else:
|
| 578 |
+
writer.write(im)
|
| 579 |
+
if self.file_name is None: # use camera_id
|
| 580 |
+
cv2.imshow('Paddle-Pipeline', im)
|
| 581 |
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
| 582 |
+
break
|
| 583 |
+
continue
|
| 584 |
+
|
| 585 |
+
self.pipeline_res.update(mot_res, 'mot')
|
| 586 |
+
crop_input, new_bboxes, ori_bboxes = crop_image_with_mot(
|
| 587 |
+
frame_rgb, mot_res)
|
| 588 |
+
|
| 589 |
+
if self.with_human_attr:
|
| 590 |
+
if frame_id > self.warmup_frame:
|
| 591 |
+
self.pipe_timer.module_time['attr'].start()
|
| 592 |
+
attr_res = self.attr_predictor.predict_image(
|
| 593 |
+
crop_input, visual=False)
|
| 594 |
+
if frame_id > self.warmup_frame:
|
| 595 |
+
self.pipe_timer.module_time['attr'].end()
|
| 596 |
+
self.pipeline_res.update(attr_res, 'attr')
|
| 597 |
+
|
| 598 |
+
if self.with_idbased_detaction:
|
| 599 |
+
if frame_id > self.warmup_frame:
|
| 600 |
+
self.pipe_timer.module_time['det_action'].start()
|
| 601 |
+
det_action_res = self.det_action_predictor.predict(
|
| 602 |
+
crop_input, mot_res)
|
| 603 |
+
if frame_id > self.warmup_frame:
|
| 604 |
+
self.pipe_timer.module_time['det_action'].end()
|
| 605 |
+
self.pipeline_res.update(det_action_res, 'det_action')
|
| 606 |
+
|
| 607 |
+
if self.cfg['visual']:
|
| 608 |
+
self.det_action_visual_helper.update(det_action_res)
|
| 609 |
+
|
| 610 |
+
if self.with_idbased_clsaction:
|
| 611 |
+
if frame_id > self.warmup_frame:
|
| 612 |
+
self.pipe_timer.module_time['cls_action'].start()
|
| 613 |
+
cls_action_res = self.cls_action_predictor.predict_with_mot(
|
| 614 |
+
crop_input, mot_res)
|
| 615 |
+
if frame_id > self.warmup_frame:
|
| 616 |
+
self.pipe_timer.module_time['cls_action'].end()
|
| 617 |
+
self.pipeline_res.update(cls_action_res, 'cls_action')
|
| 618 |
+
|
| 619 |
+
if self.cfg['visual']:
|
| 620 |
+
self.cls_action_visual_helper.update(cls_action_res)
|
| 621 |
+
|
| 622 |
+
if self.with_skeleton_action:
|
| 623 |
+
if frame_id > self.warmup_frame:
|
| 624 |
+
self.pipe_timer.module_time['kpt'].start()
|
| 625 |
+
kpt_pred = self.kpt_predictor.predict_image(
|
| 626 |
+
crop_input, visual=False)
|
| 627 |
+
keypoint_vector, score_vector = translate_to_ori_images(
|
| 628 |
+
kpt_pred, np.array(new_bboxes))
|
| 629 |
+
kpt_res = {}
|
| 630 |
+
kpt_res['keypoint'] = [
|
| 631 |
+
keypoint_vector.tolist(), score_vector.tolist()
|
| 632 |
+
] if len(keypoint_vector) > 0 else [[], []]
|
| 633 |
+
kpt_res['bbox'] = ori_bboxes
|
| 634 |
+
if frame_id > self.warmup_frame:
|
| 635 |
+
self.pipe_timer.module_time['kpt'].end()
|
| 636 |
+
|
| 637 |
+
self.pipeline_res.update(kpt_res, 'kpt')
|
| 638 |
+
|
| 639 |
+
self.kpt_buff.update(kpt_res,
|
| 640 |
+
mot_res) # collect kpt output
|
| 641 |
+
state = self.kpt_buff.get_state(
|
| 642 |
+
) # whether frame num is enough or lost tracker
|
| 643 |
+
|
| 644 |
+
skeleton_action_res = {}
|
| 645 |
+
if state:
|
| 646 |
+
if frame_id > self.warmup_frame:
|
| 647 |
+
self.pipe_timer.module_time[
|
| 648 |
+
'skeleton_action'].start()
|
| 649 |
+
collected_keypoint = self.kpt_buff.get_collected_keypoint(
|
| 650 |
+
) # reoragnize kpt output with ID
|
| 651 |
+
skeleton_action_input = parse_mot_keypoint(
|
| 652 |
+
collected_keypoint, self.coord_size)
|
| 653 |
+
skeleton_action_res = self.skeleton_action_predictor.predict_skeleton_with_mot(
|
| 654 |
+
skeleton_action_input)
|
| 655 |
+
if frame_id > self.warmup_frame:
|
| 656 |
+
self.pipe_timer.module_time['skeleton_action'].end(
|
| 657 |
+
)
|
| 658 |
+
self.pipeline_res.update(skeleton_action_res,
|
| 659 |
+
'skeleton_action')
|
| 660 |
+
|
| 661 |
+
if self.cfg['visual']:
|
| 662 |
+
self.skeleton_action_visual_helper.update(
|
| 663 |
+
skeleton_action_res)
|
| 664 |
+
|
| 665 |
+
if self.with_mtmct and frame_id % 10 == 0:
|
| 666 |
+
crop_input, img_qualities, rects = self.reid_predictor.crop_image_with_mot(
|
| 667 |
+
frame_rgb, mot_res)
|
| 668 |
+
if frame_id > self.warmup_frame:
|
| 669 |
+
self.pipe_timer.module_time['reid'].start()
|
| 670 |
+
reid_res = self.reid_predictor.predict_batch(crop_input)
|
| 671 |
+
|
| 672 |
+
if frame_id > self.warmup_frame:
|
| 673 |
+
self.pipe_timer.module_time['reid'].end()
|
| 674 |
+
|
| 675 |
+
reid_res_dict = {
|
| 676 |
+
'features': reid_res,
|
| 677 |
+
"qualities": img_qualities,
|
| 678 |
+
"rects": rects
|
| 679 |
+
}
|
| 680 |
+
self.pipeline_res.update(reid_res_dict, 'reid')
|
| 681 |
+
else:
|
| 682 |
+
self.pipeline_res.clear('reid')
|
| 683 |
+
|
| 684 |
+
if self.with_video_action:
|
| 685 |
+
# get the params
|
| 686 |
+
frame_len = self.cfg["VIDEO_ACTION"]["frame_len"]
|
| 687 |
+
sample_freq = self.cfg["VIDEO_ACTION"]["sample_freq"]
|
| 688 |
+
|
| 689 |
+
if sample_freq * frame_len > frame_count: # video is too short
|
| 690 |
+
sample_freq = int(frame_count / frame_len)
|
| 691 |
+
|
| 692 |
+
# filter the warmup frames
|
| 693 |
+
if frame_id > self.warmup_frame:
|
| 694 |
+
self.pipe_timer.module_time['video_action'].start()
|
| 695 |
+
|
| 696 |
+
# collect frames
|
| 697 |
+
if frame_id % sample_freq == 0:
|
| 698 |
+
# Scale image
|
| 699 |
+
scaled_img = scale(frame_rgb)
|
| 700 |
+
video_action_imgs.append(scaled_img)
|
| 701 |
+
|
| 702 |
+
# the number of collected frames is enough to predict video action
|
| 703 |
+
if len(video_action_imgs) == frame_len:
|
| 704 |
+
classes, scores = self.video_action_predictor.predict(
|
| 705 |
+
video_action_imgs)
|
| 706 |
+
if frame_id > self.warmup_frame:
|
| 707 |
+
self.pipe_timer.module_time['video_action'].end()
|
| 708 |
+
|
| 709 |
+
video_action_res = {
|
| 710 |
+
"class": classes[0],
|
| 711 |
+
"score": scores[0]
|
| 712 |
+
}
|
| 713 |
+
self.pipeline_res.update(video_action_res, 'video_action')
|
| 714 |
+
|
| 715 |
+
print("video_action_res:", video_action_res)
|
| 716 |
+
|
| 717 |
+
video_action_imgs.clear() # next clip
|
| 718 |
+
|
| 719 |
+
self.collector.append(frame_id, self.pipeline_res)
|
| 720 |
+
|
| 721 |
+
if frame_id > self.warmup_frame:
|
| 722 |
+
self.pipe_timer.img_num += 1
|
| 723 |
+
self.pipe_timer.total_time.end()
|
| 724 |
+
frame_id += 1
|
| 725 |
+
|
| 726 |
+
if self.cfg['visual']:
|
| 727 |
+
_, _, fps = self.pipe_timer.get_total_time()
|
| 728 |
+
|
| 729 |
+
im = self.visualize_video(frame, self.pipeline_res,
|
| 730 |
+
self.collector, frame_id, fps,
|
| 731 |
+
entrance, records, center_traj,
|
| 732 |
+
self.illegal_parking_time != -1,
|
| 733 |
+
illegal_parking_dict) # visualize
|
| 734 |
+
if len(self.pushurl) > 0:
|
| 735 |
+
pushstream.pipe.stdin.write(im.tobytes())
|
| 736 |
+
else:
|
| 737 |
+
writer.write(im)
|
| 738 |
+
if self.cfg['visual'] and len(self.pushurl) == 0:
|
| 739 |
+
writer.release()
|
| 740 |
+
|
| 741 |
+
return out_path
|
| 742 |
+
|
| 743 |
+
def visualize_video(self,
|
| 744 |
+
image,
|
| 745 |
+
result,
|
| 746 |
+
collector,
|
| 747 |
+
frame_id,
|
| 748 |
+
fps,
|
| 749 |
+
entrance=None,
|
| 750 |
+
records=None,
|
| 751 |
+
center_traj=None,
|
| 752 |
+
do_illegal_parking_recognition=False,
|
| 753 |
+
illegal_parking_dict=None):
|
| 754 |
+
mot_res = copy.deepcopy(result.get('mot'))
|
| 755 |
+
if mot_res is not None:
|
| 756 |
+
ids = mot_res['boxes'][:, 0]
|
| 757 |
+
scores = mot_res['boxes'][:, 2]
|
| 758 |
+
boxes = mot_res['boxes'][:, 3:]
|
| 759 |
+
boxes[:, 2] = boxes[:, 2] - boxes[:, 0]
|
| 760 |
+
boxes[:, 3] = boxes[:, 3] - boxes[:, 1]
|
| 761 |
+
else:
|
| 762 |
+
boxes = np.zeros([0, 4])
|
| 763 |
+
ids = np.zeros([0])
|
| 764 |
+
scores = np.zeros([0])
|
| 765 |
+
|
| 766 |
+
# single class, still need to be defaultdict type for ploting
|
| 767 |
+
num_classes = 1
|
| 768 |
+
online_tlwhs = defaultdict(list)
|
| 769 |
+
online_scores = defaultdict(list)
|
| 770 |
+
online_ids = defaultdict(list)
|
| 771 |
+
online_tlwhs[0] = boxes
|
| 772 |
+
online_scores[0] = scores
|
| 773 |
+
online_ids[0] = ids
|
| 774 |
+
|
| 775 |
+
if mot_res is not None:
|
| 776 |
+
image = plot_tracking_dict(
|
| 777 |
+
image,
|
| 778 |
+
num_classes,
|
| 779 |
+
online_tlwhs,
|
| 780 |
+
online_ids,
|
| 781 |
+
online_scores,
|
| 782 |
+
frame_id=frame_id,
|
| 783 |
+
fps=fps,
|
| 784 |
+
ids2names=self.mot_predictor.pred_config.labels,
|
| 785 |
+
do_entrance_counting=self.do_entrance_counting,
|
| 786 |
+
do_break_in_counting=self.do_break_in_counting,
|
| 787 |
+
do_illegal_parking_recognition=do_illegal_parking_recognition,
|
| 788 |
+
illegal_parking_dict=illegal_parking_dict,
|
| 789 |
+
entrance=entrance,
|
| 790 |
+
records=records,
|
| 791 |
+
center_traj=center_traj)
|
| 792 |
+
|
| 793 |
+
human_attr_res = result.get('attr')
|
| 794 |
+
if human_attr_res is not None:
|
| 795 |
+
boxes = mot_res['boxes'][:, 1:]
|
| 796 |
+
human_attr_res = human_attr_res['output']
|
| 797 |
+
image = visualize_attr(image, human_attr_res, boxes)
|
| 798 |
+
image = np.array(image)
|
| 799 |
+
|
| 800 |
+
kpt_res = result.get('kpt')
|
| 801 |
+
if kpt_res is not None:
|
| 802 |
+
image = visualize_pose(
|
| 803 |
+
image,
|
| 804 |
+
kpt_res,
|
| 805 |
+
visual_thresh=self.cfg['kpt_thresh'],
|
| 806 |
+
returnimg=True)
|
| 807 |
+
|
| 808 |
+
video_action_res = result.get('video_action')
|
| 809 |
+
if video_action_res is not None:
|
| 810 |
+
video_action_score = None
|
| 811 |
+
if video_action_res and video_action_res["class"] == 1:
|
| 812 |
+
video_action_score = video_action_res["score"]
|
| 813 |
+
mot_boxes = None
|
| 814 |
+
if mot_res:
|
| 815 |
+
mot_boxes = mot_res['boxes']
|
| 816 |
+
image = visualize_action(
|
| 817 |
+
image,
|
| 818 |
+
mot_boxes,
|
| 819 |
+
action_visual_collector=None,
|
| 820 |
+
action_text="SkeletonAction",
|
| 821 |
+
video_action_score=video_action_score,
|
| 822 |
+
video_action_text="Fight")
|
| 823 |
+
|
| 824 |
+
visual_helper_for_display = []
|
| 825 |
+
action_to_display = []
|
| 826 |
+
|
| 827 |
+
skeleton_action_res = result.get('skeleton_action')
|
| 828 |
+
if skeleton_action_res is not None:
|
| 829 |
+
visual_helper_for_display.append(
|
| 830 |
+
self.skeleton_action_visual_helper)
|
| 831 |
+
action_to_display.append("Falling")
|
| 832 |
+
|
| 833 |
+
det_action_res = result.get('det_action')
|
| 834 |
+
if det_action_res is not None:
|
| 835 |
+
visual_helper_for_display.append(self.det_action_visual_helper)
|
| 836 |
+
action_to_display.append("Smoking")
|
| 837 |
+
|
| 838 |
+
cls_action_res = result.get('cls_action')
|
| 839 |
+
if cls_action_res is not None:
|
| 840 |
+
visual_helper_for_display.append(self.cls_action_visual_helper)
|
| 841 |
+
action_to_display.append("Calling")
|
| 842 |
+
|
| 843 |
+
if len(visual_helper_for_display) > 0:
|
| 844 |
+
image = visualize_action(image, mot_res['boxes'],
|
| 845 |
+
visual_helper_for_display,
|
| 846 |
+
action_to_display)
|
| 847 |
+
|
| 848 |
+
return image
|
| 849 |
+
|
| 850 |
+
def visualize_image(self, images, result):
|
| 851 |
+
|
| 852 |
+
det_res = result.get('det')
|
| 853 |
+
human_attr_res = result.get('attr')
|
| 854 |
+
vehicle_attr_res = result.get('vehicle_attr')
|
| 855 |
+
vehicleplate_res = result.get('vehicleplate')
|
| 856 |
+
|
| 857 |
+
if det_res is not None:
|
| 858 |
+
det_res_i = {}
|
| 859 |
+
boxes_num_i = det_res['boxes_num'][0]
|
| 860 |
+
det_res_i['boxes'] = det_res['boxes'][0:0 + boxes_num_i, :]
|
| 861 |
+
im = visualize_box_mask(
|
| 862 |
+
images[0],
|
| 863 |
+
det_res_i,
|
| 864 |
+
labels=['target'],
|
| 865 |
+
threshold=self.cfg['crop_thresh'])
|
| 866 |
+
im = np.ascontiguousarray(np.copy(im))
|
| 867 |
+
im = cv2.cvtColor(im, cv2.COLOR_RGB2BGR)
|
| 868 |
+
if human_attr_res is not None:
|
| 869 |
+
human_attr_res_i = human_attr_res['output'][0:0 + boxes_num_i]
|
| 870 |
+
im = visualize_attr(im, human_attr_res_i, det_res_i['boxes'])
|
| 871 |
+
|
| 872 |
+
return im
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
def pp_humanv2(input_date, avtivity_list):
|
| 876 |
+
|
| 877 |
+
paddle.enable_static()
|
| 878 |
+
|
| 879 |
+
# parse params from command
|
| 880 |
+
parser = argsparser()
|
| 881 |
+
FLAGS = parser.parse_args()
|
| 882 |
+
FLAGS.device = FLAGS.device.upper()
|
| 883 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 884 |
+
], "device should be CPU, GPU or XPU"
|
| 885 |
+
|
| 886 |
+
cfg = merge_cfg(FLAGS) # use command params to update config
|
| 887 |
+
|
| 888 |
+
if isinstance(input_date, str):
|
| 889 |
+
FLAGS.video_file = input_date
|
| 890 |
+
else:
|
| 891 |
+
FLAGS.image_file = input_date
|
| 892 |
+
|
| 893 |
+
FLAGS.avtivity_list = avtivity_list
|
| 894 |
+
|
| 895 |
+
pipeline = Pipeline(FLAGS, cfg)
|
| 896 |
+
out = pipeline.run_multithreads()
|
| 897 |
+
|
| 898 |
+
return out
|
pipeline/pphuman/action_infer.py
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import yaml
|
| 17 |
+
import glob
|
| 18 |
+
|
| 19 |
+
import cv2
|
| 20 |
+
import numpy as np
|
| 21 |
+
import math
|
| 22 |
+
import paddle
|
| 23 |
+
import sys
|
| 24 |
+
|
| 25 |
+
# add deploy path of PadleDetection to sys.path
|
| 26 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'] * 2)))
|
| 27 |
+
sys.path.insert(0, parent_path)
|
| 28 |
+
|
| 29 |
+
from paddle.inference import Config, create_predictor
|
| 30 |
+
from python.utils import argsparser, Timer, get_current_memory_mb
|
| 31 |
+
from python.benchmark_utils import PaddleInferBenchmark
|
| 32 |
+
from python.infer import Detector, print_arguments
|
| 33 |
+
from attr_infer import AttrDetector
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class SkeletonActionRecognizer(Detector):
|
| 37 |
+
"""
|
| 38 |
+
Args:
|
| 39 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 40 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 41 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 42 |
+
batch_size (int): size of pre batch in inference
|
| 43 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 44 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 45 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 46 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 47 |
+
calibration, trt_calib_mode need to set True
|
| 48 |
+
cpu_threads (int): cpu threads
|
| 49 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 50 |
+
threshold (float): The threshold of score for visualization
|
| 51 |
+
window_size(int): Temporal size of skeleton feature.
|
| 52 |
+
random_pad (bool): Whether do random padding when frame length < window_size.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
def __init__(self,
|
| 56 |
+
model_dir,
|
| 57 |
+
device='CPU',
|
| 58 |
+
run_mode='paddle',
|
| 59 |
+
batch_size=1,
|
| 60 |
+
trt_min_shape=1,
|
| 61 |
+
trt_max_shape=1280,
|
| 62 |
+
trt_opt_shape=640,
|
| 63 |
+
trt_calib_mode=False,
|
| 64 |
+
cpu_threads=1,
|
| 65 |
+
enable_mkldnn=False,
|
| 66 |
+
output_dir='output',
|
| 67 |
+
threshold=0.5,
|
| 68 |
+
window_size=100,
|
| 69 |
+
random_pad=False):
|
| 70 |
+
assert batch_size == 1, "SkeletonActionRecognizer only support batch_size=1 now."
|
| 71 |
+
super(SkeletonActionRecognizer, self).__init__(
|
| 72 |
+
model_dir=model_dir,
|
| 73 |
+
device=device,
|
| 74 |
+
run_mode=run_mode,
|
| 75 |
+
batch_size=batch_size,
|
| 76 |
+
trt_min_shape=trt_min_shape,
|
| 77 |
+
trt_max_shape=trt_max_shape,
|
| 78 |
+
trt_opt_shape=trt_opt_shape,
|
| 79 |
+
trt_calib_mode=trt_calib_mode,
|
| 80 |
+
cpu_threads=cpu_threads,
|
| 81 |
+
enable_mkldnn=enable_mkldnn,
|
| 82 |
+
output_dir=output_dir,
|
| 83 |
+
threshold=threshold,
|
| 84 |
+
delete_shuffle_pass=True)
|
| 85 |
+
|
| 86 |
+
@classmethod
|
| 87 |
+
def init_with_cfg(cls, args, cfg):
|
| 88 |
+
return cls(model_dir=cfg['model_dir'],
|
| 89 |
+
batch_size=cfg['batch_size'],
|
| 90 |
+
window_size=cfg['max_frames'],
|
| 91 |
+
device=args.device,
|
| 92 |
+
run_mode=args.run_mode,
|
| 93 |
+
trt_min_shape=args.trt_min_shape,
|
| 94 |
+
trt_max_shape=args.trt_max_shape,
|
| 95 |
+
trt_opt_shape=args.trt_opt_shape,
|
| 96 |
+
trt_calib_mode=args.trt_calib_mode,
|
| 97 |
+
cpu_threads=args.cpu_threads,
|
| 98 |
+
enable_mkldnn=args.enable_mkldnn)
|
| 99 |
+
|
| 100 |
+
def predict(self, repeats=1):
|
| 101 |
+
'''
|
| 102 |
+
Args:
|
| 103 |
+
repeats (int): repeat number for prediction
|
| 104 |
+
Returns:
|
| 105 |
+
results (dict):
|
| 106 |
+
'''
|
| 107 |
+
# model prediction
|
| 108 |
+
output_names = self.predictor.get_output_names()
|
| 109 |
+
for i in range(repeats):
|
| 110 |
+
self.predictor.run()
|
| 111 |
+
output_tensor = self.predictor.get_output_handle(output_names[0])
|
| 112 |
+
np_output = output_tensor.copy_to_cpu()
|
| 113 |
+
result = dict(output=np_output)
|
| 114 |
+
return result
|
| 115 |
+
|
| 116 |
+
def predict_skeleton(self, skeleton_list, run_benchmark=False, repeats=1):
|
| 117 |
+
results = []
|
| 118 |
+
for i, skeleton in enumerate(skeleton_list):
|
| 119 |
+
if run_benchmark:
|
| 120 |
+
# preprocess
|
| 121 |
+
inputs = self.preprocess(skeleton) # warmup
|
| 122 |
+
self.det_times.preprocess_time_s.start()
|
| 123 |
+
inputs = self.preprocess(skeleton)
|
| 124 |
+
self.det_times.preprocess_time_s.end()
|
| 125 |
+
|
| 126 |
+
# model prediction
|
| 127 |
+
result = self.predict(repeats=repeats) # warmup
|
| 128 |
+
self.det_times.inference_time_s.start()
|
| 129 |
+
result = self.predict(repeats=repeats)
|
| 130 |
+
self.det_times.inference_time_s.end(repeats=repeats)
|
| 131 |
+
|
| 132 |
+
# postprocess
|
| 133 |
+
result_warmup = self.postprocess(inputs, result) # warmup
|
| 134 |
+
self.det_times.postprocess_time_s.start()
|
| 135 |
+
result = self.postprocess(inputs, result)
|
| 136 |
+
self.det_times.postprocess_time_s.end()
|
| 137 |
+
self.det_times.img_num += len(skeleton)
|
| 138 |
+
|
| 139 |
+
cm, gm, gu = get_current_memory_mb()
|
| 140 |
+
self.cpu_mem += cm
|
| 141 |
+
self.gpu_mem += gm
|
| 142 |
+
self.gpu_util += gu
|
| 143 |
+
else:
|
| 144 |
+
# preprocess
|
| 145 |
+
self.det_times.preprocess_time_s.start()
|
| 146 |
+
inputs = self.preprocess(skeleton)
|
| 147 |
+
self.det_times.preprocess_time_s.end()
|
| 148 |
+
|
| 149 |
+
# model prediction
|
| 150 |
+
self.det_times.inference_time_s.start()
|
| 151 |
+
result = self.predict()
|
| 152 |
+
self.det_times.inference_time_s.end()
|
| 153 |
+
|
| 154 |
+
# postprocess
|
| 155 |
+
self.det_times.postprocess_time_s.start()
|
| 156 |
+
result = self.postprocess(inputs, result)
|
| 157 |
+
self.det_times.postprocess_time_s.end()
|
| 158 |
+
self.det_times.img_num += len(skeleton)
|
| 159 |
+
|
| 160 |
+
results.append(result)
|
| 161 |
+
return results
|
| 162 |
+
|
| 163 |
+
def predict_skeleton_with_mot(self, skeleton_with_mot,
|
| 164 |
+
run_benchmark=False):
|
| 165 |
+
"""
|
| 166 |
+
skeleton_with_mot (dict): includes individual skeleton sequences, which shape is [C, T, K, 1]
|
| 167 |
+
and its corresponding track id.
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
skeleton_list = skeleton_with_mot["skeleton"]
|
| 171 |
+
mot_id = skeleton_with_mot["mot_id"]
|
| 172 |
+
act_res = self.predict_skeleton(
|
| 173 |
+
skeleton_list, run_benchmark, repeats=1)
|
| 174 |
+
results = list(zip(mot_id, act_res))
|
| 175 |
+
return results
|
| 176 |
+
|
| 177 |
+
def preprocess(self, data):
|
| 178 |
+
preprocess_ops = []
|
| 179 |
+
for op_info in self.pred_config.preprocess_infos:
|
| 180 |
+
new_op_info = op_info.copy()
|
| 181 |
+
op_type = new_op_info.pop('type')
|
| 182 |
+
preprocess_ops.append(eval(op_type)(**new_op_info))
|
| 183 |
+
|
| 184 |
+
input_lst = []
|
| 185 |
+
data = action_preprocess(data, preprocess_ops)
|
| 186 |
+
input_lst.append(data)
|
| 187 |
+
input_names = self.predictor.get_input_names()
|
| 188 |
+
inputs = {}
|
| 189 |
+
inputs['data_batch_0'] = np.stack(input_lst, axis=0).astype('float32')
|
| 190 |
+
|
| 191 |
+
for i in range(len(input_names)):
|
| 192 |
+
input_tensor = self.predictor.get_input_handle(input_names[i])
|
| 193 |
+
input_tensor.copy_from_cpu(inputs[input_names[i]])
|
| 194 |
+
|
| 195 |
+
return inputs
|
| 196 |
+
|
| 197 |
+
def postprocess(self, inputs, result):
|
| 198 |
+
# postprocess output of predictor
|
| 199 |
+
output_logit = result['output'][0]
|
| 200 |
+
classes = np.argpartition(output_logit, -1)[-1:]
|
| 201 |
+
classes = classes[np.argsort(-output_logit[classes])]
|
| 202 |
+
scores = output_logit[classes]
|
| 203 |
+
result = {'class': classes, 'score': scores}
|
| 204 |
+
return result
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def action_preprocess(input, preprocess_ops):
|
| 208 |
+
"""
|
| 209 |
+
input (str | numpy.array): if input is str, it should be a legal file path with numpy array saved.
|
| 210 |
+
Otherwise it should be numpy.array as direct input.
|
| 211 |
+
return (numpy.array)
|
| 212 |
+
"""
|
| 213 |
+
if isinstance(input, str):
|
| 214 |
+
assert os.path.isfile(input) is not None, "{0} not exists".format(
|
| 215 |
+
input)
|
| 216 |
+
data = np.load(input)
|
| 217 |
+
else:
|
| 218 |
+
data = input
|
| 219 |
+
for operator in preprocess_ops:
|
| 220 |
+
data = operator(data)
|
| 221 |
+
return data
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class AutoPadding(object):
|
| 225 |
+
"""
|
| 226 |
+
Sample or Padding frame skeleton feature.
|
| 227 |
+
Args:
|
| 228 |
+
window_size (int): Temporal size of skeleton feature.
|
| 229 |
+
random_pad (bool): Whether do random padding when frame length < window size. Default: False.
|
| 230 |
+
"""
|
| 231 |
+
|
| 232 |
+
def __init__(self, window_size=100, random_pad=False):
|
| 233 |
+
self.window_size = window_size
|
| 234 |
+
self.random_pad = random_pad
|
| 235 |
+
|
| 236 |
+
def get_frame_num(self, data):
|
| 237 |
+
C, T, V, M = data.shape
|
| 238 |
+
for i in range(T - 1, -1, -1):
|
| 239 |
+
tmp = np.sum(data[:, i, :, :])
|
| 240 |
+
if tmp > 0:
|
| 241 |
+
T = i + 1
|
| 242 |
+
break
|
| 243 |
+
return T
|
| 244 |
+
|
| 245 |
+
def __call__(self, results):
|
| 246 |
+
data = results
|
| 247 |
+
|
| 248 |
+
C, T, V, M = data.shape
|
| 249 |
+
T = self.get_frame_num(data)
|
| 250 |
+
if T == self.window_size:
|
| 251 |
+
data_pad = data[:, :self.window_size, :, :]
|
| 252 |
+
elif T < self.window_size:
|
| 253 |
+
begin = random.randint(
|
| 254 |
+
0, self.window_size - T) if self.random_pad else 0
|
| 255 |
+
data_pad = np.zeros((C, self.window_size, V, M))
|
| 256 |
+
data_pad[:, begin:begin + T, :, :] = data[:, :T, :, :]
|
| 257 |
+
else:
|
| 258 |
+
if self.random_pad:
|
| 259 |
+
index = np.random.choice(
|
| 260 |
+
T, self.window_size, replace=False).astype('int64')
|
| 261 |
+
else:
|
| 262 |
+
index = np.linspace(0, T, self.window_size).astype("int64")
|
| 263 |
+
data_pad = data[:, index, :, :]
|
| 264 |
+
|
| 265 |
+
return data_pad
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def get_test_skeletons(input_file):
|
| 269 |
+
assert input_file is not None, "--action_file can not be None"
|
| 270 |
+
input_data = np.load(input_file)
|
| 271 |
+
if input_data.ndim == 4:
|
| 272 |
+
return [input_data]
|
| 273 |
+
elif input_data.ndim == 5:
|
| 274 |
+
output = list(
|
| 275 |
+
map(lambda x: np.squeeze(x, 0),
|
| 276 |
+
np.split(input_data, input_data.shape[0], 0)))
|
| 277 |
+
return output
|
| 278 |
+
else:
|
| 279 |
+
raise ValueError(
|
| 280 |
+
"Now only support input with shape: (N, C, T, K, M) or (C, T, K, M)"
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
class DetActionRecognizer(object):
|
| 285 |
+
"""
|
| 286 |
+
Args:
|
| 287 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 288 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 289 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 290 |
+
batch_size (int): size of pre batch in inference
|
| 291 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 292 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 293 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 294 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 295 |
+
calibration, trt_calib_mode need to set True
|
| 296 |
+
cpu_threads (int): cpu threads
|
| 297 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 298 |
+
threshold (float): The threshold of score for action feature object detection.
|
| 299 |
+
display_frames (int): The duration for corresponding detected action.
|
| 300 |
+
skip_frame_num (int): The number of frames for interval prediction. A skipped frame will
|
| 301 |
+
reuse the result of its last frame. If it is set to 0, no frame will be skipped. Default
|
| 302 |
+
is 0.
|
| 303 |
+
|
| 304 |
+
"""
|
| 305 |
+
|
| 306 |
+
def __init__(self,
|
| 307 |
+
model_dir,
|
| 308 |
+
device='CPU',
|
| 309 |
+
run_mode='paddle',
|
| 310 |
+
batch_size=1,
|
| 311 |
+
trt_min_shape=1,
|
| 312 |
+
trt_max_shape=1280,
|
| 313 |
+
trt_opt_shape=640,
|
| 314 |
+
trt_calib_mode=False,
|
| 315 |
+
cpu_threads=1,
|
| 316 |
+
enable_mkldnn=False,
|
| 317 |
+
output_dir='output',
|
| 318 |
+
threshold=0.5,
|
| 319 |
+
display_frames=20,
|
| 320 |
+
skip_frame_num=0):
|
| 321 |
+
super(DetActionRecognizer, self).__init__()
|
| 322 |
+
self.detector = Detector(
|
| 323 |
+
model_dir=model_dir,
|
| 324 |
+
device=device,
|
| 325 |
+
run_mode=run_mode,
|
| 326 |
+
batch_size=batch_size,
|
| 327 |
+
trt_min_shape=trt_min_shape,
|
| 328 |
+
trt_max_shape=trt_max_shape,
|
| 329 |
+
trt_opt_shape=trt_opt_shape,
|
| 330 |
+
trt_calib_mode=trt_calib_mode,
|
| 331 |
+
cpu_threads=cpu_threads,
|
| 332 |
+
enable_mkldnn=enable_mkldnn,
|
| 333 |
+
output_dir=output_dir,
|
| 334 |
+
threshold=threshold)
|
| 335 |
+
self.threshold = threshold
|
| 336 |
+
self.frame_life = display_frames
|
| 337 |
+
self.result_history = {}
|
| 338 |
+
self.skip_frame_num = skip_frame_num
|
| 339 |
+
self.skip_frame_cnt = 0
|
| 340 |
+
self.id_in_last_frame = []
|
| 341 |
+
|
| 342 |
+
@classmethod
|
| 343 |
+
def init_with_cfg(cls, args, cfg):
|
| 344 |
+
return cls(model_dir=cfg['model_dir'],
|
| 345 |
+
batch_size=cfg['batch_size'],
|
| 346 |
+
threshold=cfg['threshold'],
|
| 347 |
+
display_frames=cfg['display_frames'],
|
| 348 |
+
skip_frame_num=cfg['skip_frame_num'],
|
| 349 |
+
device=args.device,
|
| 350 |
+
run_mode=args.run_mode,
|
| 351 |
+
trt_min_shape=args.trt_min_shape,
|
| 352 |
+
trt_max_shape=args.trt_max_shape,
|
| 353 |
+
trt_opt_shape=args.trt_opt_shape,
|
| 354 |
+
trt_calib_mode=args.trt_calib_mode,
|
| 355 |
+
cpu_threads=args.cpu_threads,
|
| 356 |
+
enable_mkldnn=args.enable_mkldnn)
|
| 357 |
+
|
| 358 |
+
def predict(self, images, mot_result):
|
| 359 |
+
if self.skip_frame_cnt == 0 or (not self.check_id_is_same(mot_result)):
|
| 360 |
+
det_result = self.detector.predict_image(images, visual=False)
|
| 361 |
+
result = self.postprocess(det_result, mot_result)
|
| 362 |
+
else:
|
| 363 |
+
result = self.reuse_result(mot_result)
|
| 364 |
+
|
| 365 |
+
self.skip_frame_cnt += 1
|
| 366 |
+
if self.skip_frame_cnt >= self.skip_frame_num:
|
| 367 |
+
self.skip_frame_cnt = 0
|
| 368 |
+
|
| 369 |
+
return result
|
| 370 |
+
|
| 371 |
+
def postprocess(self, det_result, mot_result):
|
| 372 |
+
np_boxes_num = det_result['boxes_num']
|
| 373 |
+
if np_boxes_num[0] <= 0:
|
| 374 |
+
return [[], []]
|
| 375 |
+
|
| 376 |
+
mot_bboxes = mot_result.get('boxes')
|
| 377 |
+
|
| 378 |
+
cur_box_idx = 0
|
| 379 |
+
mot_id = []
|
| 380 |
+
act_res = []
|
| 381 |
+
for idx in range(len(mot_bboxes)):
|
| 382 |
+
tracker_id = mot_bboxes[idx, 0]
|
| 383 |
+
|
| 384 |
+
# Current now, class 0 is positive, class 1 is negative.
|
| 385 |
+
action_ret = {'class': 1.0, 'score': -1.0}
|
| 386 |
+
box_num = np_boxes_num[idx]
|
| 387 |
+
boxes = det_result['boxes'][cur_box_idx:cur_box_idx + box_num]
|
| 388 |
+
cur_box_idx += box_num
|
| 389 |
+
isvalid = (boxes[:, 1] > self.threshold) & (boxes[:, 0] == 0)
|
| 390 |
+
valid_boxes = boxes[isvalid, :]
|
| 391 |
+
|
| 392 |
+
if valid_boxes.shape[0] >= 1:
|
| 393 |
+
action_ret['class'] = valid_boxes[0, 0]
|
| 394 |
+
action_ret['score'] = valid_boxes[0, 1]
|
| 395 |
+
self.result_history[
|
| 396 |
+
tracker_id] = [0, self.frame_life, valid_boxes[0, 1]]
|
| 397 |
+
else:
|
| 398 |
+
history_det, life_remain, history_score = self.result_history.get(
|
| 399 |
+
tracker_id, [1, self.frame_life, -1.0])
|
| 400 |
+
action_ret['class'] = history_det
|
| 401 |
+
action_ret['score'] = -1.0
|
| 402 |
+
life_remain -= 1
|
| 403 |
+
if life_remain <= 0 and tracker_id in self.result_history:
|
| 404 |
+
del (self.result_history[tracker_id])
|
| 405 |
+
elif tracker_id in self.result_history:
|
| 406 |
+
self.result_history[tracker_id][1] = life_remain
|
| 407 |
+
else:
|
| 408 |
+
self.result_history[tracker_id] = [
|
| 409 |
+
history_det, life_remain, history_score
|
| 410 |
+
]
|
| 411 |
+
|
| 412 |
+
mot_id.append(tracker_id)
|
| 413 |
+
act_res.append(action_ret)
|
| 414 |
+
result = list(zip(mot_id, act_res))
|
| 415 |
+
self.id_in_last_frame = mot_id
|
| 416 |
+
|
| 417 |
+
return result
|
| 418 |
+
|
| 419 |
+
def check_id_is_same(self, mot_result):
|
| 420 |
+
mot_bboxes = mot_result.get('boxes')
|
| 421 |
+
for idx in range(len(mot_bboxes)):
|
| 422 |
+
tracker_id = mot_bboxes[idx, 0]
|
| 423 |
+
if tracker_id not in self.id_in_last_frame:
|
| 424 |
+
return False
|
| 425 |
+
return True
|
| 426 |
+
|
| 427 |
+
def reuse_result(self, mot_result):
|
| 428 |
+
# This function reusing previous results of the same ID directly.
|
| 429 |
+
mot_bboxes = mot_result.get('boxes')
|
| 430 |
+
|
| 431 |
+
mot_id = []
|
| 432 |
+
act_res = []
|
| 433 |
+
|
| 434 |
+
for idx in range(len(mot_bboxes)):
|
| 435 |
+
tracker_id = mot_bboxes[idx, 0]
|
| 436 |
+
history_cls, life_remain, history_score = self.result_history.get(
|
| 437 |
+
tracker_id, [1, 0, -1.0])
|
| 438 |
+
|
| 439 |
+
life_remain -= 1
|
| 440 |
+
if tracker_id in self.result_history:
|
| 441 |
+
self.result_history[tracker_id][1] = life_remain
|
| 442 |
+
|
| 443 |
+
action_ret = {'class': history_cls, 'score': history_score}
|
| 444 |
+
mot_id.append(tracker_id)
|
| 445 |
+
act_res.append(action_ret)
|
| 446 |
+
|
| 447 |
+
result = list(zip(mot_id, act_res))
|
| 448 |
+
self.id_in_last_frame = mot_id
|
| 449 |
+
|
| 450 |
+
return result
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
class ClsActionRecognizer(AttrDetector):
|
| 454 |
+
"""
|
| 455 |
+
Args:
|
| 456 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 457 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 458 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 459 |
+
batch_size (int): size of pre batch in inference
|
| 460 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 461 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 462 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 463 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 464 |
+
calibration, trt_calib_mode need to set True
|
| 465 |
+
cpu_threads (int): cpu threads
|
| 466 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 467 |
+
threshold (float): The threshold of score for action feature object detection.
|
| 468 |
+
display_frames (int): The duration for corresponding detected action.
|
| 469 |
+
skip_frame_num (int): The number of frames for interval prediction. A skipped frame will
|
| 470 |
+
reuse the result of its last frame. If it is set to 0, no frame will be skipped. Default
|
| 471 |
+
is 0.
|
| 472 |
+
"""
|
| 473 |
+
|
| 474 |
+
def __init__(self,
|
| 475 |
+
model_dir,
|
| 476 |
+
device='CPU',
|
| 477 |
+
run_mode='paddle',
|
| 478 |
+
batch_size=1,
|
| 479 |
+
trt_min_shape=1,
|
| 480 |
+
trt_max_shape=1280,
|
| 481 |
+
trt_opt_shape=640,
|
| 482 |
+
trt_calib_mode=False,
|
| 483 |
+
cpu_threads=1,
|
| 484 |
+
enable_mkldnn=False,
|
| 485 |
+
output_dir='output',
|
| 486 |
+
threshold=0.5,
|
| 487 |
+
display_frames=80,
|
| 488 |
+
skip_frame_num=0):
|
| 489 |
+
super(ClsActionRecognizer, self).__init__(
|
| 490 |
+
model_dir=model_dir,
|
| 491 |
+
device=device,
|
| 492 |
+
run_mode=run_mode,
|
| 493 |
+
batch_size=batch_size,
|
| 494 |
+
trt_min_shape=trt_min_shape,
|
| 495 |
+
trt_max_shape=trt_max_shape,
|
| 496 |
+
trt_opt_shape=trt_opt_shape,
|
| 497 |
+
trt_calib_mode=trt_calib_mode,
|
| 498 |
+
cpu_threads=cpu_threads,
|
| 499 |
+
enable_mkldnn=enable_mkldnn,
|
| 500 |
+
output_dir=output_dir,
|
| 501 |
+
threshold=threshold)
|
| 502 |
+
self.threshold = threshold
|
| 503 |
+
self.frame_life = display_frames
|
| 504 |
+
self.result_history = {}
|
| 505 |
+
self.skip_frame_num = skip_frame_num
|
| 506 |
+
self.skip_frame_cnt = 0
|
| 507 |
+
self.id_in_last_frame = []
|
| 508 |
+
|
| 509 |
+
@classmethod
|
| 510 |
+
def init_with_cfg(cls, args, cfg):
|
| 511 |
+
return cls(model_dir=cfg['model_dir'],
|
| 512 |
+
batch_size=cfg['batch_size'],
|
| 513 |
+
threshold=cfg['threshold'],
|
| 514 |
+
display_frames=cfg['display_frames'],
|
| 515 |
+
skip_frame_num=cfg['skip_frame_num'],
|
| 516 |
+
device=args.device,
|
| 517 |
+
run_mode=args.run_mode,
|
| 518 |
+
trt_min_shape=args.trt_min_shape,
|
| 519 |
+
trt_max_shape=args.trt_max_shape,
|
| 520 |
+
trt_opt_shape=args.trt_opt_shape,
|
| 521 |
+
trt_calib_mode=args.trt_calib_mode,
|
| 522 |
+
cpu_threads=args.cpu_threads,
|
| 523 |
+
enable_mkldnn=args.enable_mkldnn)
|
| 524 |
+
|
| 525 |
+
def predict_with_mot(self, images, mot_result):
|
| 526 |
+
if self.skip_frame_cnt == 0 or (not self.check_id_is_same(mot_result)):
|
| 527 |
+
images = self.crop_half_body(images)
|
| 528 |
+
cls_result = self.predict_image(images, visual=False)["output"]
|
| 529 |
+
result = self.match_action_with_id(cls_result, mot_result)
|
| 530 |
+
else:
|
| 531 |
+
result = self.reuse_result(mot_result)
|
| 532 |
+
|
| 533 |
+
self.skip_frame_cnt += 1
|
| 534 |
+
if self.skip_frame_cnt >= self.skip_frame_num:
|
| 535 |
+
self.skip_frame_cnt = 0
|
| 536 |
+
|
| 537 |
+
return result
|
| 538 |
+
|
| 539 |
+
def crop_half_body(self, images):
|
| 540 |
+
crop_images = []
|
| 541 |
+
for image in images:
|
| 542 |
+
h = image.shape[0]
|
| 543 |
+
crop_images.append(image[:h // 2 + 1, :, :])
|
| 544 |
+
return crop_images
|
| 545 |
+
|
| 546 |
+
def postprocess(self, inputs, result):
|
| 547 |
+
# postprocess output of predictor
|
| 548 |
+
im_results = result['output']
|
| 549 |
+
batch_res = []
|
| 550 |
+
for res in im_results:
|
| 551 |
+
action_res = res.tolist()
|
| 552 |
+
for cid, score in enumerate(action_res):
|
| 553 |
+
action_res[cid] = score
|
| 554 |
+
batch_res.append(action_res)
|
| 555 |
+
result = {'output': batch_res}
|
| 556 |
+
return result
|
| 557 |
+
|
| 558 |
+
def match_action_with_id(self, cls_result, mot_result):
|
| 559 |
+
mot_bboxes = mot_result.get('boxes')
|
| 560 |
+
|
| 561 |
+
mot_id = []
|
| 562 |
+
act_res = []
|
| 563 |
+
|
| 564 |
+
for idx in range(len(mot_bboxes)):
|
| 565 |
+
tracker_id = mot_bboxes[idx, 0]
|
| 566 |
+
|
| 567 |
+
cls_id_res = 1
|
| 568 |
+
cls_score_res = -1.0
|
| 569 |
+
for cls_id in range(len(cls_result[idx])):
|
| 570 |
+
score = cls_result[idx][cls_id]
|
| 571 |
+
if score > cls_score_res:
|
| 572 |
+
cls_id_res = cls_id
|
| 573 |
+
cls_score_res = score
|
| 574 |
+
|
| 575 |
+
# Current now, class 0 is positive, class 1 is negative.
|
| 576 |
+
if cls_id_res == 1 or (cls_id_res == 0 and
|
| 577 |
+
cls_score_res < self.threshold):
|
| 578 |
+
history_cls, life_remain, history_score = self.result_history.get(
|
| 579 |
+
tracker_id, [1, self.frame_life, -1.0])
|
| 580 |
+
cls_id_res = history_cls
|
| 581 |
+
cls_score_res = 1 - cls_score_res
|
| 582 |
+
life_remain -= 1
|
| 583 |
+
if life_remain <= 0 and tracker_id in self.result_history:
|
| 584 |
+
del (self.result_history[tracker_id])
|
| 585 |
+
elif tracker_id in self.result_history:
|
| 586 |
+
self.result_history[tracker_id][1] = life_remain
|
| 587 |
+
else:
|
| 588 |
+
self.result_history[tracker_id] = [
|
| 589 |
+
cls_id_res, life_remain, cls_score_res
|
| 590 |
+
]
|
| 591 |
+
else:
|
| 592 |
+
self.result_history[tracker_id] = [
|
| 593 |
+
cls_id_res, self.frame_life, cls_score_res
|
| 594 |
+
]
|
| 595 |
+
|
| 596 |
+
action_ret = {'class': cls_id_res, 'score': cls_score_res}
|
| 597 |
+
mot_id.append(tracker_id)
|
| 598 |
+
act_res.append(action_ret)
|
| 599 |
+
result = list(zip(mot_id, act_res))
|
| 600 |
+
self.id_in_last_frame = mot_id
|
| 601 |
+
|
| 602 |
+
return result
|
| 603 |
+
|
| 604 |
+
def check_id_is_same(self, mot_result):
|
| 605 |
+
mot_bboxes = mot_result.get('boxes')
|
| 606 |
+
for idx in range(len(mot_bboxes)):
|
| 607 |
+
tracker_id = mot_bboxes[idx, 0]
|
| 608 |
+
if tracker_id not in self.id_in_last_frame:
|
| 609 |
+
return False
|
| 610 |
+
return True
|
| 611 |
+
|
| 612 |
+
def reuse_result(self, mot_result):
|
| 613 |
+
# This function reusing previous results of the same ID directly.
|
| 614 |
+
mot_bboxes = mot_result.get('boxes')
|
| 615 |
+
|
| 616 |
+
mot_id = []
|
| 617 |
+
act_res = []
|
| 618 |
+
|
| 619 |
+
for idx in range(len(mot_bboxes)):
|
| 620 |
+
tracker_id = mot_bboxes[idx, 0]
|
| 621 |
+
history_cls, life_remain, history_score = self.result_history.get(
|
| 622 |
+
tracker_id, [1, 0, -1.0])
|
| 623 |
+
|
| 624 |
+
life_remain -= 1
|
| 625 |
+
if tracker_id in self.result_history:
|
| 626 |
+
self.result_history[tracker_id][1] = life_remain
|
| 627 |
+
|
| 628 |
+
action_ret = {'class': history_cls, 'score': history_score}
|
| 629 |
+
mot_id.append(tracker_id)
|
| 630 |
+
act_res.append(action_ret)
|
| 631 |
+
|
| 632 |
+
result = list(zip(mot_id, act_res))
|
| 633 |
+
self.id_in_last_frame = mot_id
|
| 634 |
+
|
| 635 |
+
return result
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
def main():
|
| 639 |
+
detector = SkeletonActionRecognizer(
|
| 640 |
+
FLAGS.model_dir,
|
| 641 |
+
device=FLAGS.device,
|
| 642 |
+
run_mode=FLAGS.run_mode,
|
| 643 |
+
batch_size=FLAGS.batch_size,
|
| 644 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 645 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 646 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 647 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 648 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 649 |
+
enable_mkldnn=FLAGS.enable_mkldnn,
|
| 650 |
+
threshold=FLAGS.threshold,
|
| 651 |
+
output_dir=FLAGS.output_dir,
|
| 652 |
+
window_size=FLAGS.window_size,
|
| 653 |
+
random_pad=FLAGS.random_pad)
|
| 654 |
+
# predict from numpy array
|
| 655 |
+
input_list = get_test_skeletons(FLAGS.action_file)
|
| 656 |
+
detector.predict_skeleton(input_list, FLAGS.run_benchmark, repeats=10)
|
| 657 |
+
if not FLAGS.run_benchmark:
|
| 658 |
+
detector.det_times.info(average=True)
|
| 659 |
+
else:
|
| 660 |
+
mems = {
|
| 661 |
+
'cpu_rss_mb': detector.cpu_mem / len(input_list),
|
| 662 |
+
'gpu_rss_mb': detector.gpu_mem / len(input_list),
|
| 663 |
+
'gpu_util': detector.gpu_util * 100 / len(input_list)
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
perf_info = detector.det_times.report(average=True)
|
| 667 |
+
model_dir = FLAGS.model_dir
|
| 668 |
+
mode = FLAGS.run_mode
|
| 669 |
+
model_info = {
|
| 670 |
+
'model_name': model_dir.strip('/').split('/')[-1],
|
| 671 |
+
'precision': mode.split('_')[-1]
|
| 672 |
+
}
|
| 673 |
+
data_info = {
|
| 674 |
+
'batch_size': FLAGS.batch_size,
|
| 675 |
+
'shape': "dynamic_shape",
|
| 676 |
+
'data_num': perf_info['img_num']
|
| 677 |
+
}
|
| 678 |
+
det_log = PaddleInferBenchmark(detector.config, model_info, data_info,
|
| 679 |
+
perf_info, mems)
|
| 680 |
+
det_log('SkeletonAction')
|
| 681 |
+
|
| 682 |
+
|
| 683 |
+
if __name__ == '__main__':
|
| 684 |
+
paddle.enable_static()
|
| 685 |
+
parser = argsparser()
|
| 686 |
+
FLAGS = parser.parse_args()
|
| 687 |
+
print_arguments(FLAGS)
|
| 688 |
+
FLAGS.device = FLAGS.device.upper()
|
| 689 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 690 |
+
], "device should be CPU, GPU or XPU"
|
| 691 |
+
assert not FLAGS.use_gpu, "use_gpu has been deprecated, please use --device"
|
| 692 |
+
|
| 693 |
+
main()
|
pipeline/pphuman/action_utils.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
|
| 16 |
+
class KeyPointSequence(object):
|
| 17 |
+
def __init__(self, max_size=100):
|
| 18 |
+
self.frames = 0
|
| 19 |
+
self.kpts = []
|
| 20 |
+
self.bboxes = []
|
| 21 |
+
self.max_size = max_size
|
| 22 |
+
|
| 23 |
+
def save(self, kpt, bbox):
|
| 24 |
+
self.kpts.append(kpt)
|
| 25 |
+
self.bboxes.append(bbox)
|
| 26 |
+
self.frames += 1
|
| 27 |
+
if self.frames == self.max_size:
|
| 28 |
+
return True
|
| 29 |
+
return False
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class KeyPointBuff(object):
|
| 33 |
+
def __init__(self, max_size=100):
|
| 34 |
+
self.flag_track_interrupt = False
|
| 35 |
+
self.keypoint_saver = dict()
|
| 36 |
+
self.max_size = max_size
|
| 37 |
+
self.id_to_pop = set()
|
| 38 |
+
self.flag_to_pop = False
|
| 39 |
+
|
| 40 |
+
def get_state(self):
|
| 41 |
+
return self.flag_to_pop
|
| 42 |
+
|
| 43 |
+
def update(self, kpt_res, mot_res):
|
| 44 |
+
kpts = kpt_res.get('keypoint')[0]
|
| 45 |
+
bboxes = kpt_res.get('bbox')
|
| 46 |
+
mot_bboxes = mot_res.get('boxes')
|
| 47 |
+
updated_id = set()
|
| 48 |
+
|
| 49 |
+
for idx in range(len(kpts)):
|
| 50 |
+
tracker_id = mot_bboxes[idx, 0]
|
| 51 |
+
updated_id.add(tracker_id)
|
| 52 |
+
|
| 53 |
+
kpt_seq = self.keypoint_saver.get(tracker_id,
|
| 54 |
+
KeyPointSequence(self.max_size))
|
| 55 |
+
is_full = kpt_seq.save(kpts[idx], bboxes[idx])
|
| 56 |
+
self.keypoint_saver[tracker_id] = kpt_seq
|
| 57 |
+
|
| 58 |
+
#Scene1: result should be popped when frames meet max size
|
| 59 |
+
if is_full:
|
| 60 |
+
self.id_to_pop.add(tracker_id)
|
| 61 |
+
self.flag_to_pop = True
|
| 62 |
+
|
| 63 |
+
#Scene2: result of a lost tracker should be popped
|
| 64 |
+
interrupted_id = set(self.keypoint_saver.keys()) - updated_id
|
| 65 |
+
if len(interrupted_id) > 0:
|
| 66 |
+
self.flag_to_pop = True
|
| 67 |
+
self.id_to_pop.update(interrupted_id)
|
| 68 |
+
|
| 69 |
+
def get_collected_keypoint(self):
|
| 70 |
+
"""
|
| 71 |
+
Output (List): List of keypoint results for Skeletonbased Recognition task, where
|
| 72 |
+
the format of each element is [tracker_id, KeyPointSequence of tracker_id]
|
| 73 |
+
"""
|
| 74 |
+
output = []
|
| 75 |
+
for tracker_id in self.id_to_pop:
|
| 76 |
+
output.append([tracker_id, self.keypoint_saver[tracker_id]])
|
| 77 |
+
del (self.keypoint_saver[tracker_id])
|
| 78 |
+
self.flag_to_pop = False
|
| 79 |
+
self.id_to_pop.clear()
|
| 80 |
+
return output
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
class ActionVisualHelper(object):
|
| 84 |
+
def __init__(self, frame_life=20):
|
| 85 |
+
self.frame_life = frame_life
|
| 86 |
+
self.action_history = {}
|
| 87 |
+
|
| 88 |
+
def get_visualize_ids(self):
|
| 89 |
+
id_detected = self.check_detected()
|
| 90 |
+
return id_detected
|
| 91 |
+
|
| 92 |
+
def check_detected(self):
|
| 93 |
+
id_detected = set()
|
| 94 |
+
deperate_id = []
|
| 95 |
+
for mot_id in self.action_history:
|
| 96 |
+
self.action_history[mot_id]["life_remain"] -= 1
|
| 97 |
+
if int(self.action_history[mot_id]["class"]) == 0:
|
| 98 |
+
id_detected.add(mot_id)
|
| 99 |
+
if self.action_history[mot_id]["life_remain"] == 0:
|
| 100 |
+
deperate_id.append(mot_id)
|
| 101 |
+
for mot_id in deperate_id:
|
| 102 |
+
del (self.action_history[mot_id])
|
| 103 |
+
return id_detected
|
| 104 |
+
|
| 105 |
+
def update(self, action_res_list):
|
| 106 |
+
for mot_id, action_res in action_res_list:
|
| 107 |
+
if mot_id in self.action_history:
|
| 108 |
+
if int(action_res["class"]) != 0 and int(self.action_history[
|
| 109 |
+
mot_id]["class"]) == 0:
|
| 110 |
+
continue
|
| 111 |
+
action_info = self.action_history.get(mot_id, {})
|
| 112 |
+
action_info["class"] = action_res["class"]
|
| 113 |
+
action_info["life_remain"] = self.frame_life
|
| 114 |
+
self.action_history[mot_id] = action_info
|
pipeline/pphuman/attr_infer.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import yaml
|
| 17 |
+
import glob
|
| 18 |
+
from functools import reduce
|
| 19 |
+
|
| 20 |
+
import cv2
|
| 21 |
+
import numpy as np
|
| 22 |
+
import math
|
| 23 |
+
import paddle
|
| 24 |
+
from paddle.inference import Config
|
| 25 |
+
from paddle.inference import create_predictor
|
| 26 |
+
|
| 27 |
+
import sys
|
| 28 |
+
# add deploy path of PadleDetection to sys.path
|
| 29 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'])))
|
| 30 |
+
sys.path.insert(0, parent_path)
|
| 31 |
+
|
| 32 |
+
from python.benchmark_utils import PaddleInferBenchmark
|
| 33 |
+
from python.preprocess import preprocess, Resize, NormalizeImage, Permute, PadStride, LetterBoxResize, WarpAffine
|
| 34 |
+
from python.visualize import visualize_attr
|
| 35 |
+
from python.utils import argsparser, Timer, get_current_memory_mb
|
| 36 |
+
from python.infer import Detector, get_test_images, print_arguments, load_predictor
|
| 37 |
+
|
| 38 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class AttrDetector(Detector):
|
| 42 |
+
"""
|
| 43 |
+
Args:
|
| 44 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 45 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 46 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 47 |
+
batch_size (int): size of pre batch in inference
|
| 48 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 49 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 50 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 51 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 52 |
+
calibration, trt_calib_mode need to set True
|
| 53 |
+
cpu_threads (int): cpu threads
|
| 54 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 55 |
+
output_dir (str): The path of output
|
| 56 |
+
threshold (float): The threshold of score for visualization
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
def __init__(
|
| 60 |
+
self,
|
| 61 |
+
model_dir,
|
| 62 |
+
device='CPU',
|
| 63 |
+
run_mode='paddle',
|
| 64 |
+
batch_size=1,
|
| 65 |
+
trt_min_shape=1,
|
| 66 |
+
trt_max_shape=1280,
|
| 67 |
+
trt_opt_shape=640,
|
| 68 |
+
trt_calib_mode=False,
|
| 69 |
+
cpu_threads=1,
|
| 70 |
+
enable_mkldnn=False,
|
| 71 |
+
output_dir='output',
|
| 72 |
+
threshold=0.5, ):
|
| 73 |
+
super(AttrDetector, self).__init__(
|
| 74 |
+
model_dir=model_dir,
|
| 75 |
+
device=device,
|
| 76 |
+
run_mode=run_mode,
|
| 77 |
+
batch_size=batch_size,
|
| 78 |
+
trt_min_shape=trt_min_shape,
|
| 79 |
+
trt_max_shape=trt_max_shape,
|
| 80 |
+
trt_opt_shape=trt_opt_shape,
|
| 81 |
+
trt_calib_mode=trt_calib_mode,
|
| 82 |
+
cpu_threads=cpu_threads,
|
| 83 |
+
enable_mkldnn=enable_mkldnn,
|
| 84 |
+
output_dir=output_dir,
|
| 85 |
+
threshold=threshold, )
|
| 86 |
+
|
| 87 |
+
@classmethod
|
| 88 |
+
def init_with_cfg(cls, args, cfg):
|
| 89 |
+
return cls(model_dir=cfg['model_dir'],
|
| 90 |
+
batch_size=cfg['batch_size'],
|
| 91 |
+
device=args.device,
|
| 92 |
+
run_mode=args.run_mode,
|
| 93 |
+
trt_min_shape=args.trt_min_shape,
|
| 94 |
+
trt_max_shape=args.trt_max_shape,
|
| 95 |
+
trt_opt_shape=args.trt_opt_shape,
|
| 96 |
+
trt_calib_mode=args.trt_calib_mode,
|
| 97 |
+
cpu_threads=args.cpu_threads,
|
| 98 |
+
enable_mkldnn=args.enable_mkldnn)
|
| 99 |
+
|
| 100 |
+
def get_label(self):
|
| 101 |
+
return self.pred_config.labels
|
| 102 |
+
|
| 103 |
+
def postprocess(self, inputs, result):
|
| 104 |
+
# postprocess output of predictor
|
| 105 |
+
im_results = result['output']
|
| 106 |
+
|
| 107 |
+
labels = self.pred_config.labels
|
| 108 |
+
age_list = ['AgeLess18', 'Age18-60', 'AgeOver60']
|
| 109 |
+
direct_list = ['Front', 'Side', 'Back']
|
| 110 |
+
bag_list = ['HandBag', 'ShoulderBag', 'Backpack']
|
| 111 |
+
upper_list = ['UpperStride', 'UpperLogo', 'UpperPlaid', 'UpperSplice']
|
| 112 |
+
lower_list = [
|
| 113 |
+
'LowerStripe', 'LowerPattern', 'LongCoat', 'Trousers', 'Shorts',
|
| 114 |
+
'Skirt&Dress'
|
| 115 |
+
]
|
| 116 |
+
glasses_threshold = 0.3
|
| 117 |
+
hold_threshold = 0.6
|
| 118 |
+
batch_res = []
|
| 119 |
+
for res in im_results:
|
| 120 |
+
res = res.tolist()
|
| 121 |
+
label_res = []
|
| 122 |
+
# gender
|
| 123 |
+
gender = 'Female' if res[22] > self.threshold else 'Male'
|
| 124 |
+
label_res.append(gender)
|
| 125 |
+
# age
|
| 126 |
+
age = age_list[np.argmax(res[19:22])]
|
| 127 |
+
label_res.append(age)
|
| 128 |
+
# direction
|
| 129 |
+
direction = direct_list[np.argmax(res[23:])]
|
| 130 |
+
label_res.append(direction)
|
| 131 |
+
# glasses
|
| 132 |
+
glasses = 'Glasses: '
|
| 133 |
+
if res[1] > glasses_threshold:
|
| 134 |
+
glasses += 'True'
|
| 135 |
+
else:
|
| 136 |
+
glasses += 'False'
|
| 137 |
+
label_res.append(glasses)
|
| 138 |
+
# hat
|
| 139 |
+
hat = 'Hat: '
|
| 140 |
+
if res[0] > self.threshold:
|
| 141 |
+
hat += 'True'
|
| 142 |
+
else:
|
| 143 |
+
hat += 'False'
|
| 144 |
+
label_res.append(hat)
|
| 145 |
+
# hold obj
|
| 146 |
+
hold_obj = 'HoldObjectsInFront: '
|
| 147 |
+
if res[18] > hold_threshold:
|
| 148 |
+
hold_obj += 'True'
|
| 149 |
+
else:
|
| 150 |
+
hold_obj += 'False'
|
| 151 |
+
label_res.append(hold_obj)
|
| 152 |
+
# bag
|
| 153 |
+
bag = bag_list[np.argmax(res[15:18])]
|
| 154 |
+
bag_score = res[15 + np.argmax(res[15:18])]
|
| 155 |
+
bag_label = bag if bag_score > self.threshold else 'No bag'
|
| 156 |
+
label_res.append(bag_label)
|
| 157 |
+
# upper
|
| 158 |
+
upper_label = 'Upper:'
|
| 159 |
+
sleeve = 'LongSleeve' if res[3] > res[2] else 'ShortSleeve'
|
| 160 |
+
upper_label += ' {}'.format(sleeve)
|
| 161 |
+
upper_res = res[4:8]
|
| 162 |
+
if np.max(upper_res) > self.threshold:
|
| 163 |
+
upper_label += ' {}'.format(upper_list[np.argmax(upper_res)])
|
| 164 |
+
label_res.append(upper_label)
|
| 165 |
+
# lower
|
| 166 |
+
lower_res = res[8:14]
|
| 167 |
+
lower_label = 'Lower: '
|
| 168 |
+
has_lower = False
|
| 169 |
+
for i, l in enumerate(lower_res):
|
| 170 |
+
if l > self.threshold:
|
| 171 |
+
lower_label += ' {}'.format(lower_list[i])
|
| 172 |
+
has_lower = True
|
| 173 |
+
if not has_lower:
|
| 174 |
+
lower_label += ' {}'.format(lower_list[np.argmax(lower_res)])
|
| 175 |
+
|
| 176 |
+
label_res.append(lower_label)
|
| 177 |
+
# shoe
|
| 178 |
+
shoe = 'Boots' if res[14] > self.threshold else 'No boots'
|
| 179 |
+
label_res.append(shoe)
|
| 180 |
+
|
| 181 |
+
batch_res.append(label_res)
|
| 182 |
+
result = {'output': batch_res}
|
| 183 |
+
return result
|
| 184 |
+
|
| 185 |
+
def predict(self, repeats=1):
|
| 186 |
+
'''
|
| 187 |
+
Args:
|
| 188 |
+
repeats (int): repeats number for prediction
|
| 189 |
+
Returns:
|
| 190 |
+
result (dict): include 'boxes': np.ndarray: shape:[N,6], N: number of box,
|
| 191 |
+
matix element:[class, score, x_min, y_min, x_max, y_max]
|
| 192 |
+
MaskRCNN's result include 'masks': np.ndarray:
|
| 193 |
+
shape: [N, im_h, im_w]
|
| 194 |
+
'''
|
| 195 |
+
# model prediction
|
| 196 |
+
for i in range(repeats):
|
| 197 |
+
self.predictor.run()
|
| 198 |
+
output_names = self.predictor.get_output_names()
|
| 199 |
+
output_tensor = self.predictor.get_output_handle(output_names[0])
|
| 200 |
+
np_output = output_tensor.copy_to_cpu()
|
| 201 |
+
result = dict(output=np_output)
|
| 202 |
+
return result
|
| 203 |
+
|
| 204 |
+
def predict_image(self,
|
| 205 |
+
image_list,
|
| 206 |
+
run_benchmark=False,
|
| 207 |
+
repeats=1,
|
| 208 |
+
visual=True):
|
| 209 |
+
batch_loop_cnt = math.ceil(float(len(image_list)) / self.batch_size)
|
| 210 |
+
results = []
|
| 211 |
+
for i in range(batch_loop_cnt):
|
| 212 |
+
start_index = i * self.batch_size
|
| 213 |
+
end_index = min((i + 1) * self.batch_size, len(image_list))
|
| 214 |
+
batch_image_list = image_list[start_index:end_index]
|
| 215 |
+
if run_benchmark:
|
| 216 |
+
# preprocess
|
| 217 |
+
inputs = self.preprocess(batch_image_list) # warmup
|
| 218 |
+
self.det_times.preprocess_time_s.start()
|
| 219 |
+
inputs = self.preprocess(batch_image_list)
|
| 220 |
+
self.det_times.preprocess_time_s.end()
|
| 221 |
+
|
| 222 |
+
# model prediction
|
| 223 |
+
result = self.predict(repeats=repeats) # warmup
|
| 224 |
+
self.det_times.inference_time_s.start()
|
| 225 |
+
result = self.predict(repeats=repeats)
|
| 226 |
+
self.det_times.inference_time_s.end(repeats=repeats)
|
| 227 |
+
|
| 228 |
+
# postprocess
|
| 229 |
+
result_warmup = self.postprocess(inputs, result) # warmup
|
| 230 |
+
self.det_times.postprocess_time_s.start()
|
| 231 |
+
result = self.postprocess(inputs, result)
|
| 232 |
+
self.det_times.postprocess_time_s.end()
|
| 233 |
+
self.det_times.img_num += len(batch_image_list)
|
| 234 |
+
|
| 235 |
+
cm, gm, gu = get_current_memory_mb()
|
| 236 |
+
self.cpu_mem += cm
|
| 237 |
+
self.gpu_mem += gm
|
| 238 |
+
self.gpu_util += gu
|
| 239 |
+
else:
|
| 240 |
+
# preprocess
|
| 241 |
+
self.det_times.preprocess_time_s.start()
|
| 242 |
+
inputs = self.preprocess(batch_image_list)
|
| 243 |
+
self.det_times.preprocess_time_s.end()
|
| 244 |
+
|
| 245 |
+
# model prediction
|
| 246 |
+
self.det_times.inference_time_s.start()
|
| 247 |
+
result = self.predict()
|
| 248 |
+
self.det_times.inference_time_s.end()
|
| 249 |
+
|
| 250 |
+
# postprocess
|
| 251 |
+
self.det_times.postprocess_time_s.start()
|
| 252 |
+
result = self.postprocess(inputs, result)
|
| 253 |
+
self.det_times.postprocess_time_s.end()
|
| 254 |
+
self.det_times.img_num += len(batch_image_list)
|
| 255 |
+
|
| 256 |
+
if visual:
|
| 257 |
+
visualize(
|
| 258 |
+
batch_image_list, result, output_dir=self.output_dir)
|
| 259 |
+
|
| 260 |
+
results.append(result)
|
| 261 |
+
if visual:
|
| 262 |
+
print('Test iter {}'.format(i))
|
| 263 |
+
|
| 264 |
+
results = self.merge_batch_result(results)
|
| 265 |
+
return results
|
| 266 |
+
|
| 267 |
+
def merge_batch_result(self, batch_result):
|
| 268 |
+
if len(batch_result) == 1:
|
| 269 |
+
return batch_result[0]
|
| 270 |
+
res_key = batch_result[0].keys()
|
| 271 |
+
results = {k: [] for k in res_key}
|
| 272 |
+
for res in batch_result:
|
| 273 |
+
for k, v in res.items():
|
| 274 |
+
results[k].extend(v)
|
| 275 |
+
return results
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
def visualize(image_list, batch_res, output_dir='output'):
|
| 279 |
+
|
| 280 |
+
# visualize the predict result
|
| 281 |
+
batch_res = batch_res['output']
|
| 282 |
+
for image_file, res in zip(image_list, batch_res):
|
| 283 |
+
im = visualize_attr(image_file, [res])
|
| 284 |
+
if not os.path.exists(output_dir):
|
| 285 |
+
os.makedirs(output_dir)
|
| 286 |
+
img_name = os.path.split(image_file)[-1]
|
| 287 |
+
out_path = os.path.join(output_dir, img_name)
|
| 288 |
+
cv2.imwrite(out_path, im)
|
| 289 |
+
print("save result to: " + out_path)
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def main():
|
| 293 |
+
detector = AttrDetector(
|
| 294 |
+
FLAGS.model_dir,
|
| 295 |
+
device=FLAGS.device,
|
| 296 |
+
run_mode=FLAGS.run_mode,
|
| 297 |
+
batch_size=FLAGS.batch_size,
|
| 298 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 299 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 300 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 301 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 302 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 303 |
+
enable_mkldnn=FLAGS.enable_mkldnn,
|
| 304 |
+
threshold=FLAGS.threshold,
|
| 305 |
+
output_dir=FLAGS.output_dir)
|
| 306 |
+
|
| 307 |
+
# predict from image
|
| 308 |
+
if FLAGS.image_dir is None and FLAGS.image_file is not None:
|
| 309 |
+
assert FLAGS.batch_size == 1, "batch_size should be 1, when image_file is not None"
|
| 310 |
+
img_list = get_test_images(FLAGS.image_dir, FLAGS.image_file)
|
| 311 |
+
detector.predict_image(img_list, FLAGS.run_benchmark, repeats=10)
|
| 312 |
+
if not FLAGS.run_benchmark:
|
| 313 |
+
detector.det_times.info(average=True)
|
| 314 |
+
else:
|
| 315 |
+
mems = {
|
| 316 |
+
'cpu_rss_mb': detector.cpu_mem / len(img_list),
|
| 317 |
+
'gpu_rss_mb': detector.gpu_mem / len(img_list),
|
| 318 |
+
'gpu_util': detector.gpu_util * 100 / len(img_list)
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
perf_info = detector.det_times.report(average=True)
|
| 322 |
+
model_dir = FLAGS.model_dir
|
| 323 |
+
mode = FLAGS.run_mode
|
| 324 |
+
model_info = {
|
| 325 |
+
'model_name': model_dir.strip('/').split('/')[-1],
|
| 326 |
+
'precision': mode.split('_')[-1]
|
| 327 |
+
}
|
| 328 |
+
data_info = {
|
| 329 |
+
'batch_size': FLAGS.batch_size,
|
| 330 |
+
'shape': "dynamic_shape",
|
| 331 |
+
'data_num': perf_info['img_num']
|
| 332 |
+
}
|
| 333 |
+
det_log = PaddleInferBenchmark(detector.config, model_info, data_info,
|
| 334 |
+
perf_info, mems)
|
| 335 |
+
det_log('Attr')
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
if __name__ == '__main__':
|
| 339 |
+
paddle.enable_static()
|
| 340 |
+
parser = argsparser()
|
| 341 |
+
FLAGS = parser.parse_args()
|
| 342 |
+
print_arguments(FLAGS)
|
| 343 |
+
FLAGS.device = FLAGS.device.upper()
|
| 344 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 345 |
+
], "device should be CPU, GPU or XPU"
|
| 346 |
+
assert not FLAGS.use_gpu, "use_gpu has been deprecated, please use --device"
|
| 347 |
+
|
| 348 |
+
main()
|
pipeline/pphuman/mtmct.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
from pptracking.python.mot.visualize import plot_tracking
|
| 16 |
+
from python.visualize import visualize_attr
|
| 17 |
+
import os
|
| 18 |
+
import re
|
| 19 |
+
import cv2
|
| 20 |
+
import gc
|
| 21 |
+
import numpy as np
|
| 22 |
+
try:
|
| 23 |
+
from sklearn import preprocessing
|
| 24 |
+
from sklearn.cluster import AgglomerativeClustering
|
| 25 |
+
except:
|
| 26 |
+
print(
|
| 27 |
+
'Warning: Unable to use MTMCT in PP-Human, please install sklearn, for example: `pip install sklearn`'
|
| 28 |
+
)
|
| 29 |
+
pass
|
| 30 |
+
import pandas as pd
|
| 31 |
+
from tqdm import tqdm
|
| 32 |
+
from functools import reduce
|
| 33 |
+
import warnings
|
| 34 |
+
warnings.filterwarnings("ignore")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def gen_restxt(output_dir_filename, map_tid, cid_tid_dict):
|
| 38 |
+
pattern = re.compile(r'c(\d)_t(\d)')
|
| 39 |
+
f_w = open(output_dir_filename, 'w')
|
| 40 |
+
for key, res in cid_tid_dict.items():
|
| 41 |
+
cid, tid = pattern.search(key).groups()
|
| 42 |
+
cid = int(cid) + 1
|
| 43 |
+
rects = res["rects"]
|
| 44 |
+
frames = res["frames"]
|
| 45 |
+
for idx, bbox in enumerate(rects):
|
| 46 |
+
bbox[0][3:] -= bbox[0][1:3]
|
| 47 |
+
fid = frames[idx] + 1
|
| 48 |
+
rect = [max(int(x), 0) for x in bbox[0][1:]]
|
| 49 |
+
if key in map_tid:
|
| 50 |
+
new_tid = map_tid[key]
|
| 51 |
+
f_w.write(
|
| 52 |
+
str(cid) + ' ' + str(new_tid) + ' ' + str(fid) + ' ' +
|
| 53 |
+
' '.join(map(str, rect)) + '\n')
|
| 54 |
+
print('gen_res: write file in {}'.format(output_dir_filename))
|
| 55 |
+
f_w.close()
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def get_mtmct_matching_results(pred_mtmct_file,
|
| 59 |
+
secs_interval=0.5,
|
| 60 |
+
video_fps=20):
|
| 61 |
+
res = np.loadtxt(pred_mtmct_file) # 'cid, tid, fid, x1, y1, w, h, -1, -1'
|
| 62 |
+
camera_ids = list(map(int, np.unique(res[:, 0])))
|
| 63 |
+
|
| 64 |
+
res = res[:, :7]
|
| 65 |
+
# each line in res: 'cid, tid, fid, x1, y1, w, h'
|
| 66 |
+
|
| 67 |
+
camera_tids = []
|
| 68 |
+
camera_results = dict()
|
| 69 |
+
for c_id in camera_ids:
|
| 70 |
+
camera_results[c_id] = res[res[:, 0] == c_id]
|
| 71 |
+
tids = np.unique(camera_results[c_id][:, 1])
|
| 72 |
+
tids = list(map(int, tids))
|
| 73 |
+
camera_tids.append(tids)
|
| 74 |
+
|
| 75 |
+
# select common tids throughout each video
|
| 76 |
+
common_tids = reduce(np.intersect1d, camera_tids)
|
| 77 |
+
|
| 78 |
+
# get mtmct matching results by cid_tid_fid_results[c_id][t_id][f_id]
|
| 79 |
+
cid_tid_fid_results = dict()
|
| 80 |
+
cid_tid_to_fids = dict()
|
| 81 |
+
interval = int(secs_interval * video_fps) # preferably less than 10
|
| 82 |
+
for c_id in camera_ids:
|
| 83 |
+
cid_tid_fid_results[c_id] = dict()
|
| 84 |
+
cid_tid_to_fids[c_id] = dict()
|
| 85 |
+
for t_id in common_tids:
|
| 86 |
+
tid_mask = camera_results[c_id][:, 1] == t_id
|
| 87 |
+
cid_tid_fid_results[c_id][t_id] = dict()
|
| 88 |
+
|
| 89 |
+
camera_trackid_results = camera_results[c_id][tid_mask]
|
| 90 |
+
fids = np.unique(camera_trackid_results[:, 2])
|
| 91 |
+
fids = fids[fids % interval == 0]
|
| 92 |
+
fids = list(map(int, fids))
|
| 93 |
+
cid_tid_to_fids[c_id][t_id] = fids
|
| 94 |
+
|
| 95 |
+
for f_id in fids:
|
| 96 |
+
st_frame = f_id
|
| 97 |
+
ed_frame = f_id + interval
|
| 98 |
+
|
| 99 |
+
st_mask = camera_trackid_results[:, 2] >= st_frame
|
| 100 |
+
ed_mask = camera_trackid_results[:, 2] < ed_frame
|
| 101 |
+
frame_mask = np.logical_and(st_mask, ed_mask)
|
| 102 |
+
cid_tid_fid_results[c_id][t_id][f_id] = camera_trackid_results[
|
| 103 |
+
frame_mask]
|
| 104 |
+
|
| 105 |
+
return camera_results, cid_tid_fid_results
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def save_mtmct_vis_results(camera_results,
|
| 109 |
+
captures,
|
| 110 |
+
output_dir,
|
| 111 |
+
multi_res=None):
|
| 112 |
+
# camera_results: 'cid, tid, fid, x1, y1, w, h'
|
| 113 |
+
camera_ids = list(camera_results.keys())
|
| 114 |
+
|
| 115 |
+
import shutil
|
| 116 |
+
save_dir = os.path.join(output_dir, 'mtmct_vis')
|
| 117 |
+
if os.path.exists(save_dir):
|
| 118 |
+
shutil.rmtree(save_dir)
|
| 119 |
+
os.makedirs(save_dir)
|
| 120 |
+
|
| 121 |
+
for idx, video_file in enumerate(captures):
|
| 122 |
+
capture = cv2.VideoCapture(video_file)
|
| 123 |
+
cid = camera_ids[idx]
|
| 124 |
+
basename = os.path.basename(video_file)
|
| 125 |
+
video_out_name = "vis_" + basename
|
| 126 |
+
out_path = os.path.join(save_dir, video_out_name)
|
| 127 |
+
print("Start visualizing output video: {}".format(out_path))
|
| 128 |
+
|
| 129 |
+
# Get Video info : resolution, fps, frame count
|
| 130 |
+
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 131 |
+
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 132 |
+
fps = int(capture.get(cv2.CAP_PROP_FPS))
|
| 133 |
+
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 134 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 135 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
|
| 136 |
+
frame_id = 0
|
| 137 |
+
while (1):
|
| 138 |
+
if frame_id % 50 == 0:
|
| 139 |
+
print('frame id: ', frame_id)
|
| 140 |
+
ret, frame = capture.read()
|
| 141 |
+
frame_id += 1
|
| 142 |
+
if not ret:
|
| 143 |
+
if frame_id == 1:
|
| 144 |
+
print("video read failed!")
|
| 145 |
+
break
|
| 146 |
+
frame_results = camera_results[cid][camera_results[cid][:, 2] ==
|
| 147 |
+
frame_id]
|
| 148 |
+
boxes = frame_results[:, -4:]
|
| 149 |
+
ids = frame_results[:, 1]
|
| 150 |
+
image = plot_tracking(
|
| 151 |
+
frame, boxes, ids, frame_id=frame_id, fps=fps)
|
| 152 |
+
|
| 153 |
+
# add attr vis
|
| 154 |
+
if multi_res:
|
| 155 |
+
tid_list = multi_res.keys() # c0_t1, c0_t2...
|
| 156 |
+
all_attr_result = [multi_res[i]["attrs"]
|
| 157 |
+
for i in tid_list] # all cid_tid result
|
| 158 |
+
if any(
|
| 159 |
+
all_attr_result
|
| 160 |
+
): # at least one cid_tid[attrs] is not None will goes to attrs_vis
|
| 161 |
+
attr_res = []
|
| 162 |
+
for k in tid_list:
|
| 163 |
+
if (frame_id - 1) >= len(multi_res[k]['attrs']):
|
| 164 |
+
t_attr = None
|
| 165 |
+
else:
|
| 166 |
+
t_attr = multi_res[k]['attrs'][frame_id - 1]
|
| 167 |
+
attr_res.append(t_attr)
|
| 168 |
+
image = visualize_attr(
|
| 169 |
+
image, attr_res, boxes, is_mtmct=True)
|
| 170 |
+
|
| 171 |
+
writer.write(image)
|
| 172 |
+
writer.release()
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def get_euclidean(x, y, **kwargs):
|
| 176 |
+
m = x.shape[0]
|
| 177 |
+
n = y.shape[0]
|
| 178 |
+
distmat = (np.power(x, 2).sum(axis=1, keepdims=True).repeat(
|
| 179 |
+
n, axis=1) + np.power(y, 2).sum(axis=1, keepdims=True).repeat(
|
| 180 |
+
m, axis=1).T)
|
| 181 |
+
distmat -= np.dot(2 * x, y.T)
|
| 182 |
+
return distmat
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def cosine_similarity(x, y, eps=1e-12):
|
| 186 |
+
"""
|
| 187 |
+
Computes cosine similarity between two tensors.
|
| 188 |
+
Value == 1 means the same vector
|
| 189 |
+
Value == 0 means perpendicular vectors
|
| 190 |
+
"""
|
| 191 |
+
x_n, y_n = np.linalg.norm(
|
| 192 |
+
x, axis=1, keepdims=True), np.linalg.norm(
|
| 193 |
+
y, axis=1, keepdims=True)
|
| 194 |
+
x_norm = x / np.maximum(x_n, eps * np.ones_like(x_n))
|
| 195 |
+
y_norm = y / np.maximum(y_n, eps * np.ones_like(y_n))
|
| 196 |
+
sim_mt = np.dot(x_norm, y_norm.T)
|
| 197 |
+
return sim_mt
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def get_cosine(x, y, eps=1e-12):
|
| 201 |
+
"""
|
| 202 |
+
Computes cosine distance between two tensors.
|
| 203 |
+
The cosine distance is the inverse cosine similarity
|
| 204 |
+
-> cosine_distance = abs(-cosine_distance) to make it
|
| 205 |
+
similar in behaviour to euclidean distance
|
| 206 |
+
"""
|
| 207 |
+
sim_mt = cosine_similarity(x, y, eps)
|
| 208 |
+
return sim_mt
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def get_dist_mat(x, y, func_name="euclidean"):
|
| 212 |
+
if func_name == "cosine":
|
| 213 |
+
dist_mat = get_cosine(x, y)
|
| 214 |
+
elif func_name == "euclidean":
|
| 215 |
+
dist_mat = get_euclidean(x, y)
|
| 216 |
+
print("Using {} as distance function during evaluation".format(func_name))
|
| 217 |
+
return dist_mat
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def intracam_ignore(st_mask, cid_tids):
|
| 221 |
+
count = len(cid_tids)
|
| 222 |
+
for i in range(count):
|
| 223 |
+
for j in range(count):
|
| 224 |
+
if cid_tids[i][1] == cid_tids[j][1]:
|
| 225 |
+
st_mask[i, j] = 0.
|
| 226 |
+
return st_mask
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def get_sim_matrix_new(cid_tid_dict, cid_tids):
|
| 230 |
+
# Note: camera independent get_sim_matrix function,
|
| 231 |
+
# which is different from the one in camera_utils.py.
|
| 232 |
+
count = len(cid_tids)
|
| 233 |
+
|
| 234 |
+
q_arr = np.array(
|
| 235 |
+
[cid_tid_dict[cid_tids[i]]['mean_feat'] for i in range(count)])
|
| 236 |
+
g_arr = np.array(
|
| 237 |
+
[cid_tid_dict[cid_tids[i]]['mean_feat'] for i in range(count)])
|
| 238 |
+
#compute distmat
|
| 239 |
+
distmat = get_dist_mat(q_arr, g_arr, func_name="cosine")
|
| 240 |
+
|
| 241 |
+
#mask the element which belongs to same video
|
| 242 |
+
st_mask = np.ones((count, count), dtype=np.float32)
|
| 243 |
+
st_mask = intracam_ignore(st_mask, cid_tids)
|
| 244 |
+
|
| 245 |
+
sim_matrix = distmat * st_mask
|
| 246 |
+
np.fill_diagonal(sim_matrix, 0.)
|
| 247 |
+
return 1. - sim_matrix
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def get_match(cluster_labels):
|
| 251 |
+
cluster_dict = dict()
|
| 252 |
+
cluster = list()
|
| 253 |
+
for i, l in enumerate(cluster_labels):
|
| 254 |
+
if l in list(cluster_dict.keys()):
|
| 255 |
+
cluster_dict[l].append(i)
|
| 256 |
+
else:
|
| 257 |
+
cluster_dict[l] = [i]
|
| 258 |
+
for idx in cluster_dict:
|
| 259 |
+
cluster.append(cluster_dict[idx])
|
| 260 |
+
return cluster
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def get_cid_tid(cluster_labels, cid_tids):
|
| 264 |
+
cluster = list()
|
| 265 |
+
for labels in cluster_labels:
|
| 266 |
+
cid_tid_list = list()
|
| 267 |
+
for label in labels:
|
| 268 |
+
cid_tid_list.append(cid_tids[label])
|
| 269 |
+
cluster.append(cid_tid_list)
|
| 270 |
+
return cluster
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def get_labels(cid_tid_dict, cid_tids):
|
| 274 |
+
#compute cost matrix between features
|
| 275 |
+
cost_matrix = get_sim_matrix_new(cid_tid_dict, cid_tids)
|
| 276 |
+
|
| 277 |
+
#cluster all the features
|
| 278 |
+
cluster1 = AgglomerativeClustering(
|
| 279 |
+
n_clusters=None,
|
| 280 |
+
distance_threshold=0.5,
|
| 281 |
+
affinity='precomputed',
|
| 282 |
+
linkage='complete')
|
| 283 |
+
cluster_labels1 = cluster1.fit_predict(cost_matrix)
|
| 284 |
+
labels = get_match(cluster_labels1)
|
| 285 |
+
|
| 286 |
+
sub_cluster = get_cid_tid(labels, cid_tids)
|
| 287 |
+
return labels
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def sub_cluster(cid_tid_dict):
|
| 291 |
+
'''
|
| 292 |
+
cid_tid_dict: all camera_id and track_id
|
| 293 |
+
'''
|
| 294 |
+
#get all keys
|
| 295 |
+
cid_tids = sorted([key for key in cid_tid_dict.keys()])
|
| 296 |
+
|
| 297 |
+
#cluster all trackid
|
| 298 |
+
clu = get_labels(cid_tid_dict, cid_tids)
|
| 299 |
+
|
| 300 |
+
#relabel every cluster groups
|
| 301 |
+
new_clu = list()
|
| 302 |
+
for c_list in clu:
|
| 303 |
+
new_clu.append([cid_tids[c] for c in c_list])
|
| 304 |
+
cid_tid_label = dict()
|
| 305 |
+
for i, c_list in enumerate(new_clu):
|
| 306 |
+
for c in c_list:
|
| 307 |
+
cid_tid_label[c] = i + 1
|
| 308 |
+
return cid_tid_label
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def distill_idfeat(mot_res):
|
| 312 |
+
qualities_list = mot_res["qualities"]
|
| 313 |
+
feature_list = mot_res["features"]
|
| 314 |
+
rects = mot_res["rects"]
|
| 315 |
+
|
| 316 |
+
qualities_new = []
|
| 317 |
+
feature_new = []
|
| 318 |
+
#filter rect less than 100*20
|
| 319 |
+
for idx, rect in enumerate(rects):
|
| 320 |
+
conf, xmin, ymin, xmax, ymax = rect[0]
|
| 321 |
+
if (xmax - xmin) * (ymax - ymin) and (xmax > xmin) > 2000:
|
| 322 |
+
qualities_new.append(qualities_list[idx])
|
| 323 |
+
feature_new.append(feature_list[idx])
|
| 324 |
+
#take all features if available rect is less than 2
|
| 325 |
+
if len(qualities_new) < 2:
|
| 326 |
+
qualities_new = qualities_list
|
| 327 |
+
feature_new = feature_list
|
| 328 |
+
|
| 329 |
+
#if available frames number is more than 200, take one frame data per 20 frames
|
| 330 |
+
skipf = 1
|
| 331 |
+
if len(qualities_new) > 20:
|
| 332 |
+
skipf = 2
|
| 333 |
+
quality_skip = np.array(qualities_new[::skipf])
|
| 334 |
+
feature_skip = np.array(feature_new[::skipf])
|
| 335 |
+
|
| 336 |
+
#sort features with image qualities, take the most trustworth features
|
| 337 |
+
topk_argq = np.argsort(quality_skip)[::-1]
|
| 338 |
+
if (quality_skip > 0.6).sum() > 1:
|
| 339 |
+
topk_feat = feature_skip[topk_argq[quality_skip > 0.6]]
|
| 340 |
+
else:
|
| 341 |
+
topk_feat = feature_skip[topk_argq]
|
| 342 |
+
|
| 343 |
+
#get final features by mean or cluster, at most take five
|
| 344 |
+
mean_feat = np.mean(topk_feat[:5], axis=0)
|
| 345 |
+
return mean_feat
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def res2dict(multi_res):
|
| 349 |
+
cid_tid_dict = {}
|
| 350 |
+
for cid, c_res in enumerate(multi_res):
|
| 351 |
+
for tid, res in c_res.items():
|
| 352 |
+
key = "c" + str(cid) + "_t" + str(tid)
|
| 353 |
+
if key not in cid_tid_dict:
|
| 354 |
+
if len(res["features"]) == 0:
|
| 355 |
+
continue
|
| 356 |
+
cid_tid_dict[key] = res
|
| 357 |
+
cid_tid_dict[key]['mean_feat'] = distill_idfeat(res)
|
| 358 |
+
return cid_tid_dict
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
def mtmct_process(multi_res, captures, mtmct_vis=True, output_dir="output"):
|
| 362 |
+
cid_tid_dict = res2dict(multi_res)
|
| 363 |
+
if len(cid_tid_dict) == 0:
|
| 364 |
+
print("no tracking result found, mtmct will be skiped.")
|
| 365 |
+
return
|
| 366 |
+
map_tid = sub_cluster(cid_tid_dict)
|
| 367 |
+
|
| 368 |
+
if not os.path.exists(output_dir):
|
| 369 |
+
os.mkdir(output_dir)
|
| 370 |
+
pred_mtmct_file = os.path.join(output_dir, 'mtmct_result.txt')
|
| 371 |
+
gen_restxt(pred_mtmct_file, map_tid, cid_tid_dict)
|
| 372 |
+
|
| 373 |
+
if mtmct_vis:
|
| 374 |
+
camera_results, cid_tid_fid_res = get_mtmct_matching_results(
|
| 375 |
+
pred_mtmct_file)
|
| 376 |
+
|
| 377 |
+
save_mtmct_vis_results(
|
| 378 |
+
camera_results,
|
| 379 |
+
captures,
|
| 380 |
+
output_dir=output_dir,
|
| 381 |
+
multi_res=cid_tid_dict)
|
pipeline/pphuman/reid.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
import cv2
|
| 18 |
+
import numpy as np
|
| 19 |
+
# add deploy path of PadleDetection to sys.path
|
| 20 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'] * 2)))
|
| 21 |
+
sys.path.insert(0, parent_path)
|
| 22 |
+
|
| 23 |
+
from python.infer import PredictConfig
|
| 24 |
+
from pptracking.python.det_infer import load_predictor
|
| 25 |
+
from python.utils import Timer
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class ReID(object):
|
| 29 |
+
"""
|
| 30 |
+
ReID of SDE methods
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
pred_config (object): config of model, defined by `Config(model_dir)`
|
| 34 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 35 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 36 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 37 |
+
batch_size (int): size of per batch in inference, default 50 means at most
|
| 38 |
+
50 sub images can be made a batch and send into ReID model
|
| 39 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 40 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 41 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 42 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 43 |
+
calibration, trt_calib_mode need to set True
|
| 44 |
+
cpu_threads (int): cpu threads
|
| 45 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
def __init__(self,
|
| 49 |
+
model_dir,
|
| 50 |
+
device='CPU',
|
| 51 |
+
run_mode='paddle',
|
| 52 |
+
batch_size=50,
|
| 53 |
+
trt_min_shape=1,
|
| 54 |
+
trt_max_shape=1088,
|
| 55 |
+
trt_opt_shape=608,
|
| 56 |
+
trt_calib_mode=False,
|
| 57 |
+
cpu_threads=4,
|
| 58 |
+
enable_mkldnn=False):
|
| 59 |
+
self.pred_config = self.set_config(model_dir)
|
| 60 |
+
self.predictor, self.config = load_predictor(
|
| 61 |
+
model_dir,
|
| 62 |
+
run_mode=run_mode,
|
| 63 |
+
batch_size=batch_size,
|
| 64 |
+
min_subgraph_size=self.pred_config.min_subgraph_size,
|
| 65 |
+
device=device,
|
| 66 |
+
use_dynamic_shape=self.pred_config.use_dynamic_shape,
|
| 67 |
+
trt_min_shape=trt_min_shape,
|
| 68 |
+
trt_max_shape=trt_max_shape,
|
| 69 |
+
trt_opt_shape=trt_opt_shape,
|
| 70 |
+
trt_calib_mode=trt_calib_mode,
|
| 71 |
+
cpu_threads=cpu_threads,
|
| 72 |
+
enable_mkldnn=enable_mkldnn)
|
| 73 |
+
self.det_times = Timer()
|
| 74 |
+
self.cpu_mem, self.gpu_mem, self.gpu_util = 0, 0, 0
|
| 75 |
+
self.batch_size = batch_size
|
| 76 |
+
self.input_wh = (128, 256)
|
| 77 |
+
|
| 78 |
+
@classmethod
|
| 79 |
+
def init_with_cfg(cls, args, cfg):
|
| 80 |
+
return cls(model_dir=cfg['model_dir'],
|
| 81 |
+
batch_size=cfg['batch_size'],
|
| 82 |
+
device=args.device,
|
| 83 |
+
run_mode=args.run_mode,
|
| 84 |
+
trt_min_shape=args.trt_min_shape,
|
| 85 |
+
trt_max_shape=args.trt_max_shape,
|
| 86 |
+
trt_opt_shape=args.trt_opt_shape,
|
| 87 |
+
trt_calib_mode=args.trt_calib_mode,
|
| 88 |
+
cpu_threads=args.cpu_threads,
|
| 89 |
+
enable_mkldnn=args.enable_mkldnn)
|
| 90 |
+
|
| 91 |
+
def set_config(self, model_dir):
|
| 92 |
+
return PredictConfig(model_dir)
|
| 93 |
+
|
| 94 |
+
def check_img_quality(self, crop, bbox, xyxy):
|
| 95 |
+
if crop is None:
|
| 96 |
+
return None
|
| 97 |
+
#eclipse
|
| 98 |
+
eclipse_quality = 1.0
|
| 99 |
+
inner_rect = np.zeros(xyxy.shape)
|
| 100 |
+
inner_rect[:, :2] = np.maximum(xyxy[:, :2], bbox[None, :2])
|
| 101 |
+
inner_rect[:, 2:] = np.minimum(xyxy[:, 2:], bbox[None, 2:])
|
| 102 |
+
wh_array = inner_rect[:, 2:] - inner_rect[:, :2]
|
| 103 |
+
filt = np.logical_and(wh_array[:, 0] > 0, wh_array[:, 1] > 0)
|
| 104 |
+
wh_array = wh_array[filt]
|
| 105 |
+
if wh_array.shape[0] > 1:
|
| 106 |
+
eclipse_ratio = wh_array / (bbox[2:] - bbox[:2])
|
| 107 |
+
eclipse_area_ratio = eclipse_ratio[:, 0] * eclipse_ratio[:, 1]
|
| 108 |
+
ear_lst = eclipse_area_ratio.tolist()
|
| 109 |
+
ear_lst.sort(reverse=True)
|
| 110 |
+
eclipse_quality = 1.0 - ear_lst[1]
|
| 111 |
+
bbox_wh = (bbox[2:] - bbox[:2])
|
| 112 |
+
height_quality = bbox_wh[1] / (bbox_wh[0] * 2)
|
| 113 |
+
eclipse_quality = min(eclipse_quality, height_quality)
|
| 114 |
+
|
| 115 |
+
#definition
|
| 116 |
+
cropgray = cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY)
|
| 117 |
+
definition = int(cv2.Laplacian(cropgray, cv2.CV_64F, ksize=3).var())
|
| 118 |
+
brightness = int(cropgray.mean())
|
| 119 |
+
bd_quality = min(1., brightness / 50.)
|
| 120 |
+
|
| 121 |
+
eclipse_weight = 0.7
|
| 122 |
+
return eclipse_quality * eclipse_weight + bd_quality * (1 -
|
| 123 |
+
eclipse_weight)
|
| 124 |
+
|
| 125 |
+
def normal_crop(self, image, rect):
|
| 126 |
+
imgh, imgw, c = image.shape
|
| 127 |
+
label, conf, xmin, ymin, xmax, ymax = [int(x) for x in rect.tolist()]
|
| 128 |
+
xmin = max(0, xmin)
|
| 129 |
+
ymin = max(0, ymin)
|
| 130 |
+
xmax = min(imgw, xmax)
|
| 131 |
+
ymax = min(imgh, ymax)
|
| 132 |
+
if label != 0 or xmax <= xmin or ymax <= ymin:
|
| 133 |
+
print("Warning! label missed!!")
|
| 134 |
+
return None, None, None
|
| 135 |
+
return image[ymin:ymax, xmin:xmax, :]
|
| 136 |
+
|
| 137 |
+
def crop_image_with_mot(self, image, mot_res):
|
| 138 |
+
res = mot_res['boxes']
|
| 139 |
+
crop_res = []
|
| 140 |
+
img_quality = []
|
| 141 |
+
rects = []
|
| 142 |
+
for box in res:
|
| 143 |
+
crop_image = self.normal_crop(image, box[1:])
|
| 144 |
+
quality_item = self.check_img_quality(crop_image, box[3:],
|
| 145 |
+
res[:, 3:])
|
| 146 |
+
if crop_image is not None:
|
| 147 |
+
crop_res.append(crop_image)
|
| 148 |
+
img_quality.append(quality_item)
|
| 149 |
+
rects.append(box)
|
| 150 |
+
return crop_res, img_quality, rects
|
| 151 |
+
|
| 152 |
+
def preprocess(self,
|
| 153 |
+
imgs,
|
| 154 |
+
mean=[0.485, 0.456, 0.406],
|
| 155 |
+
std=[0.229, 0.224, 0.225]):
|
| 156 |
+
im_batch = []
|
| 157 |
+
for img in imgs:
|
| 158 |
+
img = cv2.resize(img, self.input_wh)
|
| 159 |
+
img = img.astype('float32') / 255.
|
| 160 |
+
img -= np.array(mean)
|
| 161 |
+
img /= np.array(std)
|
| 162 |
+
im_batch.append(img.transpose((2, 0, 1)))
|
| 163 |
+
inputs = {}
|
| 164 |
+
inputs['x'] = np.array(im_batch).astype('float32')
|
| 165 |
+
return inputs
|
| 166 |
+
|
| 167 |
+
def predict(self, crops, repeats=1, add_timer=True, seq_name=''):
|
| 168 |
+
# preprocess
|
| 169 |
+
if add_timer:
|
| 170 |
+
self.det_times.preprocess_time_s.start()
|
| 171 |
+
inputs = self.preprocess(crops)
|
| 172 |
+
input_names = self.predictor.get_input_names()
|
| 173 |
+
for i in range(len(input_names)):
|
| 174 |
+
input_tensor = self.predictor.get_input_handle(input_names[i])
|
| 175 |
+
input_tensor.copy_from_cpu(inputs[input_names[i]])
|
| 176 |
+
|
| 177 |
+
if add_timer:
|
| 178 |
+
self.det_times.preprocess_time_s.end()
|
| 179 |
+
self.det_times.inference_time_s.start()
|
| 180 |
+
|
| 181 |
+
# model prediction
|
| 182 |
+
for i in range(repeats):
|
| 183 |
+
self.predictor.run()
|
| 184 |
+
output_names = self.predictor.get_output_names()
|
| 185 |
+
feature_tensor = self.predictor.get_output_handle(output_names[0])
|
| 186 |
+
pred_embs = feature_tensor.copy_to_cpu()
|
| 187 |
+
if add_timer:
|
| 188 |
+
self.det_times.inference_time_s.end(repeats=repeats)
|
| 189 |
+
self.det_times.postprocess_time_s.start()
|
| 190 |
+
|
| 191 |
+
if add_timer:
|
| 192 |
+
self.det_times.postprocess_time_s.end()
|
| 193 |
+
self.det_times.img_num += 1
|
| 194 |
+
return pred_embs
|
| 195 |
+
|
| 196 |
+
def predict_batch(self, imgs, batch_size=4):
|
| 197 |
+
batch_feat = []
|
| 198 |
+
for b in range(0, len(imgs), batch_size):
|
| 199 |
+
b_end = min(len(imgs), b + batch_size)
|
| 200 |
+
batch_imgs = imgs[b:b_end]
|
| 201 |
+
feat = self.predict(batch_imgs)
|
| 202 |
+
batch_feat.extend(feat.tolist())
|
| 203 |
+
|
| 204 |
+
return batch_feat
|
pipeline/pphuman/video_action_infer.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import yaml
|
| 17 |
+
import glob
|
| 18 |
+
|
| 19 |
+
import cv2
|
| 20 |
+
import numpy as np
|
| 21 |
+
import math
|
| 22 |
+
import paddle
|
| 23 |
+
import sys
|
| 24 |
+
import paddle.nn.functional as F
|
| 25 |
+
|
| 26 |
+
# add deploy path of PadleDetection to sys.path
|
| 27 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'] * 2)))
|
| 28 |
+
sys.path.insert(0, parent_path)
|
| 29 |
+
|
| 30 |
+
from paddle.inference import Config, create_predictor
|
| 31 |
+
from python.utils import argsparser, Timer, get_current_memory_mb
|
| 32 |
+
from python.benchmark_utils import PaddleInferBenchmark
|
| 33 |
+
from python.infer import Detector, print_arguments
|
| 34 |
+
from video_action_preprocess import VideoDecoder, Sampler, Scale, CenterCrop, Normalization, Image2Array
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def softmax(x):
|
| 38 |
+
f_x = np.exp(x) / np.sum(np.exp(x))
|
| 39 |
+
return f_x
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class VideoActionRecognizer(object):
|
| 43 |
+
"""
|
| 44 |
+
Args:
|
| 45 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 46 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 47 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 48 |
+
batch_size (int): size of pre batch in inference
|
| 49 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 50 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 51 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 52 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 53 |
+
calibration, trt_calib_mode need to set True
|
| 54 |
+
cpu_threads (int): cpu threads
|
| 55 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
def __init__(self,
|
| 59 |
+
model_dir,
|
| 60 |
+
device='CPU',
|
| 61 |
+
run_mode='paddle',
|
| 62 |
+
num_seg=8,
|
| 63 |
+
seg_len=1,
|
| 64 |
+
short_size=256,
|
| 65 |
+
target_size=224,
|
| 66 |
+
top_k=1,
|
| 67 |
+
batch_size=1,
|
| 68 |
+
trt_min_shape=1,
|
| 69 |
+
trt_max_shape=1280,
|
| 70 |
+
trt_opt_shape=640,
|
| 71 |
+
trt_calib_mode=False,
|
| 72 |
+
cpu_threads=1,
|
| 73 |
+
enable_mkldnn=False,
|
| 74 |
+
ir_optim=True):
|
| 75 |
+
|
| 76 |
+
self.num_seg = num_seg
|
| 77 |
+
self.seg_len = seg_len
|
| 78 |
+
self.short_size = short_size
|
| 79 |
+
self.target_size = target_size
|
| 80 |
+
self.top_k = top_k
|
| 81 |
+
|
| 82 |
+
assert batch_size == 1, "VideoActionRecognizer only support batch_size=1 now."
|
| 83 |
+
|
| 84 |
+
self.model_dir = model_dir
|
| 85 |
+
self.device = device
|
| 86 |
+
self.run_mode = run_mode
|
| 87 |
+
self.batch_size = batch_size
|
| 88 |
+
self.trt_min_shape = trt_min_shape
|
| 89 |
+
self.trt_max_shape = trt_max_shape
|
| 90 |
+
self.trt_opt_shape = trt_opt_shape
|
| 91 |
+
self.trt_calib_mode = trt_calib_mode
|
| 92 |
+
self.cpu_threads = cpu_threads
|
| 93 |
+
self.enable_mkldnn = enable_mkldnn
|
| 94 |
+
self.ir_optim = ir_optim
|
| 95 |
+
|
| 96 |
+
self.recognize_times = Timer()
|
| 97 |
+
|
| 98 |
+
model_file_path = glob.glob(os.path.join(model_dir, "*.pdmodel"))[0]
|
| 99 |
+
params_file_path = glob.glob(os.path.join(model_dir, "*.pdiparams"))[0]
|
| 100 |
+
self.config = Config(model_file_path, params_file_path)
|
| 101 |
+
|
| 102 |
+
if device == "GPU" or device == "gpu":
|
| 103 |
+
self.config.enable_use_gpu(8000, 0)
|
| 104 |
+
else:
|
| 105 |
+
self.config.disable_gpu()
|
| 106 |
+
if self.enable_mkldnn:
|
| 107 |
+
# cache 10 different shapes for mkldnn to avoid memory leak
|
| 108 |
+
self.config.set_mkldnn_cache_capacity(10)
|
| 109 |
+
self.config.enable_mkldnn()
|
| 110 |
+
|
| 111 |
+
self.config.switch_ir_optim(self.ir_optim) # default true
|
| 112 |
+
|
| 113 |
+
precision_map = {
|
| 114 |
+
'trt_int8': Config.Precision.Int8,
|
| 115 |
+
'trt_fp32': Config.Precision.Float32,
|
| 116 |
+
'trt_fp16': Config.Precision.Half
|
| 117 |
+
}
|
| 118 |
+
if run_mode in precision_map.keys():
|
| 119 |
+
self.config.enable_tensorrt_engine(
|
| 120 |
+
max_batch_size=8, precision_mode=precision_map[run_mode])
|
| 121 |
+
|
| 122 |
+
self.config.enable_memory_optim()
|
| 123 |
+
# use zero copy
|
| 124 |
+
self.config.switch_use_feed_fetch_ops(False)
|
| 125 |
+
|
| 126 |
+
self.predictor = create_predictor(self.config)
|
| 127 |
+
|
| 128 |
+
@classmethod
|
| 129 |
+
def init_with_cfg(cls, args, cfg):
|
| 130 |
+
return cls(model_dir=cfg['model_dir'],
|
| 131 |
+
short_size=cfg['short_size'],
|
| 132 |
+
target_size=cfg['target_size'],
|
| 133 |
+
batch_size=cfg['batch_size'],
|
| 134 |
+
device=args.device,
|
| 135 |
+
run_mode=args.run_mode,
|
| 136 |
+
trt_min_shape=args.trt_min_shape,
|
| 137 |
+
trt_max_shape=args.trt_max_shape,
|
| 138 |
+
trt_opt_shape=args.trt_opt_shape,
|
| 139 |
+
trt_calib_mode=args.trt_calib_mode,
|
| 140 |
+
cpu_threads=args.cpu_threads,
|
| 141 |
+
enable_mkldnn=args.enable_mkldnn)
|
| 142 |
+
|
| 143 |
+
def preprocess_batch(self, file_list):
|
| 144 |
+
batched_inputs = []
|
| 145 |
+
for file in file_list:
|
| 146 |
+
inputs = self.preprocess(file)
|
| 147 |
+
batched_inputs.append(inputs)
|
| 148 |
+
batched_inputs = [
|
| 149 |
+
np.concatenate([item[i] for item in batched_inputs])
|
| 150 |
+
for i in range(len(batched_inputs[0]))
|
| 151 |
+
]
|
| 152 |
+
self.input_file = file_list
|
| 153 |
+
return batched_inputs
|
| 154 |
+
|
| 155 |
+
def get_timer(self):
|
| 156 |
+
return self.recognize_times
|
| 157 |
+
|
| 158 |
+
def predict(self, input):
|
| 159 |
+
'''
|
| 160 |
+
Args:
|
| 161 |
+
input (str) or (list): video file path or image data list
|
| 162 |
+
Returns:
|
| 163 |
+
results (dict):
|
| 164 |
+
'''
|
| 165 |
+
|
| 166 |
+
input_names = self.predictor.get_input_names()
|
| 167 |
+
input_tensor = self.predictor.get_input_handle(input_names[0])
|
| 168 |
+
|
| 169 |
+
output_names = self.predictor.get_output_names()
|
| 170 |
+
output_tensor = self.predictor.get_output_handle(output_names[0])
|
| 171 |
+
|
| 172 |
+
# preprocess
|
| 173 |
+
self.recognize_times.preprocess_time_s.start()
|
| 174 |
+
if type(input) == str:
|
| 175 |
+
inputs = self.preprocess_video(input)
|
| 176 |
+
else:
|
| 177 |
+
inputs = self.preprocess_frames(input)
|
| 178 |
+
self.recognize_times.preprocess_time_s.end()
|
| 179 |
+
|
| 180 |
+
inputs = np.expand_dims(
|
| 181 |
+
inputs, axis=0).repeat(
|
| 182 |
+
self.batch_size, axis=0).copy()
|
| 183 |
+
|
| 184 |
+
input_tensor.copy_from_cpu(inputs)
|
| 185 |
+
|
| 186 |
+
# model prediction
|
| 187 |
+
self.recognize_times.inference_time_s.start()
|
| 188 |
+
self.predictor.run()
|
| 189 |
+
self.recognize_times.inference_time_s.end()
|
| 190 |
+
|
| 191 |
+
output = output_tensor.copy_to_cpu()
|
| 192 |
+
|
| 193 |
+
# postprocess
|
| 194 |
+
self.recognize_times.postprocess_time_s.start()
|
| 195 |
+
classes, scores = self.postprocess(output)
|
| 196 |
+
self.recognize_times.postprocess_time_s.end()
|
| 197 |
+
|
| 198 |
+
return classes, scores
|
| 199 |
+
|
| 200 |
+
def preprocess_frames(self, frame_list):
|
| 201 |
+
"""
|
| 202 |
+
frame_list: list, frame list
|
| 203 |
+
return: list
|
| 204 |
+
"""
|
| 205 |
+
|
| 206 |
+
results = {}
|
| 207 |
+
results['frames_len'] = len(frame_list)
|
| 208 |
+
results["imgs"] = frame_list
|
| 209 |
+
|
| 210 |
+
img_mean = [0.485, 0.456, 0.406]
|
| 211 |
+
img_std = [0.229, 0.224, 0.225]
|
| 212 |
+
ops = [
|
| 213 |
+
CenterCrop(self.target_size), Image2Array(),
|
| 214 |
+
Normalization(img_mean, img_std)
|
| 215 |
+
]
|
| 216 |
+
for op in ops:
|
| 217 |
+
results = op(results)
|
| 218 |
+
|
| 219 |
+
res = np.expand_dims(results['imgs'], axis=0).copy()
|
| 220 |
+
return [res]
|
| 221 |
+
|
| 222 |
+
def preprocess_video(self, input_file):
|
| 223 |
+
"""
|
| 224 |
+
input_file: str, file path
|
| 225 |
+
return: list
|
| 226 |
+
"""
|
| 227 |
+
assert os.path.isfile(input_file) is not None, "{0} not exists".format(
|
| 228 |
+
input_file)
|
| 229 |
+
|
| 230 |
+
results = {'filename': input_file}
|
| 231 |
+
img_mean = [0.485, 0.456, 0.406]
|
| 232 |
+
img_std = [0.229, 0.224, 0.225]
|
| 233 |
+
ops = [
|
| 234 |
+
VideoDecoder(), Sampler(
|
| 235 |
+
self.num_seg, self.seg_len, valid_mode=True),
|
| 236 |
+
Scale(self.short_size), CenterCrop(self.target_size),
|
| 237 |
+
Image2Array(), Normalization(img_mean, img_std)
|
| 238 |
+
]
|
| 239 |
+
for op in ops:
|
| 240 |
+
results = op(results)
|
| 241 |
+
|
| 242 |
+
res = np.expand_dims(results['imgs'], axis=0).copy()
|
| 243 |
+
return [res]
|
| 244 |
+
|
| 245 |
+
def postprocess(self, output):
|
| 246 |
+
output = output.flatten() # numpy.ndarray
|
| 247 |
+
output = softmax(output)
|
| 248 |
+
classes = np.argpartition(output, -self.top_k)[-self.top_k:]
|
| 249 |
+
classes = classes[np.argsort(-output[classes])]
|
| 250 |
+
scores = output[classes]
|
| 251 |
+
return classes, scores
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def main():
|
| 255 |
+
if not FLAGS.run_benchmark:
|
| 256 |
+
assert FLAGS.batch_size == 1
|
| 257 |
+
assert FLAGS.use_fp16 is False
|
| 258 |
+
else:
|
| 259 |
+
assert FLAGS.use_gpu is True
|
| 260 |
+
|
| 261 |
+
recognizer = VideoActionRecognizer(
|
| 262 |
+
FLAGS.model_dir,
|
| 263 |
+
short_size=FLAGS.short_size,
|
| 264 |
+
target_size=FLAGS.target_size,
|
| 265 |
+
device=FLAGS.device,
|
| 266 |
+
run_mode=FLAGS.run_mode,
|
| 267 |
+
batch_size=FLAGS.batch_size,
|
| 268 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 269 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 270 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 271 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 272 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 273 |
+
enable_mkldnn=FLAGS.enable_mkldnn, )
|
| 274 |
+
|
| 275 |
+
if not FLAGS.run_benchmark:
|
| 276 |
+
classes, scores = recognizer.predict(FLAGS.video_file)
|
| 277 |
+
print("Current video file: {}".format(FLAGS.video_file))
|
| 278 |
+
print("\ttop-1 class: {0}".format(classes[0]))
|
| 279 |
+
print("\ttop-1 score: {0}".format(scores[0]))
|
| 280 |
+
else:
|
| 281 |
+
cm, gm, gu = get_current_memory_mb()
|
| 282 |
+
mems = {'cpu_rss_mb': cm, 'gpu_rss_mb': gm, 'gpu_util': gu * 100}
|
| 283 |
+
|
| 284 |
+
perf_info = recognizer.recognize_times.report()
|
| 285 |
+
model_dir = FLAGS.model_dir
|
| 286 |
+
mode = FLAGS.run_mode
|
| 287 |
+
model_info = {
|
| 288 |
+
'model_name': model_dir.strip('/').split('/')[-1],
|
| 289 |
+
'precision': mode.split('_')[-1]
|
| 290 |
+
}
|
| 291 |
+
data_info = {
|
| 292 |
+
'batch_size': FLAGS.batch_size,
|
| 293 |
+
'shape': "dynamic_shape",
|
| 294 |
+
'data_num': perf_info['img_num']
|
| 295 |
+
}
|
| 296 |
+
recognize_log = PaddleInferBenchmark(recognizer.config, model_info,
|
| 297 |
+
data_info, perf_info, mems)
|
| 298 |
+
recognize_log('Fight')
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
if __name__ == '__main__':
|
| 302 |
+
paddle.enable_static()
|
| 303 |
+
parser = argsparser()
|
| 304 |
+
FLAGS = parser.parse_args()
|
| 305 |
+
print_arguments(FLAGS)
|
| 306 |
+
FLAGS.device = FLAGS.device.upper()
|
| 307 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 308 |
+
], "device should be CPU, GPU or XPU"
|
| 309 |
+
|
| 310 |
+
main()
|
pipeline/pphuman/video_action_preprocess.py
ADDED
|
@@ -0,0 +1,545 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020 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 |
+
|
| 15 |
+
import cv2
|
| 16 |
+
import numpy as np
|
| 17 |
+
from collections.abc import Sequence
|
| 18 |
+
from PIL import Image
|
| 19 |
+
import paddle
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class Sampler(object):
|
| 23 |
+
"""
|
| 24 |
+
Sample frames id.
|
| 25 |
+
NOTE: Use PIL to read image here, has diff with CV2
|
| 26 |
+
Args:
|
| 27 |
+
num_seg(int): number of segments.
|
| 28 |
+
seg_len(int): number of sampled frames in each segment.
|
| 29 |
+
valid_mode(bool): True or False.
|
| 30 |
+
Returns:
|
| 31 |
+
frames_idx: the index of sampled #frames.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
def __init__(self,
|
| 35 |
+
num_seg,
|
| 36 |
+
seg_len,
|
| 37 |
+
frame_interval=None,
|
| 38 |
+
valid_mode=True,
|
| 39 |
+
dense_sample=False,
|
| 40 |
+
linspace_sample=False,
|
| 41 |
+
use_pil=True):
|
| 42 |
+
self.num_seg = num_seg
|
| 43 |
+
self.seg_len = seg_len
|
| 44 |
+
self.frame_interval = frame_interval
|
| 45 |
+
self.valid_mode = valid_mode
|
| 46 |
+
self.dense_sample = dense_sample
|
| 47 |
+
self.linspace_sample = linspace_sample
|
| 48 |
+
self.use_pil = use_pil
|
| 49 |
+
|
| 50 |
+
def _get(self, frames_idx, results):
|
| 51 |
+
data_format = results['format']
|
| 52 |
+
|
| 53 |
+
if data_format == "frame":
|
| 54 |
+
frame_dir = results['frame_dir']
|
| 55 |
+
imgs = []
|
| 56 |
+
for idx in frames_idx:
|
| 57 |
+
img = Image.open(
|
| 58 |
+
os.path.join(frame_dir, results['suffix'].format(
|
| 59 |
+
idx))).convert('RGB')
|
| 60 |
+
imgs.append(img)
|
| 61 |
+
|
| 62 |
+
elif data_format == "video":
|
| 63 |
+
if results['backend'] == 'cv2':
|
| 64 |
+
frames = np.array(results['frames'])
|
| 65 |
+
imgs = []
|
| 66 |
+
for idx in frames_idx:
|
| 67 |
+
imgbuf = frames[idx]
|
| 68 |
+
img = Image.fromarray(imgbuf, mode='RGB')
|
| 69 |
+
imgs.append(img)
|
| 70 |
+
elif results['backend'] == 'decord':
|
| 71 |
+
container = results['frames']
|
| 72 |
+
if self.use_pil:
|
| 73 |
+
frames_select = container.get_batch(frames_idx)
|
| 74 |
+
# dearray_to_img
|
| 75 |
+
np_frames = frames_select.asnumpy()
|
| 76 |
+
imgs = []
|
| 77 |
+
for i in range(np_frames.shape[0]):
|
| 78 |
+
imgbuf = np_frames[i]
|
| 79 |
+
imgs.append(Image.fromarray(imgbuf, mode='RGB'))
|
| 80 |
+
else:
|
| 81 |
+
if frames_idx.ndim != 1:
|
| 82 |
+
frames_idx = np.squeeze(frames_idx)
|
| 83 |
+
frame_dict = {
|
| 84 |
+
idx: container[idx].asnumpy()
|
| 85 |
+
for idx in np.unique(frames_idx)
|
| 86 |
+
}
|
| 87 |
+
imgs = [frame_dict[idx] for idx in frames_idx]
|
| 88 |
+
elif results['backend'] == 'pyav':
|
| 89 |
+
imgs = []
|
| 90 |
+
frames = np.array(results['frames'])
|
| 91 |
+
for idx in frames_idx:
|
| 92 |
+
imgbuf = frames[idx]
|
| 93 |
+
imgs.append(imgbuf)
|
| 94 |
+
imgs = np.stack(imgs) # thwc
|
| 95 |
+
else:
|
| 96 |
+
raise NotImplementedError
|
| 97 |
+
else:
|
| 98 |
+
raise NotImplementedError
|
| 99 |
+
results['imgs'] = imgs # all image data
|
| 100 |
+
return results
|
| 101 |
+
|
| 102 |
+
def _get_train_clips(self, num_frames):
|
| 103 |
+
ori_seg_len = self.seg_len * self.frame_interval
|
| 104 |
+
avg_interval = (num_frames - ori_seg_len + 1) // self.num_seg
|
| 105 |
+
|
| 106 |
+
if avg_interval > 0:
|
| 107 |
+
base_offsets = np.arange(self.num_seg) * avg_interval
|
| 108 |
+
clip_offsets = base_offsets + np.random.randint(
|
| 109 |
+
avg_interval, size=self.num_seg)
|
| 110 |
+
elif num_frames > max(self.num_seg, ori_seg_len):
|
| 111 |
+
clip_offsets = np.sort(
|
| 112 |
+
np.random.randint(
|
| 113 |
+
num_frames - ori_seg_len + 1, size=self.num_seg))
|
| 114 |
+
elif avg_interval == 0:
|
| 115 |
+
ratio = (num_frames - ori_seg_len + 1.0) / self.num_seg
|
| 116 |
+
clip_offsets = np.around(np.arange(self.num_seg) * ratio)
|
| 117 |
+
else:
|
| 118 |
+
clip_offsets = np.zeros((self.num_seg, ), dtype=np.int)
|
| 119 |
+
return clip_offsets
|
| 120 |
+
|
| 121 |
+
def _get_test_clips(self, num_frames):
|
| 122 |
+
ori_seg_len = self.seg_len * self.frame_interval
|
| 123 |
+
avg_interval = (num_frames - ori_seg_len + 1) / float(self.num_seg)
|
| 124 |
+
if num_frames > ori_seg_len - 1:
|
| 125 |
+
base_offsets = np.arange(self.num_seg) * avg_interval
|
| 126 |
+
clip_offsets = (base_offsets + avg_interval / 2.0).astype(np.int)
|
| 127 |
+
else:
|
| 128 |
+
clip_offsets = np.zeros((self.num_seg, ), dtype=np.int)
|
| 129 |
+
return clip_offsets
|
| 130 |
+
|
| 131 |
+
def __call__(self, results):
|
| 132 |
+
"""
|
| 133 |
+
Args:
|
| 134 |
+
frames_len: length of frames.
|
| 135 |
+
return:
|
| 136 |
+
sampling id.
|
| 137 |
+
"""
|
| 138 |
+
frames_len = int(results['frames_len']) # total number of frames
|
| 139 |
+
|
| 140 |
+
frames_idx = []
|
| 141 |
+
if self.frame_interval is not None:
|
| 142 |
+
assert isinstance(self.frame_interval, int)
|
| 143 |
+
if not self.valid_mode:
|
| 144 |
+
offsets = self._get_train_clips(frames_len)
|
| 145 |
+
else:
|
| 146 |
+
offsets = self._get_test_clips(frames_len)
|
| 147 |
+
|
| 148 |
+
offsets = offsets[:, None] + np.arange(self.seg_len)[
|
| 149 |
+
None, :] * self.frame_interval
|
| 150 |
+
offsets = np.concatenate(offsets)
|
| 151 |
+
|
| 152 |
+
offsets = offsets.reshape((-1, self.seg_len))
|
| 153 |
+
offsets = np.mod(offsets, frames_len)
|
| 154 |
+
offsets = np.concatenate(offsets)
|
| 155 |
+
|
| 156 |
+
if results['format'] == 'video':
|
| 157 |
+
frames_idx = offsets
|
| 158 |
+
elif results['format'] == 'frame':
|
| 159 |
+
frames_idx = list(offsets + 1)
|
| 160 |
+
else:
|
| 161 |
+
raise NotImplementedError
|
| 162 |
+
|
| 163 |
+
return self._get(frames_idx, results)
|
| 164 |
+
|
| 165 |
+
print("self.frame_interval:", self.frame_interval)
|
| 166 |
+
|
| 167 |
+
if self.linspace_sample: # default if False
|
| 168 |
+
if 'start_idx' in results and 'end_idx' in results:
|
| 169 |
+
offsets = np.linspace(results['start_idx'], results['end_idx'],
|
| 170 |
+
self.num_seg)
|
| 171 |
+
else:
|
| 172 |
+
offsets = np.linspace(0, frames_len - 1, self.num_seg)
|
| 173 |
+
offsets = np.clip(offsets, 0, frames_len - 1).astype(np.int64)
|
| 174 |
+
if results['format'] == 'video':
|
| 175 |
+
frames_idx = list(offsets)
|
| 176 |
+
frames_idx = [x % frames_len for x in frames_idx]
|
| 177 |
+
elif results['format'] == 'frame':
|
| 178 |
+
frames_idx = list(offsets + 1)
|
| 179 |
+
else:
|
| 180 |
+
raise NotImplementedError
|
| 181 |
+
return self._get(frames_idx, results)
|
| 182 |
+
|
| 183 |
+
average_dur = int(frames_len / self.num_seg)
|
| 184 |
+
|
| 185 |
+
print("results['format']:", results['format'])
|
| 186 |
+
|
| 187 |
+
if self.dense_sample: # For ppTSM, default is False
|
| 188 |
+
if not self.valid_mode: # train
|
| 189 |
+
sample_pos = max(1, 1 + frames_len - 64)
|
| 190 |
+
t_stride = 64 // self.num_seg
|
| 191 |
+
start_idx = 0 if sample_pos == 1 else np.random.randint(
|
| 192 |
+
0, sample_pos - 1)
|
| 193 |
+
offsets = [(idx * t_stride + start_idx) % frames_len + 1
|
| 194 |
+
for idx in range(self.num_seg)]
|
| 195 |
+
frames_idx = offsets
|
| 196 |
+
else:
|
| 197 |
+
sample_pos = max(1, 1 + frames_len - 64)
|
| 198 |
+
t_stride = 64 // self.num_seg
|
| 199 |
+
start_list = np.linspace(0, sample_pos - 1, num=10, dtype=int)
|
| 200 |
+
offsets = []
|
| 201 |
+
for start_idx in start_list.tolist():
|
| 202 |
+
offsets += [(idx * t_stride + start_idx) % frames_len + 1
|
| 203 |
+
for idx in range(self.num_seg)]
|
| 204 |
+
frames_idx = offsets
|
| 205 |
+
else:
|
| 206 |
+
for i in range(self.num_seg):
|
| 207 |
+
idx = 0
|
| 208 |
+
if not self.valid_mode:
|
| 209 |
+
if average_dur >= self.seg_len:
|
| 210 |
+
idx = random.randint(0, average_dur - self.seg_len)
|
| 211 |
+
idx += i * average_dur
|
| 212 |
+
elif average_dur >= 1:
|
| 213 |
+
idx += i * average_dur
|
| 214 |
+
else:
|
| 215 |
+
idx = i
|
| 216 |
+
else:
|
| 217 |
+
if average_dur >= self.seg_len:
|
| 218 |
+
idx = (average_dur - 1) // 2
|
| 219 |
+
idx += i * average_dur
|
| 220 |
+
elif average_dur >= 1:
|
| 221 |
+
idx += i * average_dur
|
| 222 |
+
else:
|
| 223 |
+
idx = i
|
| 224 |
+
|
| 225 |
+
for jj in range(idx, idx + self.seg_len):
|
| 226 |
+
if results['format'] == 'video':
|
| 227 |
+
frames_idx.append(int(jj % frames_len))
|
| 228 |
+
elif results['format'] == 'frame':
|
| 229 |
+
frames_idx.append(jj + 1)
|
| 230 |
+
|
| 231 |
+
elif results['format'] == 'MRI':
|
| 232 |
+
frames_idx.append(jj)
|
| 233 |
+
else:
|
| 234 |
+
raise NotImplementedError
|
| 235 |
+
|
| 236 |
+
return self._get(frames_idx, results)
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
class Scale(object):
|
| 240 |
+
"""
|
| 241 |
+
Scale images.
|
| 242 |
+
Args:
|
| 243 |
+
short_size(float | int): Short size of an image will be scaled to the short_size.
|
| 244 |
+
fixed_ratio(bool): Set whether to zoom according to a fixed ratio. default: True
|
| 245 |
+
do_round(bool): Whether to round up when calculating the zoom ratio. default: False
|
| 246 |
+
backend(str): Choose pillow or cv2 as the graphics processing backend. default: 'pillow'
|
| 247 |
+
"""
|
| 248 |
+
|
| 249 |
+
def __init__(self,
|
| 250 |
+
short_size,
|
| 251 |
+
fixed_ratio=True,
|
| 252 |
+
keep_ratio=None,
|
| 253 |
+
do_round=False,
|
| 254 |
+
backend='pillow'):
|
| 255 |
+
self.short_size = short_size
|
| 256 |
+
assert (fixed_ratio and not keep_ratio) or (
|
| 257 |
+
not fixed_ratio
|
| 258 |
+
), "fixed_ratio and keep_ratio cannot be true at the same time"
|
| 259 |
+
self.fixed_ratio = fixed_ratio
|
| 260 |
+
self.keep_ratio = keep_ratio
|
| 261 |
+
self.do_round = do_round
|
| 262 |
+
|
| 263 |
+
assert backend in [
|
| 264 |
+
'pillow', 'cv2'
|
| 265 |
+
], "Scale's backend must be pillow or cv2, but get {backend}"
|
| 266 |
+
|
| 267 |
+
self.backend = backend
|
| 268 |
+
|
| 269 |
+
def __call__(self, results):
|
| 270 |
+
"""
|
| 271 |
+
Performs resize operations.
|
| 272 |
+
Args:
|
| 273 |
+
imgs (Sequence[PIL.Image]): List where each item is a PIL.Image.
|
| 274 |
+
For example, [PIL.Image0, PIL.Image1, PIL.Image2, ...]
|
| 275 |
+
return:
|
| 276 |
+
resized_imgs: List where each item is a PIL.Image after scaling.
|
| 277 |
+
"""
|
| 278 |
+
imgs = results['imgs']
|
| 279 |
+
resized_imgs = []
|
| 280 |
+
for i in range(len(imgs)):
|
| 281 |
+
img = imgs[i]
|
| 282 |
+
if isinstance(img, np.ndarray):
|
| 283 |
+
h, w, _ = img.shape
|
| 284 |
+
elif isinstance(img, Image.Image):
|
| 285 |
+
w, h = img.size
|
| 286 |
+
else:
|
| 287 |
+
raise NotImplementedError
|
| 288 |
+
|
| 289 |
+
if w <= h:
|
| 290 |
+
ow = self.short_size
|
| 291 |
+
if self.fixed_ratio: # default is True
|
| 292 |
+
oh = int(self.short_size * 4.0 / 3.0)
|
| 293 |
+
elif not self.keep_ratio: # no
|
| 294 |
+
oh = self.short_size
|
| 295 |
+
else:
|
| 296 |
+
scale_factor = self.short_size / w
|
| 297 |
+
oh = int(h * float(scale_factor) +
|
| 298 |
+
0.5) if self.do_round else int(
|
| 299 |
+
h * self.short_size / w)
|
| 300 |
+
ow = int(w * float(scale_factor) +
|
| 301 |
+
0.5) if self.do_round else int(
|
| 302 |
+
w * self.short_size / h)
|
| 303 |
+
else:
|
| 304 |
+
oh = self.short_size
|
| 305 |
+
if self.fixed_ratio:
|
| 306 |
+
ow = int(self.short_size * 4.0 / 3.0)
|
| 307 |
+
elif not self.keep_ratio: # no
|
| 308 |
+
ow = self.short_size
|
| 309 |
+
else:
|
| 310 |
+
scale_factor = self.short_size / h
|
| 311 |
+
oh = int(h * float(scale_factor) +
|
| 312 |
+
0.5) if self.do_round else int(
|
| 313 |
+
h * self.short_size / w)
|
| 314 |
+
ow = int(w * float(scale_factor) +
|
| 315 |
+
0.5) if self.do_round else int(
|
| 316 |
+
w * self.short_size / h)
|
| 317 |
+
|
| 318 |
+
if type(img) == np.ndarray:
|
| 319 |
+
img = Image.fromarray(img, mode='RGB')
|
| 320 |
+
|
| 321 |
+
if self.backend == 'pillow':
|
| 322 |
+
resized_imgs.append(img.resize((ow, oh), Image.BILINEAR))
|
| 323 |
+
elif self.backend == 'cv2' and (self.keep_ratio is not None):
|
| 324 |
+
resized_imgs.append(
|
| 325 |
+
cv2.resize(
|
| 326 |
+
img, (ow, oh), interpolation=cv2.INTER_LINEAR))
|
| 327 |
+
else:
|
| 328 |
+
resized_imgs.append(
|
| 329 |
+
Image.fromarray(
|
| 330 |
+
cv2.resize(
|
| 331 |
+
np.asarray(img), (ow, oh),
|
| 332 |
+
interpolation=cv2.INTER_LINEAR)))
|
| 333 |
+
results['imgs'] = resized_imgs
|
| 334 |
+
return results
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
class CenterCrop(object):
|
| 338 |
+
"""
|
| 339 |
+
Center crop images
|
| 340 |
+
Args:
|
| 341 |
+
target_size(int): Center crop a square with the target_size from an image.
|
| 342 |
+
do_round(bool): Whether to round up the coordinates of the upper left corner of the cropping area. default: True
|
| 343 |
+
"""
|
| 344 |
+
|
| 345 |
+
def __init__(self, target_size, do_round=True, backend='pillow'):
|
| 346 |
+
self.target_size = target_size
|
| 347 |
+
self.do_round = do_round
|
| 348 |
+
self.backend = backend
|
| 349 |
+
|
| 350 |
+
def __call__(self, results):
|
| 351 |
+
"""
|
| 352 |
+
Performs Center crop operations.
|
| 353 |
+
Args:
|
| 354 |
+
imgs: List where each item is a PIL.Image.
|
| 355 |
+
For example, [PIL.Image0, PIL.Image1, PIL.Image2, ...]
|
| 356 |
+
return:
|
| 357 |
+
ccrop_imgs: List where each item is a PIL.Image after Center crop.
|
| 358 |
+
"""
|
| 359 |
+
imgs = results['imgs']
|
| 360 |
+
ccrop_imgs = []
|
| 361 |
+
th, tw = self.target_size, self.target_size
|
| 362 |
+
if isinstance(imgs, paddle.Tensor):
|
| 363 |
+
h, w = imgs.shape[-2:]
|
| 364 |
+
x1 = int(round((w - tw) / 2.0)) if self.do_round else (w - tw) // 2
|
| 365 |
+
y1 = int(round((h - th) / 2.0)) if self.do_round else (h - th) // 2
|
| 366 |
+
ccrop_imgs = imgs[:, :, y1:y1 + th, x1:x1 + tw]
|
| 367 |
+
else:
|
| 368 |
+
for img in imgs:
|
| 369 |
+
if self.backend == 'pillow':
|
| 370 |
+
w, h = img.size
|
| 371 |
+
elif self.backend == 'cv2':
|
| 372 |
+
h, w, _ = img.shape
|
| 373 |
+
else:
|
| 374 |
+
raise NotImplementedError
|
| 375 |
+
assert (w >= self.target_size) and (h >= self.target_size), \
|
| 376 |
+
"image width({}) and height({}) should be larger than crop size".format(
|
| 377 |
+
w, h, self.target_size)
|
| 378 |
+
x1 = int(round((w - tw) / 2.0)) if self.do_round else (
|
| 379 |
+
w - tw) // 2
|
| 380 |
+
y1 = int(round((h - th) / 2.0)) if self.do_round else (
|
| 381 |
+
h - th) // 2
|
| 382 |
+
if self.backend == 'cv2':
|
| 383 |
+
ccrop_imgs.append(img[y1:y1 + th, x1:x1 + tw])
|
| 384 |
+
elif self.backend == 'pillow':
|
| 385 |
+
ccrop_imgs.append(img.crop((x1, y1, x1 + tw, y1 + th)))
|
| 386 |
+
results['imgs'] = ccrop_imgs
|
| 387 |
+
return results
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
class Image2Array(object):
|
| 391 |
+
"""
|
| 392 |
+
transfer PIL.Image to Numpy array and transpose dimensions from 'dhwc' to 'dchw'.
|
| 393 |
+
Args:
|
| 394 |
+
transpose: whether to transpose or not, default True, False for slowfast.
|
| 395 |
+
"""
|
| 396 |
+
|
| 397 |
+
def __init__(self, transpose=True, data_format='tchw'):
|
| 398 |
+
assert data_format in [
|
| 399 |
+
'tchw', 'cthw'
|
| 400 |
+
], "Target format must in ['tchw', 'cthw'], but got {data_format}"
|
| 401 |
+
self.transpose = transpose
|
| 402 |
+
self.data_format = data_format
|
| 403 |
+
|
| 404 |
+
def __call__(self, results):
|
| 405 |
+
"""
|
| 406 |
+
Performs Image to NumpyArray operations.
|
| 407 |
+
Args:
|
| 408 |
+
imgs: List where each item is a PIL.Image.
|
| 409 |
+
For example, [PIL.Image0, PIL.Image1, PIL.Image2, ...]
|
| 410 |
+
return:
|
| 411 |
+
np_imgs: Numpy array.
|
| 412 |
+
"""
|
| 413 |
+
imgs = results['imgs']
|
| 414 |
+
if 'backend' in results and results[
|
| 415 |
+
'backend'] == 'pyav': # [T,H,W,C] in [0, 1]
|
| 416 |
+
if self.transpose:
|
| 417 |
+
if self.data_format == 'tchw':
|
| 418 |
+
t_imgs = imgs.transpose((0, 3, 1, 2)) # tchw
|
| 419 |
+
else:
|
| 420 |
+
t_imgs = imgs.transpose((3, 0, 1, 2)) # cthw
|
| 421 |
+
results['imgs'] = t_imgs
|
| 422 |
+
else:
|
| 423 |
+
t_imgs = np.stack(imgs).astype('float32')
|
| 424 |
+
if self.transpose:
|
| 425 |
+
if self.data_format == 'tchw':
|
| 426 |
+
t_imgs = t_imgs.transpose(0, 3, 1, 2) # tchw
|
| 427 |
+
else:
|
| 428 |
+
t_imgs = t_imgs.transpose(3, 0, 1, 2) # cthw
|
| 429 |
+
results['imgs'] = t_imgs
|
| 430 |
+
return results
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
class VideoDecoder(object):
|
| 434 |
+
"""
|
| 435 |
+
Decode mp4 file to frames.
|
| 436 |
+
Args:
|
| 437 |
+
filepath: the file path of mp4 file
|
| 438 |
+
"""
|
| 439 |
+
|
| 440 |
+
def __init__(self,
|
| 441 |
+
backend='cv2',
|
| 442 |
+
mode='train',
|
| 443 |
+
sampling_rate=32,
|
| 444 |
+
num_seg=8,
|
| 445 |
+
num_clips=1,
|
| 446 |
+
target_fps=30):
|
| 447 |
+
|
| 448 |
+
self.backend = backend
|
| 449 |
+
# params below only for TimeSformer
|
| 450 |
+
self.mode = mode
|
| 451 |
+
self.sampling_rate = sampling_rate
|
| 452 |
+
self.num_seg = num_seg
|
| 453 |
+
self.num_clips = num_clips
|
| 454 |
+
self.target_fps = target_fps
|
| 455 |
+
|
| 456 |
+
def __call__(self, results):
|
| 457 |
+
"""
|
| 458 |
+
Perform mp4 decode operations.
|
| 459 |
+
return:
|
| 460 |
+
List where each item is a numpy array after decoder.
|
| 461 |
+
"""
|
| 462 |
+
file_path = results['filename']
|
| 463 |
+
results['format'] = 'video'
|
| 464 |
+
results['backend'] = self.backend
|
| 465 |
+
|
| 466 |
+
if self.backend == 'cv2': # here
|
| 467 |
+
cap = cv2.VideoCapture(file_path)
|
| 468 |
+
videolen = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 469 |
+
|
| 470 |
+
sampledFrames = []
|
| 471 |
+
for i in range(videolen):
|
| 472 |
+
ret, frame = cap.read()
|
| 473 |
+
# maybe first frame is empty
|
| 474 |
+
if ret == False:
|
| 475 |
+
continue
|
| 476 |
+
img = frame[:, :, ::-1]
|
| 477 |
+
sampledFrames.append(img)
|
| 478 |
+
results['frames'] = sampledFrames
|
| 479 |
+
results['frames_len'] = len(sampledFrames)
|
| 480 |
+
|
| 481 |
+
elif self.backend == 'decord':
|
| 482 |
+
container = de.VideoReader(file_path)
|
| 483 |
+
frames_len = len(container)
|
| 484 |
+
results['frames'] = container
|
| 485 |
+
results['frames_len'] = frames_len
|
| 486 |
+
else:
|
| 487 |
+
raise NotImplementedError
|
| 488 |
+
return results
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
class Normalization(object):
|
| 492 |
+
"""
|
| 493 |
+
Normalization.
|
| 494 |
+
Args:
|
| 495 |
+
mean(Sequence[float]): mean values of different channels.
|
| 496 |
+
std(Sequence[float]): std values of different channels.
|
| 497 |
+
tensor_shape(list): size of mean, default [3,1,1]. For slowfast, [1,1,1,3]
|
| 498 |
+
"""
|
| 499 |
+
|
| 500 |
+
def __init__(self, mean, std, tensor_shape=[3, 1, 1], inplace=False):
|
| 501 |
+
if not isinstance(mean, Sequence):
|
| 502 |
+
raise TypeError(
|
| 503 |
+
'Mean must be list, tuple or np.ndarray, but got {type(mean)}')
|
| 504 |
+
if not isinstance(std, Sequence):
|
| 505 |
+
raise TypeError(
|
| 506 |
+
'Std must be list, tuple or np.ndarray, but got {type(std)}')
|
| 507 |
+
|
| 508 |
+
self.inplace = inplace
|
| 509 |
+
if not inplace:
|
| 510 |
+
self.mean = np.array(mean).reshape(tensor_shape).astype(np.float32)
|
| 511 |
+
self.std = np.array(std).reshape(tensor_shape).astype(np.float32)
|
| 512 |
+
else:
|
| 513 |
+
self.mean = np.array(mean, dtype=np.float32)
|
| 514 |
+
self.std = np.array(std, dtype=np.float32)
|
| 515 |
+
|
| 516 |
+
def __call__(self, results):
|
| 517 |
+
"""
|
| 518 |
+
Performs normalization operations.
|
| 519 |
+
Args:
|
| 520 |
+
imgs: Numpy array.
|
| 521 |
+
return:
|
| 522 |
+
np_imgs: Numpy array after normalization.
|
| 523 |
+
"""
|
| 524 |
+
|
| 525 |
+
if self.inplace: # default is False
|
| 526 |
+
n = len(results['imgs'])
|
| 527 |
+
h, w, c = results['imgs'][0].shape
|
| 528 |
+
norm_imgs = np.empty((n, h, w, c), dtype=np.float32)
|
| 529 |
+
for i, img in enumerate(results['imgs']):
|
| 530 |
+
norm_imgs[i] = img
|
| 531 |
+
|
| 532 |
+
for img in norm_imgs: # [n,h,w,c]
|
| 533 |
+
mean = np.float64(self.mean.reshape(1, -1)) # [1, 3]
|
| 534 |
+
stdinv = 1 / np.float64(self.std.reshape(1, -1)) # [1, 3]
|
| 535 |
+
cv2.subtract(img, mean, img)
|
| 536 |
+
cv2.multiply(img, stdinv, img)
|
| 537 |
+
else:
|
| 538 |
+
imgs = results['imgs']
|
| 539 |
+
norm_imgs = imgs / 255.0
|
| 540 |
+
norm_imgs -= self.mean
|
| 541 |
+
norm_imgs /= self.std
|
| 542 |
+
if 'backend' in results and results['backend'] == 'pyav':
|
| 543 |
+
norm_imgs = paddle.to_tensor(norm_imgs, dtype=paddle.float32)
|
| 544 |
+
results['imgs'] = norm_imgs
|
| 545 |
+
return results
|
pptracking/python/det_infer.py
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import yaml
|
| 17 |
+
import glob
|
| 18 |
+
from functools import reduce
|
| 19 |
+
|
| 20 |
+
import cv2
|
| 21 |
+
import numpy as np
|
| 22 |
+
import math
|
| 23 |
+
|
| 24 |
+
import paddle
|
| 25 |
+
from paddle.inference import Config
|
| 26 |
+
from paddle.inference import create_predictor
|
| 27 |
+
|
| 28 |
+
import sys
|
| 29 |
+
# add deploy path of PadleDetection to sys.path
|
| 30 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'])))
|
| 31 |
+
sys.path.insert(0, parent_path)
|
| 32 |
+
|
| 33 |
+
from benchmark_utils import PaddleInferBenchmark
|
| 34 |
+
from picodet_postprocess import PicoDetPostProcess
|
| 35 |
+
from preprocess import preprocess, Resize, NormalizeImage, Permute, PadStride, LetterBoxResize, Pad, decode_image
|
| 36 |
+
from mot.visualize import visualize_box_mask
|
| 37 |
+
from mot_utils import argsparser, Timer, get_current_memory_mb
|
| 38 |
+
|
| 39 |
+
# Global dictionary
|
| 40 |
+
SUPPORT_MODELS = {
|
| 41 |
+
'YOLO',
|
| 42 |
+
'PicoDet',
|
| 43 |
+
'JDE',
|
| 44 |
+
'FairMOT',
|
| 45 |
+
'DeepSORT',
|
| 46 |
+
'StrongBaseline',
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def bench_log(detector, img_list, model_info, batch_size=1, name=None):
|
| 51 |
+
mems = {
|
| 52 |
+
'cpu_rss_mb': detector.cpu_mem / len(img_list),
|
| 53 |
+
'gpu_rss_mb': detector.gpu_mem / len(img_list),
|
| 54 |
+
'gpu_util': detector.gpu_util * 100 / len(img_list)
|
| 55 |
+
}
|
| 56 |
+
perf_info = detector.det_times.report(average=True)
|
| 57 |
+
data_info = {
|
| 58 |
+
'batch_size': batch_size,
|
| 59 |
+
'shape': "dynamic_shape",
|
| 60 |
+
'data_num': perf_info['img_num']
|
| 61 |
+
}
|
| 62 |
+
log = PaddleInferBenchmark(detector.config, model_info, data_info,
|
| 63 |
+
perf_info, mems)
|
| 64 |
+
log(name)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class Detector(object):
|
| 68 |
+
"""
|
| 69 |
+
Args:
|
| 70 |
+
pred_config (object): config of model, defined by `Config(model_dir)`
|
| 71 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 72 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 73 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 74 |
+
batch_size (int): size of pre batch in inference
|
| 75 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 76 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 77 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 78 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 79 |
+
calibration, trt_calib_mode need to set True
|
| 80 |
+
cpu_threads (int): cpu threads
|
| 81 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 82 |
+
output_dir (str): The path of output
|
| 83 |
+
threshold (float): The threshold of score for visualization
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
def __init__(
|
| 87 |
+
self,
|
| 88 |
+
model_dir,
|
| 89 |
+
device='CPU',
|
| 90 |
+
run_mode='paddle',
|
| 91 |
+
batch_size=1,
|
| 92 |
+
trt_min_shape=1,
|
| 93 |
+
trt_max_shape=1280,
|
| 94 |
+
trt_opt_shape=640,
|
| 95 |
+
trt_calib_mode=False,
|
| 96 |
+
cpu_threads=1,
|
| 97 |
+
enable_mkldnn=False,
|
| 98 |
+
output_dir='output',
|
| 99 |
+
threshold=0.5, ):
|
| 100 |
+
self.pred_config = self.set_config(model_dir)
|
| 101 |
+
self.predictor, self.config = load_predictor(
|
| 102 |
+
model_dir,
|
| 103 |
+
run_mode=run_mode,
|
| 104 |
+
batch_size=batch_size,
|
| 105 |
+
min_subgraph_size=self.pred_config.min_subgraph_size,
|
| 106 |
+
device=device,
|
| 107 |
+
use_dynamic_shape=self.pred_config.use_dynamic_shape,
|
| 108 |
+
trt_min_shape=trt_min_shape,
|
| 109 |
+
trt_max_shape=trt_max_shape,
|
| 110 |
+
trt_opt_shape=trt_opt_shape,
|
| 111 |
+
trt_calib_mode=trt_calib_mode,
|
| 112 |
+
cpu_threads=cpu_threads,
|
| 113 |
+
enable_mkldnn=enable_mkldnn)
|
| 114 |
+
self.det_times = Timer()
|
| 115 |
+
self.cpu_mem, self.gpu_mem, self.gpu_util = 0, 0, 0
|
| 116 |
+
self.batch_size = batch_size
|
| 117 |
+
self.output_dir = output_dir
|
| 118 |
+
self.threshold = threshold
|
| 119 |
+
|
| 120 |
+
def set_config(self, model_dir):
|
| 121 |
+
return PredictConfig(model_dir)
|
| 122 |
+
|
| 123 |
+
def preprocess(self, image_list):
|
| 124 |
+
preprocess_ops = []
|
| 125 |
+
for op_info in self.pred_config.preprocess_infos:
|
| 126 |
+
new_op_info = op_info.copy()
|
| 127 |
+
op_type = new_op_info.pop('type')
|
| 128 |
+
preprocess_ops.append(eval(op_type)(**new_op_info))
|
| 129 |
+
|
| 130 |
+
input_im_lst = []
|
| 131 |
+
input_im_info_lst = []
|
| 132 |
+
for im_path in image_list:
|
| 133 |
+
im, im_info = preprocess(im_path, preprocess_ops)
|
| 134 |
+
input_im_lst.append(im)
|
| 135 |
+
input_im_info_lst.append(im_info)
|
| 136 |
+
inputs = create_inputs(input_im_lst, input_im_info_lst)
|
| 137 |
+
input_names = self.predictor.get_input_names()
|
| 138 |
+
for i in range(len(input_names)):
|
| 139 |
+
input_tensor = self.predictor.get_input_handle(input_names[i])
|
| 140 |
+
input_tensor.copy_from_cpu(inputs[input_names[i]])
|
| 141 |
+
|
| 142 |
+
return inputs
|
| 143 |
+
|
| 144 |
+
def postprocess(self, inputs, result):
|
| 145 |
+
# postprocess output of predictor
|
| 146 |
+
np_boxes_num = result['boxes_num']
|
| 147 |
+
if np_boxes_num[0] <= 0:
|
| 148 |
+
print('[WARNNING] No object detected.')
|
| 149 |
+
result = {'boxes': np.zeros([0, 6]), 'boxes_num': [0]}
|
| 150 |
+
result = {k: v for k, v in result.items() if v is not None}
|
| 151 |
+
return result
|
| 152 |
+
|
| 153 |
+
def predict(self, repeats=1):
|
| 154 |
+
'''
|
| 155 |
+
Args:
|
| 156 |
+
repeats (int): repeats number for prediction
|
| 157 |
+
Returns:
|
| 158 |
+
result (dict): include 'boxes': np.ndarray: shape:[N,6], N: number of box,
|
| 159 |
+
matix element:[class, score, x_min, y_min, x_max, y_max]
|
| 160 |
+
'''
|
| 161 |
+
# model prediction
|
| 162 |
+
np_boxes, np_boxes_num = None, None
|
| 163 |
+
for i in range(repeats):
|
| 164 |
+
self.predictor.run()
|
| 165 |
+
output_names = self.predictor.get_output_names()
|
| 166 |
+
boxes_tensor = self.predictor.get_output_handle(output_names[0])
|
| 167 |
+
np_boxes = boxes_tensor.copy_to_cpu()
|
| 168 |
+
boxes_num = self.predictor.get_output_handle(output_names[1])
|
| 169 |
+
np_boxes_num = boxes_num.copy_to_cpu()
|
| 170 |
+
result = dict(boxes=np_boxes, boxes_num=np_boxes_num)
|
| 171 |
+
return result
|
| 172 |
+
|
| 173 |
+
def merge_batch_result(self, batch_result):
|
| 174 |
+
if len(batch_result) == 1:
|
| 175 |
+
return batch_result[0]
|
| 176 |
+
res_key = batch_result[0].keys()
|
| 177 |
+
results = {k: [] for k in res_key}
|
| 178 |
+
for res in batch_result:
|
| 179 |
+
for k, v in res.items():
|
| 180 |
+
results[k].append(v)
|
| 181 |
+
for k, v in results.items():
|
| 182 |
+
results[k] = np.concatenate(v)
|
| 183 |
+
return results
|
| 184 |
+
|
| 185 |
+
def get_timer(self):
|
| 186 |
+
return self.det_times
|
| 187 |
+
|
| 188 |
+
def predict_image(self,
|
| 189 |
+
image_list,
|
| 190 |
+
run_benchmark=False,
|
| 191 |
+
repeats=1,
|
| 192 |
+
visual=True):
|
| 193 |
+
batch_loop_cnt = math.ceil(float(len(image_list)) / self.batch_size)
|
| 194 |
+
results = []
|
| 195 |
+
for i in range(batch_loop_cnt):
|
| 196 |
+
start_index = i * self.batch_size
|
| 197 |
+
end_index = min((i + 1) * self.batch_size, len(image_list))
|
| 198 |
+
batch_image_list = image_list[start_index:end_index]
|
| 199 |
+
if run_benchmark:
|
| 200 |
+
# preprocess
|
| 201 |
+
inputs = self.preprocess(batch_image_list) # warmup
|
| 202 |
+
self.det_times.preprocess_time_s.start()
|
| 203 |
+
inputs = self.preprocess(batch_image_list)
|
| 204 |
+
self.det_times.preprocess_time_s.end()
|
| 205 |
+
|
| 206 |
+
# model prediction
|
| 207 |
+
result = self.predict(repeats=repeats) # warmup
|
| 208 |
+
self.det_times.inference_time_s.start()
|
| 209 |
+
result = self.predict(repeats=repeats)
|
| 210 |
+
self.det_times.inference_time_s.end(repeats=repeats)
|
| 211 |
+
|
| 212 |
+
# postprocess
|
| 213 |
+
result_warmup = self.postprocess(inputs, result) # warmup
|
| 214 |
+
self.det_times.postprocess_time_s.start()
|
| 215 |
+
result = self.postprocess(inputs, result)
|
| 216 |
+
self.det_times.postprocess_time_s.end()
|
| 217 |
+
self.det_times.img_num += len(batch_image_list)
|
| 218 |
+
|
| 219 |
+
cm, gm, gu = get_current_memory_mb()
|
| 220 |
+
self.cpu_mem += cm
|
| 221 |
+
self.gpu_mem += gm
|
| 222 |
+
self.gpu_util += gu
|
| 223 |
+
else:
|
| 224 |
+
# preprocess
|
| 225 |
+
self.det_times.preprocess_time_s.start()
|
| 226 |
+
inputs = self.preprocess(batch_image_list)
|
| 227 |
+
self.det_times.preprocess_time_s.end()
|
| 228 |
+
|
| 229 |
+
# model prediction
|
| 230 |
+
self.det_times.inference_time_s.start()
|
| 231 |
+
result = self.predict()
|
| 232 |
+
self.det_times.inference_time_s.end()
|
| 233 |
+
|
| 234 |
+
# postprocess
|
| 235 |
+
self.det_times.postprocess_time_s.start()
|
| 236 |
+
result = self.postprocess(inputs, result)
|
| 237 |
+
self.det_times.postprocess_time_s.end()
|
| 238 |
+
self.det_times.img_num += len(batch_image_list)
|
| 239 |
+
|
| 240 |
+
if visual:
|
| 241 |
+
visualize(
|
| 242 |
+
batch_image_list,
|
| 243 |
+
result,
|
| 244 |
+
self.pred_config.labels,
|
| 245 |
+
output_dir=self.output_dir,
|
| 246 |
+
threshold=self.threshold)
|
| 247 |
+
|
| 248 |
+
results.append(result)
|
| 249 |
+
if visual:
|
| 250 |
+
print('Test iter {}'.format(i))
|
| 251 |
+
|
| 252 |
+
results = self.merge_batch_result(results)
|
| 253 |
+
return results
|
| 254 |
+
|
| 255 |
+
def predict_video(self, video_file, camera_id):
|
| 256 |
+
video_out_name = 'output.mp4'
|
| 257 |
+
if camera_id != -1:
|
| 258 |
+
capture = cv2.VideoCapture(camera_id)
|
| 259 |
+
else:
|
| 260 |
+
capture = cv2.VideoCapture(video_file)
|
| 261 |
+
video_out_name = os.path.split(video_file)[-1]
|
| 262 |
+
# Get Video info : resolution, fps, frame count
|
| 263 |
+
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 264 |
+
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 265 |
+
fps = int(capture.get(cv2.CAP_PROP_FPS))
|
| 266 |
+
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 267 |
+
print("fps: %d, frame_count: %d" % (fps, frame_count))
|
| 268 |
+
|
| 269 |
+
if not os.path.exists(self.output_dir):
|
| 270 |
+
os.makedirs(self.output_dir)
|
| 271 |
+
out_path = os.path.join(self.output_dir, video_out_name)
|
| 272 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 273 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
|
| 274 |
+
index = 1
|
| 275 |
+
while (1):
|
| 276 |
+
ret, frame = capture.read()
|
| 277 |
+
if not ret:
|
| 278 |
+
break
|
| 279 |
+
print('detect frame: %d' % (index))
|
| 280 |
+
index += 1
|
| 281 |
+
results = self.predict_image([frame], visual=False)
|
| 282 |
+
|
| 283 |
+
im = visualize_box_mask(
|
| 284 |
+
frame,
|
| 285 |
+
results,
|
| 286 |
+
self.pred_config.labels,
|
| 287 |
+
threshold=self.threshold)
|
| 288 |
+
im = np.array(im)
|
| 289 |
+
writer.write(im)
|
| 290 |
+
if camera_id != -1:
|
| 291 |
+
cv2.imshow('Mask Detection', im)
|
| 292 |
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
| 293 |
+
break
|
| 294 |
+
writer.release()
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def create_inputs(imgs, im_info):
|
| 298 |
+
"""generate input for different model type
|
| 299 |
+
Args:
|
| 300 |
+
imgs (list(numpy)): list of images (np.ndarray)
|
| 301 |
+
im_info (list(dict)): list of image info
|
| 302 |
+
Returns:
|
| 303 |
+
inputs (dict): input of model
|
| 304 |
+
"""
|
| 305 |
+
inputs = {}
|
| 306 |
+
|
| 307 |
+
im_shape = []
|
| 308 |
+
scale_factor = []
|
| 309 |
+
if len(imgs) == 1:
|
| 310 |
+
inputs['image'] = np.array((imgs[0], )).astype('float32')
|
| 311 |
+
inputs['im_shape'] = np.array(
|
| 312 |
+
(im_info[0]['im_shape'], )).astype('float32')
|
| 313 |
+
inputs['scale_factor'] = np.array(
|
| 314 |
+
(im_info[0]['scale_factor'], )).astype('float32')
|
| 315 |
+
return inputs
|
| 316 |
+
|
| 317 |
+
for e in im_info:
|
| 318 |
+
im_shape.append(np.array((e['im_shape'], )).astype('float32'))
|
| 319 |
+
scale_factor.append(np.array((e['scale_factor'], )).astype('float32'))
|
| 320 |
+
|
| 321 |
+
inputs['im_shape'] = np.concatenate(im_shape, axis=0)
|
| 322 |
+
inputs['scale_factor'] = np.concatenate(scale_factor, axis=0)
|
| 323 |
+
|
| 324 |
+
imgs_shape = [[e.shape[1], e.shape[2]] for e in imgs]
|
| 325 |
+
max_shape_h = max([e[0] for e in imgs_shape])
|
| 326 |
+
max_shape_w = max([e[1] for e in imgs_shape])
|
| 327 |
+
padding_imgs = []
|
| 328 |
+
for img in imgs:
|
| 329 |
+
im_c, im_h, im_w = img.shape[:]
|
| 330 |
+
padding_im = np.zeros(
|
| 331 |
+
(im_c, max_shape_h, max_shape_w), dtype=np.float32)
|
| 332 |
+
padding_im[:, :im_h, :im_w] = img
|
| 333 |
+
padding_imgs.append(padding_im)
|
| 334 |
+
inputs['image'] = np.stack(padding_imgs, axis=0)
|
| 335 |
+
return inputs
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
class PredictConfig():
|
| 339 |
+
"""set config of preprocess, postprocess and visualize
|
| 340 |
+
Args:
|
| 341 |
+
model_dir (str): root path of model.yml
|
| 342 |
+
"""
|
| 343 |
+
|
| 344 |
+
def __init__(self, model_dir):
|
| 345 |
+
# parsing Yaml config for Preprocess
|
| 346 |
+
deploy_file = os.path.join(model_dir, 'infer_cfg.yml')
|
| 347 |
+
with open(deploy_file) as f:
|
| 348 |
+
yml_conf = yaml.safe_load(f)
|
| 349 |
+
self.check_model(yml_conf)
|
| 350 |
+
self.arch = yml_conf['arch']
|
| 351 |
+
self.preprocess_infos = yml_conf['Preprocess']
|
| 352 |
+
self.min_subgraph_size = yml_conf['min_subgraph_size']
|
| 353 |
+
self.labels = yml_conf['label_list']
|
| 354 |
+
self.mask = False
|
| 355 |
+
self.use_dynamic_shape = yml_conf['use_dynamic_shape']
|
| 356 |
+
if 'mask' in yml_conf:
|
| 357 |
+
self.mask = yml_conf['mask']
|
| 358 |
+
self.tracker = None
|
| 359 |
+
if 'tracker' in yml_conf:
|
| 360 |
+
self.tracker = yml_conf['tracker']
|
| 361 |
+
if 'NMS' in yml_conf:
|
| 362 |
+
self.nms = yml_conf['NMS']
|
| 363 |
+
if 'fpn_stride' in yml_conf:
|
| 364 |
+
self.fpn_stride = yml_conf['fpn_stride']
|
| 365 |
+
self.print_config()
|
| 366 |
+
|
| 367 |
+
def check_model(self, yml_conf):
|
| 368 |
+
"""
|
| 369 |
+
Raises:
|
| 370 |
+
ValueError: loaded model not in supported model type
|
| 371 |
+
"""
|
| 372 |
+
for support_model in SUPPORT_MODELS:
|
| 373 |
+
if support_model in yml_conf['arch']:
|
| 374 |
+
return True
|
| 375 |
+
raise ValueError("Unsupported arch: {}, expect {}".format(yml_conf[
|
| 376 |
+
'arch'], SUPPORT_MODELS))
|
| 377 |
+
|
| 378 |
+
def print_config(self):
|
| 379 |
+
print('----------- Model Configuration -----------')
|
| 380 |
+
print('%s: %s' % ('Model Arch', self.arch))
|
| 381 |
+
print('%s: ' % ('Transform Order'))
|
| 382 |
+
for op_info in self.preprocess_infos:
|
| 383 |
+
print('--%s: %s' % ('transform op', op_info['type']))
|
| 384 |
+
print('--------------------------------------------')
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def load_predictor(model_dir,
|
| 388 |
+
run_mode='paddle',
|
| 389 |
+
batch_size=1,
|
| 390 |
+
device='CPU',
|
| 391 |
+
min_subgraph_size=3,
|
| 392 |
+
use_dynamic_shape=False,
|
| 393 |
+
trt_min_shape=1,
|
| 394 |
+
trt_max_shape=1280,
|
| 395 |
+
trt_opt_shape=640,
|
| 396 |
+
trt_calib_mode=False,
|
| 397 |
+
cpu_threads=1,
|
| 398 |
+
enable_mkldnn=False):
|
| 399 |
+
"""set AnalysisConfig, generate AnalysisPredictor
|
| 400 |
+
Args:
|
| 401 |
+
model_dir (str): root path of __model__ and __params__
|
| 402 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 403 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16/trt_int8)
|
| 404 |
+
use_dynamic_shape (bool): use dynamic shape or not
|
| 405 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 406 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 407 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 408 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 409 |
+
calibration, trt_calib_mode need to set True
|
| 410 |
+
Returns:
|
| 411 |
+
predictor (PaddlePredictor): AnalysisPredictor
|
| 412 |
+
Raises:
|
| 413 |
+
ValueError: predict by TensorRT need device == 'GPU'.
|
| 414 |
+
"""
|
| 415 |
+
if device != 'GPU' and run_mode != 'paddle':
|
| 416 |
+
raise ValueError(
|
| 417 |
+
"Predict by TensorRT mode: {}, expect device=='GPU', but device == {}"
|
| 418 |
+
.format(run_mode, device))
|
| 419 |
+
infer_model = os.path.join(model_dir, 'model.pdmodel')
|
| 420 |
+
infer_params = os.path.join(model_dir, 'model.pdiparams')
|
| 421 |
+
if not os.path.exists(infer_model):
|
| 422 |
+
infer_model = os.path.join(model_dir, 'inference.pdmodel')
|
| 423 |
+
infer_params = os.path.join(model_dir, 'inference.pdiparams')
|
| 424 |
+
if not os.path.exists(infer_model):
|
| 425 |
+
raise ValueError("Cannot find any inference model in dir: {},".
|
| 426 |
+
format(model_dir))
|
| 427 |
+
config = Config(infer_model, infer_params)
|
| 428 |
+
if device == 'GPU':
|
| 429 |
+
# initial GPU memory(M), device ID
|
| 430 |
+
config.enable_use_gpu(200, 0)
|
| 431 |
+
# optimize graph and fuse op
|
| 432 |
+
config.switch_ir_optim(True)
|
| 433 |
+
elif device == 'XPU':
|
| 434 |
+
config.enable_lite_engine()
|
| 435 |
+
config.enable_xpu(10 * 1024 * 1024)
|
| 436 |
+
else:
|
| 437 |
+
config.disable_gpu()
|
| 438 |
+
config.set_cpu_math_library_num_threads(cpu_threads)
|
| 439 |
+
if enable_mkldnn:
|
| 440 |
+
try:
|
| 441 |
+
# cache 10 different shapes for mkldnn to avoid memory leak
|
| 442 |
+
config.set_mkldnn_cache_capacity(10)
|
| 443 |
+
config.enable_mkldnn()
|
| 444 |
+
except Exception as e:
|
| 445 |
+
print(
|
| 446 |
+
"The current environment does not support `mkldnn`, so disable mkldnn."
|
| 447 |
+
)
|
| 448 |
+
pass
|
| 449 |
+
|
| 450 |
+
precision_map = {
|
| 451 |
+
'trt_int8': Config.Precision.Int8,
|
| 452 |
+
'trt_fp32': Config.Precision.Float32,
|
| 453 |
+
'trt_fp16': Config.Precision.Half
|
| 454 |
+
}
|
| 455 |
+
if run_mode in precision_map.keys():
|
| 456 |
+
config.enable_tensorrt_engine(
|
| 457 |
+
workspace_size=1 << 25,
|
| 458 |
+
max_batch_size=batch_size,
|
| 459 |
+
min_subgraph_size=min_subgraph_size,
|
| 460 |
+
precision_mode=precision_map[run_mode],
|
| 461 |
+
use_static=False,
|
| 462 |
+
use_calib_mode=trt_calib_mode)
|
| 463 |
+
|
| 464 |
+
if use_dynamic_shape:
|
| 465 |
+
min_input_shape = {
|
| 466 |
+
'image': [batch_size, 3, trt_min_shape, trt_min_shape]
|
| 467 |
+
}
|
| 468 |
+
max_input_shape = {
|
| 469 |
+
'image': [batch_size, 3, trt_max_shape, trt_max_shape]
|
| 470 |
+
}
|
| 471 |
+
opt_input_shape = {
|
| 472 |
+
'image': [batch_size, 3, trt_opt_shape, trt_opt_shape]
|
| 473 |
+
}
|
| 474 |
+
config.set_trt_dynamic_shape_info(min_input_shape, max_input_shape,
|
| 475 |
+
opt_input_shape)
|
| 476 |
+
print('trt set dynamic shape done!')
|
| 477 |
+
|
| 478 |
+
# disable print log when predict
|
| 479 |
+
config.disable_glog_info()
|
| 480 |
+
# enable shared memory
|
| 481 |
+
config.enable_memory_optim()
|
| 482 |
+
# disable feed, fetch OP, needed by zero_copy_run
|
| 483 |
+
config.switch_use_feed_fetch_ops(False)
|
| 484 |
+
predictor = create_predictor(config)
|
| 485 |
+
return predictor, config
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
def get_test_images(infer_dir, infer_img):
|
| 489 |
+
"""
|
| 490 |
+
Get image path list in TEST mode
|
| 491 |
+
"""
|
| 492 |
+
assert infer_img is not None or infer_dir is not None, \
|
| 493 |
+
"--infer_img or --infer_dir should be set"
|
| 494 |
+
assert infer_img is None or os.path.isfile(infer_img), \
|
| 495 |
+
"{} is not a file".format(infer_img)
|
| 496 |
+
assert infer_dir is None or os.path.isdir(infer_dir), \
|
| 497 |
+
"{} is not a directory".format(infer_dir)
|
| 498 |
+
|
| 499 |
+
# infer_img has a higher priority
|
| 500 |
+
if infer_img and os.path.isfile(infer_img):
|
| 501 |
+
return [infer_img]
|
| 502 |
+
|
| 503 |
+
images = set()
|
| 504 |
+
infer_dir = os.path.abspath(infer_dir)
|
| 505 |
+
assert os.path.isdir(infer_dir), \
|
| 506 |
+
"infer_dir {} is not a directory".format(infer_dir)
|
| 507 |
+
exts = ['jpg', 'jpeg', 'png', 'bmp']
|
| 508 |
+
exts += [ext.upper() for ext in exts]
|
| 509 |
+
for ext in exts:
|
| 510 |
+
images.update(glob.glob('{}/*.{}'.format(infer_dir, ext)))
|
| 511 |
+
images = list(images)
|
| 512 |
+
|
| 513 |
+
assert len(images) > 0, "no image found in {}".format(infer_dir)
|
| 514 |
+
print("Found {} inference images in total.".format(len(images)))
|
| 515 |
+
|
| 516 |
+
return images
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
def visualize(image_list, result, labels, output_dir='output/', threshold=0.5):
|
| 520 |
+
# visualize the predict result
|
| 521 |
+
start_idx = 0
|
| 522 |
+
for idx, image_file in enumerate(image_list):
|
| 523 |
+
im_bboxes_num = result['boxes_num'][idx]
|
| 524 |
+
im_results = {}
|
| 525 |
+
if 'boxes' in result:
|
| 526 |
+
im_results['boxes'] = result['boxes'][start_idx:start_idx +
|
| 527 |
+
im_bboxes_num, :]
|
| 528 |
+
start_idx += im_bboxes_num
|
| 529 |
+
im = visualize_box_mask(
|
| 530 |
+
image_file, im_results, labels, threshold=threshold)
|
| 531 |
+
img_name = os.path.split(image_file)[-1]
|
| 532 |
+
if not os.path.exists(output_dir):
|
| 533 |
+
os.makedirs(output_dir)
|
| 534 |
+
out_path = os.path.join(output_dir, img_name)
|
| 535 |
+
im.save(out_path, quality=95)
|
| 536 |
+
print("save result to: " + out_path)
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
def print_arguments(args):
|
| 540 |
+
print('----------- Running Arguments -----------')
|
| 541 |
+
for arg, value in sorted(vars(args).items()):
|
| 542 |
+
print('%s: %s' % (arg, value))
|
| 543 |
+
print('------------------------------------------')
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def main():
|
| 547 |
+
deploy_file = os.path.join(FLAGS.model_dir, 'infer_cfg.yml')
|
| 548 |
+
with open(deploy_file) as f:
|
| 549 |
+
yml_conf = yaml.safe_load(f)
|
| 550 |
+
arch = yml_conf['arch']
|
| 551 |
+
detector_func = 'Detector'
|
| 552 |
+
detector = eval(detector_func)(FLAGS.model_dir,
|
| 553 |
+
device=FLAGS.device,
|
| 554 |
+
run_mode=FLAGS.run_mode,
|
| 555 |
+
batch_size=FLAGS.batch_size,
|
| 556 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 557 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 558 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 559 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 560 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 561 |
+
enable_mkldnn=FLAGS.enable_mkldnn,
|
| 562 |
+
threshold=FLAGS.threshold,
|
| 563 |
+
output_dir=FLAGS.output_dir)
|
| 564 |
+
|
| 565 |
+
# predict from video file or camera video stream
|
| 566 |
+
if FLAGS.video_file is not None or FLAGS.camera_id != -1:
|
| 567 |
+
detector.predict_video(FLAGS.video_file, FLAGS.camera_id)
|
| 568 |
+
else:
|
| 569 |
+
# predict from image
|
| 570 |
+
if FLAGS.image_dir is None and FLAGS.image_file is not None:
|
| 571 |
+
assert FLAGS.batch_size == 1, "batch_size should be 1, when image_file is not None"
|
| 572 |
+
img_list = get_test_images(FLAGS.image_dir, FLAGS.image_file)
|
| 573 |
+
detector.predict_image(img_list, FLAGS.run_benchmark, repeats=10)
|
| 574 |
+
if not FLAGS.run_benchmark:
|
| 575 |
+
detector.det_times.info(average=True)
|
| 576 |
+
else:
|
| 577 |
+
mode = FLAGS.run_mode
|
| 578 |
+
model_dir = FLAGS.model_dir
|
| 579 |
+
model_info = {
|
| 580 |
+
'model_name': model_dir.strip('/').split('/')[-1],
|
| 581 |
+
'precision': mode.split('_')[-1]
|
| 582 |
+
}
|
| 583 |
+
bench_log(detector, img_list, model_info, name='DET')
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
if __name__ == '__main__':
|
| 587 |
+
paddle.enable_static()
|
| 588 |
+
parser = argsparser()
|
| 589 |
+
FLAGS = parser.parse_args()
|
| 590 |
+
print_arguments(FLAGS)
|
| 591 |
+
FLAGS.device = FLAGS.device.upper()
|
| 592 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 593 |
+
], "device should be CPU, GPU or XPU"
|
| 594 |
+
main()
|
pptracking/python/mot/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
from . import matching
|
| 16 |
+
from . import tracker
|
| 17 |
+
from . import motion
|
| 18 |
+
from . import utils
|
| 19 |
+
from . import mtmct
|
| 20 |
+
|
| 21 |
+
from .matching import *
|
| 22 |
+
from .tracker import *
|
| 23 |
+
from .motion import *
|
| 24 |
+
from .utils import *
|
| 25 |
+
from .mtmct import *
|
pptracking/python/mot/matching/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
from . import jde_matching
|
| 16 |
+
from . import deepsort_matching
|
| 17 |
+
from . import ocsort_matching
|
| 18 |
+
|
| 19 |
+
from .jde_matching import *
|
| 20 |
+
from .deepsort_matching import *
|
| 21 |
+
from .ocsort_matching import *
|
pptracking/python/mot/matching/deepsort_matching.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/nwojke/deep_sort/tree/master/deep_sort
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
from scipy.optimize import linear_sum_assignment
|
| 20 |
+
from ..motion import kalman_filter
|
| 21 |
+
|
| 22 |
+
INFTY_COST = 1e+5
|
| 23 |
+
|
| 24 |
+
__all__ = [
|
| 25 |
+
'iou_1toN',
|
| 26 |
+
'iou_cost',
|
| 27 |
+
'_nn_euclidean_distance',
|
| 28 |
+
'_nn_cosine_distance',
|
| 29 |
+
'NearestNeighborDistanceMetric',
|
| 30 |
+
'min_cost_matching',
|
| 31 |
+
'matching_cascade',
|
| 32 |
+
'gate_cost_matrix',
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def iou_1toN(bbox, candidates):
|
| 37 |
+
"""
|
| 38 |
+
Computer intersection over union (IoU) by one box to N candidates.
|
| 39 |
+
|
| 40 |
+
Args:
|
| 41 |
+
bbox (ndarray): A bounding box in format `(top left x, top left y, width, height)`.
|
| 42 |
+
candidates (ndarray): A matrix of candidate bounding boxes (one per row) in the
|
| 43 |
+
same format as `bbox`.
|
| 44 |
+
|
| 45 |
+
Returns:
|
| 46 |
+
ious (ndarray): The intersection over union in [0, 1] between the `bbox`
|
| 47 |
+
and each candidate. A higher score means a larger fraction of the
|
| 48 |
+
`bbox` is occluded by the candidate.
|
| 49 |
+
"""
|
| 50 |
+
bbox_tl = bbox[:2]
|
| 51 |
+
bbox_br = bbox[:2] + bbox[2:]
|
| 52 |
+
candidates_tl = candidates[:, :2]
|
| 53 |
+
candidates_br = candidates[:, :2] + candidates[:, 2:]
|
| 54 |
+
|
| 55 |
+
tl = np.c_[np.maximum(bbox_tl[0], candidates_tl[:, 0])[:, np.newaxis],
|
| 56 |
+
np.maximum(bbox_tl[1], candidates_tl[:, 1])[:, np.newaxis]]
|
| 57 |
+
br = np.c_[np.minimum(bbox_br[0], candidates_br[:, 0])[:, np.newaxis],
|
| 58 |
+
np.minimum(bbox_br[1], candidates_br[:, 1])[:, np.newaxis]]
|
| 59 |
+
wh = np.maximum(0., br - tl)
|
| 60 |
+
|
| 61 |
+
area_intersection = wh.prod(axis=1)
|
| 62 |
+
area_bbox = bbox[2:].prod()
|
| 63 |
+
area_candidates = candidates[:, 2:].prod(axis=1)
|
| 64 |
+
ious = area_intersection / (
|
| 65 |
+
area_bbox + area_candidates - area_intersection)
|
| 66 |
+
return ious
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def iou_cost(tracks, detections, track_indices=None, detection_indices=None):
|
| 70 |
+
"""
|
| 71 |
+
IoU distance metric.
|
| 72 |
+
|
| 73 |
+
Args:
|
| 74 |
+
tracks (list[Track]): A list of tracks.
|
| 75 |
+
detections (list[Detection]): A list of detections.
|
| 76 |
+
track_indices (Optional[list[int]]): A list of indices to tracks that
|
| 77 |
+
should be matched. Defaults to all `tracks`.
|
| 78 |
+
detection_indices (Optional[list[int]]): A list of indices to detections
|
| 79 |
+
that should be matched. Defaults to all `detections`.
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
cost_matrix (ndarray): A cost matrix of shape len(track_indices),
|
| 83 |
+
len(detection_indices) where entry (i, j) is
|
| 84 |
+
`1 - iou(tracks[track_indices[i]], detections[detection_indices[j]])`.
|
| 85 |
+
"""
|
| 86 |
+
if track_indices is None:
|
| 87 |
+
track_indices = np.arange(len(tracks))
|
| 88 |
+
if detection_indices is None:
|
| 89 |
+
detection_indices = np.arange(len(detections))
|
| 90 |
+
|
| 91 |
+
cost_matrix = np.zeros((len(track_indices), len(detection_indices)))
|
| 92 |
+
for row, track_idx in enumerate(track_indices):
|
| 93 |
+
if tracks[track_idx].time_since_update > 1:
|
| 94 |
+
cost_matrix[row, :] = 1e+5
|
| 95 |
+
continue
|
| 96 |
+
|
| 97 |
+
bbox = tracks[track_idx].to_tlwh()
|
| 98 |
+
candidates = np.asarray(
|
| 99 |
+
[detections[i].tlwh for i in detection_indices])
|
| 100 |
+
cost_matrix[row, :] = 1. - iou_1toN(bbox, candidates)
|
| 101 |
+
return cost_matrix
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _nn_euclidean_distance(s, q):
|
| 105 |
+
"""
|
| 106 |
+
Compute pair-wise squared (Euclidean) distance between points in `s` and `q`.
|
| 107 |
+
|
| 108 |
+
Args:
|
| 109 |
+
s (ndarray): Sample points: an NxM matrix of N samples of dimensionality M.
|
| 110 |
+
q (ndarray): Query points: an LxM matrix of L samples of dimensionality M.
|
| 111 |
+
|
| 112 |
+
Returns:
|
| 113 |
+
distances (ndarray): A vector of length M that contains for each entry in `q` the
|
| 114 |
+
smallest Euclidean distance to a sample in `s`.
|
| 115 |
+
"""
|
| 116 |
+
s, q = np.asarray(s), np.asarray(q)
|
| 117 |
+
if len(s) == 0 or len(q) == 0:
|
| 118 |
+
return np.zeros((len(s), len(q)))
|
| 119 |
+
s2, q2 = np.square(s).sum(axis=1), np.square(q).sum(axis=1)
|
| 120 |
+
distances = -2. * np.dot(s, q.T) + s2[:, None] + q2[None, :]
|
| 121 |
+
distances = np.clip(distances, 0., float(np.inf))
|
| 122 |
+
|
| 123 |
+
return np.maximum(0.0, distances.min(axis=0))
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def _nn_cosine_distance(s, q):
|
| 127 |
+
"""
|
| 128 |
+
Compute pair-wise cosine distance between points in `s` and `q`.
|
| 129 |
+
|
| 130 |
+
Args:
|
| 131 |
+
s (ndarray): Sample points: an NxM matrix of N samples of dimensionality M.
|
| 132 |
+
q (ndarray): Query points: an LxM matrix of L samples of dimensionality M.
|
| 133 |
+
|
| 134 |
+
Returns:
|
| 135 |
+
distances (ndarray): A vector of length M that contains for each entry in `q` the
|
| 136 |
+
smallest Euclidean distance to a sample in `s`.
|
| 137 |
+
"""
|
| 138 |
+
s = np.asarray(s) / np.linalg.norm(s, axis=1, keepdims=True)
|
| 139 |
+
q = np.asarray(q) / np.linalg.norm(q, axis=1, keepdims=True)
|
| 140 |
+
distances = 1. - np.dot(s, q.T)
|
| 141 |
+
|
| 142 |
+
return distances.min(axis=0)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
class NearestNeighborDistanceMetric(object):
|
| 146 |
+
"""
|
| 147 |
+
A nearest neighbor distance metric that, for each target, returns
|
| 148 |
+
the closest distance to any sample that has been observed so far.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
metric (str): Either "euclidean" or "cosine".
|
| 152 |
+
matching_threshold (float): The matching threshold. Samples with larger
|
| 153 |
+
distance are considered an invalid match.
|
| 154 |
+
budget (Optional[int]): If not None, fix samples per class to at most
|
| 155 |
+
this number. Removes the oldest samples when the budget is reached.
|
| 156 |
+
|
| 157 |
+
Attributes:
|
| 158 |
+
samples (Dict[int -> List[ndarray]]): A dictionary that maps from target
|
| 159 |
+
identities to the list of samples that have been observed so far.
|
| 160 |
+
"""
|
| 161 |
+
|
| 162 |
+
def __init__(self, metric, matching_threshold, budget=None):
|
| 163 |
+
if metric == "euclidean":
|
| 164 |
+
self._metric = _nn_euclidean_distance
|
| 165 |
+
elif metric == "cosine":
|
| 166 |
+
self._metric = _nn_cosine_distance
|
| 167 |
+
else:
|
| 168 |
+
raise ValueError(
|
| 169 |
+
"Invalid metric; must be either 'euclidean' or 'cosine'")
|
| 170 |
+
self.matching_threshold = matching_threshold
|
| 171 |
+
self.budget = budget
|
| 172 |
+
self.samples = {}
|
| 173 |
+
|
| 174 |
+
def partial_fit(self, features, targets, active_targets):
|
| 175 |
+
"""
|
| 176 |
+
Update the distance metric with new data.
|
| 177 |
+
|
| 178 |
+
Args:
|
| 179 |
+
features (ndarray): An NxM matrix of N features of dimensionality M.
|
| 180 |
+
targets (ndarray): An integer array of associated target identities.
|
| 181 |
+
active_targets (List[int]): A list of targets that are currently
|
| 182 |
+
present in the scene.
|
| 183 |
+
"""
|
| 184 |
+
for feature, target in zip(features, targets):
|
| 185 |
+
self.samples.setdefault(target, []).append(feature)
|
| 186 |
+
if self.budget is not None:
|
| 187 |
+
self.samples[target] = self.samples[target][-self.budget:]
|
| 188 |
+
self.samples = {k: self.samples[k] for k in active_targets}
|
| 189 |
+
|
| 190 |
+
def distance(self, features, targets):
|
| 191 |
+
"""
|
| 192 |
+
Compute distance between features and targets.
|
| 193 |
+
|
| 194 |
+
Args:
|
| 195 |
+
features (ndarray): An NxM matrix of N features of dimensionality M.
|
| 196 |
+
targets (list[int]): A list of targets to match the given `features` against.
|
| 197 |
+
|
| 198 |
+
Returns:
|
| 199 |
+
cost_matrix (ndarray): a cost matrix of shape len(targets), len(features),
|
| 200 |
+
where element (i, j) contains the closest squared distance between
|
| 201 |
+
`targets[i]` and `features[j]`.
|
| 202 |
+
"""
|
| 203 |
+
cost_matrix = np.zeros((len(targets), len(features)))
|
| 204 |
+
for i, target in enumerate(targets):
|
| 205 |
+
cost_matrix[i, :] = self._metric(self.samples[target], features)
|
| 206 |
+
return cost_matrix
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def min_cost_matching(distance_metric,
|
| 210 |
+
max_distance,
|
| 211 |
+
tracks,
|
| 212 |
+
detections,
|
| 213 |
+
track_indices=None,
|
| 214 |
+
detection_indices=None):
|
| 215 |
+
"""
|
| 216 |
+
Solve linear assignment problem.
|
| 217 |
+
|
| 218 |
+
Args:
|
| 219 |
+
distance_metric :
|
| 220 |
+
Callable[List[Track], List[Detection], List[int], List[int]) -> ndarray
|
| 221 |
+
The distance metric is given a list of tracks and detections as
|
| 222 |
+
well as a list of N track indices and M detection indices. The
|
| 223 |
+
metric should return the NxM dimensional cost matrix, where element
|
| 224 |
+
(i, j) is the association cost between the i-th track in the given
|
| 225 |
+
track indices and the j-th detection in the given detection_indices.
|
| 226 |
+
max_distance (float): Gating threshold. Associations with cost larger
|
| 227 |
+
than this value are disregarded.
|
| 228 |
+
tracks (list[Track]): A list of predicted tracks at the current time
|
| 229 |
+
step.
|
| 230 |
+
detections (list[Detection]): A list of detections at the current time
|
| 231 |
+
step.
|
| 232 |
+
track_indices (list[int]): List of track indices that maps rows in
|
| 233 |
+
`cost_matrix` to tracks in `tracks`.
|
| 234 |
+
detection_indices (List[int]): List of detection indices that maps
|
| 235 |
+
columns in `cost_matrix` to detections in `detections`.
|
| 236 |
+
|
| 237 |
+
Returns:
|
| 238 |
+
A tuple (List[(int, int)], List[int], List[int]) with the following
|
| 239 |
+
three entries:
|
| 240 |
+
* A list of matched track and detection indices.
|
| 241 |
+
* A list of unmatched track indices.
|
| 242 |
+
* A list of unmatched detection indices.
|
| 243 |
+
"""
|
| 244 |
+
if track_indices is None:
|
| 245 |
+
track_indices = np.arange(len(tracks))
|
| 246 |
+
if detection_indices is None:
|
| 247 |
+
detection_indices = np.arange(len(detections))
|
| 248 |
+
|
| 249 |
+
if len(detection_indices) == 0 or len(track_indices) == 0:
|
| 250 |
+
return [], track_indices, detection_indices # Nothing to match.
|
| 251 |
+
|
| 252 |
+
cost_matrix = distance_metric(tracks, detections, track_indices,
|
| 253 |
+
detection_indices)
|
| 254 |
+
|
| 255 |
+
cost_matrix[cost_matrix > max_distance] = max_distance + 1e-5
|
| 256 |
+
indices = linear_sum_assignment(cost_matrix)
|
| 257 |
+
|
| 258 |
+
matches, unmatched_tracks, unmatched_detections = [], [], []
|
| 259 |
+
for col, detection_idx in enumerate(detection_indices):
|
| 260 |
+
if col not in indices[1]:
|
| 261 |
+
unmatched_detections.append(detection_idx)
|
| 262 |
+
for row, track_idx in enumerate(track_indices):
|
| 263 |
+
if row not in indices[0]:
|
| 264 |
+
unmatched_tracks.append(track_idx)
|
| 265 |
+
for row, col in zip(indices[0], indices[1]):
|
| 266 |
+
track_idx = track_indices[row]
|
| 267 |
+
detection_idx = detection_indices[col]
|
| 268 |
+
if cost_matrix[row, col] > max_distance:
|
| 269 |
+
unmatched_tracks.append(track_idx)
|
| 270 |
+
unmatched_detections.append(detection_idx)
|
| 271 |
+
else:
|
| 272 |
+
matches.append((track_idx, detection_idx))
|
| 273 |
+
return matches, unmatched_tracks, unmatched_detections
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def matching_cascade(distance_metric,
|
| 277 |
+
max_distance,
|
| 278 |
+
cascade_depth,
|
| 279 |
+
tracks,
|
| 280 |
+
detections,
|
| 281 |
+
track_indices=None,
|
| 282 |
+
detection_indices=None):
|
| 283 |
+
"""
|
| 284 |
+
Run matching cascade.
|
| 285 |
+
|
| 286 |
+
Args:
|
| 287 |
+
distance_metric :
|
| 288 |
+
Callable[List[Track], List[Detection], List[int], List[int]) -> ndarray
|
| 289 |
+
The distance metric is given a list of tracks and detections as
|
| 290 |
+
well as a list of N track indices and M detection indices. The
|
| 291 |
+
metric should return the NxM dimensional cost matrix, where element
|
| 292 |
+
(i, j) is the association cost between the i-th track in the given
|
| 293 |
+
track indices and the j-th detection in the given detection_indices.
|
| 294 |
+
max_distance (float): Gating threshold. Associations with cost larger
|
| 295 |
+
than this value are disregarded.
|
| 296 |
+
cascade_depth (int): The cascade depth, should be se to the maximum
|
| 297 |
+
track age.
|
| 298 |
+
tracks (list[Track]): A list of predicted tracks at the current time
|
| 299 |
+
step.
|
| 300 |
+
detections (list[Detection]): A list of detections at the current time
|
| 301 |
+
step.
|
| 302 |
+
track_indices (list[int]): List of track indices that maps rows in
|
| 303 |
+
`cost_matrix` to tracks in `tracks`.
|
| 304 |
+
detection_indices (List[int]): List of detection indices that maps
|
| 305 |
+
columns in `cost_matrix` to detections in `detections`.
|
| 306 |
+
|
| 307 |
+
Returns:
|
| 308 |
+
A tuple (List[(int, int)], List[int], List[int]) with the following
|
| 309 |
+
three entries:
|
| 310 |
+
* A list of matched track and detection indices.
|
| 311 |
+
* A list of unmatched track indices.
|
| 312 |
+
* A list of unmatched detection indices.
|
| 313 |
+
"""
|
| 314 |
+
if track_indices is None:
|
| 315 |
+
track_indices = list(range(len(tracks)))
|
| 316 |
+
if detection_indices is None:
|
| 317 |
+
detection_indices = list(range(len(detections)))
|
| 318 |
+
|
| 319 |
+
unmatched_detections = detection_indices
|
| 320 |
+
matches = []
|
| 321 |
+
for level in range(cascade_depth):
|
| 322 |
+
if len(unmatched_detections) == 0: # No detections left
|
| 323 |
+
break
|
| 324 |
+
|
| 325 |
+
track_indices_l = [
|
| 326 |
+
k for k in track_indices
|
| 327 |
+
if tracks[k].time_since_update == 1 + level
|
| 328 |
+
]
|
| 329 |
+
if len(track_indices_l) == 0: # Nothing to match at this level
|
| 330 |
+
continue
|
| 331 |
+
|
| 332 |
+
matches_l, _, unmatched_detections = \
|
| 333 |
+
min_cost_matching(
|
| 334 |
+
distance_metric, max_distance, tracks, detections,
|
| 335 |
+
track_indices_l, unmatched_detections)
|
| 336 |
+
matches += matches_l
|
| 337 |
+
unmatched_tracks = list(set(track_indices) - set(k for k, _ in matches))
|
| 338 |
+
return matches, unmatched_tracks, unmatched_detections
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def gate_cost_matrix(kf,
|
| 342 |
+
cost_matrix,
|
| 343 |
+
tracks,
|
| 344 |
+
detections,
|
| 345 |
+
track_indices,
|
| 346 |
+
detection_indices,
|
| 347 |
+
gated_cost=INFTY_COST,
|
| 348 |
+
only_position=False):
|
| 349 |
+
"""
|
| 350 |
+
Invalidate infeasible entries in cost matrix based on the state
|
| 351 |
+
distributions obtained by Kalman filtering.
|
| 352 |
+
|
| 353 |
+
Args:
|
| 354 |
+
kf (object): The Kalman filter.
|
| 355 |
+
cost_matrix (ndarray): The NxM dimensional cost matrix, where N is the
|
| 356 |
+
number of track indices and M is the number of detection indices,
|
| 357 |
+
such that entry (i, j) is the association cost between
|
| 358 |
+
`tracks[track_indices[i]]` and `detections[detection_indices[j]]`.
|
| 359 |
+
tracks (list[Track]): A list of predicted tracks at the current time
|
| 360 |
+
step.
|
| 361 |
+
detections (list[Detection]): A list of detections at the current time
|
| 362 |
+
step.
|
| 363 |
+
track_indices (List[int]): List of track indices that maps rows in
|
| 364 |
+
`cost_matrix` to tracks in `tracks`.
|
| 365 |
+
detection_indices (List[int]): List of detection indices that maps
|
| 366 |
+
columns in `cost_matrix` to detections in `detections`.
|
| 367 |
+
gated_cost (Optional[float]): Entries in the cost matrix corresponding
|
| 368 |
+
to infeasible associations are set this value. Defaults to a very
|
| 369 |
+
large value.
|
| 370 |
+
only_position (Optional[bool]): If True, only the x, y position of the
|
| 371 |
+
state distribution is considered during gating. Default False.
|
| 372 |
+
"""
|
| 373 |
+
gating_dim = 2 if only_position else 4
|
| 374 |
+
gating_threshold = kalman_filter.chi2inv95[gating_dim]
|
| 375 |
+
measurements = np.asarray(
|
| 376 |
+
[detections[i].to_xyah() for i in detection_indices])
|
| 377 |
+
for row, track_idx in enumerate(track_indices):
|
| 378 |
+
track = tracks[track_idx]
|
| 379 |
+
gating_distance = kf.gating_distance(track.mean, track.covariance,
|
| 380 |
+
measurements, only_position)
|
| 381 |
+
cost_matrix[row, gating_distance > gating_threshold] = gated_cost
|
| 382 |
+
return cost_matrix
|
pptracking/python/mot/matching/jde_matching.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/Zhongdao/Towards-Realtime-MOT/blob/master/tracker/matching.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
import lap
|
| 20 |
+
except:
|
| 21 |
+
print(
|
| 22 |
+
'Warning: Unable to use JDE/FairMOT/ByteTrack, please install lap, for example: `pip install lap`, see https://github.com/gatagat/lap'
|
| 23 |
+
)
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
import scipy
|
| 27 |
+
import numpy as np
|
| 28 |
+
from scipy.spatial.distance import cdist
|
| 29 |
+
from ..motion import kalman_filter
|
| 30 |
+
import warnings
|
| 31 |
+
warnings.filterwarnings("ignore")
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
'merge_matches',
|
| 35 |
+
'linear_assignment',
|
| 36 |
+
'bbox_ious',
|
| 37 |
+
'iou_distance',
|
| 38 |
+
'embedding_distance',
|
| 39 |
+
'fuse_motion',
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def merge_matches(m1, m2, shape):
|
| 44 |
+
O, P, Q = shape
|
| 45 |
+
m1 = np.asarray(m1)
|
| 46 |
+
m2 = np.asarray(m2)
|
| 47 |
+
|
| 48 |
+
M1 = scipy.sparse.coo_matrix(
|
| 49 |
+
(np.ones(len(m1)), (m1[:, 0], m1[:, 1])), shape=(O, P))
|
| 50 |
+
M2 = scipy.sparse.coo_matrix(
|
| 51 |
+
(np.ones(len(m2)), (m2[:, 0], m2[:, 1])), shape=(P, Q))
|
| 52 |
+
|
| 53 |
+
mask = M1 * M2
|
| 54 |
+
match = mask.nonzero()
|
| 55 |
+
match = list(zip(match[0], match[1]))
|
| 56 |
+
unmatched_O = tuple(set(range(O)) - set([i for i, j in match]))
|
| 57 |
+
unmatched_Q = tuple(set(range(Q)) - set([j for i, j in match]))
|
| 58 |
+
|
| 59 |
+
return match, unmatched_O, unmatched_Q
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def linear_assignment(cost_matrix, thresh):
|
| 63 |
+
try:
|
| 64 |
+
import lap
|
| 65 |
+
except Exception as e:
|
| 66 |
+
raise RuntimeError(
|
| 67 |
+
'Unable to use JDE/FairMOT/ByteTrack, please install lap, for example: `pip install lap`, see https://github.com/gatagat/lap'
|
| 68 |
+
)
|
| 69 |
+
if cost_matrix.size == 0:
|
| 70 |
+
return np.empty(
|
| 71 |
+
(0, 2), dtype=int), tuple(range(cost_matrix.shape[0])), tuple(
|
| 72 |
+
range(cost_matrix.shape[1]))
|
| 73 |
+
matches, unmatched_a, unmatched_b = [], [], []
|
| 74 |
+
cost, x, y = lap.lapjv(cost_matrix, extend_cost=True, cost_limit=thresh)
|
| 75 |
+
for ix, mx in enumerate(x):
|
| 76 |
+
if mx >= 0:
|
| 77 |
+
matches.append([ix, mx])
|
| 78 |
+
unmatched_a = np.where(x < 0)[0]
|
| 79 |
+
unmatched_b = np.where(y < 0)[0]
|
| 80 |
+
matches = np.asarray(matches)
|
| 81 |
+
return matches, unmatched_a, unmatched_b
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def bbox_ious(atlbrs, btlbrs):
|
| 85 |
+
boxes = np.ascontiguousarray(atlbrs, dtype=np.float)
|
| 86 |
+
query_boxes = np.ascontiguousarray(btlbrs, dtype=np.float)
|
| 87 |
+
N = boxes.shape[0]
|
| 88 |
+
K = query_boxes.shape[0]
|
| 89 |
+
ious = np.zeros((N, K), dtype=boxes.dtype)
|
| 90 |
+
if N * K == 0:
|
| 91 |
+
return ious
|
| 92 |
+
|
| 93 |
+
for k in range(K):
|
| 94 |
+
box_area = ((query_boxes[k, 2] - query_boxes[k, 0] + 1) *
|
| 95 |
+
(query_boxes[k, 3] - query_boxes[k, 1] + 1))
|
| 96 |
+
for n in range(N):
|
| 97 |
+
iw = (min(boxes[n, 2], query_boxes[k, 2]) - max(
|
| 98 |
+
boxes[n, 0], query_boxes[k, 0]) + 1)
|
| 99 |
+
if iw > 0:
|
| 100 |
+
ih = (min(boxes[n, 3], query_boxes[k, 3]) - max(
|
| 101 |
+
boxes[n, 1], query_boxes[k, 1]) + 1)
|
| 102 |
+
if ih > 0:
|
| 103 |
+
ua = float((boxes[n, 2] - boxes[n, 0] + 1) * (boxes[
|
| 104 |
+
n, 3] - boxes[n, 1] + 1) + box_area - iw * ih)
|
| 105 |
+
ious[n, k] = iw * ih / ua
|
| 106 |
+
return ious
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def iou_distance(atracks, btracks):
|
| 110 |
+
"""
|
| 111 |
+
Compute cost based on IoU between two list[STrack].
|
| 112 |
+
"""
|
| 113 |
+
if (len(atracks) > 0 and isinstance(atracks[0], np.ndarray)) or (
|
| 114 |
+
len(btracks) > 0 and isinstance(btracks[0], np.ndarray)):
|
| 115 |
+
atlbrs = atracks
|
| 116 |
+
btlbrs = btracks
|
| 117 |
+
else:
|
| 118 |
+
atlbrs = [track.tlbr for track in atracks]
|
| 119 |
+
btlbrs = [track.tlbr for track in btracks]
|
| 120 |
+
_ious = bbox_ious(atlbrs, btlbrs)
|
| 121 |
+
cost_matrix = 1 - _ious
|
| 122 |
+
|
| 123 |
+
return cost_matrix
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def embedding_distance(tracks, detections, metric='euclidean'):
|
| 127 |
+
"""
|
| 128 |
+
Compute cost based on features between two list[STrack].
|
| 129 |
+
"""
|
| 130 |
+
cost_matrix = np.zeros((len(tracks), len(detections)), dtype=np.float)
|
| 131 |
+
if cost_matrix.size == 0:
|
| 132 |
+
return cost_matrix
|
| 133 |
+
det_features = np.asarray(
|
| 134 |
+
[track.curr_feat for track in detections], dtype=np.float)
|
| 135 |
+
track_features = np.asarray(
|
| 136 |
+
[track.smooth_feat for track in tracks], dtype=np.float)
|
| 137 |
+
cost_matrix = np.maximum(0.0, cdist(track_features, det_features,
|
| 138 |
+
metric)) # Nomalized features
|
| 139 |
+
return cost_matrix
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def fuse_motion(kf,
|
| 143 |
+
cost_matrix,
|
| 144 |
+
tracks,
|
| 145 |
+
detections,
|
| 146 |
+
only_position=False,
|
| 147 |
+
lambda_=0.98):
|
| 148 |
+
if cost_matrix.size == 0:
|
| 149 |
+
return cost_matrix
|
| 150 |
+
gating_dim = 2 if only_position else 4
|
| 151 |
+
gating_threshold = kalman_filter.chi2inv95[gating_dim]
|
| 152 |
+
measurements = np.asarray([det.to_xyah() for det in detections])
|
| 153 |
+
for row, track in enumerate(tracks):
|
| 154 |
+
gating_distance = kf.gating_distance(
|
| 155 |
+
track.mean,
|
| 156 |
+
track.covariance,
|
| 157 |
+
measurements,
|
| 158 |
+
only_position,
|
| 159 |
+
metric='maha')
|
| 160 |
+
cost_matrix[row, gating_distance > gating_threshold] = np.inf
|
| 161 |
+
cost_matrix[row] = lambda_ * cost_matrix[row] + (1 - lambda_
|
| 162 |
+
) * gating_distance
|
| 163 |
+
return cost_matrix
|
pptracking/python/mot/matching/ocsort_matching.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/noahcao/OC_SORT/blob/master/trackers/ocsort_tracker/association.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
import numpy as np
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def iou_batch(bboxes1, bboxes2):
|
| 23 |
+
"""
|
| 24 |
+
From SORT: Computes IOU between two bboxes in the form [x1,y1,x2,y2]
|
| 25 |
+
"""
|
| 26 |
+
bboxes2 = np.expand_dims(bboxes2, 0)
|
| 27 |
+
bboxes1 = np.expand_dims(bboxes1, 1)
|
| 28 |
+
|
| 29 |
+
xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])
|
| 30 |
+
yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])
|
| 31 |
+
xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])
|
| 32 |
+
yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])
|
| 33 |
+
w = np.maximum(0., xx2 - xx1)
|
| 34 |
+
h = np.maximum(0., yy2 - yy1)
|
| 35 |
+
wh = w * h
|
| 36 |
+
o = wh / ((bboxes1[..., 2] - bboxes1[..., 0]) *
|
| 37 |
+
(bboxes1[..., 3] - bboxes1[..., 1]) +
|
| 38 |
+
(bboxes2[..., 2] - bboxes2[..., 0]) *
|
| 39 |
+
(bboxes2[..., 3] - bboxes2[..., 1]) - wh)
|
| 40 |
+
return (o)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def speed_direction_batch(dets, tracks):
|
| 44 |
+
tracks = tracks[..., np.newaxis]
|
| 45 |
+
CX1, CY1 = (dets[:, 0] + dets[:, 2]) / 2.0, (dets[:, 1] + dets[:, 3]) / 2.0
|
| 46 |
+
CX2, CY2 = (tracks[:, 0] + tracks[:, 2]) / 2.0, (
|
| 47 |
+
tracks[:, 1] + tracks[:, 3]) / 2.0
|
| 48 |
+
dx = CX1 - CX2
|
| 49 |
+
dy = CY1 - CY2
|
| 50 |
+
norm = np.sqrt(dx**2 + dy**2) + 1e-6
|
| 51 |
+
dx = dx / norm
|
| 52 |
+
dy = dy / norm
|
| 53 |
+
return dy, dx # size: num_track x num_det
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def linear_assignment(cost_matrix):
|
| 57 |
+
try:
|
| 58 |
+
import lap
|
| 59 |
+
_, x, y = lap.lapjv(cost_matrix, extend_cost=True)
|
| 60 |
+
return np.array([[y[i], i] for i in x if i >= 0]) #
|
| 61 |
+
except ImportError:
|
| 62 |
+
from scipy.optimize import linear_sum_assignment
|
| 63 |
+
x, y = linear_sum_assignment(cost_matrix)
|
| 64 |
+
return np.array(list(zip(x, y)))
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def associate(detections, trackers, iou_threshold, velocities, previous_obs,
|
| 68 |
+
vdc_weight):
|
| 69 |
+
if (len(trackers) == 0):
|
| 70 |
+
return np.empty(
|
| 71 |
+
(0, 2), dtype=int), np.arange(len(detections)), np.empty(
|
| 72 |
+
(0, 5), dtype=int)
|
| 73 |
+
|
| 74 |
+
Y, X = speed_direction_batch(detections, previous_obs)
|
| 75 |
+
inertia_Y, inertia_X = velocities[:, 0], velocities[:, 1]
|
| 76 |
+
inertia_Y = np.repeat(inertia_Y[:, np.newaxis], Y.shape[1], axis=1)
|
| 77 |
+
inertia_X = np.repeat(inertia_X[:, np.newaxis], X.shape[1], axis=1)
|
| 78 |
+
diff_angle_cos = inertia_X * X + inertia_Y * Y
|
| 79 |
+
diff_angle_cos = np.clip(diff_angle_cos, a_min=-1, a_max=1)
|
| 80 |
+
diff_angle = np.arccos(diff_angle_cos)
|
| 81 |
+
diff_angle = (np.pi / 2.0 - np.abs(diff_angle)) / np.pi
|
| 82 |
+
|
| 83 |
+
valid_mask = np.ones(previous_obs.shape[0])
|
| 84 |
+
valid_mask[np.where(previous_obs[:, 4] < 0)] = 0
|
| 85 |
+
|
| 86 |
+
iou_matrix = iou_batch(detections, trackers)
|
| 87 |
+
scores = np.repeat(
|
| 88 |
+
detections[:, -1][:, np.newaxis], trackers.shape[0], axis=1)
|
| 89 |
+
# iou_matrix = iou_matrix * scores # a trick sometiems works, we don't encourage this
|
| 90 |
+
valid_mask = np.repeat(valid_mask[:, np.newaxis], X.shape[1], axis=1)
|
| 91 |
+
|
| 92 |
+
angle_diff_cost = (valid_mask * diff_angle) * vdc_weight
|
| 93 |
+
angle_diff_cost = angle_diff_cost.T
|
| 94 |
+
angle_diff_cost = angle_diff_cost * scores
|
| 95 |
+
|
| 96 |
+
if min(iou_matrix.shape) > 0:
|
| 97 |
+
a = (iou_matrix > iou_threshold).astype(np.int32)
|
| 98 |
+
if a.sum(1).max() == 1 and a.sum(0).max() == 1:
|
| 99 |
+
matched_indices = np.stack(np.where(a), axis=1)
|
| 100 |
+
else:
|
| 101 |
+
matched_indices = linear_assignment(-(iou_matrix + angle_diff_cost
|
| 102 |
+
))
|
| 103 |
+
else:
|
| 104 |
+
matched_indices = np.empty(shape=(0, 2))
|
| 105 |
+
|
| 106 |
+
unmatched_detections = []
|
| 107 |
+
for d, det in enumerate(detections):
|
| 108 |
+
if (d not in matched_indices[:, 0]):
|
| 109 |
+
unmatched_detections.append(d)
|
| 110 |
+
unmatched_trackers = []
|
| 111 |
+
for t, trk in enumerate(trackers):
|
| 112 |
+
if (t not in matched_indices[:, 1]):
|
| 113 |
+
unmatched_trackers.append(t)
|
| 114 |
+
|
| 115 |
+
# filter out matched with low IOU
|
| 116 |
+
matches = []
|
| 117 |
+
for m in matched_indices:
|
| 118 |
+
if (iou_matrix[m[0], m[1]] < iou_threshold):
|
| 119 |
+
unmatched_detections.append(m[0])
|
| 120 |
+
unmatched_trackers.append(m[1])
|
| 121 |
+
else:
|
| 122 |
+
matches.append(m.reshape(1, 2))
|
| 123 |
+
if (len(matches) == 0):
|
| 124 |
+
matches = np.empty((0, 2), dtype=int)
|
| 125 |
+
else:
|
| 126 |
+
matches = np.concatenate(matches, axis=0)
|
| 127 |
+
|
| 128 |
+
return matches, np.array(unmatched_detections), np.array(
|
| 129 |
+
unmatched_trackers)
|
pptracking/python/mot/motion/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
from . import kalman_filter
|
| 16 |
+
|
| 17 |
+
from .kalman_filter import *
|
pptracking/python/mot/motion/kalman_filter.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/nwojke/deep_sort/blob/master/deep_sort/kalman_filter.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
import scipy.linalg
|
| 20 |
+
|
| 21 |
+
__all__ = ['KalmanFilter']
|
| 22 |
+
"""
|
| 23 |
+
Table for the 0.95 quantile of the chi-square distribution with N degrees of
|
| 24 |
+
freedom (contains values for N=1, ..., 9). Taken from MATLAB/Octave's chi2inv
|
| 25 |
+
function and used as Mahalanobis gating threshold.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
chi2inv95 = {
|
| 29 |
+
1: 3.8415,
|
| 30 |
+
2: 5.9915,
|
| 31 |
+
3: 7.8147,
|
| 32 |
+
4: 9.4877,
|
| 33 |
+
5: 11.070,
|
| 34 |
+
6: 12.592,
|
| 35 |
+
7: 14.067,
|
| 36 |
+
8: 15.507,
|
| 37 |
+
9: 16.919
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class KalmanFilter(object):
|
| 42 |
+
"""
|
| 43 |
+
A simple Kalman filter for tracking bounding boxes in image space.
|
| 44 |
+
|
| 45 |
+
The 8-dimensional state space
|
| 46 |
+
|
| 47 |
+
x, y, a, h, vx, vy, va, vh
|
| 48 |
+
|
| 49 |
+
contains the bounding box center position (x, y), aspect ratio a, height h,
|
| 50 |
+
and their respective velocities.
|
| 51 |
+
|
| 52 |
+
Object motion follows a constant velocity model. The bounding box location
|
| 53 |
+
(x, y, a, h) is taken as direct observation of the state space (linear
|
| 54 |
+
observation model).
|
| 55 |
+
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
def __init__(self):
|
| 59 |
+
ndim, dt = 4, 1.
|
| 60 |
+
|
| 61 |
+
# Create Kalman filter model matrices.
|
| 62 |
+
self._motion_mat = np.eye(2 * ndim, 2 * ndim)
|
| 63 |
+
for i in range(ndim):
|
| 64 |
+
self._motion_mat[i, ndim + i] = dt
|
| 65 |
+
self._update_mat = np.eye(ndim, 2 * ndim)
|
| 66 |
+
|
| 67 |
+
# Motion and observation uncertainty are chosen relative to the current
|
| 68 |
+
# state estimate. These weights control the amount of uncertainty in
|
| 69 |
+
# the model. This is a bit hacky.
|
| 70 |
+
self._std_weight_position = 1. / 20
|
| 71 |
+
self._std_weight_velocity = 1. / 160
|
| 72 |
+
|
| 73 |
+
def initiate(self, measurement):
|
| 74 |
+
"""
|
| 75 |
+
Create track from unassociated measurement.
|
| 76 |
+
|
| 77 |
+
Args:
|
| 78 |
+
measurement (ndarray): Bounding box coordinates (x, y, a, h) with
|
| 79 |
+
center position (x, y), aspect ratio a, and height h.
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
The mean vector (8 dimensional) and covariance matrix (8x8
|
| 83 |
+
dimensional) of the new track. Unobserved velocities are
|
| 84 |
+
initialized to 0 mean.
|
| 85 |
+
"""
|
| 86 |
+
mean_pos = measurement
|
| 87 |
+
mean_vel = np.zeros_like(mean_pos)
|
| 88 |
+
mean = np.r_[mean_pos, mean_vel]
|
| 89 |
+
|
| 90 |
+
std = [
|
| 91 |
+
2 * self._std_weight_position * measurement[3],
|
| 92 |
+
2 * self._std_weight_position * measurement[3], 1e-2,
|
| 93 |
+
2 * self._std_weight_position * measurement[3],
|
| 94 |
+
10 * self._std_weight_velocity * measurement[3],
|
| 95 |
+
10 * self._std_weight_velocity * measurement[3], 1e-5,
|
| 96 |
+
10 * self._std_weight_velocity * measurement[3]
|
| 97 |
+
]
|
| 98 |
+
covariance = np.diag(np.square(std))
|
| 99 |
+
return mean, covariance
|
| 100 |
+
|
| 101 |
+
def predict(self, mean, covariance):
|
| 102 |
+
"""
|
| 103 |
+
Run Kalman filter prediction step.
|
| 104 |
+
|
| 105 |
+
Args:
|
| 106 |
+
mean (ndarray): The 8 dimensional mean vector of the object state
|
| 107 |
+
at the previous time step.
|
| 108 |
+
covariance (ndarray): The 8x8 dimensional covariance matrix of the
|
| 109 |
+
object state at the previous time step.
|
| 110 |
+
|
| 111 |
+
Returns:
|
| 112 |
+
The mean vector and covariance matrix of the predicted state.
|
| 113 |
+
Unobserved velocities are initialized to 0 mean.
|
| 114 |
+
"""
|
| 115 |
+
std_pos = [
|
| 116 |
+
self._std_weight_position * mean[3], self._std_weight_position *
|
| 117 |
+
mean[3], 1e-2, self._std_weight_position * mean[3]
|
| 118 |
+
]
|
| 119 |
+
std_vel = [
|
| 120 |
+
self._std_weight_velocity * mean[3], self._std_weight_velocity *
|
| 121 |
+
mean[3], 1e-5, self._std_weight_velocity * mean[3]
|
| 122 |
+
]
|
| 123 |
+
motion_cov = np.diag(np.square(np.r_[std_pos, std_vel]))
|
| 124 |
+
|
| 125 |
+
#mean = np.dot(self._motion_mat, mean)
|
| 126 |
+
mean = np.dot(mean, self._motion_mat.T)
|
| 127 |
+
covariance = np.linalg.multi_dot(
|
| 128 |
+
(self._motion_mat, covariance, self._motion_mat.T)) + motion_cov
|
| 129 |
+
|
| 130 |
+
return mean, covariance
|
| 131 |
+
|
| 132 |
+
def project(self, mean, covariance):
|
| 133 |
+
"""
|
| 134 |
+
Project state distribution to measurement space.
|
| 135 |
+
|
| 136 |
+
Args
|
| 137 |
+
mean (ndarray): The state's mean vector (8 dimensional array).
|
| 138 |
+
covariance (ndarray): The state's covariance matrix (8x8 dimensional).
|
| 139 |
+
|
| 140 |
+
Returns:
|
| 141 |
+
The projected mean and covariance matrix of the given state estimate.
|
| 142 |
+
"""
|
| 143 |
+
std = [
|
| 144 |
+
self._std_weight_position * mean[3], self._std_weight_position *
|
| 145 |
+
mean[3], 1e-1, self._std_weight_position * mean[3]
|
| 146 |
+
]
|
| 147 |
+
innovation_cov = np.diag(np.square(std))
|
| 148 |
+
|
| 149 |
+
mean = np.dot(self._update_mat, mean)
|
| 150 |
+
covariance = np.linalg.multi_dot((self._update_mat, covariance,
|
| 151 |
+
self._update_mat.T))
|
| 152 |
+
return mean, covariance + innovation_cov
|
| 153 |
+
|
| 154 |
+
def multi_predict(self, mean, covariance):
|
| 155 |
+
"""
|
| 156 |
+
Run Kalman filter prediction step (Vectorized version).
|
| 157 |
+
|
| 158 |
+
Args:
|
| 159 |
+
mean (ndarray): The Nx8 dimensional mean matrix of the object states
|
| 160 |
+
at the previous time step.
|
| 161 |
+
covariance (ndarray): The Nx8x8 dimensional covariance matrics of the
|
| 162 |
+
object states at the previous time step.
|
| 163 |
+
|
| 164 |
+
Returns:
|
| 165 |
+
The mean vector and covariance matrix of the predicted state.
|
| 166 |
+
Unobserved velocities are initialized to 0 mean.
|
| 167 |
+
"""
|
| 168 |
+
std_pos = [
|
| 169 |
+
self._std_weight_position * mean[:, 3], self._std_weight_position *
|
| 170 |
+
mean[:, 3], 1e-2 * np.ones_like(mean[:, 3]),
|
| 171 |
+
self._std_weight_position * mean[:, 3]
|
| 172 |
+
]
|
| 173 |
+
std_vel = [
|
| 174 |
+
self._std_weight_velocity * mean[:, 3], self._std_weight_velocity *
|
| 175 |
+
mean[:, 3], 1e-5 * np.ones_like(mean[:, 3]),
|
| 176 |
+
self._std_weight_velocity * mean[:, 3]
|
| 177 |
+
]
|
| 178 |
+
sqr = np.square(np.r_[std_pos, std_vel]).T
|
| 179 |
+
|
| 180 |
+
motion_cov = []
|
| 181 |
+
for i in range(len(mean)):
|
| 182 |
+
motion_cov.append(np.diag(sqr[i]))
|
| 183 |
+
motion_cov = np.asarray(motion_cov)
|
| 184 |
+
|
| 185 |
+
mean = np.dot(mean, self._motion_mat.T)
|
| 186 |
+
left = np.dot(self._motion_mat, covariance).transpose((1, 0, 2))
|
| 187 |
+
covariance = np.dot(left, self._motion_mat.T) + motion_cov
|
| 188 |
+
|
| 189 |
+
return mean, covariance
|
| 190 |
+
|
| 191 |
+
def update(self, mean, covariance, measurement):
|
| 192 |
+
"""
|
| 193 |
+
Run Kalman filter correction step.
|
| 194 |
+
|
| 195 |
+
Args:
|
| 196 |
+
mean (ndarray): The predicted state's mean vector (8 dimensional).
|
| 197 |
+
covariance (ndarray): The state's covariance matrix (8x8 dimensional).
|
| 198 |
+
measurement (ndarray): The 4 dimensional measurement vector
|
| 199 |
+
(x, y, a, h), where (x, y) is the center position, a the aspect
|
| 200 |
+
ratio, and h the height of the bounding box.
|
| 201 |
+
|
| 202 |
+
Returns:
|
| 203 |
+
The measurement-corrected state distribution.
|
| 204 |
+
"""
|
| 205 |
+
projected_mean, projected_cov = self.project(mean, covariance)
|
| 206 |
+
|
| 207 |
+
chol_factor, lower = scipy.linalg.cho_factor(
|
| 208 |
+
projected_cov, lower=True, check_finite=False)
|
| 209 |
+
kalman_gain = scipy.linalg.cho_solve(
|
| 210 |
+
(chol_factor, lower),
|
| 211 |
+
np.dot(covariance, self._update_mat.T).T,
|
| 212 |
+
check_finite=False).T
|
| 213 |
+
innovation = measurement - projected_mean
|
| 214 |
+
|
| 215 |
+
new_mean = mean + np.dot(innovation, kalman_gain.T)
|
| 216 |
+
new_covariance = covariance - np.linalg.multi_dot(
|
| 217 |
+
(kalman_gain, projected_cov, kalman_gain.T))
|
| 218 |
+
return new_mean, new_covariance
|
| 219 |
+
|
| 220 |
+
def gating_distance(self,
|
| 221 |
+
mean,
|
| 222 |
+
covariance,
|
| 223 |
+
measurements,
|
| 224 |
+
only_position=False,
|
| 225 |
+
metric='maha'):
|
| 226 |
+
"""
|
| 227 |
+
Compute gating distance between state distribution and measurements.
|
| 228 |
+
A suitable distance threshold can be obtained from `chi2inv95`. If
|
| 229 |
+
`only_position` is False, the chi-square distribution has 4 degrees of
|
| 230 |
+
freedom, otherwise 2.
|
| 231 |
+
|
| 232 |
+
Args:
|
| 233 |
+
mean (ndarray): Mean vector over the state distribution (8
|
| 234 |
+
dimensional).
|
| 235 |
+
covariance (ndarray): Covariance of the state distribution (8x8
|
| 236 |
+
dimensional).
|
| 237 |
+
measurements (ndarray): An Nx4 dimensional matrix of N measurements,
|
| 238 |
+
each in format (x, y, a, h) where (x, y) is the bounding box center
|
| 239 |
+
position, a the aspect ratio, and h the height.
|
| 240 |
+
only_position (Optional[bool]): If True, distance computation is
|
| 241 |
+
done with respect to the bounding box center position only.
|
| 242 |
+
metric (str): Metric type, 'gaussian' or 'maha'.
|
| 243 |
+
|
| 244 |
+
Returns
|
| 245 |
+
An array of length N, where the i-th element contains the squared
|
| 246 |
+
Mahalanobis distance between (mean, covariance) and `measurements[i]`.
|
| 247 |
+
"""
|
| 248 |
+
mean, covariance = self.project(mean, covariance)
|
| 249 |
+
if only_position:
|
| 250 |
+
mean, covariance = mean[:2], covariance[:2, :2]
|
| 251 |
+
measurements = measurements[:, :2]
|
| 252 |
+
|
| 253 |
+
d = measurements - mean
|
| 254 |
+
if metric == 'gaussian':
|
| 255 |
+
return np.sum(d * d, axis=1)
|
| 256 |
+
elif metric == 'maha':
|
| 257 |
+
cholesky_factor = np.linalg.cholesky(covariance)
|
| 258 |
+
z = scipy.linalg.solve_triangular(
|
| 259 |
+
cholesky_factor,
|
| 260 |
+
d.T,
|
| 261 |
+
lower=True,
|
| 262 |
+
check_finite=False,
|
| 263 |
+
overwrite_b=True)
|
| 264 |
+
squared_maha = np.sum(z * z, axis=0)
|
| 265 |
+
return squared_maha
|
| 266 |
+
else:
|
| 267 |
+
raise ValueError('invalid distance metric')
|
pptracking/python/mot/mtmct/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
from . import utils
|
| 16 |
+
from . import postprocess
|
| 17 |
+
from .utils import *
|
| 18 |
+
from .postprocess import *
|
| 19 |
+
|
| 20 |
+
# The following codes are strongly related to zone and camera parameters
|
| 21 |
+
from . import camera_utils
|
| 22 |
+
from . import zone
|
| 23 |
+
from .camera_utils import *
|
| 24 |
+
from .zone import *
|
pptracking/python/mot/mtmct/camera_utils.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/LCFractal/AIC21-MTMC/tree/main/reid/reid-matching/tools
|
| 16 |
+
|
| 17 |
+
Note: The following codes are strongly related to camera parameters of the AIC21 test-set S06,
|
| 18 |
+
so they can only be used in S06, and can not be used for other MTMCT datasets.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import numpy as np
|
| 22 |
+
try:
|
| 23 |
+
from sklearn.cluster import AgglomerativeClustering
|
| 24 |
+
except:
|
| 25 |
+
print(
|
| 26 |
+
'Warning: Unable to use MTMCT in PP-Tracking, please install sklearn, for example: `pip install sklearn`'
|
| 27 |
+
)
|
| 28 |
+
pass
|
| 29 |
+
from .utils import get_dire, get_match, get_cid_tid, combin_feature, combin_cluster
|
| 30 |
+
from .utils import normalize, intracam_ignore, visual_rerank
|
| 31 |
+
|
| 32 |
+
__all__ = [
|
| 33 |
+
'st_filter',
|
| 34 |
+
'get_labels_with_camera',
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
CAM_DIST = [[0, 40, 55, 100, 120, 145], [40, 0, 15, 60, 80, 105],
|
| 38 |
+
[55, 15, 0, 40, 65, 90], [100, 60, 40, 0, 20, 45],
|
| 39 |
+
[120, 80, 65, 20, 0, 25], [145, 105, 90, 45, 25, 0]]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def st_filter(st_mask, cid_tids, cid_tid_dict):
|
| 43 |
+
count = len(cid_tids)
|
| 44 |
+
for i in range(count):
|
| 45 |
+
i_tracklet = cid_tid_dict[cid_tids[i]]
|
| 46 |
+
i_cid = i_tracklet['cam']
|
| 47 |
+
i_dire = get_dire(i_tracklet['zone_list'], i_cid)
|
| 48 |
+
i_iot = i_tracklet['io_time']
|
| 49 |
+
for j in range(count):
|
| 50 |
+
j_tracklet = cid_tid_dict[cid_tids[j]]
|
| 51 |
+
j_cid = j_tracklet['cam']
|
| 52 |
+
j_dire = get_dire(j_tracklet['zone_list'], j_cid)
|
| 53 |
+
j_iot = j_tracklet['io_time']
|
| 54 |
+
|
| 55 |
+
match_dire = True
|
| 56 |
+
cam_dist = CAM_DIST[i_cid - 41][j_cid - 41]
|
| 57 |
+
# if time overlopped
|
| 58 |
+
if i_iot[0] - cam_dist < j_iot[0] and j_iot[0] < i_iot[
|
| 59 |
+
1] + cam_dist:
|
| 60 |
+
match_dire = False
|
| 61 |
+
if i_iot[0] - cam_dist < j_iot[1] and j_iot[1] < i_iot[
|
| 62 |
+
1] + cam_dist:
|
| 63 |
+
match_dire = False
|
| 64 |
+
|
| 65 |
+
# not match after go out
|
| 66 |
+
if i_dire[1] in [1, 2]: # i out
|
| 67 |
+
if i_iot[0] < j_iot[1] + cam_dist:
|
| 68 |
+
match_dire = False
|
| 69 |
+
|
| 70 |
+
if i_dire[1] in [1, 2]:
|
| 71 |
+
if i_dire[0] in [3] and i_cid > j_cid:
|
| 72 |
+
match_dire = False
|
| 73 |
+
if i_dire[0] in [4] and i_cid < j_cid:
|
| 74 |
+
match_dire = False
|
| 75 |
+
|
| 76 |
+
if i_cid in [41] and i_dire[1] in [4]:
|
| 77 |
+
if i_iot[0] < j_iot[1] + cam_dist:
|
| 78 |
+
match_dire = False
|
| 79 |
+
if i_iot[1] > 199:
|
| 80 |
+
match_dire = False
|
| 81 |
+
if i_cid in [46] and i_dire[1] in [3]:
|
| 82 |
+
if i_iot[0] < j_iot[1] + cam_dist:
|
| 83 |
+
match_dire = False
|
| 84 |
+
|
| 85 |
+
# match after come into
|
| 86 |
+
if i_dire[0] in [1, 2]:
|
| 87 |
+
if i_iot[1] > j_iot[0] - cam_dist:
|
| 88 |
+
match_dire = False
|
| 89 |
+
|
| 90 |
+
if i_dire[0] in [1, 2]:
|
| 91 |
+
if i_dire[1] in [3] and i_cid > j_cid:
|
| 92 |
+
match_dire = False
|
| 93 |
+
if i_dire[1] in [4] and i_cid < j_cid:
|
| 94 |
+
match_dire = False
|
| 95 |
+
|
| 96 |
+
is_ignore = False
|
| 97 |
+
if ((i_dire[0] == i_dire[1] and i_dire[0] in [3, 4]) or
|
| 98 |
+
(j_dire[0] == j_dire[1] and j_dire[0] in [3, 4])):
|
| 99 |
+
is_ignore = True
|
| 100 |
+
|
| 101 |
+
if not is_ignore:
|
| 102 |
+
# direction conflict
|
| 103 |
+
if (i_dire[0] in [3] and j_dire[0] in [4]) or (
|
| 104 |
+
i_dire[1] in [3] and j_dire[1] in [4]):
|
| 105 |
+
match_dire = False
|
| 106 |
+
# filter before going next scene
|
| 107 |
+
if i_dire[1] in [3] and i_cid < j_cid:
|
| 108 |
+
if i_iot[1] > j_iot[1] - cam_dist:
|
| 109 |
+
match_dire = False
|
| 110 |
+
if i_dire[1] in [4] and i_cid > j_cid:
|
| 111 |
+
if i_iot[1] > j_iot[1] - cam_dist:
|
| 112 |
+
match_dire = False
|
| 113 |
+
|
| 114 |
+
if i_dire[0] in [3] and i_cid < j_cid:
|
| 115 |
+
if i_iot[0] < j_iot[0] + cam_dist:
|
| 116 |
+
match_dire = False
|
| 117 |
+
if i_dire[0] in [4] and i_cid > j_cid:
|
| 118 |
+
if i_iot[0] < j_iot[0] + cam_dist:
|
| 119 |
+
match_dire = False
|
| 120 |
+
## 3-30
|
| 121 |
+
## 4-1
|
| 122 |
+
if i_dire[0] in [3] and i_cid > j_cid:
|
| 123 |
+
if i_iot[1] > j_iot[0] - cam_dist:
|
| 124 |
+
match_dire = False
|
| 125 |
+
if i_dire[0] in [4] and i_cid < j_cid:
|
| 126 |
+
if i_iot[1] > j_iot[0] - cam_dist:
|
| 127 |
+
match_dire = False
|
| 128 |
+
# filter before going next scene
|
| 129 |
+
## 4-7
|
| 130 |
+
if i_dire[1] in [3] and i_cid > j_cid:
|
| 131 |
+
if i_iot[0] < j_iot[1] + cam_dist:
|
| 132 |
+
match_dire = False
|
| 133 |
+
if i_dire[1] in [4] and i_cid < j_cid:
|
| 134 |
+
if i_iot[0] < j_iot[1] + cam_dist:
|
| 135 |
+
match_dire = False
|
| 136 |
+
else:
|
| 137 |
+
if i_iot[1] > 199:
|
| 138 |
+
if i_dire[0] in [3] and i_cid < j_cid:
|
| 139 |
+
if i_iot[0] < j_iot[0] + cam_dist:
|
| 140 |
+
match_dire = False
|
| 141 |
+
if i_dire[0] in [4] and i_cid > j_cid:
|
| 142 |
+
if i_iot[0] < j_iot[0] + cam_dist:
|
| 143 |
+
match_dire = False
|
| 144 |
+
if i_dire[0] in [3] and i_cid > j_cid:
|
| 145 |
+
match_dire = False
|
| 146 |
+
if i_dire[0] in [4] and i_cid < j_cid:
|
| 147 |
+
match_dire = False
|
| 148 |
+
if i_iot[0] < 1:
|
| 149 |
+
if i_dire[1] in [3] and i_cid > j_cid:
|
| 150 |
+
match_dire = False
|
| 151 |
+
if i_dire[1] in [4] and i_cid < j_cid:
|
| 152 |
+
match_dire = False
|
| 153 |
+
|
| 154 |
+
if not match_dire:
|
| 155 |
+
st_mask[i, j] = 0.0
|
| 156 |
+
st_mask[j, i] = 0.0
|
| 157 |
+
return st_mask
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def subcam_list(cid_tid_dict, cid_tids):
|
| 161 |
+
sub_3_4 = dict()
|
| 162 |
+
sub_4_3 = dict()
|
| 163 |
+
for cid_tid in cid_tids:
|
| 164 |
+
cid, tid = cid_tid
|
| 165 |
+
tracklet = cid_tid_dict[cid_tid]
|
| 166 |
+
zs, ze = get_dire(tracklet['zone_list'], cid)
|
| 167 |
+
if zs in [3] and cid not in [46]: # 4 to 3
|
| 168 |
+
if not cid + 1 in sub_4_3:
|
| 169 |
+
sub_4_3[cid + 1] = []
|
| 170 |
+
sub_4_3[cid + 1].append(cid_tid)
|
| 171 |
+
if ze in [4] and cid not in [41]: # 4 to 3
|
| 172 |
+
if not cid in sub_4_3:
|
| 173 |
+
sub_4_3[cid] = []
|
| 174 |
+
sub_4_3[cid].append(cid_tid)
|
| 175 |
+
if zs in [4] and cid not in [41]: # 3 to 4
|
| 176 |
+
if not cid - 1 in sub_3_4:
|
| 177 |
+
sub_3_4[cid - 1] = []
|
| 178 |
+
sub_3_4[cid - 1].append(cid_tid)
|
| 179 |
+
if ze in [3] and cid not in [46]: # 3 to 4
|
| 180 |
+
if not cid in sub_3_4:
|
| 181 |
+
sub_3_4[cid] = []
|
| 182 |
+
sub_3_4[cid].append(cid_tid)
|
| 183 |
+
sub_cid_tids = dict()
|
| 184 |
+
for i in sub_3_4:
|
| 185 |
+
sub_cid_tids[(i, i + 1)] = sub_3_4[i]
|
| 186 |
+
for i in sub_4_3:
|
| 187 |
+
sub_cid_tids[(i, i - 1)] = sub_4_3[i]
|
| 188 |
+
return sub_cid_tids
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def subcam_list2(cid_tid_dict, cid_tids):
|
| 192 |
+
sub_dict = dict()
|
| 193 |
+
for cid_tid in cid_tids:
|
| 194 |
+
cid, tid = cid_tid
|
| 195 |
+
if cid not in [41]:
|
| 196 |
+
if not cid in sub_dict:
|
| 197 |
+
sub_dict[cid] = []
|
| 198 |
+
sub_dict[cid].append(cid_tid)
|
| 199 |
+
if cid not in [46]:
|
| 200 |
+
if not cid + 1 in sub_dict:
|
| 201 |
+
sub_dict[cid + 1] = []
|
| 202 |
+
sub_dict[cid + 1].append(cid_tid)
|
| 203 |
+
return sub_dict
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def get_sim_matrix(cid_tid_dict,
|
| 207 |
+
cid_tids,
|
| 208 |
+
use_ff=True,
|
| 209 |
+
use_rerank=True,
|
| 210 |
+
use_st_filter=False):
|
| 211 |
+
# Note: camera releated get_sim_matrix function,
|
| 212 |
+
# which is different from the one in utils.py.
|
| 213 |
+
count = len(cid_tids)
|
| 214 |
+
|
| 215 |
+
q_arr = np.array(
|
| 216 |
+
[cid_tid_dict[cid_tids[i]]['mean_feat'] for i in range(count)])
|
| 217 |
+
g_arr = np.array(
|
| 218 |
+
[cid_tid_dict[cid_tids[i]]['mean_feat'] for i in range(count)])
|
| 219 |
+
q_arr = normalize(q_arr, axis=1)
|
| 220 |
+
g_arr = normalize(g_arr, axis=1)
|
| 221 |
+
|
| 222 |
+
st_mask = np.ones((count, count), dtype=np.float32)
|
| 223 |
+
st_mask = intracam_ignore(st_mask, cid_tids)
|
| 224 |
+
|
| 225 |
+
# different from utils.py
|
| 226 |
+
if use_st_filter:
|
| 227 |
+
st_mask = st_filter(st_mask, cid_tids, cid_tid_dict)
|
| 228 |
+
|
| 229 |
+
visual_sim_matrix = visual_rerank(
|
| 230 |
+
q_arr, g_arr, cid_tids, use_ff=use_ff, use_rerank=use_rerank)
|
| 231 |
+
visual_sim_matrix = visual_sim_matrix.astype('float32')
|
| 232 |
+
|
| 233 |
+
np.set_printoptions(precision=3)
|
| 234 |
+
sim_matrix = visual_sim_matrix * st_mask
|
| 235 |
+
|
| 236 |
+
np.fill_diagonal(sim_matrix, 0)
|
| 237 |
+
return sim_matrix
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def get_labels_with_camera(cid_tid_dict,
|
| 241 |
+
cid_tids,
|
| 242 |
+
use_ff=True,
|
| 243 |
+
use_rerank=True,
|
| 244 |
+
use_st_filter=False):
|
| 245 |
+
# 1st cluster
|
| 246 |
+
sub_cid_tids = subcam_list(cid_tid_dict, cid_tids)
|
| 247 |
+
sub_labels = dict()
|
| 248 |
+
dis_thrs = [0.7, 0.5, 0.5, 0.5, 0.5, 0.7, 0.5, 0.5, 0.5, 0.5]
|
| 249 |
+
|
| 250 |
+
for i, sub_c_to_c in enumerate(sub_cid_tids):
|
| 251 |
+
sim_matrix = get_sim_matrix(
|
| 252 |
+
cid_tid_dict,
|
| 253 |
+
sub_cid_tids[sub_c_to_c],
|
| 254 |
+
use_ff=use_ff,
|
| 255 |
+
use_rerank=use_rerank,
|
| 256 |
+
use_st_filter=use_st_filter)
|
| 257 |
+
cluster_labels = AgglomerativeClustering(
|
| 258 |
+
n_clusters=None,
|
| 259 |
+
distance_threshold=1 - dis_thrs[i],
|
| 260 |
+
affinity='precomputed',
|
| 261 |
+
linkage='complete').fit_predict(1 - sim_matrix)
|
| 262 |
+
labels = get_match(cluster_labels)
|
| 263 |
+
cluster_cid_tids = get_cid_tid(labels, sub_cid_tids[sub_c_to_c])
|
| 264 |
+
sub_labels[sub_c_to_c] = cluster_cid_tids
|
| 265 |
+
labels, sub_cluster = combin_cluster(sub_labels, cid_tids)
|
| 266 |
+
|
| 267 |
+
# 2nd cluster
|
| 268 |
+
cid_tid_dict_new = combin_feature(cid_tid_dict, sub_cluster)
|
| 269 |
+
sub_cid_tids = subcam_list2(cid_tid_dict_new, cid_tids)
|
| 270 |
+
sub_labels = dict()
|
| 271 |
+
for i, sub_c_to_c in enumerate(sub_cid_tids):
|
| 272 |
+
sim_matrix = get_sim_matrix(
|
| 273 |
+
cid_tid_dict_new,
|
| 274 |
+
sub_cid_tids[sub_c_to_c],
|
| 275 |
+
use_ff=use_ff,
|
| 276 |
+
use_rerank=use_rerank,
|
| 277 |
+
use_st_filter=use_st_filter)
|
| 278 |
+
cluster_labels = AgglomerativeClustering(
|
| 279 |
+
n_clusters=None,
|
| 280 |
+
distance_threshold=1 - 0.1,
|
| 281 |
+
affinity='precomputed',
|
| 282 |
+
linkage='complete').fit_predict(1 - sim_matrix)
|
| 283 |
+
labels = get_match(cluster_labels)
|
| 284 |
+
cluster_cid_tids = get_cid_tid(labels, sub_cid_tids[sub_c_to_c])
|
| 285 |
+
sub_labels[sub_c_to_c] = cluster_cid_tids
|
| 286 |
+
labels, sub_cluster = combin_cluster(sub_labels, cid_tids)
|
| 287 |
+
|
| 288 |
+
return labels
|
pptracking/python/mot/mtmct/postprocess.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/LCFractal/AIC21-MTMC/tree/main/reid/reid-matching/tools
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
import re
|
| 20 |
+
import cv2
|
| 21 |
+
from tqdm import tqdm
|
| 22 |
+
import numpy as np
|
| 23 |
+
try:
|
| 24 |
+
import motmetrics as mm
|
| 25 |
+
except:
|
| 26 |
+
print(
|
| 27 |
+
'Warning: Unable to use motmetrics in MTMCT in PP-Tracking, please install motmetrics, for example: `pip install motmetrics`, see https://github.com/longcw/py-motmetrics'
|
| 28 |
+
)
|
| 29 |
+
pass
|
| 30 |
+
from functools import reduce
|
| 31 |
+
|
| 32 |
+
from .utils import parse_pt_gt, parse_pt, compare_dataframes_mtmc
|
| 33 |
+
from .utils import get_labels, getData, gen_new_mot
|
| 34 |
+
from .camera_utils import get_labels_with_camera
|
| 35 |
+
from .zone import Zone
|
| 36 |
+
from ..visualize import plot_tracking
|
| 37 |
+
|
| 38 |
+
__all__ = [
|
| 39 |
+
'trajectory_fusion',
|
| 40 |
+
'sub_cluster',
|
| 41 |
+
'gen_res',
|
| 42 |
+
'print_mtmct_result',
|
| 43 |
+
'get_mtmct_matching_results',
|
| 44 |
+
'save_mtmct_crops',
|
| 45 |
+
'save_mtmct_vis_results',
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def trajectory_fusion(mot_feature, cid, cid_bias, use_zone=False,
|
| 50 |
+
zone_path=''):
|
| 51 |
+
cur_bias = cid_bias[cid]
|
| 52 |
+
mot_list_break = {}
|
| 53 |
+
if use_zone:
|
| 54 |
+
zones = Zone(zone_path=zone_path)
|
| 55 |
+
zones.set_cam(cid)
|
| 56 |
+
mot_list = parse_pt(mot_feature, zones)
|
| 57 |
+
else:
|
| 58 |
+
mot_list = parse_pt(mot_feature)
|
| 59 |
+
|
| 60 |
+
if use_zone:
|
| 61 |
+
mot_list = zones.break_mot(mot_list, cid)
|
| 62 |
+
mot_list = zones.filter_mot(mot_list, cid) # filter by zone
|
| 63 |
+
mot_list = zones.filter_bbox(mot_list, cid) # filter bbox
|
| 64 |
+
|
| 65 |
+
mot_list_break = gen_new_mot(mot_list) # save break feature for gen result
|
| 66 |
+
|
| 67 |
+
tid_data = dict()
|
| 68 |
+
for tid in mot_list:
|
| 69 |
+
tracklet = mot_list[tid]
|
| 70 |
+
if len(tracklet) <= 1:
|
| 71 |
+
continue
|
| 72 |
+
frame_list = list(tracklet.keys())
|
| 73 |
+
frame_list.sort()
|
| 74 |
+
# filter area too large
|
| 75 |
+
zone_list = [tracklet[f]['zone'] for f in frame_list]
|
| 76 |
+
feature_list = [
|
| 77 |
+
tracklet[f]['feat'] for f in frame_list
|
| 78 |
+
if (tracklet[f]['bbox'][3] - tracklet[f]['bbox'][1]
|
| 79 |
+
) * (tracklet[f]['bbox'][2] - tracklet[f]['bbox'][0]) > 2000
|
| 80 |
+
]
|
| 81 |
+
if len(feature_list) < 2:
|
| 82 |
+
feature_list = [tracklet[f]['feat'] for f in frame_list]
|
| 83 |
+
io_time = [
|
| 84 |
+
cur_bias + frame_list[0] / 10., cur_bias + frame_list[-1] / 10.
|
| 85 |
+
]
|
| 86 |
+
all_feat = np.array([feat for feat in feature_list])
|
| 87 |
+
mean_feat = np.mean(all_feat, axis=0)
|
| 88 |
+
tid_data[tid] = {
|
| 89 |
+
'cam': cid,
|
| 90 |
+
'tid': tid,
|
| 91 |
+
'mean_feat': mean_feat,
|
| 92 |
+
'zone_list': zone_list,
|
| 93 |
+
'frame_list': frame_list,
|
| 94 |
+
'tracklet': tracklet,
|
| 95 |
+
'io_time': io_time
|
| 96 |
+
}
|
| 97 |
+
return tid_data, mot_list_break
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def sub_cluster(cid_tid_dict,
|
| 101 |
+
scene_cluster,
|
| 102 |
+
use_ff=True,
|
| 103 |
+
use_rerank=True,
|
| 104 |
+
use_camera=False,
|
| 105 |
+
use_st_filter=False):
|
| 106 |
+
'''
|
| 107 |
+
cid_tid_dict: all camera_id and track_id
|
| 108 |
+
scene_cluster: like [41, 42, 43, 44, 45, 46] in AIC21 MTMCT S06 test videos
|
| 109 |
+
'''
|
| 110 |
+
assert (len(scene_cluster) != 0), "Error: scene_cluster length equals 0"
|
| 111 |
+
cid_tids = sorted(
|
| 112 |
+
[key for key in cid_tid_dict.keys() if key[0] in scene_cluster])
|
| 113 |
+
if use_camera:
|
| 114 |
+
clu = get_labels_with_camera(
|
| 115 |
+
cid_tid_dict,
|
| 116 |
+
cid_tids,
|
| 117 |
+
use_ff=use_ff,
|
| 118 |
+
use_rerank=use_rerank,
|
| 119 |
+
use_st_filter=use_st_filter)
|
| 120 |
+
else:
|
| 121 |
+
clu = get_labels(
|
| 122 |
+
cid_tid_dict,
|
| 123 |
+
cid_tids,
|
| 124 |
+
use_ff=use_ff,
|
| 125 |
+
use_rerank=use_rerank,
|
| 126 |
+
use_st_filter=use_st_filter)
|
| 127 |
+
new_clu = list()
|
| 128 |
+
for c_list in clu:
|
| 129 |
+
if len(c_list) <= 1: continue
|
| 130 |
+
cam_list = [cid_tids[c][0] for c in c_list]
|
| 131 |
+
if len(cam_list) != len(set(cam_list)): continue
|
| 132 |
+
new_clu.append([cid_tids[c] for c in c_list])
|
| 133 |
+
all_clu = new_clu
|
| 134 |
+
cid_tid_label = dict()
|
| 135 |
+
for i, c_list in enumerate(all_clu):
|
| 136 |
+
for c in c_list:
|
| 137 |
+
cid_tid_label[c] = i + 1
|
| 138 |
+
return cid_tid_label
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def gen_res(output_dir_filename,
|
| 142 |
+
scene_cluster,
|
| 143 |
+
map_tid,
|
| 144 |
+
mot_list_breaks,
|
| 145 |
+
use_roi=False,
|
| 146 |
+
roi_dir=''):
|
| 147 |
+
f_w = open(output_dir_filename, 'w')
|
| 148 |
+
for idx, mot_feature in enumerate(mot_list_breaks):
|
| 149 |
+
cid = scene_cluster[idx]
|
| 150 |
+
img_rects = parse_pt_gt(mot_feature)
|
| 151 |
+
if use_roi:
|
| 152 |
+
assert (roi_dir != ''), "Error: roi_dir is not empty!"
|
| 153 |
+
roi = cv2.imread(os.path.join(roi_dir, f'c{cid:03d}/roi.jpg'), 0)
|
| 154 |
+
height, width = roi.shape
|
| 155 |
+
|
| 156 |
+
for fid in img_rects:
|
| 157 |
+
tid_rects = img_rects[fid]
|
| 158 |
+
fid = int(fid) + 1
|
| 159 |
+
for tid_rect in tid_rects:
|
| 160 |
+
tid = tid_rect[0]
|
| 161 |
+
rect = tid_rect[1:]
|
| 162 |
+
cx = 0.5 * rect[0] + 0.5 * rect[2]
|
| 163 |
+
cy = 0.5 * rect[1] + 0.5 * rect[3]
|
| 164 |
+
w = rect[2] - rect[0]
|
| 165 |
+
w = min(w * 1.2, w + 40)
|
| 166 |
+
h = rect[3] - rect[1]
|
| 167 |
+
h = min(h * 1.2, h + 40)
|
| 168 |
+
rect[2] -= rect[0]
|
| 169 |
+
rect[3] -= rect[1]
|
| 170 |
+
rect[0] = max(0, rect[0])
|
| 171 |
+
rect[1] = max(0, rect[1])
|
| 172 |
+
x1, y1 = max(0, cx - 0.5 * w), max(0, cy - 0.5 * h)
|
| 173 |
+
if use_roi:
|
| 174 |
+
x2, y2 = min(width, cx + 0.5 * w), min(height,
|
| 175 |
+
cy + 0.5 * h)
|
| 176 |
+
else:
|
| 177 |
+
x2, y2 = cx + 0.5 * w, cy + 0.5 * h
|
| 178 |
+
w, h = x2 - x1, y2 - y1
|
| 179 |
+
new_rect = list(map(int, [x1, y1, w, h]))
|
| 180 |
+
rect = list(map(int, rect))
|
| 181 |
+
if (cid, tid) in map_tid:
|
| 182 |
+
new_tid = map_tid[(cid, tid)]
|
| 183 |
+
f_w.write(
|
| 184 |
+
str(cid) + ' ' + str(new_tid) + ' ' + str(fid) + ' ' +
|
| 185 |
+
' '.join(map(str, new_rect)) + ' -1 -1'
|
| 186 |
+
'\n')
|
| 187 |
+
print('gen_res: write file in {}'.format(output_dir_filename))
|
| 188 |
+
f_w.close()
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def print_mtmct_result(gt_file, pred_file):
|
| 192 |
+
names = [
|
| 193 |
+
'CameraId', 'Id', 'FrameId', 'X', 'Y', 'Width', 'Height', 'Xworld',
|
| 194 |
+
'Yworld'
|
| 195 |
+
]
|
| 196 |
+
gt = getData(gt_file, names=names)
|
| 197 |
+
pred = getData(pred_file, names=names)
|
| 198 |
+
summary = compare_dataframes_mtmc(gt, pred)
|
| 199 |
+
print('MTMCT summary: ', summary.columns.tolist())
|
| 200 |
+
|
| 201 |
+
formatters = {
|
| 202 |
+
'idf1': '{:2.2f}'.format,
|
| 203 |
+
'idp': '{:2.2f}'.format,
|
| 204 |
+
'idr': '{:2.2f}'.format,
|
| 205 |
+
'mota': '{:2.2f}'.format
|
| 206 |
+
}
|
| 207 |
+
summary = summary[['idf1', 'idp', 'idr', 'mota']]
|
| 208 |
+
summary.loc[:, 'idp'] *= 100
|
| 209 |
+
summary.loc[:, 'idr'] *= 100
|
| 210 |
+
summary.loc[:, 'idf1'] *= 100
|
| 211 |
+
summary.loc[:, 'mota'] *= 100
|
| 212 |
+
try:
|
| 213 |
+
import motmetrics as mm
|
| 214 |
+
except Exception as e:
|
| 215 |
+
raise RuntimeError(
|
| 216 |
+
'Unable to use motmetrics in MTMCT in PP-Tracking, please install motmetrics, for example: `pip install motmetrics`, see https://github.com/longcw/py-motmetrics'
|
| 217 |
+
)
|
| 218 |
+
print(
|
| 219 |
+
mm.io.render_summary(
|
| 220 |
+
summary,
|
| 221 |
+
formatters=formatters,
|
| 222 |
+
namemap=mm.io.motchallenge_metric_names))
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def get_mtmct_matching_results(pred_mtmct_file,
|
| 226 |
+
secs_interval=0.5,
|
| 227 |
+
video_fps=20):
|
| 228 |
+
res = np.loadtxt(pred_mtmct_file) # 'cid, tid, fid, x1, y1, w, h, -1, -1'
|
| 229 |
+
camera_ids = list(map(int, np.unique(res[:, 0])))
|
| 230 |
+
|
| 231 |
+
res = res[:, :7]
|
| 232 |
+
# each line in res: 'cid, tid, fid, x1, y1, w, h'
|
| 233 |
+
|
| 234 |
+
camera_tids = []
|
| 235 |
+
camera_results = dict()
|
| 236 |
+
for c_id in camera_ids:
|
| 237 |
+
camera_results[c_id] = res[res[:, 0] == c_id]
|
| 238 |
+
tids = np.unique(camera_results[c_id][:, 1])
|
| 239 |
+
tids = list(map(int, tids))
|
| 240 |
+
camera_tids.append(tids)
|
| 241 |
+
|
| 242 |
+
# select common tids throughout each video
|
| 243 |
+
common_tids = reduce(np.intersect1d, camera_tids)
|
| 244 |
+
if len(common_tids) == 0:
|
| 245 |
+
print(
|
| 246 |
+
'No common tracked ids in these videos, please check your MOT result or select new videos.'
|
| 247 |
+
)
|
| 248 |
+
return None, None
|
| 249 |
+
|
| 250 |
+
# get mtmct matching results by cid_tid_fid_results[c_id][t_id][f_id]
|
| 251 |
+
cid_tid_fid_results = dict()
|
| 252 |
+
cid_tid_to_fids = dict()
|
| 253 |
+
interval = int(secs_interval * video_fps) # preferably less than 10
|
| 254 |
+
for c_id in camera_ids:
|
| 255 |
+
cid_tid_fid_results[c_id] = dict()
|
| 256 |
+
cid_tid_to_fids[c_id] = dict()
|
| 257 |
+
for t_id in common_tids:
|
| 258 |
+
tid_mask = camera_results[c_id][:, 1] == t_id
|
| 259 |
+
cid_tid_fid_results[c_id][t_id] = dict()
|
| 260 |
+
|
| 261 |
+
camera_trackid_results = camera_results[c_id][tid_mask]
|
| 262 |
+
fids = np.unique(camera_trackid_results[:, 2])
|
| 263 |
+
fids = fids[fids % interval == 0]
|
| 264 |
+
fids = list(map(int, fids))
|
| 265 |
+
cid_tid_to_fids[c_id][t_id] = fids
|
| 266 |
+
|
| 267 |
+
for f_id in fids:
|
| 268 |
+
st_frame = f_id
|
| 269 |
+
ed_frame = f_id + interval
|
| 270 |
+
|
| 271 |
+
st_mask = camera_trackid_results[:, 2] >= st_frame
|
| 272 |
+
ed_mask = camera_trackid_results[:, 2] < ed_frame
|
| 273 |
+
frame_mask = np.logical_and(st_mask, ed_mask)
|
| 274 |
+
cid_tid_fid_results[c_id][t_id][f_id] = camera_trackid_results[
|
| 275 |
+
frame_mask]
|
| 276 |
+
|
| 277 |
+
return camera_results, cid_tid_fid_results
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def save_mtmct_crops(cid_tid_fid_res,
|
| 281 |
+
images_dir,
|
| 282 |
+
crops_dir,
|
| 283 |
+
width=300,
|
| 284 |
+
height=200):
|
| 285 |
+
camera_ids = cid_tid_fid_res.keys()
|
| 286 |
+
seqs_folder = os.listdir(images_dir)
|
| 287 |
+
seqs = []
|
| 288 |
+
for x in seqs_folder:
|
| 289 |
+
if os.path.isdir(os.path.join(images_dir, x)):
|
| 290 |
+
seqs.append(x)
|
| 291 |
+
assert len(seqs) == len(camera_ids)
|
| 292 |
+
seqs.sort()
|
| 293 |
+
|
| 294 |
+
if not os.path.exists(crops_dir):
|
| 295 |
+
os.makedirs(crops_dir)
|
| 296 |
+
|
| 297 |
+
common_tids = list(cid_tid_fid_res[list(camera_ids)[0]].keys())
|
| 298 |
+
|
| 299 |
+
# get crops by name 'tid_cid_fid.jpg
|
| 300 |
+
for t_id in common_tids:
|
| 301 |
+
for i, c_id in enumerate(camera_ids):
|
| 302 |
+
infer_dir = os.path.join(images_dir, seqs[i])
|
| 303 |
+
if os.path.exists(os.path.join(infer_dir, 'img1')):
|
| 304 |
+
infer_dir = os.path.join(infer_dir, 'img1')
|
| 305 |
+
all_images = os.listdir(infer_dir)
|
| 306 |
+
all_images.sort()
|
| 307 |
+
|
| 308 |
+
for f_id in cid_tid_fid_res[c_id][t_id].keys():
|
| 309 |
+
frame_idx = f_id - 1 if f_id > 0 else 0
|
| 310 |
+
im_path = os.path.join(infer_dir, all_images[frame_idx])
|
| 311 |
+
|
| 312 |
+
im = cv2.imread(im_path) # (H, W, 3)
|
| 313 |
+
|
| 314 |
+
# only select one track
|
| 315 |
+
track = cid_tid_fid_res[c_id][t_id][f_id][0]
|
| 316 |
+
|
| 317 |
+
cid, tid, fid, x1, y1, w, h = [int(v) for v in track]
|
| 318 |
+
clip = im[y1:(y1 + h), x1:(x1 + w)]
|
| 319 |
+
clip = cv2.resize(clip, (width, height))
|
| 320 |
+
|
| 321 |
+
cv2.imwrite(
|
| 322 |
+
os.path.join(crops_dir,
|
| 323 |
+
'tid{:06d}_cid{:06d}_fid{:06d}.jpg'.format(
|
| 324 |
+
tid, cid, fid)), clip)
|
| 325 |
+
|
| 326 |
+
print("Finish cropping image of tracked_id {} in camera: {}".
|
| 327 |
+
format(t_id, c_id))
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def save_mtmct_vis_results(camera_results,
|
| 331 |
+
images_dir,
|
| 332 |
+
save_dir,
|
| 333 |
+
save_videos=False):
|
| 334 |
+
# camera_results: 'cid, tid, fid, x1, y1, w, h'
|
| 335 |
+
camera_ids = camera_results.keys()
|
| 336 |
+
seqs_folder = os.listdir(images_dir)
|
| 337 |
+
seqs = []
|
| 338 |
+
for x in seqs_folder:
|
| 339 |
+
if os.path.isdir(os.path.join(images_dir, x)):
|
| 340 |
+
seqs.append(x)
|
| 341 |
+
assert len(seqs) == len(camera_ids)
|
| 342 |
+
seqs.sort()
|
| 343 |
+
|
| 344 |
+
if not os.path.exists(save_dir):
|
| 345 |
+
os.makedirs(save_dir)
|
| 346 |
+
|
| 347 |
+
for i, c_id in enumerate(camera_ids):
|
| 348 |
+
print("Start visualization for camera {} of sequence {}.".format(
|
| 349 |
+
c_id, seqs[i]))
|
| 350 |
+
cid_save_dir = os.path.join(save_dir, '{}'.format(seqs[i]))
|
| 351 |
+
if not os.path.exists(cid_save_dir):
|
| 352 |
+
os.makedirs(cid_save_dir)
|
| 353 |
+
|
| 354 |
+
infer_dir = os.path.join(images_dir, seqs[i])
|
| 355 |
+
if os.path.exists(os.path.join(infer_dir, 'img1')):
|
| 356 |
+
infer_dir = os.path.join(infer_dir, 'img1')
|
| 357 |
+
all_images = os.listdir(infer_dir)
|
| 358 |
+
all_images.sort()
|
| 359 |
+
|
| 360 |
+
for f_id, im_path in enumerate(all_images):
|
| 361 |
+
img = cv2.imread(os.path.join(infer_dir, im_path))
|
| 362 |
+
tracks = camera_results[c_id][camera_results[c_id][:, 2] == f_id]
|
| 363 |
+
if tracks.shape[0] > 0:
|
| 364 |
+
tracked_ids = tracks[:, 1]
|
| 365 |
+
xywhs = tracks[:, 3:]
|
| 366 |
+
online_im = plot_tracking(
|
| 367 |
+
img, xywhs, tracked_ids, scores=None, frame_id=f_id)
|
| 368 |
+
else:
|
| 369 |
+
online_im = img
|
| 370 |
+
print('Frame {} of seq {} has no tracking results'.format(
|
| 371 |
+
f_id, seqs[i]))
|
| 372 |
+
|
| 373 |
+
cv2.imwrite(
|
| 374 |
+
os.path.join(cid_save_dir, '{:05d}.jpg'.format(f_id)),
|
| 375 |
+
online_im)
|
| 376 |
+
if f_id % 40 == 0:
|
| 377 |
+
print('Processing frame {}'.format(f_id))
|
| 378 |
+
|
| 379 |
+
if save_videos:
|
| 380 |
+
output_video_path = os.path.join(
|
| 381 |
+
cid_save_dir, '..', '{}_mtmct_vis.mp4'.format(seqs[i]))
|
| 382 |
+
cmd_str = 'ffmpeg -f image2 -i {}/%05d.jpg {}'.format(
|
| 383 |
+
cid_save_dir, output_video_path)
|
| 384 |
+
os.system(cmd_str)
|
| 385 |
+
print('Save camera {} video in {}.'.format(seqs[i],
|
| 386 |
+
output_video_path))
|
pptracking/python/mot/mtmct/utils.py
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/LCFractal/AIC21-MTMC/tree/main/reid/reid-matching/tools
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
import re
|
| 20 |
+
import cv2
|
| 21 |
+
import gc
|
| 22 |
+
import numpy as np
|
| 23 |
+
import pandas as pd
|
| 24 |
+
from tqdm import tqdm
|
| 25 |
+
import warnings
|
| 26 |
+
warnings.filterwarnings("ignore")
|
| 27 |
+
|
| 28 |
+
__all__ = [
|
| 29 |
+
'parse_pt', 'parse_bias', 'get_dire', 'parse_pt_gt',
|
| 30 |
+
'compare_dataframes_mtmc', 'get_sim_matrix', 'get_labels', 'getData',
|
| 31 |
+
'gen_new_mot'
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def parse_pt(mot_feature, zones=None):
|
| 36 |
+
mot_list = dict()
|
| 37 |
+
for line in mot_feature:
|
| 38 |
+
fid = int(re.sub('[a-z,A-Z]', "", mot_feature[line]['frame']))
|
| 39 |
+
tid = mot_feature[line]['id']
|
| 40 |
+
bbox = list(map(lambda x: int(float(x)), mot_feature[line]['bbox']))
|
| 41 |
+
if tid not in mot_list:
|
| 42 |
+
mot_list[tid] = dict()
|
| 43 |
+
out_dict = mot_feature[line]
|
| 44 |
+
if zones is not None:
|
| 45 |
+
out_dict['zone'] = zones.get_zone(bbox)
|
| 46 |
+
else:
|
| 47 |
+
out_dict['zone'] = None
|
| 48 |
+
mot_list[tid][fid] = out_dict
|
| 49 |
+
return mot_list
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def gen_new_mot(mot_list):
|
| 53 |
+
out_dict = dict()
|
| 54 |
+
for tracklet in mot_list:
|
| 55 |
+
tracklet = mot_list[tracklet]
|
| 56 |
+
for f in tracklet:
|
| 57 |
+
out_dict[tracklet[f]['imgname']] = tracklet[f]
|
| 58 |
+
return out_dict
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def mergesetfeat1_notrk(P, neg_vector, in_feats, in_labels):
|
| 62 |
+
out_feats = []
|
| 63 |
+
for i in range(in_feats.shape[0]):
|
| 64 |
+
camera_id = in_labels[i, 1]
|
| 65 |
+
feat = in_feats[i] - neg_vector[camera_id]
|
| 66 |
+
feat = P[camera_id].dot(feat)
|
| 67 |
+
feat = feat / np.linalg.norm(feat, ord=2)
|
| 68 |
+
out_feats.append(feat)
|
| 69 |
+
out_feats = np.vstack(out_feats)
|
| 70 |
+
return out_feats
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def compute_P2(prb_feats, gal_feats, gal_labels, la=3.0):
|
| 74 |
+
X = gal_feats
|
| 75 |
+
neg_vector = {}
|
| 76 |
+
u_labels = np.unique(gal_labels[:, 1])
|
| 77 |
+
P = {}
|
| 78 |
+
for label in u_labels:
|
| 79 |
+
curX = gal_feats[gal_labels[:, 1] == label, :]
|
| 80 |
+
neg_vector[label] = np.mean(curX, axis=0)
|
| 81 |
+
P[label] = np.linalg.inv(
|
| 82 |
+
curX.T.dot(curX) + curX.shape[0] * la * np.eye(X.shape[1]))
|
| 83 |
+
return P, neg_vector
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def parse_bias(cameras_bias):
|
| 87 |
+
cid_bias = dict()
|
| 88 |
+
for cameras in cameras_bias.keys():
|
| 89 |
+
cameras_id = re.sub('[a-z,A-Z]', "", cameras)
|
| 90 |
+
cameras_id = int(cameras_id)
|
| 91 |
+
bias = cameras_bias[cameras]
|
| 92 |
+
cid_bias[cameras_id] = float(bias)
|
| 93 |
+
return cid_bias
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def get_dire(zone_list, cid):
|
| 97 |
+
zs, ze = zone_list[0], zone_list[-1]
|
| 98 |
+
return (zs, ze)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def intracam_ignore(st_mask, cid_tids):
|
| 102 |
+
count = len(cid_tids)
|
| 103 |
+
for i in range(count):
|
| 104 |
+
for j in range(count):
|
| 105 |
+
if cid_tids[i][0] == cid_tids[j][0]:
|
| 106 |
+
st_mask[i, j] = 0.
|
| 107 |
+
return st_mask
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def mergesetfeat(in_feats, in_labels, in_tracks):
|
| 111 |
+
trackset = list(set(list(in_tracks)))
|
| 112 |
+
out_feats = []
|
| 113 |
+
out_labels = []
|
| 114 |
+
for track in trackset:
|
| 115 |
+
feat = np.mean(in_feats[in_tracks == track], axis=0)
|
| 116 |
+
feat = feat / np.linalg.norm(feat, ord=2)
|
| 117 |
+
label = in_labels[in_tracks == track][0]
|
| 118 |
+
out_feats.append(feat)
|
| 119 |
+
out_labels.append(label)
|
| 120 |
+
out_feats = np.vstack(out_feats)
|
| 121 |
+
out_labels = np.vstack(out_labels)
|
| 122 |
+
return out_feats, out_labels
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def mergesetfeat3(X, labels, gX, glabels, beta=0.08, knn=20, lr=0.5):
|
| 126 |
+
for i in range(0, X.shape[0]):
|
| 127 |
+
if i % 1000 == 0:
|
| 128 |
+
print('feat3:%d/%d' % (i, X.shape[0]))
|
| 129 |
+
knnX = gX[glabels[:, 1] != labels[i, 1], :]
|
| 130 |
+
sim = knnX.dot(X[i, :])
|
| 131 |
+
knnX = knnX[sim > 0, :]
|
| 132 |
+
sim = sim[sim > 0]
|
| 133 |
+
if len(sim) > 0:
|
| 134 |
+
idx = np.argsort(-sim)
|
| 135 |
+
if len(sim) > 2 * knn:
|
| 136 |
+
sim = sim[idx[:2 * knn]]
|
| 137 |
+
knnX = knnX[idx[:2 * knn], :]
|
| 138 |
+
else:
|
| 139 |
+
sim = sim[idx]
|
| 140 |
+
knnX = knnX[idx, :]
|
| 141 |
+
knn = min(knn, len(sim))
|
| 142 |
+
knn_pos_weight = np.exp((sim[:knn] - 1) / beta)
|
| 143 |
+
knn_neg_weight = np.ones(len(sim) - knn)
|
| 144 |
+
knn_pos_prob = knn_pos_weight / np.sum(knn_pos_weight)
|
| 145 |
+
knn_neg_prob = knn_neg_weight / np.sum(knn_neg_weight)
|
| 146 |
+
X[i, :] += lr * (knn_pos_prob.dot(knnX[:knn, :]) -
|
| 147 |
+
knn_neg_prob.dot(knnX[knn:, :]))
|
| 148 |
+
X[i, :] /= np.linalg.norm(X[i, :])
|
| 149 |
+
return X
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def run_fic(prb_feats, gal_feats, prb_labels, gal_labels, la=3.0):
|
| 153 |
+
P, neg_vector = compute_P2(prb_feats, gal_feats, gal_labels, la)
|
| 154 |
+
prb_feats_new = mergesetfeat1_notrk(P, neg_vector, prb_feats, prb_labels)
|
| 155 |
+
gal_feats_new = mergesetfeat1_notrk(P, neg_vector, gal_feats, gal_labels)
|
| 156 |
+
return prb_feats_new, gal_feats_new
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def run_fac(prb_feats,
|
| 160 |
+
gal_feats,
|
| 161 |
+
prb_labels,
|
| 162 |
+
gal_labels,
|
| 163 |
+
beta=0.08,
|
| 164 |
+
knn=20,
|
| 165 |
+
lr=0.5,
|
| 166 |
+
prb_epoch=2,
|
| 167 |
+
gal_epoch=3):
|
| 168 |
+
gal_feats_new = gal_feats.copy()
|
| 169 |
+
for i in range(prb_epoch):
|
| 170 |
+
gal_feats_new = mergesetfeat3(gal_feats_new, gal_labels, gal_feats,
|
| 171 |
+
gal_labels, beta, knn, lr)
|
| 172 |
+
prb_feats_new = prb_feats.copy()
|
| 173 |
+
for i in range(gal_epoch):
|
| 174 |
+
prb_feats_new = mergesetfeat3(prb_feats_new, prb_labels, gal_feats_new,
|
| 175 |
+
gal_labels, beta, knn, lr)
|
| 176 |
+
return prb_feats_new, gal_feats_new
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def euclidean_distance(qf, gf):
|
| 180 |
+
m = qf.shape[0]
|
| 181 |
+
n = gf.shape[0]
|
| 182 |
+
dist_mat = 2 - 2 * np.matmul(qf, gf.T)
|
| 183 |
+
return dist_mat
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def find_topk(a, k, axis=-1, largest=True, sorted=True):
|
| 187 |
+
if axis is None:
|
| 188 |
+
axis_size = a.size
|
| 189 |
+
else:
|
| 190 |
+
axis_size = a.shape[axis]
|
| 191 |
+
assert 1 <= k <= axis_size
|
| 192 |
+
|
| 193 |
+
a = np.asanyarray(a)
|
| 194 |
+
if largest:
|
| 195 |
+
index_array = np.argpartition(a, axis_size - k, axis=axis)
|
| 196 |
+
topk_indices = np.take(index_array, -np.arange(k) - 1, axis=axis)
|
| 197 |
+
else:
|
| 198 |
+
index_array = np.argpartition(a, k - 1, axis=axis)
|
| 199 |
+
topk_indices = np.take(index_array, np.arange(k), axis=axis)
|
| 200 |
+
topk_values = np.take_along_axis(a, topk_indices, axis=axis)
|
| 201 |
+
if sorted:
|
| 202 |
+
sorted_indices_in_topk = np.argsort(topk_values, axis=axis)
|
| 203 |
+
if largest:
|
| 204 |
+
sorted_indices_in_topk = np.flip(sorted_indices_in_topk, axis=axis)
|
| 205 |
+
sorted_topk_values = np.take_along_axis(
|
| 206 |
+
topk_values, sorted_indices_in_topk, axis=axis)
|
| 207 |
+
sorted_topk_indices = np.take_along_axis(
|
| 208 |
+
topk_indices, sorted_indices_in_topk, axis=axis)
|
| 209 |
+
return sorted_topk_values, sorted_topk_indices
|
| 210 |
+
return topk_values, topk_indices
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def batch_numpy_topk(qf, gf, k1, N=6000):
|
| 214 |
+
m = qf.shape[0]
|
| 215 |
+
n = gf.shape[0]
|
| 216 |
+
initial_rank = []
|
| 217 |
+
for j in range(n // N + 1):
|
| 218 |
+
temp_gf = gf[j * N:j * N + N]
|
| 219 |
+
temp_qd = []
|
| 220 |
+
for i in range(m // N + 1):
|
| 221 |
+
temp_qf = qf[i * N:i * N + N]
|
| 222 |
+
temp_d = euclidean_distance(temp_qf, temp_gf)
|
| 223 |
+
temp_qd.append(temp_d)
|
| 224 |
+
temp_qd = np.concatenate(temp_qd, axis=0)
|
| 225 |
+
temp_qd = temp_qd / (np.max(temp_qd, axis=0)[0])
|
| 226 |
+
temp_qd = temp_qd.T
|
| 227 |
+
initial_rank.append(
|
| 228 |
+
find_topk(
|
| 229 |
+
temp_qd, k=k1, axis=1, largest=False, sorted=True)[1])
|
| 230 |
+
del temp_qd
|
| 231 |
+
del temp_gf
|
| 232 |
+
del temp_qf
|
| 233 |
+
del temp_d
|
| 234 |
+
initial_rank = np.concatenate(initial_rank, axis=0)
|
| 235 |
+
return initial_rank
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def batch_euclidean_distance(qf, gf, N=6000):
|
| 239 |
+
m = qf.shape[0]
|
| 240 |
+
n = gf.shape[0]
|
| 241 |
+
dist_mat = []
|
| 242 |
+
for j in range(n // N + 1):
|
| 243 |
+
temp_gf = gf[j * N:j * N + N]
|
| 244 |
+
temp_qd = []
|
| 245 |
+
for i in range(m // N + 1):
|
| 246 |
+
temp_qf = qf[i * N:i * N + N]
|
| 247 |
+
temp_d = euclidean_distance(temp_qf, temp_gf)
|
| 248 |
+
temp_qd.append(temp_d)
|
| 249 |
+
temp_qd = np.concatenate(temp_qd, axis=0)
|
| 250 |
+
temp_qd = temp_qd / (np.max(temp_qd, axis=0)[0])
|
| 251 |
+
dist_mat.append(temp_qd.T)
|
| 252 |
+
del temp_qd
|
| 253 |
+
del temp_gf
|
| 254 |
+
del temp_qf
|
| 255 |
+
del temp_d
|
| 256 |
+
dist_mat = np.concatenate(dist_mat, axis=0)
|
| 257 |
+
return dist_mat
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def batch_v(feat, R, all_num):
|
| 261 |
+
V = np.zeros((all_num, all_num), dtype=np.float32)
|
| 262 |
+
m = feat.shape[0]
|
| 263 |
+
for i in tqdm(range(m)):
|
| 264 |
+
temp_gf = feat[i].reshape(1, -1)
|
| 265 |
+
temp_qd = euclidean_distance(temp_gf, feat)
|
| 266 |
+
temp_qd = temp_qd / (np.max(temp_qd))
|
| 267 |
+
temp_qd = temp_qd.reshape(-1)
|
| 268 |
+
temp_qd = temp_qd[R[i].tolist()]
|
| 269 |
+
weight = np.exp(-temp_qd)
|
| 270 |
+
weight = weight / np.sum(weight)
|
| 271 |
+
V[i, R[i]] = weight.astype(np.float32)
|
| 272 |
+
return V
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def k_reciprocal_neigh(initial_rank, i, k1):
|
| 276 |
+
forward_k_neigh_index = initial_rank[i, :k1 + 1]
|
| 277 |
+
backward_k_neigh_index = initial_rank[forward_k_neigh_index, :k1 + 1]
|
| 278 |
+
fi = np.where(backward_k_neigh_index == i)[0]
|
| 279 |
+
return forward_k_neigh_index[fi]
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def ReRank2(probFea, galFea, k1=20, k2=6, lambda_value=0.3):
|
| 283 |
+
query_num = probFea.shape[0]
|
| 284 |
+
all_num = query_num + galFea.shape[0]
|
| 285 |
+
feat = np.concatenate((probFea, galFea), axis=0)
|
| 286 |
+
|
| 287 |
+
initial_rank = batch_numpy_topk(feat, feat, k1 + 1, N=6000)
|
| 288 |
+
del probFea
|
| 289 |
+
del galFea
|
| 290 |
+
gc.collect() # empty memory
|
| 291 |
+
R = []
|
| 292 |
+
for i in tqdm(range(all_num)):
|
| 293 |
+
# k-reciprocal neighbors
|
| 294 |
+
k_reciprocal_index = k_reciprocal_neigh(initial_rank, i, k1)
|
| 295 |
+
k_reciprocal_expansion_index = k_reciprocal_index
|
| 296 |
+
for j in range(len(k_reciprocal_index)):
|
| 297 |
+
candidate = k_reciprocal_index[j]
|
| 298 |
+
candidate_k_reciprocal_index = k_reciprocal_neigh(
|
| 299 |
+
initial_rank, candidate, int(np.around(k1 / 2)))
|
| 300 |
+
if len(
|
| 301 |
+
np.intersect1d(candidate_k_reciprocal_index,
|
| 302 |
+
k_reciprocal_index)) > 2. / 3 * len(
|
| 303 |
+
candidate_k_reciprocal_index):
|
| 304 |
+
k_reciprocal_expansion_index = np.append(
|
| 305 |
+
k_reciprocal_expansion_index, candidate_k_reciprocal_index)
|
| 306 |
+
k_reciprocal_expansion_index = np.unique(k_reciprocal_expansion_index)
|
| 307 |
+
R.append(k_reciprocal_expansion_index)
|
| 308 |
+
|
| 309 |
+
gc.collect() # empty memory
|
| 310 |
+
V = batch_v(feat, R, all_num)
|
| 311 |
+
del R
|
| 312 |
+
gc.collect() # empty memory
|
| 313 |
+
initial_rank = initial_rank[:, :k2]
|
| 314 |
+
|
| 315 |
+
# Faster version
|
| 316 |
+
if k2 != 1:
|
| 317 |
+
V_qe = np.zeros_like(V, dtype=np.float16)
|
| 318 |
+
for i in range(all_num):
|
| 319 |
+
V_qe[i, :] = np.mean(V[initial_rank[i], :], axis=0)
|
| 320 |
+
V = V_qe
|
| 321 |
+
del V_qe
|
| 322 |
+
del initial_rank
|
| 323 |
+
gc.collect() # empty memory
|
| 324 |
+
invIndex = []
|
| 325 |
+
for i in range(all_num):
|
| 326 |
+
invIndex.append(np.where(V[:, i] != 0)[0])
|
| 327 |
+
jaccard_dist = np.zeros((query_num, all_num), dtype=np.float32)
|
| 328 |
+
for i in tqdm(range(query_num)):
|
| 329 |
+
temp_min = np.zeros(shape=[1, all_num], dtype=np.float32)
|
| 330 |
+
indNonZero = np.where(V[i, :] != 0)[0]
|
| 331 |
+
indImages = [invIndex[ind] for ind in indNonZero]
|
| 332 |
+
for j in range(len(indNonZero)):
|
| 333 |
+
temp_min[0, indImages[j]] = temp_min[0, indImages[j]] + np.minimum(
|
| 334 |
+
V[i, indNonZero[j]], V[indImages[j], indNonZero[j]])
|
| 335 |
+
jaccard_dist[i] = 1 - temp_min / (2. - temp_min)
|
| 336 |
+
del V
|
| 337 |
+
gc.collect() # empty memory
|
| 338 |
+
original_dist = batch_euclidean_distance(feat, feat[:query_num, :])
|
| 339 |
+
final_dist = jaccard_dist * (1 - lambda_value
|
| 340 |
+
) + original_dist * lambda_value
|
| 341 |
+
del original_dist
|
| 342 |
+
del jaccard_dist
|
| 343 |
+
final_dist = final_dist[:query_num, query_num:]
|
| 344 |
+
return final_dist
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def visual_rerank(prb_feats,
|
| 348 |
+
gal_feats,
|
| 349 |
+
cid_tids,
|
| 350 |
+
use_ff=False,
|
| 351 |
+
use_rerank=False):
|
| 352 |
+
"""Rerank by visual cures."""
|
| 353 |
+
gal_labels = np.array([[0, item[0]] for item in cid_tids])
|
| 354 |
+
prb_labels = gal_labels.copy()
|
| 355 |
+
if use_ff:
|
| 356 |
+
print('current use ff finetuned parameters....')
|
| 357 |
+
# Step1-1: fic. finetuned parameters: [la]
|
| 358 |
+
prb_feats, gal_feats = run_fic(prb_feats, gal_feats, prb_labels,
|
| 359 |
+
gal_labels, 3.0)
|
| 360 |
+
# Step1=2: fac. finetuned parameters: [beta,knn,lr,prb_epoch,gal_epoch]
|
| 361 |
+
prb_feats, gal_feats = run_fac(prb_feats, gal_feats, prb_labels,
|
| 362 |
+
gal_labels, 0.08, 20, 0.5, 1, 1)
|
| 363 |
+
if use_rerank:
|
| 364 |
+
print('current use rerank finetuned parameters....')
|
| 365 |
+
# Step2: k-reciprocal. finetuned parameters: [k1,k2,lambda_value]
|
| 366 |
+
sims = ReRank2(prb_feats, gal_feats, 20, 3, 0.3)
|
| 367 |
+
else:
|
| 368 |
+
sims = 1.0 - np.dot(prb_feats, gal_feats.T)
|
| 369 |
+
|
| 370 |
+
# NOTE: sims here is actually dist, the smaller the more similar
|
| 371 |
+
return 1.0 - sims
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def normalize(nparray, axis=0):
|
| 375 |
+
try:
|
| 376 |
+
from sklearn import preprocessing
|
| 377 |
+
except Exception as e:
|
| 378 |
+
raise RuntimeError(
|
| 379 |
+
'Unable to use sklearn in MTMCT in PP-Tracking, please install sklearn, for example: `pip install sklearn`'
|
| 380 |
+
)
|
| 381 |
+
nparray = preprocessing.normalize(nparray, norm='l2', axis=axis)
|
| 382 |
+
return nparray
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
def get_match(cluster_labels):
|
| 386 |
+
cluster_dict = dict()
|
| 387 |
+
cluster = list()
|
| 388 |
+
for i, l in enumerate(cluster_labels):
|
| 389 |
+
if l in list(cluster_dict.keys()):
|
| 390 |
+
cluster_dict[l].append(i)
|
| 391 |
+
else:
|
| 392 |
+
cluster_dict[l] = [i]
|
| 393 |
+
for idx in cluster_dict:
|
| 394 |
+
cluster.append(cluster_dict[idx])
|
| 395 |
+
return cluster
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
def get_cid_tid(cluster_labels, cid_tids):
|
| 399 |
+
cluster = list()
|
| 400 |
+
for labels in cluster_labels:
|
| 401 |
+
cid_tid_list = list()
|
| 402 |
+
for label in labels:
|
| 403 |
+
cid_tid_list.append(cid_tids[label])
|
| 404 |
+
cluster.append(cid_tid_list)
|
| 405 |
+
return cluster
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
def combin_feature(cid_tid_dict, sub_cluster):
|
| 409 |
+
for sub_ct in sub_cluster:
|
| 410 |
+
if len(sub_ct) < 2: continue
|
| 411 |
+
mean_feat = np.array([cid_tid_dict[i]['mean_feat'] for i in sub_ct])
|
| 412 |
+
for i in sub_ct:
|
| 413 |
+
cid_tid_dict[i]['mean_feat'] = mean_feat.mean(axis=0)
|
| 414 |
+
return cid_tid_dict
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def combin_cluster(sub_labels, cid_tids):
|
| 418 |
+
cluster = list()
|
| 419 |
+
for sub_c_to_c in sub_labels:
|
| 420 |
+
if len(cluster) < 1:
|
| 421 |
+
cluster = sub_labels[sub_c_to_c]
|
| 422 |
+
continue
|
| 423 |
+
for c_ts in sub_labels[sub_c_to_c]:
|
| 424 |
+
is_add = False
|
| 425 |
+
for i_c, c_set in enumerate(cluster):
|
| 426 |
+
if len(set(c_ts) & set(c_set)) > 0:
|
| 427 |
+
new_list = list(set(c_ts) | set(c_set))
|
| 428 |
+
cluster[i_c] = new_list
|
| 429 |
+
is_add = True
|
| 430 |
+
break
|
| 431 |
+
if not is_add:
|
| 432 |
+
cluster.append(c_ts)
|
| 433 |
+
labels = list()
|
| 434 |
+
num_tr = 0
|
| 435 |
+
for c_ts in cluster:
|
| 436 |
+
label_list = list()
|
| 437 |
+
for c_t in c_ts:
|
| 438 |
+
label_list.append(cid_tids.index(c_t))
|
| 439 |
+
num_tr += 1
|
| 440 |
+
label_list.sort()
|
| 441 |
+
labels.append(label_list)
|
| 442 |
+
return labels, cluster
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def parse_pt_gt(mot_feature):
|
| 446 |
+
img_rects = dict()
|
| 447 |
+
for line in mot_feature:
|
| 448 |
+
fid = int(re.sub('[a-z,A-Z]', "", mot_feature[line]['frame']))
|
| 449 |
+
tid = mot_feature[line]['id']
|
| 450 |
+
rect = list(map(lambda x: int(float(x)), mot_feature[line]['bbox']))
|
| 451 |
+
if fid not in img_rects:
|
| 452 |
+
img_rects[fid] = list()
|
| 453 |
+
rect.insert(0, tid)
|
| 454 |
+
img_rects[fid].append(rect)
|
| 455 |
+
return img_rects
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
# eval result
|
| 459 |
+
def compare_dataframes_mtmc(gts, ts):
|
| 460 |
+
try:
|
| 461 |
+
import motmetrics as mm
|
| 462 |
+
except Exception as e:
|
| 463 |
+
raise RuntimeError(
|
| 464 |
+
'Unable to use motmetrics in MTMCT in PP-Tracking, please install motmetrics, for example: `pip install motmetrics`, see https://github.com/longcw/py-motmetrics'
|
| 465 |
+
)
|
| 466 |
+
"""Compute ID-based evaluation metrics for MTMCT
|
| 467 |
+
Return:
|
| 468 |
+
df (pandas.DataFrame): Results of the evaluations in a df with only the 'idf1', 'idp', and 'idr' columns.
|
| 469 |
+
"""
|
| 470 |
+
gtds = []
|
| 471 |
+
tsds = []
|
| 472 |
+
gtcams = gts['CameraId'].drop_duplicates().tolist()
|
| 473 |
+
tscams = ts['CameraId'].drop_duplicates().tolist()
|
| 474 |
+
maxFrameId = 0
|
| 475 |
+
|
| 476 |
+
for k in sorted(gtcams):
|
| 477 |
+
gtd = gts.query('CameraId == %d' % k)
|
| 478 |
+
gtd = gtd[['FrameId', 'Id', 'X', 'Y', 'Width', 'Height']]
|
| 479 |
+
# max FrameId in gtd only
|
| 480 |
+
mfid = gtd['FrameId'].max()
|
| 481 |
+
gtd['FrameId'] += maxFrameId
|
| 482 |
+
gtd = gtd.set_index(['FrameId', 'Id'])
|
| 483 |
+
gtds.append(gtd)
|
| 484 |
+
|
| 485 |
+
if k in tscams:
|
| 486 |
+
tsd = ts.query('CameraId == %d' % k)
|
| 487 |
+
tsd = tsd[['FrameId', 'Id', 'X', 'Y', 'Width', 'Height']]
|
| 488 |
+
# max FrameId among both gtd and tsd
|
| 489 |
+
mfid = max(mfid, tsd['FrameId'].max())
|
| 490 |
+
tsd['FrameId'] += maxFrameId
|
| 491 |
+
tsd = tsd.set_index(['FrameId', 'Id'])
|
| 492 |
+
tsds.append(tsd)
|
| 493 |
+
|
| 494 |
+
maxFrameId += mfid
|
| 495 |
+
|
| 496 |
+
# compute multi-camera tracking evaluation stats
|
| 497 |
+
multiCamAcc = mm.utils.compare_to_groundtruth(
|
| 498 |
+
pd.concat(gtds), pd.concat(tsds), 'iou')
|
| 499 |
+
metrics = list(mm.metrics.motchallenge_metrics)
|
| 500 |
+
metrics.extend(['num_frames', 'idfp', 'idfn', 'idtp'])
|
| 501 |
+
mh = mm.metrics.create()
|
| 502 |
+
summary = mh.compute(multiCamAcc, metrics=metrics, name='MultiCam')
|
| 503 |
+
return summary
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
def get_sim_matrix(cid_tid_dict,
|
| 507 |
+
cid_tids,
|
| 508 |
+
use_ff=True,
|
| 509 |
+
use_rerank=True,
|
| 510 |
+
use_st_filter=False):
|
| 511 |
+
# Note: camera independent get_sim_matrix function,
|
| 512 |
+
# which is different from the one in camera_utils.py.
|
| 513 |
+
count = len(cid_tids)
|
| 514 |
+
|
| 515 |
+
q_arr = np.array(
|
| 516 |
+
[cid_tid_dict[cid_tids[i]]['mean_feat'] for i in range(count)])
|
| 517 |
+
g_arr = np.array(
|
| 518 |
+
[cid_tid_dict[cid_tids[i]]['mean_feat'] for i in range(count)])
|
| 519 |
+
q_arr = normalize(q_arr, axis=1)
|
| 520 |
+
g_arr = normalize(g_arr, axis=1)
|
| 521 |
+
|
| 522 |
+
st_mask = np.ones((count, count), dtype=np.float32)
|
| 523 |
+
st_mask = intracam_ignore(st_mask, cid_tids)
|
| 524 |
+
|
| 525 |
+
visual_sim_matrix = visual_rerank(
|
| 526 |
+
q_arr, g_arr, cid_tids, use_ff=use_ff, use_rerank=use_rerank)
|
| 527 |
+
visual_sim_matrix = visual_sim_matrix.astype('float32')
|
| 528 |
+
|
| 529 |
+
np.set_printoptions(precision=3)
|
| 530 |
+
sim_matrix = visual_sim_matrix * st_mask
|
| 531 |
+
|
| 532 |
+
np.fill_diagonal(sim_matrix, 0)
|
| 533 |
+
return sim_matrix
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
def get_labels(cid_tid_dict,
|
| 537 |
+
cid_tids,
|
| 538 |
+
use_ff=True,
|
| 539 |
+
use_rerank=True,
|
| 540 |
+
use_st_filter=False):
|
| 541 |
+
try:
|
| 542 |
+
from sklearn.cluster import AgglomerativeClustering
|
| 543 |
+
except Exception as e:
|
| 544 |
+
raise RuntimeError(
|
| 545 |
+
'Unable to use sklearn in MTMCT in PP-Tracking, please install sklearn, for example: `pip install sklearn`'
|
| 546 |
+
)
|
| 547 |
+
# 1st cluster
|
| 548 |
+
sim_matrix = get_sim_matrix(
|
| 549 |
+
cid_tid_dict,
|
| 550 |
+
cid_tids,
|
| 551 |
+
use_ff=use_ff,
|
| 552 |
+
use_rerank=use_rerank,
|
| 553 |
+
use_st_filter=use_st_filter)
|
| 554 |
+
cluster_labels = AgglomerativeClustering(
|
| 555 |
+
n_clusters=None,
|
| 556 |
+
distance_threshold=0.5,
|
| 557 |
+
affinity='precomputed',
|
| 558 |
+
linkage='complete').fit_predict(1 - sim_matrix)
|
| 559 |
+
labels = get_match(cluster_labels)
|
| 560 |
+
sub_cluster = get_cid_tid(labels, cid_tids)
|
| 561 |
+
|
| 562 |
+
# 2nd cluster
|
| 563 |
+
cid_tid_dict_new = combin_feature(cid_tid_dict, sub_cluster)
|
| 564 |
+
sim_matrix = get_sim_matrix(
|
| 565 |
+
cid_tid_dict_new,
|
| 566 |
+
cid_tids,
|
| 567 |
+
use_ff=use_ff,
|
| 568 |
+
use_rerank=use_rerank,
|
| 569 |
+
use_st_filter=use_st_filter)
|
| 570 |
+
cluster_labels = AgglomerativeClustering(
|
| 571 |
+
n_clusters=None,
|
| 572 |
+
distance_threshold=0.9,
|
| 573 |
+
affinity='precomputed',
|
| 574 |
+
linkage='complete').fit_predict(1 - sim_matrix)
|
| 575 |
+
labels = get_match(cluster_labels)
|
| 576 |
+
sub_cluster = get_cid_tid(labels, cid_tids)
|
| 577 |
+
|
| 578 |
+
return labels
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
def getData(fpath, names=None, sep='\s+|\t+|,'):
|
| 582 |
+
""" Get the necessary track data from a file handle.
|
| 583 |
+
Args:
|
| 584 |
+
fpath (str) : Original path of file reading from.
|
| 585 |
+
names (list[str]): List of column names for the data.
|
| 586 |
+
sep (str): Allowed separators regular expression string.
|
| 587 |
+
Return:
|
| 588 |
+
df (pandas.DataFrame): Data frame containing the data loaded from the
|
| 589 |
+
stream with optionally assigned column names. No index is set on the data.
|
| 590 |
+
"""
|
| 591 |
+
try:
|
| 592 |
+
df = pd.read_csv(
|
| 593 |
+
fpath,
|
| 594 |
+
sep=sep,
|
| 595 |
+
index_col=None,
|
| 596 |
+
skipinitialspace=True,
|
| 597 |
+
header=None,
|
| 598 |
+
names=names,
|
| 599 |
+
engine='python')
|
| 600 |
+
return df
|
| 601 |
+
|
| 602 |
+
except Exception as e:
|
| 603 |
+
raise ValueError("Could not read input from %s. Error: %s" %
|
| 604 |
+
(fpath, repr(e)))
|
pptracking/python/mot/mtmct/zone.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/LCFractal/AIC21-MTMC/tree/main/reid/reid-matching/tools
|
| 16 |
+
|
| 17 |
+
Note: The following codes are strongly related to zone of the AIC21 test-set S06,
|
| 18 |
+
so they can only be used in S06, and can not be used for other MTMCT datasets.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import os
|
| 22 |
+
import cv2
|
| 23 |
+
import numpy as np
|
| 24 |
+
try:
|
| 25 |
+
from sklearn.cluster import AgglomerativeClustering
|
| 26 |
+
except:
|
| 27 |
+
print(
|
| 28 |
+
'Warning: Unable to use MTMCT in PP-Tracking, please install sklearn, for example: `pip install sklearn`'
|
| 29 |
+
)
|
| 30 |
+
pass
|
| 31 |
+
|
| 32 |
+
BBOX_B = 10 / 15
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class Zone(object):
|
| 36 |
+
def __init__(self, zone_path='datasets/zone'):
|
| 37 |
+
# 0: b 1: g 3: r 123:w
|
| 38 |
+
# w r not high speed
|
| 39 |
+
# b g high speed
|
| 40 |
+
assert zone_path != '', "Error: zone_path is not empty!"
|
| 41 |
+
zones = {}
|
| 42 |
+
for img_name in os.listdir(zone_path):
|
| 43 |
+
camnum = int(img_name.split('.')[0][-3:])
|
| 44 |
+
zone_img = cv2.imread(os.path.join(zone_path, img_name))
|
| 45 |
+
zones[camnum] = zone_img
|
| 46 |
+
self.zones = zones
|
| 47 |
+
self.current_cam = 0
|
| 48 |
+
|
| 49 |
+
def set_cam(self, cam):
|
| 50 |
+
self.current_cam = cam
|
| 51 |
+
|
| 52 |
+
def get_zone(self, bbox):
|
| 53 |
+
cx = int((bbox[0] + bbox[2]) / 2)
|
| 54 |
+
cy = int((bbox[1] + bbox[3]) / 2)
|
| 55 |
+
pix = self.zones[self.current_cam][max(cy - 1, 0), max(cx - 1, 0), :]
|
| 56 |
+
zone_num = 0
|
| 57 |
+
if pix[0] > 50 and pix[1] > 50 and pix[2] > 50: # w
|
| 58 |
+
zone_num = 1
|
| 59 |
+
if pix[0] < 50 and pix[1] < 50 and pix[2] > 50: # r
|
| 60 |
+
zone_num = 2
|
| 61 |
+
if pix[0] < 50 and pix[1] > 50 and pix[2] < 50: # g
|
| 62 |
+
zone_num = 3
|
| 63 |
+
if pix[0] > 50 and pix[1] < 50 and pix[2] < 50: # b
|
| 64 |
+
zone_num = 4
|
| 65 |
+
return zone_num
|
| 66 |
+
|
| 67 |
+
def is_ignore(self, zone_list, frame_list, cid):
|
| 68 |
+
# 0 not in any corssroad, 1 white 2 red 3 green 4 bule
|
| 69 |
+
zs, ze = zone_list[0], zone_list[-1]
|
| 70 |
+
fs, fe = frame_list[0], frame_list[-1]
|
| 71 |
+
if zs == ze:
|
| 72 |
+
# if always on one section, excluding
|
| 73 |
+
if ze in [1, 2]:
|
| 74 |
+
return 2
|
| 75 |
+
if zs != 0 and 0 in zone_list:
|
| 76 |
+
return 0
|
| 77 |
+
if fe - fs > 1500:
|
| 78 |
+
return 2
|
| 79 |
+
if fs < 2:
|
| 80 |
+
if cid in [45]:
|
| 81 |
+
if ze in [3, 4]:
|
| 82 |
+
return 1
|
| 83 |
+
else:
|
| 84 |
+
return 2
|
| 85 |
+
if fe > 1999:
|
| 86 |
+
if cid in [41]:
|
| 87 |
+
if ze not in [3]:
|
| 88 |
+
return 2
|
| 89 |
+
else:
|
| 90 |
+
return 0
|
| 91 |
+
if fs < 2 or fe > 1999:
|
| 92 |
+
if ze in [3, 4]:
|
| 93 |
+
return 0
|
| 94 |
+
if ze in [3, 4]:
|
| 95 |
+
return 1
|
| 96 |
+
return 2
|
| 97 |
+
else:
|
| 98 |
+
# if camera section change
|
| 99 |
+
if cid in [41, 42, 43, 44, 45, 46]:
|
| 100 |
+
# come from road extension, exclusing
|
| 101 |
+
if zs == 1 and ze == 2:
|
| 102 |
+
return 2
|
| 103 |
+
if zs == 2 and ze == 1:
|
| 104 |
+
return 2
|
| 105 |
+
if cid in [41]:
|
| 106 |
+
# On 41 camera, no vehicle come into 42 camera
|
| 107 |
+
if (zs in [1, 2]) and ze == 4:
|
| 108 |
+
return 2
|
| 109 |
+
if zs == 4 and (ze in [1, 2]):
|
| 110 |
+
return 2
|
| 111 |
+
if cid in [46]:
|
| 112 |
+
# On 46 camera,no vehicle come into 45
|
| 113 |
+
if (zs in [1, 2]) and ze == 3:
|
| 114 |
+
return 2
|
| 115 |
+
if zs == 3 and (ze in [1, 2]):
|
| 116 |
+
return 2
|
| 117 |
+
return 0
|
| 118 |
+
|
| 119 |
+
def filter_mot(self, mot_list, cid):
|
| 120 |
+
new_mot_list = dict()
|
| 121 |
+
sub_mot_list = dict()
|
| 122 |
+
for tracklet in mot_list:
|
| 123 |
+
tracklet_dict = mot_list[tracklet]
|
| 124 |
+
frame_list = list(tracklet_dict.keys())
|
| 125 |
+
frame_list.sort()
|
| 126 |
+
zone_list = []
|
| 127 |
+
for f in frame_list:
|
| 128 |
+
zone_list.append(tracklet_dict[f]['zone'])
|
| 129 |
+
if self.is_ignore(zone_list, frame_list, cid) == 0:
|
| 130 |
+
new_mot_list[tracklet] = tracklet_dict
|
| 131 |
+
if self.is_ignore(zone_list, frame_list, cid) == 1:
|
| 132 |
+
sub_mot_list[tracklet] = tracklet_dict
|
| 133 |
+
return new_mot_list
|
| 134 |
+
|
| 135 |
+
def filter_bbox(self, mot_list, cid):
|
| 136 |
+
new_mot_list = dict()
|
| 137 |
+
yh = self.zones[cid].shape[0]
|
| 138 |
+
for tracklet in mot_list:
|
| 139 |
+
tracklet_dict = mot_list[tracklet]
|
| 140 |
+
frame_list = list(tracklet_dict.keys())
|
| 141 |
+
frame_list.sort()
|
| 142 |
+
bbox_list = []
|
| 143 |
+
for f in frame_list:
|
| 144 |
+
bbox_list.append(tracklet_dict[f]['bbox'])
|
| 145 |
+
bbox_x = [b[0] for b in bbox_list]
|
| 146 |
+
bbox_y = [b[1] for b in bbox_list]
|
| 147 |
+
bbox_w = [b[2] - b[0] for b in bbox_list]
|
| 148 |
+
bbox_h = [b[3] - b[1] for b in bbox_list]
|
| 149 |
+
new_frame_list = list()
|
| 150 |
+
if 0 in bbox_x or 0 in bbox_y:
|
| 151 |
+
b0 = [
|
| 152 |
+
i for i, f in enumerate(frame_list)
|
| 153 |
+
if bbox_x[i] < 5 or bbox_y[i] + bbox_h[i] > yh - 5
|
| 154 |
+
]
|
| 155 |
+
if len(b0) == len(frame_list):
|
| 156 |
+
if cid in [41, 42, 44, 45, 46]:
|
| 157 |
+
continue
|
| 158 |
+
max_w = max(bbox_w)
|
| 159 |
+
max_h = max(bbox_h)
|
| 160 |
+
for i, f in enumerate(frame_list):
|
| 161 |
+
if bbox_w[i] > max_w * BBOX_B and bbox_h[
|
| 162 |
+
i] > max_h * BBOX_B:
|
| 163 |
+
new_frame_list.append(f)
|
| 164 |
+
else:
|
| 165 |
+
l_i, r_i = 0, len(frame_list) - 1
|
| 166 |
+
if len(b0) == 0:
|
| 167 |
+
continue
|
| 168 |
+
if b0[0] == 0:
|
| 169 |
+
for i in range(len(b0) - 1):
|
| 170 |
+
if b0[i] + 1 == b0[i + 1]:
|
| 171 |
+
l_i = b0[i + 1]
|
| 172 |
+
else:
|
| 173 |
+
break
|
| 174 |
+
if b0[-1] == len(frame_list) - 1:
|
| 175 |
+
for i in range(len(b0) - 1):
|
| 176 |
+
i = len(b0) - 1 - i
|
| 177 |
+
if b0[i] - 1 == b0[i - 1]:
|
| 178 |
+
r_i = b0[i - 1]
|
| 179 |
+
else:
|
| 180 |
+
break
|
| 181 |
+
|
| 182 |
+
max_lw, max_lh = bbox_w[l_i], bbox_h[l_i]
|
| 183 |
+
max_rw, max_rh = bbox_w[r_i], bbox_h[r_i]
|
| 184 |
+
for i, f in enumerate(frame_list):
|
| 185 |
+
if i < l_i:
|
| 186 |
+
if bbox_w[i] > max_lw * BBOX_B and bbox_h[
|
| 187 |
+
i] > max_lh * BBOX_B:
|
| 188 |
+
new_frame_list.append(f)
|
| 189 |
+
elif i > r_i:
|
| 190 |
+
if bbox_w[i] > max_rw * BBOX_B and bbox_h[
|
| 191 |
+
i] > max_rh * BBOX_B:
|
| 192 |
+
new_frame_list.append(f)
|
| 193 |
+
else:
|
| 194 |
+
new_frame_list.append(f)
|
| 195 |
+
new_tracklet_dict = dict()
|
| 196 |
+
for f in new_frame_list:
|
| 197 |
+
new_tracklet_dict[f] = tracklet_dict[f]
|
| 198 |
+
new_mot_list[tracklet] = new_tracklet_dict
|
| 199 |
+
else:
|
| 200 |
+
new_mot_list[tracklet] = tracklet_dict
|
| 201 |
+
return new_mot_list
|
| 202 |
+
|
| 203 |
+
def break_mot(self, mot_list, cid):
|
| 204 |
+
new_mot_list = dict()
|
| 205 |
+
new_num_tracklets = max(mot_list) + 1
|
| 206 |
+
for tracklet in mot_list:
|
| 207 |
+
tracklet_dict = mot_list[tracklet]
|
| 208 |
+
frame_list = list(tracklet_dict.keys())
|
| 209 |
+
frame_list.sort()
|
| 210 |
+
zone_list = []
|
| 211 |
+
back_tracklet = False
|
| 212 |
+
new_zone_f = 0
|
| 213 |
+
pre_frame = frame_list[0]
|
| 214 |
+
time_break = False
|
| 215 |
+
for f in frame_list:
|
| 216 |
+
if f - pre_frame > 100:
|
| 217 |
+
if cid in [44, 45]:
|
| 218 |
+
time_break = True
|
| 219 |
+
break
|
| 220 |
+
if not cid in [41, 44, 45, 46]:
|
| 221 |
+
break
|
| 222 |
+
pre_frame = f
|
| 223 |
+
new_zone = tracklet_dict[f]['zone']
|
| 224 |
+
if len(zone_list) > 0 and zone_list[-1] == new_zone:
|
| 225 |
+
continue
|
| 226 |
+
if new_zone_f > 1:
|
| 227 |
+
if len(zone_list) > 1 and new_zone in zone_list:
|
| 228 |
+
back_tracklet = True
|
| 229 |
+
zone_list.append(new_zone)
|
| 230 |
+
new_zone_f = 0
|
| 231 |
+
else:
|
| 232 |
+
new_zone_f += 1
|
| 233 |
+
if back_tracklet:
|
| 234 |
+
new_tracklet_dict = dict()
|
| 235 |
+
pre_bbox = -1
|
| 236 |
+
pre_arrow = 0
|
| 237 |
+
have_break = False
|
| 238 |
+
for f in frame_list:
|
| 239 |
+
now_bbox = tracklet_dict[f]['bbox']
|
| 240 |
+
if type(pre_bbox) == int:
|
| 241 |
+
if pre_bbox == -1:
|
| 242 |
+
pre_bbox = now_bbox
|
| 243 |
+
now_arrow = now_bbox[0] - pre_bbox[0]
|
| 244 |
+
if pre_arrow * now_arrow < 0 and len(
|
| 245 |
+
new_tracklet_dict) > 15 and not have_break:
|
| 246 |
+
new_mot_list[tracklet] = new_tracklet_dict
|
| 247 |
+
new_tracklet_dict = dict()
|
| 248 |
+
have_break = True
|
| 249 |
+
if have_break:
|
| 250 |
+
tracklet_dict[f]['id'] = new_num_tracklets
|
| 251 |
+
new_tracklet_dict[f] = tracklet_dict[f]
|
| 252 |
+
pre_bbox, pre_arrow = now_bbox, now_arrow
|
| 253 |
+
|
| 254 |
+
if have_break:
|
| 255 |
+
new_mot_list[new_num_tracklets] = new_tracklet_dict
|
| 256 |
+
new_num_tracklets += 1
|
| 257 |
+
else:
|
| 258 |
+
new_mot_list[tracklet] = new_tracklet_dict
|
| 259 |
+
elif time_break:
|
| 260 |
+
new_tracklet_dict = dict()
|
| 261 |
+
have_break = False
|
| 262 |
+
pre_frame = frame_list[0]
|
| 263 |
+
for f in frame_list:
|
| 264 |
+
if f - pre_frame > 100:
|
| 265 |
+
new_mot_list[tracklet] = new_tracklet_dict
|
| 266 |
+
new_tracklet_dict = dict()
|
| 267 |
+
have_break = True
|
| 268 |
+
new_tracklet_dict[f] = tracklet_dict[f]
|
| 269 |
+
pre_frame = f
|
| 270 |
+
if have_break:
|
| 271 |
+
new_mot_list[new_num_tracklets] = new_tracklet_dict
|
| 272 |
+
new_num_tracklets += 1
|
| 273 |
+
else:
|
| 274 |
+
new_mot_list[tracklet] = new_tracklet_dict
|
| 275 |
+
else:
|
| 276 |
+
new_mot_list[tracklet] = tracklet_dict
|
| 277 |
+
return new_mot_list
|
| 278 |
+
|
| 279 |
+
def intra_matching(self, mot_list, sub_mot_list):
|
| 280 |
+
sub_zone_dict = dict()
|
| 281 |
+
new_mot_list = dict()
|
| 282 |
+
new_mot_list, new_sub_mot_list = self.do_intra_matching2(mot_list,
|
| 283 |
+
sub_mot_list)
|
| 284 |
+
return new_mot_list
|
| 285 |
+
|
| 286 |
+
def do_intra_matching2(self, mot_list, sub_list):
|
| 287 |
+
new_zone_dict = dict()
|
| 288 |
+
|
| 289 |
+
def get_trac_info(tracklet1):
|
| 290 |
+
t1_f = list(tracklet1)
|
| 291 |
+
t1_f.sort()
|
| 292 |
+
t1_fs = t1_f[0]
|
| 293 |
+
t1_fe = t1_f[-1]
|
| 294 |
+
t1_zs = tracklet1[t1_fs]['zone']
|
| 295 |
+
t1_ze = tracklet1[t1_fe]['zone']
|
| 296 |
+
t1_boxs = tracklet1[t1_fs]['bbox']
|
| 297 |
+
t1_boxe = tracklet1[t1_fe]['bbox']
|
| 298 |
+
t1_boxs = [(t1_boxs[2] + t1_boxs[0]) / 2,
|
| 299 |
+
(t1_boxs[3] + t1_boxs[1]) / 2]
|
| 300 |
+
t1_boxe = [(t1_boxe[2] + t1_boxe[0]) / 2,
|
| 301 |
+
(t1_boxe[3] + t1_boxe[1]) / 2]
|
| 302 |
+
return t1_fs, t1_fe, t1_zs, t1_ze, t1_boxs, t1_boxe
|
| 303 |
+
|
| 304 |
+
for t1id in sub_list:
|
| 305 |
+
tracklet1 = sub_list[t1id]
|
| 306 |
+
if tracklet1 == -1:
|
| 307 |
+
continue
|
| 308 |
+
t1_fs, t1_fe, t1_zs, t1_ze, t1_boxs, t1_boxe = get_trac_info(
|
| 309 |
+
tracklet1)
|
| 310 |
+
sim_dict = dict()
|
| 311 |
+
for t2id in mot_list:
|
| 312 |
+
tracklet2 = mot_list[t2id]
|
| 313 |
+
t2_fs, t2_fe, t2_zs, t2_ze, t2_boxs, t2_boxe = get_trac_info(
|
| 314 |
+
tracklet2)
|
| 315 |
+
if t1_ze == t2_zs:
|
| 316 |
+
if abs(t2_fs - t1_fe) < 5 and abs(t2_boxe[0] - t1_boxs[
|
| 317 |
+
0]) < 50 and abs(t2_boxe[1] - t1_boxs[1]) < 50:
|
| 318 |
+
t1_feat = tracklet1[t1_fe]['feat']
|
| 319 |
+
t2_feat = tracklet2[t2_fs]['feat']
|
| 320 |
+
sim_dict[t2id] = np.matmul(t1_feat, t2_feat)
|
| 321 |
+
if t1_zs == t2_ze:
|
| 322 |
+
if abs(t2_fe - t1_fs) < 5 and abs(t2_boxs[0] - t1_boxe[
|
| 323 |
+
0]) < 50 and abs(t2_boxs[1] - t1_boxe[1]) < 50:
|
| 324 |
+
t1_feat = tracklet1[t1_fs]['feat']
|
| 325 |
+
t2_feat = tracklet2[t2_fe]['feat']
|
| 326 |
+
sim_dict[t2id] = np.matmul(t1_feat, t2_feat)
|
| 327 |
+
if len(sim_dict) > 0:
|
| 328 |
+
max_sim = 0
|
| 329 |
+
max_id = 0
|
| 330 |
+
for t2id in sim_dict:
|
| 331 |
+
if sim_dict[t2id] > max_sim:
|
| 332 |
+
sim_dict[t2id] = max_sim
|
| 333 |
+
max_id = t2id
|
| 334 |
+
if max_sim > 0.5:
|
| 335 |
+
t2 = mot_list[max_id]
|
| 336 |
+
for t1f in tracklet1:
|
| 337 |
+
if t1f not in t2:
|
| 338 |
+
tracklet1[t1f]['id'] = max_id
|
| 339 |
+
t2[t1f] = tracklet1[t1f]
|
| 340 |
+
mot_list[max_id] = t2
|
| 341 |
+
sub_list[t1id] = -1
|
| 342 |
+
return mot_list, sub_list
|
| 343 |
+
|
| 344 |
+
def do_intra_matching(self, sub_zone_dict, sub_zone):
|
| 345 |
+
new_zone_dict = dict()
|
| 346 |
+
id_list = list(sub_zone_dict)
|
| 347 |
+
id2index = dict()
|
| 348 |
+
for index, id in enumerate(id_list):
|
| 349 |
+
id2index[id] = index
|
| 350 |
+
|
| 351 |
+
def get_trac_info(tracklet1):
|
| 352 |
+
t1_f = list(tracklet1)
|
| 353 |
+
t1_f.sort()
|
| 354 |
+
t1_fs = t1_f[0]
|
| 355 |
+
t1_fe = t1_f[-1]
|
| 356 |
+
t1_zs = tracklet1[t1_fs]['zone']
|
| 357 |
+
t1_ze = tracklet1[t1_fe]['zone']
|
| 358 |
+
t1_boxs = tracklet1[t1_fs]['bbox']
|
| 359 |
+
t1_boxe = tracklet1[t1_fe]['bbox']
|
| 360 |
+
t1_boxs = [(t1_boxs[2] + t1_boxs[0]) / 2,
|
| 361 |
+
(t1_boxs[3] + t1_boxs[1]) / 2]
|
| 362 |
+
t1_boxe = [(t1_boxe[2] + t1_boxe[0]) / 2,
|
| 363 |
+
(t1_boxe[3] + t1_boxe[1]) / 2]
|
| 364 |
+
return t1_fs, t1_fe, t1_zs, t1_ze, t1_boxs, t1_boxe
|
| 365 |
+
|
| 366 |
+
sim_matrix = np.zeros([len(id_list), len(id_list)])
|
| 367 |
+
|
| 368 |
+
for t1id in sub_zone_dict:
|
| 369 |
+
tracklet1 = sub_zone_dict[t1id]
|
| 370 |
+
t1_fs, t1_fe, t1_zs, t1_ze, t1_boxs, t1_boxe = get_trac_info(
|
| 371 |
+
tracklet1)
|
| 372 |
+
t1_feat = tracklet1[t1_fe]['feat']
|
| 373 |
+
for t2id in sub_zone_dict:
|
| 374 |
+
if t1id == t2id:
|
| 375 |
+
continue
|
| 376 |
+
tracklet2 = sub_zone_dict[t2id]
|
| 377 |
+
t2_fs, t2_fe, t2_zs, t2_ze, t2_boxs, t2_boxe = get_trac_info(
|
| 378 |
+
tracklet2)
|
| 379 |
+
if t1_zs != t1_ze and t2_ze != t2_zs or t1_fe > t2_fs:
|
| 380 |
+
continue
|
| 381 |
+
if abs(t1_boxe[0] - t2_boxs[0]) > 50 or abs(t1_boxe[1] -
|
| 382 |
+
t2_boxs[1]) > 50:
|
| 383 |
+
continue
|
| 384 |
+
if t2_fs - t1_fe > 5:
|
| 385 |
+
continue
|
| 386 |
+
t2_feat = tracklet2[t2_fs]['feat']
|
| 387 |
+
sim_matrix[id2index[t1id], id2index[t2id]] = np.matmul(t1_feat,
|
| 388 |
+
t2_feat)
|
| 389 |
+
sim_matrix[id2index[t2id], id2index[t1id]] = np.matmul(t1_feat,
|
| 390 |
+
t2_feat)
|
| 391 |
+
sim_matrix = 1 - sim_matrix
|
| 392 |
+
cluster_labels = AgglomerativeClustering(
|
| 393 |
+
n_clusters=None,
|
| 394 |
+
distance_threshold=0.7,
|
| 395 |
+
affinity='precomputed',
|
| 396 |
+
linkage='complete').fit_predict(sim_matrix)
|
| 397 |
+
new_zone_dict = dict()
|
| 398 |
+
label2id = dict()
|
| 399 |
+
for index, label in enumerate(cluster_labels):
|
| 400 |
+
tracklet = sub_zone_dict[id_list[index]]
|
| 401 |
+
if label not in label2id:
|
| 402 |
+
new_id = tracklet[list(tracklet)[0]]
|
| 403 |
+
new_tracklet = dict()
|
| 404 |
+
else:
|
| 405 |
+
new_id = label2id[label]
|
| 406 |
+
new_tracklet = new_zone_dict[label2id[label]]
|
| 407 |
+
for tf in tracklet:
|
| 408 |
+
tracklet[tf]['id'] = new_id
|
| 409 |
+
new_tracklet[tf] = tracklet[tf]
|
| 410 |
+
new_zone_dict[label] = new_tracklet
|
| 411 |
+
|
| 412 |
+
return new_zone_dict
|
pptracking/python/mot/tracker/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
from . import base_jde_tracker
|
| 16 |
+
from . import base_sde_tracker
|
| 17 |
+
from . import jde_tracker
|
| 18 |
+
from . import deepsort_tracker
|
| 19 |
+
from . import ocsort_tracker
|
| 20 |
+
|
| 21 |
+
from .base_jde_tracker import *
|
| 22 |
+
from .base_sde_tracker import *
|
| 23 |
+
from .jde_tracker import *
|
| 24 |
+
from .deepsort_tracker import *
|
| 25 |
+
from .ocsort_tracker import *
|
pptracking/python/mot/tracker/base_jde_tracker.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/Zhongdao/Towards-Realtime-MOT/blob/master/tracker/multitracker.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
from collections import defaultdict
|
| 20 |
+
from collections import deque, OrderedDict
|
| 21 |
+
from ..matching import jde_matching as matching
|
| 22 |
+
|
| 23 |
+
__all__ = [
|
| 24 |
+
'TrackState',
|
| 25 |
+
'BaseTrack',
|
| 26 |
+
'STrack',
|
| 27 |
+
'joint_stracks',
|
| 28 |
+
'sub_stracks',
|
| 29 |
+
'remove_duplicate_stracks',
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class TrackState(object):
|
| 34 |
+
New = 0
|
| 35 |
+
Tracked = 1
|
| 36 |
+
Lost = 2
|
| 37 |
+
Removed = 3
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class BaseTrack(object):
|
| 41 |
+
_count_dict = defaultdict(int) # support single class and multi classes
|
| 42 |
+
|
| 43 |
+
track_id = 0
|
| 44 |
+
is_activated = False
|
| 45 |
+
state = TrackState.New
|
| 46 |
+
|
| 47 |
+
history = OrderedDict()
|
| 48 |
+
features = []
|
| 49 |
+
curr_feat = None
|
| 50 |
+
score = 0
|
| 51 |
+
start_frame = 0
|
| 52 |
+
frame_id = 0
|
| 53 |
+
time_since_update = 0
|
| 54 |
+
|
| 55 |
+
# multi-camera
|
| 56 |
+
location = (np.inf, np.inf)
|
| 57 |
+
|
| 58 |
+
@property
|
| 59 |
+
def end_frame(self):
|
| 60 |
+
return self.frame_id
|
| 61 |
+
|
| 62 |
+
@staticmethod
|
| 63 |
+
def next_id(cls_id):
|
| 64 |
+
BaseTrack._count_dict[cls_id] += 1
|
| 65 |
+
return BaseTrack._count_dict[cls_id]
|
| 66 |
+
|
| 67 |
+
# @even: reset track id
|
| 68 |
+
@staticmethod
|
| 69 |
+
def init_count(num_classes):
|
| 70 |
+
"""
|
| 71 |
+
Initiate _count for all object classes
|
| 72 |
+
:param num_classes:
|
| 73 |
+
"""
|
| 74 |
+
for cls_id in range(num_classes):
|
| 75 |
+
BaseTrack._count_dict[cls_id] = 0
|
| 76 |
+
|
| 77 |
+
@staticmethod
|
| 78 |
+
def reset_track_count(cls_id):
|
| 79 |
+
BaseTrack._count_dict[cls_id] = 0
|
| 80 |
+
|
| 81 |
+
def activate(self, *args):
|
| 82 |
+
raise NotImplementedError
|
| 83 |
+
|
| 84 |
+
def predict(self):
|
| 85 |
+
raise NotImplementedError
|
| 86 |
+
|
| 87 |
+
def update(self, *args, **kwargs):
|
| 88 |
+
raise NotImplementedError
|
| 89 |
+
|
| 90 |
+
def mark_lost(self):
|
| 91 |
+
self.state = TrackState.Lost
|
| 92 |
+
|
| 93 |
+
def mark_removed(self):
|
| 94 |
+
self.state = TrackState.Removed
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class STrack(BaseTrack):
|
| 98 |
+
def __init__(self, tlwh, score, cls_id, buff_size=30, temp_feat=None):
|
| 99 |
+
# wait activate
|
| 100 |
+
self._tlwh = np.asarray(tlwh, dtype=np.float)
|
| 101 |
+
self.score = score
|
| 102 |
+
self.cls_id = cls_id
|
| 103 |
+
self.track_len = 0
|
| 104 |
+
|
| 105 |
+
self.kalman_filter = None
|
| 106 |
+
self.mean, self.covariance = None, None
|
| 107 |
+
self.is_activated = False
|
| 108 |
+
|
| 109 |
+
self.use_reid = True if temp_feat is not None else False
|
| 110 |
+
if self.use_reid:
|
| 111 |
+
self.smooth_feat = None
|
| 112 |
+
self.update_features(temp_feat)
|
| 113 |
+
self.features = deque([], maxlen=buff_size)
|
| 114 |
+
self.alpha = 0.9
|
| 115 |
+
|
| 116 |
+
def update_features(self, feat):
|
| 117 |
+
# L2 normalizing, this function has no use for BYTETracker
|
| 118 |
+
feat /= np.linalg.norm(feat)
|
| 119 |
+
self.curr_feat = feat
|
| 120 |
+
if self.smooth_feat is None:
|
| 121 |
+
self.smooth_feat = feat
|
| 122 |
+
else:
|
| 123 |
+
self.smooth_feat = self.alpha * self.smooth_feat + (
|
| 124 |
+
1.0 - self.alpha) * feat
|
| 125 |
+
self.features.append(feat)
|
| 126 |
+
self.smooth_feat /= np.linalg.norm(self.smooth_feat)
|
| 127 |
+
|
| 128 |
+
def predict(self):
|
| 129 |
+
mean_state = self.mean.copy()
|
| 130 |
+
if self.state != TrackState.Tracked:
|
| 131 |
+
mean_state[7] = 0
|
| 132 |
+
self.mean, self.covariance = self.kalman_filter.predict(
|
| 133 |
+
mean_state, self.covariance)
|
| 134 |
+
|
| 135 |
+
@staticmethod
|
| 136 |
+
def multi_predict(tracks, kalman_filter):
|
| 137 |
+
if len(tracks) > 0:
|
| 138 |
+
multi_mean = np.asarray([track.mean.copy() for track in tracks])
|
| 139 |
+
multi_covariance = np.asarray(
|
| 140 |
+
[track.covariance for track in tracks])
|
| 141 |
+
for i, st in enumerate(tracks):
|
| 142 |
+
if st.state != TrackState.Tracked:
|
| 143 |
+
multi_mean[i][7] = 0
|
| 144 |
+
multi_mean, multi_covariance = kalman_filter.multi_predict(
|
| 145 |
+
multi_mean, multi_covariance)
|
| 146 |
+
for i, (mean, cov) in enumerate(zip(multi_mean, multi_covariance)):
|
| 147 |
+
tracks[i].mean = mean
|
| 148 |
+
tracks[i].covariance = cov
|
| 149 |
+
|
| 150 |
+
def reset_track_id(self):
|
| 151 |
+
self.reset_track_count(self.cls_id)
|
| 152 |
+
|
| 153 |
+
def activate(self, kalman_filter, frame_id):
|
| 154 |
+
"""Start a new track"""
|
| 155 |
+
self.kalman_filter = kalman_filter
|
| 156 |
+
# update track id for the object class
|
| 157 |
+
self.track_id = self.next_id(self.cls_id)
|
| 158 |
+
self.mean, self.covariance = self.kalman_filter.initiate(
|
| 159 |
+
self.tlwh_to_xyah(self._tlwh))
|
| 160 |
+
|
| 161 |
+
self.track_len = 0
|
| 162 |
+
self.state = TrackState.Tracked # set flag 'tracked'
|
| 163 |
+
|
| 164 |
+
if frame_id == 1: # to record the first frame's detection result
|
| 165 |
+
self.is_activated = True
|
| 166 |
+
|
| 167 |
+
self.frame_id = frame_id
|
| 168 |
+
self.start_frame = frame_id
|
| 169 |
+
|
| 170 |
+
def re_activate(self, new_track, frame_id, new_id=False):
|
| 171 |
+
self.mean, self.covariance = self.kalman_filter.update(
|
| 172 |
+
self.mean, self.covariance, self.tlwh_to_xyah(new_track.tlwh))
|
| 173 |
+
if self.use_reid:
|
| 174 |
+
self.update_features(new_track.curr_feat)
|
| 175 |
+
self.track_len = 0
|
| 176 |
+
self.state = TrackState.Tracked
|
| 177 |
+
self.is_activated = True
|
| 178 |
+
self.frame_id = frame_id
|
| 179 |
+
if new_id: # update track id for the object class
|
| 180 |
+
self.track_id = self.next_id(self.cls_id)
|
| 181 |
+
|
| 182 |
+
def update(self, new_track, frame_id, update_feature=True):
|
| 183 |
+
self.frame_id = frame_id
|
| 184 |
+
self.track_len += 1
|
| 185 |
+
|
| 186 |
+
new_tlwh = new_track.tlwh
|
| 187 |
+
self.mean, self.covariance = self.kalman_filter.update(
|
| 188 |
+
self.mean, self.covariance, self.tlwh_to_xyah(new_tlwh))
|
| 189 |
+
self.state = TrackState.Tracked # set flag 'tracked'
|
| 190 |
+
self.is_activated = True # set flag 'activated'
|
| 191 |
+
|
| 192 |
+
self.score = new_track.score
|
| 193 |
+
if update_feature and self.use_reid:
|
| 194 |
+
self.update_features(new_track.curr_feat)
|
| 195 |
+
|
| 196 |
+
@property
|
| 197 |
+
def tlwh(self):
|
| 198 |
+
"""Get current position in bounding box format `(top left x, top left y,
|
| 199 |
+
width, height)`.
|
| 200 |
+
"""
|
| 201 |
+
if self.mean is None:
|
| 202 |
+
return self._tlwh.copy()
|
| 203 |
+
|
| 204 |
+
ret = self.mean[:4].copy()
|
| 205 |
+
ret[2] *= ret[3]
|
| 206 |
+
ret[:2] -= ret[2:] / 2
|
| 207 |
+
return ret
|
| 208 |
+
|
| 209 |
+
@property
|
| 210 |
+
def tlbr(self):
|
| 211 |
+
"""Convert bounding box to format `(min x, min y, max x, max y)`, i.e.,
|
| 212 |
+
`(top left, bottom right)`.
|
| 213 |
+
"""
|
| 214 |
+
ret = self.tlwh.copy()
|
| 215 |
+
ret[2:] += ret[:2]
|
| 216 |
+
return ret
|
| 217 |
+
|
| 218 |
+
@staticmethod
|
| 219 |
+
def tlwh_to_xyah(tlwh):
|
| 220 |
+
"""Convert bounding box to format `(center x, center y, aspect ratio,
|
| 221 |
+
height)`, where the aspect ratio is `width / height`.
|
| 222 |
+
"""
|
| 223 |
+
ret = np.asarray(tlwh).copy()
|
| 224 |
+
ret[:2] += ret[2:] / 2
|
| 225 |
+
ret[2] /= ret[3]
|
| 226 |
+
return ret
|
| 227 |
+
|
| 228 |
+
def to_xyah(self):
|
| 229 |
+
return self.tlwh_to_xyah(self.tlwh)
|
| 230 |
+
|
| 231 |
+
@staticmethod
|
| 232 |
+
def tlbr_to_tlwh(tlbr):
|
| 233 |
+
ret = np.asarray(tlbr).copy()
|
| 234 |
+
ret[2:] -= ret[:2]
|
| 235 |
+
return ret
|
| 236 |
+
|
| 237 |
+
@staticmethod
|
| 238 |
+
def tlwh_to_tlbr(tlwh):
|
| 239 |
+
ret = np.asarray(tlwh).copy()
|
| 240 |
+
ret[2:] += ret[:2]
|
| 241 |
+
return ret
|
| 242 |
+
|
| 243 |
+
def __repr__(self):
|
| 244 |
+
return 'OT_({}-{})_({}-{})'.format(self.cls_id, self.track_id,
|
| 245 |
+
self.start_frame, self.end_frame)
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def joint_stracks(tlista, tlistb):
|
| 249 |
+
exists = {}
|
| 250 |
+
res = []
|
| 251 |
+
for t in tlista:
|
| 252 |
+
exists[t.track_id] = 1
|
| 253 |
+
res.append(t)
|
| 254 |
+
for t in tlistb:
|
| 255 |
+
tid = t.track_id
|
| 256 |
+
if not exists.get(tid, 0):
|
| 257 |
+
exists[tid] = 1
|
| 258 |
+
res.append(t)
|
| 259 |
+
return res
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def sub_stracks(tlista, tlistb):
|
| 263 |
+
stracks = {}
|
| 264 |
+
for t in tlista:
|
| 265 |
+
stracks[t.track_id] = t
|
| 266 |
+
for t in tlistb:
|
| 267 |
+
tid = t.track_id
|
| 268 |
+
if stracks.get(tid, 0):
|
| 269 |
+
del stracks[tid]
|
| 270 |
+
return list(stracks.values())
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def remove_duplicate_stracks(stracksa, stracksb):
|
| 274 |
+
pdist = matching.iou_distance(stracksa, stracksb)
|
| 275 |
+
pairs = np.where(pdist < 0.15)
|
| 276 |
+
dupa, dupb = list(), list()
|
| 277 |
+
for p, q in zip(*pairs):
|
| 278 |
+
timep = stracksa[p].frame_id - stracksa[p].start_frame
|
| 279 |
+
timeq = stracksb[q].frame_id - stracksb[q].start_frame
|
| 280 |
+
if timep > timeq:
|
| 281 |
+
dupb.append(q)
|
| 282 |
+
else:
|
| 283 |
+
dupa.append(p)
|
| 284 |
+
resa = [t for i, t in enumerate(stracksa) if not i in dupa]
|
| 285 |
+
resb = [t for i, t in enumerate(stracksb) if not i in dupb]
|
| 286 |
+
return resa, resb
|
pptracking/python/mot/tracker/base_sde_tracker.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/nwojke/deep_sort/blob/master/deep_sort/track.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import datetime
|
| 19 |
+
|
| 20 |
+
__all__ = ['TrackState', 'Track']
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TrackState(object):
|
| 24 |
+
"""
|
| 25 |
+
Enumeration type for the single target track state. Newly created tracks are
|
| 26 |
+
classified as `tentative` until enough evidence has been collected. Then,
|
| 27 |
+
the track state is changed to `confirmed`. Tracks that are no longer alive
|
| 28 |
+
are classified as `deleted` to mark them for removal from the set of active
|
| 29 |
+
tracks.
|
| 30 |
+
"""
|
| 31 |
+
Tentative = 1
|
| 32 |
+
Confirmed = 2
|
| 33 |
+
Deleted = 3
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class Track(object):
|
| 37 |
+
"""
|
| 38 |
+
A single target track with state space `(x, y, a, h)` and associated
|
| 39 |
+
velocities, where `(x, y)` is the center of the bounding box, `a` is the
|
| 40 |
+
aspect ratio and `h` is the height.
|
| 41 |
+
|
| 42 |
+
Args:
|
| 43 |
+
mean (ndarray): Mean vector of the initial state distribution.
|
| 44 |
+
covariance (ndarray): Covariance matrix of the initial state distribution.
|
| 45 |
+
track_id (int): A unique track identifier.
|
| 46 |
+
n_init (int): Number of consecutive detections before the track is confirmed.
|
| 47 |
+
The track state is set to `Deleted` if a miss occurs within the first
|
| 48 |
+
`n_init` frames.
|
| 49 |
+
max_age (int): The maximum number of consecutive misses before the track
|
| 50 |
+
state is set to `Deleted`.
|
| 51 |
+
cls_id (int): The category id of the tracked box.
|
| 52 |
+
score (float): The confidence score of the tracked box.
|
| 53 |
+
feature (Optional[ndarray]): Feature vector of the detection this track
|
| 54 |
+
originates from. If not None, this feature is added to the `features` cache.
|
| 55 |
+
|
| 56 |
+
Attributes:
|
| 57 |
+
hits (int): Total number of measurement updates.
|
| 58 |
+
age (int): Total number of frames since first occurance.
|
| 59 |
+
time_since_update (int): Total number of frames since last measurement
|
| 60 |
+
update.
|
| 61 |
+
state (TrackState): The current track state.
|
| 62 |
+
features (List[ndarray]): A cache of features. On each measurement update,
|
| 63 |
+
the associated feature vector is added to this list.
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __init__(self,
|
| 67 |
+
mean,
|
| 68 |
+
covariance,
|
| 69 |
+
track_id,
|
| 70 |
+
n_init,
|
| 71 |
+
max_age,
|
| 72 |
+
cls_id,
|
| 73 |
+
score,
|
| 74 |
+
feature=None):
|
| 75 |
+
self.mean = mean
|
| 76 |
+
self.covariance = covariance
|
| 77 |
+
self.track_id = track_id
|
| 78 |
+
self.hits = 1
|
| 79 |
+
self.age = 1
|
| 80 |
+
self.time_since_update = 0
|
| 81 |
+
self.cls_id = cls_id
|
| 82 |
+
self.score = score
|
| 83 |
+
self.start_time = datetime.datetime.now()
|
| 84 |
+
|
| 85 |
+
self.state = TrackState.Tentative
|
| 86 |
+
self.features = []
|
| 87 |
+
self.feat = feature
|
| 88 |
+
if feature is not None:
|
| 89 |
+
self.features.append(feature)
|
| 90 |
+
|
| 91 |
+
self._n_init = n_init
|
| 92 |
+
self._max_age = max_age
|
| 93 |
+
|
| 94 |
+
def to_tlwh(self):
|
| 95 |
+
"""Get position in format `(top left x, top left y, width, height)`."""
|
| 96 |
+
ret = self.mean[:4].copy()
|
| 97 |
+
ret[2] *= ret[3]
|
| 98 |
+
ret[:2] -= ret[2:] / 2
|
| 99 |
+
return ret
|
| 100 |
+
|
| 101 |
+
def to_tlbr(self):
|
| 102 |
+
"""Get position in bounding box format `(min x, miny, max x, max y)`."""
|
| 103 |
+
ret = self.to_tlwh()
|
| 104 |
+
ret[2:] = ret[:2] + ret[2:]
|
| 105 |
+
return ret
|
| 106 |
+
|
| 107 |
+
def predict(self, kalman_filter):
|
| 108 |
+
"""
|
| 109 |
+
Propagate the state distribution to the current time step using a Kalman
|
| 110 |
+
filter prediction step.
|
| 111 |
+
"""
|
| 112 |
+
self.mean, self.covariance = kalman_filter.predict(self.mean,
|
| 113 |
+
self.covariance)
|
| 114 |
+
self.age += 1
|
| 115 |
+
self.time_since_update += 1
|
| 116 |
+
|
| 117 |
+
def update(self, kalman_filter, detection):
|
| 118 |
+
"""
|
| 119 |
+
Perform Kalman filter measurement update step and update the associated
|
| 120 |
+
detection feature cache.
|
| 121 |
+
"""
|
| 122 |
+
self.mean, self.covariance = kalman_filter.update(self.mean,
|
| 123 |
+
self.covariance,
|
| 124 |
+
detection.to_xyah())
|
| 125 |
+
self.features.append(detection.feature)
|
| 126 |
+
self.feat = detection.feature
|
| 127 |
+
self.cls_id = detection.cls_id
|
| 128 |
+
self.score = detection.score
|
| 129 |
+
|
| 130 |
+
self.hits += 1
|
| 131 |
+
self.time_since_update = 0
|
| 132 |
+
if self.state == TrackState.Tentative and self.hits >= self._n_init:
|
| 133 |
+
self.state = TrackState.Confirmed
|
| 134 |
+
|
| 135 |
+
def mark_missed(self):
|
| 136 |
+
"""Mark this track as missed (no association at the current time step).
|
| 137 |
+
"""
|
| 138 |
+
if self.state == TrackState.Tentative:
|
| 139 |
+
self.state = TrackState.Deleted
|
| 140 |
+
elif self.time_since_update > self._max_age:
|
| 141 |
+
self.state = TrackState.Deleted
|
| 142 |
+
|
| 143 |
+
def is_tentative(self):
|
| 144 |
+
"""Returns True if this track is tentative (unconfirmed)."""
|
| 145 |
+
return self.state == TrackState.Tentative
|
| 146 |
+
|
| 147 |
+
def is_confirmed(self):
|
| 148 |
+
"""Returns True if this track is confirmed."""
|
| 149 |
+
return self.state == TrackState.Confirmed
|
| 150 |
+
|
| 151 |
+
def is_deleted(self):
|
| 152 |
+
"""Returns True if this track is dead and should be deleted."""
|
| 153 |
+
return self.state == TrackState.Deleted
|
pptracking/python/mot/tracker/deepsort_tracker.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/nwojke/deep_sort/blob/master/deep_sort/tracker.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
|
| 20 |
+
from ..motion import KalmanFilter
|
| 21 |
+
from ..matching.deepsort_matching import NearestNeighborDistanceMetric
|
| 22 |
+
from ..matching.deepsort_matching import iou_cost, min_cost_matching, matching_cascade, gate_cost_matrix
|
| 23 |
+
from .base_sde_tracker import Track
|
| 24 |
+
from ..utils import Detection
|
| 25 |
+
|
| 26 |
+
__all__ = ['DeepSORTTracker']
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class DeepSORTTracker(object):
|
| 30 |
+
"""
|
| 31 |
+
DeepSORT tracker
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
input_size (list): input feature map size to reid model, [h, w] format,
|
| 35 |
+
[64, 192] as default.
|
| 36 |
+
min_box_area (int): min box area to filter out low quality boxes
|
| 37 |
+
vertical_ratio (float): w/h, the vertical ratio of the bbox to filter
|
| 38 |
+
bad results, set 1.6 default for pedestrian tracking. If set <=0
|
| 39 |
+
means no need to filter bboxes.
|
| 40 |
+
budget (int): If not None, fix samples per class to at most this number.
|
| 41 |
+
Removes the oldest samples when the budget is reached.
|
| 42 |
+
max_age (int): maximum number of missed misses before a track is deleted
|
| 43 |
+
n_init (float): Number of frames that a track remains in initialization
|
| 44 |
+
phase. Number of consecutive detections before the track is confirmed.
|
| 45 |
+
The track state is set to `Deleted` if a miss occurs within the first
|
| 46 |
+
`n_init` frames.
|
| 47 |
+
metric_type (str): either "euclidean" or "cosine", the distance metric
|
| 48 |
+
used for measurement to track association.
|
| 49 |
+
matching_threshold (float): samples with larger distance are
|
| 50 |
+
considered an invalid match.
|
| 51 |
+
max_iou_distance (float): max iou distance threshold
|
| 52 |
+
motion (object): KalmanFilter instance
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
def __init__(self,
|
| 56 |
+
input_size=[64, 192],
|
| 57 |
+
min_box_area=0,
|
| 58 |
+
vertical_ratio=-1,
|
| 59 |
+
budget=100,
|
| 60 |
+
max_age=70,
|
| 61 |
+
n_init=3,
|
| 62 |
+
metric_type='cosine',
|
| 63 |
+
matching_threshold=0.2,
|
| 64 |
+
max_iou_distance=0.9,
|
| 65 |
+
motion='KalmanFilter'):
|
| 66 |
+
self.input_size = input_size
|
| 67 |
+
self.min_box_area = min_box_area
|
| 68 |
+
self.vertical_ratio = vertical_ratio
|
| 69 |
+
self.max_age = max_age
|
| 70 |
+
self.n_init = n_init
|
| 71 |
+
self.metric = NearestNeighborDistanceMetric(metric_type,
|
| 72 |
+
matching_threshold, budget)
|
| 73 |
+
self.max_iou_distance = max_iou_distance
|
| 74 |
+
if motion == 'KalmanFilter':
|
| 75 |
+
self.motion = KalmanFilter()
|
| 76 |
+
|
| 77 |
+
self.tracks = []
|
| 78 |
+
self._next_id = 1
|
| 79 |
+
|
| 80 |
+
def predict(self):
|
| 81 |
+
"""
|
| 82 |
+
Propagate track state distributions one time step forward.
|
| 83 |
+
This function should be called once every time step, before `update`.
|
| 84 |
+
"""
|
| 85 |
+
for track in self.tracks:
|
| 86 |
+
track.predict(self.motion)
|
| 87 |
+
|
| 88 |
+
def update(self, pred_dets, pred_embs):
|
| 89 |
+
"""
|
| 90 |
+
Perform measurement update and track management.
|
| 91 |
+
Args:
|
| 92 |
+
pred_dets (np.array): Detection results of the image, the shape is
|
| 93 |
+
[N, 6], means 'cls_id, score, x0, y0, x1, y1'.
|
| 94 |
+
pred_embs (np.array): Embedding results of the image, the shape is
|
| 95 |
+
[N, 128], usually pred_embs.shape[1] is a multiple of 128.
|
| 96 |
+
"""
|
| 97 |
+
pred_cls_ids = pred_dets[:, 0:1]
|
| 98 |
+
pred_scores = pred_dets[:, 1:2]
|
| 99 |
+
pred_xyxys = pred_dets[:, 2:6]
|
| 100 |
+
pred_tlwhs = np.concatenate(
|
| 101 |
+
(pred_xyxys[:, 0:2], pred_xyxys[:, 2:4] - pred_xyxys[:, 0:2] + 1),
|
| 102 |
+
axis=1)
|
| 103 |
+
|
| 104 |
+
detections = [
|
| 105 |
+
Detection(tlwh, score, feat, cls_id)
|
| 106 |
+
for tlwh, score, feat, cls_id in zip(pred_tlwhs, pred_scores,
|
| 107 |
+
pred_embs, pred_cls_ids)
|
| 108 |
+
]
|
| 109 |
+
|
| 110 |
+
# Run matching cascade.
|
| 111 |
+
matches, unmatched_tracks, unmatched_detections = \
|
| 112 |
+
self._match(detections)
|
| 113 |
+
|
| 114 |
+
# Update track set.
|
| 115 |
+
for track_idx, detection_idx in matches:
|
| 116 |
+
self.tracks[track_idx].update(self.motion,
|
| 117 |
+
detections[detection_idx])
|
| 118 |
+
for track_idx in unmatched_tracks:
|
| 119 |
+
self.tracks[track_idx].mark_missed()
|
| 120 |
+
for detection_idx in unmatched_detections:
|
| 121 |
+
self._initiate_track(detections[detection_idx])
|
| 122 |
+
self.tracks = [t for t in self.tracks if not t.is_deleted()]
|
| 123 |
+
|
| 124 |
+
# Update distance metric.
|
| 125 |
+
active_targets = [t.track_id for t in self.tracks if t.is_confirmed()]
|
| 126 |
+
features, targets = [], []
|
| 127 |
+
for track in self.tracks:
|
| 128 |
+
if not track.is_confirmed():
|
| 129 |
+
continue
|
| 130 |
+
features += track.features
|
| 131 |
+
targets += [track.track_id for _ in track.features]
|
| 132 |
+
track.features = []
|
| 133 |
+
self.metric.partial_fit(
|
| 134 |
+
np.asarray(features), np.asarray(targets), active_targets)
|
| 135 |
+
output_stracks = self.tracks
|
| 136 |
+
return output_stracks
|
| 137 |
+
|
| 138 |
+
def _match(self, detections):
|
| 139 |
+
def gated_metric(tracks, dets, track_indices, detection_indices):
|
| 140 |
+
features = np.array([dets[i].feature for i in detection_indices])
|
| 141 |
+
targets = np.array([tracks[i].track_id for i in track_indices])
|
| 142 |
+
cost_matrix = self.metric.distance(features, targets)
|
| 143 |
+
cost_matrix = gate_cost_matrix(self.motion, cost_matrix, tracks,
|
| 144 |
+
dets, track_indices,
|
| 145 |
+
detection_indices)
|
| 146 |
+
return cost_matrix
|
| 147 |
+
|
| 148 |
+
# Split track set into confirmed and unconfirmed tracks.
|
| 149 |
+
confirmed_tracks = [
|
| 150 |
+
i for i, t in enumerate(self.tracks) if t.is_confirmed()
|
| 151 |
+
]
|
| 152 |
+
unconfirmed_tracks = [
|
| 153 |
+
i for i, t in enumerate(self.tracks) if not t.is_confirmed()
|
| 154 |
+
]
|
| 155 |
+
|
| 156 |
+
# Associate confirmed tracks using appearance features.
|
| 157 |
+
matches_a, unmatched_tracks_a, unmatched_detections = \
|
| 158 |
+
matching_cascade(
|
| 159 |
+
gated_metric, self.metric.matching_threshold, self.max_age,
|
| 160 |
+
self.tracks, detections, confirmed_tracks)
|
| 161 |
+
|
| 162 |
+
# Associate remaining tracks together with unconfirmed tracks using IOU.
|
| 163 |
+
iou_track_candidates = unconfirmed_tracks + [
|
| 164 |
+
k for k in unmatched_tracks_a
|
| 165 |
+
if self.tracks[k].time_since_update == 1
|
| 166 |
+
]
|
| 167 |
+
unmatched_tracks_a = [
|
| 168 |
+
k for k in unmatched_tracks_a
|
| 169 |
+
if self.tracks[k].time_since_update != 1
|
| 170 |
+
]
|
| 171 |
+
matches_b, unmatched_tracks_b, unmatched_detections = \
|
| 172 |
+
min_cost_matching(
|
| 173 |
+
iou_cost, self.max_iou_distance, self.tracks,
|
| 174 |
+
detections, iou_track_candidates, unmatched_detections)
|
| 175 |
+
|
| 176 |
+
matches = matches_a + matches_b
|
| 177 |
+
unmatched_tracks = list(set(unmatched_tracks_a + unmatched_tracks_b))
|
| 178 |
+
return matches, unmatched_tracks, unmatched_detections
|
| 179 |
+
|
| 180 |
+
def _initiate_track(self, detection):
|
| 181 |
+
mean, covariance = self.motion.initiate(detection.to_xyah())
|
| 182 |
+
self.tracks.append(
|
| 183 |
+
Track(mean, covariance, self._next_id, self.n_init, self.max_age,
|
| 184 |
+
detection.cls_id, detection.score, detection.feature))
|
| 185 |
+
self._next_id += 1
|
pptracking/python/mot/tracker/jde_tracker.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/Zhongdao/Towards-Realtime-MOT/blob/master/tracker/multitracker.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
from collections import defaultdict
|
| 20 |
+
|
| 21 |
+
from ..matching import jde_matching as matching
|
| 22 |
+
from ..motion import KalmanFilter
|
| 23 |
+
from .base_jde_tracker import TrackState, STrack
|
| 24 |
+
from .base_jde_tracker import joint_stracks, sub_stracks, remove_duplicate_stracks
|
| 25 |
+
|
| 26 |
+
__all__ = ['JDETracker']
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class JDETracker(object):
|
| 30 |
+
__shared__ = ['num_classes']
|
| 31 |
+
"""
|
| 32 |
+
JDE tracker, support single class and multi classes
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
use_byte (bool): Whether use ByteTracker, default False
|
| 36 |
+
num_classes (int): the number of classes
|
| 37 |
+
det_thresh (float): threshold of detection score
|
| 38 |
+
track_buffer (int): buffer for tracker
|
| 39 |
+
min_box_area (int): min box area to filter out low quality boxes
|
| 40 |
+
vertical_ratio (float): w/h, the vertical ratio of the bbox to filter
|
| 41 |
+
bad results. If set <= 0 means no need to filter bboxes,usually set
|
| 42 |
+
1.6 for pedestrian tracking.
|
| 43 |
+
tracked_thresh (float): linear assignment threshold of tracked
|
| 44 |
+
stracks and detections
|
| 45 |
+
r_tracked_thresh (float): linear assignment threshold of
|
| 46 |
+
tracked stracks and unmatched detections
|
| 47 |
+
unconfirmed_thresh (float): linear assignment threshold of
|
| 48 |
+
unconfirmed stracks and unmatched detections
|
| 49 |
+
conf_thres (float): confidence threshold for tracking, also used in
|
| 50 |
+
ByteTracker as higher confidence threshold
|
| 51 |
+
match_thres (float): linear assignment threshold of tracked
|
| 52 |
+
stracks and detections in ByteTracker
|
| 53 |
+
low_conf_thres (float): lower confidence threshold for tracking in
|
| 54 |
+
ByteTracker
|
| 55 |
+
input_size (list): input feature map size to reid model, [h, w] format,
|
| 56 |
+
[64, 192] as default.
|
| 57 |
+
motion (str): motion model, KalmanFilter as default
|
| 58 |
+
metric_type (str): either "euclidean" or "cosine", the distance metric
|
| 59 |
+
used for measurement to track association.
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
def __init__(self,
|
| 63 |
+
use_byte=False,
|
| 64 |
+
num_classes=1,
|
| 65 |
+
det_thresh=0.3,
|
| 66 |
+
track_buffer=30,
|
| 67 |
+
min_box_area=0,
|
| 68 |
+
vertical_ratio=0,
|
| 69 |
+
tracked_thresh=0.7,
|
| 70 |
+
r_tracked_thresh=0.5,
|
| 71 |
+
unconfirmed_thresh=0.7,
|
| 72 |
+
conf_thres=0,
|
| 73 |
+
match_thres=0.8,
|
| 74 |
+
low_conf_thres=0.2,
|
| 75 |
+
input_size=[64, 192],
|
| 76 |
+
motion='KalmanFilter',
|
| 77 |
+
metric_type='euclidean'):
|
| 78 |
+
self.use_byte = use_byte
|
| 79 |
+
self.num_classes = num_classes
|
| 80 |
+
self.det_thresh = det_thresh if not use_byte else conf_thres + 0.1
|
| 81 |
+
self.track_buffer = track_buffer
|
| 82 |
+
self.min_box_area = min_box_area
|
| 83 |
+
self.vertical_ratio = vertical_ratio
|
| 84 |
+
|
| 85 |
+
self.tracked_thresh = tracked_thresh
|
| 86 |
+
self.r_tracked_thresh = r_tracked_thresh
|
| 87 |
+
self.unconfirmed_thresh = unconfirmed_thresh
|
| 88 |
+
self.conf_thres = conf_thres
|
| 89 |
+
self.match_thres = match_thres
|
| 90 |
+
self.low_conf_thres = low_conf_thres
|
| 91 |
+
|
| 92 |
+
self.input_size = input_size
|
| 93 |
+
if motion == 'KalmanFilter':
|
| 94 |
+
self.motion = KalmanFilter()
|
| 95 |
+
self.metric_type = metric_type
|
| 96 |
+
|
| 97 |
+
self.frame_id = 0
|
| 98 |
+
self.tracked_tracks_dict = defaultdict(list) # dict(list[STrack])
|
| 99 |
+
self.lost_tracks_dict = defaultdict(list) # dict(list[STrack])
|
| 100 |
+
self.removed_tracks_dict = defaultdict(list) # dict(list[STrack])
|
| 101 |
+
|
| 102 |
+
self.max_time_lost = 0
|
| 103 |
+
# max_time_lost will be calculated: int(frame_rate / 30.0 * track_buffer)
|
| 104 |
+
|
| 105 |
+
def update(self, pred_dets, pred_embs=None):
|
| 106 |
+
"""
|
| 107 |
+
Processes the image frame and finds bounding box(detections).
|
| 108 |
+
Associates the detection with corresponding tracklets and also handles
|
| 109 |
+
lost, removed, refound and active tracklets.
|
| 110 |
+
|
| 111 |
+
Args:
|
| 112 |
+
pred_dets (np.array): Detection results of the image, the shape is
|
| 113 |
+
[N, 6], means 'cls_id, score, x0, y0, x1, y1'.
|
| 114 |
+
pred_embs (np.array): Embedding results of the image, the shape is
|
| 115 |
+
[N, 128] or [N, 512].
|
| 116 |
+
|
| 117 |
+
Return:
|
| 118 |
+
output_stracks_dict (dict(list)): The list contains information
|
| 119 |
+
regarding the online_tracklets for the received image tensor.
|
| 120 |
+
"""
|
| 121 |
+
self.frame_id += 1
|
| 122 |
+
if self.frame_id == 1:
|
| 123 |
+
STrack.init_count(self.num_classes)
|
| 124 |
+
activated_tracks_dict = defaultdict(list)
|
| 125 |
+
refined_tracks_dict = defaultdict(list)
|
| 126 |
+
lost_tracks_dict = defaultdict(list)
|
| 127 |
+
removed_tracks_dict = defaultdict(list)
|
| 128 |
+
output_tracks_dict = defaultdict(list)
|
| 129 |
+
|
| 130 |
+
pred_dets_dict = defaultdict(list)
|
| 131 |
+
pred_embs_dict = defaultdict(list)
|
| 132 |
+
|
| 133 |
+
# unify single and multi classes detection and embedding results
|
| 134 |
+
for cls_id in range(self.num_classes):
|
| 135 |
+
cls_idx = (pred_dets[:, 0:1] == cls_id).squeeze(-1)
|
| 136 |
+
pred_dets_dict[cls_id] = pred_dets[cls_idx]
|
| 137 |
+
if pred_embs is not None:
|
| 138 |
+
pred_embs_dict[cls_id] = pred_embs[cls_idx]
|
| 139 |
+
else:
|
| 140 |
+
pred_embs_dict[cls_id] = None
|
| 141 |
+
|
| 142 |
+
for cls_id in range(self.num_classes):
|
| 143 |
+
""" Step 1: Get detections by class"""
|
| 144 |
+
pred_dets_cls = pred_dets_dict[cls_id]
|
| 145 |
+
pred_embs_cls = pred_embs_dict[cls_id]
|
| 146 |
+
remain_inds = (pred_dets_cls[:, 1:2] > self.conf_thres).squeeze(-1)
|
| 147 |
+
if remain_inds.sum() > 0:
|
| 148 |
+
pred_dets_cls = pred_dets_cls[remain_inds]
|
| 149 |
+
if pred_embs_cls is None:
|
| 150 |
+
# in original ByteTrack
|
| 151 |
+
detections = [
|
| 152 |
+
STrack(
|
| 153 |
+
STrack.tlbr_to_tlwh(tlbrs[2:6]),
|
| 154 |
+
tlbrs[1],
|
| 155 |
+
cls_id,
|
| 156 |
+
30,
|
| 157 |
+
temp_feat=None) for tlbrs in pred_dets_cls
|
| 158 |
+
]
|
| 159 |
+
else:
|
| 160 |
+
pred_embs_cls = pred_embs_cls[remain_inds]
|
| 161 |
+
detections = [
|
| 162 |
+
STrack(
|
| 163 |
+
STrack.tlbr_to_tlwh(tlbrs[2:6]), tlbrs[1], cls_id,
|
| 164 |
+
30, temp_feat)
|
| 165 |
+
for (tlbrs, temp_feat
|
| 166 |
+
) in zip(pred_dets_cls, pred_embs_cls)
|
| 167 |
+
]
|
| 168 |
+
else:
|
| 169 |
+
detections = []
|
| 170 |
+
''' Add newly detected tracklets to tracked_stracks'''
|
| 171 |
+
unconfirmed_dict = defaultdict(list)
|
| 172 |
+
tracked_tracks_dict = defaultdict(list)
|
| 173 |
+
for track in self.tracked_tracks_dict[cls_id]:
|
| 174 |
+
if not track.is_activated:
|
| 175 |
+
# previous tracks which are not active in the current frame are added in unconfirmed list
|
| 176 |
+
unconfirmed_dict[cls_id].append(track)
|
| 177 |
+
else:
|
| 178 |
+
# Active tracks are added to the local list 'tracked_stracks'
|
| 179 |
+
tracked_tracks_dict[cls_id].append(track)
|
| 180 |
+
""" Step 2: First association, with embedding"""
|
| 181 |
+
# building tracking pool for the current frame
|
| 182 |
+
track_pool_dict = defaultdict(list)
|
| 183 |
+
track_pool_dict[cls_id] = joint_stracks(
|
| 184 |
+
tracked_tracks_dict[cls_id], self.lost_tracks_dict[cls_id])
|
| 185 |
+
|
| 186 |
+
# Predict the current location with KalmanFilter
|
| 187 |
+
STrack.multi_predict(track_pool_dict[cls_id], self.motion)
|
| 188 |
+
|
| 189 |
+
if pred_embs_cls is None:
|
| 190 |
+
# in original ByteTrack
|
| 191 |
+
dists = matching.iou_distance(track_pool_dict[cls_id],
|
| 192 |
+
detections)
|
| 193 |
+
matches, u_track, u_detection = matching.linear_assignment(
|
| 194 |
+
dists, thresh=self.match_thres) # not self.tracked_thresh
|
| 195 |
+
else:
|
| 196 |
+
dists = matching.embedding_distance(
|
| 197 |
+
track_pool_dict[cls_id],
|
| 198 |
+
detections,
|
| 199 |
+
metric=self.metric_type)
|
| 200 |
+
dists = matching.fuse_motion(
|
| 201 |
+
self.motion, dists, track_pool_dict[cls_id], detections)
|
| 202 |
+
matches, u_track, u_detection = matching.linear_assignment(
|
| 203 |
+
dists, thresh=self.tracked_thresh)
|
| 204 |
+
|
| 205 |
+
for i_tracked, idet in matches:
|
| 206 |
+
# i_tracked is the id of the track and idet is the detection
|
| 207 |
+
track = track_pool_dict[cls_id][i_tracked]
|
| 208 |
+
det = detections[idet]
|
| 209 |
+
if track.state == TrackState.Tracked:
|
| 210 |
+
# If the track is active, add the detection to the track
|
| 211 |
+
track.update(detections[idet], self.frame_id)
|
| 212 |
+
activated_tracks_dict[cls_id].append(track)
|
| 213 |
+
else:
|
| 214 |
+
# We have obtained a detection from a track which is not active,
|
| 215 |
+
# hence put the track in refind_stracks list
|
| 216 |
+
track.re_activate(det, self.frame_id, new_id=False)
|
| 217 |
+
refined_tracks_dict[cls_id].append(track)
|
| 218 |
+
|
| 219 |
+
# None of the steps below happen if there are no undetected tracks.
|
| 220 |
+
""" Step 3: Second association, with IOU"""
|
| 221 |
+
if self.use_byte:
|
| 222 |
+
inds_low = pred_dets_dict[cls_id][:, 1:2] > self.low_conf_thres
|
| 223 |
+
inds_high = pred_dets_dict[cls_id][:, 1:2] < self.conf_thres
|
| 224 |
+
inds_second = np.logical_and(inds_low, inds_high).squeeze(-1)
|
| 225 |
+
pred_dets_cls_second = pred_dets_dict[cls_id][inds_second]
|
| 226 |
+
|
| 227 |
+
# association the untrack to the low score detections
|
| 228 |
+
if len(pred_dets_cls_second) > 0:
|
| 229 |
+
if pred_embs_dict[cls_id] is None:
|
| 230 |
+
# in original ByteTrack
|
| 231 |
+
detections_second = [
|
| 232 |
+
STrack(
|
| 233 |
+
STrack.tlbr_to_tlwh(tlbrs[2:6]),
|
| 234 |
+
tlbrs[1],
|
| 235 |
+
cls_id,
|
| 236 |
+
30,
|
| 237 |
+
temp_feat=None)
|
| 238 |
+
for tlbrs in pred_dets_cls_second
|
| 239 |
+
]
|
| 240 |
+
else:
|
| 241 |
+
pred_embs_cls_second = pred_embs_dict[cls_id][
|
| 242 |
+
inds_second]
|
| 243 |
+
detections_second = [
|
| 244 |
+
STrack(
|
| 245 |
+
STrack.tlbr_to_tlwh(tlbrs[2:6]), tlbrs[1],
|
| 246 |
+
cls_id, 30, temp_feat)
|
| 247 |
+
for (tlbrs, temp_feat) in zip(pred_dets_cls_second,
|
| 248 |
+
pred_embs_cls_second)
|
| 249 |
+
]
|
| 250 |
+
else:
|
| 251 |
+
detections_second = []
|
| 252 |
+
r_tracked_stracks = [
|
| 253 |
+
track_pool_dict[cls_id][i] for i in u_track
|
| 254 |
+
if track_pool_dict[cls_id][i].state == TrackState.Tracked
|
| 255 |
+
]
|
| 256 |
+
dists = matching.iou_distance(r_tracked_stracks,
|
| 257 |
+
detections_second)
|
| 258 |
+
matches, u_track, u_detection_second = matching.linear_assignment(
|
| 259 |
+
dists, thresh=0.4) # not r_tracked_thresh
|
| 260 |
+
else:
|
| 261 |
+
detections = [detections[i] for i in u_detection]
|
| 262 |
+
r_tracked_stracks = []
|
| 263 |
+
for i in u_track:
|
| 264 |
+
if track_pool_dict[cls_id][i].state == TrackState.Tracked:
|
| 265 |
+
r_tracked_stracks.append(track_pool_dict[cls_id][i])
|
| 266 |
+
dists = matching.iou_distance(r_tracked_stracks, detections)
|
| 267 |
+
|
| 268 |
+
matches, u_track, u_detection = matching.linear_assignment(
|
| 269 |
+
dists, thresh=self.r_tracked_thresh)
|
| 270 |
+
|
| 271 |
+
for i_tracked, idet in matches:
|
| 272 |
+
track = r_tracked_stracks[i_tracked]
|
| 273 |
+
det = detections[
|
| 274 |
+
idet] if not self.use_byte else detections_second[idet]
|
| 275 |
+
if track.state == TrackState.Tracked:
|
| 276 |
+
track.update(det, self.frame_id)
|
| 277 |
+
activated_tracks_dict[cls_id].append(track)
|
| 278 |
+
else:
|
| 279 |
+
track.re_activate(det, self.frame_id, new_id=False)
|
| 280 |
+
refined_tracks_dict[cls_id].append(track)
|
| 281 |
+
|
| 282 |
+
for it in u_track:
|
| 283 |
+
track = r_tracked_stracks[it]
|
| 284 |
+
if not track.state == TrackState.Lost:
|
| 285 |
+
track.mark_lost()
|
| 286 |
+
lost_tracks_dict[cls_id].append(track)
|
| 287 |
+
'''Deal with unconfirmed tracks, usually tracks with only one beginning frame'''
|
| 288 |
+
detections = [detections[i] for i in u_detection]
|
| 289 |
+
dists = matching.iou_distance(unconfirmed_dict[cls_id], detections)
|
| 290 |
+
matches, u_unconfirmed, u_detection = matching.linear_assignment(
|
| 291 |
+
dists, thresh=self.unconfirmed_thresh)
|
| 292 |
+
for i_tracked, idet in matches:
|
| 293 |
+
unconfirmed_dict[cls_id][i_tracked].update(detections[idet],
|
| 294 |
+
self.frame_id)
|
| 295 |
+
activated_tracks_dict[cls_id].append(unconfirmed_dict[cls_id][
|
| 296 |
+
i_tracked])
|
| 297 |
+
for it in u_unconfirmed:
|
| 298 |
+
track = unconfirmed_dict[cls_id][it]
|
| 299 |
+
track.mark_removed()
|
| 300 |
+
removed_tracks_dict[cls_id].append(track)
|
| 301 |
+
""" Step 4: Init new stracks"""
|
| 302 |
+
for inew in u_detection:
|
| 303 |
+
track = detections[inew]
|
| 304 |
+
if track.score < self.det_thresh:
|
| 305 |
+
continue
|
| 306 |
+
track.activate(self.motion, self.frame_id)
|
| 307 |
+
activated_tracks_dict[cls_id].append(track)
|
| 308 |
+
""" Step 5: Update state"""
|
| 309 |
+
for track in self.lost_tracks_dict[cls_id]:
|
| 310 |
+
if self.frame_id - track.end_frame > self.max_time_lost:
|
| 311 |
+
track.mark_removed()
|
| 312 |
+
removed_tracks_dict[cls_id].append(track)
|
| 313 |
+
|
| 314 |
+
self.tracked_tracks_dict[cls_id] = [
|
| 315 |
+
t for t in self.tracked_tracks_dict[cls_id]
|
| 316 |
+
if t.state == TrackState.Tracked
|
| 317 |
+
]
|
| 318 |
+
self.tracked_tracks_dict[cls_id] = joint_stracks(
|
| 319 |
+
self.tracked_tracks_dict[cls_id],
|
| 320 |
+
activated_tracks_dict[cls_id])
|
| 321 |
+
self.tracked_tracks_dict[cls_id] = joint_stracks(
|
| 322 |
+
self.tracked_tracks_dict[cls_id], refined_tracks_dict[cls_id])
|
| 323 |
+
self.lost_tracks_dict[cls_id] = sub_stracks(
|
| 324 |
+
self.lost_tracks_dict[cls_id],
|
| 325 |
+
self.tracked_tracks_dict[cls_id])
|
| 326 |
+
self.lost_tracks_dict[cls_id].extend(lost_tracks_dict[cls_id])
|
| 327 |
+
self.lost_tracks_dict[cls_id] = sub_stracks(
|
| 328 |
+
self.lost_tracks_dict[cls_id],
|
| 329 |
+
self.removed_tracks_dict[cls_id])
|
| 330 |
+
self.removed_tracks_dict[cls_id].extend(removed_tracks_dict[
|
| 331 |
+
cls_id])
|
| 332 |
+
self.tracked_tracks_dict[cls_id], self.lost_tracks_dict[
|
| 333 |
+
cls_id] = remove_duplicate_stracks(
|
| 334 |
+
self.tracked_tracks_dict[cls_id],
|
| 335 |
+
self.lost_tracks_dict[cls_id])
|
| 336 |
+
|
| 337 |
+
# get scores of lost tracks
|
| 338 |
+
output_tracks_dict[cls_id] = [
|
| 339 |
+
track for track in self.tracked_tracks_dict[cls_id]
|
| 340 |
+
if track.is_activated
|
| 341 |
+
]
|
| 342 |
+
|
| 343 |
+
return output_tracks_dict
|
pptracking/python/mot/tracker/ocsort_tracker.py
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"""
|
| 15 |
+
This code is based on https://github.com/noahcao/OC_SORT/blob/master/trackers/ocsort_tracker/ocsort.py
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
try:
|
| 20 |
+
from filterpy.kalman import KalmanFilter
|
| 21 |
+
except:
|
| 22 |
+
print(
|
| 23 |
+
'Warning: Unable to use OC-SORT, please install filterpy, for example: `pip install filterpy`, see https://github.com/rlabbe/filterpy'
|
| 24 |
+
)
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
+
from ..matching.ocsort_matching import associate, linear_assignment, iou_batch
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def k_previous_obs(observations, cur_age, k):
|
| 31 |
+
if len(observations) == 0:
|
| 32 |
+
return [-1, -1, -1, -1, -1]
|
| 33 |
+
for i in range(k):
|
| 34 |
+
dt = k - i
|
| 35 |
+
if cur_age - dt in observations:
|
| 36 |
+
return observations[cur_age - dt]
|
| 37 |
+
max_age = max(observations.keys())
|
| 38 |
+
return observations[max_age]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def convert_bbox_to_z(bbox):
|
| 42 |
+
"""
|
| 43 |
+
Takes a bounding box in the form [x1,y1,x2,y2] and returns z in the form
|
| 44 |
+
[x,y,s,r] where x,y is the centre of the box and s is the scale/area and r is
|
| 45 |
+
the aspect ratio
|
| 46 |
+
"""
|
| 47 |
+
w = bbox[2] - bbox[0]
|
| 48 |
+
h = bbox[3] - bbox[1]
|
| 49 |
+
x = bbox[0] + w / 2.
|
| 50 |
+
y = bbox[1] + h / 2.
|
| 51 |
+
s = w * h # scale is just area
|
| 52 |
+
r = w / float(h + 1e-6)
|
| 53 |
+
return np.array([x, y, s, r]).reshape((4, 1))
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def convert_x_to_bbox(x, score=None):
|
| 57 |
+
"""
|
| 58 |
+
Takes a bounding box in the centre form [x,y,s,r] and returns it in the form
|
| 59 |
+
[x1,y1,x2,y2] where x1,y1 is the top left and x2,y2 is the bottom right
|
| 60 |
+
"""
|
| 61 |
+
w = np.sqrt(x[2] * x[3])
|
| 62 |
+
h = x[2] / w
|
| 63 |
+
if (score == None):
|
| 64 |
+
return np.array(
|
| 65 |
+
[x[0] - w / 2., x[1] - h / 2., x[0] + w / 2.,
|
| 66 |
+
x[1] + h / 2.]).reshape((1, 4))
|
| 67 |
+
else:
|
| 68 |
+
score = np.array([score])
|
| 69 |
+
return np.array([
|
| 70 |
+
x[0] - w / 2., x[1] - h / 2., x[0] + w / 2., x[1] + h / 2., score
|
| 71 |
+
]).reshape((1, 5))
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def speed_direction(bbox1, bbox2):
|
| 75 |
+
cx1, cy1 = (bbox1[0] + bbox1[2]) / 2.0, (bbox1[1] + bbox1[3]) / 2.0
|
| 76 |
+
cx2, cy2 = (bbox2[0] + bbox2[2]) / 2.0, (bbox2[1] + bbox2[3]) / 2.0
|
| 77 |
+
speed = np.array([cy2 - cy1, cx2 - cx1])
|
| 78 |
+
norm = np.sqrt((cy2 - cy1)**2 + (cx2 - cx1)**2) + 1e-6
|
| 79 |
+
return speed / norm
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class KalmanBoxTracker(object):
|
| 83 |
+
"""
|
| 84 |
+
This class represents the internal state of individual tracked objects observed as bbox.
|
| 85 |
+
|
| 86 |
+
Args:
|
| 87 |
+
bbox (np.array): bbox in [x1,y1,x2,y2,score] format.
|
| 88 |
+
delta_t (int): delta_t of previous observation
|
| 89 |
+
"""
|
| 90 |
+
count = 0
|
| 91 |
+
|
| 92 |
+
def __init__(self, bbox, delta_t=3):
|
| 93 |
+
try:
|
| 94 |
+
from filterpy.kalman import KalmanFilter
|
| 95 |
+
except Exception as e:
|
| 96 |
+
raise RuntimeError(
|
| 97 |
+
'Unable to use OC-SORT, please install filterpy, for example: `pip install filterpy`, see https://github.com/rlabbe/filterpy'
|
| 98 |
+
)
|
| 99 |
+
self.kf = KalmanFilter(dim_x=7, dim_z=4)
|
| 100 |
+
self.kf.F = np.array([[1, 0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 1, 0],
|
| 101 |
+
[0, 0, 1, 0, 0, 0, 1], [0, 0, 0, 1, 0, 0, 0],
|
| 102 |
+
[0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 0],
|
| 103 |
+
[0, 0, 0, 0, 0, 0, 1]])
|
| 104 |
+
self.kf.H = np.array([[1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0],
|
| 105 |
+
[0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0]])
|
| 106 |
+
self.kf.R[2:, 2:] *= 10.
|
| 107 |
+
self.kf.P[4:, 4:] *= 1000.
|
| 108 |
+
# give high uncertainty to the unobservable initial velocities
|
| 109 |
+
self.kf.P *= 10.
|
| 110 |
+
self.kf.Q[-1, -1] *= 0.01
|
| 111 |
+
self.kf.Q[4:, 4:] *= 0.01
|
| 112 |
+
|
| 113 |
+
self.score = bbox[4]
|
| 114 |
+
self.kf.x[:4] = convert_bbox_to_z(bbox)
|
| 115 |
+
self.time_since_update = 0
|
| 116 |
+
self.id = KalmanBoxTracker.count
|
| 117 |
+
KalmanBoxTracker.count += 1
|
| 118 |
+
self.history = []
|
| 119 |
+
self.hits = 0
|
| 120 |
+
self.hit_streak = 0
|
| 121 |
+
self.age = 0
|
| 122 |
+
"""
|
| 123 |
+
NOTE: [-1,-1,-1,-1,-1] is a compromising placeholder for non-observation status, the same for the return of
|
| 124 |
+
function k_previous_obs. It is ugly and I do not like it. But to support generate observation array in a
|
| 125 |
+
fast and unified way, which you would see below k_observations = np.array([k_previous_obs(...]]), let's bear it for now.
|
| 126 |
+
"""
|
| 127 |
+
self.last_observation = np.array([-1, -1, -1, -1, -1]) # placeholder
|
| 128 |
+
self.observations = dict()
|
| 129 |
+
self.history_observations = []
|
| 130 |
+
self.velocity = None
|
| 131 |
+
self.delta_t = delta_t
|
| 132 |
+
|
| 133 |
+
def update(self, bbox):
|
| 134 |
+
"""
|
| 135 |
+
Updates the state vector with observed bbox.
|
| 136 |
+
"""
|
| 137 |
+
if bbox is not None:
|
| 138 |
+
if self.last_observation.sum() >= 0: # no previous observation
|
| 139 |
+
previous_box = None
|
| 140 |
+
for i in range(self.delta_t):
|
| 141 |
+
dt = self.delta_t - i
|
| 142 |
+
if self.age - dt in self.observations:
|
| 143 |
+
previous_box = self.observations[self.age - dt]
|
| 144 |
+
break
|
| 145 |
+
if previous_box is None:
|
| 146 |
+
previous_box = self.last_observation
|
| 147 |
+
"""
|
| 148 |
+
Estimate the track speed direction with observations \Delta t steps away
|
| 149 |
+
"""
|
| 150 |
+
self.velocity = speed_direction(previous_box, bbox)
|
| 151 |
+
"""
|
| 152 |
+
Insert new observations. This is a ugly way to maintain both self.observations
|
| 153 |
+
and self.history_observations. Bear it for the moment.
|
| 154 |
+
"""
|
| 155 |
+
self.last_observation = bbox
|
| 156 |
+
self.observations[self.age] = bbox
|
| 157 |
+
self.history_observations.append(bbox)
|
| 158 |
+
|
| 159 |
+
self.time_since_update = 0
|
| 160 |
+
self.history = []
|
| 161 |
+
self.hits += 1
|
| 162 |
+
self.hit_streak += 1
|
| 163 |
+
self.kf.update(convert_bbox_to_z(bbox))
|
| 164 |
+
else:
|
| 165 |
+
self.kf.update(bbox)
|
| 166 |
+
|
| 167 |
+
def predict(self):
|
| 168 |
+
"""
|
| 169 |
+
Advances the state vector and returns the predicted bounding box estimate.
|
| 170 |
+
"""
|
| 171 |
+
if ((self.kf.x[6] + self.kf.x[2]) <= 0):
|
| 172 |
+
self.kf.x[6] *= 0.0
|
| 173 |
+
|
| 174 |
+
self.kf.predict()
|
| 175 |
+
self.age += 1
|
| 176 |
+
if (self.time_since_update > 0):
|
| 177 |
+
self.hit_streak = 0
|
| 178 |
+
self.time_since_update += 1
|
| 179 |
+
self.history.append(convert_x_to_bbox(self.kf.x, score=self.score))
|
| 180 |
+
return self.history[-1]
|
| 181 |
+
|
| 182 |
+
def get_state(self):
|
| 183 |
+
return convert_x_to_bbox(self.kf.x, score=self.score)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
class OCSORTTracker(object):
|
| 187 |
+
"""
|
| 188 |
+
OCSORT tracker, support single class
|
| 189 |
+
|
| 190 |
+
Args:
|
| 191 |
+
det_thresh (float): threshold of detection score
|
| 192 |
+
max_age (int): maximum number of missed misses before a track is deleted
|
| 193 |
+
min_hits (int): minimum hits for associate
|
| 194 |
+
iou_threshold (float): iou threshold for associate
|
| 195 |
+
delta_t (int): delta_t of previous observation
|
| 196 |
+
inertia (float): vdc_weight of angle_diff_cost for associate
|
| 197 |
+
vertical_ratio (float): w/h, the vertical ratio of the bbox to filter
|
| 198 |
+
bad results. If set <= 0 means no need to filter bboxes,usually set
|
| 199 |
+
1.6 for pedestrian tracking.
|
| 200 |
+
min_box_area (int): min box area to filter out low quality boxes
|
| 201 |
+
use_byte (bool): Whether use ByteTracker, default False
|
| 202 |
+
"""
|
| 203 |
+
|
| 204 |
+
def __init__(self,
|
| 205 |
+
det_thresh=0.6,
|
| 206 |
+
max_age=30,
|
| 207 |
+
min_hits=3,
|
| 208 |
+
iou_threshold=0.3,
|
| 209 |
+
delta_t=3,
|
| 210 |
+
inertia=0.2,
|
| 211 |
+
vertical_ratio=-1,
|
| 212 |
+
min_box_area=0,
|
| 213 |
+
use_byte=False):
|
| 214 |
+
self.det_thresh = det_thresh
|
| 215 |
+
self.max_age = max_age
|
| 216 |
+
self.min_hits = min_hits
|
| 217 |
+
self.iou_threshold = iou_threshold
|
| 218 |
+
self.delta_t = delta_t
|
| 219 |
+
self.inertia = inertia
|
| 220 |
+
self.vertical_ratio = vertical_ratio
|
| 221 |
+
self.min_box_area = min_box_area
|
| 222 |
+
self.use_byte = use_byte
|
| 223 |
+
|
| 224 |
+
self.trackers = []
|
| 225 |
+
self.frame_count = 0
|
| 226 |
+
KalmanBoxTracker.count = 0
|
| 227 |
+
|
| 228 |
+
def update(self, pred_dets, pred_embs=None):
|
| 229 |
+
"""
|
| 230 |
+
Args:
|
| 231 |
+
pred_dets (np.array): Detection results of the image, the shape is
|
| 232 |
+
[N, 6], means 'cls_id, score, x0, y0, x1, y1'.
|
| 233 |
+
pred_embs (np.array): Embedding results of the image, the shape is
|
| 234 |
+
[N, 128] or [N, 512], default as None.
|
| 235 |
+
|
| 236 |
+
Return:
|
| 237 |
+
tracking boxes (np.array): [M, 6], means 'x0, y0, x1, y1, score, id'.
|
| 238 |
+
"""
|
| 239 |
+
if pred_dets is None:
|
| 240 |
+
return np.empty((0, 6))
|
| 241 |
+
|
| 242 |
+
self.frame_count += 1
|
| 243 |
+
|
| 244 |
+
bboxes = pred_dets[:, 2:]
|
| 245 |
+
scores = pred_dets[:, 1:2]
|
| 246 |
+
dets = np.concatenate((bboxes, scores), axis=1)
|
| 247 |
+
scores = scores.squeeze(-1)
|
| 248 |
+
|
| 249 |
+
inds_low = scores > 0.1
|
| 250 |
+
inds_high = scores < self.det_thresh
|
| 251 |
+
inds_second = np.logical_and(inds_low, inds_high)
|
| 252 |
+
# self.det_thresh > score > 0.1, for second matching
|
| 253 |
+
dets_second = dets[inds_second] # detections for second matching
|
| 254 |
+
remain_inds = scores > self.det_thresh
|
| 255 |
+
dets = dets[remain_inds]
|
| 256 |
+
|
| 257 |
+
# get predicted locations from existing trackers.
|
| 258 |
+
trks = np.zeros((len(self.trackers), 5))
|
| 259 |
+
to_del = []
|
| 260 |
+
ret = []
|
| 261 |
+
for t, trk in enumerate(trks):
|
| 262 |
+
pos = self.trackers[t].predict()[0]
|
| 263 |
+
trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]
|
| 264 |
+
if np.any(np.isnan(pos)):
|
| 265 |
+
to_del.append(t)
|
| 266 |
+
trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
|
| 267 |
+
for t in reversed(to_del):
|
| 268 |
+
self.trackers.pop(t)
|
| 269 |
+
|
| 270 |
+
velocities = np.array([
|
| 271 |
+
trk.velocity if trk.velocity is not None else np.array((0, 0))
|
| 272 |
+
for trk in self.trackers
|
| 273 |
+
])
|
| 274 |
+
last_boxes = np.array([trk.last_observation for trk in self.trackers])
|
| 275 |
+
k_observations = np.array([
|
| 276 |
+
k_previous_obs(trk.observations, trk.age, self.delta_t)
|
| 277 |
+
for trk in self.trackers
|
| 278 |
+
])
|
| 279 |
+
"""
|
| 280 |
+
First round of association
|
| 281 |
+
"""
|
| 282 |
+
matched, unmatched_dets, unmatched_trks = associate(
|
| 283 |
+
dets, trks, self.iou_threshold, velocities, k_observations,
|
| 284 |
+
self.inertia)
|
| 285 |
+
for m in matched:
|
| 286 |
+
self.trackers[m[1]].update(dets[m[0], :])
|
| 287 |
+
"""
|
| 288 |
+
Second round of associaton by OCR
|
| 289 |
+
"""
|
| 290 |
+
# BYTE association
|
| 291 |
+
if self.use_byte and len(dets_second) > 0 and unmatched_trks.shape[
|
| 292 |
+
0] > 0:
|
| 293 |
+
u_trks = trks[unmatched_trks]
|
| 294 |
+
iou_left = iou_batch(
|
| 295 |
+
dets_second,
|
| 296 |
+
u_trks) # iou between low score detections and unmatched tracks
|
| 297 |
+
iou_left = np.array(iou_left)
|
| 298 |
+
if iou_left.max() > self.iou_threshold:
|
| 299 |
+
"""
|
| 300 |
+
NOTE: by using a lower threshold, e.g., self.iou_threshold - 0.1, you may
|
| 301 |
+
get a higher performance especially on MOT17/MOT20 datasets. But we keep it
|
| 302 |
+
uniform here for simplicity
|
| 303 |
+
"""
|
| 304 |
+
matched_indices = linear_assignment(-iou_left)
|
| 305 |
+
to_remove_trk_indices = []
|
| 306 |
+
for m in matched_indices:
|
| 307 |
+
det_ind, trk_ind = m[0], unmatched_trks[m[1]]
|
| 308 |
+
if iou_left[m[0], m[1]] < self.iou_threshold:
|
| 309 |
+
continue
|
| 310 |
+
self.trackers[trk_ind].update(dets_second[det_ind, :])
|
| 311 |
+
to_remove_trk_indices.append(trk_ind)
|
| 312 |
+
unmatched_trks = np.setdiff1d(unmatched_trks,
|
| 313 |
+
np.array(to_remove_trk_indices))
|
| 314 |
+
|
| 315 |
+
if unmatched_dets.shape[0] > 0 and unmatched_trks.shape[0] > 0:
|
| 316 |
+
left_dets = dets[unmatched_dets]
|
| 317 |
+
left_trks = last_boxes[unmatched_trks]
|
| 318 |
+
iou_left = iou_batch(left_dets, left_trks)
|
| 319 |
+
iou_left = np.array(iou_left)
|
| 320 |
+
if iou_left.max() > self.iou_threshold:
|
| 321 |
+
"""
|
| 322 |
+
NOTE: by using a lower threshold, e.g., self.iou_threshold - 0.1, you may
|
| 323 |
+
get a higher performance especially on MOT17/MOT20 datasets. But we keep it
|
| 324 |
+
uniform here for simplicity
|
| 325 |
+
"""
|
| 326 |
+
rematched_indices = linear_assignment(-iou_left)
|
| 327 |
+
to_remove_det_indices = []
|
| 328 |
+
to_remove_trk_indices = []
|
| 329 |
+
for m in rematched_indices:
|
| 330 |
+
det_ind, trk_ind = unmatched_dets[m[0]], unmatched_trks[m[
|
| 331 |
+
1]]
|
| 332 |
+
if iou_left[m[0], m[1]] < self.iou_threshold:
|
| 333 |
+
continue
|
| 334 |
+
self.trackers[trk_ind].update(dets[det_ind, :])
|
| 335 |
+
to_remove_det_indices.append(det_ind)
|
| 336 |
+
to_remove_trk_indices.append(trk_ind)
|
| 337 |
+
unmatched_dets = np.setdiff1d(unmatched_dets,
|
| 338 |
+
np.array(to_remove_det_indices))
|
| 339 |
+
unmatched_trks = np.setdiff1d(unmatched_trks,
|
| 340 |
+
np.array(to_remove_trk_indices))
|
| 341 |
+
|
| 342 |
+
for m in unmatched_trks:
|
| 343 |
+
self.trackers[m].update(None)
|
| 344 |
+
|
| 345 |
+
# create and initialise new trackers for unmatched detections
|
| 346 |
+
for i in unmatched_dets:
|
| 347 |
+
trk = KalmanBoxTracker(dets[i, :], delta_t=self.delta_t)
|
| 348 |
+
self.trackers.append(trk)
|
| 349 |
+
i = len(self.trackers)
|
| 350 |
+
for trk in reversed(self.trackers):
|
| 351 |
+
if trk.last_observation.sum() < 0:
|
| 352 |
+
d = trk.get_state()[0]
|
| 353 |
+
else:
|
| 354 |
+
d = trk.last_observation # tlbr + score
|
| 355 |
+
if (trk.time_since_update < 1) and (
|
| 356 |
+
trk.hit_streak >= self.min_hits or
|
| 357 |
+
self.frame_count <= self.min_hits):
|
| 358 |
+
# +1 as MOT benchmark requires positive
|
| 359 |
+
ret.append(np.concatenate((d, [trk.id + 1])).reshape(1, -1))
|
| 360 |
+
i -= 1
|
| 361 |
+
# remove dead tracklet
|
| 362 |
+
if (trk.time_since_update > self.max_age):
|
| 363 |
+
self.trackers.pop(i)
|
| 364 |
+
if (len(ret) > 0):
|
| 365 |
+
return np.concatenate(ret)
|
| 366 |
+
return np.empty((0, 6))
|
pptracking/python/mot/utils.py
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import cv2
|
| 17 |
+
import time
|
| 18 |
+
import numpy as np
|
| 19 |
+
import collections
|
| 20 |
+
import math
|
| 21 |
+
|
| 22 |
+
__all__ = [
|
| 23 |
+
'MOTTimer', 'Detection', 'write_mot_results', 'load_det_results',
|
| 24 |
+
'preprocess_reid', 'get_crops', 'clip_box', 'scale_coords',
|
| 25 |
+
'flow_statistic', 'update_object_info'
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class MOTTimer(object):
|
| 30 |
+
"""
|
| 31 |
+
This class used to compute and print the current FPS while evaling.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
def __init__(self, window_size=20):
|
| 35 |
+
self.start_time = 0.
|
| 36 |
+
self.diff = 0.
|
| 37 |
+
self.duration = 0.
|
| 38 |
+
self.deque = collections.deque(maxlen=window_size)
|
| 39 |
+
|
| 40 |
+
def tic(self):
|
| 41 |
+
# using time.time instead of time.clock because time time.clock
|
| 42 |
+
# does not normalize for multithreading
|
| 43 |
+
self.start_time = time.time()
|
| 44 |
+
|
| 45 |
+
def toc(self, average=True):
|
| 46 |
+
self.diff = time.time() - self.start_time
|
| 47 |
+
self.deque.append(self.diff)
|
| 48 |
+
if average:
|
| 49 |
+
self.duration = np.mean(self.deque)
|
| 50 |
+
else:
|
| 51 |
+
self.duration = np.sum(self.deque)
|
| 52 |
+
return self.duration
|
| 53 |
+
|
| 54 |
+
def clear(self):
|
| 55 |
+
self.start_time = 0.
|
| 56 |
+
self.diff = 0.
|
| 57 |
+
self.duration = 0.
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class Detection(object):
|
| 61 |
+
"""
|
| 62 |
+
This class represents a bounding box detection in a single image.
|
| 63 |
+
|
| 64 |
+
Args:
|
| 65 |
+
tlwh (Tensor): Bounding box in format `(top left x, top left y,
|
| 66 |
+
width, height)`.
|
| 67 |
+
score (Tensor): Bounding box confidence score.
|
| 68 |
+
feature (Tensor): A feature vector that describes the object
|
| 69 |
+
contained in this image.
|
| 70 |
+
cls_id (Tensor): Bounding box category id.
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
def __init__(self, tlwh, score, feature, cls_id):
|
| 74 |
+
self.tlwh = np.asarray(tlwh, dtype=np.float32)
|
| 75 |
+
self.score = float(score)
|
| 76 |
+
self.feature = np.asarray(feature, dtype=np.float32)
|
| 77 |
+
self.cls_id = int(cls_id)
|
| 78 |
+
|
| 79 |
+
def to_tlbr(self):
|
| 80 |
+
"""
|
| 81 |
+
Convert bounding box to format `(min x, min y, max x, max y)`, i.e.,
|
| 82 |
+
`(top left, bottom right)`.
|
| 83 |
+
"""
|
| 84 |
+
ret = self.tlwh.copy()
|
| 85 |
+
ret[2:] += ret[:2]
|
| 86 |
+
return ret
|
| 87 |
+
|
| 88 |
+
def to_xyah(self):
|
| 89 |
+
"""
|
| 90 |
+
Convert bounding box to format `(center x, center y, aspect ratio,
|
| 91 |
+
height)`, where the aspect ratio is `width / height`.
|
| 92 |
+
"""
|
| 93 |
+
ret = self.tlwh.copy()
|
| 94 |
+
ret[:2] += ret[2:] / 2
|
| 95 |
+
ret[2] /= ret[3]
|
| 96 |
+
return ret
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def write_mot_results(filename, results, data_type='mot', num_classes=1):
|
| 100 |
+
# support single and multi classes
|
| 101 |
+
if data_type in ['mot', 'mcmot']:
|
| 102 |
+
save_format = '{frame},{id},{x1},{y1},{w},{h},{score},{cls_id},-1,-1\n'
|
| 103 |
+
elif data_type == 'kitti':
|
| 104 |
+
save_format = '{frame} {id} car 0 0 -10 {x1} {y1} {x2} {y2} -10 -10 -10 -1000 -1000 -1000 -10\n'
|
| 105 |
+
else:
|
| 106 |
+
raise ValueError(data_type)
|
| 107 |
+
|
| 108 |
+
f = open(filename, 'w')
|
| 109 |
+
for cls_id in range(num_classes):
|
| 110 |
+
for frame_id, tlwhs, tscores, track_ids in results[cls_id]:
|
| 111 |
+
if data_type == 'kitti':
|
| 112 |
+
frame_id -= 1
|
| 113 |
+
for tlwh, score, track_id in zip(tlwhs, tscores, track_ids):
|
| 114 |
+
if track_id < 0: continue
|
| 115 |
+
if data_type == 'mot':
|
| 116 |
+
cls_id = -1
|
| 117 |
+
|
| 118 |
+
x1, y1, w, h = tlwh
|
| 119 |
+
x2, y2 = x1 + w, y1 + h
|
| 120 |
+
line = save_format.format(
|
| 121 |
+
frame=frame_id,
|
| 122 |
+
id=track_id,
|
| 123 |
+
x1=x1,
|
| 124 |
+
y1=y1,
|
| 125 |
+
x2=x2,
|
| 126 |
+
y2=y2,
|
| 127 |
+
w=w,
|
| 128 |
+
h=h,
|
| 129 |
+
score=score,
|
| 130 |
+
cls_id=cls_id)
|
| 131 |
+
f.write(line)
|
| 132 |
+
print('MOT results save in {}'.format(filename))
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def load_det_results(det_file, num_frames):
|
| 136 |
+
assert os.path.exists(det_file) and os.path.isfile(det_file), \
|
| 137 |
+
'{} is not exist or not a file.'.format(det_file)
|
| 138 |
+
labels = np.loadtxt(det_file, dtype='float32', delimiter=',')
|
| 139 |
+
assert labels.shape[1] == 7, \
|
| 140 |
+
"Each line of {} should have 7 items: '[frame_id],[x0],[y0],[w],[h],[score],[class_id]'.".format(det_file)
|
| 141 |
+
results_list = []
|
| 142 |
+
for frame_i in range(num_frames):
|
| 143 |
+
results = {'bbox': [], 'score': [], 'cls_id': []}
|
| 144 |
+
lables_with_frame = labels[labels[:, 0] == frame_i + 1]
|
| 145 |
+
# each line of lables_with_frame:
|
| 146 |
+
# [frame_id],[x0],[y0],[w],[h],[score],[class_id]
|
| 147 |
+
for l in lables_with_frame:
|
| 148 |
+
results['bbox'].append(l[1:5])
|
| 149 |
+
results['score'].append(l[5:6])
|
| 150 |
+
results['cls_id'].append(l[6:7])
|
| 151 |
+
results_list.append(results)
|
| 152 |
+
return results_list
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def scale_coords(coords, input_shape, im_shape, scale_factor):
|
| 156 |
+
# Note: ratio has only one value, scale_factor[0] == scale_factor[1]
|
| 157 |
+
#
|
| 158 |
+
# This function only used for JDE YOLOv3 or other detectors with
|
| 159 |
+
# LetterBoxResize and JDEBBoxPostProcess, coords output from detector had
|
| 160 |
+
# not scaled back to the origin image.
|
| 161 |
+
|
| 162 |
+
ratio = scale_factor[0]
|
| 163 |
+
pad_w = (input_shape[1] - int(im_shape[1])) / 2
|
| 164 |
+
pad_h = (input_shape[0] - int(im_shape[0])) / 2
|
| 165 |
+
coords[:, 0::2] -= pad_w
|
| 166 |
+
coords[:, 1::2] -= pad_h
|
| 167 |
+
coords[:, 0:4] /= ratio
|
| 168 |
+
coords[:, :4] = np.clip(coords[:, :4], a_min=0, a_max=coords[:, :4].max())
|
| 169 |
+
return coords.round()
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def clip_box(xyxy, ori_image_shape):
|
| 173 |
+
H, W = ori_image_shape
|
| 174 |
+
xyxy[:, 0::2] = np.clip(xyxy[:, 0::2], a_min=0, a_max=W)
|
| 175 |
+
xyxy[:, 1::2] = np.clip(xyxy[:, 1::2], a_min=0, a_max=H)
|
| 176 |
+
w = xyxy[:, 2:3] - xyxy[:, 0:1]
|
| 177 |
+
h = xyxy[:, 3:4] - xyxy[:, 1:2]
|
| 178 |
+
mask = np.logical_and(h > 0, w > 0)
|
| 179 |
+
keep_idx = np.nonzero(mask)
|
| 180 |
+
return xyxy[keep_idx[0]], keep_idx
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def get_crops(xyxy, ori_img, w, h):
|
| 184 |
+
crops = []
|
| 185 |
+
xyxy = xyxy.astype(np.int64)
|
| 186 |
+
ori_img = ori_img.transpose(1, 0, 2) # [h,w,3]->[w,h,3]
|
| 187 |
+
for i, bbox in enumerate(xyxy):
|
| 188 |
+
crop = ori_img[bbox[0]:bbox[2], bbox[1]:bbox[3], :]
|
| 189 |
+
crops.append(crop)
|
| 190 |
+
crops = preprocess_reid(crops, w, h)
|
| 191 |
+
return crops
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def preprocess_reid(imgs,
|
| 195 |
+
w=64,
|
| 196 |
+
h=192,
|
| 197 |
+
mean=[0.485, 0.456, 0.406],
|
| 198 |
+
std=[0.229, 0.224, 0.225]):
|
| 199 |
+
im_batch = []
|
| 200 |
+
for img in imgs:
|
| 201 |
+
img = cv2.resize(img, (w, h))
|
| 202 |
+
img = img[:, :, ::-1].astype('float32').transpose((2, 0, 1)) / 255
|
| 203 |
+
img_mean = np.array(mean).reshape((3, 1, 1))
|
| 204 |
+
img_std = np.array(std).reshape((3, 1, 1))
|
| 205 |
+
img -= img_mean
|
| 206 |
+
img /= img_std
|
| 207 |
+
img = np.expand_dims(img, axis=0)
|
| 208 |
+
im_batch.append(img)
|
| 209 |
+
im_batch = np.concatenate(im_batch, 0)
|
| 210 |
+
return im_batch
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def flow_statistic(result,
|
| 214 |
+
secs_interval,
|
| 215 |
+
do_entrance_counting,
|
| 216 |
+
do_break_in_counting,
|
| 217 |
+
region_type,
|
| 218 |
+
video_fps,
|
| 219 |
+
entrance,
|
| 220 |
+
id_set,
|
| 221 |
+
interval_id_set,
|
| 222 |
+
in_id_list,
|
| 223 |
+
out_id_list,
|
| 224 |
+
prev_center,
|
| 225 |
+
records,
|
| 226 |
+
data_type='mot',
|
| 227 |
+
ids2names=['pedestrian']):
|
| 228 |
+
# Count in/out number:
|
| 229 |
+
# Note that 'region_type' should be one of ['horizontal', 'vertical', 'custom'],
|
| 230 |
+
# 'horizontal' and 'vertical' means entrance is the center line as the entrance when do_entrance_counting,
|
| 231 |
+
# 'custom' means entrance is a region defined by users when do_break_in_counting.
|
| 232 |
+
|
| 233 |
+
if do_entrance_counting:
|
| 234 |
+
assert region_type in [
|
| 235 |
+
'horizontal', 'vertical'
|
| 236 |
+
], "region_type should be 'horizontal' or 'vertical' when do entrance counting."
|
| 237 |
+
entrance_x, entrance_y = entrance[0], entrance[1]
|
| 238 |
+
frame_id, tlwhs, tscores, track_ids = result
|
| 239 |
+
for tlwh, score, track_id in zip(tlwhs, tscores, track_ids):
|
| 240 |
+
if track_id < 0: continue
|
| 241 |
+
if data_type == 'kitti':
|
| 242 |
+
frame_id -= 1
|
| 243 |
+
x1, y1, w, h = tlwh
|
| 244 |
+
center_x = x1 + w / 2.
|
| 245 |
+
center_y = y1 + h / 2.
|
| 246 |
+
if track_id in prev_center:
|
| 247 |
+
if region_type == 'horizontal':
|
| 248 |
+
# horizontal center line
|
| 249 |
+
if prev_center[track_id][1] <= entrance_y and \
|
| 250 |
+
center_y > entrance_y:
|
| 251 |
+
in_id_list.append(track_id)
|
| 252 |
+
if prev_center[track_id][1] >= entrance_y and \
|
| 253 |
+
center_y < entrance_y:
|
| 254 |
+
out_id_list.append(track_id)
|
| 255 |
+
else:
|
| 256 |
+
# vertical center line
|
| 257 |
+
if prev_center[track_id][0] <= entrance_x and \
|
| 258 |
+
center_x > entrance_x:
|
| 259 |
+
in_id_list.append(track_id)
|
| 260 |
+
if prev_center[track_id][0] >= entrance_x and \
|
| 261 |
+
center_x < entrance_x:
|
| 262 |
+
out_id_list.append(track_id)
|
| 263 |
+
prev_center[track_id][0] = center_x
|
| 264 |
+
prev_center[track_id][1] = center_y
|
| 265 |
+
else:
|
| 266 |
+
prev_center[track_id] = [center_x, center_y]
|
| 267 |
+
|
| 268 |
+
if do_break_in_counting:
|
| 269 |
+
assert region_type in [
|
| 270 |
+
'custom'
|
| 271 |
+
], "region_type should be 'custom' when do break_in counting."
|
| 272 |
+
assert len(
|
| 273 |
+
entrance
|
| 274 |
+
) >= 4, "entrance should be at least 3 points and (w,h) of image when do break_in counting."
|
| 275 |
+
im_w, im_h = entrance[-1][:]
|
| 276 |
+
entrance = np.array(entrance[:-1])
|
| 277 |
+
|
| 278 |
+
frame_id, tlwhs, tscores, track_ids = result
|
| 279 |
+
for tlwh, score, track_id in zip(tlwhs, tscores, track_ids):
|
| 280 |
+
if track_id < 0: continue
|
| 281 |
+
if data_type == 'kitti':
|
| 282 |
+
frame_id -= 1
|
| 283 |
+
x1, y1, w, h = tlwh
|
| 284 |
+
center_x = min(x1 + w / 2., im_w - 1)
|
| 285 |
+
if ids2names[0] == 'pedestrian':
|
| 286 |
+
center_y = min(y1 + h, im_h - 1)
|
| 287 |
+
else:
|
| 288 |
+
center_y = min(y1 + h / 2, im_h - 1)
|
| 289 |
+
|
| 290 |
+
# counting objects in region of the first frame
|
| 291 |
+
if frame_id == 1:
|
| 292 |
+
if in_quadrangle([center_x, center_y], entrance, im_h, im_w):
|
| 293 |
+
in_id_list.append(-1)
|
| 294 |
+
else:
|
| 295 |
+
prev_center[track_id] = [center_x, center_y]
|
| 296 |
+
else:
|
| 297 |
+
if track_id in prev_center:
|
| 298 |
+
if not in_quadrangle(prev_center[track_id], entrance, im_h,
|
| 299 |
+
im_w) and in_quadrangle(
|
| 300 |
+
[center_x, center_y], entrance,
|
| 301 |
+
im_h, im_w):
|
| 302 |
+
in_id_list.append(track_id)
|
| 303 |
+
prev_center[track_id] = [center_x, center_y]
|
| 304 |
+
else:
|
| 305 |
+
prev_center[track_id] = [center_x, center_y]
|
| 306 |
+
|
| 307 |
+
# Count totol number, number at a manual-setting interval
|
| 308 |
+
frame_id, tlwhs, tscores, track_ids = result
|
| 309 |
+
for tlwh, score, track_id in zip(tlwhs, tscores, track_ids):
|
| 310 |
+
if track_id < 0: continue
|
| 311 |
+
id_set.add(track_id)
|
| 312 |
+
interval_id_set.add(track_id)
|
| 313 |
+
|
| 314 |
+
# Reset counting at the interval beginning
|
| 315 |
+
if frame_id % video_fps == 0 and frame_id / video_fps % secs_interval == 0:
|
| 316 |
+
curr_interval_count = len(interval_id_set)
|
| 317 |
+
interval_id_set.clear()
|
| 318 |
+
info = "Frame id: {}, Total count: {}".format(frame_id, len(id_set))
|
| 319 |
+
if do_entrance_counting:
|
| 320 |
+
info += ", In count: {}, Out count: {}".format(
|
| 321 |
+
len(in_id_list), len(out_id_list))
|
| 322 |
+
if do_break_in_counting:
|
| 323 |
+
info += ", Break_in count: {}".format(len(in_id_list))
|
| 324 |
+
if frame_id % video_fps == 0 and frame_id / video_fps % secs_interval == 0:
|
| 325 |
+
info += ", Count during {} secs: {}".format(secs_interval,
|
| 326 |
+
curr_interval_count)
|
| 327 |
+
interval_id_set.clear()
|
| 328 |
+
# print(info)
|
| 329 |
+
info += "\n"
|
| 330 |
+
records.append(info)
|
| 331 |
+
|
| 332 |
+
return {
|
| 333 |
+
"id_set": id_set,
|
| 334 |
+
"interval_id_set": interval_id_set,
|
| 335 |
+
"in_id_list": in_id_list,
|
| 336 |
+
"out_id_list": out_id_list,
|
| 337 |
+
"prev_center": prev_center,
|
| 338 |
+
"records": records,
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def distance(center_1, center_2):
|
| 343 |
+
return math.sqrt(
|
| 344 |
+
math.pow(center_1[0] - center_2[0], 2) + math.pow(center_1[1] -
|
| 345 |
+
center_2[1], 2))
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
# update vehicle parking info
|
| 349 |
+
def update_object_info(object_in_region_info,
|
| 350 |
+
result,
|
| 351 |
+
region_type,
|
| 352 |
+
entrance,
|
| 353 |
+
fps,
|
| 354 |
+
illegal_parking_time,
|
| 355 |
+
distance_threshold_frame=3,
|
| 356 |
+
distance_threshold_interval=50):
|
| 357 |
+
'''
|
| 358 |
+
For consecutive frames, the distance between two frame is smaller than distance_threshold_frame, regard as parking
|
| 359 |
+
For parking in general, the move distance should smaller than distance_threshold_interval
|
| 360 |
+
The moving distance of the vehicle is scaled according to the y, which is inversely proportional to y.
|
| 361 |
+
'''
|
| 362 |
+
|
| 363 |
+
assert region_type in [
|
| 364 |
+
'custom'
|
| 365 |
+
], "region_type should be 'custom' when do break_in counting."
|
| 366 |
+
assert len(
|
| 367 |
+
entrance
|
| 368 |
+
) >= 4, "entrance should be at least 3 points and (w,h) of image when do break_in counting."
|
| 369 |
+
|
| 370 |
+
frame_id, tlwhs, tscores, track_ids = result # result from mot
|
| 371 |
+
|
| 372 |
+
im_w, im_h = entrance[-1][:]
|
| 373 |
+
entrance = np.array(entrance[:-1])
|
| 374 |
+
|
| 375 |
+
illegal_parking_dict = {}
|
| 376 |
+
for tlwh, score, track_id in zip(tlwhs, tscores, track_ids):
|
| 377 |
+
if track_id < 0: continue
|
| 378 |
+
|
| 379 |
+
x1, y1, w, h = tlwh
|
| 380 |
+
center_x = min(x1 + w / 2., im_w - 1)
|
| 381 |
+
center_y = min(y1 + h / 2, im_h - 1)
|
| 382 |
+
|
| 383 |
+
if not in_quadrangle([center_x, center_y], entrance, im_h, im_w):
|
| 384 |
+
continue
|
| 385 |
+
|
| 386 |
+
current_center = (center_x, center_y)
|
| 387 |
+
if track_id not in object_in_region_info.keys(
|
| 388 |
+
): # first time appear in region
|
| 389 |
+
object_in_region_info[track_id] = {}
|
| 390 |
+
object_in_region_info[track_id]["start_frame"] = frame_id
|
| 391 |
+
object_in_region_info[track_id]["end_frame"] = frame_id
|
| 392 |
+
object_in_region_info[track_id]["prev_center"] = current_center
|
| 393 |
+
object_in_region_info[track_id]["start_center"] = current_center
|
| 394 |
+
else:
|
| 395 |
+
prev_center = object_in_region_info[track_id]["prev_center"]
|
| 396 |
+
|
| 397 |
+
dis = distance(current_center, prev_center)
|
| 398 |
+
scaled_dis = 200 * dis / (
|
| 399 |
+
current_center[1] + 1) # scale distance according to y
|
| 400 |
+
dis = scaled_dis
|
| 401 |
+
|
| 402 |
+
if dis < distance_threshold_frame: # not move
|
| 403 |
+
object_in_region_info[track_id]["end_frame"] = frame_id
|
| 404 |
+
object_in_region_info[track_id]["prev_center"] = current_center
|
| 405 |
+
else: # move
|
| 406 |
+
object_in_region_info[track_id]["start_frame"] = frame_id
|
| 407 |
+
object_in_region_info[track_id]["end_frame"] = frame_id
|
| 408 |
+
object_in_region_info[track_id]["prev_center"] = current_center
|
| 409 |
+
object_in_region_info[track_id][
|
| 410 |
+
"start_center"] = current_center
|
| 411 |
+
|
| 412 |
+
# whether current object parking
|
| 413 |
+
distance_from_start = distance(
|
| 414 |
+
object_in_region_info[track_id]["start_center"], current_center)
|
| 415 |
+
if distance_from_start > distance_threshold_interval:
|
| 416 |
+
# moved
|
| 417 |
+
object_in_region_info[track_id]["start_frame"] = frame_id
|
| 418 |
+
object_in_region_info[track_id]["end_frame"] = frame_id
|
| 419 |
+
object_in_region_info[track_id]["prev_center"] = current_center
|
| 420 |
+
object_in_region_info[track_id]["start_center"] = current_center
|
| 421 |
+
continue
|
| 422 |
+
|
| 423 |
+
if (object_in_region_info[track_id]["end_frame"]-object_in_region_info[track_id]["start_frame"]) /fps >= illegal_parking_time \
|
| 424 |
+
and distance_from_start<distance_threshold_interval:
|
| 425 |
+
illegal_parking_dict[track_id] = {"bbox": [x1, y1, w, h]}
|
| 426 |
+
|
| 427 |
+
return object_in_region_info, illegal_parking_dict
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def in_quadrangle(point, entrance, im_h, im_w):
|
| 431 |
+
mask = np.zeros((im_h, im_w, 1), np.uint8)
|
| 432 |
+
cv2.fillPoly(mask, [entrance], 255)
|
| 433 |
+
p = tuple(map(int, point))
|
| 434 |
+
if mask[p[1], p[0], :] > 0:
|
| 435 |
+
return True
|
| 436 |
+
else:
|
| 437 |
+
return False
|
pptracking/python/mot/visualize.py
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
from __future__ import division
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import cv2
|
| 19 |
+
import numpy as np
|
| 20 |
+
from PIL import Image, ImageDraw, ImageFile
|
| 21 |
+
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
| 22 |
+
from collections import deque
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def visualize_box_mask(im, results, labels, threshold=0.5):
|
| 26 |
+
"""
|
| 27 |
+
Args:
|
| 28 |
+
im (str/np.ndarray): path of image/np.ndarray read by cv2
|
| 29 |
+
results (dict): include 'boxes': np.ndarray: shape:[N,6], N: number of box,
|
| 30 |
+
matix element:[class, score, x_min, y_min, x_max, y_max]
|
| 31 |
+
labels (list): labels:['class1', ..., 'classn']
|
| 32 |
+
threshold (float): Threshold of score.
|
| 33 |
+
Returns:
|
| 34 |
+
im (PIL.Image.Image): visualized image
|
| 35 |
+
"""
|
| 36 |
+
if isinstance(im, str):
|
| 37 |
+
im = Image.open(im).convert('RGB')
|
| 38 |
+
else:
|
| 39 |
+
im = Image.fromarray(im)
|
| 40 |
+
if 'boxes' in results and len(results['boxes']) > 0:
|
| 41 |
+
im = draw_box(im, results['boxes'], labels, threshold=threshold)
|
| 42 |
+
return im
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def get_color_map_list(num_classes):
|
| 46 |
+
"""
|
| 47 |
+
Args:
|
| 48 |
+
num_classes (int): number of class
|
| 49 |
+
Returns:
|
| 50 |
+
color_map (list): RGB color list
|
| 51 |
+
"""
|
| 52 |
+
color_map = num_classes * [0, 0, 0]
|
| 53 |
+
for i in range(0, num_classes):
|
| 54 |
+
j = 0
|
| 55 |
+
lab = i
|
| 56 |
+
while lab:
|
| 57 |
+
color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j))
|
| 58 |
+
color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j))
|
| 59 |
+
color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j))
|
| 60 |
+
j += 1
|
| 61 |
+
lab >>= 3
|
| 62 |
+
color_map = [color_map[i:i + 3] for i in range(0, len(color_map), 3)]
|
| 63 |
+
return color_map
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def draw_box(im, np_boxes, labels, threshold=0.5):
|
| 67 |
+
"""
|
| 68 |
+
Args:
|
| 69 |
+
im (PIL.Image.Image): PIL image
|
| 70 |
+
np_boxes (np.ndarray): shape:[N,6], N: number of box,
|
| 71 |
+
matix element:[class, score, x_min, y_min, x_max, y_max]
|
| 72 |
+
labels (list): labels:['class1', ..., 'classn']
|
| 73 |
+
threshold (float): threshold of box
|
| 74 |
+
Returns:
|
| 75 |
+
im (PIL.Image.Image): visualized image
|
| 76 |
+
"""
|
| 77 |
+
draw_thickness = min(im.size) // 320
|
| 78 |
+
draw = ImageDraw.Draw(im)
|
| 79 |
+
clsid2color = {}
|
| 80 |
+
color_list = get_color_map_list(len(labels))
|
| 81 |
+
expect_boxes = (np_boxes[:, 1] > threshold) & (np_boxes[:, 0] > -1)
|
| 82 |
+
np_boxes = np_boxes[expect_boxes, :]
|
| 83 |
+
|
| 84 |
+
for dt in np_boxes:
|
| 85 |
+
clsid, bbox, score = int(dt[0]), dt[2:], dt[1]
|
| 86 |
+
if clsid not in clsid2color:
|
| 87 |
+
clsid2color[clsid] = color_list[clsid]
|
| 88 |
+
color = tuple(clsid2color[clsid])
|
| 89 |
+
|
| 90 |
+
if len(bbox) == 4:
|
| 91 |
+
xmin, ymin, xmax, ymax = bbox
|
| 92 |
+
print('class_id:{:d}, confidence:{:.4f}, left_top:[{:.2f},{:.2f}],'
|
| 93 |
+
'right_bottom:[{:.2f},{:.2f}]'.format(
|
| 94 |
+
int(clsid), score, xmin, ymin, xmax, ymax))
|
| 95 |
+
# draw bbox
|
| 96 |
+
draw.line(
|
| 97 |
+
[(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin),
|
| 98 |
+
(xmin, ymin)],
|
| 99 |
+
width=draw_thickness,
|
| 100 |
+
fill=color)
|
| 101 |
+
elif len(bbox) == 8:
|
| 102 |
+
x1, y1, x2, y2, x3, y3, x4, y4 = bbox
|
| 103 |
+
draw.line(
|
| 104 |
+
[(x1, y1), (x2, y2), (x3, y3), (x4, y4), (x1, y1)],
|
| 105 |
+
width=2,
|
| 106 |
+
fill=color)
|
| 107 |
+
xmin = min(x1, x2, x3, x4)
|
| 108 |
+
ymin = min(y1, y2, y3, y4)
|
| 109 |
+
|
| 110 |
+
# draw label
|
| 111 |
+
text = "{} {:.4f}".format(labels[clsid], score)
|
| 112 |
+
tw, th = draw.textsize(text)
|
| 113 |
+
draw.rectangle(
|
| 114 |
+
[(xmin + 1, ymin - th), (xmin + tw + 1, ymin)], fill=color)
|
| 115 |
+
draw.text((xmin + 1, ymin - th), text, fill=(255, 255, 255))
|
| 116 |
+
return im
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def get_color(idx):
|
| 120 |
+
idx = idx * 3
|
| 121 |
+
color = ((37 * idx) % 255, (17 * idx) % 255, (29 * idx) % 255)
|
| 122 |
+
return color
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def plot_tracking(image,
|
| 126 |
+
tlwhs,
|
| 127 |
+
obj_ids,
|
| 128 |
+
scores=None,
|
| 129 |
+
frame_id=0,
|
| 130 |
+
fps=0.,
|
| 131 |
+
ids2names=[],
|
| 132 |
+
do_entrance_counting=False,
|
| 133 |
+
entrance=None):
|
| 134 |
+
im = np.ascontiguousarray(np.copy(image))
|
| 135 |
+
im_h, im_w = im.shape[:2]
|
| 136 |
+
|
| 137 |
+
text_scale = max(0.5, image.shape[1] / 3000.)
|
| 138 |
+
text_thickness = 2
|
| 139 |
+
line_thickness = max(1, int(image.shape[1] / 500.))
|
| 140 |
+
|
| 141 |
+
cv2.putText(
|
| 142 |
+
im,
|
| 143 |
+
'frame: %d fps: %.2f num: %d' % (frame_id, fps, len(tlwhs)),
|
| 144 |
+
(0, int(15 * text_scale) + 5),
|
| 145 |
+
cv2.FONT_ITALIC,
|
| 146 |
+
text_scale, (0, 0, 255),
|
| 147 |
+
thickness=text_thickness)
|
| 148 |
+
for i, tlwh in enumerate(tlwhs):
|
| 149 |
+
x1, y1, w, h = tlwh
|
| 150 |
+
intbox = tuple(map(int, (x1, y1, x1 + w, y1 + h)))
|
| 151 |
+
obj_id = int(obj_ids[i])
|
| 152 |
+
id_text = 'ID: {}'.format(int(obj_id))
|
| 153 |
+
if ids2names != []:
|
| 154 |
+
assert len(
|
| 155 |
+
ids2names) == 1, "plot_tracking only supports single classes."
|
| 156 |
+
id_text = 'ID: {}_'.format(ids2names[0]) + id_text
|
| 157 |
+
_line_thickness = 1 if obj_id <= 0 else line_thickness
|
| 158 |
+
color = get_color(abs(obj_id))
|
| 159 |
+
cv2.rectangle(
|
| 160 |
+
im,
|
| 161 |
+
intbox[0:2],
|
| 162 |
+
intbox[2:4],
|
| 163 |
+
color=color,
|
| 164 |
+
thickness=line_thickness)
|
| 165 |
+
cv2.putText(
|
| 166 |
+
im,
|
| 167 |
+
id_text, (intbox[0], intbox[1] - 25),
|
| 168 |
+
cv2.FONT_ITALIC,
|
| 169 |
+
text_scale, (0, 255, 255),
|
| 170 |
+
thickness=text_thickness)
|
| 171 |
+
|
| 172 |
+
if scores is not None:
|
| 173 |
+
text = 'score: {:.2f}'.format(float(scores[i]))
|
| 174 |
+
cv2.putText(
|
| 175 |
+
im,
|
| 176 |
+
text, (intbox[0], intbox[1] - 6),
|
| 177 |
+
cv2.FONT_ITALIC,
|
| 178 |
+
text_scale, (0, 255, 0),
|
| 179 |
+
thickness=text_thickness)
|
| 180 |
+
if do_entrance_counting:
|
| 181 |
+
entrance_line = tuple(map(int, entrance))
|
| 182 |
+
cv2.rectangle(
|
| 183 |
+
im,
|
| 184 |
+
entrance_line[0:2],
|
| 185 |
+
entrance_line[2:4],
|
| 186 |
+
color=(0, 255, 255),
|
| 187 |
+
thickness=line_thickness)
|
| 188 |
+
return im
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def plot_tracking_dict(image,
|
| 192 |
+
num_classes,
|
| 193 |
+
tlwhs_dict,
|
| 194 |
+
obj_ids_dict,
|
| 195 |
+
scores_dict,
|
| 196 |
+
frame_id=0,
|
| 197 |
+
fps=0.,
|
| 198 |
+
ids2names=[],
|
| 199 |
+
do_entrance_counting=False,
|
| 200 |
+
do_break_in_counting=False,
|
| 201 |
+
do_illegal_parking_recognition=False,
|
| 202 |
+
illegal_parking_dict=None,
|
| 203 |
+
entrance=None,
|
| 204 |
+
records=None,
|
| 205 |
+
center_traj=None):
|
| 206 |
+
im = np.ascontiguousarray(np.copy(image))
|
| 207 |
+
im_h, im_w = im.shape[:2]
|
| 208 |
+
if do_break_in_counting or do_illegal_parking_recognition:
|
| 209 |
+
entrance = np.array(entrance[:-1]) # last pair is [im_w, im_h]
|
| 210 |
+
|
| 211 |
+
text_scale = max(0.5, image.shape[1] / 3000.)
|
| 212 |
+
text_thickness = 2
|
| 213 |
+
line_thickness = max(1, int(image.shape[1] / 500.))
|
| 214 |
+
|
| 215 |
+
if num_classes == 1:
|
| 216 |
+
if records is not None:
|
| 217 |
+
start = records[-1].find('Total')
|
| 218 |
+
end = records[-1].find('In')
|
| 219 |
+
cv2.putText(
|
| 220 |
+
im,
|
| 221 |
+
records[-1][start:end], (0, int(40 * text_scale) + 10),
|
| 222 |
+
cv2.FONT_ITALIC,
|
| 223 |
+
text_scale, (0, 0, 255),
|
| 224 |
+
thickness=text_thickness)
|
| 225 |
+
|
| 226 |
+
if num_classes == 1 and do_entrance_counting:
|
| 227 |
+
entrance_line = tuple(map(int, entrance))
|
| 228 |
+
cv2.rectangle(
|
| 229 |
+
im,
|
| 230 |
+
entrance_line[0:2],
|
| 231 |
+
entrance_line[2:4],
|
| 232 |
+
color=(0, 255, 255),
|
| 233 |
+
thickness=line_thickness)
|
| 234 |
+
# find start location for entrance counting data
|
| 235 |
+
start = records[-1].find('In')
|
| 236 |
+
cv2.putText(
|
| 237 |
+
im,
|
| 238 |
+
records[-1][start:-1], (0, int(60 * text_scale) + 10),
|
| 239 |
+
cv2.FONT_ITALIC,
|
| 240 |
+
text_scale, (0, 0, 255),
|
| 241 |
+
thickness=text_thickness)
|
| 242 |
+
|
| 243 |
+
if num_classes == 1 and (do_break_in_counting or
|
| 244 |
+
do_illegal_parking_recognition):
|
| 245 |
+
np_masks = np.zeros((im_h, im_w, 1), np.uint8)
|
| 246 |
+
cv2.fillPoly(np_masks, [entrance], 255)
|
| 247 |
+
|
| 248 |
+
# Draw region mask
|
| 249 |
+
alpha = 0.3
|
| 250 |
+
im = np.array(im).astype('float32')
|
| 251 |
+
mask = np_masks[:, :, 0]
|
| 252 |
+
color_mask = [0, 0, 255]
|
| 253 |
+
idx = np.nonzero(mask)
|
| 254 |
+
color_mask = np.array(color_mask)
|
| 255 |
+
im[idx[0], idx[1], :] *= 1.0 - alpha
|
| 256 |
+
im[idx[0], idx[1], :] += alpha * color_mask
|
| 257 |
+
im = np.array(im).astype('uint8')
|
| 258 |
+
|
| 259 |
+
if do_break_in_counting:
|
| 260 |
+
# find start location for break in counting data
|
| 261 |
+
start = records[-1].find('Break_in')
|
| 262 |
+
cv2.putText(
|
| 263 |
+
im,
|
| 264 |
+
records[-1][start:-1],
|
| 265 |
+
(entrance[0][0] - 10, entrance[0][1] - 10),
|
| 266 |
+
cv2.FONT_ITALIC,
|
| 267 |
+
text_scale, (0, 0, 255),
|
| 268 |
+
thickness=text_thickness)
|
| 269 |
+
|
| 270 |
+
if illegal_parking_dict is not None and len(illegal_parking_dict) != 0:
|
| 271 |
+
for key, value in illegal_parking_dict.items():
|
| 272 |
+
x1, y1, w, h = value['bbox']
|
| 273 |
+
plate = value['plate']
|
| 274 |
+
if plate is None:
|
| 275 |
+
plate = ""
|
| 276 |
+
|
| 277 |
+
# red box
|
| 278 |
+
cv2.rectangle(im, (int(x1), int(y1)),
|
| 279 |
+
(int(x1 + w), int(y1 + h)), (0, 0, 255), 2)
|
| 280 |
+
|
| 281 |
+
cv2.putText(
|
| 282 |
+
im,
|
| 283 |
+
"illegal_parking:" + plate,
|
| 284 |
+
(int(x1) + 5, int(16 * text_scale + y1 + 15)),
|
| 285 |
+
cv2.FONT_ITALIC,
|
| 286 |
+
text_scale * 1.5, (0, 0, 255),
|
| 287 |
+
thickness=text_thickness)
|
| 288 |
+
|
| 289 |
+
for cls_id in range(num_classes):
|
| 290 |
+
tlwhs = tlwhs_dict[cls_id]
|
| 291 |
+
obj_ids = obj_ids_dict[cls_id]
|
| 292 |
+
scores = scores_dict[cls_id]
|
| 293 |
+
cv2.putText(
|
| 294 |
+
im,
|
| 295 |
+
'frame: %d fps: %.2f num: %d' % (frame_id, fps, len(tlwhs)),
|
| 296 |
+
(0, int(15 * text_scale) + 5),
|
| 297 |
+
cv2.FONT_ITALIC,
|
| 298 |
+
text_scale, (0, 0, 255),
|
| 299 |
+
thickness=text_thickness)
|
| 300 |
+
|
| 301 |
+
record_id = set()
|
| 302 |
+
for i, tlwh in enumerate(tlwhs):
|
| 303 |
+
x1, y1, w, h = tlwh
|
| 304 |
+
intbox = tuple(map(int, (x1, y1, x1 + w, y1 + h)))
|
| 305 |
+
center = tuple(map(int, (x1 + w / 2., y1 + h / 2.)))
|
| 306 |
+
obj_id = int(obj_ids[i])
|
| 307 |
+
if center_traj is not None:
|
| 308 |
+
record_id.add(obj_id)
|
| 309 |
+
if obj_id not in center_traj[cls_id]:
|
| 310 |
+
center_traj[cls_id][obj_id] = deque(maxlen=30)
|
| 311 |
+
center_traj[cls_id][obj_id].append(center)
|
| 312 |
+
|
| 313 |
+
id_text = '{}'.format(int(obj_id))
|
| 314 |
+
if ids2names != []:
|
| 315 |
+
id_text = '{}_{}'.format(ids2names[cls_id], id_text)
|
| 316 |
+
else:
|
| 317 |
+
id_text = 'class{}_{}'.format(cls_id, id_text)
|
| 318 |
+
|
| 319 |
+
_line_thickness = 1 if obj_id <= 0 else line_thickness
|
| 320 |
+
|
| 321 |
+
in_region = False
|
| 322 |
+
if do_break_in_counting:
|
| 323 |
+
center_x = min(x1 + w / 2., im_w - 1)
|
| 324 |
+
center_down_y = min(y1 + h, im_h - 1)
|
| 325 |
+
if in_quadrangle([center_x, center_down_y], entrance, im_h,
|
| 326 |
+
im_w):
|
| 327 |
+
in_region = True
|
| 328 |
+
|
| 329 |
+
color = get_color(abs(obj_id)) if in_region == False else (0, 0,
|
| 330 |
+
255)
|
| 331 |
+
cv2.rectangle(
|
| 332 |
+
im,
|
| 333 |
+
intbox[0:2],
|
| 334 |
+
intbox[2:4],
|
| 335 |
+
color=color,
|
| 336 |
+
thickness=line_thickness)
|
| 337 |
+
cv2.putText(
|
| 338 |
+
im,
|
| 339 |
+
id_text, (intbox[0], intbox[1] - 25),
|
| 340 |
+
cv2.FONT_ITALIC,
|
| 341 |
+
text_scale,
|
| 342 |
+
color,
|
| 343 |
+
thickness=text_thickness)
|
| 344 |
+
|
| 345 |
+
if do_break_in_counting and in_region:
|
| 346 |
+
cv2.putText(
|
| 347 |
+
im,
|
| 348 |
+
'Break in now.', (intbox[0], intbox[1] - 50),
|
| 349 |
+
cv2.FONT_ITALIC,
|
| 350 |
+
text_scale, (0, 0, 255),
|
| 351 |
+
thickness=text_thickness)
|
| 352 |
+
|
| 353 |
+
if scores is not None:
|
| 354 |
+
text = 'score: {:.2f}'.format(float(scores[i]))
|
| 355 |
+
cv2.putText(
|
| 356 |
+
im,
|
| 357 |
+
text, (intbox[0], intbox[1] - 6),
|
| 358 |
+
cv2.FONT_ITALIC,
|
| 359 |
+
text_scale,
|
| 360 |
+
color,
|
| 361 |
+
thickness=text_thickness)
|
| 362 |
+
if center_traj is not None:
|
| 363 |
+
for traj in center_traj:
|
| 364 |
+
for i in traj.keys():
|
| 365 |
+
if i not in record_id:
|
| 366 |
+
continue
|
| 367 |
+
for point in traj[i]:
|
| 368 |
+
cv2.circle(im, point, 3, (0, 0, 255), -1)
|
| 369 |
+
return im
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def in_quadrangle(point, entrance, im_h, im_w):
|
| 373 |
+
mask = np.zeros((im_h, im_w, 1), np.uint8)
|
| 374 |
+
cv2.fillPoly(mask, [entrance], 255)
|
| 375 |
+
p = tuple(map(int, point))
|
| 376 |
+
if mask[p[1], p[0], :] > 0:
|
| 377 |
+
return True
|
| 378 |
+
else:
|
| 379 |
+
return False
|
pptracking/python/mot_jde_infer.py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import time
|
| 17 |
+
import yaml
|
| 18 |
+
import cv2
|
| 19 |
+
import numpy as np
|
| 20 |
+
from collections import defaultdict
|
| 21 |
+
import paddle
|
| 22 |
+
|
| 23 |
+
from benchmark_utils import PaddleInferBenchmark
|
| 24 |
+
from preprocess import decode_image
|
| 25 |
+
from mot_utils import argsparser, Timer, get_current_memory_mb
|
| 26 |
+
from det_infer import Detector, get_test_images, print_arguments, bench_log, PredictConfig
|
| 27 |
+
|
| 28 |
+
# add python path
|
| 29 |
+
import sys
|
| 30 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'] * 2)))
|
| 31 |
+
sys.path.insert(0, parent_path)
|
| 32 |
+
|
| 33 |
+
from mot import JDETracker
|
| 34 |
+
from mot.utils import MOTTimer, write_mot_results, flow_statistic
|
| 35 |
+
from mot.visualize import plot_tracking, plot_tracking_dict
|
| 36 |
+
|
| 37 |
+
# Global dictionary
|
| 38 |
+
MOT_JDE_SUPPORT_MODELS = {
|
| 39 |
+
'JDE',
|
| 40 |
+
'FairMOT',
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class JDE_Detector(Detector):
|
| 45 |
+
"""
|
| 46 |
+
Args:
|
| 47 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 48 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 49 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 50 |
+
batch_size (int): size of pre batch in inference
|
| 51 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 52 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 53 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 54 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 55 |
+
calibration, trt_calib_mode need to set True
|
| 56 |
+
cpu_threads (int): cpu threads
|
| 57 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 58 |
+
output_dir (string): The path of output, default as 'output'
|
| 59 |
+
threshold (float): Score threshold of the detected bbox, default as 0.5
|
| 60 |
+
save_images (bool): Whether to save visualization image results, default as False
|
| 61 |
+
save_mot_txts (bool): Whether to save tracking results (txt), default as False
|
| 62 |
+
draw_center_traj (bool): Whether drawing the trajectory of center, default as False
|
| 63 |
+
secs_interval (int): The seconds interval to count after tracking, default as 10
|
| 64 |
+
skip_frame_num (int): Skip frame num to get faster MOT results, default as -1
|
| 65 |
+
do_entrance_counting(bool): Whether counting the numbers of identifiers entering
|
| 66 |
+
or getting out from the entrance, default as False,only support single class
|
| 67 |
+
counting in MOT.
|
| 68 |
+
do_break_in_counting(bool): Whether counting the numbers of identifiers break in
|
| 69 |
+
the area, default as False,only support single class counting in MOT,
|
| 70 |
+
and the video should be taken by a static camera.
|
| 71 |
+
region_type (str): Area type for entrance counting or break in counting, 'horizontal'
|
| 72 |
+
and 'vertical' used when do entrance counting. 'custom' used when do break in counting.
|
| 73 |
+
Note that only support single-class MOT, and the video should be taken by a static camera.
|
| 74 |
+
region_polygon (list): Clockwise point coords (x0,y0,x1,y1...) of polygon of area when
|
| 75 |
+
do_break_in_counting. Note that only support single-class MOT and
|
| 76 |
+
the video should be taken by a static camera.
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
def __init__(self,
|
| 80 |
+
model_dir,
|
| 81 |
+
tracker_config=None,
|
| 82 |
+
device='CPU',
|
| 83 |
+
run_mode='paddle',
|
| 84 |
+
batch_size=1,
|
| 85 |
+
trt_min_shape=1,
|
| 86 |
+
trt_max_shape=1088,
|
| 87 |
+
trt_opt_shape=608,
|
| 88 |
+
trt_calib_mode=False,
|
| 89 |
+
cpu_threads=1,
|
| 90 |
+
enable_mkldnn=False,
|
| 91 |
+
output_dir='output',
|
| 92 |
+
threshold=0.5,
|
| 93 |
+
save_images=False,
|
| 94 |
+
save_mot_txts=False,
|
| 95 |
+
draw_center_traj=False,
|
| 96 |
+
secs_interval=10,
|
| 97 |
+
skip_frame_num=-1,
|
| 98 |
+
do_entrance_counting=False,
|
| 99 |
+
do_break_in_counting=False,
|
| 100 |
+
region_type='horizontal',
|
| 101 |
+
region_polygon=[]):
|
| 102 |
+
super(JDE_Detector, self).__init__(
|
| 103 |
+
model_dir=model_dir,
|
| 104 |
+
device=device,
|
| 105 |
+
run_mode=run_mode,
|
| 106 |
+
batch_size=batch_size,
|
| 107 |
+
trt_min_shape=trt_min_shape,
|
| 108 |
+
trt_max_shape=trt_max_shape,
|
| 109 |
+
trt_opt_shape=trt_opt_shape,
|
| 110 |
+
trt_calib_mode=trt_calib_mode,
|
| 111 |
+
cpu_threads=cpu_threads,
|
| 112 |
+
enable_mkldnn=enable_mkldnn,
|
| 113 |
+
output_dir=output_dir,
|
| 114 |
+
threshold=threshold, )
|
| 115 |
+
self.save_images = save_images
|
| 116 |
+
self.save_mot_txts = save_mot_txts
|
| 117 |
+
self.draw_center_traj = draw_center_traj
|
| 118 |
+
self.secs_interval = secs_interval
|
| 119 |
+
self.skip_frame_num = skip_frame_num
|
| 120 |
+
self.do_entrance_counting = do_entrance_counting
|
| 121 |
+
self.do_break_in_counting = do_break_in_counting
|
| 122 |
+
self.region_type = region_type
|
| 123 |
+
self.region_polygon = region_polygon
|
| 124 |
+
if self.region_type == 'custom':
|
| 125 |
+
assert len(
|
| 126 |
+
self.region_polygon
|
| 127 |
+
) > 6, 'region_type is custom, region_polygon should be at least 3 pairs of point coords.'
|
| 128 |
+
|
| 129 |
+
assert batch_size == 1, "MOT model only supports batch_size=1."
|
| 130 |
+
self.det_times = Timer(with_tracker=True)
|
| 131 |
+
self.num_classes = len(self.pred_config.labels)
|
| 132 |
+
if self.skip_frame_num > 1:
|
| 133 |
+
self.previous_det_result = None
|
| 134 |
+
|
| 135 |
+
# tracker config
|
| 136 |
+
assert self.pred_config.tracker, "The exported JDE Detector model should have tracker."
|
| 137 |
+
cfg = self.pred_config.tracker
|
| 138 |
+
min_box_area = cfg.get('min_box_area', 0.0)
|
| 139 |
+
vertical_ratio = cfg.get('vertical_ratio', 0.0)
|
| 140 |
+
conf_thres = cfg.get('conf_thres', 0.0)
|
| 141 |
+
tracked_thresh = cfg.get('tracked_thresh', 0.7)
|
| 142 |
+
metric_type = cfg.get('metric_type', 'euclidean')
|
| 143 |
+
|
| 144 |
+
self.tracker = JDETracker(
|
| 145 |
+
num_classes=self.num_classes,
|
| 146 |
+
min_box_area=min_box_area,
|
| 147 |
+
vertical_ratio=vertical_ratio,
|
| 148 |
+
conf_thres=conf_thres,
|
| 149 |
+
tracked_thresh=tracked_thresh,
|
| 150 |
+
metric_type=metric_type)
|
| 151 |
+
|
| 152 |
+
def postprocess(self, inputs, result):
|
| 153 |
+
# postprocess output of predictor
|
| 154 |
+
np_boxes = result['pred_dets']
|
| 155 |
+
if np_boxes.shape[0] <= 0:
|
| 156 |
+
print('[WARNNING] No object detected.')
|
| 157 |
+
result = {'pred_dets': np.zeros([0, 6]), 'pred_embs': None}
|
| 158 |
+
result = {k: v for k, v in result.items() if v is not None}
|
| 159 |
+
return result
|
| 160 |
+
|
| 161 |
+
def tracking(self, det_results):
|
| 162 |
+
pred_dets = det_results['pred_dets'] # cls_id, score, x0, y0, x1, y1
|
| 163 |
+
pred_embs = det_results['pred_embs']
|
| 164 |
+
online_targets_dict = self.tracker.update(pred_dets, pred_embs)
|
| 165 |
+
|
| 166 |
+
online_tlwhs = defaultdict(list)
|
| 167 |
+
online_scores = defaultdict(list)
|
| 168 |
+
online_ids = defaultdict(list)
|
| 169 |
+
for cls_id in range(self.num_classes):
|
| 170 |
+
online_targets = online_targets_dict[cls_id]
|
| 171 |
+
for t in online_targets:
|
| 172 |
+
tlwh = t.tlwh
|
| 173 |
+
tid = t.track_id
|
| 174 |
+
tscore = t.score
|
| 175 |
+
if tlwh[2] * tlwh[3] <= self.tracker.min_box_area: continue
|
| 176 |
+
if self.tracker.vertical_ratio > 0 and tlwh[2] / tlwh[
|
| 177 |
+
3] > self.tracker.vertical_ratio:
|
| 178 |
+
continue
|
| 179 |
+
online_tlwhs[cls_id].append(tlwh)
|
| 180 |
+
online_ids[cls_id].append(tid)
|
| 181 |
+
online_scores[cls_id].append(tscore)
|
| 182 |
+
return online_tlwhs, online_scores, online_ids
|
| 183 |
+
|
| 184 |
+
def predict(self, repeats=1):
|
| 185 |
+
'''
|
| 186 |
+
Args:
|
| 187 |
+
repeats (int): repeats number for prediction
|
| 188 |
+
Returns:
|
| 189 |
+
result (dict): include 'pred_dets': np.ndarray: shape:[N,6], N: number of box,
|
| 190 |
+
matix element:[class, score, x_min, y_min, x_max, y_max]
|
| 191 |
+
FairMOT(JDE)'s result include 'pred_embs': np.ndarray:
|
| 192 |
+
shape: [N, 128]
|
| 193 |
+
'''
|
| 194 |
+
# model prediction
|
| 195 |
+
np_pred_dets, np_pred_embs = None, None
|
| 196 |
+
for i in range(repeats):
|
| 197 |
+
self.predictor.run()
|
| 198 |
+
output_names = self.predictor.get_output_names()
|
| 199 |
+
boxes_tensor = self.predictor.get_output_handle(output_names[0])
|
| 200 |
+
np_pred_dets = boxes_tensor.copy_to_cpu()
|
| 201 |
+
embs_tensor = self.predictor.get_output_handle(output_names[1])
|
| 202 |
+
np_pred_embs = embs_tensor.copy_to_cpu()
|
| 203 |
+
|
| 204 |
+
result = dict(pred_dets=np_pred_dets, pred_embs=np_pred_embs)
|
| 205 |
+
return result
|
| 206 |
+
|
| 207 |
+
def predict_image(self,
|
| 208 |
+
image_list,
|
| 209 |
+
run_benchmark=False,
|
| 210 |
+
repeats=1,
|
| 211 |
+
visual=True,
|
| 212 |
+
seq_name=None,
|
| 213 |
+
reuse_det_result=False):
|
| 214 |
+
mot_results = []
|
| 215 |
+
num_classes = self.num_classes
|
| 216 |
+
image_list.sort()
|
| 217 |
+
ids2names = self.pred_config.labels
|
| 218 |
+
data_type = 'mcmot' if num_classes > 1 else 'mot'
|
| 219 |
+
for frame_id, img_file in enumerate(image_list):
|
| 220 |
+
batch_image_list = [img_file] # bs=1 in MOT model
|
| 221 |
+
if run_benchmark:
|
| 222 |
+
# preprocess
|
| 223 |
+
inputs = self.preprocess(batch_image_list) # warmup
|
| 224 |
+
self.det_times.preprocess_time_s.start()
|
| 225 |
+
inputs = self.preprocess(batch_image_list)
|
| 226 |
+
self.det_times.preprocess_time_s.end()
|
| 227 |
+
|
| 228 |
+
# model prediction
|
| 229 |
+
result_warmup = self.predict(repeats=repeats) # warmup
|
| 230 |
+
self.det_times.inference_time_s.start()
|
| 231 |
+
result = self.predict(repeats=repeats)
|
| 232 |
+
self.det_times.inference_time_s.end(repeats=repeats)
|
| 233 |
+
|
| 234 |
+
# postprocess
|
| 235 |
+
result_warmup = self.postprocess(inputs, result) # warmup
|
| 236 |
+
self.det_times.postprocess_time_s.start()
|
| 237 |
+
det_result = self.postprocess(inputs, result)
|
| 238 |
+
self.det_times.postprocess_time_s.end()
|
| 239 |
+
|
| 240 |
+
# tracking
|
| 241 |
+
result_warmup = self.tracking(det_result)
|
| 242 |
+
self.det_times.tracking_time_s.start()
|
| 243 |
+
online_tlwhs, online_scores, online_ids = self.tracking(
|
| 244 |
+
det_result)
|
| 245 |
+
self.det_times.tracking_time_s.end()
|
| 246 |
+
self.det_times.img_num += 1
|
| 247 |
+
|
| 248 |
+
cm, gm, gu = get_current_memory_mb()
|
| 249 |
+
self.cpu_mem += cm
|
| 250 |
+
self.gpu_mem += gm
|
| 251 |
+
self.gpu_util += gu
|
| 252 |
+
|
| 253 |
+
else:
|
| 254 |
+
self.det_times.preprocess_time_s.start()
|
| 255 |
+
if not reuse_det_result:
|
| 256 |
+
inputs = self.preprocess(batch_image_list)
|
| 257 |
+
self.det_times.preprocess_time_s.end()
|
| 258 |
+
|
| 259 |
+
self.det_times.inference_time_s.start()
|
| 260 |
+
if not reuse_det_result:
|
| 261 |
+
result = self.predict()
|
| 262 |
+
self.det_times.inference_time_s.end()
|
| 263 |
+
|
| 264 |
+
self.det_times.postprocess_time_s.start()
|
| 265 |
+
if not reuse_det_result:
|
| 266 |
+
det_result = self.postprocess(inputs, result)
|
| 267 |
+
self.previous_det_result = det_result
|
| 268 |
+
else:
|
| 269 |
+
assert self.previous_det_result is not None
|
| 270 |
+
det_result = self.previous_det_result
|
| 271 |
+
self.det_times.postprocess_time_s.end()
|
| 272 |
+
|
| 273 |
+
# tracking process
|
| 274 |
+
self.det_times.tracking_time_s.start()
|
| 275 |
+
online_tlwhs, online_scores, online_ids = self.tracking(
|
| 276 |
+
det_result)
|
| 277 |
+
self.det_times.tracking_time_s.end()
|
| 278 |
+
self.det_times.img_num += 1
|
| 279 |
+
|
| 280 |
+
if visual:
|
| 281 |
+
if len(image_list) > 1 and frame_id % 10 == 0:
|
| 282 |
+
print('Tracking frame {}'.format(frame_id))
|
| 283 |
+
frame, _ = decode_image(img_file, {})
|
| 284 |
+
|
| 285 |
+
im = plot_tracking_dict(
|
| 286 |
+
frame,
|
| 287 |
+
num_classes,
|
| 288 |
+
online_tlwhs,
|
| 289 |
+
online_ids,
|
| 290 |
+
online_scores,
|
| 291 |
+
frame_id=frame_id,
|
| 292 |
+
ids2names=ids2names)
|
| 293 |
+
if seq_name is None:
|
| 294 |
+
seq_name = image_list[0].split('/')[-2]
|
| 295 |
+
save_dir = os.path.join(self.output_dir, seq_name)
|
| 296 |
+
if not os.path.exists(save_dir):
|
| 297 |
+
os.makedirs(save_dir)
|
| 298 |
+
cv2.imwrite(
|
| 299 |
+
os.path.join(save_dir, '{:05d}.jpg'.format(frame_id)), im)
|
| 300 |
+
|
| 301 |
+
mot_results.append([online_tlwhs, online_scores, online_ids])
|
| 302 |
+
return mot_results
|
| 303 |
+
|
| 304 |
+
def predict_video(self, video_file, camera_id):
|
| 305 |
+
video_out_name = 'mot_output.mp4'
|
| 306 |
+
if camera_id != -1:
|
| 307 |
+
capture = cv2.VideoCapture(camera_id)
|
| 308 |
+
else:
|
| 309 |
+
capture = cv2.VideoCapture(video_file)
|
| 310 |
+
video_out_name = os.path.split(video_file)[-1]
|
| 311 |
+
# Get Video info : resolution, fps, frame count
|
| 312 |
+
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 313 |
+
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 314 |
+
fps = int(capture.get(cv2.CAP_PROP_FPS))
|
| 315 |
+
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 316 |
+
print("fps: %d, frame_count: %d" % (fps, frame_count))
|
| 317 |
+
|
| 318 |
+
if not os.path.exists(self.output_dir):
|
| 319 |
+
os.makedirs(self.output_dir)
|
| 320 |
+
out_path = os.path.join(self.output_dir, video_out_name)
|
| 321 |
+
video_format = 'mp4v'
|
| 322 |
+
fourcc = cv2.VideoWriter_fourcc(*video_format)
|
| 323 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
|
| 324 |
+
|
| 325 |
+
frame_id = 0
|
| 326 |
+
timer = MOTTimer()
|
| 327 |
+
results = defaultdict(list) # support single class and multi classes
|
| 328 |
+
num_classes = self.num_classes
|
| 329 |
+
data_type = 'mcmot' if num_classes > 1 else 'mot'
|
| 330 |
+
ids2names = self.pred_config.labels
|
| 331 |
+
|
| 332 |
+
center_traj = None
|
| 333 |
+
entrance = None
|
| 334 |
+
records = None
|
| 335 |
+
if self.draw_center_traj:
|
| 336 |
+
center_traj = [{} for i in range(num_classes)]
|
| 337 |
+
if num_classes == 1:
|
| 338 |
+
id_set = set()
|
| 339 |
+
interval_id_set = set()
|
| 340 |
+
in_id_list = list()
|
| 341 |
+
out_id_list = list()
|
| 342 |
+
prev_center = dict()
|
| 343 |
+
records = list()
|
| 344 |
+
if self.do_entrance_counting or self.do_break_in_counting:
|
| 345 |
+
if self.region_type == 'horizontal':
|
| 346 |
+
entrance = [0, height / 2., width, height / 2.]
|
| 347 |
+
elif self.region_type == 'vertical':
|
| 348 |
+
entrance = [width / 2, 0., width / 2, height]
|
| 349 |
+
elif self.region_type == 'custom':
|
| 350 |
+
entrance = []
|
| 351 |
+
assert len(
|
| 352 |
+
self.region_polygon
|
| 353 |
+
) % 2 == 0, "region_polygon should be pairs of coords points when do break_in counting."
|
| 354 |
+
for i in range(0, len(self.region_polygon), 2):
|
| 355 |
+
entrance.append([
|
| 356 |
+
self.region_polygon[i], self.region_polygon[i + 1]
|
| 357 |
+
])
|
| 358 |
+
entrance.append([width, height])
|
| 359 |
+
else:
|
| 360 |
+
raise ValueError("region_type:{} is not supported.".format(
|
| 361 |
+
self.region_type))
|
| 362 |
+
|
| 363 |
+
video_fps = fps
|
| 364 |
+
|
| 365 |
+
while (1):
|
| 366 |
+
ret, frame = capture.read()
|
| 367 |
+
if not ret:
|
| 368 |
+
break
|
| 369 |
+
if frame_id % 10 == 0:
|
| 370 |
+
print('Tracking frame: %d' % (frame_id))
|
| 371 |
+
|
| 372 |
+
timer.tic()
|
| 373 |
+
mot_skip_frame_num = self.skip_frame_num
|
| 374 |
+
reuse_det_result = False
|
| 375 |
+
if mot_skip_frame_num > 1 and frame_id > 0 and frame_id % mot_skip_frame_num > 0:
|
| 376 |
+
reuse_det_result = True
|
| 377 |
+
seq_name = video_out_name.split('.')[0]
|
| 378 |
+
mot_results = self.predict_image(
|
| 379 |
+
[frame],
|
| 380 |
+
visual=False,
|
| 381 |
+
seq_name=seq_name,
|
| 382 |
+
reuse_det_result=reuse_det_result)
|
| 383 |
+
timer.toc()
|
| 384 |
+
|
| 385 |
+
online_tlwhs, online_scores, online_ids = mot_results[0]
|
| 386 |
+
for cls_id in range(num_classes):
|
| 387 |
+
results[cls_id].append(
|
| 388 |
+
(frame_id + 1, online_tlwhs[cls_id], online_scores[cls_id],
|
| 389 |
+
online_ids[cls_id]))
|
| 390 |
+
|
| 391 |
+
# NOTE: just implement flow statistic for single class
|
| 392 |
+
if num_classes == 1:
|
| 393 |
+
result = (frame_id + 1, online_tlwhs[0], online_scores[0],
|
| 394 |
+
online_ids[0])
|
| 395 |
+
statistic = flow_statistic(
|
| 396 |
+
result,
|
| 397 |
+
self.secs_interval,
|
| 398 |
+
self.do_entrance_counting,
|
| 399 |
+
self.do_break_in_counting,
|
| 400 |
+
self.region_type,
|
| 401 |
+
video_fps,
|
| 402 |
+
entrance,
|
| 403 |
+
id_set,
|
| 404 |
+
interval_id_set,
|
| 405 |
+
in_id_list,
|
| 406 |
+
out_id_list,
|
| 407 |
+
prev_center,
|
| 408 |
+
records,
|
| 409 |
+
data_type,
|
| 410 |
+
ids2names=self.pred_config.labels)
|
| 411 |
+
records = statistic['records']
|
| 412 |
+
|
| 413 |
+
fps = 1. / timer.duration
|
| 414 |
+
im = plot_tracking_dict(
|
| 415 |
+
frame,
|
| 416 |
+
num_classes,
|
| 417 |
+
online_tlwhs,
|
| 418 |
+
online_ids,
|
| 419 |
+
online_scores,
|
| 420 |
+
frame_id=frame_id,
|
| 421 |
+
fps=fps,
|
| 422 |
+
ids2names=ids2names,
|
| 423 |
+
do_entrance_counting=self.do_entrance_counting,
|
| 424 |
+
entrance=entrance,
|
| 425 |
+
records=records,
|
| 426 |
+
center_traj=center_traj)
|
| 427 |
+
|
| 428 |
+
writer.write(im)
|
| 429 |
+
if camera_id != -1:
|
| 430 |
+
cv2.imshow('Mask Detection', im)
|
| 431 |
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
| 432 |
+
break
|
| 433 |
+
frame_id += 1
|
| 434 |
+
|
| 435 |
+
if self.save_mot_txts:
|
| 436 |
+
result_filename = os.path.join(
|
| 437 |
+
self.output_dir, video_out_name.split('.')[-2] + '.txt')
|
| 438 |
+
|
| 439 |
+
write_mot_results(result_filename, results, data_type, num_classes)
|
| 440 |
+
|
| 441 |
+
if num_classes == 1:
|
| 442 |
+
result_filename = os.path.join(
|
| 443 |
+
self.output_dir,
|
| 444 |
+
video_out_name.split('.')[-2] + '_flow_statistic.txt')
|
| 445 |
+
f = open(result_filename, 'w')
|
| 446 |
+
for line in records:
|
| 447 |
+
f.write(line)
|
| 448 |
+
print('Flow statistic save in {}'.format(result_filename))
|
| 449 |
+
f.close()
|
| 450 |
+
|
| 451 |
+
writer.release()
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
def main():
|
| 455 |
+
detector = JDE_Detector(
|
| 456 |
+
FLAGS.model_dir,
|
| 457 |
+
tracker_config=None,
|
| 458 |
+
device=FLAGS.device,
|
| 459 |
+
run_mode=FLAGS.run_mode,
|
| 460 |
+
batch_size=1,
|
| 461 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 462 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 463 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 464 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 465 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 466 |
+
enable_mkldnn=FLAGS.enable_mkldnn,
|
| 467 |
+
output_dir=FLAGS.output_dir,
|
| 468 |
+
threshold=FLAGS.threshold,
|
| 469 |
+
save_images=FLAGS.save_images,
|
| 470 |
+
save_mot_txts=FLAGS.save_mot_txts,
|
| 471 |
+
draw_center_traj=FLAGS.draw_center_traj,
|
| 472 |
+
secs_interval=FLAGS.secs_interval,
|
| 473 |
+
skip_frame_num=FLAGS.skip_frame_num,
|
| 474 |
+
do_entrance_counting=FLAGS.do_entrance_counting,
|
| 475 |
+
do_break_in_counting=FLAGS.do_break_in_counting,
|
| 476 |
+
region_type=FLAGS.region_type,
|
| 477 |
+
region_polygon=FLAGS.region_polygon)
|
| 478 |
+
|
| 479 |
+
# predict from video file or camera video stream
|
| 480 |
+
if FLAGS.video_file is not None or FLAGS.camera_id != -1:
|
| 481 |
+
detector.predict_video(FLAGS.video_file, FLAGS.camera_id)
|
| 482 |
+
else:
|
| 483 |
+
# predict from image
|
| 484 |
+
img_list = get_test_images(FLAGS.image_dir, FLAGS.image_file)
|
| 485 |
+
detector.predict_image(img_list, FLAGS.run_benchmark, repeats=10)
|
| 486 |
+
|
| 487 |
+
if not FLAGS.run_benchmark:
|
| 488 |
+
detector.det_times.info(average=True)
|
| 489 |
+
else:
|
| 490 |
+
mode = FLAGS.run_mode
|
| 491 |
+
model_dir = FLAGS.model_dir
|
| 492 |
+
model_info = {
|
| 493 |
+
'model_name': model_dir.strip('/').split('/')[-1],
|
| 494 |
+
'precision': mode.split('_')[-1]
|
| 495 |
+
}
|
| 496 |
+
bench_log(detector, img_list, model_info, name='MOT')
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
if __name__ == '__main__':
|
| 500 |
+
paddle.enable_static()
|
| 501 |
+
parser = argsparser()
|
| 502 |
+
FLAGS = parser.parse_args()
|
| 503 |
+
print_arguments(FLAGS)
|
| 504 |
+
FLAGS.device = FLAGS.device.upper()
|
| 505 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 506 |
+
], "device should be CPU, GPU or XPU"
|
| 507 |
+
|
| 508 |
+
main()
|
pptracking/python/mot_sde_infer.py
ADDED
|
@@ -0,0 +1,882 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import time
|
| 17 |
+
import yaml
|
| 18 |
+
import cv2
|
| 19 |
+
import re
|
| 20 |
+
import glob
|
| 21 |
+
import numpy as np
|
| 22 |
+
from collections import defaultdict
|
| 23 |
+
import paddle
|
| 24 |
+
|
| 25 |
+
from benchmark_utils import PaddleInferBenchmark
|
| 26 |
+
from preprocess import decode_image
|
| 27 |
+
|
| 28 |
+
# add python path
|
| 29 |
+
import sys
|
| 30 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'])))
|
| 31 |
+
sys.path.insert(0, parent_path)
|
| 32 |
+
|
| 33 |
+
from det_infer import Detector, get_test_images, print_arguments, bench_log, PredictConfig, load_predictor
|
| 34 |
+
from mot_utils import argsparser, Timer, get_current_memory_mb, video2frames, _is_valid_video
|
| 35 |
+
from mot.tracker import JDETracker, DeepSORTTracker, OCSORTTracker
|
| 36 |
+
from mot.utils import MOTTimer, write_mot_results, get_crops, clip_box, flow_statistic
|
| 37 |
+
from mot.visualize import plot_tracking, plot_tracking_dict
|
| 38 |
+
|
| 39 |
+
from mot.mtmct.utils import parse_bias
|
| 40 |
+
from mot.mtmct.postprocess import trajectory_fusion, sub_cluster, gen_res, print_mtmct_result
|
| 41 |
+
from mot.mtmct.postprocess import get_mtmct_matching_results, save_mtmct_crops, save_mtmct_vis_results
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class SDE_Detector(Detector):
|
| 45 |
+
"""
|
| 46 |
+
Args:
|
| 47 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 48 |
+
tracker_config (str): tracker config path
|
| 49 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 50 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 51 |
+
batch_size (int): size of pre batch in inference
|
| 52 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 53 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 54 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 55 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 56 |
+
calibration, trt_calib_mode need to set True
|
| 57 |
+
cpu_threads (int): cpu threads
|
| 58 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 59 |
+
output_dir (string): The path of output, default as 'output'
|
| 60 |
+
threshold (float): Score threshold of the detected bbox, default as 0.5
|
| 61 |
+
save_images (bool): Whether to save visualization image results, default as False
|
| 62 |
+
save_mot_txts (bool): Whether to save tracking results (txt), default as False
|
| 63 |
+
draw_center_traj (bool): Whether drawing the trajectory of center, default as False
|
| 64 |
+
secs_interval (int): The seconds interval to count after tracking, default as 10
|
| 65 |
+
skip_frame_num (int): Skip frame num to get faster MOT results, default as -1
|
| 66 |
+
do_entrance_counting(bool): Whether counting the numbers of identifiers entering
|
| 67 |
+
or getting out from the entrance, default as False,only support single class
|
| 68 |
+
counting in MOT, and the video should be taken by a static camera.
|
| 69 |
+
do_break_in_counting(bool): Whether counting the numbers of identifiers break in
|
| 70 |
+
the area, default as False,only support single class counting in MOT,
|
| 71 |
+
and the video should be taken by a static camera.
|
| 72 |
+
region_type (str): Area type for entrance counting or break in counting, 'horizontal'
|
| 73 |
+
and 'vertical' used when do entrance counting. 'custom' used when do break in counting.
|
| 74 |
+
Note that only support single-class MOT, and the video should be taken by a static camera.
|
| 75 |
+
region_polygon (list): Clockwise point coords (x0,y0,x1,y1...) of polygon of area when
|
| 76 |
+
do_break_in_counting. Note that only support single-class MOT and
|
| 77 |
+
the video should be taken by a static camera.
|
| 78 |
+
reid_model_dir (str): reid model dir, default None for ByteTrack, but set for DeepSORT
|
| 79 |
+
mtmct_dir (str): MTMCT dir, default None, set for doing MTMCT
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
def __init__(self,
|
| 83 |
+
model_dir,
|
| 84 |
+
tracker_config,
|
| 85 |
+
device='CPU',
|
| 86 |
+
run_mode='paddle',
|
| 87 |
+
batch_size=1,
|
| 88 |
+
trt_min_shape=1,
|
| 89 |
+
trt_max_shape=1280,
|
| 90 |
+
trt_opt_shape=640,
|
| 91 |
+
trt_calib_mode=False,
|
| 92 |
+
cpu_threads=1,
|
| 93 |
+
enable_mkldnn=False,
|
| 94 |
+
output_dir='output',
|
| 95 |
+
threshold=0.5,
|
| 96 |
+
save_images=False,
|
| 97 |
+
save_mot_txts=False,
|
| 98 |
+
draw_center_traj=False,
|
| 99 |
+
secs_interval=10,
|
| 100 |
+
skip_frame_num=-1,
|
| 101 |
+
do_entrance_counting=False,
|
| 102 |
+
do_break_in_counting=False,
|
| 103 |
+
region_type='horizontal',
|
| 104 |
+
region_polygon=[],
|
| 105 |
+
reid_model_dir=None,
|
| 106 |
+
mtmct_dir=None):
|
| 107 |
+
super(SDE_Detector, self).__init__(
|
| 108 |
+
model_dir=model_dir,
|
| 109 |
+
device=device,
|
| 110 |
+
run_mode=run_mode,
|
| 111 |
+
batch_size=batch_size,
|
| 112 |
+
trt_min_shape=trt_min_shape,
|
| 113 |
+
trt_max_shape=trt_max_shape,
|
| 114 |
+
trt_opt_shape=trt_opt_shape,
|
| 115 |
+
trt_calib_mode=trt_calib_mode,
|
| 116 |
+
cpu_threads=cpu_threads,
|
| 117 |
+
enable_mkldnn=enable_mkldnn,
|
| 118 |
+
output_dir=output_dir,
|
| 119 |
+
threshold=threshold, )
|
| 120 |
+
self.save_images = save_images
|
| 121 |
+
self.save_mot_txts = save_mot_txts
|
| 122 |
+
self.draw_center_traj = draw_center_traj
|
| 123 |
+
self.secs_interval = secs_interval
|
| 124 |
+
self.skip_frame_num = skip_frame_num
|
| 125 |
+
self.do_entrance_counting = do_entrance_counting
|
| 126 |
+
self.do_break_in_counting = do_break_in_counting
|
| 127 |
+
self.region_type = region_type
|
| 128 |
+
self.region_polygon = region_polygon
|
| 129 |
+
if self.region_type == 'custom':
|
| 130 |
+
assert len(
|
| 131 |
+
self.region_polygon
|
| 132 |
+
) > 6, 'region_type is custom, region_polygon should be at least 3 pairs of point coords.'
|
| 133 |
+
|
| 134 |
+
assert batch_size == 1, "MOT model only supports batch_size=1."
|
| 135 |
+
self.det_times = Timer(with_tracker=True)
|
| 136 |
+
self.num_classes = len(self.pred_config.labels)
|
| 137 |
+
if self.skip_frame_num > 1:
|
| 138 |
+
self.previous_det_result = None
|
| 139 |
+
|
| 140 |
+
# reid config
|
| 141 |
+
self.use_reid = False if reid_model_dir is None else True
|
| 142 |
+
if self.use_reid:
|
| 143 |
+
self.reid_pred_config = self.set_config(reid_model_dir)
|
| 144 |
+
self.reid_predictor, self.config = load_predictor(
|
| 145 |
+
reid_model_dir,
|
| 146 |
+
run_mode=run_mode,
|
| 147 |
+
batch_size=50, # reid_batch_size
|
| 148 |
+
min_subgraph_size=self.reid_pred_config.min_subgraph_size,
|
| 149 |
+
device=device,
|
| 150 |
+
use_dynamic_shape=self.reid_pred_config.use_dynamic_shape,
|
| 151 |
+
trt_min_shape=trt_min_shape,
|
| 152 |
+
trt_max_shape=trt_max_shape,
|
| 153 |
+
trt_opt_shape=trt_opt_shape,
|
| 154 |
+
trt_calib_mode=trt_calib_mode,
|
| 155 |
+
cpu_threads=cpu_threads,
|
| 156 |
+
enable_mkldnn=enable_mkldnn)
|
| 157 |
+
else:
|
| 158 |
+
self.reid_pred_config = None
|
| 159 |
+
self.reid_predictor = None
|
| 160 |
+
|
| 161 |
+
assert tracker_config is not None, 'Note that tracker_config should be set.'
|
| 162 |
+
self.tracker_config = tracker_config
|
| 163 |
+
tracker_cfg = yaml.safe_load(open(self.tracker_config))
|
| 164 |
+
cfg = tracker_cfg[tracker_cfg['type']]
|
| 165 |
+
|
| 166 |
+
# tracker config
|
| 167 |
+
self.use_deepsort_tracker = True if tracker_cfg[
|
| 168 |
+
'type'] == 'DeepSORTTracker' else False
|
| 169 |
+
self.use_ocsort_tracker = True if tracker_cfg[
|
| 170 |
+
'type'] == 'OCSORTTracker' else False
|
| 171 |
+
|
| 172 |
+
if self.use_deepsort_tracker:
|
| 173 |
+
if self.reid_pred_config is not None and hasattr(
|
| 174 |
+
self.reid_pred_config, 'tracker'):
|
| 175 |
+
cfg = self.reid_pred_config.tracker
|
| 176 |
+
budget = cfg.get('budget', 100)
|
| 177 |
+
max_age = cfg.get('max_age', 30)
|
| 178 |
+
max_iou_distance = cfg.get('max_iou_distance', 0.7)
|
| 179 |
+
matching_threshold = cfg.get('matching_threshold', 0.2)
|
| 180 |
+
min_box_area = cfg.get('min_box_area', 0)
|
| 181 |
+
vertical_ratio = cfg.get('vertical_ratio', 0)
|
| 182 |
+
|
| 183 |
+
self.tracker = DeepSORTTracker(
|
| 184 |
+
budget=budget,
|
| 185 |
+
max_age=max_age,
|
| 186 |
+
max_iou_distance=max_iou_distance,
|
| 187 |
+
matching_threshold=matching_threshold,
|
| 188 |
+
min_box_area=min_box_area,
|
| 189 |
+
vertical_ratio=vertical_ratio, )
|
| 190 |
+
|
| 191 |
+
elif self.use_ocsort_tracker:
|
| 192 |
+
det_thresh = cfg.get('det_thresh', 0.4)
|
| 193 |
+
max_age = cfg.get('max_age', 30)
|
| 194 |
+
min_hits = cfg.get('min_hits', 3)
|
| 195 |
+
iou_threshold = cfg.get('iou_threshold', 0.3)
|
| 196 |
+
delta_t = cfg.get('delta_t', 3)
|
| 197 |
+
inertia = cfg.get('inertia', 0.2)
|
| 198 |
+
min_box_area = cfg.get('min_box_area', 0)
|
| 199 |
+
vertical_ratio = cfg.get('vertical_ratio', 0)
|
| 200 |
+
use_byte = cfg.get('use_byte', False)
|
| 201 |
+
|
| 202 |
+
self.tracker = OCSORTTracker(
|
| 203 |
+
det_thresh=det_thresh,
|
| 204 |
+
max_age=max_age,
|
| 205 |
+
min_hits=min_hits,
|
| 206 |
+
iou_threshold=iou_threshold,
|
| 207 |
+
delta_t=delta_t,
|
| 208 |
+
inertia=inertia,
|
| 209 |
+
min_box_area=min_box_area,
|
| 210 |
+
vertical_ratio=vertical_ratio,
|
| 211 |
+
use_byte=use_byte)
|
| 212 |
+
else:
|
| 213 |
+
# use ByteTracker
|
| 214 |
+
use_byte = cfg.get('use_byte', False)
|
| 215 |
+
det_thresh = cfg.get('det_thresh', 0.3)
|
| 216 |
+
min_box_area = cfg.get('min_box_area', 0)
|
| 217 |
+
vertical_ratio = cfg.get('vertical_ratio', 0)
|
| 218 |
+
match_thres = cfg.get('match_thres', 0.9)
|
| 219 |
+
conf_thres = cfg.get('conf_thres', 0.6)
|
| 220 |
+
low_conf_thres = cfg.get('low_conf_thres', 0.1)
|
| 221 |
+
|
| 222 |
+
self.tracker = JDETracker(
|
| 223 |
+
use_byte=use_byte,
|
| 224 |
+
det_thresh=det_thresh,
|
| 225 |
+
num_classes=self.num_classes,
|
| 226 |
+
min_box_area=min_box_area,
|
| 227 |
+
vertical_ratio=vertical_ratio,
|
| 228 |
+
match_thres=match_thres,
|
| 229 |
+
conf_thres=conf_thres,
|
| 230 |
+
low_conf_thres=low_conf_thres, )
|
| 231 |
+
|
| 232 |
+
self.do_mtmct = False if mtmct_dir is None else True
|
| 233 |
+
self.mtmct_dir = mtmct_dir
|
| 234 |
+
|
| 235 |
+
def postprocess(self, inputs, result):
|
| 236 |
+
# postprocess output of predictor
|
| 237 |
+
keep_idx = result['boxes'][:, 1] > self.threshold
|
| 238 |
+
result['boxes'] = result['boxes'][keep_idx]
|
| 239 |
+
np_boxes_num = [len(result['boxes'])]
|
| 240 |
+
if np_boxes_num[0] <= 0:
|
| 241 |
+
print('[WARNNING] No object detected.')
|
| 242 |
+
result = {'boxes': np.zeros([0, 6]), 'boxes_num': [0]}
|
| 243 |
+
result = {k: v for k, v in result.items() if v is not None}
|
| 244 |
+
return result
|
| 245 |
+
|
| 246 |
+
def reidprocess(self, det_results, repeats=1):
|
| 247 |
+
pred_dets = det_results['boxes'] # cls_id, score, x0, y0, x1, y1
|
| 248 |
+
pred_xyxys = pred_dets[:, 2:6]
|
| 249 |
+
|
| 250 |
+
ori_image = det_results['ori_image']
|
| 251 |
+
ori_image_shape = ori_image.shape[:2]
|
| 252 |
+
pred_xyxys, keep_idx = clip_box(pred_xyxys, ori_image_shape)
|
| 253 |
+
|
| 254 |
+
if len(keep_idx[0]) == 0:
|
| 255 |
+
det_results['boxes'] = np.zeros((1, 6), dtype=np.float32)
|
| 256 |
+
det_results['embeddings'] = None
|
| 257 |
+
return det_results
|
| 258 |
+
|
| 259 |
+
pred_dets = pred_dets[keep_idx[0]]
|
| 260 |
+
pred_xyxys = pred_dets[:, 2:6]
|
| 261 |
+
|
| 262 |
+
w, h = self.tracker.input_size
|
| 263 |
+
crops = get_crops(pred_xyxys, ori_image, w, h)
|
| 264 |
+
|
| 265 |
+
# to keep fast speed, only use topk crops
|
| 266 |
+
crops = crops[:50] # reid_batch_size
|
| 267 |
+
det_results['crops'] = np.array(crops).astype('float32')
|
| 268 |
+
det_results['boxes'] = pred_dets[:50]
|
| 269 |
+
|
| 270 |
+
input_names = self.reid_predictor.get_input_names()
|
| 271 |
+
for i in range(len(input_names)):
|
| 272 |
+
input_tensor = self.reid_predictor.get_input_handle(input_names[i])
|
| 273 |
+
input_tensor.copy_from_cpu(det_results[input_names[i]])
|
| 274 |
+
|
| 275 |
+
# model prediction
|
| 276 |
+
for i in range(repeats):
|
| 277 |
+
self.reid_predictor.run()
|
| 278 |
+
output_names = self.reid_predictor.get_output_names()
|
| 279 |
+
feature_tensor = self.reid_predictor.get_output_handle(
|
| 280 |
+
output_names[0])
|
| 281 |
+
pred_embs = feature_tensor.copy_to_cpu()
|
| 282 |
+
|
| 283 |
+
det_results['embeddings'] = pred_embs
|
| 284 |
+
return det_results
|
| 285 |
+
|
| 286 |
+
def tracking(self, det_results):
|
| 287 |
+
pred_dets = det_results['boxes'] # cls_id, score, x0, y0, x1, y1
|
| 288 |
+
pred_embs = det_results.get('embeddings', None)
|
| 289 |
+
|
| 290 |
+
if self.use_deepsort_tracker:
|
| 291 |
+
# use DeepSORTTracker, only support singe class
|
| 292 |
+
self.tracker.predict()
|
| 293 |
+
online_targets = self.tracker.update(pred_dets, pred_embs)
|
| 294 |
+
online_tlwhs, online_scores, online_ids = [], [], []
|
| 295 |
+
if self.do_mtmct:
|
| 296 |
+
online_tlbrs, online_feats = [], []
|
| 297 |
+
for t in online_targets:
|
| 298 |
+
if not t.is_confirmed() or t.time_since_update > 1:
|
| 299 |
+
continue
|
| 300 |
+
tlwh = t.to_tlwh()
|
| 301 |
+
tscore = t.score
|
| 302 |
+
tid = t.track_id
|
| 303 |
+
if self.tracker.vertical_ratio > 0 and tlwh[2] / tlwh[
|
| 304 |
+
3] > self.tracker.vertical_ratio:
|
| 305 |
+
continue
|
| 306 |
+
online_tlwhs.append(tlwh)
|
| 307 |
+
online_scores.append(tscore)
|
| 308 |
+
online_ids.append(tid)
|
| 309 |
+
if self.do_mtmct:
|
| 310 |
+
online_tlbrs.append(t.to_tlbr())
|
| 311 |
+
online_feats.append(t.feat)
|
| 312 |
+
|
| 313 |
+
tracking_outs = {
|
| 314 |
+
'online_tlwhs': online_tlwhs,
|
| 315 |
+
'online_scores': online_scores,
|
| 316 |
+
'online_ids': online_ids,
|
| 317 |
+
}
|
| 318 |
+
if self.do_mtmct:
|
| 319 |
+
seq_name = det_results['seq_name']
|
| 320 |
+
frame_id = det_results['frame_id']
|
| 321 |
+
|
| 322 |
+
tracking_outs['feat_data'] = {}
|
| 323 |
+
for _tlbr, _id, _feat in zip(online_tlbrs, online_ids,
|
| 324 |
+
online_feats):
|
| 325 |
+
feat_data = {}
|
| 326 |
+
feat_data['bbox'] = _tlbr
|
| 327 |
+
feat_data['frame'] = f"{frame_id:06d}"
|
| 328 |
+
feat_data['id'] = _id
|
| 329 |
+
_imgname = f'{seq_name}_{_id}_{frame_id}.jpg'
|
| 330 |
+
feat_data['imgname'] = _imgname
|
| 331 |
+
feat_data['feat'] = _feat
|
| 332 |
+
tracking_outs['feat_data'].update({_imgname: feat_data})
|
| 333 |
+
return tracking_outs
|
| 334 |
+
|
| 335 |
+
elif self.use_ocsort_tracker:
|
| 336 |
+
# use OCSORTTracker, only support singe class
|
| 337 |
+
online_targets = self.tracker.update(pred_dets, pred_embs)
|
| 338 |
+
online_tlwhs = defaultdict(list)
|
| 339 |
+
online_scores = defaultdict(list)
|
| 340 |
+
online_ids = defaultdict(list)
|
| 341 |
+
for t in online_targets:
|
| 342 |
+
tlwh = [t[0], t[1], t[2] - t[0], t[3] - t[1]]
|
| 343 |
+
tscore = float(t[4])
|
| 344 |
+
tid = int(t[5])
|
| 345 |
+
if tlwh[2] * tlwh[3] <= self.tracker.min_box_area: continue
|
| 346 |
+
if self.tracker.vertical_ratio > 0 and tlwh[2] / tlwh[
|
| 347 |
+
3] > self.tracker.vertical_ratio:
|
| 348 |
+
continue
|
| 349 |
+
if tlwh[2] * tlwh[3] > 0:
|
| 350 |
+
online_tlwhs[0].append(tlwh)
|
| 351 |
+
online_ids[0].append(tid)
|
| 352 |
+
online_scores[0].append(tscore)
|
| 353 |
+
tracking_outs = {
|
| 354 |
+
'online_tlwhs': online_tlwhs,
|
| 355 |
+
'online_scores': online_scores,
|
| 356 |
+
'online_ids': online_ids,
|
| 357 |
+
}
|
| 358 |
+
return tracking_outs
|
| 359 |
+
|
| 360 |
+
else:
|
| 361 |
+
# use ByteTracker, support multiple class
|
| 362 |
+
online_tlwhs = defaultdict(list)
|
| 363 |
+
online_scores = defaultdict(list)
|
| 364 |
+
online_ids = defaultdict(list)
|
| 365 |
+
if self.do_mtmct:
|
| 366 |
+
online_tlbrs, online_feats = defaultdict(list), defaultdict(
|
| 367 |
+
list)
|
| 368 |
+
online_targets_dict = self.tracker.update(pred_dets, pred_embs)
|
| 369 |
+
for cls_id in range(self.num_classes):
|
| 370 |
+
online_targets = online_targets_dict[cls_id]
|
| 371 |
+
for t in online_targets:
|
| 372 |
+
tlwh = t.tlwh
|
| 373 |
+
tid = t.track_id
|
| 374 |
+
tscore = t.score
|
| 375 |
+
if tlwh[2] * tlwh[3] <= self.tracker.min_box_area:
|
| 376 |
+
continue
|
| 377 |
+
if self.tracker.vertical_ratio > 0 and tlwh[2] / tlwh[
|
| 378 |
+
3] > self.tracker.vertical_ratio:
|
| 379 |
+
continue
|
| 380 |
+
online_tlwhs[cls_id].append(tlwh)
|
| 381 |
+
online_ids[cls_id].append(tid)
|
| 382 |
+
online_scores[cls_id].append(tscore)
|
| 383 |
+
if self.do_mtmct:
|
| 384 |
+
online_tlbrs[cls_id].append(t.tlbr)
|
| 385 |
+
online_feats[cls_id].append(t.curr_feat)
|
| 386 |
+
|
| 387 |
+
if self.do_mtmct:
|
| 388 |
+
assert self.num_classes == 1, 'MTMCT only support single class.'
|
| 389 |
+
tracking_outs = {
|
| 390 |
+
'online_tlwhs': online_tlwhs[0],
|
| 391 |
+
'online_scores': online_scores[0],
|
| 392 |
+
'online_ids': online_ids[0],
|
| 393 |
+
}
|
| 394 |
+
seq_name = det_results['seq_name']
|
| 395 |
+
frame_id = det_results['frame_id']
|
| 396 |
+
tracking_outs['feat_data'] = {}
|
| 397 |
+
for _tlbr, _id, _feat in zip(online_tlbrs[0], online_ids[0],
|
| 398 |
+
online_feats[0]):
|
| 399 |
+
feat_data = {}
|
| 400 |
+
feat_data['bbox'] = _tlbr
|
| 401 |
+
feat_data['frame'] = f"{frame_id:06d}"
|
| 402 |
+
feat_data['id'] = _id
|
| 403 |
+
_imgname = f'{seq_name}_{_id}_{frame_id}.jpg'
|
| 404 |
+
feat_data['imgname'] = _imgname
|
| 405 |
+
feat_data['feat'] = _feat
|
| 406 |
+
tracking_outs['feat_data'].update({_imgname: feat_data})
|
| 407 |
+
return tracking_outs
|
| 408 |
+
|
| 409 |
+
else:
|
| 410 |
+
tracking_outs = {
|
| 411 |
+
'online_tlwhs': online_tlwhs,
|
| 412 |
+
'online_scores': online_scores,
|
| 413 |
+
'online_ids': online_ids,
|
| 414 |
+
}
|
| 415 |
+
return tracking_outs
|
| 416 |
+
|
| 417 |
+
def predict_image(self,
|
| 418 |
+
image_list,
|
| 419 |
+
run_benchmark=False,
|
| 420 |
+
repeats=1,
|
| 421 |
+
visual=True,
|
| 422 |
+
seq_name=None,
|
| 423 |
+
reuse_det_result=False):
|
| 424 |
+
num_classes = self.num_classes
|
| 425 |
+
image_list.sort()
|
| 426 |
+
ids2names = self.pred_config.labels
|
| 427 |
+
if self.do_mtmct:
|
| 428 |
+
mot_features_dict = {} # cid_tid_fid feats
|
| 429 |
+
else:
|
| 430 |
+
mot_results = []
|
| 431 |
+
for frame_id, img_file in enumerate(image_list):
|
| 432 |
+
if self.do_mtmct:
|
| 433 |
+
if frame_id % 10 == 0:
|
| 434 |
+
print('Tracking frame: %d' % (frame_id))
|
| 435 |
+
batch_image_list = [img_file] # bs=1 in MOT model
|
| 436 |
+
frame, _ = decode_image(img_file, {})
|
| 437 |
+
if run_benchmark:
|
| 438 |
+
# preprocess
|
| 439 |
+
inputs = self.preprocess(batch_image_list) # warmup
|
| 440 |
+
self.det_times.preprocess_time_s.start()
|
| 441 |
+
inputs = self.preprocess(batch_image_list)
|
| 442 |
+
self.det_times.preprocess_time_s.end()
|
| 443 |
+
|
| 444 |
+
# model prediction
|
| 445 |
+
result_warmup = self.predict(repeats=repeats) # warmup
|
| 446 |
+
self.det_times.inference_time_s.start()
|
| 447 |
+
result = self.predict(repeats=repeats)
|
| 448 |
+
self.det_times.inference_time_s.end(repeats=repeats)
|
| 449 |
+
|
| 450 |
+
# postprocess
|
| 451 |
+
result_warmup = self.postprocess(inputs, result) # warmup
|
| 452 |
+
self.det_times.postprocess_time_s.start()
|
| 453 |
+
det_result = self.postprocess(inputs, result)
|
| 454 |
+
self.det_times.postprocess_time_s.end()
|
| 455 |
+
|
| 456 |
+
# tracking
|
| 457 |
+
if self.use_reid:
|
| 458 |
+
det_result['frame_id'] = frame_id
|
| 459 |
+
det_result['seq_name'] = seq_name
|
| 460 |
+
det_result['ori_image'] = frame
|
| 461 |
+
det_result = self.reidprocess(det_result)
|
| 462 |
+
result_warmup = self.tracking(det_result)
|
| 463 |
+
self.det_times.tracking_time_s.start()
|
| 464 |
+
if self.use_reid:
|
| 465 |
+
det_result = self.reidprocess(det_result)
|
| 466 |
+
tracking_outs = self.tracking(det_result)
|
| 467 |
+
self.det_times.tracking_time_s.end()
|
| 468 |
+
self.det_times.img_num += 1
|
| 469 |
+
|
| 470 |
+
cm, gm, gu = get_current_memory_mb()
|
| 471 |
+
self.cpu_mem += cm
|
| 472 |
+
self.gpu_mem += gm
|
| 473 |
+
self.gpu_util += gu
|
| 474 |
+
|
| 475 |
+
else:
|
| 476 |
+
self.det_times.preprocess_time_s.start()
|
| 477 |
+
if not reuse_det_result:
|
| 478 |
+
inputs = self.preprocess(batch_image_list)
|
| 479 |
+
self.det_times.preprocess_time_s.end()
|
| 480 |
+
|
| 481 |
+
self.det_times.inference_time_s.start()
|
| 482 |
+
if not reuse_det_result:
|
| 483 |
+
result = self.predict()
|
| 484 |
+
self.det_times.inference_time_s.end()
|
| 485 |
+
|
| 486 |
+
self.det_times.postprocess_time_s.start()
|
| 487 |
+
if not reuse_det_result:
|
| 488 |
+
det_result = self.postprocess(inputs, result)
|
| 489 |
+
self.previous_det_result = det_result
|
| 490 |
+
else:
|
| 491 |
+
assert self.previous_det_result is not None
|
| 492 |
+
det_result = self.previous_det_result
|
| 493 |
+
self.det_times.postprocess_time_s.end()
|
| 494 |
+
|
| 495 |
+
# tracking process
|
| 496 |
+
self.det_times.tracking_time_s.start()
|
| 497 |
+
if self.use_reid:
|
| 498 |
+
det_result['frame_id'] = frame_id
|
| 499 |
+
det_result['seq_name'] = seq_name
|
| 500 |
+
det_result['ori_image'] = frame
|
| 501 |
+
det_result = self.reidprocess(det_result)
|
| 502 |
+
tracking_outs = self.tracking(det_result)
|
| 503 |
+
self.det_times.tracking_time_s.end()
|
| 504 |
+
self.det_times.img_num += 1
|
| 505 |
+
|
| 506 |
+
online_tlwhs = tracking_outs['online_tlwhs']
|
| 507 |
+
online_scores = tracking_outs['online_scores']
|
| 508 |
+
online_ids = tracking_outs['online_ids']
|
| 509 |
+
|
| 510 |
+
if self.do_mtmct:
|
| 511 |
+
feat_data_dict = tracking_outs['feat_data']
|
| 512 |
+
mot_features_dict = dict(mot_features_dict, **feat_data_dict)
|
| 513 |
+
else:
|
| 514 |
+
mot_results.append([online_tlwhs, online_scores, online_ids])
|
| 515 |
+
|
| 516 |
+
if visual:
|
| 517 |
+
if len(image_list) > 1 and frame_id % 10 == 0:
|
| 518 |
+
print('Tracking frame {}'.format(frame_id))
|
| 519 |
+
frame, _ = decode_image(img_file, {})
|
| 520 |
+
if isinstance(online_tlwhs, defaultdict):
|
| 521 |
+
im = plot_tracking_dict(
|
| 522 |
+
frame,
|
| 523 |
+
num_classes,
|
| 524 |
+
online_tlwhs,
|
| 525 |
+
online_ids,
|
| 526 |
+
online_scores,
|
| 527 |
+
frame_id=frame_id,
|
| 528 |
+
ids2names=ids2names)
|
| 529 |
+
else:
|
| 530 |
+
im = plot_tracking(
|
| 531 |
+
frame,
|
| 532 |
+
online_tlwhs,
|
| 533 |
+
online_ids,
|
| 534 |
+
online_scores,
|
| 535 |
+
frame_id=frame_id,
|
| 536 |
+
ids2names=ids2names)
|
| 537 |
+
save_dir = os.path.join(self.output_dir, seq_name)
|
| 538 |
+
if not os.path.exists(save_dir):
|
| 539 |
+
os.makedirs(save_dir)
|
| 540 |
+
cv2.imwrite(
|
| 541 |
+
os.path.join(save_dir, '{:05d}.jpg'.format(frame_id)), im)
|
| 542 |
+
|
| 543 |
+
if self.do_mtmct:
|
| 544 |
+
return mot_features_dict
|
| 545 |
+
else:
|
| 546 |
+
return mot_results
|
| 547 |
+
|
| 548 |
+
def predict_video(self, video_file, camera_id):
|
| 549 |
+
video_out_name = 'output.mp4'
|
| 550 |
+
if camera_id != -1:
|
| 551 |
+
capture = cv2.VideoCapture(camera_id)
|
| 552 |
+
else:
|
| 553 |
+
capture = cv2.VideoCapture(video_file)
|
| 554 |
+
video_out_name = os.path.split(video_file)[-1]
|
| 555 |
+
# Get Video info : resolution, fps, frame count
|
| 556 |
+
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 557 |
+
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 558 |
+
fps = int(capture.get(cv2.CAP_PROP_FPS))
|
| 559 |
+
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 560 |
+
print("fps: %d, frame_count: %d" % (fps, frame_count))
|
| 561 |
+
|
| 562 |
+
if not os.path.exists(self.output_dir):
|
| 563 |
+
os.makedirs(self.output_dir)
|
| 564 |
+
out_path = os.path.join(self.output_dir, video_out_name)
|
| 565 |
+
video_format = 'mp4v'
|
| 566 |
+
fourcc = cv2.VideoWriter_fourcc(*video_format)
|
| 567 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
|
| 568 |
+
|
| 569 |
+
frame_id = 0
|
| 570 |
+
timer = MOTTimer()
|
| 571 |
+
results = defaultdict(list)
|
| 572 |
+
num_classes = self.num_classes
|
| 573 |
+
data_type = 'mcmot' if num_classes > 1 else 'mot'
|
| 574 |
+
ids2names = self.pred_config.labels
|
| 575 |
+
|
| 576 |
+
center_traj = None
|
| 577 |
+
entrance = None
|
| 578 |
+
records = None
|
| 579 |
+
if self.draw_center_traj:
|
| 580 |
+
center_traj = [{} for i in range(num_classes)]
|
| 581 |
+
if num_classes == 1:
|
| 582 |
+
id_set = set()
|
| 583 |
+
interval_id_set = set()
|
| 584 |
+
in_id_list = list()
|
| 585 |
+
out_id_list = list()
|
| 586 |
+
prev_center = dict()
|
| 587 |
+
records = list()
|
| 588 |
+
if self.do_entrance_counting or self.do_break_in_counting:
|
| 589 |
+
if self.region_type == 'horizontal':
|
| 590 |
+
entrance = [0, height / 2., width, height / 2.]
|
| 591 |
+
elif self.region_type == 'vertical':
|
| 592 |
+
entrance = [width / 2, 0., width / 2, height]
|
| 593 |
+
elif self.region_type == 'custom':
|
| 594 |
+
entrance = []
|
| 595 |
+
assert len(
|
| 596 |
+
self.region_polygon
|
| 597 |
+
) % 2 == 0, "region_polygon should be pairs of coords points when do break_in counting."
|
| 598 |
+
for i in range(0, len(self.region_polygon), 2):
|
| 599 |
+
entrance.append([
|
| 600 |
+
self.region_polygon[i], self.region_polygon[i + 1]
|
| 601 |
+
])
|
| 602 |
+
entrance.append([width, height])
|
| 603 |
+
else:
|
| 604 |
+
raise ValueError("region_type:{} is not supported.".format(
|
| 605 |
+
self.region_type))
|
| 606 |
+
|
| 607 |
+
video_fps = fps
|
| 608 |
+
|
| 609 |
+
while (1):
|
| 610 |
+
ret, frame = capture.read()
|
| 611 |
+
if not ret:
|
| 612 |
+
break
|
| 613 |
+
if frame_id % 10 == 0:
|
| 614 |
+
print('Tracking frame: %d' % (frame_id))
|
| 615 |
+
|
| 616 |
+
timer.tic()
|
| 617 |
+
mot_skip_frame_num = self.skip_frame_num
|
| 618 |
+
reuse_det_result = False
|
| 619 |
+
if mot_skip_frame_num > 1 and frame_id > 0 and frame_id % mot_skip_frame_num > 0:
|
| 620 |
+
reuse_det_result = True
|
| 621 |
+
seq_name = video_out_name.split('.')[0]
|
| 622 |
+
mot_results = self.predict_image(
|
| 623 |
+
[frame],
|
| 624 |
+
visual=False,
|
| 625 |
+
seq_name=seq_name,
|
| 626 |
+
reuse_det_result=reuse_det_result)
|
| 627 |
+
timer.toc()
|
| 628 |
+
|
| 629 |
+
# bs=1 in MOT model
|
| 630 |
+
online_tlwhs, online_scores, online_ids = mot_results[0]
|
| 631 |
+
|
| 632 |
+
# flow statistic for one class, and only for bytetracker
|
| 633 |
+
if num_classes == 1 and not self.use_deepsort_tracker and not self.use_ocsort_tracker:
|
| 634 |
+
result = (frame_id + 1, online_tlwhs[0], online_scores[0],
|
| 635 |
+
online_ids[0])
|
| 636 |
+
statistic = flow_statistic(
|
| 637 |
+
result,
|
| 638 |
+
self.secs_interval,
|
| 639 |
+
self.do_entrance_counting,
|
| 640 |
+
self.do_break_in_counting,
|
| 641 |
+
self.region_type,
|
| 642 |
+
video_fps,
|
| 643 |
+
entrance,
|
| 644 |
+
id_set,
|
| 645 |
+
interval_id_set,
|
| 646 |
+
in_id_list,
|
| 647 |
+
out_id_list,
|
| 648 |
+
prev_center,
|
| 649 |
+
records,
|
| 650 |
+
data_type,
|
| 651 |
+
ids2names=self.pred_config.labels)
|
| 652 |
+
records = statistic['records']
|
| 653 |
+
|
| 654 |
+
fps = 1. / timer.duration
|
| 655 |
+
if self.use_deepsort_tracker or self.use_ocsort_tracker:
|
| 656 |
+
# use DeepSORTTracker or OCSORTTracker, only support singe class
|
| 657 |
+
results[0].append(
|
| 658 |
+
(frame_id + 1, online_tlwhs, online_scores, online_ids))
|
| 659 |
+
im = plot_tracking(
|
| 660 |
+
frame,
|
| 661 |
+
online_tlwhs,
|
| 662 |
+
online_ids,
|
| 663 |
+
online_scores,
|
| 664 |
+
frame_id=frame_id,
|
| 665 |
+
fps=fps,
|
| 666 |
+
ids2names=ids2names,
|
| 667 |
+
do_entrance_counting=self.do_entrance_counting,
|
| 668 |
+
entrance=entrance)
|
| 669 |
+
else:
|
| 670 |
+
# use ByteTracker, support multiple class
|
| 671 |
+
for cls_id in range(num_classes):
|
| 672 |
+
results[cls_id].append(
|
| 673 |
+
(frame_id + 1, online_tlwhs[cls_id],
|
| 674 |
+
online_scores[cls_id], online_ids[cls_id]))
|
| 675 |
+
im = plot_tracking_dict(
|
| 676 |
+
frame,
|
| 677 |
+
num_classes,
|
| 678 |
+
online_tlwhs,
|
| 679 |
+
online_ids,
|
| 680 |
+
online_scores,
|
| 681 |
+
frame_id=frame_id,
|
| 682 |
+
fps=fps,
|
| 683 |
+
ids2names=ids2names,
|
| 684 |
+
do_entrance_counting=self.do_entrance_counting,
|
| 685 |
+
entrance=entrance,
|
| 686 |
+
records=records,
|
| 687 |
+
center_traj=center_traj)
|
| 688 |
+
|
| 689 |
+
writer.write(im)
|
| 690 |
+
if camera_id != -1:
|
| 691 |
+
cv2.imshow('Mask Detection', im)
|
| 692 |
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
| 693 |
+
break
|
| 694 |
+
frame_id += 1
|
| 695 |
+
|
| 696 |
+
if self.save_mot_txts:
|
| 697 |
+
result_filename = os.path.join(
|
| 698 |
+
self.output_dir, video_out_name.split('.')[-2] + '.txt')
|
| 699 |
+
write_mot_results(result_filename, results)
|
| 700 |
+
|
| 701 |
+
result_filename = os.path.join(
|
| 702 |
+
self.output_dir,
|
| 703 |
+
video_out_name.split('.')[-2] + '_flow_statistic.txt')
|
| 704 |
+
f = open(result_filename, 'w')
|
| 705 |
+
for line in records:
|
| 706 |
+
f.write(line)
|
| 707 |
+
print('Flow statistic save in {}'.format(result_filename))
|
| 708 |
+
f.close()
|
| 709 |
+
|
| 710 |
+
writer.release()
|
| 711 |
+
|
| 712 |
+
def predict_mtmct(self, mtmct_dir, mtmct_cfg):
|
| 713 |
+
cameras_bias = mtmct_cfg['cameras_bias']
|
| 714 |
+
cid_bias = parse_bias(cameras_bias)
|
| 715 |
+
scene_cluster = list(cid_bias.keys())
|
| 716 |
+
# 1.zone releated parameters
|
| 717 |
+
use_zone = mtmct_cfg.get('use_zone', False)
|
| 718 |
+
zone_path = mtmct_cfg.get('zone_path', None)
|
| 719 |
+
|
| 720 |
+
# 2.tricks parameters, can be used for other mtmct dataset
|
| 721 |
+
use_ff = mtmct_cfg.get('use_ff', False)
|
| 722 |
+
use_rerank = mtmct_cfg.get('use_rerank', False)
|
| 723 |
+
|
| 724 |
+
# 3.camera releated parameters
|
| 725 |
+
use_camera = mtmct_cfg.get('use_camera', False)
|
| 726 |
+
use_st_filter = mtmct_cfg.get('use_st_filter', False)
|
| 727 |
+
|
| 728 |
+
# 4.zone releated parameters
|
| 729 |
+
use_roi = mtmct_cfg.get('use_roi', False)
|
| 730 |
+
roi_dir = mtmct_cfg.get('roi_dir', False)
|
| 731 |
+
|
| 732 |
+
mot_list_breaks = []
|
| 733 |
+
cid_tid_dict = dict()
|
| 734 |
+
|
| 735 |
+
output_dir = self.output_dir
|
| 736 |
+
if not os.path.exists(output_dir):
|
| 737 |
+
os.makedirs(output_dir)
|
| 738 |
+
|
| 739 |
+
seqs = os.listdir(mtmct_dir)
|
| 740 |
+
for seq in sorted(seqs):
|
| 741 |
+
fpath = os.path.join(mtmct_dir, seq)
|
| 742 |
+
if os.path.isfile(fpath) and _is_valid_video(fpath):
|
| 743 |
+
seq = seq.split('.')[-2]
|
| 744 |
+
print('ffmpeg processing of video {}'.format(fpath))
|
| 745 |
+
frames_path = video2frames(
|
| 746 |
+
video_path=fpath, outpath=mtmct_dir, frame_rate=25)
|
| 747 |
+
fpath = os.path.join(mtmct_dir, seq)
|
| 748 |
+
|
| 749 |
+
if os.path.isdir(fpath) == False:
|
| 750 |
+
print('{} is not a image folder.'.format(fpath))
|
| 751 |
+
continue
|
| 752 |
+
if os.path.exists(os.path.join(fpath, 'img1')):
|
| 753 |
+
fpath = os.path.join(fpath, 'img1')
|
| 754 |
+
assert os.path.isdir(fpath), '{} should be a directory'.format(
|
| 755 |
+
fpath)
|
| 756 |
+
image_list = glob.glob(os.path.join(fpath, '*.jpg'))
|
| 757 |
+
image_list.sort()
|
| 758 |
+
assert len(image_list) > 0, '{} has no images.'.format(fpath)
|
| 759 |
+
print('start tracking seq: {}'.format(seq))
|
| 760 |
+
|
| 761 |
+
mot_features_dict = self.predict_image(
|
| 762 |
+
image_list, visual=False, seq_name=seq)
|
| 763 |
+
|
| 764 |
+
cid = int(re.sub('[a-z,A-Z]', "", seq))
|
| 765 |
+
tid_data, mot_list_break = trajectory_fusion(
|
| 766 |
+
mot_features_dict,
|
| 767 |
+
cid,
|
| 768 |
+
cid_bias,
|
| 769 |
+
use_zone=use_zone,
|
| 770 |
+
zone_path=zone_path)
|
| 771 |
+
mot_list_breaks.append(mot_list_break)
|
| 772 |
+
# single seq process
|
| 773 |
+
for line in tid_data:
|
| 774 |
+
tracklet = tid_data[line]
|
| 775 |
+
tid = tracklet['tid']
|
| 776 |
+
if (cid, tid) not in cid_tid_dict:
|
| 777 |
+
cid_tid_dict[(cid, tid)] = tracklet
|
| 778 |
+
|
| 779 |
+
map_tid = sub_cluster(
|
| 780 |
+
cid_tid_dict,
|
| 781 |
+
scene_cluster,
|
| 782 |
+
use_ff=use_ff,
|
| 783 |
+
use_rerank=use_rerank,
|
| 784 |
+
use_camera=use_camera,
|
| 785 |
+
use_st_filter=use_st_filter)
|
| 786 |
+
|
| 787 |
+
pred_mtmct_file = os.path.join(output_dir, 'mtmct_result.txt')
|
| 788 |
+
if use_camera:
|
| 789 |
+
gen_res(pred_mtmct_file, scene_cluster, map_tid, mot_list_breaks)
|
| 790 |
+
else:
|
| 791 |
+
gen_res(
|
| 792 |
+
pred_mtmct_file,
|
| 793 |
+
scene_cluster,
|
| 794 |
+
map_tid,
|
| 795 |
+
mot_list_breaks,
|
| 796 |
+
use_roi=use_roi,
|
| 797 |
+
roi_dir=roi_dir)
|
| 798 |
+
|
| 799 |
+
camera_results, cid_tid_fid_res = get_mtmct_matching_results(
|
| 800 |
+
pred_mtmct_file)
|
| 801 |
+
|
| 802 |
+
crops_dir = os.path.join(output_dir, 'mtmct_crops')
|
| 803 |
+
save_mtmct_crops(
|
| 804 |
+
cid_tid_fid_res, images_dir=mtmct_dir, crops_dir=crops_dir)
|
| 805 |
+
|
| 806 |
+
save_dir = os.path.join(output_dir, 'mtmct_vis')
|
| 807 |
+
save_mtmct_vis_results(
|
| 808 |
+
camera_results,
|
| 809 |
+
images_dir=mtmct_dir,
|
| 810 |
+
save_dir=save_dir,
|
| 811 |
+
save_videos=FLAGS.save_images)
|
| 812 |
+
|
| 813 |
+
|
| 814 |
+
def main():
|
| 815 |
+
deploy_file = os.path.join(FLAGS.model_dir, 'infer_cfg.yml')
|
| 816 |
+
with open(deploy_file) as f:
|
| 817 |
+
yml_conf = yaml.safe_load(f)
|
| 818 |
+
arch = yml_conf['arch']
|
| 819 |
+
detector = SDE_Detector(
|
| 820 |
+
FLAGS.model_dir,
|
| 821 |
+
tracker_config=FLAGS.tracker_config,
|
| 822 |
+
device=FLAGS.device,
|
| 823 |
+
run_mode=FLAGS.run_mode,
|
| 824 |
+
batch_size=1,
|
| 825 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 826 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 827 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 828 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 829 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 830 |
+
enable_mkldnn=FLAGS.enable_mkldnn,
|
| 831 |
+
output_dir=FLAGS.output_dir,
|
| 832 |
+
threshold=FLAGS.threshold,
|
| 833 |
+
save_images=FLAGS.save_images,
|
| 834 |
+
save_mot_txts=FLAGS.save_mot_txts,
|
| 835 |
+
draw_center_traj=FLAGS.draw_center_traj,
|
| 836 |
+
secs_interval=FLAGS.secs_interval,
|
| 837 |
+
skip_frame_num=FLAGS.skip_frame_num,
|
| 838 |
+
do_entrance_counting=FLAGS.do_entrance_counting,
|
| 839 |
+
do_break_in_counting=FLAGS.do_break_in_counting,
|
| 840 |
+
region_type=FLAGS.region_type,
|
| 841 |
+
region_polygon=FLAGS.region_polygon,
|
| 842 |
+
reid_model_dir=FLAGS.reid_model_dir,
|
| 843 |
+
mtmct_dir=FLAGS.mtmct_dir, )
|
| 844 |
+
|
| 845 |
+
# predict from video file or camera video stream
|
| 846 |
+
if FLAGS.video_file is not None or FLAGS.camera_id != -1:
|
| 847 |
+
detector.predict_video(FLAGS.video_file, FLAGS.camera_id)
|
| 848 |
+
elif FLAGS.mtmct_dir is not None:
|
| 849 |
+
with open(FLAGS.mtmct_cfg) as f:
|
| 850 |
+
mtmct_cfg = yaml.safe_load(f)
|
| 851 |
+
detector.predict_mtmct(FLAGS.mtmct_dir, mtmct_cfg)
|
| 852 |
+
else:
|
| 853 |
+
# predict from image
|
| 854 |
+
if FLAGS.image_dir is None and FLAGS.image_file is not None:
|
| 855 |
+
assert FLAGS.batch_size == 1, "--batch_size should be 1 in MOT models."
|
| 856 |
+
img_list = get_test_images(FLAGS.image_dir, FLAGS.image_file)
|
| 857 |
+
seq_name = FLAGS.image_dir.split('/')[-1]
|
| 858 |
+
detector.predict_image(
|
| 859 |
+
img_list, FLAGS.run_benchmark, repeats=10, seq_name=seq_name)
|
| 860 |
+
|
| 861 |
+
if not FLAGS.run_benchmark:
|
| 862 |
+
detector.det_times.info(average=True)
|
| 863 |
+
else:
|
| 864 |
+
mode = FLAGS.run_mode
|
| 865 |
+
model_dir = FLAGS.model_dir
|
| 866 |
+
model_info = {
|
| 867 |
+
'model_name': model_dir.strip('/').split('/')[-1],
|
| 868 |
+
'precision': mode.split('_')[-1]
|
| 869 |
+
}
|
| 870 |
+
bench_log(detector, img_list, model_info, name='MOT')
|
| 871 |
+
|
| 872 |
+
|
| 873 |
+
if __name__ == '__main__':
|
| 874 |
+
paddle.enable_static()
|
| 875 |
+
parser = argsparser()
|
| 876 |
+
FLAGS = parser.parse_args()
|
| 877 |
+
print_arguments(FLAGS)
|
| 878 |
+
FLAGS.device = FLAGS.device.upper()
|
| 879 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 880 |
+
], "device should be CPU, GPU or XPU"
|
| 881 |
+
|
| 882 |
+
main()
|
pptracking/python/mot_utils.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import time
|
| 16 |
+
import os
|
| 17 |
+
import sys
|
| 18 |
+
import ast
|
| 19 |
+
import argparse
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def argsparser():
|
| 23 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 24 |
+
parser.add_argument(
|
| 25 |
+
"--model_dir",
|
| 26 |
+
type=str,
|
| 27 |
+
default=None,
|
| 28 |
+
help=("Directory include:'model.pdiparams', 'model.pdmodel', "
|
| 29 |
+
"'infer_cfg.yml', created by tools/export_model.py."),
|
| 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 |
+
"--batch_size", type=int, default=1, help="batch_size for inference.")
|
| 40 |
+
parser.add_argument(
|
| 41 |
+
"--video_file",
|
| 42 |
+
type=str,
|
| 43 |
+
default=None,
|
| 44 |
+
help="Path of video file, `video_file` or `camera_id` has a highest priority."
|
| 45 |
+
)
|
| 46 |
+
parser.add_argument(
|
| 47 |
+
"--camera_id",
|
| 48 |
+
type=int,
|
| 49 |
+
default=-1,
|
| 50 |
+
help="device id of camera to predict.")
|
| 51 |
+
parser.add_argument(
|
| 52 |
+
"--threshold", type=float, default=0.5, help="Threshold of score.")
|
| 53 |
+
parser.add_argument(
|
| 54 |
+
"--output_dir",
|
| 55 |
+
type=str,
|
| 56 |
+
default="output",
|
| 57 |
+
help="Directory of output visualization files.")
|
| 58 |
+
parser.add_argument(
|
| 59 |
+
"--run_mode",
|
| 60 |
+
type=str,
|
| 61 |
+
default='paddle',
|
| 62 |
+
help="mode of running(paddle/trt_fp32/trt_fp16/trt_int8)")
|
| 63 |
+
parser.add_argument(
|
| 64 |
+
"--device",
|
| 65 |
+
type=str,
|
| 66 |
+
default='cpu',
|
| 67 |
+
help="Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU."
|
| 68 |
+
)
|
| 69 |
+
parser.add_argument(
|
| 70 |
+
"--use_gpu",
|
| 71 |
+
type=ast.literal_eval,
|
| 72 |
+
default=False,
|
| 73 |
+
help="Deprecated, please use `--device`.")
|
| 74 |
+
parser.add_argument(
|
| 75 |
+
"--run_benchmark",
|
| 76 |
+
type=ast.literal_eval,
|
| 77 |
+
default=False,
|
| 78 |
+
help="Whether to predict a image_file repeatedly for benchmark")
|
| 79 |
+
parser.add_argument(
|
| 80 |
+
"--enable_mkldnn",
|
| 81 |
+
type=ast.literal_eval,
|
| 82 |
+
default=False,
|
| 83 |
+
help="Whether use mkldnn with CPU.")
|
| 84 |
+
parser.add_argument(
|
| 85 |
+
"--cpu_threads", type=int, default=1, help="Num of threads with CPU.")
|
| 86 |
+
parser.add_argument(
|
| 87 |
+
"--trt_min_shape", type=int, default=1, help="min_shape for TensorRT.")
|
| 88 |
+
parser.add_argument(
|
| 89 |
+
"--trt_max_shape",
|
| 90 |
+
type=int,
|
| 91 |
+
default=1280,
|
| 92 |
+
help="max_shape for TensorRT.")
|
| 93 |
+
parser.add_argument(
|
| 94 |
+
"--trt_opt_shape",
|
| 95 |
+
type=int,
|
| 96 |
+
default=640,
|
| 97 |
+
help="opt_shape for TensorRT.")
|
| 98 |
+
parser.add_argument(
|
| 99 |
+
"--trt_calib_mode",
|
| 100 |
+
type=bool,
|
| 101 |
+
default=False,
|
| 102 |
+
help="If the model is produced by TRT offline quantitative "
|
| 103 |
+
"calibration, trt_calib_mode need to set True.")
|
| 104 |
+
parser.add_argument(
|
| 105 |
+
'--save_images',
|
| 106 |
+
action='store_true',
|
| 107 |
+
help='Save visualization image results.')
|
| 108 |
+
parser.add_argument(
|
| 109 |
+
'--save_mot_txts',
|
| 110 |
+
action='store_true',
|
| 111 |
+
help='Save tracking results (txt).')
|
| 112 |
+
parser.add_argument(
|
| 113 |
+
'--save_mot_txt_per_img',
|
| 114 |
+
action='store_true',
|
| 115 |
+
help='Save tracking results (txt) for each image.')
|
| 116 |
+
parser.add_argument(
|
| 117 |
+
'--scaled',
|
| 118 |
+
type=bool,
|
| 119 |
+
default=False,
|
| 120 |
+
help="Whether coords after detector outputs are scaled, False in JDE YOLOv3 "
|
| 121 |
+
"True in general detector.")
|
| 122 |
+
parser.add_argument(
|
| 123 |
+
"--tracker_config", type=str, default=None, help=("tracker donfig"))
|
| 124 |
+
parser.add_argument(
|
| 125 |
+
"--reid_model_dir",
|
| 126 |
+
type=str,
|
| 127 |
+
default=None,
|
| 128 |
+
help=("Directory include:'model.pdiparams', 'model.pdmodel', "
|
| 129 |
+
"'infer_cfg.yml', created by tools/export_model.py."))
|
| 130 |
+
parser.add_argument(
|
| 131 |
+
"--reid_batch_size",
|
| 132 |
+
type=int,
|
| 133 |
+
default=50,
|
| 134 |
+
help="max batch_size for reid model inference.")
|
| 135 |
+
parser.add_argument(
|
| 136 |
+
'--use_dark',
|
| 137 |
+
type=ast.literal_eval,
|
| 138 |
+
default=True,
|
| 139 |
+
help='whether to use darkpose to get better keypoint position predict ')
|
| 140 |
+
parser.add_argument(
|
| 141 |
+
'--skip_frame_num',
|
| 142 |
+
type=int,
|
| 143 |
+
default=-1,
|
| 144 |
+
help='Skip frames to speed up the process of getting mot results.')
|
| 145 |
+
parser.add_argument(
|
| 146 |
+
"--do_entrance_counting",
|
| 147 |
+
action='store_true',
|
| 148 |
+
help="Whether counting the numbers of identifiers entering "
|
| 149 |
+
"or getting out from the entrance. Note that only support single-class MOT."
|
| 150 |
+
)
|
| 151 |
+
parser.add_argument(
|
| 152 |
+
"--do_break_in_counting",
|
| 153 |
+
action='store_true',
|
| 154 |
+
help="Whether counting the numbers of identifiers break in "
|
| 155 |
+
"the area. Note that only support single-class MOT and "
|
| 156 |
+
"the video should be taken by a static camera.")
|
| 157 |
+
parser.add_argument(
|
| 158 |
+
"--region_type",
|
| 159 |
+
type=str,
|
| 160 |
+
default='horizontal',
|
| 161 |
+
help="Area type for entrance counting or break in counting, 'horizontal' and "
|
| 162 |
+
"'vertical' used when do entrance counting. 'custom' used when do break in counting. "
|
| 163 |
+
"Note that only support single-class MOT, and the video should be taken by a static camera."
|
| 164 |
+
)
|
| 165 |
+
parser.add_argument(
|
| 166 |
+
'--region_polygon',
|
| 167 |
+
nargs='+',
|
| 168 |
+
type=int,
|
| 169 |
+
default=[],
|
| 170 |
+
help="Clockwise point coords (x0,y0,x1,y1...) of polygon of area when "
|
| 171 |
+
"do_break_in_counting. Note that only support single-class MOT and "
|
| 172 |
+
"the video should be taken by a static camera.")
|
| 173 |
+
parser.add_argument(
|
| 174 |
+
"--secs_interval",
|
| 175 |
+
type=int,
|
| 176 |
+
default=2,
|
| 177 |
+
help="The seconds interval to count after tracking")
|
| 178 |
+
parser.add_argument(
|
| 179 |
+
"--draw_center_traj",
|
| 180 |
+
action='store_true',
|
| 181 |
+
help="Whether drawing the trajectory of center")
|
| 182 |
+
parser.add_argument(
|
| 183 |
+
"--mtmct_dir",
|
| 184 |
+
type=str,
|
| 185 |
+
default=None,
|
| 186 |
+
help="The MTMCT scene video folder.")
|
| 187 |
+
parser.add_argument(
|
| 188 |
+
"--mtmct_cfg", type=str, default=None, help="The MTMCT config.")
|
| 189 |
+
return parser
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
class Times(object):
|
| 193 |
+
def __init__(self):
|
| 194 |
+
self.time = 0.
|
| 195 |
+
# start time
|
| 196 |
+
self.st = 0.
|
| 197 |
+
# end time
|
| 198 |
+
self.et = 0.
|
| 199 |
+
|
| 200 |
+
def start(self):
|
| 201 |
+
self.st = time.time()
|
| 202 |
+
|
| 203 |
+
def end(self, repeats=1, accumulative=True):
|
| 204 |
+
self.et = time.time()
|
| 205 |
+
if accumulative:
|
| 206 |
+
self.time += (self.et - self.st) / repeats
|
| 207 |
+
else:
|
| 208 |
+
self.time = (self.et - self.st) / repeats
|
| 209 |
+
|
| 210 |
+
def reset(self):
|
| 211 |
+
self.time = 0.
|
| 212 |
+
self.st = 0.
|
| 213 |
+
self.et = 0.
|
| 214 |
+
|
| 215 |
+
def value(self):
|
| 216 |
+
return round(self.time, 4)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
class Timer(Times):
|
| 220 |
+
def __init__(self, with_tracker=False):
|
| 221 |
+
super(Timer, self).__init__()
|
| 222 |
+
self.with_tracker = with_tracker
|
| 223 |
+
self.preprocess_time_s = Times()
|
| 224 |
+
self.inference_time_s = Times()
|
| 225 |
+
self.postprocess_time_s = Times()
|
| 226 |
+
self.tracking_time_s = Times()
|
| 227 |
+
self.img_num = 0
|
| 228 |
+
|
| 229 |
+
def info(self, average=False):
|
| 230 |
+
pre_time = self.preprocess_time_s.value()
|
| 231 |
+
infer_time = self.inference_time_s.value()
|
| 232 |
+
post_time = self.postprocess_time_s.value()
|
| 233 |
+
track_time = self.tracking_time_s.value()
|
| 234 |
+
|
| 235 |
+
total_time = pre_time + infer_time + post_time
|
| 236 |
+
if self.with_tracker:
|
| 237 |
+
total_time = total_time + track_time
|
| 238 |
+
total_time = round(total_time, 4)
|
| 239 |
+
print("------------------ Inference Time Info ----------------------")
|
| 240 |
+
print("total_time(ms): {}, img_num: {}".format(total_time * 1000,
|
| 241 |
+
self.img_num))
|
| 242 |
+
preprocess_time = round(pre_time / max(1, self.img_num),
|
| 243 |
+
4) if average else pre_time
|
| 244 |
+
postprocess_time = round(post_time / max(1, self.img_num),
|
| 245 |
+
4) if average else post_time
|
| 246 |
+
inference_time = round(infer_time / max(1, self.img_num),
|
| 247 |
+
4) if average else infer_time
|
| 248 |
+
tracking_time = round(track_time / max(1, self.img_num),
|
| 249 |
+
4) if average else track_time
|
| 250 |
+
|
| 251 |
+
average_latency = total_time / max(1, self.img_num)
|
| 252 |
+
qps = 0
|
| 253 |
+
if total_time > 0:
|
| 254 |
+
qps = 1 / average_latency
|
| 255 |
+
print("average latency time(ms): {:.2f}, QPS: {:2f}".format(
|
| 256 |
+
average_latency * 1000, qps))
|
| 257 |
+
if self.with_tracker:
|
| 258 |
+
print(
|
| 259 |
+
"preprocess_time(ms): {:.2f}, inference_time(ms): {:.2f}, postprocess_time(ms): {:.2f}, tracking_time(ms): {:.2f}".
|
| 260 |
+
format(preprocess_time * 1000, inference_time * 1000,
|
| 261 |
+
postprocess_time * 1000, tracking_time * 1000))
|
| 262 |
+
else:
|
| 263 |
+
print(
|
| 264 |
+
"preprocess_time(ms): {:.2f}, inference_time(ms): {:.2f}, postprocess_time(ms): {:.2f}".
|
| 265 |
+
format(preprocess_time * 1000, inference_time * 1000,
|
| 266 |
+
postprocess_time * 1000))
|
| 267 |
+
|
| 268 |
+
def report(self, average=False):
|
| 269 |
+
dic = {}
|
| 270 |
+
pre_time = self.preprocess_time_s.value()
|
| 271 |
+
infer_time = self.inference_time_s.value()
|
| 272 |
+
post_time = self.postprocess_time_s.value()
|
| 273 |
+
track_time = self.tracking_time_s.value()
|
| 274 |
+
|
| 275 |
+
dic['preprocess_time_s'] = round(pre_time / max(1, self.img_num),
|
| 276 |
+
4) if average else pre_time
|
| 277 |
+
dic['inference_time_s'] = round(infer_time / max(1, self.img_num),
|
| 278 |
+
4) if average else infer_time
|
| 279 |
+
dic['postprocess_time_s'] = round(post_time / max(1, self.img_num),
|
| 280 |
+
4) if average else post_time
|
| 281 |
+
dic['img_num'] = self.img_num
|
| 282 |
+
total_time = pre_time + infer_time + post_time
|
| 283 |
+
if self.with_tracker:
|
| 284 |
+
dic['tracking_time_s'] = round(track_time / max(1, self.img_num),
|
| 285 |
+
4) if average else track_time
|
| 286 |
+
total_time = total_time + track_time
|
| 287 |
+
dic['total_time_s'] = round(total_time, 4)
|
| 288 |
+
return dic
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def get_current_memory_mb():
|
| 292 |
+
"""
|
| 293 |
+
It is used to Obtain the memory usage of the CPU and GPU during the running of the program.
|
| 294 |
+
And this function Current program is time-consuming.
|
| 295 |
+
"""
|
| 296 |
+
import pynvml
|
| 297 |
+
import psutil
|
| 298 |
+
import GPUtil
|
| 299 |
+
gpu_id = int(os.environ.get('CUDA_VISIBLE_DEVICES', 0))
|
| 300 |
+
|
| 301 |
+
pid = os.getpid()
|
| 302 |
+
p = psutil.Process(pid)
|
| 303 |
+
info = p.memory_full_info()
|
| 304 |
+
cpu_mem = info.uss / 1024. / 1024.
|
| 305 |
+
gpu_mem = 0
|
| 306 |
+
gpu_percent = 0
|
| 307 |
+
gpus = GPUtil.getGPUs()
|
| 308 |
+
if gpu_id is not None and len(gpus) > 0:
|
| 309 |
+
gpu_percent = gpus[gpu_id].load
|
| 310 |
+
pynvml.nvmlInit()
|
| 311 |
+
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
|
| 312 |
+
meminfo = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
| 313 |
+
gpu_mem = meminfo.used / 1024. / 1024.
|
| 314 |
+
return round(cpu_mem, 4), round(gpu_mem, 4), round(gpu_percent, 4)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def video2frames(video_path, outpath, frame_rate=25, **kargs):
|
| 318 |
+
def _dict2str(kargs):
|
| 319 |
+
cmd_str = ''
|
| 320 |
+
for k, v in kargs.items():
|
| 321 |
+
cmd_str += (' ' + str(k) + ' ' + str(v))
|
| 322 |
+
return cmd_str
|
| 323 |
+
|
| 324 |
+
ffmpeg = ['ffmpeg ', ' -y -loglevel ', ' error ']
|
| 325 |
+
vid_name = os.path.basename(video_path).split('.')[0]
|
| 326 |
+
out_full_path = os.path.join(outpath, vid_name)
|
| 327 |
+
|
| 328 |
+
if not os.path.exists(out_full_path):
|
| 329 |
+
os.makedirs(out_full_path)
|
| 330 |
+
|
| 331 |
+
# video file name
|
| 332 |
+
outformat = os.path.join(out_full_path, '%05d.jpg')
|
| 333 |
+
|
| 334 |
+
cmd = ffmpeg
|
| 335 |
+
cmd = ffmpeg + [
|
| 336 |
+
' -i ', video_path, ' -r ', str(frame_rate), ' -f image2 ', outformat
|
| 337 |
+
]
|
| 338 |
+
cmd = ''.join(cmd) + _dict2str(kargs)
|
| 339 |
+
|
| 340 |
+
if os.system(cmd) != 0:
|
| 341 |
+
raise RuntimeError('ffmpeg process video: {} error'.format(video_path))
|
| 342 |
+
sys.exit(-1)
|
| 343 |
+
|
| 344 |
+
sys.stdout.flush()
|
| 345 |
+
return out_full_path
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def _is_valid_video(f, extensions=('.mp4', '.avi', '.mov', '.rmvb', '.flv')):
|
| 349 |
+
return f.lower().endswith(extensions)
|
pptracking/python/mtmct_cfg.yml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# config for MTMCT
|
| 2 |
+
MTMCT: True
|
| 3 |
+
cameras_bias: # default for scene S01. For S06, should modify as 'c041: 0 c042: 0'
|
| 4 |
+
c003: 0
|
| 5 |
+
c004: 0
|
| 6 |
+
# 1.zone releated parameters
|
| 7 |
+
use_zone: False
|
| 8 |
+
zone_path: dataset/mot/aic21mtmct_vehicle/S06/zone
|
| 9 |
+
# 2.tricks parameters, can be used for other mtmct dataset
|
| 10 |
+
use_ff: False
|
| 11 |
+
use_rerank: False
|
| 12 |
+
# 3.camera releated parameters
|
| 13 |
+
use_camera: False
|
| 14 |
+
use_st_filter: False
|
| 15 |
+
# 4.zone releated parameters
|
| 16 |
+
use_roi: False
|
| 17 |
+
roi_dir: dataset/mot/aic21mtmct_vehicle/S06
|
pptracking/python/picodet_postprocess.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
from scipy.special import softmax
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def hard_nms(box_scores, iou_threshold, top_k=-1, candidate_size=200):
|
| 20 |
+
"""
|
| 21 |
+
Args:
|
| 22 |
+
box_scores (N, 5): boxes in corner-form and probabilities.
|
| 23 |
+
iou_threshold: intersection over union threshold.
|
| 24 |
+
top_k: keep top_k results. If k <= 0, keep all the results.
|
| 25 |
+
candidate_size: only consider the candidates with the highest scores.
|
| 26 |
+
Returns:
|
| 27 |
+
picked: a list of indexes of the kept boxes
|
| 28 |
+
"""
|
| 29 |
+
scores = box_scores[:, -1]
|
| 30 |
+
boxes = box_scores[:, :-1]
|
| 31 |
+
picked = []
|
| 32 |
+
indexes = np.argsort(scores)
|
| 33 |
+
indexes = indexes[-candidate_size:]
|
| 34 |
+
while len(indexes) > 0:
|
| 35 |
+
current = indexes[-1]
|
| 36 |
+
picked.append(current)
|
| 37 |
+
if 0 < top_k == len(picked) or len(indexes) == 1:
|
| 38 |
+
break
|
| 39 |
+
current_box = boxes[current, :]
|
| 40 |
+
indexes = indexes[:-1]
|
| 41 |
+
rest_boxes = boxes[indexes, :]
|
| 42 |
+
iou = iou_of(
|
| 43 |
+
rest_boxes,
|
| 44 |
+
np.expand_dims(
|
| 45 |
+
current_box, axis=0), )
|
| 46 |
+
indexes = indexes[iou <= iou_threshold]
|
| 47 |
+
|
| 48 |
+
return box_scores[picked, :]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def iou_of(boxes0, boxes1, eps=1e-5):
|
| 52 |
+
"""Return intersection-over-union (Jaccard index) of boxes.
|
| 53 |
+
Args:
|
| 54 |
+
boxes0 (N, 4): ground truth boxes.
|
| 55 |
+
boxes1 (N or 1, 4): predicted boxes.
|
| 56 |
+
eps: a small number to avoid 0 as denominator.
|
| 57 |
+
Returns:
|
| 58 |
+
iou (N): IoU values.
|
| 59 |
+
"""
|
| 60 |
+
overlap_left_top = np.maximum(boxes0[..., :2], boxes1[..., :2])
|
| 61 |
+
overlap_right_bottom = np.minimum(boxes0[..., 2:], boxes1[..., 2:])
|
| 62 |
+
|
| 63 |
+
overlap_area = area_of(overlap_left_top, overlap_right_bottom)
|
| 64 |
+
area0 = area_of(boxes0[..., :2], boxes0[..., 2:])
|
| 65 |
+
area1 = area_of(boxes1[..., :2], boxes1[..., 2:])
|
| 66 |
+
return overlap_area / (area0 + area1 - overlap_area + eps)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def area_of(left_top, right_bottom):
|
| 70 |
+
"""Compute the areas of rectangles given two corners.
|
| 71 |
+
Args:
|
| 72 |
+
left_top (N, 2): left top corner.
|
| 73 |
+
right_bottom (N, 2): right bottom corner.
|
| 74 |
+
Returns:
|
| 75 |
+
area (N): return the area.
|
| 76 |
+
"""
|
| 77 |
+
hw = np.clip(right_bottom - left_top, 0.0, None)
|
| 78 |
+
return hw[..., 0] * hw[..., 1]
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class PicoDetPostProcess(object):
|
| 82 |
+
"""
|
| 83 |
+
Args:
|
| 84 |
+
input_shape (int): network input image size
|
| 85 |
+
ori_shape (int): ori image shape of before padding
|
| 86 |
+
scale_factor (float): scale factor of ori image
|
| 87 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
def __init__(self,
|
| 91 |
+
input_shape,
|
| 92 |
+
ori_shape,
|
| 93 |
+
scale_factor,
|
| 94 |
+
strides=[8, 16, 32, 64],
|
| 95 |
+
score_threshold=0.4,
|
| 96 |
+
nms_threshold=0.5,
|
| 97 |
+
nms_top_k=1000,
|
| 98 |
+
keep_top_k=100):
|
| 99 |
+
self.ori_shape = ori_shape
|
| 100 |
+
self.input_shape = input_shape
|
| 101 |
+
self.scale_factor = scale_factor
|
| 102 |
+
self.strides = strides
|
| 103 |
+
self.score_threshold = score_threshold
|
| 104 |
+
self.nms_threshold = nms_threshold
|
| 105 |
+
self.nms_top_k = nms_top_k
|
| 106 |
+
self.keep_top_k = keep_top_k
|
| 107 |
+
|
| 108 |
+
def warp_boxes(self, boxes, ori_shape):
|
| 109 |
+
"""Apply transform to boxes
|
| 110 |
+
"""
|
| 111 |
+
width, height = ori_shape[1], ori_shape[0]
|
| 112 |
+
n = len(boxes)
|
| 113 |
+
if n:
|
| 114 |
+
# warp points
|
| 115 |
+
xy = np.ones((n * 4, 3))
|
| 116 |
+
xy[:, :2] = boxes[:, [0, 1, 2, 3, 0, 3, 2, 1]].reshape(
|
| 117 |
+
n * 4, 2) # x1y1, x2y2, x1y2, x2y1
|
| 118 |
+
# xy = xy @ M.T # transform
|
| 119 |
+
xy = (xy[:, :2] / xy[:, 2:3]).reshape(n, 8) # rescale
|
| 120 |
+
# create new boxes
|
| 121 |
+
x = xy[:, [0, 2, 4, 6]]
|
| 122 |
+
y = xy[:, [1, 3, 5, 7]]
|
| 123 |
+
xy = np.concatenate(
|
| 124 |
+
(x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
|
| 125 |
+
# clip boxes
|
| 126 |
+
xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width)
|
| 127 |
+
xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height)
|
| 128 |
+
return xy.astype(np.float32)
|
| 129 |
+
else:
|
| 130 |
+
return boxes
|
| 131 |
+
|
| 132 |
+
def __call__(self, scores, raw_boxes):
|
| 133 |
+
batch_size = raw_boxes[0].shape[0]
|
| 134 |
+
reg_max = int(raw_boxes[0].shape[-1] / 4 - 1)
|
| 135 |
+
out_boxes_num = []
|
| 136 |
+
out_boxes_list = []
|
| 137 |
+
for batch_id in range(batch_size):
|
| 138 |
+
# generate centers
|
| 139 |
+
decode_boxes = []
|
| 140 |
+
select_scores = []
|
| 141 |
+
for stride, box_distribute, score in zip(self.strides, raw_boxes,
|
| 142 |
+
scores):
|
| 143 |
+
box_distribute = box_distribute[batch_id]
|
| 144 |
+
score = score[batch_id]
|
| 145 |
+
# centers
|
| 146 |
+
fm_h = self.input_shape[0] / stride
|
| 147 |
+
fm_w = self.input_shape[1] / stride
|
| 148 |
+
h_range = np.arange(fm_h)
|
| 149 |
+
w_range = np.arange(fm_w)
|
| 150 |
+
ww, hh = np.meshgrid(w_range, h_range)
|
| 151 |
+
ct_row = (hh.flatten() + 0.5) * stride
|
| 152 |
+
ct_col = (ww.flatten() + 0.5) * stride
|
| 153 |
+
center = np.stack((ct_col, ct_row, ct_col, ct_row), axis=1)
|
| 154 |
+
|
| 155 |
+
# box distribution to distance
|
| 156 |
+
reg_range = np.arange(reg_max + 1)
|
| 157 |
+
box_distance = box_distribute.reshape((-1, reg_max + 1))
|
| 158 |
+
box_distance = softmax(box_distance, axis=1)
|
| 159 |
+
box_distance = box_distance * np.expand_dims(reg_range, axis=0)
|
| 160 |
+
box_distance = np.sum(box_distance, axis=1).reshape((-1, 4))
|
| 161 |
+
box_distance = box_distance * stride
|
| 162 |
+
|
| 163 |
+
# top K candidate
|
| 164 |
+
topk_idx = np.argsort(score.max(axis=1))[::-1]
|
| 165 |
+
topk_idx = topk_idx[:self.nms_top_k]
|
| 166 |
+
center = center[topk_idx]
|
| 167 |
+
score = score[topk_idx]
|
| 168 |
+
box_distance = box_distance[topk_idx]
|
| 169 |
+
|
| 170 |
+
# decode box
|
| 171 |
+
decode_box = center + [-1, -1, 1, 1] * box_distance
|
| 172 |
+
|
| 173 |
+
select_scores.append(score)
|
| 174 |
+
decode_boxes.append(decode_box)
|
| 175 |
+
|
| 176 |
+
# nms
|
| 177 |
+
bboxes = np.concatenate(decode_boxes, axis=0)
|
| 178 |
+
confidences = np.concatenate(select_scores, axis=0)
|
| 179 |
+
picked_box_probs = []
|
| 180 |
+
picked_labels = []
|
| 181 |
+
for class_index in range(0, confidences.shape[1]):
|
| 182 |
+
probs = confidences[:, class_index]
|
| 183 |
+
mask = probs > self.score_threshold
|
| 184 |
+
probs = probs[mask]
|
| 185 |
+
if probs.shape[0] == 0:
|
| 186 |
+
continue
|
| 187 |
+
subset_boxes = bboxes[mask, :]
|
| 188 |
+
box_probs = np.concatenate(
|
| 189 |
+
[subset_boxes, probs.reshape(-1, 1)], axis=1)
|
| 190 |
+
box_probs = hard_nms(
|
| 191 |
+
box_probs,
|
| 192 |
+
iou_threshold=self.nms_threshold,
|
| 193 |
+
top_k=self.keep_top_k, )
|
| 194 |
+
picked_box_probs.append(box_probs)
|
| 195 |
+
picked_labels.extend([class_index] * box_probs.shape[0])
|
| 196 |
+
|
| 197 |
+
if len(picked_box_probs) == 0:
|
| 198 |
+
out_boxes_list.append(np.empty((0, 4)))
|
| 199 |
+
out_boxes_num.append(0)
|
| 200 |
+
|
| 201 |
+
else:
|
| 202 |
+
picked_box_probs = np.concatenate(picked_box_probs)
|
| 203 |
+
|
| 204 |
+
# resize output boxes
|
| 205 |
+
picked_box_probs[:, :4] = self.warp_boxes(
|
| 206 |
+
picked_box_probs[:, :4], self.ori_shape[batch_id])
|
| 207 |
+
im_scale = np.concatenate([
|
| 208 |
+
self.scale_factor[batch_id][::-1],
|
| 209 |
+
self.scale_factor[batch_id][::-1]
|
| 210 |
+
])
|
| 211 |
+
picked_box_probs[:, :4] /= im_scale
|
| 212 |
+
# clas score box
|
| 213 |
+
out_boxes_list.append(
|
| 214 |
+
np.concatenate(
|
| 215 |
+
[
|
| 216 |
+
np.expand_dims(
|
| 217 |
+
np.array(picked_labels),
|
| 218 |
+
axis=-1), np.expand_dims(
|
| 219 |
+
picked_box_probs[:, 4], axis=-1),
|
| 220 |
+
picked_box_probs[:, :4]
|
| 221 |
+
],
|
| 222 |
+
axis=1))
|
| 223 |
+
out_boxes_num.append(len(picked_labels))
|
| 224 |
+
|
| 225 |
+
out_boxes_list = np.concatenate(out_boxes_list, axis=0)
|
| 226 |
+
out_boxes_num = np.asarray(out_boxes_num).astype(np.int32)
|
| 227 |
+
return out_boxes_list, out_boxes_num
|
pptracking/python/preprocess.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import cv2
|
| 16 |
+
import numpy as np
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def decode_image(im_file, im_info):
|
| 20 |
+
"""read rgb image
|
| 21 |
+
Args:
|
| 22 |
+
im_file (str|np.ndarray): input can be image path or np.ndarray
|
| 23 |
+
im_info (dict): info of image
|
| 24 |
+
Returns:
|
| 25 |
+
im (np.ndarray): processed image (np.ndarray)
|
| 26 |
+
im_info (dict): info of processed image
|
| 27 |
+
"""
|
| 28 |
+
if isinstance(im_file, str):
|
| 29 |
+
with open(im_file, 'rb') as f:
|
| 30 |
+
im_read = f.read()
|
| 31 |
+
data = np.frombuffer(im_read, dtype='uint8')
|
| 32 |
+
im = cv2.imdecode(data, 1) # BGR mode, but need RGB mode
|
| 33 |
+
im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
|
| 34 |
+
else:
|
| 35 |
+
im = im_file
|
| 36 |
+
im_info['im_shape'] = np.array(im.shape[:2], dtype=np.float32)
|
| 37 |
+
im_info['scale_factor'] = np.array([1., 1.], dtype=np.float32)
|
| 38 |
+
return im, im_info
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class Resize(object):
|
| 42 |
+
"""resize image by target_size and max_size
|
| 43 |
+
Args:
|
| 44 |
+
target_size (int): the target size of image
|
| 45 |
+
keep_ratio (bool): whether keep_ratio or not, default true
|
| 46 |
+
interp (int): method of resize
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
def __init__(self, target_size, keep_ratio=True, interp=cv2.INTER_LINEAR):
|
| 50 |
+
if isinstance(target_size, int):
|
| 51 |
+
target_size = [target_size, target_size]
|
| 52 |
+
self.target_size = target_size
|
| 53 |
+
self.keep_ratio = keep_ratio
|
| 54 |
+
self.interp = interp
|
| 55 |
+
|
| 56 |
+
def __call__(self, im, im_info):
|
| 57 |
+
"""
|
| 58 |
+
Args:
|
| 59 |
+
im (np.ndarray): image (np.ndarray)
|
| 60 |
+
im_info (dict): info of image
|
| 61 |
+
Returns:
|
| 62 |
+
im (np.ndarray): processed image (np.ndarray)
|
| 63 |
+
im_info (dict): info of processed image
|
| 64 |
+
"""
|
| 65 |
+
assert len(self.target_size) == 2
|
| 66 |
+
assert self.target_size[0] > 0 and self.target_size[1] > 0
|
| 67 |
+
im_channel = im.shape[2]
|
| 68 |
+
im_scale_y, im_scale_x = self.generate_scale(im)
|
| 69 |
+
im = cv2.resize(
|
| 70 |
+
im,
|
| 71 |
+
None,
|
| 72 |
+
None,
|
| 73 |
+
fx=im_scale_x,
|
| 74 |
+
fy=im_scale_y,
|
| 75 |
+
interpolation=self.interp)
|
| 76 |
+
im_info['im_shape'] = np.array(im.shape[:2]).astype('float32')
|
| 77 |
+
im_info['scale_factor'] = np.array(
|
| 78 |
+
[im_scale_y, im_scale_x]).astype('float32')
|
| 79 |
+
return im, im_info
|
| 80 |
+
|
| 81 |
+
def generate_scale(self, im):
|
| 82 |
+
"""
|
| 83 |
+
Args:
|
| 84 |
+
im (np.ndarray): image (np.ndarray)
|
| 85 |
+
Returns:
|
| 86 |
+
im_scale_x: the resize ratio of X
|
| 87 |
+
im_scale_y: the resize ratio of Y
|
| 88 |
+
"""
|
| 89 |
+
origin_shape = im.shape[:2]
|
| 90 |
+
im_c = im.shape[2]
|
| 91 |
+
if self.keep_ratio:
|
| 92 |
+
im_size_min = np.min(origin_shape)
|
| 93 |
+
im_size_max = np.max(origin_shape)
|
| 94 |
+
target_size_min = np.min(self.target_size)
|
| 95 |
+
target_size_max = np.max(self.target_size)
|
| 96 |
+
im_scale = float(target_size_min) / float(im_size_min)
|
| 97 |
+
if np.round(im_scale * im_size_max) > target_size_max:
|
| 98 |
+
im_scale = float(target_size_max) / float(im_size_max)
|
| 99 |
+
im_scale_x = im_scale
|
| 100 |
+
im_scale_y = im_scale
|
| 101 |
+
else:
|
| 102 |
+
resize_h, resize_w = self.target_size
|
| 103 |
+
im_scale_y = resize_h / float(origin_shape[0])
|
| 104 |
+
im_scale_x = resize_w / float(origin_shape[1])
|
| 105 |
+
return im_scale_y, im_scale_x
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class NormalizeImage(object):
|
| 109 |
+
"""normalize image
|
| 110 |
+
Args:
|
| 111 |
+
mean (list): im - mean
|
| 112 |
+
std (list): im / std
|
| 113 |
+
is_scale (bool): whether need im / 255
|
| 114 |
+
is_channel_first (bool): if True: image shape is CHW, else: HWC
|
| 115 |
+
"""
|
| 116 |
+
|
| 117 |
+
def __init__(self, mean, std, is_scale=True):
|
| 118 |
+
self.mean = mean
|
| 119 |
+
self.std = std
|
| 120 |
+
self.is_scale = is_scale
|
| 121 |
+
|
| 122 |
+
def __call__(self, im, im_info):
|
| 123 |
+
"""
|
| 124 |
+
Args:
|
| 125 |
+
im (np.ndarray): image (np.ndarray)
|
| 126 |
+
im_info (dict): info of image
|
| 127 |
+
Returns:
|
| 128 |
+
im (np.ndarray): processed image (np.ndarray)
|
| 129 |
+
im_info (dict): info of processed image
|
| 130 |
+
"""
|
| 131 |
+
im = im.astype(np.float32, copy=False)
|
| 132 |
+
mean = np.array(self.mean)[np.newaxis, np.newaxis, :]
|
| 133 |
+
std = np.array(self.std)[np.newaxis, np.newaxis, :]
|
| 134 |
+
|
| 135 |
+
if self.is_scale:
|
| 136 |
+
im = im / 255.0
|
| 137 |
+
im -= mean
|
| 138 |
+
im /= std
|
| 139 |
+
return im, im_info
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class Permute(object):
|
| 143 |
+
"""permute image
|
| 144 |
+
Args:
|
| 145 |
+
to_bgr (bool): whether convert RGB to BGR
|
| 146 |
+
channel_first (bool): whether convert HWC to CHW
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
def __init__(self, ):
|
| 150 |
+
super(Permute, self).__init__()
|
| 151 |
+
|
| 152 |
+
def __call__(self, im, im_info):
|
| 153 |
+
"""
|
| 154 |
+
Args:
|
| 155 |
+
im (np.ndarray): image (np.ndarray)
|
| 156 |
+
im_info (dict): info of image
|
| 157 |
+
Returns:
|
| 158 |
+
im (np.ndarray): processed image (np.ndarray)
|
| 159 |
+
im_info (dict): info of processed image
|
| 160 |
+
"""
|
| 161 |
+
im = im.transpose((2, 0, 1)).copy()
|
| 162 |
+
return im, im_info
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
class PadStride(object):
|
| 166 |
+
""" padding image for model with FPN, instead PadBatch(pad_to_stride) in original config
|
| 167 |
+
Args:
|
| 168 |
+
stride (bool): model with FPN need image shape % stride == 0
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
def __init__(self, stride=0):
|
| 172 |
+
self.coarsest_stride = stride
|
| 173 |
+
|
| 174 |
+
def __call__(self, im, im_info):
|
| 175 |
+
"""
|
| 176 |
+
Args:
|
| 177 |
+
im (np.ndarray): image (np.ndarray)
|
| 178 |
+
im_info (dict): info of image
|
| 179 |
+
Returns:
|
| 180 |
+
im (np.ndarray): processed image (np.ndarray)
|
| 181 |
+
im_info (dict): info of processed image
|
| 182 |
+
"""
|
| 183 |
+
coarsest_stride = self.coarsest_stride
|
| 184 |
+
if coarsest_stride <= 0:
|
| 185 |
+
return im, im_info
|
| 186 |
+
im_c, im_h, im_w = im.shape
|
| 187 |
+
pad_h = int(np.ceil(float(im_h) / coarsest_stride) * coarsest_stride)
|
| 188 |
+
pad_w = int(np.ceil(float(im_w) / coarsest_stride) * coarsest_stride)
|
| 189 |
+
padding_im = np.zeros((im_c, pad_h, pad_w), dtype=np.float32)
|
| 190 |
+
padding_im[:, :im_h, :im_w] = im
|
| 191 |
+
return padding_im, im_info
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
class LetterBoxResize(object):
|
| 195 |
+
def __init__(self, target_size):
|
| 196 |
+
"""
|
| 197 |
+
Resize image to target size, convert normalized xywh to pixel xyxy
|
| 198 |
+
format ([x_center, y_center, width, height] -> [x0, y0, x1, y1]).
|
| 199 |
+
Args:
|
| 200 |
+
target_size (int|list): image target size.
|
| 201 |
+
"""
|
| 202 |
+
super(LetterBoxResize, self).__init__()
|
| 203 |
+
if isinstance(target_size, int):
|
| 204 |
+
target_size = [target_size, target_size]
|
| 205 |
+
self.target_size = target_size
|
| 206 |
+
|
| 207 |
+
def letterbox(self, img, height, width, color=(127.5, 127.5, 127.5)):
|
| 208 |
+
# letterbox: resize a rectangular image to a padded rectangular
|
| 209 |
+
shape = img.shape[:2] # [height, width]
|
| 210 |
+
ratio_h = float(height) / shape[0]
|
| 211 |
+
ratio_w = float(width) / shape[1]
|
| 212 |
+
ratio = min(ratio_h, ratio_w)
|
| 213 |
+
new_shape = (round(shape[1] * ratio),
|
| 214 |
+
round(shape[0] * ratio)) # [width, height]
|
| 215 |
+
padw = (width - new_shape[0]) / 2
|
| 216 |
+
padh = (height - new_shape[1]) / 2
|
| 217 |
+
top, bottom = round(padh - 0.1), round(padh + 0.1)
|
| 218 |
+
left, right = round(padw - 0.1), round(padw + 0.1)
|
| 219 |
+
|
| 220 |
+
img = cv2.resize(
|
| 221 |
+
img, new_shape, interpolation=cv2.INTER_AREA) # resized, no border
|
| 222 |
+
img = cv2.copyMakeBorder(
|
| 223 |
+
img, top, bottom, left, right, cv2.BORDER_CONSTANT,
|
| 224 |
+
value=color) # padded rectangular
|
| 225 |
+
return img, ratio, padw, padh
|
| 226 |
+
|
| 227 |
+
def __call__(self, im, im_info):
|
| 228 |
+
"""
|
| 229 |
+
Args:
|
| 230 |
+
im (np.ndarray): image (np.ndarray)
|
| 231 |
+
im_info (dict): info of image
|
| 232 |
+
Returns:
|
| 233 |
+
im (np.ndarray): processed image (np.ndarray)
|
| 234 |
+
im_info (dict): info of processed image
|
| 235 |
+
"""
|
| 236 |
+
assert len(self.target_size) == 2
|
| 237 |
+
assert self.target_size[0] > 0 and self.target_size[1] > 0
|
| 238 |
+
height, width = self.target_size
|
| 239 |
+
h, w = im.shape[:2]
|
| 240 |
+
im, ratio, padw, padh = self.letterbox(im, height=height, width=width)
|
| 241 |
+
|
| 242 |
+
new_shape = [round(h * ratio), round(w * ratio)]
|
| 243 |
+
im_info['im_shape'] = np.array(new_shape, dtype=np.float32)
|
| 244 |
+
im_info['scale_factor'] = np.array([ratio, ratio], dtype=np.float32)
|
| 245 |
+
return im, im_info
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
class Pad(object):
|
| 249 |
+
def __init__(self, size, fill_value=[114.0, 114.0, 114.0]):
|
| 250 |
+
"""
|
| 251 |
+
Pad image to a specified size.
|
| 252 |
+
Args:
|
| 253 |
+
size (list[int]): image target size
|
| 254 |
+
fill_value (list[float]): rgb value of pad area, default (114.0, 114.0, 114.0)
|
| 255 |
+
"""
|
| 256 |
+
super(Pad, self).__init__()
|
| 257 |
+
if isinstance(size, int):
|
| 258 |
+
size = [size, size]
|
| 259 |
+
self.size = size
|
| 260 |
+
self.fill_value = fill_value
|
| 261 |
+
|
| 262 |
+
def __call__(self, im, im_info):
|
| 263 |
+
im_h, im_w = im.shape[:2]
|
| 264 |
+
h, w = self.size
|
| 265 |
+
if h == im_h and w == im_w:
|
| 266 |
+
im = im.astype(np.float32)
|
| 267 |
+
return im, im_info
|
| 268 |
+
|
| 269 |
+
canvas = np.ones((h, w, 3), dtype=np.float32)
|
| 270 |
+
canvas *= np.array(self.fill_value, dtype=np.float32)
|
| 271 |
+
canvas[0:im_h, 0:im_w, :] = im.astype(np.float32)
|
| 272 |
+
im = canvas
|
| 273 |
+
return im, im_info
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def preprocess(im, preprocess_ops):
|
| 277 |
+
# process image by preprocess_ops
|
| 278 |
+
im_info = {
|
| 279 |
+
'scale_factor': np.array(
|
| 280 |
+
[1., 1.], dtype=np.float32),
|
| 281 |
+
'im_shape': None,
|
| 282 |
+
}
|
| 283 |
+
im, im_info = decode_image(im, im_info)
|
| 284 |
+
for operator in preprocess_ops:
|
| 285 |
+
im, im_info = operator(im, im_info)
|
| 286 |
+
return im, im_info
|
pptracking/python/tracker_config.yml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# config of tracker for MOT SDE Detector, use 'OCSORTTracker' as default, 'JDETracker' here is just BYTETracker.
|
| 2 |
+
# The tracker of MOT JDE Detector (such as FairMOT) is exported together with the model.
|
| 3 |
+
# Here 'min_box_area' and 'vertical_ratio' are set for pedestrian, you can modify for other objects tracking.
|
| 4 |
+
|
| 5 |
+
type: OCSORTTracker # choose one tracker in ['JDETracker', 'OCSORTTracker', 'DeepSORTTracker']
|
| 6 |
+
# When using for MTMCT(Multi-Target Multi-Camera Tracking), you should modify to 'DeepSORTTracker'
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# just as BYTETracker, used for FairMOT in PP-Tracking project and for ByteTrack in PP-Humanv1 project
|
| 10 |
+
JDETracker:
|
| 11 |
+
use_byte: True
|
| 12 |
+
det_thresh: 0.3
|
| 13 |
+
conf_thres: 0.6
|
| 14 |
+
low_conf_thres: 0.1
|
| 15 |
+
match_thres: 0.9
|
| 16 |
+
min_box_area: 0
|
| 17 |
+
vertical_ratio: 0 # 1.6 for pedestrian
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# used for OC-SORT in PP-Humanv2 project and PP-Vehicle project
|
| 21 |
+
OCSORTTracker:
|
| 22 |
+
det_thresh: 0.4
|
| 23 |
+
max_age: 30
|
| 24 |
+
min_hits: 3
|
| 25 |
+
iou_threshold: 0.3
|
| 26 |
+
delta_t: 3
|
| 27 |
+
inertia: 0.2
|
| 28 |
+
min_box_area: 0
|
| 29 |
+
vertical_ratio: 0
|
| 30 |
+
use_byte: False
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# used for DeepSORT and MTMCT in PP-Tracking project
|
| 34 |
+
DeepSORTTracker:
|
| 35 |
+
input_size: [64, 192] # An unique operation to scale the sub-image of the selected detected boxes to a fixed size
|
| 36 |
+
min_box_area: 0
|
| 37 |
+
vertical_ratio: -1
|
| 38 |
+
budget: 100
|
| 39 |
+
max_age: 70
|
| 40 |
+
n_init: 3
|
| 41 |
+
metric_type: cosine
|
| 42 |
+
matching_threshold: 0.2
|
| 43 |
+
max_iou_distance: 0.9
|
python/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python端预测部署
|
| 2 |
+
|
| 3 |
+
在PaddlePaddle中预测引擎和训练引擎底层有着不同的优化方法, 预测引擎使用了AnalysisPredictor,专门针对推理进行了优化,是基于[C++预测库](https://www.paddlepaddle.org.cn/documentation/docs/zh/advanced_guide/inference_deployment/inference/native_infer.html)的Python接口,该引擎可以对模型进行多项图优化,减少不必要的内存拷贝。如果用户在部署已训练模型的过程中对性能有较高的要求,我们提供了独立于PaddleDetection的预测脚本,方便用户直接集成部署。
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
Python端预测部署主要包含两个步骤:
|
| 7 |
+
- 导出预测模型
|
| 8 |
+
- 基于Python进行预测
|
| 9 |
+
|
| 10 |
+
## 1. 导出预测模型
|
| 11 |
+
|
| 12 |
+
PaddleDetection在训练过程包括网络的前向和优化器相关参数,而在部署过程中,我们只需要前向参数,具体参考:[导出模型](../EXPORT_MODEL.md),例如
|
| 13 |
+
|
| 14 |
+
```bash
|
| 15 |
+
# 导出YOLOv3检测模型
|
| 16 |
+
python tools/export_model.py -c configs/yolov3/yolov3_darknet53_270e_coco.yml --output_dir=./inference_model \
|
| 17 |
+
-o weights=https://paddledet.bj.bcebos.com/models/yolov3_darknet53_270e_coco.pdparams
|
| 18 |
+
|
| 19 |
+
# 导出HigherHRNet(bottom-up)关键点检测模型
|
| 20 |
+
python tools/export_model.py -c configs/keypoint/higherhrnet/higherhrnet_hrnet_w32_512.yml -o weights=https://paddledet.bj.bcebos.com/models/keypoint/higherhrnet_hrnet_w32_512.pdparams
|
| 21 |
+
|
| 22 |
+
# 导出HRNet(top-down)关键点检测模型
|
| 23 |
+
python tools/export_model.py -c configs/keypoint/hrnet/hrnet_w32_384x288.yml -o weights=https://paddledet.bj.bcebos.com/models/keypoint/hrnet_w32_384x288.pdparams
|
| 24 |
+
|
| 25 |
+
# 导出FairMOT多目标跟踪模型
|
| 26 |
+
python tools/export_model.py -c configs/mot/fairmot/fairmot_dla34_30e_1088x608.yml -o weights=https://paddledet.bj.bcebos.com/models/mot/fairmot_dla34_30e_1088x608.pdparams
|
| 27 |
+
|
| 28 |
+
# 导出ByteTrack多目标跟踪模型(相当于只导出检测器)
|
| 29 |
+
python tools/export_model.py -c configs/mot/bytetrack/detector/ppyoloe_crn_l_36e_640x640_mot17half.yml -o weights=https://paddledet.bj.bcebos.com/models/mot/ppyoloe_crn_l_36e_640x640_mot17half.pdparams
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
导出后目录下,包括`infer_cfg.yml`, `model.pdiparams`, `model.pdiparams.info`, `model.pdmodel`四个文件。
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
## 2. 基于Python的预测
|
| 36 |
+
|
| 37 |
+
### 2.1 通用检测
|
| 38 |
+
在终端输入以下命令进行预测:
|
| 39 |
+
```bash
|
| 40 |
+
python deploy/python/infer.py --model_dir=./output_inference/yolov3_darknet53_270e_coco --image_file=./demo/000000014439.jpg --device=GPU
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### 2.2 关键点检测
|
| 44 |
+
在终端输入以下命令进行预测:
|
| 45 |
+
```bash
|
| 46 |
+
# keypoint top-down(HRNet)/bottom-up(HigherHRNet)单独推理,该模式下top-down模型HRNet只支持单人截图预测
|
| 47 |
+
python deploy/python/keypoint_infer.py --model_dir=output_inference/hrnet_w32_384x288/ --image_file=./demo/hrnet_demo.jpg --device=GPU --threshold=0.5
|
| 48 |
+
python deploy/python/keypoint_infer.py --model_dir=output_inference/higherhrnet_hrnet_w32_512/ --image_file=./demo/000000014439_640x640.jpg --device=GPU --threshold=0.5
|
| 49 |
+
|
| 50 |
+
# detector 检测 + keypoint top-down模型联合部署(联合推理只支持top-down关键点模型)
|
| 51 |
+
python deploy/python/det_keypoint_unite_infer.py --det_model_dir=output_inference/yolov3_darknet53_270e_coco/ --keypoint_model_dir=output_inference/hrnet_w32_384x288/ --video_file={your video name}.mp4 --device=GPU
|
| 52 |
+
```
|
| 53 |
+
**注意:**
|
| 54 |
+
- 关键点检测模型导出和预测具体可参照[keypoint](../../configs/keypoint/README.md),可分别在各个模型的文档中查找具体用法;
|
| 55 |
+
- 此目录下的关键点检测部署为基础前向功能,更多关键点检测功能可使用PP-Human项目,参照[pipeline](../pipeline/README.md);
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
### 2.3 多目标跟踪
|
| 59 |
+
在终端输入以下命令进行预测:
|
| 60 |
+
```bash
|
| 61 |
+
# FairMOT跟踪
|
| 62 |
+
python deploy/python/mot_jde_infer.py --model_dir=output_inference/fairmot_dla34_30e_1088x608 --video_file={your video name}.mp4 --device=GPU
|
| 63 |
+
|
| 64 |
+
# ByteTrack跟踪
|
| 65 |
+
python deploy/python/mot_sde_infer.py --model_dir=output_inference/ppyoloe_crn_l_36e_640x640_mot17half/ --tracker_config=deploy/python/tracker_config.yml --video_file={your video name}.mp4 --device=GPU --scaled=True
|
| 66 |
+
|
| 67 |
+
# FairMOT多目标跟踪联合HRNet关键点检测(联合推理只支持top-down关键点模型)
|
| 68 |
+
python deploy/python/mot_keypoint_unite_infer.py --mot_model_dir=output_inference/fairmot_dla34_30e_1088x608/ --keypoint_model_dir=output_inference/hrnet_w32_384x288/ --video_file={your video name}.mp4 --device=GPU
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**注意:**
|
| 72 |
+
- 多目标跟踪模型导出和预测具体可参照[mot]](../../configs/mot/README.md),可分别在各个模型的文档中查找具体用法;
|
| 73 |
+
- 此目录下的跟踪部署为基础前向功能以及联合关键点部署,更多跟踪功能可使用PP-Human项目,参照[pipeline](../pipeline/README.md),或PP-Tracking项目(绘制轨迹、出入口流量计数),参照[pptracking](../pptracking/README.md);
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
参数说明如下:
|
| 77 |
+
|
| 78 |
+
| 参数 | 是否必须| 含义 |
|
| 79 |
+
|-------|-------|---------------------------------------------------------------------------------------------|
|
| 80 |
+
| --model_dir | Yes| 上述导出的模型路径 |
|
| 81 |
+
| --image_file | Option | 需要预测的图片 |
|
| 82 |
+
| --image_dir | Option | 要预测的图片文件夹路径 |
|
| 83 |
+
| --video_file | Option | 需要预测的视频 |
|
| 84 |
+
| --camera_id | Option | 用来预测的摄像头ID,默认为-1(表示不使用摄像头预测,可设置为:0 - (摄像头数目-1) ),预测过程中在可视化界面按`q`退出输出预测结果到:output/output.mp4 |
|
| 85 |
+
| --device | Option | 运行时的设备,可选择`CPU/GPU/XPU`,默认为`CPU` |
|
| 86 |
+
| --run_mode | Option | 使用GPU时,默认为paddle, 可选(paddle/trt_fp32/trt_fp16/trt_int8) |
|
| 87 |
+
| --batch_size | Option | 预测时的batch size,在指定`image_dir`时有效,默认为1 |
|
| 88 |
+
| --threshold | Option| 预测得分的阈值,默认为0.5 |
|
| 89 |
+
| --output_dir | Option| 可视化结果保存的根目录,默认为output/ |
|
| 90 |
+
| --run_benchmark | Option| 是否运行benchmark,同时需指定`--image_file`或`--image_dir`,默认为False |
|
| 91 |
+
| --enable_mkldnn | Option | CPU预测中是否开启MKLDNN加速,默认为False |
|
| 92 |
+
| --cpu_threads | Option| 设置cpu线程数,默认为1 |
|
| 93 |
+
| --trt_calib_mode | Option| TensorRT是否使用校准功能,默认为False。使用TensorRT的int8功能时,需设置为True,使用PaddleSlim量化后的模型时需要设置为False |
|
| 94 |
+
| --save_images | Option| 是否保存可视化结果 |
|
| 95 |
+
| --save_results | Option| 是否在文件夹下将图片的预测结果以JSON的形式保存 |
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
说明:
|
| 99 |
+
|
| 100 |
+
- 参数优先级顺序:`camera_id` > `video_file` > `image_dir` > `image_file`。
|
| 101 |
+
- run_mode:paddle代表使用AnalysisPredictor,精度float32来推理,其他参数指用AnalysisPredictor,TensorRT不同精度来推理。
|
| 102 |
+
- 如果安装的PaddlePaddle不支持基于TensorRT进行预测,需要自行编译,详细可参考[预测库编译教程](https://paddleinference.paddlepaddle.org.cn/user_guides/source_compile.html)。
|
| 103 |
+
- --run_benchmark如果设置为True,则需要安装依赖`pip install pynvml psutil GPUtil`。
|
| 104 |
+
- 如果需要使用导出模型在coco数据集上进行评估,请在推理时添加`--save_results`和`--use_coco_category`参数用以保存coco评估所需要的json文件
|
python/benchmark_utils.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import logging
|
| 17 |
+
|
| 18 |
+
import paddle
|
| 19 |
+
import paddle.inference as paddle_infer
|
| 20 |
+
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
|
| 23 |
+
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 24 |
+
LOG_PATH_ROOT = f"{CUR_DIR}/../../output"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class PaddleInferBenchmark(object):
|
| 28 |
+
def __init__(self,
|
| 29 |
+
config,
|
| 30 |
+
model_info: dict={},
|
| 31 |
+
data_info: dict={},
|
| 32 |
+
perf_info: dict={},
|
| 33 |
+
resource_info: dict={},
|
| 34 |
+
**kwargs):
|
| 35 |
+
"""
|
| 36 |
+
Construct PaddleInferBenchmark Class to format logs.
|
| 37 |
+
args:
|
| 38 |
+
config(paddle.inference.Config): paddle inference config
|
| 39 |
+
model_info(dict): basic model info
|
| 40 |
+
{'model_name': 'resnet50'
|
| 41 |
+
'precision': 'fp32'}
|
| 42 |
+
data_info(dict): input data info
|
| 43 |
+
{'batch_size': 1
|
| 44 |
+
'shape': '3,224,224'
|
| 45 |
+
'data_num': 1000}
|
| 46 |
+
perf_info(dict): performance result
|
| 47 |
+
{'preprocess_time_s': 1.0
|
| 48 |
+
'inference_time_s': 2.0
|
| 49 |
+
'postprocess_time_s': 1.0
|
| 50 |
+
'total_time_s': 4.0}
|
| 51 |
+
resource_info(dict):
|
| 52 |
+
cpu and gpu resources
|
| 53 |
+
{'cpu_rss': 100
|
| 54 |
+
'gpu_rss': 100
|
| 55 |
+
'gpu_util': 60}
|
| 56 |
+
"""
|
| 57 |
+
# PaddleInferBenchmark Log Version
|
| 58 |
+
self.log_version = "1.0.3"
|
| 59 |
+
|
| 60 |
+
# Paddle Version
|
| 61 |
+
self.paddle_version = paddle.__version__
|
| 62 |
+
self.paddle_commit = paddle.__git_commit__
|
| 63 |
+
paddle_infer_info = paddle_infer.get_version()
|
| 64 |
+
self.paddle_branch = paddle_infer_info.strip().split(': ')[-1]
|
| 65 |
+
|
| 66 |
+
# model info
|
| 67 |
+
self.model_info = model_info
|
| 68 |
+
|
| 69 |
+
# data info
|
| 70 |
+
self.data_info = data_info
|
| 71 |
+
|
| 72 |
+
# perf info
|
| 73 |
+
self.perf_info = perf_info
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
# required value
|
| 77 |
+
self.model_name = model_info['model_name']
|
| 78 |
+
self.precision = model_info['precision']
|
| 79 |
+
|
| 80 |
+
self.batch_size = data_info['batch_size']
|
| 81 |
+
self.shape = data_info['shape']
|
| 82 |
+
self.data_num = data_info['data_num']
|
| 83 |
+
|
| 84 |
+
self.inference_time_s = round(perf_info['inference_time_s'], 4)
|
| 85 |
+
except:
|
| 86 |
+
self.print_help()
|
| 87 |
+
raise ValueError(
|
| 88 |
+
"Set argument wrong, please check input argument and its type")
|
| 89 |
+
|
| 90 |
+
self.preprocess_time_s = perf_info.get('preprocess_time_s', 0)
|
| 91 |
+
self.postprocess_time_s = perf_info.get('postprocess_time_s', 0)
|
| 92 |
+
self.with_tracker = True if 'tracking_time_s' in perf_info else False
|
| 93 |
+
self.tracking_time_s = perf_info.get('tracking_time_s', 0)
|
| 94 |
+
self.total_time_s = perf_info.get('total_time_s', 0)
|
| 95 |
+
|
| 96 |
+
self.inference_time_s_90 = perf_info.get("inference_time_s_90", "")
|
| 97 |
+
self.inference_time_s_99 = perf_info.get("inference_time_s_99", "")
|
| 98 |
+
self.succ_rate = perf_info.get("succ_rate", "")
|
| 99 |
+
self.qps = perf_info.get("qps", "")
|
| 100 |
+
|
| 101 |
+
# conf info
|
| 102 |
+
self.config_status = self.parse_config(config)
|
| 103 |
+
|
| 104 |
+
# mem info
|
| 105 |
+
if isinstance(resource_info, dict):
|
| 106 |
+
self.cpu_rss_mb = int(resource_info.get('cpu_rss_mb', 0))
|
| 107 |
+
self.cpu_vms_mb = int(resource_info.get('cpu_vms_mb', 0))
|
| 108 |
+
self.cpu_shared_mb = int(resource_info.get('cpu_shared_mb', 0))
|
| 109 |
+
self.cpu_dirty_mb = int(resource_info.get('cpu_dirty_mb', 0))
|
| 110 |
+
self.cpu_util = round(resource_info.get('cpu_util', 0), 2)
|
| 111 |
+
|
| 112 |
+
self.gpu_rss_mb = int(resource_info.get('gpu_rss_mb', 0))
|
| 113 |
+
self.gpu_util = round(resource_info.get('gpu_util', 0), 2)
|
| 114 |
+
self.gpu_mem_util = round(resource_info.get('gpu_mem_util', 0), 2)
|
| 115 |
+
else:
|
| 116 |
+
self.cpu_rss_mb = 0
|
| 117 |
+
self.cpu_vms_mb = 0
|
| 118 |
+
self.cpu_shared_mb = 0
|
| 119 |
+
self.cpu_dirty_mb = 0
|
| 120 |
+
self.cpu_util = 0
|
| 121 |
+
|
| 122 |
+
self.gpu_rss_mb = 0
|
| 123 |
+
self.gpu_util = 0
|
| 124 |
+
self.gpu_mem_util = 0
|
| 125 |
+
|
| 126 |
+
# init benchmark logger
|
| 127 |
+
self.benchmark_logger()
|
| 128 |
+
|
| 129 |
+
def benchmark_logger(self):
|
| 130 |
+
"""
|
| 131 |
+
benchmark logger
|
| 132 |
+
"""
|
| 133 |
+
# remove other logging handler
|
| 134 |
+
for handler in logging.root.handlers[:]:
|
| 135 |
+
logging.root.removeHandler(handler)
|
| 136 |
+
|
| 137 |
+
# Init logger
|
| 138 |
+
FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 139 |
+
log_output = f"{LOG_PATH_ROOT}/{self.model_name}.log"
|
| 140 |
+
Path(f"{LOG_PATH_ROOT}").mkdir(parents=True, exist_ok=True)
|
| 141 |
+
logging.basicConfig(
|
| 142 |
+
level=logging.INFO,
|
| 143 |
+
format=FORMAT,
|
| 144 |
+
handlers=[
|
| 145 |
+
logging.FileHandler(
|
| 146 |
+
filename=log_output, mode='w'),
|
| 147 |
+
logging.StreamHandler(),
|
| 148 |
+
])
|
| 149 |
+
self.logger = logging.getLogger(__name__)
|
| 150 |
+
self.logger.info(
|
| 151 |
+
f"Paddle Inference benchmark log will be saved to {log_output}")
|
| 152 |
+
|
| 153 |
+
def parse_config(self, config) -> dict:
|
| 154 |
+
"""
|
| 155 |
+
parse paddle predictor config
|
| 156 |
+
args:
|
| 157 |
+
config(paddle.inference.Config): paddle inference config
|
| 158 |
+
return:
|
| 159 |
+
config_status(dict): dict style config info
|
| 160 |
+
"""
|
| 161 |
+
if isinstance(config, paddle_infer.Config):
|
| 162 |
+
config_status = {}
|
| 163 |
+
config_status['runtime_device'] = "gpu" if config.use_gpu(
|
| 164 |
+
) else "cpu"
|
| 165 |
+
config_status['ir_optim'] = config.ir_optim()
|
| 166 |
+
config_status['enable_tensorrt'] = config.tensorrt_engine_enabled()
|
| 167 |
+
config_status['precision'] = self.precision
|
| 168 |
+
config_status['enable_mkldnn'] = config.mkldnn_enabled()
|
| 169 |
+
config_status[
|
| 170 |
+
'cpu_math_library_num_threads'] = config.cpu_math_library_num_threads(
|
| 171 |
+
)
|
| 172 |
+
elif isinstance(config, dict):
|
| 173 |
+
config_status['runtime_device'] = config.get('runtime_device', "")
|
| 174 |
+
config_status['ir_optim'] = config.get('ir_optim', "")
|
| 175 |
+
config_status['enable_tensorrt'] = config.get('enable_tensorrt',
|
| 176 |
+
"")
|
| 177 |
+
config_status['precision'] = config.get('precision', "")
|
| 178 |
+
config_status['enable_mkldnn'] = config.get('enable_mkldnn', "")
|
| 179 |
+
config_status['cpu_math_library_num_threads'] = config.get(
|
| 180 |
+
'cpu_math_library_num_threads', "")
|
| 181 |
+
else:
|
| 182 |
+
self.print_help()
|
| 183 |
+
raise ValueError(
|
| 184 |
+
"Set argument config wrong, please check input argument and its type"
|
| 185 |
+
)
|
| 186 |
+
return config_status
|
| 187 |
+
|
| 188 |
+
def report(self, identifier=None):
|
| 189 |
+
"""
|
| 190 |
+
print log report
|
| 191 |
+
args:
|
| 192 |
+
identifier(string): identify log
|
| 193 |
+
"""
|
| 194 |
+
if identifier:
|
| 195 |
+
identifier = f"[{identifier}]"
|
| 196 |
+
else:
|
| 197 |
+
identifier = ""
|
| 198 |
+
|
| 199 |
+
self.logger.info("\n")
|
| 200 |
+
self.logger.info(
|
| 201 |
+
"---------------------- Paddle info ----------------------")
|
| 202 |
+
self.logger.info(f"{identifier} paddle_version: {self.paddle_version}")
|
| 203 |
+
self.logger.info(f"{identifier} paddle_commit: {self.paddle_commit}")
|
| 204 |
+
self.logger.info(f"{identifier} paddle_branch: {self.paddle_branch}")
|
| 205 |
+
self.logger.info(f"{identifier} log_api_version: {self.log_version}")
|
| 206 |
+
self.logger.info(
|
| 207 |
+
"----------------------- Conf info -----------------------")
|
| 208 |
+
self.logger.info(
|
| 209 |
+
f"{identifier} runtime_device: {self.config_status['runtime_device']}"
|
| 210 |
+
)
|
| 211 |
+
self.logger.info(
|
| 212 |
+
f"{identifier} ir_optim: {self.config_status['ir_optim']}")
|
| 213 |
+
self.logger.info(f"{identifier} enable_memory_optim: {True}")
|
| 214 |
+
self.logger.info(
|
| 215 |
+
f"{identifier} enable_tensorrt: {self.config_status['enable_tensorrt']}"
|
| 216 |
+
)
|
| 217 |
+
self.logger.info(
|
| 218 |
+
f"{identifier} enable_mkldnn: {self.config_status['enable_mkldnn']}"
|
| 219 |
+
)
|
| 220 |
+
self.logger.info(
|
| 221 |
+
f"{identifier} cpu_math_library_num_threads: {self.config_status['cpu_math_library_num_threads']}"
|
| 222 |
+
)
|
| 223 |
+
self.logger.info(
|
| 224 |
+
"----------------------- Model info ----------------------")
|
| 225 |
+
self.logger.info(f"{identifier} model_name: {self.model_name}")
|
| 226 |
+
self.logger.info(f"{identifier} precision: {self.precision}")
|
| 227 |
+
self.logger.info(
|
| 228 |
+
"----------------------- Data info -----------------------")
|
| 229 |
+
self.logger.info(f"{identifier} batch_size: {self.batch_size}")
|
| 230 |
+
self.logger.info(f"{identifier} input_shape: {self.shape}")
|
| 231 |
+
self.logger.info(f"{identifier} data_num: {self.data_num}")
|
| 232 |
+
self.logger.info(
|
| 233 |
+
"----------------------- Perf info -----------------------")
|
| 234 |
+
self.logger.info(
|
| 235 |
+
f"{identifier} cpu_rss(MB): {self.cpu_rss_mb}, cpu_vms: {self.cpu_vms_mb}, cpu_shared_mb: {self.cpu_shared_mb}, cpu_dirty_mb: {self.cpu_dirty_mb}, cpu_util: {self.cpu_util}%"
|
| 236 |
+
)
|
| 237 |
+
self.logger.info(
|
| 238 |
+
f"{identifier} gpu_rss(MB): {self.gpu_rss_mb}, gpu_util: {self.gpu_util}%, gpu_mem_util: {self.gpu_mem_util}%"
|
| 239 |
+
)
|
| 240 |
+
self.logger.info(
|
| 241 |
+
f"{identifier} total time spent(s): {self.total_time_s}")
|
| 242 |
+
|
| 243 |
+
if self.with_tracker:
|
| 244 |
+
self.logger.info(
|
| 245 |
+
f"{identifier} preprocess_time(ms): {round(self.preprocess_time_s*1000, 1)}, "
|
| 246 |
+
f"inference_time(ms): {round(self.inference_time_s*1000, 1)}, "
|
| 247 |
+
f"postprocess_time(ms): {round(self.postprocess_time_s*1000, 1)}, "
|
| 248 |
+
f"tracking_time(ms): {round(self.tracking_time_s*1000, 1)}")
|
| 249 |
+
else:
|
| 250 |
+
self.logger.info(
|
| 251 |
+
f"{identifier} preprocess_time(ms): {round(self.preprocess_time_s*1000, 1)}, "
|
| 252 |
+
f"inference_time(ms): {round(self.inference_time_s*1000, 1)}, "
|
| 253 |
+
f"postprocess_time(ms): {round(self.postprocess_time_s*1000, 1)}"
|
| 254 |
+
)
|
| 255 |
+
if self.inference_time_s_90:
|
| 256 |
+
self.looger.info(
|
| 257 |
+
f"{identifier} 90%_cost: {self.inference_time_s_90}, 99%_cost: {self.inference_time_s_99}, succ_rate: {self.succ_rate}"
|
| 258 |
+
)
|
| 259 |
+
if self.qps:
|
| 260 |
+
self.logger.info(f"{identifier} QPS: {self.qps}")
|
| 261 |
+
|
| 262 |
+
def print_help(self):
|
| 263 |
+
"""
|
| 264 |
+
print function help
|
| 265 |
+
"""
|
| 266 |
+
print("""Usage:
|
| 267 |
+
==== Print inference benchmark logs. ====
|
| 268 |
+
config = paddle.inference.Config()
|
| 269 |
+
model_info = {'model_name': 'resnet50'
|
| 270 |
+
'precision': 'fp32'}
|
| 271 |
+
data_info = {'batch_size': 1
|
| 272 |
+
'shape': '3,224,224'
|
| 273 |
+
'data_num': 1000}
|
| 274 |
+
perf_info = {'preprocess_time_s': 1.0
|
| 275 |
+
'inference_time_s': 2.0
|
| 276 |
+
'postprocess_time_s': 1.0
|
| 277 |
+
'total_time_s': 4.0}
|
| 278 |
+
resource_info = {'cpu_rss_mb': 100
|
| 279 |
+
'gpu_rss_mb': 100
|
| 280 |
+
'gpu_util': 60}
|
| 281 |
+
log = PaddleInferBenchmark(config, model_info, data_info, perf_info, resource_info)
|
| 282 |
+
log('Test')
|
| 283 |
+
""")
|
| 284 |
+
|
| 285 |
+
def __call__(self, identifier=None):
|
| 286 |
+
"""
|
| 287 |
+
__call__
|
| 288 |
+
args:
|
| 289 |
+
identifier(string): identify log
|
| 290 |
+
"""
|
| 291 |
+
self.report(identifier)
|
python/det_keypoint_unite_infer.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import json
|
| 17 |
+
import cv2
|
| 18 |
+
import math
|
| 19 |
+
import numpy as np
|
| 20 |
+
import paddle
|
| 21 |
+
import yaml
|
| 22 |
+
|
| 23 |
+
from det_keypoint_unite_utils import argsparser
|
| 24 |
+
from preprocess import decode_image
|
| 25 |
+
from infer import Detector, DetectorPicoDet, PredictConfig, print_arguments, get_test_images, bench_log
|
| 26 |
+
from keypoint_infer import KeyPointDetector, PredictConfig_KeyPoint
|
| 27 |
+
from visualize import visualize_pose
|
| 28 |
+
from benchmark_utils import PaddleInferBenchmark
|
| 29 |
+
from utils import get_current_memory_mb
|
| 30 |
+
from keypoint_postprocess import translate_to_ori_images
|
| 31 |
+
|
| 32 |
+
KEYPOINT_SUPPORT_MODELS = {
|
| 33 |
+
'HigherHRNet': 'keypoint_bottomup',
|
| 34 |
+
'HRNet': 'keypoint_topdown'
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def predict_with_given_det(image, det_res, keypoint_detector,
|
| 39 |
+
keypoint_batch_size, run_benchmark):
|
| 40 |
+
keypoint_res = {}
|
| 41 |
+
|
| 42 |
+
rec_images, records, det_rects = keypoint_detector.get_person_from_rect(
|
| 43 |
+
image, det_res)
|
| 44 |
+
|
| 45 |
+
if len(det_rects) == 0:
|
| 46 |
+
keypoint_res['keypoint'] = [[], []]
|
| 47 |
+
return keypoint_res
|
| 48 |
+
|
| 49 |
+
keypoint_vector = []
|
| 50 |
+
score_vector = []
|
| 51 |
+
|
| 52 |
+
rect_vector = det_rects
|
| 53 |
+
keypoint_results = keypoint_detector.predict_image(
|
| 54 |
+
rec_images, run_benchmark, repeats=10, visual=False)
|
| 55 |
+
keypoint_vector, score_vector = translate_to_ori_images(keypoint_results,
|
| 56 |
+
np.array(records))
|
| 57 |
+
keypoint_res['keypoint'] = [
|
| 58 |
+
keypoint_vector.tolist(), score_vector.tolist()
|
| 59 |
+
] if len(keypoint_vector) > 0 else [[], []]
|
| 60 |
+
keypoint_res['bbox'] = rect_vector
|
| 61 |
+
return keypoint_res
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def topdown_unite_predict(detector,
|
| 65 |
+
topdown_keypoint_detector,
|
| 66 |
+
image_list,
|
| 67 |
+
keypoint_batch_size=1,
|
| 68 |
+
save_res=False):
|
| 69 |
+
det_timer = detector.get_timer()
|
| 70 |
+
store_res = []
|
| 71 |
+
for i, img_file in enumerate(image_list):
|
| 72 |
+
# Decode image in advance in det + pose prediction
|
| 73 |
+
det_timer.preprocess_time_s.start()
|
| 74 |
+
image, _ = decode_image(img_file, {})
|
| 75 |
+
det_timer.preprocess_time_s.end()
|
| 76 |
+
|
| 77 |
+
if FLAGS.run_benchmark:
|
| 78 |
+
results = detector.predict_image(
|
| 79 |
+
[image], run_benchmark=True, repeats=10)
|
| 80 |
+
|
| 81 |
+
cm, gm, gu = get_current_memory_mb()
|
| 82 |
+
detector.cpu_mem += cm
|
| 83 |
+
detector.gpu_mem += gm
|
| 84 |
+
detector.gpu_util += gu
|
| 85 |
+
else:
|
| 86 |
+
results = detector.predict_image([image], visual=False)
|
| 87 |
+
results = detector.filter_box(results, FLAGS.det_threshold)
|
| 88 |
+
if results['boxes_num'] > 0:
|
| 89 |
+
keypoint_res = predict_with_given_det(
|
| 90 |
+
image, results, topdown_keypoint_detector, keypoint_batch_size,
|
| 91 |
+
FLAGS.run_benchmark)
|
| 92 |
+
|
| 93 |
+
if save_res:
|
| 94 |
+
save_name = img_file if isinstance(img_file, str) else i
|
| 95 |
+
store_res.append([
|
| 96 |
+
save_name, keypoint_res['bbox'], [
|
| 97 |
+
keypoint_res['keypoint'][0],
|
| 98 |
+
keypoint_res['keypoint'][1]
|
| 99 |
+
]
|
| 100 |
+
])
|
| 101 |
+
else:
|
| 102 |
+
results["keypoint"] = [[], []]
|
| 103 |
+
keypoint_res = results
|
| 104 |
+
if FLAGS.run_benchmark:
|
| 105 |
+
cm, gm, gu = get_current_memory_mb()
|
| 106 |
+
topdown_keypoint_detector.cpu_mem += cm
|
| 107 |
+
topdown_keypoint_detector.gpu_mem += gm
|
| 108 |
+
topdown_keypoint_detector.gpu_util += gu
|
| 109 |
+
else:
|
| 110 |
+
if not os.path.exists(FLAGS.output_dir):
|
| 111 |
+
os.makedirs(FLAGS.output_dir)
|
| 112 |
+
visualize_pose(
|
| 113 |
+
img_file,
|
| 114 |
+
keypoint_res,
|
| 115 |
+
visual_thresh=FLAGS.keypoint_threshold,
|
| 116 |
+
save_dir=FLAGS.output_dir)
|
| 117 |
+
if save_res:
|
| 118 |
+
"""
|
| 119 |
+
1) store_res: a list of image_data
|
| 120 |
+
2) image_data: [imageid, rects, [keypoints, scores]]
|
| 121 |
+
3) rects: list of rect [xmin, ymin, xmax, ymax]
|
| 122 |
+
4) keypoints: 17(joint numbers)*[x, y, conf], total 51 data in list
|
| 123 |
+
5) scores: mean of all joint conf
|
| 124 |
+
"""
|
| 125 |
+
with open("det_keypoint_unite_image_results.json", 'w') as wf:
|
| 126 |
+
json.dump(store_res, wf, indent=4)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def topdown_unite_predict_video(detector,
|
| 130 |
+
topdown_keypoint_detector,
|
| 131 |
+
camera_id,
|
| 132 |
+
keypoint_batch_size=1,
|
| 133 |
+
save_res=False):
|
| 134 |
+
video_name = 'output.mp4'
|
| 135 |
+
if camera_id != -1:
|
| 136 |
+
capture = cv2.VideoCapture(camera_id)
|
| 137 |
+
else:
|
| 138 |
+
capture = cv2.VideoCapture(FLAGS.video_file)
|
| 139 |
+
video_name = os.path.split(FLAGS.video_file)[-1]
|
| 140 |
+
# Get Video info : resolution, fps, frame count
|
| 141 |
+
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 142 |
+
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 143 |
+
fps = int(capture.get(cv2.CAP_PROP_FPS))
|
| 144 |
+
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 145 |
+
print("fps: %d, frame_count: %d" % (fps, frame_count))
|
| 146 |
+
|
| 147 |
+
if not os.path.exists(FLAGS.output_dir):
|
| 148 |
+
os.makedirs(FLAGS.output_dir)
|
| 149 |
+
out_path = os.path.join(FLAGS.output_dir, video_name)
|
| 150 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 151 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
|
| 152 |
+
index = 0
|
| 153 |
+
store_res = []
|
| 154 |
+
keypoint_smoothing = KeypointSmoothing(
|
| 155 |
+
width, height, filter_type=FLAGS.filter_type, beta=0.05)
|
| 156 |
+
|
| 157 |
+
while (1):
|
| 158 |
+
ret, frame = capture.read()
|
| 159 |
+
if not ret:
|
| 160 |
+
break
|
| 161 |
+
index += 1
|
| 162 |
+
print('detect frame: %d' % (index))
|
| 163 |
+
|
| 164 |
+
frame2 = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 165 |
+
|
| 166 |
+
results = detector.predict_image([frame2], visual=False)
|
| 167 |
+
results = detector.filter_box(results, FLAGS.det_threshold)
|
| 168 |
+
if results['boxes_num'] == 0:
|
| 169 |
+
writer.write(frame)
|
| 170 |
+
continue
|
| 171 |
+
|
| 172 |
+
keypoint_res = predict_with_given_det(
|
| 173 |
+
frame2, results, topdown_keypoint_detector, keypoint_batch_size,
|
| 174 |
+
FLAGS.run_benchmark)
|
| 175 |
+
|
| 176 |
+
if FLAGS.smooth and len(keypoint_res['keypoint'][0]) == 1:
|
| 177 |
+
current_keypoints = np.array(keypoint_res['keypoint'][0][0])
|
| 178 |
+
smooth_keypoints = keypoint_smoothing.smooth_process(
|
| 179 |
+
current_keypoints)
|
| 180 |
+
|
| 181 |
+
keypoint_res['keypoint'][0][0] = smooth_keypoints.tolist()
|
| 182 |
+
|
| 183 |
+
im = visualize_pose(
|
| 184 |
+
frame,
|
| 185 |
+
keypoint_res,
|
| 186 |
+
visual_thresh=FLAGS.keypoint_threshold,
|
| 187 |
+
returnimg=True)
|
| 188 |
+
|
| 189 |
+
if save_res:
|
| 190 |
+
store_res.append([
|
| 191 |
+
index, keypoint_res['bbox'],
|
| 192 |
+
[keypoint_res['keypoint'][0], keypoint_res['keypoint'][1]]
|
| 193 |
+
])
|
| 194 |
+
|
| 195 |
+
writer.write(im)
|
| 196 |
+
if camera_id != -1:
|
| 197 |
+
cv2.imshow('Mask Detection', im)
|
| 198 |
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
| 199 |
+
break
|
| 200 |
+
writer.release()
|
| 201 |
+
print('output_video saved to: {}'.format(out_path))
|
| 202 |
+
if save_res:
|
| 203 |
+
"""
|
| 204 |
+
1) store_res: a list of frame_data
|
| 205 |
+
2) frame_data: [frameid, rects, [keypoints, scores]]
|
| 206 |
+
3) rects: list of rect [xmin, ymin, xmax, ymax]
|
| 207 |
+
4) keypoints: 17(joint numbers)*[x, y, conf], total 51 data in list
|
| 208 |
+
5) scores: mean of all joint conf
|
| 209 |
+
"""
|
| 210 |
+
with open("det_keypoint_unite_video_results.json", 'w') as wf:
|
| 211 |
+
json.dump(store_res, wf, indent=4)
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
class KeypointSmoothing(object):
|
| 215 |
+
# The following code are modified from:
|
| 216 |
+
# https://github.com/jaantollander/OneEuroFilter
|
| 217 |
+
|
| 218 |
+
def __init__(self,
|
| 219 |
+
width,
|
| 220 |
+
height,
|
| 221 |
+
filter_type,
|
| 222 |
+
alpha=0.5,
|
| 223 |
+
fc_d=0.1,
|
| 224 |
+
fc_min=0.1,
|
| 225 |
+
beta=0.1,
|
| 226 |
+
thres_mult=0.3):
|
| 227 |
+
super(KeypointSmoothing, self).__init__()
|
| 228 |
+
self.image_width = width
|
| 229 |
+
self.image_height = height
|
| 230 |
+
self.threshold = np.array([
|
| 231 |
+
0.005, 0.005, 0.005, 0.005, 0.005, 0.01, 0.01, 0.01, 0.01, 0.01,
|
| 232 |
+
0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01
|
| 233 |
+
]) * thres_mult
|
| 234 |
+
self.filter_type = filter_type
|
| 235 |
+
self.alpha = alpha
|
| 236 |
+
self.dx_prev_hat = None
|
| 237 |
+
self.x_prev_hat = None
|
| 238 |
+
self.fc_d = fc_d
|
| 239 |
+
self.fc_min = fc_min
|
| 240 |
+
self.beta = beta
|
| 241 |
+
|
| 242 |
+
if self.filter_type == 'OneEuro':
|
| 243 |
+
self.smooth_func = self.one_euro_filter
|
| 244 |
+
elif self.filter_type == 'EMA':
|
| 245 |
+
self.smooth_func = self.ema_filter
|
| 246 |
+
else:
|
| 247 |
+
raise ValueError('filter type must be one_euro or ema')
|
| 248 |
+
|
| 249 |
+
def smooth_process(self, current_keypoints):
|
| 250 |
+
if self.x_prev_hat is None:
|
| 251 |
+
self.x_prev_hat = current_keypoints[:, :2]
|
| 252 |
+
self.dx_prev_hat = np.zeros(current_keypoints[:, :2].shape)
|
| 253 |
+
return current_keypoints
|
| 254 |
+
else:
|
| 255 |
+
result = current_keypoints
|
| 256 |
+
num_keypoints = len(current_keypoints)
|
| 257 |
+
for i in range(num_keypoints):
|
| 258 |
+
result[i, :2] = self.smooth(current_keypoints[i, :2],
|
| 259 |
+
self.threshold[i], i)
|
| 260 |
+
return result
|
| 261 |
+
|
| 262 |
+
def smooth(self, current_keypoint, threshold, index):
|
| 263 |
+
distance = np.sqrt(
|
| 264 |
+
np.square((current_keypoint[0] - self.x_prev_hat[index][0]) /
|
| 265 |
+
self.image_width) + np.square((current_keypoint[
|
| 266 |
+
1] - self.x_prev_hat[index][1]) / self.image_height))
|
| 267 |
+
if distance < threshold:
|
| 268 |
+
result = self.x_prev_hat[index]
|
| 269 |
+
else:
|
| 270 |
+
result = self.smooth_func(current_keypoint, self.x_prev_hat[index],
|
| 271 |
+
index)
|
| 272 |
+
|
| 273 |
+
return result
|
| 274 |
+
|
| 275 |
+
def one_euro_filter(self, x_cur, x_pre, index):
|
| 276 |
+
te = 1
|
| 277 |
+
self.alpha = self.smoothing_factor(te, self.fc_d)
|
| 278 |
+
dx_cur = (x_cur - x_pre) / te
|
| 279 |
+
dx_cur_hat = self.exponential_smoothing(dx_cur,
|
| 280 |
+
self.dx_prev_hat[index])
|
| 281 |
+
|
| 282 |
+
fc = self.fc_min + self.beta * np.abs(dx_cur_hat)
|
| 283 |
+
self.alpha = self.smoothing_factor(te, fc)
|
| 284 |
+
x_cur_hat = self.exponential_smoothing(x_cur, x_pre)
|
| 285 |
+
self.dx_prev_hat[index] = dx_cur_hat
|
| 286 |
+
self.x_prev_hat[index] = x_cur_hat
|
| 287 |
+
return x_cur_hat
|
| 288 |
+
|
| 289 |
+
def ema_filter(self, x_cur, x_pre, index):
|
| 290 |
+
x_cur_hat = self.exponential_smoothing(x_cur, x_pre)
|
| 291 |
+
self.x_prev_hat[index] = x_cur_hat
|
| 292 |
+
return x_cur_hat
|
| 293 |
+
|
| 294 |
+
def smoothing_factor(self, te, fc):
|
| 295 |
+
r = 2 * math.pi * fc * te
|
| 296 |
+
return r / (r + 1)
|
| 297 |
+
|
| 298 |
+
def exponential_smoothing(self, x_cur, x_pre, index=0):
|
| 299 |
+
return self.alpha * x_cur + (1 - self.alpha) * x_pre
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def main():
|
| 303 |
+
deploy_file = os.path.join(FLAGS.det_model_dir, 'infer_cfg.yml')
|
| 304 |
+
with open(deploy_file) as f:
|
| 305 |
+
yml_conf = yaml.safe_load(f)
|
| 306 |
+
arch = yml_conf['arch']
|
| 307 |
+
detector_func = 'Detector'
|
| 308 |
+
if arch == 'PicoDet':
|
| 309 |
+
detector_func = 'DetectorPicoDet'
|
| 310 |
+
|
| 311 |
+
detector = eval(detector_func)(FLAGS.det_model_dir,
|
| 312 |
+
device=FLAGS.device,
|
| 313 |
+
run_mode=FLAGS.run_mode,
|
| 314 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 315 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 316 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 317 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 318 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 319 |
+
enable_mkldnn=FLAGS.enable_mkldnn,
|
| 320 |
+
threshold=FLAGS.det_threshold)
|
| 321 |
+
|
| 322 |
+
topdown_keypoint_detector = KeyPointDetector(
|
| 323 |
+
FLAGS.keypoint_model_dir,
|
| 324 |
+
device=FLAGS.device,
|
| 325 |
+
run_mode=FLAGS.run_mode,
|
| 326 |
+
batch_size=FLAGS.keypoint_batch_size,
|
| 327 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 328 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 329 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 330 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 331 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 332 |
+
enable_mkldnn=FLAGS.enable_mkldnn,
|
| 333 |
+
use_dark=FLAGS.use_dark)
|
| 334 |
+
keypoint_arch = topdown_keypoint_detector.pred_config.arch
|
| 335 |
+
assert KEYPOINT_SUPPORT_MODELS[
|
| 336 |
+
keypoint_arch] == 'keypoint_topdown', 'Detection-Keypoint unite inference only supports topdown models.'
|
| 337 |
+
|
| 338 |
+
# predict from video file or camera video stream
|
| 339 |
+
if FLAGS.video_file is not None or FLAGS.camera_id != -1:
|
| 340 |
+
topdown_unite_predict_video(detector, topdown_keypoint_detector,
|
| 341 |
+
FLAGS.camera_id, FLAGS.keypoint_batch_size,
|
| 342 |
+
FLAGS.save_res)
|
| 343 |
+
else:
|
| 344 |
+
# predict from image
|
| 345 |
+
img_list = get_test_images(FLAGS.image_dir, FLAGS.image_file)
|
| 346 |
+
topdown_unite_predict(detector, topdown_keypoint_detector, img_list,
|
| 347 |
+
FLAGS.keypoint_batch_size, FLAGS.save_res)
|
| 348 |
+
if not FLAGS.run_benchmark:
|
| 349 |
+
detector.det_times.info(average=True)
|
| 350 |
+
topdown_keypoint_detector.det_times.info(average=True)
|
| 351 |
+
else:
|
| 352 |
+
mode = FLAGS.run_mode
|
| 353 |
+
det_model_dir = FLAGS.det_model_dir
|
| 354 |
+
det_model_info = {
|
| 355 |
+
'model_name': det_model_dir.strip('/').split('/')[-1],
|
| 356 |
+
'precision': mode.split('_')[-1]
|
| 357 |
+
}
|
| 358 |
+
bench_log(detector, img_list, det_model_info, name='Det')
|
| 359 |
+
keypoint_model_dir = FLAGS.keypoint_model_dir
|
| 360 |
+
keypoint_model_info = {
|
| 361 |
+
'model_name': keypoint_model_dir.strip('/').split('/')[-1],
|
| 362 |
+
'precision': mode.split('_')[-1]
|
| 363 |
+
}
|
| 364 |
+
bench_log(topdown_keypoint_detector, img_list, keypoint_model_info,
|
| 365 |
+
FLAGS.keypoint_batch_size, 'KeyPoint')
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
if __name__ == '__main__':
|
| 369 |
+
paddle.enable_static()
|
| 370 |
+
parser = argsparser()
|
| 371 |
+
FLAGS = parser.parse_args()
|
| 372 |
+
print_arguments(FLAGS)
|
| 373 |
+
FLAGS.device = FLAGS.device.upper()
|
| 374 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 375 |
+
], "device should be CPU, GPU or XPU"
|
| 376 |
+
|
| 377 |
+
main()
|
python/det_keypoint_unite_utils.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 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 |
+
|
| 15 |
+
import ast
|
| 16 |
+
import argparse
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def argsparser():
|
| 20 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 21 |
+
parser.add_argument(
|
| 22 |
+
"--det_model_dir",
|
| 23 |
+
type=str,
|
| 24 |
+
default=None,
|
| 25 |
+
help=("Directory include:'model.pdiparams', 'model.pdmodel', "
|
| 26 |
+
"'infer_cfg.yml', created by tools/export_model.py."),
|
| 27 |
+
required=True)
|
| 28 |
+
parser.add_argument(
|
| 29 |
+
"--keypoint_model_dir",
|
| 30 |
+
type=str,
|
| 31 |
+
default=None,
|
| 32 |
+
help=("Directory include:'model.pdiparams', 'model.pdmodel', "
|
| 33 |
+
"'infer_cfg.yml', created by tools/export_model.py."),
|
| 34 |
+
required=True)
|
| 35 |
+
parser.add_argument(
|
| 36 |
+
"--image_file", type=str, default=None, help="Path of image file.")
|
| 37 |
+
parser.add_argument(
|
| 38 |
+
"--image_dir",
|
| 39 |
+
type=str,
|
| 40 |
+
default=None,
|
| 41 |
+
help="Dir of image file, `image_file` has a higher priority.")
|
| 42 |
+
parser.add_argument(
|
| 43 |
+
"--keypoint_batch_size",
|
| 44 |
+
type=int,
|
| 45 |
+
default=8,
|
| 46 |
+
help=("batch_size for keypoint inference. In detection-keypoint unit"
|
| 47 |
+
"inference, the batch size in detection is 1. Then collate det "
|
| 48 |
+
"result in batch for keypoint inference."))
|
| 49 |
+
parser.add_argument(
|
| 50 |
+
"--video_file",
|
| 51 |
+
type=str,
|
| 52 |
+
default=None,
|
| 53 |
+
help="Path of video file, `video_file` or `camera_id` has a highest priority."
|
| 54 |
+
)
|
| 55 |
+
parser.add_argument(
|
| 56 |
+
"--camera_id",
|
| 57 |
+
type=int,
|
| 58 |
+
default=-1,
|
| 59 |
+
help="device id of camera to predict.")
|
| 60 |
+
parser.add_argument(
|
| 61 |
+
"--det_threshold", type=float, default=0.5, help="Threshold of score.")
|
| 62 |
+
parser.add_argument(
|
| 63 |
+
"--keypoint_threshold",
|
| 64 |
+
type=float,
|
| 65 |
+
default=0.5,
|
| 66 |
+
help="Threshold of score.")
|
| 67 |
+
parser.add_argument(
|
| 68 |
+
"--output_dir",
|
| 69 |
+
type=str,
|
| 70 |
+
default="output",
|
| 71 |
+
help="Directory of output visualization files.")
|
| 72 |
+
parser.add_argument(
|
| 73 |
+
"--run_mode",
|
| 74 |
+
type=str,
|
| 75 |
+
default='paddle',
|
| 76 |
+
help="mode of running(paddle/trt_fp32/trt_fp16/trt_int8)")
|
| 77 |
+
parser.add_argument(
|
| 78 |
+
"--device",
|
| 79 |
+
type=str,
|
| 80 |
+
default='cpu',
|
| 81 |
+
help="Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU."
|
| 82 |
+
)
|
| 83 |
+
parser.add_argument(
|
| 84 |
+
"--run_benchmark",
|
| 85 |
+
type=ast.literal_eval,
|
| 86 |
+
default=False,
|
| 87 |
+
help="Whether to predict a image_file repeatedly for benchmark")
|
| 88 |
+
parser.add_argument(
|
| 89 |
+
"--enable_mkldnn",
|
| 90 |
+
type=ast.literal_eval,
|
| 91 |
+
default=False,
|
| 92 |
+
help="Whether use mkldnn with CPU.")
|
| 93 |
+
parser.add_argument(
|
| 94 |
+
"--cpu_threads", type=int, default=1, help="Num of threads with CPU.")
|
| 95 |
+
parser.add_argument(
|
| 96 |
+
"--trt_min_shape", type=int, default=1, help="min_shape for TensorRT.")
|
| 97 |
+
parser.add_argument(
|
| 98 |
+
"--trt_max_shape",
|
| 99 |
+
type=int,
|
| 100 |
+
default=1280,
|
| 101 |
+
help="max_shape for TensorRT.")
|
| 102 |
+
parser.add_argument(
|
| 103 |
+
"--trt_opt_shape",
|
| 104 |
+
type=int,
|
| 105 |
+
default=640,
|
| 106 |
+
help="opt_shape for TensorRT.")
|
| 107 |
+
parser.add_argument(
|
| 108 |
+
"--trt_calib_mode",
|
| 109 |
+
type=bool,
|
| 110 |
+
default=False,
|
| 111 |
+
help="If the model is produced by TRT offline quantitative "
|
| 112 |
+
"calibration, trt_calib_mode need to set True.")
|
| 113 |
+
parser.add_argument(
|
| 114 |
+
'--use_dark',
|
| 115 |
+
type=ast.literal_eval,
|
| 116 |
+
default=True,
|
| 117 |
+
help='whether to use darkpose to get better keypoint position predict ')
|
| 118 |
+
parser.add_argument(
|
| 119 |
+
'--save_res',
|
| 120 |
+
type=bool,
|
| 121 |
+
default=False,
|
| 122 |
+
help=(
|
| 123 |
+
"whether to save predict results to json file"
|
| 124 |
+
"1) store_res: a list of image_data"
|
| 125 |
+
"2) image_data: [imageid, rects, [keypoints, scores]]"
|
| 126 |
+
"3) rects: list of rect [xmin, ymin, xmax, ymax]"
|
| 127 |
+
"4) keypoints: 17(joint numbers)*[x, y, conf], total 51 data in list"
|
| 128 |
+
"5) scores: mean of all joint conf"))
|
| 129 |
+
parser.add_argument(
|
| 130 |
+
'--smooth',
|
| 131 |
+
type=ast.literal_eval,
|
| 132 |
+
default=False,
|
| 133 |
+
help='smoothing keypoints for each frame, new incoming keypoints will be more stable.'
|
| 134 |
+
)
|
| 135 |
+
parser.add_argument(
|
| 136 |
+
'--filter_type',
|
| 137 |
+
type=str,
|
| 138 |
+
default='OneEuro',
|
| 139 |
+
help='when set --smooth True, choose filter type you want to use, it can be [OneEuro] or [EMA].'
|
| 140 |
+
)
|
| 141 |
+
return parser
|
python/infer.py
ADDED
|
@@ -0,0 +1,1035 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020 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 |
+
|
| 15 |
+
import os
|
| 16 |
+
import yaml
|
| 17 |
+
import glob
|
| 18 |
+
import json
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from functools import reduce
|
| 21 |
+
|
| 22 |
+
import cv2
|
| 23 |
+
import numpy as np
|
| 24 |
+
import math
|
| 25 |
+
import paddle
|
| 26 |
+
from paddle.inference import Config
|
| 27 |
+
from paddle.inference import create_predictor
|
| 28 |
+
|
| 29 |
+
import sys
|
| 30 |
+
# add deploy path of PadleDetection to sys.path
|
| 31 |
+
parent_path = os.path.abspath(os.path.join(__file__, *(['..'])))
|
| 32 |
+
sys.path.insert(0, parent_path)
|
| 33 |
+
|
| 34 |
+
from benchmark_utils import PaddleInferBenchmark
|
| 35 |
+
from picodet_postprocess import PicoDetPostProcess
|
| 36 |
+
from preprocess import preprocess, Resize, NormalizeImage, Permute, PadStride, LetterBoxResize, WarpAffine, Pad, decode_image
|
| 37 |
+
from keypoint_preprocess import EvalAffine, TopDownEvalAffine, expand_crop
|
| 38 |
+
from visualize import visualize_box_mask
|
| 39 |
+
from utils import argsparser, Timer, get_current_memory_mb, multiclass_nms, coco_clsid2catid
|
| 40 |
+
|
| 41 |
+
# Global dictionary
|
| 42 |
+
SUPPORT_MODELS = {
|
| 43 |
+
'YOLO', 'RCNN', 'SSD', 'Face', 'FCOS', 'SOLOv2', 'TTFNet', 'S2ANet', 'JDE',
|
| 44 |
+
'FairMOT', 'DeepSORT', 'GFL', 'PicoDet', 'CenterNet', 'TOOD', 'RetinaNet',
|
| 45 |
+
'StrongBaseline', 'STGCN', 'YOLOX', 'PPHGNet', 'PPLCNet'
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def bench_log(detector, img_list, model_info, batch_size=1, name=None):
|
| 50 |
+
mems = {
|
| 51 |
+
'cpu_rss_mb': detector.cpu_mem / len(img_list),
|
| 52 |
+
'gpu_rss_mb': detector.gpu_mem / len(img_list),
|
| 53 |
+
'gpu_util': detector.gpu_util * 100 / len(img_list)
|
| 54 |
+
}
|
| 55 |
+
perf_info = detector.det_times.report(average=True)
|
| 56 |
+
data_info = {
|
| 57 |
+
'batch_size': batch_size,
|
| 58 |
+
'shape': "dynamic_shape",
|
| 59 |
+
'data_num': perf_info['img_num']
|
| 60 |
+
}
|
| 61 |
+
log = PaddleInferBenchmark(detector.config, model_info, data_info,
|
| 62 |
+
perf_info, mems)
|
| 63 |
+
log(name)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class Detector(object):
|
| 67 |
+
"""
|
| 68 |
+
Args:
|
| 69 |
+
pred_config (object): config of model, defined by `Config(model_dir)`
|
| 70 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 71 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 72 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 73 |
+
batch_size (int): size of pre batch in inference
|
| 74 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 75 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 76 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 77 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 78 |
+
calibration, trt_calib_mode need to set True
|
| 79 |
+
cpu_threads (int): cpu threads
|
| 80 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 81 |
+
enable_mkldnn_bfloat16 (bool): whether to turn on mkldnn bfloat16
|
| 82 |
+
output_dir (str): The path of output
|
| 83 |
+
threshold (float): The threshold of score for visualization
|
| 84 |
+
delete_shuffle_pass (bool): whether to remove shuffle_channel_detect_pass in TensorRT.
|
| 85 |
+
Used by action model.
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
def __init__(self,
|
| 89 |
+
model_dir,
|
| 90 |
+
device='CPU',
|
| 91 |
+
run_mode='paddle',
|
| 92 |
+
batch_size=1,
|
| 93 |
+
trt_min_shape=1,
|
| 94 |
+
trt_max_shape=1280,
|
| 95 |
+
trt_opt_shape=640,
|
| 96 |
+
trt_calib_mode=False,
|
| 97 |
+
cpu_threads=1,
|
| 98 |
+
enable_mkldnn=False,
|
| 99 |
+
enable_mkldnn_bfloat16=False,
|
| 100 |
+
output_dir='output',
|
| 101 |
+
threshold=0.5,
|
| 102 |
+
delete_shuffle_pass=False):
|
| 103 |
+
self.pred_config = self.set_config(model_dir)
|
| 104 |
+
self.predictor, self.config = load_predictor(
|
| 105 |
+
model_dir,
|
| 106 |
+
run_mode=run_mode,
|
| 107 |
+
batch_size=batch_size,
|
| 108 |
+
min_subgraph_size=self.pred_config.min_subgraph_size,
|
| 109 |
+
device=device,
|
| 110 |
+
use_dynamic_shape=self.pred_config.use_dynamic_shape,
|
| 111 |
+
trt_min_shape=trt_min_shape,
|
| 112 |
+
trt_max_shape=trt_max_shape,
|
| 113 |
+
trt_opt_shape=trt_opt_shape,
|
| 114 |
+
trt_calib_mode=trt_calib_mode,
|
| 115 |
+
cpu_threads=cpu_threads,
|
| 116 |
+
enable_mkldnn=enable_mkldnn,
|
| 117 |
+
enable_mkldnn_bfloat16=enable_mkldnn_bfloat16,
|
| 118 |
+
delete_shuffle_pass=delete_shuffle_pass)
|
| 119 |
+
self.det_times = Timer()
|
| 120 |
+
self.cpu_mem, self.gpu_mem, self.gpu_util = 0, 0, 0
|
| 121 |
+
self.batch_size = batch_size
|
| 122 |
+
self.output_dir = output_dir
|
| 123 |
+
self.threshold = threshold
|
| 124 |
+
|
| 125 |
+
def set_config(self, model_dir):
|
| 126 |
+
return PredictConfig(model_dir)
|
| 127 |
+
|
| 128 |
+
def preprocess(self, image_list):
|
| 129 |
+
preprocess_ops = []
|
| 130 |
+
for op_info in self.pred_config.preprocess_infos:
|
| 131 |
+
new_op_info = op_info.copy()
|
| 132 |
+
op_type = new_op_info.pop('type')
|
| 133 |
+
preprocess_ops.append(eval(op_type)(**new_op_info))
|
| 134 |
+
|
| 135 |
+
input_im_lst = []
|
| 136 |
+
input_im_info_lst = []
|
| 137 |
+
for im_path in image_list:
|
| 138 |
+
im, im_info = preprocess(im_path, preprocess_ops)
|
| 139 |
+
input_im_lst.append(im)
|
| 140 |
+
input_im_info_lst.append(im_info)
|
| 141 |
+
inputs = create_inputs(input_im_lst, input_im_info_lst)
|
| 142 |
+
input_names = self.predictor.get_input_names()
|
| 143 |
+
for i in range(len(input_names)):
|
| 144 |
+
input_tensor = self.predictor.get_input_handle(input_names[i])
|
| 145 |
+
if input_names[i] == 'x':
|
| 146 |
+
input_tensor.copy_from_cpu(inputs['image'])
|
| 147 |
+
else:
|
| 148 |
+
input_tensor.copy_from_cpu(inputs[input_names[i]])
|
| 149 |
+
|
| 150 |
+
return inputs
|
| 151 |
+
|
| 152 |
+
def postprocess(self, inputs, result):
|
| 153 |
+
# postprocess output of predictor
|
| 154 |
+
np_boxes_num = result['boxes_num']
|
| 155 |
+
assert isinstance(np_boxes_num, np.ndarray), \
|
| 156 |
+
'`np_boxes_num` should be a `numpy.ndarray`'
|
| 157 |
+
|
| 158 |
+
if np_boxes_num.sum() <= 0:
|
| 159 |
+
print('[WARNNING] No object detected.')
|
| 160 |
+
result = {'boxes': np.zeros([0, 6]), 'boxes_num': np_boxes_num}
|
| 161 |
+
result = {k: v for k, v in result.items() if v is not None}
|
| 162 |
+
return result
|
| 163 |
+
|
| 164 |
+
def filter_box(self, result, threshold):
|
| 165 |
+
np_boxes_num = result['boxes_num']
|
| 166 |
+
boxes = result['boxes']
|
| 167 |
+
start_idx = 0
|
| 168 |
+
filter_boxes = []
|
| 169 |
+
filter_num = []
|
| 170 |
+
for i in range(len(np_boxes_num)):
|
| 171 |
+
boxes_num = np_boxes_num[i]
|
| 172 |
+
boxes_i = boxes[start_idx:start_idx + boxes_num, :]
|
| 173 |
+
idx = boxes_i[:, 1] > threshold
|
| 174 |
+
filter_boxes_i = boxes_i[idx, :]
|
| 175 |
+
filter_boxes.append(filter_boxes_i)
|
| 176 |
+
filter_num.append(filter_boxes_i.shape[0])
|
| 177 |
+
start_idx += boxes_num
|
| 178 |
+
boxes = np.concatenate(filter_boxes)
|
| 179 |
+
filter_num = np.array(filter_num)
|
| 180 |
+
filter_res = {'boxes': boxes, 'boxes_num': filter_num}
|
| 181 |
+
return filter_res
|
| 182 |
+
|
| 183 |
+
def predict(self, repeats=1):
|
| 184 |
+
'''
|
| 185 |
+
Args:
|
| 186 |
+
repeats (int): repeats number for prediction
|
| 187 |
+
Returns:
|
| 188 |
+
result (dict): include 'boxes': np.ndarray: shape:[N,6], N: number of box,
|
| 189 |
+
matix element:[class, score, x_min, y_min, x_max, y_max]
|
| 190 |
+
MaskRCNN's result include 'masks': np.ndarray:
|
| 191 |
+
shape: [N, im_h, im_w]
|
| 192 |
+
'''
|
| 193 |
+
# model prediction
|
| 194 |
+
np_boxes_num, np_boxes, np_masks = np.array([0]), None, None
|
| 195 |
+
for i in range(repeats):
|
| 196 |
+
self.predictor.run()
|
| 197 |
+
output_names = self.predictor.get_output_names()
|
| 198 |
+
boxes_tensor = self.predictor.get_output_handle(output_names[0])
|
| 199 |
+
np_boxes = boxes_tensor.copy_to_cpu()
|
| 200 |
+
boxes_num = self.predictor.get_output_handle(output_names[1])
|
| 201 |
+
np_boxes_num = boxes_num.copy_to_cpu()
|
| 202 |
+
if self.pred_config.mask:
|
| 203 |
+
masks_tensor = self.predictor.get_output_handle(output_names[
|
| 204 |
+
2])
|
| 205 |
+
np_masks = masks_tensor.copy_to_cpu()
|
| 206 |
+
result = dict(boxes=np_boxes, masks=np_masks, boxes_num=np_boxes_num)
|
| 207 |
+
return result
|
| 208 |
+
|
| 209 |
+
def merge_batch_result(self, batch_result):
|
| 210 |
+
if len(batch_result) == 1:
|
| 211 |
+
return batch_result[0]
|
| 212 |
+
res_key = batch_result[0].keys()
|
| 213 |
+
results = {k: [] for k in res_key}
|
| 214 |
+
for res in batch_result:
|
| 215 |
+
for k, v in res.items():
|
| 216 |
+
results[k].append(v)
|
| 217 |
+
for k, v in results.items():
|
| 218 |
+
if k not in ['masks', 'segm']:
|
| 219 |
+
results[k] = np.concatenate(v)
|
| 220 |
+
return results
|
| 221 |
+
|
| 222 |
+
def get_timer(self):
|
| 223 |
+
return self.det_times
|
| 224 |
+
|
| 225 |
+
def predict_image_slice(self,
|
| 226 |
+
img_list,
|
| 227 |
+
slice_size=[640, 640],
|
| 228 |
+
overlap_ratio=[0.25, 0.25],
|
| 229 |
+
combine_method='nms',
|
| 230 |
+
match_threshold=0.6,
|
| 231 |
+
match_metric='ios',
|
| 232 |
+
run_benchmark=False,
|
| 233 |
+
repeats=1,
|
| 234 |
+
visual=True,
|
| 235 |
+
save_results=False):
|
| 236 |
+
# slice infer only support bs=1
|
| 237 |
+
results = []
|
| 238 |
+
try:
|
| 239 |
+
import sahi
|
| 240 |
+
from sahi.slicing import slice_image
|
| 241 |
+
except Exception as e:
|
| 242 |
+
print(
|
| 243 |
+
'sahi not found, plaese install sahi. '
|
| 244 |
+
'for example: `pip install sahi`, see https://github.com/obss/sahi.'
|
| 245 |
+
)
|
| 246 |
+
raise e
|
| 247 |
+
num_classes = len(self.pred_config.labels)
|
| 248 |
+
for i in range(len(img_list)):
|
| 249 |
+
ori_image = img_list[i]
|
| 250 |
+
slice_image_result = sahi.slicing.slice_image(
|
| 251 |
+
image=ori_image,
|
| 252 |
+
slice_height=slice_size[0],
|
| 253 |
+
slice_width=slice_size[1],
|
| 254 |
+
overlap_height_ratio=overlap_ratio[0],
|
| 255 |
+
overlap_width_ratio=overlap_ratio[1])
|
| 256 |
+
sub_img_num = len(slice_image_result)
|
| 257 |
+
merged_bboxs = []
|
| 258 |
+
print('sub_img_num', sub_img_num)
|
| 259 |
+
|
| 260 |
+
batch_image_list = [
|
| 261 |
+
slice_image_result.images[_ind] for _ind in range(sub_img_num)
|
| 262 |
+
]
|
| 263 |
+
if run_benchmark:
|
| 264 |
+
# preprocess
|
| 265 |
+
inputs = self.preprocess(batch_image_list) # warmup
|
| 266 |
+
self.det_times.preprocess_time_s.start()
|
| 267 |
+
inputs = self.preprocess(batch_image_list)
|
| 268 |
+
self.det_times.preprocess_time_s.end()
|
| 269 |
+
|
| 270 |
+
# model prediction
|
| 271 |
+
result = self.predict(repeats=50) # warmup
|
| 272 |
+
self.det_times.inference_time_s.start()
|
| 273 |
+
result = self.predict(repeats=repeats)
|
| 274 |
+
self.det_times.inference_time_s.end(repeats=repeats)
|
| 275 |
+
|
| 276 |
+
# postprocess
|
| 277 |
+
result_warmup = self.postprocess(inputs, result) # warmup
|
| 278 |
+
self.det_times.postprocess_time_s.start()
|
| 279 |
+
result = self.postprocess(inputs, result)
|
| 280 |
+
self.det_times.postprocess_time_s.end()
|
| 281 |
+
self.det_times.img_num += 1
|
| 282 |
+
|
| 283 |
+
cm, gm, gu = get_current_memory_mb()
|
| 284 |
+
self.cpu_mem += cm
|
| 285 |
+
self.gpu_mem += gm
|
| 286 |
+
self.gpu_util += gu
|
| 287 |
+
else:
|
| 288 |
+
# preprocess
|
| 289 |
+
self.det_times.preprocess_time_s.start()
|
| 290 |
+
inputs = self.preprocess(batch_image_list)
|
| 291 |
+
self.det_times.preprocess_time_s.end()
|
| 292 |
+
|
| 293 |
+
# model prediction
|
| 294 |
+
self.det_times.inference_time_s.start()
|
| 295 |
+
result = self.predict()
|
| 296 |
+
self.det_times.inference_time_s.end()
|
| 297 |
+
|
| 298 |
+
# postprocess
|
| 299 |
+
self.det_times.postprocess_time_s.start()
|
| 300 |
+
result = self.postprocess(inputs, result)
|
| 301 |
+
self.det_times.postprocess_time_s.end()
|
| 302 |
+
self.det_times.img_num += 1
|
| 303 |
+
|
| 304 |
+
st, ed = 0, result['boxes_num'][0] # start_index, end_index
|
| 305 |
+
for _ind in range(sub_img_num):
|
| 306 |
+
boxes_num = result['boxes_num'][_ind]
|
| 307 |
+
ed = boxes_num
|
| 308 |
+
shift_amount = slice_image_result.starting_pixels[_ind]
|
| 309 |
+
result['boxes'][st:ed][:, 2:4] = result['boxes'][
|
| 310 |
+
st:ed][:, 2:4] + shift_amount
|
| 311 |
+
result['boxes'][st:ed][:, 4:6] = result['boxes'][
|
| 312 |
+
st:ed][:, 4:6] + shift_amount
|
| 313 |
+
merged_bboxs.append(result['boxes'][st:ed])
|
| 314 |
+
st = ed
|
| 315 |
+
|
| 316 |
+
merged_results = {'boxes': []}
|
| 317 |
+
if combine_method == 'nms':
|
| 318 |
+
final_boxes = multiclass_nms(
|
| 319 |
+
np.concatenate(merged_bboxs), num_classes, match_threshold,
|
| 320 |
+
match_metric)
|
| 321 |
+
merged_results['boxes'] = np.concatenate(final_boxes)
|
| 322 |
+
elif combine_method == 'concat':
|
| 323 |
+
merged_results['boxes'] = np.concatenate(merged_bboxs)
|
| 324 |
+
else:
|
| 325 |
+
raise ValueError(
|
| 326 |
+
"Now only support 'nms' or 'concat' to fuse detection results."
|
| 327 |
+
)
|
| 328 |
+
merged_results['boxes_num'] = np.array(
|
| 329 |
+
[len(merged_results['boxes'])], dtype=np.int32)
|
| 330 |
+
|
| 331 |
+
if visual:
|
| 332 |
+
visualize(
|
| 333 |
+
[ori_image], # should be list
|
| 334 |
+
merged_results,
|
| 335 |
+
self.pred_config.labels,
|
| 336 |
+
output_dir=self.output_dir,
|
| 337 |
+
threshold=self.threshold)
|
| 338 |
+
|
| 339 |
+
results.append(merged_results)
|
| 340 |
+
print('Test iter {}'.format(i))
|
| 341 |
+
|
| 342 |
+
results = self.merge_batch_result(results)
|
| 343 |
+
if save_results:
|
| 344 |
+
Path(self.output_dir).mkdir(exist_ok=True)
|
| 345 |
+
self.save_coco_results(
|
| 346 |
+
img_list, results, use_coco_category=FLAGS.use_coco_category)
|
| 347 |
+
return results
|
| 348 |
+
|
| 349 |
+
def predict_image(self,
|
| 350 |
+
image_list,
|
| 351 |
+
run_benchmark=False,
|
| 352 |
+
repeats=1,
|
| 353 |
+
visual=True,
|
| 354 |
+
save_results=False):
|
| 355 |
+
batch_loop_cnt = math.ceil(float(len(image_list)) / self.batch_size)
|
| 356 |
+
results = []
|
| 357 |
+
for i in range(batch_loop_cnt):
|
| 358 |
+
start_index = i * self.batch_size
|
| 359 |
+
end_index = min((i + 1) * self.batch_size, len(image_list))
|
| 360 |
+
batch_image_list = image_list[start_index:end_index]
|
| 361 |
+
if run_benchmark:
|
| 362 |
+
# preprocess
|
| 363 |
+
inputs = self.preprocess(batch_image_list) # warmup
|
| 364 |
+
self.det_times.preprocess_time_s.start()
|
| 365 |
+
inputs = self.preprocess(batch_image_list)
|
| 366 |
+
self.det_times.preprocess_time_s.end()
|
| 367 |
+
|
| 368 |
+
# model prediction
|
| 369 |
+
result = self.predict(repeats=50) # warmup
|
| 370 |
+
self.det_times.inference_time_s.start()
|
| 371 |
+
result = self.predict(repeats=repeats)
|
| 372 |
+
self.det_times.inference_time_s.end(repeats=repeats)
|
| 373 |
+
|
| 374 |
+
# postprocess
|
| 375 |
+
result_warmup = self.postprocess(inputs, result) # warmup
|
| 376 |
+
self.det_times.postprocess_time_s.start()
|
| 377 |
+
result = self.postprocess(inputs, result)
|
| 378 |
+
self.det_times.postprocess_time_s.end()
|
| 379 |
+
self.det_times.img_num += len(batch_image_list)
|
| 380 |
+
|
| 381 |
+
cm, gm, gu = get_current_memory_mb()
|
| 382 |
+
self.cpu_mem += cm
|
| 383 |
+
self.gpu_mem += gm
|
| 384 |
+
self.gpu_util += gu
|
| 385 |
+
else:
|
| 386 |
+
# preprocess
|
| 387 |
+
self.det_times.preprocess_time_s.start()
|
| 388 |
+
inputs = self.preprocess(batch_image_list)
|
| 389 |
+
self.det_times.preprocess_time_s.end()
|
| 390 |
+
|
| 391 |
+
# model prediction
|
| 392 |
+
self.det_times.inference_time_s.start()
|
| 393 |
+
result = self.predict()
|
| 394 |
+
self.det_times.inference_time_s.end()
|
| 395 |
+
|
| 396 |
+
# postprocess
|
| 397 |
+
self.det_times.postprocess_time_s.start()
|
| 398 |
+
result = self.postprocess(inputs, result)
|
| 399 |
+
self.det_times.postprocess_time_s.end()
|
| 400 |
+
self.det_times.img_num += len(batch_image_list)
|
| 401 |
+
|
| 402 |
+
if visual:
|
| 403 |
+
visualize(
|
| 404 |
+
batch_image_list,
|
| 405 |
+
result,
|
| 406 |
+
self.pred_config.labels,
|
| 407 |
+
output_dir=self.output_dir,
|
| 408 |
+
threshold=self.threshold)
|
| 409 |
+
results.append(result)
|
| 410 |
+
print('Test iter {}'.format(i))
|
| 411 |
+
results = self.merge_batch_result(results)
|
| 412 |
+
if save_results:
|
| 413 |
+
Path(self.output_dir).mkdir(exist_ok=True)
|
| 414 |
+
self.save_coco_results(
|
| 415 |
+
image_list, results, use_coco_category=FLAGS.use_coco_category)
|
| 416 |
+
return results
|
| 417 |
+
|
| 418 |
+
def predict_video(self, video_file, camera_id):
|
| 419 |
+
video_out_name = 'output.mp4'
|
| 420 |
+
if camera_id != -1:
|
| 421 |
+
capture = cv2.VideoCapture(camera_id)
|
| 422 |
+
else:
|
| 423 |
+
capture = cv2.VideoCapture(video_file)
|
| 424 |
+
video_out_name = os.path.split(video_file)[-1]
|
| 425 |
+
# Get Video info : resolution, fps, frame count
|
| 426 |
+
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 427 |
+
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 428 |
+
fps = int(capture.get(cv2.CAP_PROP_FPS))
|
| 429 |
+
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 430 |
+
print("fps: %d, frame_count: %d" % (fps, frame_count))
|
| 431 |
+
|
| 432 |
+
if not os.path.exists(self.output_dir):
|
| 433 |
+
os.makedirs(self.output_dir)
|
| 434 |
+
out_path = os.path.join(self.output_dir, video_out_name)
|
| 435 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 436 |
+
writer = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
|
| 437 |
+
index = 1
|
| 438 |
+
while (1):
|
| 439 |
+
ret, frame = capture.read()
|
| 440 |
+
if not ret:
|
| 441 |
+
break
|
| 442 |
+
print('detect frame: %d' % (index))
|
| 443 |
+
index += 1
|
| 444 |
+
results = self.predict_image([frame[:, :, ::-1]], visual=False)
|
| 445 |
+
|
| 446 |
+
im = visualize_box_mask(
|
| 447 |
+
frame,
|
| 448 |
+
results,
|
| 449 |
+
self.pred_config.labels,
|
| 450 |
+
threshold=self.threshold)
|
| 451 |
+
im = np.array(im)
|
| 452 |
+
writer.write(im)
|
| 453 |
+
if camera_id != -1:
|
| 454 |
+
cv2.imshow('Mask Detection', im)
|
| 455 |
+
if cv2.waitKey(1) & 0xFF == ord('q'):
|
| 456 |
+
break
|
| 457 |
+
writer.release()
|
| 458 |
+
|
| 459 |
+
def save_coco_results(self, image_list, results, use_coco_category=False):
|
| 460 |
+
bbox_results = []
|
| 461 |
+
mask_results = []
|
| 462 |
+
idx = 0
|
| 463 |
+
print("Start saving coco json files...")
|
| 464 |
+
for i, box_num in enumerate(results['boxes_num']):
|
| 465 |
+
file_name = os.path.split(image_list[i])[-1]
|
| 466 |
+
if use_coco_category:
|
| 467 |
+
img_id = int(os.path.splitext(file_name)[0])
|
| 468 |
+
else:
|
| 469 |
+
img_id = i
|
| 470 |
+
|
| 471 |
+
if 'boxes' in results:
|
| 472 |
+
boxes = results['boxes'][idx:idx + box_num].tolist()
|
| 473 |
+
bbox_results.extend([{
|
| 474 |
+
'image_id': img_id,
|
| 475 |
+
'category_id': coco_clsid2catid[int(box[0])] \
|
| 476 |
+
if use_coco_category else int(box[0]),
|
| 477 |
+
'file_name': file_name,
|
| 478 |
+
'bbox': [box[2], box[3], box[4] - box[2],
|
| 479 |
+
box[5] - box[3]], # xyxy -> xywh
|
| 480 |
+
'score': box[1]} for box in boxes])
|
| 481 |
+
|
| 482 |
+
if 'masks' in results:
|
| 483 |
+
import pycocotools.mask as mask_util
|
| 484 |
+
|
| 485 |
+
boxes = results['boxes'][idx:idx + box_num].tolist()
|
| 486 |
+
masks = results['masks'][i][:box_num].astype(np.uint8)
|
| 487 |
+
seg_res = []
|
| 488 |
+
for box, mask in zip(boxes, masks):
|
| 489 |
+
rle = mask_util.encode(
|
| 490 |
+
np.array(
|
| 491 |
+
mask[:, :, None], dtype=np.uint8, order="F"))[0]
|
| 492 |
+
if 'counts' in rle:
|
| 493 |
+
rle['counts'] = rle['counts'].decode("utf8")
|
| 494 |
+
seg_res.append({
|
| 495 |
+
'image_id': img_id,
|
| 496 |
+
'category_id': coco_clsid2catid[int(box[0])] \
|
| 497 |
+
if use_coco_category else int(box[0]),
|
| 498 |
+
'file_name': file_name,
|
| 499 |
+
'segmentation': rle,
|
| 500 |
+
'score': box[1]})
|
| 501 |
+
mask_results.extend(seg_res)
|
| 502 |
+
|
| 503 |
+
idx += box_num
|
| 504 |
+
|
| 505 |
+
if bbox_results:
|
| 506 |
+
bbox_file = os.path.join(self.output_dir, "bbox.json")
|
| 507 |
+
with open(bbox_file, 'w') as f:
|
| 508 |
+
json.dump(bbox_results, f)
|
| 509 |
+
print(f"The bbox result is saved to {bbox_file}")
|
| 510 |
+
if mask_results:
|
| 511 |
+
mask_file = os.path.join(self.output_dir, "mask.json")
|
| 512 |
+
with open(mask_file, 'w') as f:
|
| 513 |
+
json.dump(mask_results, f)
|
| 514 |
+
print(f"The mask result is saved to {mask_file}")
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
class DetectorSOLOv2(Detector):
|
| 518 |
+
"""
|
| 519 |
+
Args:
|
| 520 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 521 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 522 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 523 |
+
batch_size (int): size of pre batch in inference
|
| 524 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 525 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 526 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 527 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 528 |
+
calibration, trt_calib_mode need to set True
|
| 529 |
+
cpu_threads (int): cpu threads
|
| 530 |
+
enable_mkldnn (bool): whether to open MKLDNN
|
| 531 |
+
enable_mkldnn_bfloat16 (bool): Whether to turn on mkldnn bfloat16
|
| 532 |
+
output_dir (str): The path of output
|
| 533 |
+
threshold (float): The threshold of score for visualization
|
| 534 |
+
|
| 535 |
+
"""
|
| 536 |
+
|
| 537 |
+
def __init__(
|
| 538 |
+
self,
|
| 539 |
+
model_dir,
|
| 540 |
+
device='CPU',
|
| 541 |
+
run_mode='paddle',
|
| 542 |
+
batch_size=1,
|
| 543 |
+
trt_min_shape=1,
|
| 544 |
+
trt_max_shape=1280,
|
| 545 |
+
trt_opt_shape=640,
|
| 546 |
+
trt_calib_mode=False,
|
| 547 |
+
cpu_threads=1,
|
| 548 |
+
enable_mkldnn=False,
|
| 549 |
+
enable_mkldnn_bfloat16=False,
|
| 550 |
+
output_dir='./',
|
| 551 |
+
threshold=0.5, ):
|
| 552 |
+
super(DetectorSOLOv2, self).__init__(
|
| 553 |
+
model_dir=model_dir,
|
| 554 |
+
device=device,
|
| 555 |
+
run_mode=run_mode,
|
| 556 |
+
batch_size=batch_size,
|
| 557 |
+
trt_min_shape=trt_min_shape,
|
| 558 |
+
trt_max_shape=trt_max_shape,
|
| 559 |
+
trt_opt_shape=trt_opt_shape,
|
| 560 |
+
trt_calib_mode=trt_calib_mode,
|
| 561 |
+
cpu_threads=cpu_threads,
|
| 562 |
+
enable_mkldnn=enable_mkldnn,
|
| 563 |
+
enable_mkldnn_bfloat16=enable_mkldnn_bfloat16,
|
| 564 |
+
output_dir=output_dir,
|
| 565 |
+
threshold=threshold, )
|
| 566 |
+
|
| 567 |
+
def predict(self, repeats=1):
|
| 568 |
+
'''
|
| 569 |
+
Args:
|
| 570 |
+
repeats (int): repeat number for prediction
|
| 571 |
+
Returns:
|
| 572 |
+
result (dict): 'segm': np.ndarray,shape:[N, im_h, im_w]
|
| 573 |
+
'cate_label': label of segm, shape:[N]
|
| 574 |
+
'cate_score': confidence score of segm, shape:[N]
|
| 575 |
+
'''
|
| 576 |
+
np_label, np_score, np_segms = None, None, None
|
| 577 |
+
for i in range(repeats):
|
| 578 |
+
self.predictor.run()
|
| 579 |
+
output_names = self.predictor.get_output_names()
|
| 580 |
+
np_boxes_num = self.predictor.get_output_handle(output_names[
|
| 581 |
+
0]).copy_to_cpu()
|
| 582 |
+
np_label = self.predictor.get_output_handle(output_names[
|
| 583 |
+
1]).copy_to_cpu()
|
| 584 |
+
np_score = self.predictor.get_output_handle(output_names[
|
| 585 |
+
2]).copy_to_cpu()
|
| 586 |
+
np_segms = self.predictor.get_output_handle(output_names[
|
| 587 |
+
3]).copy_to_cpu()
|
| 588 |
+
|
| 589 |
+
result = dict(
|
| 590 |
+
segm=np_segms,
|
| 591 |
+
label=np_label,
|
| 592 |
+
score=np_score,
|
| 593 |
+
boxes_num=np_boxes_num)
|
| 594 |
+
return result
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
class DetectorPicoDet(Detector):
|
| 598 |
+
"""
|
| 599 |
+
Args:
|
| 600 |
+
model_dir (str): root path of model.pdiparams, model.pdmodel and infer_cfg.yml
|
| 601 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 602 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16)
|
| 603 |
+
batch_size (int): size of pre batch in inference
|
| 604 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 605 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 606 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 607 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 608 |
+
calibration, trt_calib_mode need to set True
|
| 609 |
+
cpu_threads (int): cpu threads
|
| 610 |
+
enable_mkldnn (bool): whether to turn on MKLDNN
|
| 611 |
+
enable_mkldnn_bfloat16 (bool): whether to turn on MKLDNN_BFLOAT16
|
| 612 |
+
"""
|
| 613 |
+
|
| 614 |
+
def __init__(
|
| 615 |
+
self,
|
| 616 |
+
model_dir,
|
| 617 |
+
device='CPU',
|
| 618 |
+
run_mode='paddle',
|
| 619 |
+
batch_size=1,
|
| 620 |
+
trt_min_shape=1,
|
| 621 |
+
trt_max_shape=1280,
|
| 622 |
+
trt_opt_shape=640,
|
| 623 |
+
trt_calib_mode=False,
|
| 624 |
+
cpu_threads=1,
|
| 625 |
+
enable_mkldnn=False,
|
| 626 |
+
enable_mkldnn_bfloat16=False,
|
| 627 |
+
output_dir='./',
|
| 628 |
+
threshold=0.5, ):
|
| 629 |
+
super(DetectorPicoDet, self).__init__(
|
| 630 |
+
model_dir=model_dir,
|
| 631 |
+
device=device,
|
| 632 |
+
run_mode=run_mode,
|
| 633 |
+
batch_size=batch_size,
|
| 634 |
+
trt_min_shape=trt_min_shape,
|
| 635 |
+
trt_max_shape=trt_max_shape,
|
| 636 |
+
trt_opt_shape=trt_opt_shape,
|
| 637 |
+
trt_calib_mode=trt_calib_mode,
|
| 638 |
+
cpu_threads=cpu_threads,
|
| 639 |
+
enable_mkldnn=enable_mkldnn,
|
| 640 |
+
enable_mkldnn_bfloat16=enable_mkldnn_bfloat16,
|
| 641 |
+
output_dir=output_dir,
|
| 642 |
+
threshold=threshold, )
|
| 643 |
+
|
| 644 |
+
def postprocess(self, inputs, result):
|
| 645 |
+
# postprocess output of predictor
|
| 646 |
+
np_score_list = result['boxes']
|
| 647 |
+
np_boxes_list = result['boxes_num']
|
| 648 |
+
postprocessor = PicoDetPostProcess(
|
| 649 |
+
inputs['image'].shape[2:],
|
| 650 |
+
inputs['im_shape'],
|
| 651 |
+
inputs['scale_factor'],
|
| 652 |
+
strides=self.pred_config.fpn_stride,
|
| 653 |
+
nms_threshold=self.pred_config.nms['nms_threshold'])
|
| 654 |
+
np_boxes, np_boxes_num = postprocessor(np_score_list, np_boxes_list)
|
| 655 |
+
result = dict(boxes=np_boxes, boxes_num=np_boxes_num)
|
| 656 |
+
return result
|
| 657 |
+
|
| 658 |
+
def predict(self, repeats=1):
|
| 659 |
+
'''
|
| 660 |
+
Args:
|
| 661 |
+
repeats (int): repeat number for prediction
|
| 662 |
+
Returns:
|
| 663 |
+
result (dict): include 'boxes': np.ndarray: shape:[N,6], N: number of box,
|
| 664 |
+
matix element:[class, score, x_min, y_min, x_max, y_max]
|
| 665 |
+
'''
|
| 666 |
+
np_score_list, np_boxes_list = [], []
|
| 667 |
+
for i in range(repeats):
|
| 668 |
+
self.predictor.run()
|
| 669 |
+
np_score_list.clear()
|
| 670 |
+
np_boxes_list.clear()
|
| 671 |
+
output_names = self.predictor.get_output_names()
|
| 672 |
+
num_outs = int(len(output_names) / 2)
|
| 673 |
+
for out_idx in range(num_outs):
|
| 674 |
+
np_score_list.append(
|
| 675 |
+
self.predictor.get_output_handle(output_names[out_idx])
|
| 676 |
+
.copy_to_cpu())
|
| 677 |
+
np_boxes_list.append(
|
| 678 |
+
self.predictor.get_output_handle(output_names[
|
| 679 |
+
out_idx + num_outs]).copy_to_cpu())
|
| 680 |
+
result = dict(boxes=np_score_list, boxes_num=np_boxes_list)
|
| 681 |
+
return result
|
| 682 |
+
|
| 683 |
+
|
| 684 |
+
def create_inputs(imgs, im_info):
|
| 685 |
+
"""generate input for different model type
|
| 686 |
+
Args:
|
| 687 |
+
imgs (list(numpy)): list of images (np.ndarray)
|
| 688 |
+
im_info (list(dict)): list of image info
|
| 689 |
+
Returns:
|
| 690 |
+
inputs (dict): input of model
|
| 691 |
+
"""
|
| 692 |
+
inputs = {}
|
| 693 |
+
|
| 694 |
+
im_shape = []
|
| 695 |
+
scale_factor = []
|
| 696 |
+
if len(imgs) == 1:
|
| 697 |
+
inputs['image'] = np.array((imgs[0], )).astype('float32')
|
| 698 |
+
inputs['im_shape'] = np.array(
|
| 699 |
+
(im_info[0]['im_shape'], )).astype('float32')
|
| 700 |
+
inputs['scale_factor'] = np.array(
|
| 701 |
+
(im_info[0]['scale_factor'], )).astype('float32')
|
| 702 |
+
return inputs
|
| 703 |
+
|
| 704 |
+
for e in im_info:
|
| 705 |
+
im_shape.append(np.array((e['im_shape'], )).astype('float32'))
|
| 706 |
+
scale_factor.append(np.array((e['scale_factor'], )).astype('float32'))
|
| 707 |
+
|
| 708 |
+
inputs['im_shape'] = np.concatenate(im_shape, axis=0)
|
| 709 |
+
inputs['scale_factor'] = np.concatenate(scale_factor, axis=0)
|
| 710 |
+
|
| 711 |
+
imgs_shape = [[e.shape[1], e.shape[2]] for e in imgs]
|
| 712 |
+
max_shape_h = max([e[0] for e in imgs_shape])
|
| 713 |
+
max_shape_w = max([e[1] for e in imgs_shape])
|
| 714 |
+
padding_imgs = []
|
| 715 |
+
for img in imgs:
|
| 716 |
+
im_c, im_h, im_w = img.shape[:]
|
| 717 |
+
padding_im = np.zeros(
|
| 718 |
+
(im_c, max_shape_h, max_shape_w), dtype=np.float32)
|
| 719 |
+
padding_im[:, :im_h, :im_w] = img
|
| 720 |
+
padding_imgs.append(padding_im)
|
| 721 |
+
inputs['image'] = np.stack(padding_imgs, axis=0)
|
| 722 |
+
return inputs
|
| 723 |
+
|
| 724 |
+
|
| 725 |
+
class PredictConfig():
|
| 726 |
+
"""set config of preprocess, postprocess and visualize
|
| 727 |
+
Args:
|
| 728 |
+
model_dir (str): root path of model.yml
|
| 729 |
+
"""
|
| 730 |
+
|
| 731 |
+
def __init__(self, model_dir):
|
| 732 |
+
# parsing Yaml config for Preprocess
|
| 733 |
+
deploy_file = os.path.join(model_dir, 'infer_cfg.yml')
|
| 734 |
+
with open(deploy_file) as f:
|
| 735 |
+
yml_conf = yaml.safe_load(f)
|
| 736 |
+
self.check_model(yml_conf)
|
| 737 |
+
self.arch = yml_conf['arch']
|
| 738 |
+
self.preprocess_infos = yml_conf['Preprocess']
|
| 739 |
+
self.min_subgraph_size = yml_conf['min_subgraph_size']
|
| 740 |
+
self.labels = yml_conf['label_list']
|
| 741 |
+
self.mask = False
|
| 742 |
+
self.use_dynamic_shape = yml_conf['use_dynamic_shape']
|
| 743 |
+
if 'mask' in yml_conf:
|
| 744 |
+
self.mask = yml_conf['mask']
|
| 745 |
+
self.tracker = None
|
| 746 |
+
if 'tracker' in yml_conf:
|
| 747 |
+
self.tracker = yml_conf['tracker']
|
| 748 |
+
if 'NMS' in yml_conf:
|
| 749 |
+
self.nms = yml_conf['NMS']
|
| 750 |
+
if 'fpn_stride' in yml_conf:
|
| 751 |
+
self.fpn_stride = yml_conf['fpn_stride']
|
| 752 |
+
if self.arch == 'RCNN' and yml_conf.get('export_onnx', False):
|
| 753 |
+
print(
|
| 754 |
+
'The RCNN export model is used for ONNX and it only supports batch_size = 1'
|
| 755 |
+
)
|
| 756 |
+
self.print_config()
|
| 757 |
+
|
| 758 |
+
def check_model(self, yml_conf):
|
| 759 |
+
"""
|
| 760 |
+
Raises:
|
| 761 |
+
ValueError: loaded model not in supported model type
|
| 762 |
+
"""
|
| 763 |
+
for support_model in SUPPORT_MODELS:
|
| 764 |
+
if support_model in yml_conf['arch']:
|
| 765 |
+
return True
|
| 766 |
+
raise ValueError("Unsupported arch: {}, expect {}".format(yml_conf[
|
| 767 |
+
'arch'], SUPPORT_MODELS))
|
| 768 |
+
|
| 769 |
+
def print_config(self):
|
| 770 |
+
print('----------- Model Configuration -----------')
|
| 771 |
+
print('%s: %s' % ('Model Arch', self.arch))
|
| 772 |
+
print('%s: ' % ('Transform Order'))
|
| 773 |
+
for op_info in self.preprocess_infos:
|
| 774 |
+
print('--%s: %s' % ('transform op', op_info['type']))
|
| 775 |
+
print('--------------------------------------------')
|
| 776 |
+
|
| 777 |
+
|
| 778 |
+
def load_predictor(model_dir,
|
| 779 |
+
run_mode='paddle',
|
| 780 |
+
batch_size=1,
|
| 781 |
+
device='CPU',
|
| 782 |
+
min_subgraph_size=3,
|
| 783 |
+
use_dynamic_shape=False,
|
| 784 |
+
trt_min_shape=1,
|
| 785 |
+
trt_max_shape=1280,
|
| 786 |
+
trt_opt_shape=640,
|
| 787 |
+
trt_calib_mode=False,
|
| 788 |
+
cpu_threads=1,
|
| 789 |
+
enable_mkldnn=False,
|
| 790 |
+
enable_mkldnn_bfloat16=False,
|
| 791 |
+
delete_shuffle_pass=False):
|
| 792 |
+
"""set AnalysisConfig, generate AnalysisPredictor
|
| 793 |
+
Args:
|
| 794 |
+
model_dir (str): root path of __model__ and __params__
|
| 795 |
+
device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU
|
| 796 |
+
run_mode (str): mode of running(paddle/trt_fp32/trt_fp16/trt_int8)
|
| 797 |
+
use_dynamic_shape (bool): use dynamic shape or not
|
| 798 |
+
trt_min_shape (int): min shape for dynamic shape in trt
|
| 799 |
+
trt_max_shape (int): max shape for dynamic shape in trt
|
| 800 |
+
trt_opt_shape (int): opt shape for dynamic shape in trt
|
| 801 |
+
trt_calib_mode (bool): If the model is produced by TRT offline quantitative
|
| 802 |
+
calibration, trt_calib_mode need to set True
|
| 803 |
+
delete_shuffle_pass (bool): whether to remove shuffle_channel_detect_pass in TensorRT.
|
| 804 |
+
Used by action model.
|
| 805 |
+
Returns:
|
| 806 |
+
predictor (PaddlePredictor): AnalysisPredictor
|
| 807 |
+
Raises:
|
| 808 |
+
ValueError: predict by TensorRT need device == 'GPU'.
|
| 809 |
+
"""
|
| 810 |
+
if device != 'GPU' and run_mode != 'paddle':
|
| 811 |
+
raise ValueError(
|
| 812 |
+
"Predict by TensorRT mode: {}, expect device=='GPU', but device == {}"
|
| 813 |
+
.format(run_mode, device))
|
| 814 |
+
infer_model = os.path.join(model_dir, 'model.pdmodel')
|
| 815 |
+
infer_params = os.path.join(model_dir, 'model.pdiparams')
|
| 816 |
+
if not os.path.exists(infer_model):
|
| 817 |
+
infer_model = os.path.join(model_dir, 'inference.pdmodel')
|
| 818 |
+
infer_params = os.path.join(model_dir, 'inference.pdiparams')
|
| 819 |
+
if not os.path.exists(infer_model):
|
| 820 |
+
raise ValueError("Cannot find any inference model in dir: {},".
|
| 821 |
+
format(model_dir))
|
| 822 |
+
config = Config(infer_model, infer_params)
|
| 823 |
+
if device == 'GPU':
|
| 824 |
+
# initial GPU memory(M), device ID
|
| 825 |
+
config.enable_use_gpu(200, 0)
|
| 826 |
+
# optimize graph and fuse op
|
| 827 |
+
config.switch_ir_optim(True)
|
| 828 |
+
elif device == 'XPU':
|
| 829 |
+
config.enable_lite_engine()
|
| 830 |
+
config.enable_xpu(10 * 1024 * 1024)
|
| 831 |
+
else:
|
| 832 |
+
config.disable_gpu()
|
| 833 |
+
config.set_cpu_math_library_num_threads(cpu_threads)
|
| 834 |
+
if enable_mkldnn:
|
| 835 |
+
try:
|
| 836 |
+
# cache 10 different shapes for mkldnn to avoid memory leak
|
| 837 |
+
config.set_mkldnn_cache_capacity(10)
|
| 838 |
+
config.enable_mkldnn()
|
| 839 |
+
if enable_mkldnn_bfloat16:
|
| 840 |
+
config.enable_mkldnn_bfloat16()
|
| 841 |
+
except Exception as e:
|
| 842 |
+
print(
|
| 843 |
+
"The current environment does not support `mkldnn`, so disable mkldnn."
|
| 844 |
+
)
|
| 845 |
+
pass
|
| 846 |
+
|
| 847 |
+
precision_map = {
|
| 848 |
+
'trt_int8': Config.Precision.Int8,
|
| 849 |
+
'trt_fp32': Config.Precision.Float32,
|
| 850 |
+
'trt_fp16': Config.Precision.Half
|
| 851 |
+
}
|
| 852 |
+
if run_mode in precision_map.keys():
|
| 853 |
+
config.enable_tensorrt_engine(
|
| 854 |
+
workspace_size=(1 << 25) * batch_size,
|
| 855 |
+
max_batch_size=batch_size,
|
| 856 |
+
min_subgraph_size=min_subgraph_size,
|
| 857 |
+
precision_mode=precision_map[run_mode],
|
| 858 |
+
use_static=False,
|
| 859 |
+
use_calib_mode=trt_calib_mode)
|
| 860 |
+
|
| 861 |
+
if use_dynamic_shape:
|
| 862 |
+
min_input_shape = {
|
| 863 |
+
'image': [batch_size, 3, trt_min_shape, trt_min_shape]
|
| 864 |
+
}
|
| 865 |
+
max_input_shape = {
|
| 866 |
+
'image': [batch_size, 3, trt_max_shape, trt_max_shape]
|
| 867 |
+
}
|
| 868 |
+
opt_input_shape = {
|
| 869 |
+
'image': [batch_size, 3, trt_opt_shape, trt_opt_shape]
|
| 870 |
+
}
|
| 871 |
+
config.set_trt_dynamic_shape_info(min_input_shape, max_input_shape,
|
| 872 |
+
opt_input_shape)
|
| 873 |
+
print('trt set dynamic shape done!')
|
| 874 |
+
|
| 875 |
+
# disable print log when predict
|
| 876 |
+
config.disable_glog_info()
|
| 877 |
+
# enable shared memory
|
| 878 |
+
config.enable_memory_optim()
|
| 879 |
+
# disable feed, fetch OP, needed by zero_copy_run
|
| 880 |
+
config.switch_use_feed_fetch_ops(False)
|
| 881 |
+
if delete_shuffle_pass:
|
| 882 |
+
config.delete_pass("shuffle_channel_detect_pass")
|
| 883 |
+
predictor = create_predictor(config)
|
| 884 |
+
return predictor, config
|
| 885 |
+
|
| 886 |
+
|
| 887 |
+
def get_test_images(infer_dir, infer_img):
|
| 888 |
+
"""
|
| 889 |
+
Get image path list in TEST mode
|
| 890 |
+
"""
|
| 891 |
+
assert infer_img is not None or infer_dir is not None, \
|
| 892 |
+
"--image_file or --image_dir should be set"
|
| 893 |
+
assert infer_img is None or os.path.isfile(infer_img), \
|
| 894 |
+
"{} is not a file".format(infer_img)
|
| 895 |
+
assert infer_dir is None or os.path.isdir(infer_dir), \
|
| 896 |
+
"{} is not a directory".format(infer_dir)
|
| 897 |
+
|
| 898 |
+
# infer_img has a higher priority
|
| 899 |
+
if infer_img and os.path.isfile(infer_img):
|
| 900 |
+
return [infer_img]
|
| 901 |
+
|
| 902 |
+
images = set()
|
| 903 |
+
infer_dir = os.path.abspath(infer_dir)
|
| 904 |
+
assert os.path.isdir(infer_dir), \
|
| 905 |
+
"infer_dir {} is not a directory".format(infer_dir)
|
| 906 |
+
exts = ['jpg', 'jpeg', 'png', 'bmp']
|
| 907 |
+
exts += [ext.upper() for ext in exts]
|
| 908 |
+
for ext in exts:
|
| 909 |
+
images.update(glob.glob('{}/*.{}'.format(infer_dir, ext)))
|
| 910 |
+
images = list(images)
|
| 911 |
+
|
| 912 |
+
assert len(images) > 0, "no image found in {}".format(infer_dir)
|
| 913 |
+
print("Found {} inference images in total.".format(len(images)))
|
| 914 |
+
|
| 915 |
+
return images
|
| 916 |
+
|
| 917 |
+
|
| 918 |
+
def visualize(image_list, result, labels, output_dir='output/', threshold=0.5):
|
| 919 |
+
# visualize the predict result
|
| 920 |
+
start_idx = 0
|
| 921 |
+
for idx, image_file in enumerate(image_list):
|
| 922 |
+
im_bboxes_num = result['boxes_num'][idx]
|
| 923 |
+
im_results = {}
|
| 924 |
+
if 'boxes' in result:
|
| 925 |
+
im_results['boxes'] = result['boxes'][start_idx:start_idx +
|
| 926 |
+
im_bboxes_num, :]
|
| 927 |
+
if 'masks' in result:
|
| 928 |
+
im_results['masks'] = result['masks'][start_idx:start_idx +
|
| 929 |
+
im_bboxes_num, :]
|
| 930 |
+
if 'segm' in result:
|
| 931 |
+
im_results['segm'] = result['segm'][start_idx:start_idx +
|
| 932 |
+
im_bboxes_num, :]
|
| 933 |
+
if 'label' in result:
|
| 934 |
+
im_results['label'] = result['label'][start_idx:start_idx +
|
| 935 |
+
im_bboxes_num]
|
| 936 |
+
if 'score' in result:
|
| 937 |
+
im_results['score'] = result['score'][start_idx:start_idx +
|
| 938 |
+
im_bboxes_num]
|
| 939 |
+
|
| 940 |
+
start_idx += im_bboxes_num
|
| 941 |
+
im = visualize_box_mask(
|
| 942 |
+
image_file, im_results, labels, threshold=threshold)
|
| 943 |
+
img_name = os.path.split(image_file)[-1]
|
| 944 |
+
if not os.path.exists(output_dir):
|
| 945 |
+
os.makedirs(output_dir)
|
| 946 |
+
out_path = os.path.join(output_dir, img_name)
|
| 947 |
+
im.save(out_path, quality=95)
|
| 948 |
+
print("save result to: " + out_path)
|
| 949 |
+
|
| 950 |
+
|
| 951 |
+
def print_arguments(args):
|
| 952 |
+
print('----------- Running Arguments -----------')
|
| 953 |
+
for arg, value in sorted(vars(args).items()):
|
| 954 |
+
print('%s: %s' % (arg, value))
|
| 955 |
+
print('------------------------------------------')
|
| 956 |
+
|
| 957 |
+
|
| 958 |
+
def main():
|
| 959 |
+
deploy_file = os.path.join(FLAGS.model_dir, 'infer_cfg.yml')
|
| 960 |
+
with open(deploy_file) as f:
|
| 961 |
+
yml_conf = yaml.safe_load(f)
|
| 962 |
+
arch = yml_conf['arch']
|
| 963 |
+
detector_func = 'Detector'
|
| 964 |
+
if arch == 'SOLOv2':
|
| 965 |
+
detector_func = 'DetectorSOLOv2'
|
| 966 |
+
elif arch == 'PicoDet':
|
| 967 |
+
detector_func = 'DetectorPicoDet'
|
| 968 |
+
|
| 969 |
+
detector = eval(detector_func)(
|
| 970 |
+
FLAGS.model_dir,
|
| 971 |
+
device=FLAGS.device,
|
| 972 |
+
run_mode=FLAGS.run_mode,
|
| 973 |
+
batch_size=FLAGS.batch_size,
|
| 974 |
+
trt_min_shape=FLAGS.trt_min_shape,
|
| 975 |
+
trt_max_shape=FLAGS.trt_max_shape,
|
| 976 |
+
trt_opt_shape=FLAGS.trt_opt_shape,
|
| 977 |
+
trt_calib_mode=FLAGS.trt_calib_mode,
|
| 978 |
+
cpu_threads=FLAGS.cpu_threads,
|
| 979 |
+
enable_mkldnn=FLAGS.enable_mkldnn,
|
| 980 |
+
enable_mkldnn_bfloat16=FLAGS.enable_mkldnn_bfloat16,
|
| 981 |
+
threshold=FLAGS.threshold,
|
| 982 |
+
output_dir=FLAGS.output_dir)
|
| 983 |
+
|
| 984 |
+
# predict from video file or camera video stream
|
| 985 |
+
if FLAGS.video_file is not None or FLAGS.camera_id != -1:
|
| 986 |
+
detector.predict_video(FLAGS.video_file, FLAGS.camera_id)
|
| 987 |
+
else:
|
| 988 |
+
# predict from image
|
| 989 |
+
if FLAGS.image_dir is None and FLAGS.image_file is not None:
|
| 990 |
+
assert FLAGS.batch_size == 1, "batch_size should be 1, when image_file is not None"
|
| 991 |
+
img_list = get_test_images(FLAGS.image_dir, FLAGS.image_file)
|
| 992 |
+
if FLAGS.slice_infer:
|
| 993 |
+
detector.predict_image_slice(
|
| 994 |
+
img_list,
|
| 995 |
+
FLAGS.slice_size,
|
| 996 |
+
FLAGS.overlap_ratio,
|
| 997 |
+
FLAGS.combine_method,
|
| 998 |
+
FLAGS.match_threshold,
|
| 999 |
+
FLAGS.match_metric,
|
| 1000 |
+
visual=FLAGS.save_images,
|
| 1001 |
+
save_results=FLAGS.save_results)
|
| 1002 |
+
else:
|
| 1003 |
+
detector.predict_image(
|
| 1004 |
+
img_list,
|
| 1005 |
+
FLAGS.run_benchmark,
|
| 1006 |
+
repeats=100,
|
| 1007 |
+
visual=FLAGS.save_images,
|
| 1008 |
+
save_results=FLAGS.save_results)
|
| 1009 |
+
if not FLAGS.run_benchmark:
|
| 1010 |
+
detector.det_times.info(average=True)
|
| 1011 |
+
else:
|
| 1012 |
+
mode = FLAGS.run_mode
|
| 1013 |
+
model_dir = FLAGS.model_dir
|
| 1014 |
+
model_info = {
|
| 1015 |
+
'model_name': model_dir.strip('/').split('/')[-1],
|
| 1016 |
+
'precision': mode.split('_')[-1]
|
| 1017 |
+
}
|
| 1018 |
+
bench_log(detector, img_list, model_info, name='DET')
|
| 1019 |
+
|
| 1020 |
+
|
| 1021 |
+
if __name__ == '__main__':
|
| 1022 |
+
paddle.enable_static()
|
| 1023 |
+
parser = argsparser()
|
| 1024 |
+
FLAGS = parser.parse_args()
|
| 1025 |
+
print_arguments(FLAGS)
|
| 1026 |
+
FLAGS.device = FLAGS.device.upper()
|
| 1027 |
+
assert FLAGS.device in ['CPU', 'GPU', 'XPU'
|
| 1028 |
+
], "device should be CPU, GPU or XPU"
|
| 1029 |
+
assert not FLAGS.use_gpu, "use_gpu has been deprecated, please use --device"
|
| 1030 |
+
|
| 1031 |
+
assert not (
|
| 1032 |
+
FLAGS.enable_mkldnn == False and FLAGS.enable_mkldnn_bfloat16 == True
|
| 1033 |
+
), 'To enable mkldnn bfloat, please turn on both enable_mkldnn and enable_mkldnn_bfloat16'
|
| 1034 |
+
|
| 1035 |
+
main()
|