Hong

addyolo

Showing 58 changed files with 2128 additions and 20 deletions
1 code/web/package-lock.json 1 code/web/package-lock.json
2 -code/web/node_modules/**
...\ No newline at end of file ...\ No newline at end of file
2 +code/web/node_modules/**
3 +/code/venv
4 +/code/deep_sort_yolov4/model_data/yolov4.weights
5 +/code/deep_sort_yolov4/model_data/yolo4_weight.h5
...\ No newline at end of file ...\ No newline at end of file
......
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 +
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
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
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
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
This diff could not be displayed because it is too large.
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.
This file is too large to display.
1 +12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401
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
This diff is collapsed. Click to expand it.
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
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
1 +Keras==2.3.1
2 +tensorflow-gpu==1.13.1
3 +numpy==1.15.0
4 +opencv-python==4.2.0.34
5 +scikit-learn==0.23.1
6 +scipy==1.4.1
7 +Pillow
8 +torch==1.4.0
9 +torchvision==0.5.0
This file is too large to display.
This file is too large to display.
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')
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()
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()
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()
This diff is collapsed. Click to expand it.
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
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 }
......
...@@ -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/',
36 - } 39 + args: [file.originalname]
37 - 40 + };
38 - res.json(result); 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] });
46 + }
47 + });
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>
......