Showing
58 changed files
with
2126 additions
and
18 deletions
code/deep_sort_yolov4/README.md
0 → 100644
1 | +# YOLOv4 + Deep_SORT | ||
2 | + | ||
3 | +<img src="https://github.com/yehengchen/Object-Detection-and-Tracking/blob/master/OneStage/yolo/deep_sort_yolov4/output/comparison.png" width="81%" height="81%"> <img src="https://github.com/yehengchen/video_demo/blob/master/video_demo/output.gif" width="40%" height="40%"> <img src="https://github.com/yehengchen/video_demo/blob/master/video_demo/TownCentreXVID_output.gif" width="40%" height="40%"> | ||
4 | + | ||
5 | +__Object Tracking & Counting Demo - [[BiliBili]](https://www.bilibili.com/video/BV1Ug4y1i71w#reply3014975828) [[Chinese Version]](https://blog.csdn.net/weixin_38107271/article/details/96741706)__ | ||
6 | +## Requirement | ||
7 | +__Development Environment: [Deep-Learning-Environment-Setup](https://github.com/yehengchen/Ubuntu-16.04-Deep-Learning-Environment-Setup)__ | ||
8 | + | ||
9 | +* OpenCV | ||
10 | +* sklean | ||
11 | +* pillow | ||
12 | +* numpy 1.15.0 | ||
13 | +* torch 1.3.0 | ||
14 | +* tensorflow-gpu 1.13.1 | ||
15 | +* CUDA 10.0 | ||
16 | +*** | ||
17 | + | ||
18 | +It uses: | ||
19 | + | ||
20 | +* __Detection__: [YOLOv4](https://github.com/yehengchen/Object-Detection-and-Tracking/tree/master/OneStage/yolo/Train-a-YOLOv4-model) to detect objects on each of the video frames. - 用自己的数据训练YOLOv4模型 | ||
21 | + | ||
22 | +* __Tracking__: [Deep_SORT](https://github.com/nwojke/deep_sort) to track those objects over different frames. | ||
23 | + | ||
24 | +*This repository contains code for Simple Online and Realtime Tracking with a Deep Association Metric (Deep SORT). We extend the original SORT algorithm to integrate appearance information based on a deep appearance descriptor. See the [arXiv preprint](https://arxiv.org/abs/1703.07402) for more information.* | ||
25 | + | ||
26 | +## Quick Start | ||
27 | + | ||
28 | +__0.Requirements__ | ||
29 | + | ||
30 | + pip install -r requirements.txt | ||
31 | + | ||
32 | +__1. Download the code to your computer.__ | ||
33 | + | ||
34 | + git clone https://github.com/yehengchen/Object-Detection-and-Tracking.git | ||
35 | + | ||
36 | +__2. Download [[yolov4.weights]](https://drive.google.com/file/d/1cewMfusmPjYWbrnuJRuKhPMwRe_b9PaT/view) [[Baidu]](https://pan.baidu.com/s/1jRudrrXAS3DRGqT6mL4L3A ) - `mnv6`__ and place it in `deep_sort_yolov4/model_data/` | ||
37 | + | ||
38 | +*Here you can download my trained [[yolo4_weight.h5]](https://pan.baidu.com/s/1JuT4KCUFaE2Gvme0_S37DQ ) - `w17w` weights for detecting person/car/bicycle,etc.* | ||
39 | + | ||
40 | +__3. Convert the Darknet YOLO model to a Keras model:__ | ||
41 | +``` | ||
42 | +$ python convert.py model_data/yolov4.cfg model_data/yolov4.weights model_data/yolo.h5 | ||
43 | +``` | ||
44 | +__4. Run the YOLO_DEEP_SORT:__ | ||
45 | + | ||
46 | +``` | ||
47 | +$ python main.py -c [CLASS NAME] -i [INPUT VIDEO PATH] | ||
48 | + | ||
49 | +$ python main.py -c person -i ./test_video/testvideo.avi | ||
50 | +``` | ||
51 | + | ||
52 | +__5. Can change [deep_sort_yolov3/yolo.py] `__Line 100__` to your tracking object__ | ||
53 | + | ||
54 | +*DeepSORT pre-trained weights using people-ReID datasets only for person* | ||
55 | +``` | ||
56 | + if predicted_class != args["class"]: | ||
57 | + continue | ||
58 | + | ||
59 | + if predicted_class != 'person' and predicted_class != 'car': | ||
60 | + continue | ||
61 | +``` | ||
62 | + | ||
63 | +## Train on Market1501 & MARS | ||
64 | +*People Re-identification model* | ||
65 | + | ||
66 | +[cosine_metric_learning](https://github.com/nwojke/cosine_metric_learning) for training a metric feature representation to be used with the deep_sort tracker. | ||
67 | + | ||
68 | +## Citation | ||
69 | + | ||
70 | +### YOLOv4 : | ||
71 | + | ||
72 | + @misc{bochkovskiy2020yolov4, | ||
73 | + title={YOLOv4: Optimal Speed and Accuracy of Object Detection}, | ||
74 | + author={Alexey Bochkovskiy and Chien-Yao Wang and Hong-Yuan Mark Liao}, | ||
75 | + year={2020}, | ||
76 | + eprint={2004.10934}, | ||
77 | + archivePrefix={arXiv}, | ||
78 | + primaryClass={cs.CV} | ||
79 | + } | ||
80 | + | ||
81 | +### Deep_SORT : | ||
82 | + | ||
83 | + @inproceedings{Wojke2017simple, | ||
84 | + title={Simple Online and Realtime Tracking with a Deep Association Metric}, | ||
85 | + author={Wojke, Nicolai and Bewley, Alex and Paulus, Dietrich}, | ||
86 | + booktitle={2017 IEEE International Conference on Image Processing (ICIP)}, | ||
87 | + year={2017}, | ||
88 | + pages={3645--3649}, | ||
89 | + organization={IEEE}, | ||
90 | + doi={10.1109/ICIP.2017.8296962} | ||
91 | + } | ||
92 | + | ||
93 | + @inproceedings{Wojke2018deep, | ||
94 | + title={Deep Cosine Metric Learning for Person Re-identification}, | ||
95 | + author={Wojke, Nicolai and Bewley, Alex}, | ||
96 | + booktitle={2018 IEEE Winter Conference on Applications of Computer Vision (WACV)}, | ||
97 | + year={2018}, | ||
98 | + pages={748--756}, | ||
99 | + organization={IEEE}, | ||
100 | + doi={10.1109/WACV.2018.00087} | ||
101 | + } | ||
102 | + | ||
103 | +## Reference | ||
104 | +#### Github:deep_sort@[Nicolai Wojke nwojke](https://github.com/nwojke/deep_sort) | ||
105 | +#### Github:deep_sort_yolov3@[Qidian213 ](https://github.com/Qidian213/deep_sort_yolov3) | ||
106 | +#### Github:Deep-SORT-YOLOv4@[LeonLok](https://github.com/LeonLok/Deep-SORT-YOLOv4) | ||
107 | + |
No preview for this file type
code/deep_sort_yolov4/convert.py
0 → 100644
1 | +import os | ||
2 | +import colorsys | ||
3 | + | ||
4 | +import numpy as np | ||
5 | +from keras import backend as K | ||
6 | +from keras.models import load_model | ||
7 | +from keras.layers import Input | ||
8 | + | ||
9 | +from yolo4.model import yolo_eval, yolo4_body | ||
10 | +from yolo4.utils import letterbox_image | ||
11 | + | ||
12 | +from PIL import Image, ImageFont, ImageDraw | ||
13 | +from timeit import default_timer as timer | ||
14 | +import matplotlib.pyplot as plt | ||
15 | + | ||
16 | +from operator import itemgetter | ||
17 | + | ||
18 | +class Yolo4(object): | ||
19 | + def get_class(self): | ||
20 | + classes_path = os.path.expanduser(self.classes_path) | ||
21 | + with open(classes_path) as f: | ||
22 | + class_names = f.readlines() | ||
23 | + class_names = [c.strip() for c in class_names] | ||
24 | + return class_names | ||
25 | + | ||
26 | + def get_anchors(self): | ||
27 | + anchors_path = os.path.expanduser(self.anchors_path) | ||
28 | + with open(anchors_path) as f: | ||
29 | + anchors = f.readline() | ||
30 | + anchors = [float(x) for x in anchors.split(',')] | ||
31 | + return np.array(anchors).reshape(-1, 2) | ||
32 | + | ||
33 | + def load_yolo(self): | ||
34 | + model_path = os.path.expanduser(self.model_path) | ||
35 | + assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.' | ||
36 | + | ||
37 | + self.class_names = self.get_class() | ||
38 | + self.anchors = self.get_anchors() | ||
39 | + | ||
40 | + num_anchors = len(self.anchors) | ||
41 | + num_classes = len(self.class_names) | ||
42 | + | ||
43 | + # Generate colors for drawing bounding boxes. | ||
44 | + hsv_tuples = [(x / len(self.class_names), 1., 1.) | ||
45 | + for x in range(len(self.class_names))] | ||
46 | + self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples)) | ||
47 | + self.colors = list( | ||
48 | + map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), | ||
49 | + self.colors)) | ||
50 | + | ||
51 | + self.sess = K.get_session() | ||
52 | + | ||
53 | + # Load model, or construct model and load weights. | ||
54 | + self.yolo4_model = yolo4_body(Input(shape=(608, 608, 3)), num_anchors//3, num_classes) | ||
55 | + | ||
56 | + # Read and convert darknet weight | ||
57 | + print('Loading weights.') | ||
58 | + weights_file = open(self.weights_path, 'rb') | ||
59 | + major, minor, revision = np.ndarray( | ||
60 | + shape=(3, ), dtype='int32', buffer=weights_file.read(12)) | ||
61 | + if (major*10+minor)>=2 and major<1000 and minor<1000: | ||
62 | + seen = np.ndarray(shape=(1,), dtype='int64', buffer=weights_file.read(8)) | ||
63 | + else: | ||
64 | + seen = np.ndarray(shape=(1,), dtype='int32', buffer=weights_file.read(4)) | ||
65 | + print('Weights Header: ', major, minor, revision, seen) | ||
66 | + | ||
67 | + convs_to_load = [] | ||
68 | + bns_to_load = [] | ||
69 | + for i in range(len(self.yolo4_model.layers)): | ||
70 | + layer_name = self.yolo4_model.layers[i].name | ||
71 | + if layer_name.startswith('conv2d_'): | ||
72 | + convs_to_load.append((int(layer_name[7:]), i)) | ||
73 | + if layer_name.startswith('batch_normalization_'): | ||
74 | + bns_to_load.append((int(layer_name[20:]), i)) | ||
75 | + | ||
76 | + convs_sorted = sorted(convs_to_load, key=itemgetter(0)) | ||
77 | + bns_sorted = sorted(bns_to_load, key=itemgetter(0)) | ||
78 | + | ||
79 | + bn_index = 0 | ||
80 | + for i in range(len(convs_sorted)): | ||
81 | + print('Converting ', i) | ||
82 | + if i == 93 or i == 101 or i == 109: | ||
83 | + #no bn, with bias | ||
84 | + weights_shape = self.yolo4_model.layers[convs_sorted[i][1]].get_weights()[0].shape | ||
85 | + bias_shape = self.yolo4_model.layers[convs_sorted[i][1]].get_weights()[0].shape[3] | ||
86 | + filters = bias_shape | ||
87 | + size = weights_shape[0] | ||
88 | + darknet_w_shape = (filters, weights_shape[2], size, size) | ||
89 | + weights_size = np.product(weights_shape) | ||
90 | + | ||
91 | + conv_bias = np.ndarray( | ||
92 | + shape=(filters, ), | ||
93 | + dtype='float32', | ||
94 | + buffer=weights_file.read(filters * 4)) | ||
95 | + conv_weights = np.ndarray( | ||
96 | + shape=darknet_w_shape, | ||
97 | + dtype='float32', | ||
98 | + buffer=weights_file.read(weights_size * 4)) | ||
99 | + conv_weights = np.transpose(conv_weights, [2, 3, 1, 0]) | ||
100 | + self.yolo4_model.layers[convs_sorted[i][1]].set_weights([conv_weights, conv_bias]) | ||
101 | + else: | ||
102 | + #with bn, no bias | ||
103 | + weights_shape = self.yolo4_model.layers[convs_sorted[i][1]].get_weights()[0].shape | ||
104 | + size = weights_shape[0] | ||
105 | + bn_shape = self.yolo4_model.layers[bns_sorted[bn_index][1]].get_weights()[0].shape | ||
106 | + filters = bn_shape[0] | ||
107 | + darknet_w_shape = (filters, weights_shape[2], size, size) | ||
108 | + weights_size = np.product(weights_shape) | ||
109 | + | ||
110 | + conv_bias = np.ndarray( | ||
111 | + shape=(filters, ), | ||
112 | + dtype='float32', | ||
113 | + buffer=weights_file.read(filters * 4)) | ||
114 | + bn_weights = np.ndarray( | ||
115 | + shape=(3, filters), | ||
116 | + dtype='float32', | ||
117 | + buffer=weights_file.read(filters * 12)) | ||
118 | + | ||
119 | + bn_weight_list = [ | ||
120 | + bn_weights[0], # scale gamma | ||
121 | + conv_bias, # shift beta | ||
122 | + bn_weights[1], # running mean | ||
123 | + bn_weights[2] # running var | ||
124 | + ] | ||
125 | + self.yolo4_model.layers[bns_sorted[bn_index][1]].set_weights(bn_weight_list) | ||
126 | + | ||
127 | + conv_weights = np.ndarray( | ||
128 | + shape=darknet_w_shape, | ||
129 | + dtype='float32', | ||
130 | + buffer=weights_file.read(weights_size * 4)) | ||
131 | + conv_weights = np.transpose(conv_weights, [2, 3, 1, 0]) | ||
132 | + self.yolo4_model.layers[convs_sorted[i][1]].set_weights([conv_weights]) | ||
133 | + | ||
134 | + bn_index += 1 | ||
135 | + | ||
136 | + weights_file.close() | ||
137 | + | ||
138 | + self.yolo4_model.save(self.model_path) | ||
139 | + | ||
140 | + | ||
141 | + if self.gpu_num>=2: | ||
142 | + self.yolo4_model = multi_gpu_model(self.yolo4_model, gpus=self.gpu_num) | ||
143 | + | ||
144 | + self.input_image_shape = K.placeholder(shape=(2, )) | ||
145 | + self.boxes, self.scores, self.classes = yolo_eval(self.yolo4_model.output, self.anchors, | ||
146 | + len(self.class_names), self.input_image_shape, | ||
147 | + score_threshold=self.score) | ||
148 | + | ||
149 | + def __init__(self, score, iou, anchors_path, classes_path, model_path, weights_path, gpu_num=1): | ||
150 | + self.score = score | ||
151 | + self.iou = iou | ||
152 | + self.anchors_path = anchors_path | ||
153 | + self.classes_path = classes_path | ||
154 | + self.weights_path = weights_path | ||
155 | + self.model_path = model_path | ||
156 | + self.gpu_num = gpu_num | ||
157 | + self.load_yolo() | ||
158 | + | ||
159 | + def close_session(self): | ||
160 | + self.sess.close() | ||
161 | + | ||
162 | +if __name__ == '__main__': | ||
163 | + model_path = 'model_data/yolo4_weight.h5' | ||
164 | + anchors_path = 'model_data/yolo_anchors.txt' | ||
165 | + classes_path = 'model_data/coco_classes.txt' | ||
166 | + weights_path = 'model_data/yolov4.weights' | ||
167 | + | ||
168 | + score = 0.5 | ||
169 | + iou = 0.5 | ||
170 | + | ||
171 | + model_image_size = (608, 608) | ||
172 | + | ||
173 | + yolo4_model = Yolo4(score, iou, anchors_path, classes_path, model_path, weights_path) | ||
174 | + | ||
175 | + yolo4_model.close_session() | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/deep_sort_yolov4/deep_sort/__init__.py
0 → 100644
1 | +# vim: expandtab:ts=4:sw=4 |
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
code/deep_sort_yolov4/deep_sort/detection.py
0 → 100644
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | +import numpy as np | ||
3 | + | ||
4 | + | ||
5 | +class Detection(object): | ||
6 | + """ | ||
7 | + This class represents a bounding box detection in a single image. | ||
8 | + | ||
9 | + Parameters | ||
10 | + ---------- | ||
11 | + tlwh : array_like | ||
12 | + Bounding box in format `(x, y, w, h)`. | ||
13 | + confidence : float | ||
14 | + Detector confidence score. | ||
15 | + feature : array_like | ||
16 | + A feature vector that describes the object contained in this image. | ||
17 | + | ||
18 | + Attributes | ||
19 | + ---------- | ||
20 | + tlwh : ndarray | ||
21 | + Bounding box in format `(top left x, top left y, width, height)`. | ||
22 | + confidence : ndarray | ||
23 | + Detector confidence score. | ||
24 | + feature : ndarray | NoneType | ||
25 | + A feature vector that describes the object contained in this image. | ||
26 | + | ||
27 | + """ | ||
28 | + | ||
29 | + def __init__(self, tlwh, confidence, feature): | ||
30 | + self.tlwh = np.asarray(tlwh, dtype=np.float) | ||
31 | + self.confidence = float(confidence) | ||
32 | + self.feature = np.asarray(feature, dtype=np.float32) | ||
33 | + | ||
34 | + def to_tlbr(self): | ||
35 | + """Convert bounding box to format `(min x, min y, max x, max y)`, i.e., | ||
36 | + `(top left, bottom right)`. | ||
37 | + """ | ||
38 | + ret = self.tlwh.copy() | ||
39 | + ret[2:] += ret[:2] | ||
40 | + return ret | ||
41 | + | ||
42 | + def to_xyah(self): | ||
43 | + """Convert bounding box to format `(center x, center y, aspect ratio, | ||
44 | + height)`, where the aspect ratio is `width / height`. | ||
45 | + """ | ||
46 | + ret = self.tlwh.copy() | ||
47 | + ret[:2] += ret[2:] / 2 | ||
48 | + ret[2] /= ret[3] | ||
49 | + return ret |
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | +import numpy as np | ||
3 | + | ||
4 | + | ||
5 | +class Detection_YOLO(object): | ||
6 | + """ | ||
7 | + This class represents a bounding box detection in a single image. | ||
8 | + Parameters | ||
9 | + ---------- | ||
10 | + tlwh : array_like | ||
11 | + Bounding box in format `(x, y, w, h)`. | ||
12 | + confidence : float | ||
13 | + Detector confidence score. | ||
14 | + feature : array_like | ||
15 | + A feature vector that describes the object contained in this image. | ||
16 | + Attributesutils | ||
17 | + ---------- | ||
18 | + tlwh : ndarray | ||
19 | + Bounding box in format `(top left x, top left y, width, height)`. | ||
20 | + confidence : ndarray | ||
21 | + Detector confidence score. | ||
22 | + feature : ndarray | NoneType | ||
23 | + A feature vector that describes the object contained in this image. | ||
24 | + """ | ||
25 | + | ||
26 | + def __init__(self, tlwh, confidence, cls): | ||
27 | + self.tlwh = np.asarray(tlwh, dtype=np.float) | ||
28 | + self.confidence = float(confidence) | ||
29 | + self.cls = cls | ||
30 | + | ||
31 | + def to_tlbr(self): | ||
32 | + """Convert bounding box to format `(min x, min y, max x, max y)`, i.e., | ||
33 | + `(top left, bottom right)`. | ||
34 | + """ | ||
35 | + ret = self.tlwh.copy() | ||
36 | + ret[2:] += ret[:2] | ||
37 | + return ret | ||
38 | + | ||
39 | + def to_xyah(self): | ||
40 | + """Convert bounding box to format `(center x, center y, aspect ratio, | ||
41 | + height)`, where the aspect ratio is `width / height`. | ||
42 | + """ | ||
43 | + ret = self.tlwh.copy() | ||
44 | + ret[:2] += ret[2:] / 2 | ||
45 | + ret[2] /= ret[3] | ||
46 | + return ret |
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | +from __future__ import absolute_import | ||
3 | +import numpy as np | ||
4 | +from . import linear_assignment | ||
5 | + | ||
6 | + | ||
7 | +def iou(bbox, candidates): | ||
8 | + """Computer intersection over union. | ||
9 | + | ||
10 | + Parameters | ||
11 | + ---------- | ||
12 | + bbox : ndarray | ||
13 | + A bounding box in format `(top left x, top left y, width, height)`. | ||
14 | + candidates : ndarray | ||
15 | + A matrix of candidate bounding boxes (one per row) in the same format | ||
16 | + as `bbox`. | ||
17 | + | ||
18 | + Returns | ||
19 | + ------- | ||
20 | + ndarray | ||
21 | + The intersection over union in [0, 1] between the `bbox` and each | ||
22 | + candidate. A higher score means a larger fraction of the `bbox` is | ||
23 | + occluded by the candidate. | ||
24 | + | ||
25 | + """ | ||
26 | + bbox_tl, bbox_br = bbox[:2], bbox[:2] + bbox[2:] | ||
27 | + candidates_tl = candidates[:, :2] | ||
28 | + candidates_br = candidates[:, :2] + candidates[:, 2:] | ||
29 | + | ||
30 | + tl = np.c_[np.maximum(bbox_tl[0], candidates_tl[:, 0])[:, np.newaxis], | ||
31 | + np.maximum(bbox_tl[1], candidates_tl[:, 1])[:, np.newaxis]] | ||
32 | + br = np.c_[np.minimum(bbox_br[0], candidates_br[:, 0])[:, np.newaxis], | ||
33 | + np.minimum(bbox_br[1], candidates_br[:, 1])[:, np.newaxis]] | ||
34 | + wh = np.maximum(0., br - tl) | ||
35 | + | ||
36 | + area_intersection = wh.prod(axis=1) | ||
37 | + area_bbox = bbox[2:].prod() | ||
38 | + area_candidates = candidates[:, 2:].prod(axis=1) | ||
39 | + return area_intersection / (area_bbox + area_candidates - area_intersection) | ||
40 | + | ||
41 | + | ||
42 | +def iou_cost(tracks, detections, track_indices=None, | ||
43 | + detection_indices=None): | ||
44 | + """An intersection over union distance metric. | ||
45 | + | ||
46 | + Parameters | ||
47 | + ---------- | ||
48 | + tracks : List[deep_sort.track.Track] | ||
49 | + A list of tracks. | ||
50 | + detections : List[deep_sort.detection.Detection] | ||
51 | + A list of detections. | ||
52 | + track_indices : Optional[List[int]] | ||
53 | + A list of indices to tracks that should be matched. Defaults to | ||
54 | + all `tracks`. | ||
55 | + detection_indices : Optional[List[int]] | ||
56 | + A list of indices to detections that should be matched. Defaults | ||
57 | + to all `detections`. | ||
58 | + | ||
59 | + Returns | ||
60 | + ------- | ||
61 | + ndarray | ||
62 | + Returns a cost matrix of shape | ||
63 | + len(track_indices), len(detection_indices) where entry (i, j) is | ||
64 | + `1 - iou(tracks[track_indices[i]], detections[detection_indices[j]])`. | ||
65 | + | ||
66 | + """ | ||
67 | + if track_indices is None: | ||
68 | + track_indices = np.arange(len(tracks)) | ||
69 | + if detection_indices is None: | ||
70 | + detection_indices = np.arange(len(detections)) | ||
71 | + | ||
72 | + cost_matrix = np.zeros((len(track_indices), len(detection_indices))) | ||
73 | + for row, track_idx in enumerate(track_indices): | ||
74 | + if tracks[track_idx].time_since_update > 1: | ||
75 | + cost_matrix[row, :] = linear_assignment.INFTY_COST | ||
76 | + continue | ||
77 | + | ||
78 | + bbox = tracks[track_idx].to_tlwh() | ||
79 | + candidates = np.asarray([detections[i].tlwh for i in detection_indices]) | ||
80 | + cost_matrix[row, :] = 1. - iou(bbox, candidates) | ||
81 | + return cost_matrix |
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | +import numpy as np | ||
3 | +import scipy.linalg | ||
4 | + | ||
5 | + | ||
6 | +""" | ||
7 | +Table for the 0.95 quantile of the chi-square distribution with N degrees of | ||
8 | +freedom (contains values for N=1, ..., 9). Taken from MATLAB/Octave's chi2inv | ||
9 | +function and used as Mahalanobis gating threshold. | ||
10 | +""" | ||
11 | +chi2inv95 = { | ||
12 | + 1: 3.8415, | ||
13 | + 2: 5.9915, | ||
14 | + 3: 7.8147, | ||
15 | + 4: 9.4877, | ||
16 | + 5: 11.070, | ||
17 | + 6: 12.592, | ||
18 | + 7: 14.067, | ||
19 | + 8: 15.507, | ||
20 | + 9: 16.919} | ||
21 | + | ||
22 | + | ||
23 | +class KalmanFilter(object): | ||
24 | + """ | ||
25 | + A simple Kalman filter for tracking bounding boxes in image space. | ||
26 | + | ||
27 | + The 8-dimensional state space | ||
28 | + | ||
29 | + x, y, a, h, vx, vy, va, vh | ||
30 | + | ||
31 | + contains the bounding box center position (x, y), aspect ratio a, height h, | ||
32 | + and their respective velocities. | ||
33 | + | ||
34 | + Object motion follows a constant velocity model. The bounding box location | ||
35 | + (x, y, a, h) is taken as direct observation of the state space (linear | ||
36 | + observation model). | ||
37 | + | ||
38 | + """ | ||
39 | + | ||
40 | + def __init__(self): | ||
41 | + ndim, dt = 4, 1. | ||
42 | + | ||
43 | + # Create Kalman filter model matrices. | ||
44 | + self._motion_mat = np.eye(2 * ndim, 2 * ndim) | ||
45 | + for i in range(ndim): | ||
46 | + self._motion_mat[i, ndim + i] = dt | ||
47 | + self._update_mat = np.eye(ndim, 2 * ndim) | ||
48 | + | ||
49 | + # Motion and observation uncertainty are chosen relative to the current | ||
50 | + # state estimate. These weights control the amount of uncertainty in | ||
51 | + # the model. This is a bit hacky. | ||
52 | + self._std_weight_position = 1. / 20 | ||
53 | + self._std_weight_velocity = 1. / 160 | ||
54 | + | ||
55 | + def initiate(self, measurement): | ||
56 | + """Create track from unassociated measurement. | ||
57 | + | ||
58 | + Parameters | ||
59 | + ---------- | ||
60 | + measurement : ndarray | ||
61 | + Bounding box coordinates (x, y, a, h) with center position (x, y), | ||
62 | + aspect ratio a, and height h. | ||
63 | + | ||
64 | + Returns | ||
65 | + ------- | ||
66 | + (ndarray, ndarray) | ||
67 | + Returns the mean vector (8 dimensional) and covariance matrix (8x8 | ||
68 | + dimensional) of the new track. Unobserved velocities are initialized | ||
69 | + to 0 mean. | ||
70 | + | ||
71 | + """ | ||
72 | + mean_pos = measurement | ||
73 | + mean_vel = np.zeros_like(mean_pos) | ||
74 | + mean = np.r_[mean_pos, mean_vel] | ||
75 | + | ||
76 | + std = [ | ||
77 | + 2 * self._std_weight_position * measurement[3], | ||
78 | + 2 * self._std_weight_position * measurement[3], | ||
79 | + 1e-2, | ||
80 | + 2 * self._std_weight_position * measurement[3], | ||
81 | + 10 * self._std_weight_velocity * measurement[3], | ||
82 | + 10 * self._std_weight_velocity * measurement[3], | ||
83 | + 1e-5, | ||
84 | + 10 * self._std_weight_velocity * measurement[3]] | ||
85 | + covariance = np.diag(np.square(std)) | ||
86 | + return mean, covariance | ||
87 | + | ||
88 | + def predict(self, mean, covariance): | ||
89 | + """Run Kalman filter prediction step. | ||
90 | + | ||
91 | + Parameters | ||
92 | + ---------- | ||
93 | + mean : ndarray | ||
94 | + The 8 dimensional mean vector of the object state at the previous | ||
95 | + time step. | ||
96 | + covariance : ndarray | ||
97 | + The 8x8 dimensional covariance matrix of the object state at the | ||
98 | + previous time step. | ||
99 | + | ||
100 | + Returns | ||
101 | + ------- | ||
102 | + (ndarray, ndarray) | ||
103 | + Returns the mean vector and covariance matrix of the predicted | ||
104 | + state. Unobserved velocities are initialized to 0 mean. | ||
105 | + | ||
106 | + """ | ||
107 | + std_pos = [ | ||
108 | + self._std_weight_position * mean[3], | ||
109 | + self._std_weight_position * mean[3], | ||
110 | + 1e-2, | ||
111 | + self._std_weight_position * mean[3]] | ||
112 | + std_vel = [ | ||
113 | + self._std_weight_velocity * mean[3], | ||
114 | + self._std_weight_velocity * mean[3], | ||
115 | + 1e-5, | ||
116 | + self._std_weight_velocity * mean[3]] | ||
117 | + motion_cov = np.diag(np.square(np.r_[std_pos, std_vel])) | ||
118 | + | ||
119 | + mean = np.dot(self._motion_mat, mean) | ||
120 | + covariance = np.linalg.multi_dot(( | ||
121 | + self._motion_mat, covariance, self._motion_mat.T)) + motion_cov | ||
122 | + | ||
123 | + return mean, covariance | ||
124 | + | ||
125 | + def project(self, mean, covariance): | ||
126 | + """Project state distribution to measurement space. | ||
127 | + | ||
128 | + Parameters | ||
129 | + ---------- | ||
130 | + mean : ndarray | ||
131 | + The state's mean vector (8 dimensional array). | ||
132 | + covariance : ndarray | ||
133 | + The state's covariance matrix (8x8 dimensional). | ||
134 | + | ||
135 | + Returns | ||
136 | + ------- | ||
137 | + (ndarray, ndarray) | ||
138 | + Returns the projected mean and covariance matrix of the given state | ||
139 | + estimate. | ||
140 | + | ||
141 | + """ | ||
142 | + std = [ | ||
143 | + self._std_weight_position * mean[3], | ||
144 | + self._std_weight_position * mean[3], | ||
145 | + 1e-1, | ||
146 | + self._std_weight_position * mean[3]] | ||
147 | + innovation_cov = np.diag(np.square(std)) | ||
148 | + | ||
149 | + mean = np.dot(self._update_mat, mean) | ||
150 | + covariance = np.linalg.multi_dot(( | ||
151 | + self._update_mat, covariance, self._update_mat.T)) | ||
152 | + return mean, covariance + innovation_cov | ||
153 | + | ||
154 | + def update(self, mean, covariance, measurement): | ||
155 | + """Run Kalman filter correction step. | ||
156 | + | ||
157 | + Parameters | ||
158 | + ---------- | ||
159 | + mean : ndarray | ||
160 | + The predicted state's mean vector (8 dimensional). | ||
161 | + covariance : ndarray | ||
162 | + The state's covariance matrix (8x8 dimensional). | ||
163 | + measurement : ndarray | ||
164 | + The 4 dimensional measurement vector (x, y, a, h), where (x, y) | ||
165 | + is the center position, a the aspect ratio, and h the height of the | ||
166 | + bounding box. | ||
167 | + | ||
168 | + Returns | ||
169 | + ------- | ||
170 | + (ndarray, ndarray) | ||
171 | + Returns the measurement-corrected state distribution. | ||
172 | + | ||
173 | + """ | ||
174 | + projected_mean, projected_cov = self.project(mean, covariance) | ||
175 | + | ||
176 | + chol_factor, lower = scipy.linalg.cho_factor( | ||
177 | + projected_cov, lower=True, check_finite=False) | ||
178 | + kalman_gain = scipy.linalg.cho_solve( | ||
179 | + (chol_factor, lower), np.dot(covariance, self._update_mat.T).T, | ||
180 | + check_finite=False).T | ||
181 | + innovation = measurement - projected_mean | ||
182 | + | ||
183 | + new_mean = mean + np.dot(innovation, kalman_gain.T) | ||
184 | + new_covariance = covariance - np.linalg.multi_dot(( | ||
185 | + kalman_gain, projected_cov, kalman_gain.T)) | ||
186 | + return new_mean, new_covariance | ||
187 | + | ||
188 | + def gating_distance(self, mean, covariance, measurements, | ||
189 | + only_position=False): | ||
190 | + """Compute gating distance between state distribution and measurements. | ||
191 | + | ||
192 | + A suitable distance threshold can be obtained from `chi2inv95`. If | ||
193 | + `only_position` is False, the chi-square distribution has 4 degrees of | ||
194 | + freedom, otherwise 2. | ||
195 | + | ||
196 | + Parameters | ||
197 | + ---------- | ||
198 | + mean : ndarray | ||
199 | + Mean vector over the state distribution (8 dimensional). | ||
200 | + covariance : ndarray | ||
201 | + Covariance of the state distribution (8x8 dimensional). | ||
202 | + measurements : ndarray | ||
203 | + An Nx4 dimensional matrix of N measurements, each in | ||
204 | + format (x, y, a, h) where (x, y) is the bounding box center | ||
205 | + position, a the aspect ratio, and h the height. | ||
206 | + only_position : Optional[bool] | ||
207 | + If True, distance computation is done with respect to the bounding | ||
208 | + box center position only. | ||
209 | + | ||
210 | + Returns | ||
211 | + ------- | ||
212 | + ndarray | ||
213 | + Returns an array of length N, where the i-th element contains the | ||
214 | + squared Mahalanobis distance between (mean, covariance) and | ||
215 | + `measurements[i]`. | ||
216 | + | ||
217 | + """ | ||
218 | + mean, covariance = self.project(mean, covariance) | ||
219 | + if only_position: | ||
220 | + mean, covariance = mean[:2], covariance[:2, :2] | ||
221 | + measurements = measurements[:, :2] | ||
222 | + | ||
223 | + cholesky_factor = np.linalg.cholesky(covariance) | ||
224 | + d = measurements - mean | ||
225 | + z = scipy.linalg.solve_triangular( | ||
226 | + cholesky_factor, d.T, lower=True, check_finite=False, | ||
227 | + overwrite_b=True) | ||
228 | + squared_maha = np.sum(z * z, axis=0) | ||
229 | + return squared_maha |
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | +from __future__ import absolute_import | ||
3 | +import numpy as np | ||
4 | +from sklearn.utils.linear_assignment_ import linear_assignment | ||
5 | +from . import kalman_filter | ||
6 | + | ||
7 | + | ||
8 | +INFTY_COST = 1e+5 | ||
9 | + | ||
10 | + | ||
11 | +def min_cost_matching( | ||
12 | + distance_metric, max_distance, tracks, detections, track_indices=None, | ||
13 | + detection_indices=None): | ||
14 | + """Solve linear assignment problem. | ||
15 | + | ||
16 | + Parameters | ||
17 | + ---------- | ||
18 | + distance_metric : Callable[List[Track], List[Detection], List[int], List[int]) -> ndarray | ||
19 | + The distance metric is given a list of tracks and detections as well as | ||
20 | + a list of N track indices and M detection indices. The metric should | ||
21 | + return the NxM dimensional cost matrix, where element (i, j) is the | ||
22 | + association cost between the i-th track in the given track indices and | ||
23 | + the j-th detection in the given detection_indices. | ||
24 | + max_distance : float | ||
25 | + Gating threshold. Associations with cost larger than this value are | ||
26 | + disregarded. | ||
27 | + tracks : List[track.Track] | ||
28 | + A list of predicted tracks at the current time step. | ||
29 | + detections : List[detection.Detection] | ||
30 | + A list of detections at the current time step. | ||
31 | + track_indices : List[int] | ||
32 | + List of track indices that maps rows in `cost_matrix` to tracks in | ||
33 | + `tracks` (see description above). | ||
34 | + detection_indices : List[int] | ||
35 | + List of detection indices that maps columns in `cost_matrix` to | ||
36 | + detections in `detections` (see description above). | ||
37 | + | ||
38 | + Returns | ||
39 | + ------- | ||
40 | + (List[(int, int)], List[int], List[int]) | ||
41 | + Returns a tuple with the following three entries: | ||
42 | + * A list of matched track and detection indices. | ||
43 | + * A list of unmatched track indices. | ||
44 | + * A list of unmatched detection indices. | ||
45 | + | ||
46 | + """ | ||
47 | + if track_indices is None: | ||
48 | + track_indices = np.arange(len(tracks)) | ||
49 | + if detection_indices is None: | ||
50 | + detection_indices = np.arange(len(detections)) | ||
51 | + | ||
52 | + if len(detection_indices) == 0 or len(track_indices) == 0: | ||
53 | + return [], track_indices, detection_indices # Nothing to match. | ||
54 | + | ||
55 | + cost_matrix = distance_metric( | ||
56 | + tracks, detections, track_indices, detection_indices) | ||
57 | + cost_matrix[cost_matrix > max_distance] = max_distance + 1e-5 | ||
58 | + indices = linear_assignment(cost_matrix) | ||
59 | + | ||
60 | + matches, unmatched_tracks, unmatched_detections = [], [], [] | ||
61 | + for col, detection_idx in enumerate(detection_indices): | ||
62 | + if col not in indices[:, 1]: | ||
63 | + unmatched_detections.append(detection_idx) | ||
64 | + for row, track_idx in enumerate(track_indices): | ||
65 | + if row not in indices[:, 0]: | ||
66 | + unmatched_tracks.append(track_idx) | ||
67 | + for row, col in indices: | ||
68 | + track_idx = track_indices[row] | ||
69 | + detection_idx = detection_indices[col] | ||
70 | + if cost_matrix[row, col] > max_distance: | ||
71 | + unmatched_tracks.append(track_idx) | ||
72 | + unmatched_detections.append(detection_idx) | ||
73 | + else: | ||
74 | + matches.append((track_idx, detection_idx)) | ||
75 | + return matches, unmatched_tracks, unmatched_detections | ||
76 | + | ||
77 | + | ||
78 | +def matching_cascade( | ||
79 | + distance_metric, max_distance, cascade_depth, tracks, detections, | ||
80 | + track_indices=None, detection_indices=None): | ||
81 | + """Run matching cascade. | ||
82 | + | ||
83 | + Parameters | ||
84 | + ---------- | ||
85 | + distance_metric : Callable[List[Track], List[Detection], List[int], List[int]) -> ndarray | ||
86 | + The distance metric is given a list of tracks and detections as well as | ||
87 | + a list of N track indices and M detection indices. The metric should | ||
88 | + return the NxM dimensional cost matrix, where element (i, j) is the | ||
89 | + association cost between the i-th track in the given track indices and | ||
90 | + the j-th detection in the given detection indices. | ||
91 | + max_distance : float | ||
92 | + Gating threshold. Associations with cost larger than this value are | ||
93 | + disregarded. | ||
94 | + cascade_depth: int | ||
95 | + The cascade depth, should be se to the maximum track age. | ||
96 | + tracks : List[track.Track] | ||
97 | + A list of predicted tracks at the current time step. | ||
98 | + detections : List[detection.Detection] | ||
99 | + A list of detections at the current time step. | ||
100 | + track_indices : Optional[List[int]] | ||
101 | + List of track indices that maps rows in `cost_matrix` to tracks in | ||
102 | + `tracks` (see description above). Defaults to all tracks. | ||
103 | + detection_indices : Optional[List[int]] | ||
104 | + List of detection indices that maps columns in `cost_matrix` to | ||
105 | + detections in `detections` (see description above). Defaults to all | ||
106 | + detections. | ||
107 | + | ||
108 | + Returns | ||
109 | + ------- | ||
110 | + (List[(int, int)], List[int], List[int]) | ||
111 | + Returns a tuple with the following three entries: | ||
112 | + * A list of matched track and detection indices. | ||
113 | + * A list of unmatched track indices. | ||
114 | + * A list of unmatched detection indices. | ||
115 | + | ||
116 | + """ | ||
117 | + if track_indices is None: | ||
118 | + track_indices = list(range(len(tracks))) | ||
119 | + if detection_indices is None: | ||
120 | + detection_indices = list(range(len(detections))) | ||
121 | + | ||
122 | + unmatched_detections = detection_indices | ||
123 | + matches = [] | ||
124 | + for level in range(cascade_depth): | ||
125 | + if len(unmatched_detections) == 0: # No detections left | ||
126 | + break | ||
127 | + | ||
128 | + track_indices_l = [ | ||
129 | + k for k in track_indices | ||
130 | + if tracks[k].time_since_update == 1 + level | ||
131 | + ] | ||
132 | + if len(track_indices_l) == 0: # Nothing to match at this level | ||
133 | + continue | ||
134 | + | ||
135 | + matches_l, _, unmatched_detections = \ | ||
136 | + min_cost_matching( | ||
137 | + distance_metric, max_distance, tracks, detections, | ||
138 | + track_indices_l, unmatched_detections) | ||
139 | + matches += matches_l | ||
140 | + unmatched_tracks = list(set(track_indices) - set(k for k, _ in matches)) | ||
141 | + return matches, unmatched_tracks, unmatched_detections | ||
142 | + | ||
143 | + | ||
144 | +def gate_cost_matrix( | ||
145 | + kf, cost_matrix, tracks, detections, track_indices, detection_indices, | ||
146 | + gated_cost=INFTY_COST, only_position=False): | ||
147 | + """Invalidate infeasible entries in cost matrix based on the state | ||
148 | + distributions obtained by Kalman filtering. | ||
149 | + | ||
150 | + Parameters | ||
151 | + ---------- | ||
152 | + kf : The Kalman filter. | ||
153 | + cost_matrix : ndarray | ||
154 | + The NxM dimensional cost matrix, where N is the number of track indices | ||
155 | + and M is the number of detection indices, such that entry (i, j) is the | ||
156 | + association cost between `tracks[track_indices[i]]` and | ||
157 | + `detections[detection_indices[j]]`. | ||
158 | + tracks : List[track.Track] | ||
159 | + A list of predicted tracks at the current time step. | ||
160 | + detections : List[detection.Detection] | ||
161 | + A list of detections at the current time step. | ||
162 | + track_indices : List[int] | ||
163 | + List of track indices that maps rows in `cost_matrix` to tracks in | ||
164 | + `tracks` (see description above). | ||
165 | + detection_indices : List[int] | ||
166 | + List of detection indices that maps columns in `cost_matrix` to | ||
167 | + detections in `detections` (see description above). | ||
168 | + gated_cost : Optional[float] | ||
169 | + Entries in the cost matrix corresponding to infeasible associations are | ||
170 | + set this value. Defaults to a very large value. | ||
171 | + only_position : Optional[bool] | ||
172 | + If True, only the x, y position of the state distribution is considered | ||
173 | + during gating. Defaults to False. | ||
174 | + | ||
175 | + Returns | ||
176 | + ------- | ||
177 | + ndarray | ||
178 | + Returns the modified cost matrix. | ||
179 | + | ||
180 | + """ | ||
181 | + gating_dim = 2 if only_position else 4 | ||
182 | + gating_threshold = kalman_filter.chi2inv95[gating_dim] | ||
183 | + measurements = np.asarray( | ||
184 | + [detections[i].to_xyah() for i in detection_indices]) | ||
185 | + for row, track_idx in enumerate(track_indices): | ||
186 | + track = tracks[track_idx] | ||
187 | + gating_distance = kf.gating_distance( | ||
188 | + track.mean, track.covariance, measurements, only_position) | ||
189 | + cost_matrix[row, gating_distance > gating_threshold] = gated_cost | ||
190 | + return cost_matrix |
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | +import numpy as np | ||
3 | + | ||
4 | + | ||
5 | +def _pdist(a, b): | ||
6 | + """Compute pair-wise squared distance between points in `a` and `b`. | ||
7 | + | ||
8 | + Parameters | ||
9 | + ---------- | ||
10 | + a : array_like | ||
11 | + An NxM matrix of N samples of dimensionality M. | ||
12 | + b : array_like | ||
13 | + An LxM matrix of L samples of dimensionality M. | ||
14 | + | ||
15 | + Returns | ||
16 | + ------- | ||
17 | + ndarray | ||
18 | + Returns a matrix of size len(a), len(b) such that eleement (i, j) | ||
19 | + contains the squared distance between `a[i]` and `b[j]`. | ||
20 | + | ||
21 | + """ | ||
22 | + a, b = np.asarray(a), np.asarray(b) | ||
23 | + if len(a) == 0 or len(b) == 0: | ||
24 | + return np.zeros((len(a), len(b))) | ||
25 | + a2, b2 = np.square(a).sum(axis=1), np.square(b).sum(axis=1) | ||
26 | + r2 = -2. * np.dot(a, b.T) + a2[:, None] + b2[None, :] | ||
27 | + r2 = np.clip(r2, 0., float(np.inf)) | ||
28 | + return r2 | ||
29 | + | ||
30 | + | ||
31 | +def _cosine_distance(a, b, data_is_normalized=False): | ||
32 | + """Compute pair-wise cosine distance between points in `a` and `b`. | ||
33 | + | ||
34 | + Parameters | ||
35 | + ---------- | ||
36 | + a : array_like | ||
37 | + An NxM matrix of N samples of dimensionality M. | ||
38 | + b : array_like | ||
39 | + An LxM matrix of L samples of dimensionality M. | ||
40 | + data_is_normalized : Optional[bool] | ||
41 | + If True, assumes rows in a and b are unit length vectors. | ||
42 | + Otherwise, a and b are explicitly normalized to lenght 1. | ||
43 | + | ||
44 | + Returns | ||
45 | + ------- | ||
46 | + ndarray | ||
47 | + Returns a matrix of size len(a), len(b) such that eleement (i, j) | ||
48 | + contains the squared distance between `a[i]` and `b[j]`. | ||
49 | + | ||
50 | + """ | ||
51 | + if not data_is_normalized: | ||
52 | + a = np.asarray(a) / np.linalg.norm(a, axis=1, keepdims=True) | ||
53 | + b = np.asarray(b) / np.linalg.norm(b, axis=1, keepdims=True) | ||
54 | + return 1. - np.dot(a, b.T) | ||
55 | + | ||
56 | + | ||
57 | +def _nn_euclidean_distance(x, y): | ||
58 | + """ Helper function for nearest neighbor distance metric (Euclidean). | ||
59 | + | ||
60 | + Parameters | ||
61 | + ---------- | ||
62 | + x : ndarray | ||
63 | + A matrix of N row-vectors (sample points). | ||
64 | + y : ndarray | ||
65 | + A matrix of M row-vectors (query points). | ||
66 | + | ||
67 | + Returns | ||
68 | + ------- | ||
69 | + ndarray | ||
70 | + A vector of length M that contains for each entry in `y` the | ||
71 | + smallest Euclidean distance to a sample in `x`. | ||
72 | + | ||
73 | + """ | ||
74 | + distances = _pdist(x, y) | ||
75 | + return np.maximum(0.0, distances.min(axis=0)) | ||
76 | + | ||
77 | + | ||
78 | +def _nn_cosine_distance(x, y): | ||
79 | + """ Helper function for nearest neighbor distance metric (cosine). | ||
80 | + | ||
81 | + Parameters | ||
82 | + ---------- | ||
83 | + x : ndarray | ||
84 | + A matrix of N row-vectors (sample points). | ||
85 | + y : ndarray | ||
86 | + A matrix of M row-vectors (query points). | ||
87 | + | ||
88 | + Returns | ||
89 | + ------- | ||
90 | + ndarray | ||
91 | + A vector of length M that contains for each entry in `y` the | ||
92 | + smallest cosine distance to a sample in `x`. | ||
93 | + | ||
94 | + """ | ||
95 | + distances = _cosine_distance(x, y) | ||
96 | + return distances.min(axis=0) | ||
97 | + | ||
98 | + | ||
99 | +class NearestNeighborDistanceMetric(object): | ||
100 | + """ | ||
101 | + A nearest neighbor distance metric that, for each target, returns | ||
102 | + the closest distance to any sample that has been observed so far. | ||
103 | + | ||
104 | + Parameters | ||
105 | + ---------- | ||
106 | + metric : str | ||
107 | + Either "euclidean" or "cosine". | ||
108 | + matching_threshold: float | ||
109 | + The matching threshold. Samples with larger distance are considered an | ||
110 | + invalid match. | ||
111 | + budget : Optional[int] | ||
112 | + If not None, fix samples per class to at most this number. Removes | ||
113 | + the oldest samples when the budget is reached. | ||
114 | + | ||
115 | + Attributes | ||
116 | + ---------- | ||
117 | + samples : Dict[int -> List[ndarray]] | ||
118 | + A dictionary that maps from target identities to the list of samples | ||
119 | + that have been observed so far. | ||
120 | + | ||
121 | + """ | ||
122 | + | ||
123 | + def __init__(self, metric, matching_threshold, budget=None): | ||
124 | + | ||
125 | + | ||
126 | + if metric == "euclidean": | ||
127 | + self._metric = _nn_euclidean_distance | ||
128 | + elif metric == "cosine": | ||
129 | + self._metric = _nn_cosine_distance | ||
130 | + else: | ||
131 | + raise ValueError( | ||
132 | + "Invalid metric; must be either 'euclidean' or 'cosine'") | ||
133 | + self.matching_threshold = matching_threshold | ||
134 | + self.budget = budget | ||
135 | + self.samples = {} | ||
136 | + | ||
137 | + def partial_fit(self, features, targets, active_targets): | ||
138 | + """Update the distance metric with new data. | ||
139 | + | ||
140 | + Parameters | ||
141 | + ---------- | ||
142 | + features : ndarray | ||
143 | + An NxM matrix of N features of dimensionality M. | ||
144 | + targets : ndarray | ||
145 | + An integer array of associated target identities. | ||
146 | + active_targets : List[int] | ||
147 | + A list of targets that are currently present in the scene. | ||
148 | + | ||
149 | + """ | ||
150 | + for feature, target in zip(features, targets): | ||
151 | + self.samples.setdefault(target, []).append(feature) | ||
152 | + if self.budget is not None: | ||
153 | + self.samples[target] = self.samples[target][-self.budget:] | ||
154 | + self.samples = {k: self.samples[k] for k in active_targets} | ||
155 | + | ||
156 | + def distance(self, features, targets): | ||
157 | + """Compute distance between features and targets. | ||
158 | + | ||
159 | + Parameters | ||
160 | + ---------- | ||
161 | + features : ndarray | ||
162 | + An NxM matrix of N features of dimensionality M. | ||
163 | + targets : List[int] | ||
164 | + A list of targets to match the given `features` against. | ||
165 | + | ||
166 | + Returns | ||
167 | + ------- | ||
168 | + ndarray | ||
169 | + Returns a cost matrix of shape len(targets), len(features), where | ||
170 | + element (i, j) contains the closest squared distance between | ||
171 | + `targets[i]` and `features[j]`. | ||
172 | + | ||
173 | + """ | ||
174 | + cost_matrix = np.zeros((len(targets), len(features))) | ||
175 | + for i, target in enumerate(targets): | ||
176 | + cost_matrix[i, :] = self._metric(self.samples[target], features) | ||
177 | + return cost_matrix |
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | +import numpy as np | ||
3 | +import cv2 | ||
4 | + | ||
5 | + | ||
6 | +def non_max_suppression(boxes, max_bbox_overlap, scores=None): | ||
7 | + """Suppress overlapping detections. | ||
8 | + | ||
9 | + Original code from [1]_ has been adapted to include confidence score. | ||
10 | + | ||
11 | + .. [1] http://www.pyimagesearch.com/2015/02/16/ | ||
12 | + faster-non-maximum-suppression-python/ | ||
13 | + | ||
14 | + Examples | ||
15 | + -------- | ||
16 | + | ||
17 | + >>> boxes = [d.roi for d in detections] | ||
18 | + >>> scores = [d.confidence for d in detections] | ||
19 | + >>> indices = non_max_suppression(boxes, max_bbox_overlap, scores) | ||
20 | + >>> detections = [detections[i] for i in indices] | ||
21 | + | ||
22 | + Parameters | ||
23 | + ---------- | ||
24 | + boxes : ndarray | ||
25 | + Array of ROIs (x, y, width, height). | ||
26 | + max_bbox_overlap : float | ||
27 | + ROIs that overlap more than this values are suppressed. | ||
28 | + scores : Optional[array_like] | ||
29 | + Detector confidence score. | ||
30 | + | ||
31 | + Returns | ||
32 | + ------- | ||
33 | + List[int] | ||
34 | + Returns indices of detections that have survived non-maxima suppression. | ||
35 | + | ||
36 | + """ | ||
37 | + if len(boxes) == 0: | ||
38 | + return [] | ||
39 | + | ||
40 | + boxes = boxes.astype(np.float) | ||
41 | + pick = [] | ||
42 | + | ||
43 | + x1 = boxes[:, 0] | ||
44 | + y1 = boxes[:, 1] | ||
45 | + x2 = boxes[:, 2] + boxes[:, 0] | ||
46 | + y2 = boxes[:, 3] + boxes[:, 1] | ||
47 | + | ||
48 | + area = (x2 - x1 + 1) * (y2 - y1 + 1) | ||
49 | + if scores is not None: | ||
50 | + idxs = np.argsort(scores) | ||
51 | + else: | ||
52 | + idxs = np.argsort(y2) | ||
53 | + | ||
54 | + while len(idxs) > 0: | ||
55 | + last = len(idxs) - 1 | ||
56 | + i = idxs[last] | ||
57 | + pick.append(i) | ||
58 | + | ||
59 | + xx1 = np.maximum(x1[i], x1[idxs[:last]]) | ||
60 | + yy1 = np.maximum(y1[i], y1[idxs[:last]]) | ||
61 | + xx2 = np.minimum(x2[i], x2[idxs[:last]]) | ||
62 | + yy2 = np.minimum(y2[i], y2[idxs[:last]]) | ||
63 | + | ||
64 | + w = np.maximum(0, xx2 - xx1 + 1) | ||
65 | + h = np.maximum(0, yy2 - yy1 + 1) | ||
66 | + | ||
67 | + overlap = (w * h) / area[idxs[:last]] | ||
68 | + | ||
69 | + idxs = np.delete( | ||
70 | + idxs, np.concatenate( | ||
71 | + ([last], np.where(overlap > max_bbox_overlap)[0]))) | ||
72 | + | ||
73 | + return pick |
code/deep_sort_yolov4/deep_sort/track.py
0 → 100644
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | + | ||
3 | + | ||
4 | +class TrackState: | ||
5 | + """ | ||
6 | + Enumeration type for the single target track state. Newly created tracks are | ||
7 | + classified as `tentative` until enough evidence has been collected. Then, | ||
8 | + the track state is changed to `confirmed`. Tracks that are no longer alive | ||
9 | + are classified as `deleted` to mark them for removal from the set of active | ||
10 | + tracks. | ||
11 | + | ||
12 | + """ | ||
13 | + | ||
14 | + Tentative = 1 | ||
15 | + Confirmed = 2 | ||
16 | + Deleted = 3 | ||
17 | + | ||
18 | + | ||
19 | +class Track: | ||
20 | + """ | ||
21 | + A single target track with state space `(x, y, a, h)` and associated | ||
22 | + velocities, where `(x, y)` is the center of the bounding box, `a` is the | ||
23 | + aspect ratio and `h` is the height. | ||
24 | + | ||
25 | + Parameters | ||
26 | + ---------- | ||
27 | + mean : ndarray | ||
28 | + Mean vector of the initial state distribution. | ||
29 | + covariance : ndarray | ||
30 | + Covariance matrix of the initial state distribution. | ||
31 | + track_id : int | ||
32 | + A unique track identifier. | ||
33 | + n_init : int | ||
34 | + Number of consecutive detections before the track is confirmed. The | ||
35 | + track state is set to `Deleted` if a miss occurs within the first | ||
36 | + `n_init` frames. | ||
37 | + max_age : int | ||
38 | + The maximum number of consecutive misses before the track state is | ||
39 | + set to `Deleted`. | ||
40 | + feature : Optional[ndarray] | ||
41 | + Feature vector of the detection this track originates from. If not None, | ||
42 | + this feature is added to the `features` cache. | ||
43 | + | ||
44 | + Attributes | ||
45 | + ---------- | ||
46 | + mean : ndarray | ||
47 | + Mean vector of the initial state distribution. | ||
48 | + covariance : ndarray | ||
49 | + Covariance matrix of the initial state distribution. | ||
50 | + track_id : int | ||
51 | + A unique track identifier. | ||
52 | + hits : int | ||
53 | + Total number of measurement updates. | ||
54 | + age : int | ||
55 | + Total number of frames since first occurance. | ||
56 | + time_since_update : int | ||
57 | + Total number of frames since last measurement update. | ||
58 | + state : TrackState | ||
59 | + The current track state. | ||
60 | + features : List[ndarray] | ||
61 | + A cache of features. On each measurement update, the associated feature | ||
62 | + vector is added to this list. | ||
63 | + | ||
64 | + """ | ||
65 | + | ||
66 | + def __init__(self, mean, covariance, track_id, n_init, max_age, | ||
67 | + feature=None): | ||
68 | + self.mean = mean | ||
69 | + self.covariance = covariance | ||
70 | + self.track_id = track_id | ||
71 | + self.hits = 1 | ||
72 | + self.age = 1 | ||
73 | + self.time_since_update = 0 | ||
74 | + | ||
75 | + self.state = TrackState.Tentative | ||
76 | + self.features = [] | ||
77 | + if feature is not None: | ||
78 | + self.features.append(feature) | ||
79 | + | ||
80 | + self._n_init = n_init | ||
81 | + self._max_age = max_age | ||
82 | + | ||
83 | + def to_tlwh(self): | ||
84 | + """Get current position in bounding box format `(top left x, top left y, | ||
85 | + width, height)`. | ||
86 | + | ||
87 | + Returns | ||
88 | + ------- | ||
89 | + ndarray | ||
90 | + The bounding box. | ||
91 | + | ||
92 | + """ | ||
93 | + ret = self.mean[:4].copy() | ||
94 | + ret[2] *= ret[3] | ||
95 | + ret[:2] -= ret[2:] / 2 | ||
96 | + return ret | ||
97 | + | ||
98 | + def to_tlbr(self): | ||
99 | + """Get current position in bounding box format `(min x, miny, max x, | ||
100 | + max y)`. | ||
101 | + | ||
102 | + Returns | ||
103 | + ------- | ||
104 | + ndarray | ||
105 | + The bounding box. | ||
106 | + | ||
107 | + """ | ||
108 | + ret = self.to_tlwh() | ||
109 | + ret[2:] = ret[:2] + ret[2:] | ||
110 | + return ret | ||
111 | + | ||
112 | + def predict(self, kf): | ||
113 | + """Propagate the state distribution to the current time step using a | ||
114 | + Kalman filter prediction step. | ||
115 | + | ||
116 | + Parameters | ||
117 | + ---------- | ||
118 | + kf : kalman_filter.KalmanFilter | ||
119 | + The Kalman filter. | ||
120 | + | ||
121 | + """ | ||
122 | + self.mean, self.covariance = kf.predict(self.mean, self.covariance) | ||
123 | + self.age += 1 | ||
124 | + self.time_since_update += 1 | ||
125 | + | ||
126 | + def update(self, kf, detection): | ||
127 | + """Perform Kalman filter measurement update step and update the feature | ||
128 | + cache. | ||
129 | + | ||
130 | + Parameters | ||
131 | + ---------- | ||
132 | + kf : kalman_filter.KalmanFilter | ||
133 | + The Kalman filter. | ||
134 | + detection : Detection | ||
135 | + The associated detection. | ||
136 | + | ||
137 | + """ | ||
138 | + self.mean, self.covariance = kf.update( | ||
139 | + self.mean, self.covariance, detection.to_xyah()) | ||
140 | + self.features.append(detection.feature) | ||
141 | + | ||
142 | + self.hits += 1 | ||
143 | + self.time_since_update = 0 | ||
144 | + if self.state == TrackState.Tentative and self.hits >= self._n_init: | ||
145 | + self.state = TrackState.Confirmed | ||
146 | + | ||
147 | + def mark_missed(self): | ||
148 | + """Mark this track as missed (no association at the current time step). | ||
149 | + """ | ||
150 | + if self.state == TrackState.Tentative: | ||
151 | + self.state = TrackState.Deleted | ||
152 | + elif self.time_since_update > self._max_age: | ||
153 | + self.state = TrackState.Deleted | ||
154 | + | ||
155 | + def is_tentative(self): | ||
156 | + """Returns True if this track is tentative (unconfirmed). | ||
157 | + """ | ||
158 | + return self.state == TrackState.Tentative | ||
159 | + | ||
160 | + def is_confirmed(self): | ||
161 | + """Returns True if this track is confirmed.""" | ||
162 | + return self.state == TrackState.Confirmed | ||
163 | + | ||
164 | + def is_deleted(self): | ||
165 | + """Returns True if this track is dead and should be deleted.""" | ||
166 | + return self.state == TrackState.Deleted |
code/deep_sort_yolov4/deep_sort/tracker.py
0 → 100644
1 | +# vim: expandtab:ts=4:sw=4 | ||
2 | +from __future__ import absolute_import | ||
3 | +import numpy as np | ||
4 | +from . import kalman_filter | ||
5 | +from . import linear_assignment | ||
6 | +from . import iou_matching | ||
7 | +from .track import Track | ||
8 | + | ||
9 | + | ||
10 | +class Tracker: | ||
11 | + """ | ||
12 | + This is the multi-target tracker. | ||
13 | + | ||
14 | + Parameters | ||
15 | + ---------- | ||
16 | + metric : nn_matching.NearestNeighborDistanceMetric | ||
17 | + A distance metric for measurement-to-track association. | ||
18 | + max_age : int | ||
19 | + Maximum number of missed misses before a track is deleted. | ||
20 | + n_init : int | ||
21 | + Number of consecutive detections before the track is confirmed. The | ||
22 | + track state is set to `Deleted` if a miss occurs within the first | ||
23 | + `n_init` frames. | ||
24 | + | ||
25 | + Attributes | ||
26 | + ---------- | ||
27 | + metric : nn_matching.NearestNeighborDistanceMetric | ||
28 | + The distance metric used for measurement to track association. | ||
29 | + max_age : int | ||
30 | + Maximum number of missed misses before a track is deleted. | ||
31 | + n_init : int | ||
32 | + Number of frames that a track remains in initialization phase. | ||
33 | + kf : kalman_filter.KalmanFilter | ||
34 | + A Kalman filter to filter target trajectories in image space. | ||
35 | + tracks : List[Track] | ||
36 | + The list of active tracks at the current time step. | ||
37 | + | ||
38 | + """ | ||
39 | + | ||
40 | + def __init__(self, metric, max_iou_distance=0.7, max_age=30, n_init=3): | ||
41 | + self.metric = metric | ||
42 | + self.max_iou_distance = max_iou_distance | ||
43 | + self.max_age = max_age | ||
44 | + self.n_init = n_init | ||
45 | + | ||
46 | + self.kf = kalman_filter.KalmanFilter() | ||
47 | + self.tracks = [] | ||
48 | + self._next_id = 1 | ||
49 | + | ||
50 | + def predict(self): | ||
51 | + """Propagate track state distributions one time step forward. | ||
52 | + | ||
53 | + This function should be called once every time step, before `update`. | ||
54 | + """ | ||
55 | + for track in self.tracks: | ||
56 | + track.predict(self.kf) | ||
57 | + | ||
58 | + def update(self, detections): | ||
59 | + """Perform measurement update and track management. | ||
60 | + | ||
61 | + Parameters | ||
62 | + ---------- | ||
63 | + detections : List[deep_sort.detection.Detection] | ||
64 | + A list of detections at the current time step. | ||
65 | + | ||
66 | + """ | ||
67 | + # Run matching cascade. | ||
68 | + matches, unmatched_tracks, unmatched_detections = \ | ||
69 | + self._match(detections) | ||
70 | + | ||
71 | + # Update track set. | ||
72 | + for track_idx, detection_idx in matches: | ||
73 | + self.tracks[track_idx].update( | ||
74 | + self.kf, detections[detection_idx]) | ||
75 | + for track_idx in unmatched_tracks: | ||
76 | + self.tracks[track_idx].mark_missed() | ||
77 | + for detection_idx in unmatched_detections: | ||
78 | + self._initiate_track(detections[detection_idx]) | ||
79 | + self.tracks = [t for t in self.tracks if not t.is_deleted()] | ||
80 | + | ||
81 | + # Update distance metric. | ||
82 | + active_targets = [t.track_id for t in self.tracks if t.is_confirmed()] | ||
83 | + features, targets = [], [] | ||
84 | + for track in self.tracks: | ||
85 | + if not track.is_confirmed(): | ||
86 | + continue | ||
87 | + features += track.features | ||
88 | + targets += [track.track_id for _ in track.features] | ||
89 | + track.features = [] | ||
90 | + self.metric.partial_fit( | ||
91 | + np.asarray(features), np.asarray(targets), active_targets) | ||
92 | + | ||
93 | + def _match(self, detections): | ||
94 | + | ||
95 | + def gated_metric(tracks, dets, track_indices, detection_indices): | ||
96 | + features = np.array([dets[i].feature for i in detection_indices]) | ||
97 | + targets = np.array([tracks[i].track_id for i in track_indices]) | ||
98 | + cost_matrix = self.metric.distance(features, targets) | ||
99 | + cost_matrix = linear_assignment.gate_cost_matrix( | ||
100 | + self.kf, cost_matrix, tracks, dets, track_indices, | ||
101 | + detection_indices) | ||
102 | + | ||
103 | + return cost_matrix | ||
104 | + | ||
105 | + # Split track set into confirmed and unconfirmed tracks. | ||
106 | + confirmed_tracks = [ | ||
107 | + i for i, t in enumerate(self.tracks) if t.is_confirmed()] | ||
108 | + unconfirmed_tracks = [ | ||
109 | + i for i, t in enumerate(self.tracks) if not t.is_confirmed()] | ||
110 | + | ||
111 | + # Associate confirmed tracks using appearance features. | ||
112 | + matches_a, unmatched_tracks_a, unmatched_detections = \ | ||
113 | + linear_assignment.matching_cascade( | ||
114 | + gated_metric, self.metric.matching_threshold, self.max_age, | ||
115 | + self.tracks, detections, confirmed_tracks) | ||
116 | + | ||
117 | + # Associate remaining tracks together with unconfirmed tracks using IOU. | ||
118 | + iou_track_candidates = unconfirmed_tracks + [ | ||
119 | + k for k in unmatched_tracks_a if | ||
120 | + self.tracks[k].time_since_update == 1] | ||
121 | + unmatched_tracks_a = [ | ||
122 | + k for k in unmatched_tracks_a if | ||
123 | + self.tracks[k].time_since_update != 1] | ||
124 | + matches_b, unmatched_tracks_b, unmatched_detections = \ | ||
125 | + linear_assignment.min_cost_matching( | ||
126 | + iou_matching.iou_cost, self.max_iou_distance, self.tracks, | ||
127 | + detections, iou_track_candidates, unmatched_detections) | ||
128 | + | ||
129 | + matches = matches_a + matches_b | ||
130 | + unmatched_tracks = list(set(unmatched_tracks_a + unmatched_tracks_b)) | ||
131 | + return matches, unmatched_tracks, unmatched_detections | ||
132 | + | ||
133 | + def _initiate_track(self, detection): | ||
134 | + mean, covariance = self.kf.initiate(detection.to_xyah()) | ||
135 | + self.tracks.append(Track( | ||
136 | + mean, covariance, self._next_id, self.n_init, self.max_age, | ||
137 | + detection.feature)) | ||
138 | + self._next_id += 1 |
code/deep_sort_yolov4/detection_rslt.txt
0 → 100644
This diff could not be displayed because it is too large.
code/deep_sort_yolov4/main.py
0 → 100644
This diff is collapsed. Click to expand it.
1 | +person | ||
2 | +bicycle | ||
3 | +car | ||
4 | +motorbike | ||
5 | +aeroplane | ||
6 | +bus | ||
7 | +train | ||
8 | +truck | ||
9 | +boat | ||
10 | +traffic light | ||
11 | +fire hydrant | ||
12 | +stop sign | ||
13 | +parking meter | ||
14 | +bench | ||
15 | +bird | ||
16 | +cat | ||
17 | +dog | ||
18 | +horse | ||
19 | +sheep | ||
20 | +cow | ||
21 | +elephant | ||
22 | +bear | ||
23 | +zebra | ||
24 | +giraffe | ||
25 | +backpack | ||
26 | +umbrella | ||
27 | +handbag | ||
28 | +tie | ||
29 | +suitcase | ||
30 | +frisbee | ||
31 | +skis | ||
32 | +snowboard | ||
33 | +sports ball | ||
34 | +kite | ||
35 | +baseball bat | ||
36 | +baseball glove | ||
37 | +skateboard | ||
38 | +surfboard | ||
39 | +tennis racket | ||
40 | +bottle | ||
41 | +wine glass | ||
42 | +cup | ||
43 | +fork | ||
44 | +knife | ||
45 | +spoon | ||
46 | +bowl | ||
47 | +banana | ||
48 | +apple | ||
49 | +sandwich | ||
50 | +orange | ||
51 | +broccoli | ||
52 | +carrot | ||
53 | +hot dog | ||
54 | +pizza | ||
55 | +donut | ||
56 | +cake | ||
57 | +chair | ||
58 | +sofa | ||
59 | +pottedplant | ||
60 | +bed | ||
61 | +diningtable | ||
62 | +toilet | ||
63 | +tvmonitor | ||
64 | +laptop | ||
65 | +mouse | ||
66 | +remote | ||
67 | +keyboard | ||
68 | +cell phone | ||
69 | +microwave | ||
70 | +oven | ||
71 | +toaster | ||
72 | +sink | ||
73 | +refrigerator | ||
74 | +book | ||
75 | +clock | ||
76 | +vase | ||
77 | +scissors | ||
78 | +teddy bear | ||
79 | +hair drier | ||
80 | +toothbrush |
This file is too large to display.
This file is too large to display.
code/deep_sort_yolov4/model_data/mars.pb
0 → 100644
This file is too large to display.
code/deep_sort_yolov4/model_data/obj.txt
0 → 100644
1 | +person |
1 | +12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401 |
code/deep_sort_yolov4/output/README.md
0 → 100644
1 | +{"schema":{"fields":[{"name":"index","type":"integer"},{"name":"total","type":"string"},{"name":"now","type":"string"},{"name":"time","type":"string"},{"name":"s","type":"number"}],"primaryKey":["index"],"pandas_version":"0.20.0"},"data":[{"index":0,"total":0,"now":0,"time":"2020-06-17 03:27:10.288851","s":0.0},{"index":1,"total":11,"now":7,"time":"2020-06-17 03:27:11.051125","s":0.7622739018},{"index":2,"total":13,"now":8,"time":"2020-06-17 03:27:11.813399","s":1.5245478036},{"index":3,"total":15,"now":10,"time":"2020-06-17 03:27:12.575673","s":2.2868217054},{"index":4,"total":21,"now":13,"time":"2020-06-17 03:27:13.337947","s":3.0490956072},{"index":5,"total":22,"now":13,"time":"2020-06-17 03:27:14.100221","s":3.811369509},{"index":6,"total":23,"now":12,"time":"2020-06-17 03:27:14.862494","s":4.5736434109},{"index":7,"total":24,"now":11,"time":"2020-06-17 03:27:15.624768","s":5.3359173127},{"index":8,"total":24,"now":7,"time":"2020-06-17 03:27:16.387042","s":6.0981912145},{"index":9,"total":25,"now":8,"time":"2020-06-17 03:27:17.149316","s":6.8604651163},{"index":10,"total":26,"now":9,"time":"2020-06-17 03:27:17.911590","s":7.6227390181},{"index":11,"total":26,"now":10,"time":"2020-06-17 03:27:18.673864","s":8.3850129199},{"index":12,"total":26,"now":7,"time":"2020-06-17 03:27:19.436138","s":9.1472868217},{"index":13,"total":29,"now":8,"time":"2020-06-17 03:27:20.198412","s":9.9095607235},{"index":14,"total":31,"now":11,"time":"2020-06-17 03:27:20.960686","s":10.6718346253},{"index":15,"total":31,"now":10,"time":"2020-06-17 03:27:21.722960","s":11.4341085271},{"index":16,"total":32,"now":13,"time":"2020-06-17 03:27:22.485233","s":12.1963824289},{"index":17,"total":33,"now":11,"time":"2020-06-17 03:27:23.247507","s":12.9586563307},{"index":18,"total":33,"now":11,"time":"2020-06-17 03:27:24.009781","s":13.7209302326},{"index":19,"total":35,"now":11,"time":"2020-06-17 03:27:24.772055","s":14.4832041344},{"index":20,"total":37,"now":9,"time":"2020-06-17 03:27:25.534329","s":15.2454780362},{"index":21,"total":38,"now":10,"time":"2020-06-17 03:27:26.296603","s":16.007751938},{"index":22,"total":39,"now":11,"time":"2020-06-17 03:27:27.058877","s":16.7700258398},{"index":23,"total":41,"now":12,"time":"2020-06-17 03:27:27.821151","s":17.5322997416},{"index":24,"total":42,"now":11,"time":"2020-06-17 03:27:28.583425","s":18.2945736434},{"index":25,"total":42,"now":12,"time":"2020-06-17 03:27:29.345699","s":19.0568475452},{"index":26,"total":42,"now":10,"time":"2020-06-17 03:27:30.107972","s":19.819121447},{"index":27,"total":42,"now":8,"time":"2020-06-17 03:27:30.870246","s":20.5813953488},{"index":28,"total":43,"now":9,"time":"2020-06-17 03:27:31.632520","s":21.3436692506},{"index":29,"total":45,"now":11,"time":"2020-06-17 03:27:32.394794","s":22.1059431525},{"index":30,"total":45,"now":9,"time":"2020-06-17 03:27:33.157068","s":22.8682170543},{"index":31,"total":46,"now":11,"time":"2020-06-17 03:27:33.919342","s":23.6304909561},{"index":32,"total":46,"now":11,"time":"2020-06-17 03:27:34.681616","s":24.3927648579},{"index":33,"total":46,"now":11,"time":"2020-06-17 03:27:35.443890","s":25.1550387597},{"index":34,"total":47,"now":10,"time":"2020-06-17 03:27:36.206164","s":25.9173126615},{"index":35,"total":47,"now":9,"time":"2020-06-17 03:27:36.968438","s":26.6795865633},{"index":36,"total":48,"now":10,"time":"2020-06-17 03:27:37.730711","s":27.4418604651},{"index":37,"total":48,"now":10,"time":"2020-06-17 03:27:38.492985","s":28.2041343669},{"index":38,"total":50,"now":10,"time":"2020-06-17 03:27:39.255259","s":28.9664082687},{"index":39,"total":51,"now":10,"time":"2020-06-17 03:27:40.017533","s":29.7286821705},{"index":40,"total":52,"now":9,"time":"2020-06-17 03:27:40.779807","s":30.4909560724},{"index":41,"total":52,"now":8,"time":"2020-06-17 03:27:41.542081","s":31.2532299742},{"index":42,"total":53,"now":7,"time":"2020-06-17 03:27:42.304355","s":32.015503876},{"index":43,"total":55,"now":8,"time":"2020-06-17 03:27:43.066629","s":32.7777777778},{"index":44,"total":56,"now":8,"time":"2020-06-17 03:27:43.828903","s":33.5400516796},{"index":45,"total":56,"now":10,"time":"2020-06-17 03:27:44.591177","s":34.3023255814},{"index":46,"total":56,"now":9,"time":"2020-06-17 03:27:45.353450","s":35.0645994832},{"index":47,"total":57,"now":10,"time":"2020-06-17 03:27:46.115724","s":35.826873385},{"index":48,"total":57,"now":8,"time":"2020-06-17 03:27:46.877998","s":36.5891472868},{"index":49,"total":58,"now":10,"time":"2020-06-17 03:27:47.640272","s":37.3514211886},{"index":50,"total":58,"now":8,"time":"2020-06-17 03:27:48.402546","s":38.1136950904},{"index":51,"total":60,"now":8,"time":"2020-06-17 03:27:49.164820","s":38.8759689922},{"index":52,"total":63,"now":12,"time":"2020-06-17 03:27:49.927094","s":39.6382428941},{"index":53,"total":63,"now":12,"time":"2020-06-17 03:27:50.689368","s":40.4005167959},{"index":54,"total":63,"now":8,"time":"2020-06-17 03:27:51.451642","s":41.1627906977},{"index":55,"total":63,"now":10,"time":"2020-06-17 03:27:52.213916","s":41.9250645995},{"index":56,"total":64,"now":11,"time":"2020-06-17 03:27:52.976190","s":42.6873385013},{"index":57,"total":64,"now":9,"time":"2020-06-17 03:27:53.738463","s":43.4496124031},{"index":58,"total":64,"now":10,"time":"2020-06-17 03:27:54.500737","s":44.2118863049},{"index":59,"total":64,"now":9,"time":"2020-06-17 03:27:55.263011","s":44.9741602067},{"index":60,"total":64,"now":8,"time":"2020-06-17 03:27:56.025285","s":45.7364341085},{"index":61,"total":64,"now":9,"time":"2020-06-17 03:27:56.787559","s":46.4987080103},{"index":62,"total":65,"now":8,"time":"2020-06-17 03:27:57.549833","s":47.2609819121},{"index":63,"total":66,"now":9,"time":"2020-06-17 03:27:58.312107","s":48.023255814},{"index":64,"total":67,"now":10,"time":"2020-06-17 03:27:59.074381","s":48.7855297158},{"index":65,"total":68,"now":10,"time":"2020-06-17 03:27:59.836655","s":49.5478036176},{"index":66,"total":68,"now":9,"time":"2020-06-17 03:28:00.598929","s":50.3100775194},{"index":67,"total":69,"now":9,"time":"2020-06-17 03:28:01.361202","s":51.0723514212},{"index":68,"total":69,"now":8,"time":"2020-06-17 03:28:02.123476","s":51.834625323},{"index":69,"total":69,"now":10,"time":"2020-06-17 03:28:02.885750","s":52.5968992248},{"index":70,"total":70,"now":8,"time":"2020-06-17 03:28:03.648024","s":53.3591731266},{"index":71,"total":71,"now":9,"time":"2020-06-17 03:28:04.410298","s":54.1214470284},{"index":72,"total":73,"now":11,"time":"2020-06-17 03:28:05.172572","s":54.8837209302},{"index":73,"total":73,"now":10,"time":"2020-06-17 03:28:05.934846","s":55.645994832},{"index":74,"total":74,"now":9,"time":"2020-06-17 03:28:06.697120","s":56.4082687339},{"index":75,"total":75,"now":10,"time":"2020-06-17 03:28:07.459394","s":57.1705426357},{"index":76,"total":75,"now":7,"time":"2020-06-17 03:28:08.221668","s":57.9328165375},{"index":77,"total":77,"now":10,"time":"2020-06-17 03:28:08.983941","s":58.6950904393}]} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/deep_sort_yolov4/output/test3_img.png
0 → 100644
1.8 MB
code/deep_sort_yolov4/output/test3_xy.json
0 → 100644
This diff is collapsed. Click to expand it.
code/deep_sort_yolov4/output/test_count.json
0 → 100644
1 | +{"schema":{"fields":[{"name":"index","type":"string"},{"name":"total","type":"string"},{"name":"now","type":"string"},{"name":"time","type":"string"},{"name":"s","type":"string"}],"primaryKey":["index"],"pandas_version":"0.20.0"},"data":[]} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
code/deep_sort_yolov4/output/test_xy.json
0 → 100644
1 | +{"schema":{"fields":[{"name":"index","type":"string"},{"name":"x","type":"string"},{"name":"y","type":"string"},{"name":"id","type":"string"},{"name":"time","type":"string"},{"name":"s","type":"string"}],"primaryKey":["index"],"pandas_version":"0.20.0"},"data":[]} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
339 KB
code/deep_sort_yolov4/requirements.txt
0 → 100644
code/deep_sort_yolov4/test_video/README.md
0 → 100644
code/deep_sort_yolov4/test_video/test.mp4
0 → 100644
This file is too large to display.
code/deep_sort_yolov4/test_video/test3.mp4
0 → 100644
This file is too large to display.
No preview for this file type
code/deep_sort_yolov4/tools/frame2video.py
0 → 100644
1 | +import cv2 | ||
2 | +import os | ||
3 | +import moviepy.editor as mp | ||
4 | + | ||
5 | +video = "00046" | ||
6 | +#input_imgs | ||
7 | +im_dir = '/home/cai/Desktop/yolo_dataset/t1_video/t1_video_'+video+'/' | ||
8 | +#im_dir = | ||
9 | +#output_video | ||
10 | +video_dir = '/home/cai/Desktop/yolo_dataset/t1_video/test_video/det_t1_video_'+video+'_test_q.avi' | ||
11 | +#fps | ||
12 | +fps = 50 | ||
13 | +#num_of_imgs | ||
14 | +num = 310 | ||
15 | +#img_size | ||
16 | +img_size = (1920,1080) | ||
17 | +fourcc = cv2.VideoWriter_fourcc('M','J','P','G') | ||
18 | +#opencv3 | ||
19 | + | ||
20 | +videoWriter = cv2.VideoWriter(video_dir, fourcc, fps, img_size) | ||
21 | +for i in range(0,num): | ||
22 | + #im_name = os.path.join(im_dir,'frame-' + str(i) + '.png') | ||
23 | + im_name = os.path.join(im_dir,'t1_video_'+video+'_' + "%05d" % i + '.jpg') | ||
24 | + frame = cv2.imread(im_name) | ||
25 | + #frame = cv2.resize(frame, (480, 320)) | ||
26 | + #frame = cv2.resize(frame,(520,320), interpolation=cv2.INTER_CUBIC) | ||
27 | + videoWriter.write(frame) | ||
28 | + print (im_name) | ||
29 | +videoWriter.release() | ||
30 | +print('finish') |
code/deep_sort_yolov4/tools/freeze_model.py
0 → 100644
This diff is collapsed. Click to expand it.
1 | +import os | ||
2 | +import errno | ||
3 | +import argparse | ||
4 | +import numpy as np | ||
5 | +import cv2 | ||
6 | +import tensorflow as tf | ||
7 | + | ||
8 | + | ||
9 | +def _run_in_batches(f, data_dict, out, batch_size): | ||
10 | + data_len = len(out) | ||
11 | + num_batches = int(data_len / batch_size) | ||
12 | + | ||
13 | + s, e = 0, 0 | ||
14 | + for i in range(num_batches): | ||
15 | + s, e = i * batch_size, (i + 1) * batch_size | ||
16 | + batch_data_dict = {k: v[s:e] for k, v in data_dict.items()} | ||
17 | + out[s:e] = f(batch_data_dict) | ||
18 | + if e < len(out): | ||
19 | + batch_data_dict = {k: v[e:] for k, v in data_dict.items()} | ||
20 | + out[e:] = f(batch_data_dict) | ||
21 | + | ||
22 | + | ||
23 | +def extract_image_patch(image, bbox, patch_shape): | ||
24 | + """Extract image patch from bounding box. | ||
25 | + Parameters | ||
26 | + ---------- | ||
27 | + image : ndarray | ||
28 | + The full image. | ||
29 | + bbox : array_like | ||
30 | + The bounding box in format (x, y, width, height). | ||
31 | + patch_shape : Optional[array_like] | ||
32 | + This parameter can be used to enforce a desired patch shape | ||
33 | + (height, width). First, the `bbox` is adapted to the aspect ratio | ||
34 | + of the patch shape, then it is clipped at the image boundaries. | ||
35 | + If None, the shape is computed from :arg:`bbox`. | ||
36 | + Returns | ||
37 | + ------- | ||
38 | + ndarray | NoneType | ||
39 | + An image patch showing the :arg:`bbox`, optionally reshaped to | ||
40 | + :arg:`patch_shape`. | ||
41 | + Returns None if the bounding box is empty or fully outside of the image | ||
42 | + boundaries. | ||
43 | + """ | ||
44 | + bbox = np.array(bbox) | ||
45 | + if patch_shape is not None: | ||
46 | + # correct aspect ratio to patch shape | ||
47 | + target_aspect = float(patch_shape[1]) / patch_shape[0] | ||
48 | + new_width = target_aspect * bbox[3] | ||
49 | + bbox[0] -= (new_width - bbox[2]) / 2 | ||
50 | + bbox[2] = new_width | ||
51 | + | ||
52 | + # convert to top left, bottom right | ||
53 | + bbox[2:] += bbox[:2] | ||
54 | + bbox = bbox.astype(np.int) | ||
55 | + | ||
56 | + # clip at image boundaries | ||
57 | + bbox[:2] = np.maximum(0, bbox[:2]) | ||
58 | + bbox[2:] = np.minimum(np.asarray(image.shape[:2][::-1]) - 1, bbox[2:]) | ||
59 | + if np.any(bbox[:2] >= bbox[2:]): | ||
60 | + return None | ||
61 | + sx, sy, ex, ey = bbox | ||
62 | + image = image[sy:ey, sx:ex] | ||
63 | + image = cv2.resize(image, tuple(patch_shape[::-1])) | ||
64 | + return image | ||
65 | + | ||
66 | + | ||
67 | +class ImageEncoder(object): | ||
68 | + | ||
69 | + def __init__(self, checkpoint_filename, input_name="images", | ||
70 | + output_name="features"): | ||
71 | + self.session = tf.Session() | ||
72 | + with tf.gfile.GFile(checkpoint_filename, "rb") as file_handle: | ||
73 | + graph_def = tf.GraphDef() | ||
74 | + graph_def.ParseFromString(file_handle.read()) | ||
75 | + tf.import_graph_def(graph_def, name="net") | ||
76 | + self.input_var = tf.get_default_graph().get_tensor_by_name( | ||
77 | + "net/%s:0" % input_name) | ||
78 | + self.output_var = tf.get_default_graph().get_tensor_by_name( | ||
79 | + "net/%s:0" % output_name) | ||
80 | + | ||
81 | + assert len(self.output_var.get_shape()) == 2 | ||
82 | + assert len(self.input_var.get_shape()) == 4 | ||
83 | + self.feature_dim = self.output_var.get_shape().as_list()[-1] | ||
84 | + self.image_shape = self.input_var.get_shape().as_list()[1:] | ||
85 | + | ||
86 | + def __call__(self, data_x, batch_size=32): | ||
87 | + out = np.zeros((len(data_x), self.feature_dim), np.float32) | ||
88 | + _run_in_batches( | ||
89 | + lambda x: self.session.run(self.output_var, feed_dict=x), | ||
90 | + {self.input_var: data_x}, out, batch_size) | ||
91 | + return out | ||
92 | + | ||
93 | + | ||
94 | +def create_box_encoder(model_filename, input_name="images", | ||
95 | + output_name="features", batch_size=32): | ||
96 | + image_encoder = ImageEncoder(model_filename, input_name, output_name) | ||
97 | + image_shape = image_encoder.image_shape | ||
98 | + | ||
99 | + def encoder(image, boxes): | ||
100 | + image_patches = [] | ||
101 | + for box in boxes: | ||
102 | + patch = extract_image_patch(image, box, image_shape[:2]) | ||
103 | + if patch is None: | ||
104 | + print("WARNING: Failed to extract image patch: %s." % str(box)) | ||
105 | + patch = np.random.uniform( | ||
106 | + 0., 255., image_shape).astype(np.uint8) | ||
107 | + image_patches.append(patch) | ||
108 | + image_patches = np.asarray(image_patches) | ||
109 | + return image_encoder(image_patches, batch_size) | ||
110 | + | ||
111 | + return encoder | ||
112 | + | ||
113 | + | ||
114 | +def generate_detections(encoder, mot_dir, output_dir, detection_dir=None): | ||
115 | + """Generate detections with features. | ||
116 | + Parameters | ||
117 | + ---------- | ||
118 | + encoder : Callable[image, ndarray] -> ndarray | ||
119 | + The encoder function takes as input a BGR color image and a matrix of | ||
120 | + bounding boxes in format `(x, y, w, h)` and returns a matrix of | ||
121 | + corresponding feature vectors. | ||
122 | + mot_dir : str | ||
123 | + Path to the MOTChallenge directory (can be either train or test). | ||
124 | + output_dir | ||
125 | + Path to the output directory. Will be created if it does not exist. | ||
126 | + detection_dir | ||
127 | + Path to custom detections. The directory structure should be the default | ||
128 | + MOTChallenge structure: `[sequence]/det/det.txt`. If None, uses the | ||
129 | + standard MOTChallenge detections. | ||
130 | + """ | ||
131 | + if detection_dir is None: | ||
132 | + detection_dir = mot_dir | ||
133 | + try: | ||
134 | + os.makedirs(output_dir) | ||
135 | + except OSError as exception: | ||
136 | + if exception.errno == errno.EEXIST and os.path.isdir(output_dir): | ||
137 | + pass | ||
138 | + else: | ||
139 | + raise ValueError( | ||
140 | + "Failed to created output directory '%s'" % output_dir) | ||
141 | + | ||
142 | + for sequence in os.listdir(mot_dir): | ||
143 | + print("Processing %s" % sequence) | ||
144 | + sequence_dir = os.path.join(mot_dir, sequence) | ||
145 | + | ||
146 | + image_dir = os.path.join(sequence_dir, "img1") | ||
147 | + image_filenames = { | ||
148 | + int(os.path.splitext(f)[0]): os.path.join(image_dir, f) | ||
149 | + for f in os.listdir(image_dir)} | ||
150 | + | ||
151 | + detection_file = os.path.join( | ||
152 | + detection_dir, sequence, "det/det.txt") | ||
153 | + detections_in = np.loadtxt(detection_file, delimiter=',') | ||
154 | + detections_out = [] | ||
155 | + | ||
156 | + frame_indices = detections_in[:, 0].astype(np.int) | ||
157 | + min_frame_idx = frame_indices.astype(np.int).min() | ||
158 | + max_frame_idx = frame_indices.astype(np.int).max() | ||
159 | + for frame_idx in range(min_frame_idx, max_frame_idx + 1): | ||
160 | + print("Frame %05d/%05d" % (frame_idx, max_frame_idx)) | ||
161 | + mask = frame_indices == frame_idx | ||
162 | + rows = detections_in[mask] | ||
163 | + | ||
164 | + if frame_idx not in image_filenames: | ||
165 | + print("WARNING could not find image for frame %d" % frame_idx) | ||
166 | + continue | ||
167 | + bgr_image = cv2.imread( | ||
168 | + image_filenames[frame_idx], cv2.IMREAD_COLOR) | ||
169 | + features = encoder(bgr_image, rows[:, 2:6].copy()) | ||
170 | + detections_out += [np.r_[(row, feature)] for row, feature | ||
171 | + in zip(rows, features)] | ||
172 | + | ||
173 | + output_filename = os.path.join(output_dir, "%s.npy" % sequence) | ||
174 | + np.save( | ||
175 | + output_filename, np.asarray(detections_out), allow_pickle=False) | ||
176 | + | ||
177 | + | ||
178 | +def parse_args(): | ||
179 | + """Parse command line arguments. | ||
180 | + """ | ||
181 | + parser = argparse.ArgumentParser(description="Re-ID feature extractor") | ||
182 | + parser.add_argument( | ||
183 | + "--model", | ||
184 | + default="resources/networks/mars-small128.pb", | ||
185 | + help="Path to freezed inference graph protobuf.") | ||
186 | + parser.add_argument( | ||
187 | + "--mot_dir", help="Path to MOTChallenge directory (train or test)", | ||
188 | + required=True) | ||
189 | + parser.add_argument( | ||
190 | + "--detection_dir", help="Path to custom detections. Defaults to " | ||
191 | + "standard MOT detections Directory structure should be the default " | ||
192 | + "MOTChallenge structure: [sequence]/det/det.txt", default=None) | ||
193 | + parser.add_argument( | ||
194 | + "--output_dir", help="Output directory. Will be created if it does not" | ||
195 | + " exist.", default="detections") | ||
196 | + return parser.parse_args() | ||
197 | + | ||
198 | + | ||
199 | +def main(): | ||
200 | + args = parse_args() | ||
201 | + encoder = create_box_encoder(args.model, batch_size=32) | ||
202 | + generate_detections(encoder, args.mot_dir, args.output_dir, | ||
203 | + args.detection_dir) | ||
204 | + | ||
205 | + | ||
206 | +if __name__ == "__main__": | ||
207 | + main() |
code/deep_sort_yolov4/tools/video2frame.py
0 → 100644
1 | +import cv2 | ||
2 | + | ||
3 | +image_folder = './mask_face' | ||
4 | +video_name = './cut_test.m4v' | ||
5 | + | ||
6 | +vc = cv2.VideoCapture(video_name) | ||
7 | +c = 1 | ||
8 | +if vc.isOpened(): | ||
9 | + rval,frame=vc.read() | ||
10 | +else: | ||
11 | + rval=False | ||
12 | +while rval: | ||
13 | + rval,frame=vc.read() | ||
14 | + cv2.imwrite('./mask_face/IMG_'+str(c)+'.jpg',frame) | ||
15 | + c=c+1 | ||
16 | + cv2.waitKey(1) | ||
17 | +vc.release() |
code/deep_sort_yolov4/yolo.py
0 → 100644
1 | +import colorsys | ||
2 | + | ||
3 | +import numpy as np | ||
4 | +from keras import backend as K | ||
5 | +from keras.models import load_model | ||
6 | + | ||
7 | +from yolo4.model import yolo_eval, Mish | ||
8 | +from yolo4.utils import letterbox_image | ||
9 | +import os | ||
10 | +from keras.utils import multi_gpu_model | ||
11 | + | ||
12 | +class YOLO(object): | ||
13 | + def __init__(self): | ||
14 | + self.model_path = os.getcwd() + '/../deep_sort_yolov4/model_data/yolo4_weight.h5' | ||
15 | + self.anchors_path = os.getcwd() + '/../deep_sort_yolov4/model_data/yolo_anchors.txt' | ||
16 | + self.classes_path = os.getcwd() + '/../deep_sort_yolov4/model_data/coco_classes.txt' | ||
17 | + self.gpu_num = 1 | ||
18 | + self.score = 0.5 | ||
19 | + self.iou = 0.5 | ||
20 | + self.class_names = self._get_class() | ||
21 | + self.anchors = self._get_anchors() | ||
22 | + self.sess = K.get_session() | ||
23 | + self.model_image_size = (608, 608) # fixed size or (None, None) | ||
24 | + self.is_fixed_size = self.model_image_size != (None, None) | ||
25 | + self.boxes, self.scores, self.classes = self.generate() | ||
26 | + | ||
27 | + def _get_class(self): | ||
28 | + classes_path = os.path.expanduser(self.classes_path) | ||
29 | + with open(classes_path) as f: | ||
30 | + class_names = f.readlines() | ||
31 | + class_names = [c.strip() for c in class_names] | ||
32 | + return class_names | ||
33 | + | ||
34 | + def _get_anchors(self): | ||
35 | + anchors_path = os.path.expanduser(self.anchors_path) | ||
36 | + with open(anchors_path) as f: | ||
37 | + anchors = f.readline() | ||
38 | + anchors = [float(x) for x in anchors.split(',')] | ||
39 | + anchors = np.array(anchors).reshape(-1, 2) | ||
40 | + return anchors | ||
41 | + | ||
42 | + def generate(self): | ||
43 | + model_path = os.path.expanduser(self.model_path) | ||
44 | + assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.' | ||
45 | + | ||
46 | + self.yolo_model = load_model(model_path, custom_objects={'Mish': Mish}, compile=False) | ||
47 | + | ||
48 | + print('{} model, anchors, and classes loaded.'.format(model_path)) | ||
49 | + | ||
50 | + # Generate colors for drawing bounding boxes. | ||
51 | + hsv_tuples = [(x / len(self.class_names), 1., 1.) | ||
52 | + for x in range(len(self.class_names))] | ||
53 | + self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples)) | ||
54 | + self.colors = list( | ||
55 | + map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), | ||
56 | + self.colors)) | ||
57 | + np.random.seed(10101) # Fixed seed for consistent colors across runs. | ||
58 | + np.random.shuffle(self.colors) # Shuffle colors to decorrelate adjacent classes. | ||
59 | + np.random.seed(None) # Reset seed to default. | ||
60 | + | ||
61 | + # Generate output tensor targets for filtered bounding boxes. | ||
62 | + self.input_image_shape = K.placeholder(shape=(2, )) | ||
63 | + if self.gpu_num>=2: | ||
64 | + self.yolo_model = multi_gpu_model(self.yolo_model, gpus=self.gpu_num) | ||
65 | + boxes, scores, classes = yolo_eval(self.yolo_model.output, self.anchors, | ||
66 | + len(self.class_names), self.input_image_shape, | ||
67 | + score_threshold=self.score, iou_threshold=self.iou) | ||
68 | + return boxes, scores, classes | ||
69 | + | ||
70 | + def detect_image(self, image): | ||
71 | + | ||
72 | + if self.is_fixed_size: | ||
73 | + assert self.model_image_size[0]%32 == 0, 'Multiples of 32 required' | ||
74 | + assert self.model_image_size[1]%32 == 0, 'Multiples of 32 required' | ||
75 | + boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size))) | ||
76 | + else: | ||
77 | + new_image_size = (image.width - (image.width % 32), | ||
78 | + image.height - (image.height % 32)) | ||
79 | + boxed_image = letterbox_image(image, new_image_size) | ||
80 | + image_data = np.array(boxed_image, dtype='float32') | ||
81 | + | ||
82 | + # print(image_data.shape) | ||
83 | + image_data /= 255. | ||
84 | + image_data = np.expand_dims(image_data, 0) # Add batch dimension. | ||
85 | + | ||
86 | + out_boxes, out_scores, out_classes = self.sess.run( | ||
87 | + [self.boxes, self.scores, self.classes], | ||
88 | + feed_dict={ | ||
89 | + self.yolo_model.input: image_data, | ||
90 | + self.input_image_shape: [image.size[1], image.size[0]], | ||
91 | + K.learning_phase(): 0 | ||
92 | + }) | ||
93 | + return_boxes = [] | ||
94 | + return_scores = [] | ||
95 | + return_class_names = [] | ||
96 | + for i, c in reversed(list(enumerate(out_classes))): | ||
97 | + predicted_class = self.class_names[c] | ||
98 | + if predicted_class != 'person': # Modify to detect other classes. | ||
99 | + continue | ||
100 | + box = out_boxes[i] | ||
101 | + score = out_scores[i] | ||
102 | + x = int(box[1]) | ||
103 | + y = int(box[0]) | ||
104 | + w = int(box[3] - box[1]) | ||
105 | + h = int(box[2] - box[0]) | ||
106 | + if x < 0: | ||
107 | + w = w + x | ||
108 | + x = 0 | ||
109 | + if y < 0: | ||
110 | + h = h + y | ||
111 | + y = 0 | ||
112 | + return_boxes.append([x, y, w, h]) | ||
113 | + return_scores.append(score) | ||
114 | + return_class_names.append(predicted_class) | ||
115 | + | ||
116 | + return return_boxes, return_scores, return_class_names | ||
117 | + | ||
118 | + def close_session(self): | ||
119 | + self.sess.close() |
No preview for this file type
No preview for this file type
code/deep_sort_yolov4/yolo4/model.py
0 → 100644
This diff is collapsed. Click to expand it.
code/deep_sort_yolov4/yolo4/utils.py
0 → 100644
1 | +"""Miscellaneous utility functions.""" | ||
2 | + | ||
3 | +from functools import reduce | ||
4 | + | ||
5 | +from PIL import Image | ||
6 | +import numpy as np | ||
7 | +from matplotlib.colors import rgb_to_hsv, hsv_to_rgb | ||
8 | + | ||
9 | +def compose(*funcs): | ||
10 | + """Compose arbitrarily many functions, evaluated left to right. | ||
11 | + | ||
12 | + Reference: https://mathieularose.com/function-composition-in-python/ | ||
13 | + """ | ||
14 | + # return lambda x: reduce(lambda v, f: f(v), funcs, x) | ||
15 | + if funcs: | ||
16 | + return reduce(lambda f, g: lambda *a, **kw: g(f(*a, **kw)), funcs) | ||
17 | + else: | ||
18 | + raise ValueError('Composition of empty sequence not supported.') | ||
19 | + | ||
20 | +def letterbox_image(image, size): | ||
21 | + '''resize image with unchanged aspect ratio using padding''' | ||
22 | + iw, ih = image.size | ||
23 | + w, h = size | ||
24 | + scale = min(w/iw, h/ih) | ||
25 | + nw = int(iw*scale) | ||
26 | + nh = int(ih*scale) | ||
27 | + | ||
28 | + image = image.resize((nw,nh), Image.BICUBIC) | ||
29 | + new_image = Image.new('RGB', size, (128,128,128)) | ||
30 | + new_image.paste(image, ((w-nw)//2, (h-nh)//2)) | ||
31 | + return new_image | ||
32 | + | ||
33 | +def rand(a=0, b=1): | ||
34 | + return np.random.rand()*(b-a) + a | ||
35 | + | ||
36 | +def get_random_data(annotation_line, input_shape, random=True, max_boxes=100, jitter=.3, hue=.1, sat=1.5, val=1.5, proc_img=True): | ||
37 | + '''random preprocessing for real-time data augmentation''' | ||
38 | + line = annotation_line.split() | ||
39 | + image = Image.open(line[0]) | ||
40 | + iw, ih = image.size | ||
41 | + h, w = input_shape | ||
42 | + box = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]]) | ||
43 | + | ||
44 | + if not random: | ||
45 | + # resize image | ||
46 | + scale = min(w/iw, h/ih) | ||
47 | + nw = int(iw*scale) | ||
48 | + nh = int(ih*scale) | ||
49 | + dx = (w-nw)//2 | ||
50 | + dy = (h-nh)//2 | ||
51 | + image_data=0 | ||
52 | + if proc_img: | ||
53 | + image = image.resize((nw,nh), Image.BICUBIC) | ||
54 | + new_image = Image.new('RGB', (w,h), (128,128,128)) | ||
55 | + new_image.paste(image, (dx, dy)) | ||
56 | + image_data = np.array(new_image)/255. | ||
57 | + | ||
58 | + # correct boxes | ||
59 | + box_data = np.zeros((max_boxes,5)) | ||
60 | + if len(box)>0: | ||
61 | + np.random.shuffle(box) | ||
62 | + if len(box)>max_boxes: box = box[:max_boxes] | ||
63 | + box[:, [0,2]] = box[:, [0,2]]*scale + dx | ||
64 | + box[:, [1,3]] = box[:, [1,3]]*scale + dy | ||
65 | + box_data[:len(box)] = box | ||
66 | + | ||
67 | + return image_data, box_data | ||
68 | + | ||
69 | + # resize image | ||
70 | + new_ar = w/h * rand(1-jitter,1+jitter)/rand(1-jitter,1+jitter) | ||
71 | + scale = rand(.25, 2) | ||
72 | + if new_ar < 1: | ||
73 | + nh = int(scale*h) | ||
74 | + nw = int(nh*new_ar) | ||
75 | + else: | ||
76 | + nw = int(scale*w) | ||
77 | + nh = int(nw/new_ar) | ||
78 | + image = image.resize((nw,nh), Image.BICUBIC) | ||
79 | + | ||
80 | + # place image | ||
81 | + dx = int(rand(0, w-nw)) | ||
82 | + dy = int(rand(0, h-nh)) | ||
83 | + new_image = Image.new('RGB', (w,h), (128,128,128)) | ||
84 | + new_image.paste(image, (dx, dy)) | ||
85 | + image = new_image | ||
86 | + | ||
87 | + # flip image or not | ||
88 | + flip = rand()<.5 | ||
89 | + if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT) | ||
90 | + | ||
91 | + # distort image | ||
92 | + hue = rand(-hue, hue) | ||
93 | + sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat) | ||
94 | + val = rand(1, val) if rand()<.5 else 1/rand(1, val) | ||
95 | + x = rgb_to_hsv(np.array(image)/255.) | ||
96 | + x[..., 0] += hue | ||
97 | + x[..., 0][x[..., 0]>1] -= 1 | ||
98 | + x[..., 0][x[..., 0]<0] += 1 | ||
99 | + x[..., 1] *= sat | ||
100 | + x[..., 2] *= val | ||
101 | + x[x>1] = 1 | ||
102 | + x[x<0] = 0 | ||
103 | + image_data = hsv_to_rgb(x) # numpy array, 0 to 1 | ||
104 | + | ||
105 | + # correct boxes | ||
106 | + box_data = np.zeros((max_boxes,5)) | ||
107 | + if len(box)>0: | ||
108 | + np.random.shuffle(box) | ||
109 | + box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx | ||
110 | + box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy | ||
111 | + if flip: box[:, [0,2]] = w - box[:, [2,0]] | ||
112 | + box[:, 0:2][box[:, 0:2]<0] = 0 | ||
113 | + box[:, 2][box[:, 2]>w] = w | ||
114 | + box[:, 3][box[:, 3]>h] = h | ||
115 | + box_w = box[:, 2] - box[:, 0] | ||
116 | + box_h = box[:, 3] - box[:, 1] | ||
117 | + box = box[np.logical_and(box_w>1, box_h>1)] # discard invalid box | ||
118 | + if len(box)>max_boxes: box = box[:max_boxes] | ||
119 | + box_data[:len(box)] = box | ||
120 | + | ||
121 | + return image_data, box_data |
code/requirements.txt
0 → 100644
1 | +absl-py==0.9.0 | ||
2 | +asgiref==3.2.7 | ||
3 | +astor==0.8.1 | ||
4 | +astroid==2.3.3 | ||
5 | +cachetools==4.1.0 | ||
6 | +certifi==2020.4.5.1 | ||
7 | +chardet==3.0.4 | ||
8 | +colorama==0.4.3 | ||
9 | +cycler==0.10.0 | ||
10 | +Django==3.0.5 | ||
11 | +et-xmlfile==1.0.1 | ||
12 | +gast==0.2.2 | ||
13 | +google-auth==1.14.1 | ||
14 | +google-auth-oauthlib==0.4.1 | ||
15 | +google-pasta==0.2.0 | ||
16 | +grpcio==1.28.1 | ||
17 | +h5py==2.10.0 | ||
18 | +idna==2.9 | ||
19 | +image==1.5.31 | ||
20 | +isort==4.3.21 | ||
21 | +jdcal==1.4.1 | ||
22 | +joblib==0.14.1 | ||
23 | +Keras==2.3.1 | ||
24 | +Keras-Applications==1.0.8 | ||
25 | +Keras-Preprocessing==1.1.0 | ||
26 | +kiwisolver==1.2.0 | ||
27 | +lazy-object-proxy==1.4.3 | ||
28 | +Markdown==3.2.1 | ||
29 | +matplotlib==3.2.1 | ||
30 | +mccabe==0.6.1 | ||
31 | +numpy==1.18.3 | ||
32 | +oauthlib==3.1.0 | ||
33 | +opencv-python==4.2.0.34 | ||
34 | +openpyxl==3.0.3 | ||
35 | +opt-einsum==3.2.1 | ||
36 | +pandas==1.0.3 | ||
37 | +Pillow==7.1.1 | ||
38 | +protobuf==3.11.3 | ||
39 | +pyasn1==0.4.8 | ||
40 | +pyasn1-modules==0.2.8 | ||
41 | +pylint==2.4.4 | ||
42 | +pyparsing==2.4.7 | ||
43 | +python-dateutil==2.8.1 | ||
44 | +pytz==2019.3 | ||
45 | +PyYAML==5.3.1 | ||
46 | +requests==2.23.0 | ||
47 | +requests-oauthlib==1.3.0 | ||
48 | +rsa==4.0 | ||
49 | +scikit-learn==0.22.2.post1 | ||
50 | +scipy==1.4.1 | ||
51 | +seaborn==0.10.1 | ||
52 | +six==1.14.0 | ||
53 | +sklearn==0.0 | ||
54 | +sqlparse==0.3.1 | ||
55 | +tensorboard==1.15.0 | ||
56 | +tensorflow==1.15.2 | ||
57 | +tensorflow-estimator==1.15.1 | ||
58 | +tensorflow-gpu==1.15.2 | ||
59 | +tensorflow-gpu-estimator==2.1.0 | ||
60 | +termcolor==1.1.0 | ||
61 | +typed-ast==1.4.1 | ||
62 | +urllib3==1.25.9 | ||
63 | +Werkzeug==1.0.1 | ||
64 | +wrapt==1.11.2 | ||
65 | +xlrd==1.2.0 |
... | @@ -6,12 +6,14 @@ | ... | @@ -6,12 +6,14 @@ |
6 | "start": "node ./bin/www" | 6 | "start": "node ./bin/www" |
7 | }, | 7 | }, |
8 | "dependencies": { | 8 | "dependencies": { |
9 | + "child_process": "^1.0.2", | ||
9 | "cookie-parser": "~1.4.4", | 10 | "cookie-parser": "~1.4.4", |
10 | "debug": "~2.6.9", | 11 | "debug": "~2.6.9", |
11 | "ejs": "~2.6.1", | 12 | "ejs": "~2.6.1", |
12 | "express": "~4.16.1", | 13 | "express": "~4.16.1", |
13 | "http-errors": "~1.6.3", | 14 | "http-errors": "~1.6.3", |
14 | "morgan": "~1.9.1", | 15 | "morgan": "~1.9.1", |
15 | - "multer": "^1.4.2" | 16 | + "multer": "^1.4.2", |
17 | + "python-shell": "^2.0.1" | ||
16 | } | 18 | } |
17 | } | 19 | } | ... | ... |
code/web/public/data/test_kmeans.png
0 → 100644
338 KB
... | @@ -2,10 +2,13 @@ var express = require('express'); | ... | @@ -2,10 +2,13 @@ var express = require('express'); |
2 | var router = express.Router(); | 2 | var router = express.Router(); |
3 | var multer = require("multer"); | 3 | var multer = require("multer"); |
4 | var path = require("path"); | 4 | var path = require("path"); |
5 | +var PythonShell = require('python-shell'); | ||
6 | +var spawn = require("child_process").spawn; | ||
7 | + | ||
5 | 8 | ||
6 | var storage = multer.diskStorage({ | 9 | var storage = multer.diskStorage({ |
7 | destination: function(req, file, callback) { | 10 | destination: function(req, file, callback) { |
8 | - callback(null, "upload/") | 11 | + callback(null, "../deep_sort_yolov4/") |
9 | }, | 12 | }, |
10 | filename: function(req, file, callback) { | 13 | filename: function(req, file, callback) { |
11 | var extension = path.extname(file.originalname); | 14 | var extension = path.extname(file.originalname); |
... | @@ -21,20 +24,33 @@ var upload = multer({ | ... | @@ -21,20 +24,33 @@ var upload = multer({ |
21 | 24 | ||
22 | // 뷰 페이지 경로 | 25 | // 뷰 페이지 경로 |
23 | router.get('/', function(req, res, next) { | 26 | router.get('/', function(req, res, next) { |
24 | - res.render("index") | 27 | + res.render('index', { title: 'Express' }); |
25 | }); | 28 | }); |
26 | 29 | ||
27 | // 2. 파일 업로드 처리 | 30 | // 2. 파일 업로드 처리 |
28 | -router.post('/create', upload.single("File"), async(req, res) => { | 31 | +router.post('/create', upload.single("File"), function(req, res) { |
29 | // 3. 파일 객체 | 32 | // 3. 파일 객체 |
30 | - var file = req.file | 33 | + var file = req.file; |
31 | - | 34 | + var options = { |
32 | - // 4. 파일 정보 | 35 | + mode: 'text', |
33 | - var result = { | 36 | + pythonPath: __dirname + '/../../venv/Scripts/python.exe', |
34 | - originalName: file.originalname, | 37 | + pythonOptions: ['-u'], |
35 | - size: file.size, | 38 | + scriptPath: __dirname + '/../../deep_sort_yolov4/', |
39 | + args: [file.originalname] | ||
40 | + }; | ||
41 | + var shell1 = new PythonShell.PythonShell('main.py', options); | ||
42 | + shell1.end(function(err) { | ||
43 | + if (err) throw err; | ||
44 | + else { | ||
45 | + res.render('result', { file_name: file.originalname.split('.')[0] }); | ||
36 | } | 46 | } |
37 | - | 47 | + }); |
38 | - res.json(result); | 48 | + // PythonShell.PythonShell.run('main.py', options, (err, results) => { |
49 | + // if (err) throw err; | ||
50 | + // PythonShell.PythonShell.run('kmean.py', options2, (err, results) => { | ||
51 | + // if (err) throw err; | ||
52 | + // res.render('result', { file_name: file.originalname.split('.')[0] }); | ||
53 | + // }); | ||
54 | + // }); | ||
39 | }); | 55 | }); |
40 | module.exports = router; | 56 | module.exports = router; |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
... | @@ -32,7 +32,8 @@ | ... | @@ -32,7 +32,8 @@ |
32 | <div class="sidebar-brand-icon rotate-n-15"> | 32 | <div class="sidebar-brand-icon rotate-n-15"> |
33 | <i class="fas fa-thumbs-up"></i> | 33 | <i class="fas fa-thumbs-up"></i> |
34 | </div> | 34 | </div> |
35 | - <div class="sidebar-brand-text mx-3">유동 인구 분석</div> | 35 | + <div class="sidebar-brand-text mx-3">유동 인구 분석 |
36 | + </div> | ||
36 | </a> | 37 | </a> |
37 | 38 | ||
38 | <!-- Divider --> | 39 | <!-- Divider --> | ... | ... |
... | @@ -86,10 +86,10 @@ | ... | @@ -86,10 +86,10 @@ |
86 | <!-- /.container-fluid --> | 86 | <!-- /.container-fluid --> |
87 | <div class="container row"> | 87 | <div class="container row"> |
88 | <div class="container-video col-md-6"> | 88 | <div class="container-video col-md-6"> |
89 | - <video src="data/output2.avi" width="400" controls autoplay></video> | 89 | + <video src="data/<%=file_name%>.mp4" width="400" controls autoplay></video> |
90 | </div> | 90 | </div> |
91 | <div class="container-kmeans col-md-6"> | 91 | <div class="container-kmeans col-md-6"> |
92 | - <img src="data/test3_kmeans.png" style="width: 100%;" alt="Kmeans Image"> | 92 | + <img src="data/<%=file_name%>_kmeans.png" style="width: 100%;" alt="Kmeans Image"> |
93 | </div> | 93 | </div> |
94 | </div> | 94 | </div> |
95 | <div class="container-kibana"></div> | 95 | <div class="container-kibana"></div> |
... | @@ -127,9 +127,6 @@ | ... | @@ -127,9 +127,6 @@ |
127 | <!-- Custom scripts for all pages--> | 127 | <!-- Custom scripts for all pages--> |
128 | <script src="javascripts/sb-admin-2.min.js"></script> | 128 | <script src="javascripts/sb-admin-2.min.js"></script> |
129 | 129 | ||
130 | - <!-- Page level plugins --> | ||
131 | - <script src="vendor/chart.js/Chart.min.js"></script> | ||
132 | - | ||
133 | <!-- Page level custom scripts --> | 130 | <!-- Page level custom scripts --> |
134 | <!-- <script src="javascripts/demo/chart-pie-demo.js"></script> --> | 131 | <!-- <script src="javascripts/demo/chart-pie-demo.js"></script> --> |
135 | </body> | 132 | </body> | ... | ... |
-
Please register or login to post a comment