├── Procfile
├── deep_sort_pytorch
├── utils
│ ├── __init__.py
│ ├── asserts.py
│ ├── log.py
│ ├── tools.py
│ ├── parser.py
│ ├── draw.py
│ ├── evaluation.py
│ ├── io.py
│ └── json_logger.py
├── deep_sort
│ ├── deep
│ │ ├── __init__.py
│ │ ├── checkpoint
│ │ │ └── .gitkeep
│ │ ├── train.jpg
│ │ ├── evaluate.py
│ │ ├── feature_extractor.py
│ │ ├── test.py
│ │ ├── model.py
│ │ ├── original_model.py
│ │ └── train.py
│ ├── sort
│ │ ├── __init__.py
│ │ ├── detection.py
│ │ ├── preprocessing.py
│ │ ├── iou_matching.py
│ │ ├── track.py
│ │ ├── tracker.py
│ │ ├── nn_matching.py
│ │ ├── linear_assignment.py
│ │ └── kalman_filter.py
│ ├── README.md
│ ├── __init__.py
│ └── deep_sort.py
├── .gitignore
├── configs
│ └── deep_sort.yaml
├── LICENSE
└── README.md
├── .idea
├── .gitignore
├── vcs.xml
├── inspectionProfiles
│ └── profiles_settings.xml
├── modules.xml
├── misc.xml
└── vehicle_project.iml
├── my_video.mp4
├── README.md
├── requirements.txt
├── app.py
├── templates
└── index.html
├── .gitignore
└── vehicle_monitoring.py
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn application:app
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/checkpoint/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/my_video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nimarta-tejwani/TrafficInsight/HEAD/my_video.mp4
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/README.md:
--------------------------------------------------------------------------------
1 | # Deep Sort
2 |
3 | This is the implemention of deep sort with pytorch.
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/train.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nimarta-tejwani/TrafficInsight/HEAD/deep_sort_pytorch/deep_sort/deep/train.jpg
--------------------------------------------------------------------------------
/deep_sort_pytorch/.gitignore:
--------------------------------------------------------------------------------
1 | # Folders
2 | __pycache__/
3 | build/
4 | *.egg-info
5 |
6 |
7 | # Files
8 | *.weights
9 | *.t7
10 | *.mp4
11 | *.avi
12 | *.so
13 | *.txt
14 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/configs/deep_sort.yaml:
--------------------------------------------------------------------------------
1 | DEEPSORT:
2 | REID_CKPT: "deep_sort_pytorch/deep_sort/deep/checkpoint/ckpt.t7"
3 | MAX_DIST: 0.2
4 | MIN_CONFIDENCE: 0.3
5 | NMS_MAX_OVERLAP: 0.5
6 | MAX_IOU_DISTANCE: 0.7
7 | MAX_AGE: 70
8 | N_INIT: 3
9 | NN_BUDGET: 100
10 |
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/evaluate.py:
--------------------------------------------------------------------------------
1 | import torch
2 |
3 | features = torch.load("features.pth")
4 | qf = features["qf"]
5 | ql = features["ql"]
6 | gf = features["gf"]
7 | gl = features["gl"]
8 |
9 | scores = qf.mm(gf.t())
10 | res = scores.topk(5, dim=1)[1][:, 0]
11 | top1correct = gl[res].eq(ql).sum().item()
12 |
13 | print("Acc top1:{:.3f}".format(top1correct / ql.size(0)))
14 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/asserts.py:
--------------------------------------------------------------------------------
1 | from os import environ
2 |
3 |
4 | def assert_in(file, files_to_check):
5 | if file not in files_to_check:
6 | raise AssertionError("{} does not exist in the list".format(str(file)))
7 | return True
8 |
9 |
10 | def assert_in_env(check_list: list):
11 | for item in check_list:
12 | assert_in(item, environ.keys())
13 | return True
14 |
--------------------------------------------------------------------------------
/.idea/vehicle_project.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TrafficInsight
2 | using YOLOv8, DeepSort, OpenV and Flask
3 |
4 |
5 | ## How to run:
6 | Step 1:
7 | clone/download the repository the repository
8 |
9 | Step 2:
10 | Download yOLOv8.pt file [here](https://github.com/ultralytics/assets/releases/download/v0.0.0/yolov8n.pt) and place it in root directory
11 |
12 | Step 2:
13 | Open the terminal in the root directory
14 |
15 | Step 4:
16 | ````
17 | pip install -r requirements.txt
18 | ````
19 | Step 5:
20 | ````
21 | python app.py
22 | ````
23 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/log.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 |
4 | def get_logger(name='root'):
5 | formatter = logging.Formatter(
6 | # fmt='%(asctime)s [%(levelname)s]: %(filename)s(%(funcName)s:%(lineno)s) >> %(message)s')
7 | fmt='%(asctime)s [%(levelname)s]: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
8 |
9 | handler = logging.StreamHandler()
10 | handler.setFormatter(formatter)
11 |
12 | logger = logging.getLogger(name)
13 | logger.setLevel(logging.INFO)
14 | logger.addHandler(handler)
15 | return logger
16 |
17 |
18 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/__init__.py:
--------------------------------------------------------------------------------
1 | from .deep_sort import DeepSort
2 |
3 |
4 | __all__ = ['DeepSort', 'build_tracker']
5 |
6 |
7 | def build_tracker(cfg, use_cuda):
8 | return DeepSort(cfg.DEEPSORT.REID_CKPT,
9 | max_dist=cfg.DEEPSORT.MAX_DIST, min_confidence=cfg.DEEPSORT.MIN_CONFIDENCE,
10 | nms_max_overlap=cfg.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE,
11 | max_age=cfg.DEEPSORT.MAX_AGE, n_init=cfg.DEEPSORT.N_INIT, nn_budget=cfg.DEEPSORT.NN_BUDGET, use_cuda=use_cuda)
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/tools.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 | from time import time
3 |
4 |
5 | def is_video(ext: str):
6 | """
7 | Returns true if ext exists in
8 | allowed_exts for video files.
9 |
10 | Args:
11 | ext:
12 |
13 | Returns:
14 |
15 | """
16 |
17 | allowed_exts = ('.mp4', '.webm', '.ogg', '.avi', '.wmv', '.mkv', '.3gp')
18 | return any((ext.endswith(x) for x in allowed_exts))
19 |
20 |
21 | def tik_tok(func):
22 | """
23 | keep track of time for each process.
24 | Args:
25 | func:
26 |
27 | Returns:
28 |
29 | """
30 | @wraps(func)
31 | def _time_it(*args, **kwargs):
32 | start = time()
33 | try:
34 | return func(*args, **kwargs)
35 | finally:
36 | end_ = time()
37 | print("time: {:.03f}s, fps: {:.03f}".format(end_ - start, 1 / (end_ - start)))
38 |
39 | return _time_it
40 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Ziqiang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/parser.py:
--------------------------------------------------------------------------------
1 | import os
2 | import yaml
3 | from easydict import EasyDict as edict
4 |
5 |
6 | class YamlParser(edict):
7 | """
8 | This is yaml parser based on EasyDict.
9 | """
10 |
11 | def __init__(self, cfg_dict=None, config_file=None):
12 | if cfg_dict is None:
13 | cfg_dict = {}
14 |
15 | if config_file is not None:
16 | assert(os.path.isfile(config_file))
17 | with open(config_file, 'r') as fo:
18 | yaml_ = yaml.load(fo.read(), Loader=yaml.FullLoader)
19 | cfg_dict.update(yaml_)
20 |
21 | super(YamlParser, self).__init__(cfg_dict)
22 |
23 | def merge_from_file(self, config_file):
24 | with open(config_file, 'r') as fo:
25 | yaml_ = yaml.load(fo.read(), Loader=yaml.FullLoader)
26 | self.update(yaml_)
27 |
28 | def merge_from_dict(self, config_dict):
29 | self.update(config_dict)
30 |
31 |
32 | def get_config(config_file=None):
33 | return YamlParser(config_file=config_file)
34 |
35 |
36 | if __name__ == "__main__":
37 | cfg = YamlParser(config_file="../configs/yolov3.yaml")
38 | cfg.merge_from_file("../configs/deep_sort.yaml")
39 |
40 | import ipdb
41 | ipdb.set_trace()
42 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/draw.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import cv2
3 |
4 | palette = (2 ** 11 - 1, 2 ** 15 - 1, 2 ** 20 - 1)
5 |
6 |
7 | def compute_color_for_labels(label):
8 | """
9 | Simple function that adds fixed color depending on the class
10 | """
11 | color = [int((p * (label ** 2 - label + 1)) % 255) for p in palette]
12 | return tuple(color)
13 |
14 |
15 | def draw_boxes(img, bbox, identities=None, offset=(0,0)):
16 | for i,box in enumerate(bbox):
17 | x1,y1,x2,y2 = [int(i) for i in box]
18 | x1 += offset[0]
19 | x2 += offset[0]
20 | y1 += offset[1]
21 | y2 += offset[1]
22 | # box text and bar
23 | id = int(identities[i]) if identities is not None else 0
24 | color = compute_color_for_labels(id)
25 | label = '{}{:d}'.format("", id)
26 | t_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_PLAIN, 2 , 2)[0]
27 | cv2.rectangle(img,(x1, y1),(x2,y2),color,3)
28 | cv2.rectangle(img,(x1, y1),(x1+t_size[0]+3,y1+t_size[1]+4), color,-1)
29 | cv2.putText(img,label,(x1,y1+t_size[1]+4), cv2.FONT_HERSHEY_PLAIN, 2, [255,255,255], 2)
30 | return img
31 |
32 |
33 |
34 | if __name__ == '__main__':
35 | for i in range(82):
36 | print(compute_color_for_labels(i))
37 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/detection.py:
--------------------------------------------------------------------------------
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, oid):
30 | self.tlwh = np.asarray(tlwh, dtype=float)
31 | self.confidence = float(confidence)
32 | self.feature = np.asarray(feature, dtype=np.float32)
33 | self.oid = oid
34 |
35 | def to_tlbr(self):
36 | """Convert bounding box to format `(min x, min y, max x, max y)`, i.e.,
37 | `(top left, bottom right)`.
38 | """
39 | ret = self.tlwh.copy()
40 | ret[2:] += ret[:2]
41 | return ret
42 |
43 | def to_xyah(self):
44 | """Convert bounding box to format `(center x, center y, aspect ratio,
45 | height)`, where the aspect ratio is `width / height`.
46 | """
47 | ret = self.tlwh.copy()
48 | ret[:2] += ret[2:] / 2
49 | ret[2] /= ret[3]
50 | return ret
51 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | absl-py==2.0.0
2 | antlr4-python3-runtime==4.9.3
3 | asttokens==2.4.0
4 | backcall==0.2.0
5 | blinker==1.6.2
6 | cachetools==5.3.1
7 | certifi==2023.7.22
8 | charset-normalizer==3.3.0
9 | click==8.1.7
10 | colorama==0.4.6
11 | contourpy==1.1.1
12 | cycler==0.12.0
13 | decorator==5.1.1
14 | easydict==1.10
15 | executing==2.0.0
16 | filelock==3.12.4
17 | Flask==3.0.0
18 | fonttools==4.43.0
19 | fsspec==2023.9.2
20 | gitdb==4.0.10
21 | GitPython==3.1.37
22 | google-auth==2.23.2
23 | google-auth-oauthlib==1.0.0
24 | grpcio==1.59.0
25 | hydra-core==1.3.2
26 | idna==3.4
27 | ipython==8.16.1
28 | itsdangerous==2.1.2
29 | jedi==0.19.1
30 | Jinja2==3.1.2
31 | kiwisolver==1.4.5
32 | Markdown==3.4.4
33 | MarkupSafe==2.1.3
34 | matplotlib==3.8.0
35 | matplotlib-inline==0.1.6
36 | mpmath==1.3.0
37 | networkx==3.1
38 | numpy==1.26.0
39 | oauthlib==3.2.2
40 | omegaconf==2.3.0
41 | opencv-python==4.8.1.78
42 | packaging==23.2
43 | pandas==2.1.1
44 | parso==0.8.3
45 | pickleshare==0.7.5
46 | Pillow==10.0.1
47 | prompt-toolkit==3.0.39
48 | protobuf==4.24.4
49 | psutil==5.9.5
50 | pure-eval==0.2.2
51 | py-cpuinfo==9.0.0
52 | pyasn1==0.5.0
53 | pyasn1-modules==0.3.0
54 | Pygments==2.16.1
55 | pyparsing==3.1.1
56 | python-dateutil==2.8.2
57 | pytz==2023.3.post1
58 | PyYAML==6.0.1
59 | requests==2.31.0
60 | requests-oauthlib==1.3.1
61 | rsa==4.9
62 | scipy==1.11.3
63 | seaborn==0.13.0
64 | six==1.16.0
65 | smmap==5.0.1
66 | stack-data==0.6.3
67 | sympy==1.12
68 | tensorboard==2.14.1
69 | tensorboard-data-server==0.7.1
70 | thop==0.1.1.post2209072238
71 | torch==2.1.0
72 | torchvision==0.16.0
73 | tqdm==4.66.1
74 | traitlets==5.11.2
75 | typing_extensions==4.8.0
76 | tzdata==2023.3
77 | ultralytics==8.0.0
78 | urllib3==2.0.6
79 | wcwidth==0.2.8
80 | Werkzeug==3.0.0
81 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/feature_extractor.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torchvision.transforms as transforms
3 | import numpy as np
4 | import cv2
5 | import logging
6 |
7 | from .model import Net
8 |
9 |
10 | class Extractor(object):
11 | def __init__(self, model_path, use_cuda=True):
12 | self.net = Net(reid=True)
13 | self.device = "cuda" if torch.cuda.is_available() and use_cuda else "cpu"
14 | state_dict = torch.load(model_path, map_location=torch.device(self.device))[
15 | 'net_dict']
16 | self.net.load_state_dict(state_dict)
17 | logger = logging.getLogger("root.tracker")
18 | logger.info("Loading weights from {}... Done!".format(model_path))
19 | self.net.to(self.device)
20 | self.size = (64, 128)
21 | self.norm = transforms.Compose([
22 | transforms.ToTensor(),
23 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
24 | ])
25 |
26 | def _preprocess(self, im_crops):
27 | """
28 | TODO:
29 | 1. to float with scale from 0 to 1
30 | 2. resize to (64, 128) as Market1501 dataset did
31 | 3. concatenate to a numpy array
32 | 3. to torch Tensor
33 | 4. normalize
34 | """
35 | def _resize(im, size):
36 | return cv2.resize(im.astype(np.float32)/255., size)
37 |
38 | im_batch = torch.cat([self.norm(_resize(im, self.size)).unsqueeze(
39 | 0) for im in im_crops], dim=0).float()
40 | return im_batch
41 |
42 | def __call__(self, im_crops):
43 | im_batch = self._preprocess(im_crops)
44 | with torch.no_grad():
45 | im_batch = im_batch.to(self.device)
46 | features = self.net(im_batch)
47 | return features.cpu().numpy()
48 |
49 |
50 | if __name__ == '__main__':
51 | img = cv2.imread("demo.jpg")[:, :, (2, 1, 0)]
52 | extr = Extractor("checkpoint/ckpt.t7")
53 | feature = extr(img)
54 | print(feature.shape)
55 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, render_template, request, redirect, url_for
2 | import os
3 | import pandas as pd
4 | from vehicle_monitoring import predict
5 | from flask import send_file
6 |
7 | app = Flask(__name__)
8 |
9 | # Global variable to track processing status
10 | processing = False
11 |
12 | @app.route('/')
13 | def index():
14 | return render_template('index.html')
15 |
16 |
17 | @app.route('/upload', methods=['POST'])
18 | def upload_video():
19 | if 'video' not in request.files:
20 | return redirect(url_for('index'))
21 |
22 | video = request.files['video']
23 | if video.filename == '':
24 | return redirect(url_for('index'))
25 |
26 | video.save("my_video.mp4")
27 |
28 | upload_message = "Upload Successful!"
29 | return render_template('index.html', upload_message=upload_message)
30 |
31 |
32 | @app.route('/process')
33 | def process_video():
34 | predict()
35 | message = "Video Processed Successfully!"
36 | table_html, csv_exists = display_output()
37 | return render_template('index.html', message=message, table_html=table_html, csv_exists=csv_exists)
38 |
39 |
40 | @app.route('/download', methods=['GET'])
41 | def download_result():
42 | # File name
43 | file_name = "output.csv"
44 |
45 | # Send the file as a response
46 | try:
47 | return send_file(file_name, as_attachment=True)
48 | except Exception as e:
49 | return str(e)
50 |
51 |
52 | def display_output():
53 | output_file_path = os.path.join(os.getcwd(), 'output.csv') # Path to the CSV file
54 | if os.path.exists(output_file_path):
55 | # Read the CSV file into a Pandas DataFrame
56 | df = pd.read_csv(output_file_path)
57 | # Convert the DataFrame to an HTML table
58 | table_html = df.to_html(classes='table table-bordered table-striped')
59 | return table_html, True # Pass True to indicate that the CSV file exists
60 | else:
61 | return "", False
62 |
63 |
64 | if __name__ == '__main__':
65 | app.run(debug=True)
66 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/preprocessing.py:
--------------------------------------------------------------------------------
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
74 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/test.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.backends.cudnn as cudnn
3 | import torchvision
4 |
5 | import argparse
6 | import os
7 |
8 | from model import Net
9 |
10 | parser = argparse.ArgumentParser(description="Train on market1501")
11 | parser.add_argument("--data-dir", default='data', type=str)
12 | parser.add_argument("--no-cuda", action="store_true")
13 | parser.add_argument("--gpu-id", default=0, type=int)
14 | args = parser.parse_args()
15 |
16 | # device
17 | device = "cuda:{}".format(
18 | args.gpu_id) if torch.cuda.is_available() and not args.no_cuda else "cpu"
19 | if torch.cuda.is_available() and not args.no_cuda:
20 | cudnn.benchmark = True
21 |
22 | # data loader
23 | root = args.data_dir
24 | query_dir = os.path.join(root, "query")
25 | gallery_dir = os.path.join(root, "gallery")
26 | transform = torchvision.transforms.Compose([
27 | torchvision.transforms.Resize((128, 64)),
28 | torchvision.transforms.ToTensor(),
29 | torchvision.transforms.Normalize(
30 | [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
31 | ])
32 | queryloader = torch.utils.data.DataLoader(
33 | torchvision.datasets.ImageFolder(query_dir, transform=transform),
34 | batch_size=64, shuffle=False
35 | )
36 | galleryloader = torch.utils.data.DataLoader(
37 | torchvision.datasets.ImageFolder(gallery_dir, transform=transform),
38 | batch_size=64, shuffle=False
39 | )
40 |
41 | # net definition
42 | net = Net(reid=True)
43 | assert os.path.isfile(
44 | "./checkpoint/ckpt.t7"), "Error: no checkpoint file found!"
45 | print('Loading from checkpoint/ckpt.t7')
46 | checkpoint = torch.load("./checkpoint/ckpt.t7")
47 | net_dict = checkpoint['net_dict']
48 | net.load_state_dict(net_dict, strict=False)
49 | net.eval()
50 | net.to(device)
51 |
52 | # compute features
53 | query_features = torch.tensor([]).float()
54 | query_labels = torch.tensor([]).long()
55 | gallery_features = torch.tensor([]).float()
56 | gallery_labels = torch.tensor([]).long()
57 |
58 | with torch.no_grad():
59 | for idx, (inputs, labels) in enumerate(queryloader):
60 | inputs = inputs.to(device)
61 | features = net(inputs).cpu()
62 | query_features = torch.cat((query_features, features), dim=0)
63 | query_labels = torch.cat((query_labels, labels))
64 |
65 | for idx, (inputs, labels) in enumerate(galleryloader):
66 | inputs = inputs.to(device)
67 | features = net(inputs).cpu()
68 | gallery_features = torch.cat((gallery_features, features), dim=0)
69 | gallery_labels = torch.cat((gallery_labels, labels))
70 |
71 | gallery_labels -= 2
72 |
73 | # save features
74 | features = {
75 | "qf": query_features,
76 | "ql": query_labels,
77 | "gf": gallery_features,
78 | "gl": gallery_labels
79 | }
80 | torch.save(features, "features.pth")
81 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | TrafficInsight
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
TrafficInsight
17 |
24 |
{{upload_message}}
25 |
26 |
29 |
30 |
31 |
32 |
The video is being processed...
33 |

34 |
35 |
36 |
37 |
{{message}}
38 |
39 |
40 |
41 |
48 |
49 |
50 |
51 |
52 | {{ table_html | safe }}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/iou_matching.py:
--------------------------------------------------------------------------------
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(
80 | [detections[i].tlwh for i in detection_indices])
81 | cost_matrix[row, :] = 1. - iou(bbox, candidates)
82 | return cost_matrix
83 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/model.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 |
6 | class BasicBlock(nn.Module):
7 | def __init__(self, c_in, c_out, is_downsample=False):
8 | super(BasicBlock, self).__init__()
9 | self.is_downsample = is_downsample
10 | if is_downsample:
11 | self.conv1 = nn.Conv2d(
12 | c_in, c_out, 3, stride=2, padding=1, bias=False)
13 | else:
14 | self.conv1 = nn.Conv2d(
15 | c_in, c_out, 3, stride=1, padding=1, bias=False)
16 | self.bn1 = nn.BatchNorm2d(c_out)
17 | self.relu = nn.ReLU(True)
18 | self.conv2 = nn.Conv2d(c_out, c_out, 3, stride=1,
19 | padding=1, bias=False)
20 | self.bn2 = nn.BatchNorm2d(c_out)
21 | if is_downsample:
22 | self.downsample = nn.Sequential(
23 | nn.Conv2d(c_in, c_out, 1, stride=2, bias=False),
24 | nn.BatchNorm2d(c_out)
25 | )
26 | elif c_in != c_out:
27 | self.downsample = nn.Sequential(
28 | nn.Conv2d(c_in, c_out, 1, stride=1, bias=False),
29 | nn.BatchNorm2d(c_out)
30 | )
31 | self.is_downsample = True
32 |
33 | def forward(self, x):
34 | y = self.conv1(x)
35 | y = self.bn1(y)
36 | y = self.relu(y)
37 | y = self.conv2(y)
38 | y = self.bn2(y)
39 | if self.is_downsample:
40 | x = self.downsample(x)
41 | return F.relu(x.add(y), True)
42 |
43 |
44 | def make_layers(c_in, c_out, repeat_times, is_downsample=False):
45 | blocks = []
46 | for i in range(repeat_times):
47 | if i == 0:
48 | blocks += [BasicBlock(c_in, c_out, is_downsample=is_downsample), ]
49 | else:
50 | blocks += [BasicBlock(c_out, c_out), ]
51 | return nn.Sequential(*blocks)
52 |
53 |
54 | class Net(nn.Module):
55 | def __init__(self, num_classes=751, reid=False):
56 | super(Net, self).__init__()
57 | # 3 128 64
58 | self.conv = nn.Sequential(
59 | nn.Conv2d(3, 64, 3, stride=1, padding=1),
60 | nn.BatchNorm2d(64),
61 | nn.ReLU(inplace=True),
62 | # nn.Conv2d(32,32,3,stride=1,padding=1),
63 | # nn.BatchNorm2d(32),
64 | # nn.ReLU(inplace=True),
65 | nn.MaxPool2d(3, 2, padding=1),
66 | )
67 | # 32 64 32
68 | self.layer1 = make_layers(64, 64, 2, False)
69 | # 32 64 32
70 | self.layer2 = make_layers(64, 128, 2, True)
71 | # 64 32 16
72 | self.layer3 = make_layers(128, 256, 2, True)
73 | # 128 16 8
74 | self.layer4 = make_layers(256, 512, 2, True)
75 | # 256 8 4
76 | self.avgpool = nn.AvgPool2d((8, 4), 1)
77 | # 256 1 1
78 | self.reid = reid
79 | self.classifier = nn.Sequential(
80 | nn.Linear(512, 256),
81 | nn.BatchNorm1d(256),
82 | nn.ReLU(inplace=True),
83 | nn.Dropout(),
84 | nn.Linear(256, num_classes),
85 | )
86 |
87 | def forward(self, x):
88 | x = self.conv(x)
89 | x = self.layer1(x)
90 | x = self.layer2(x)
91 | x = self.layer3(x)
92 | x = self.layer4(x)
93 | x = self.avgpool(x)
94 | x = x.view(x.size(0), -1)
95 | # B x 128
96 | if self.reid:
97 | x = x.div(x.norm(p=2, dim=1, keepdim=True))
98 | return x
99 | # classifier
100 | x = self.classifier(x)
101 | return x
102 |
103 |
104 | if __name__ == '__main__':
105 | net = Net()
106 | x = torch.randn(4, 3, 128, 64)
107 | y = net(x)
108 | import ipdb
109 | ipdb.set_trace()
110 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pip#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/original_model.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 |
6 | class BasicBlock(nn.Module):
7 | def __init__(self, c_in, c_out, is_downsample=False):
8 | super(BasicBlock, self).__init__()
9 | self.is_downsample = is_downsample
10 | if is_downsample:
11 | self.conv1 = nn.Conv2d(
12 | c_in, c_out, 3, stride=2, padding=1, bias=False)
13 | else:
14 | self.conv1 = nn.Conv2d(
15 | c_in, c_out, 3, stride=1, padding=1, bias=False)
16 | self.bn1 = nn.BatchNorm2d(c_out)
17 | self.relu = nn.ReLU(True)
18 | self.conv2 = nn.Conv2d(c_out, c_out, 3, stride=1,
19 | padding=1, bias=False)
20 | self.bn2 = nn.BatchNorm2d(c_out)
21 | if is_downsample:
22 | self.downsample = nn.Sequential(
23 | nn.Conv2d(c_in, c_out, 1, stride=2, bias=False),
24 | nn.BatchNorm2d(c_out)
25 | )
26 | elif c_in != c_out:
27 | self.downsample = nn.Sequential(
28 | nn.Conv2d(c_in, c_out, 1, stride=1, bias=False),
29 | nn.BatchNorm2d(c_out)
30 | )
31 | self.is_downsample = True
32 |
33 | def forward(self, x):
34 | y = self.conv1(x)
35 | y = self.bn1(y)
36 | y = self.relu(y)
37 | y = self.conv2(y)
38 | y = self.bn2(y)
39 | if self.is_downsample:
40 | x = self.downsample(x)
41 | return F.relu(x.add(y), True)
42 |
43 |
44 | def make_layers(c_in, c_out, repeat_times, is_downsample=False):
45 | blocks = []
46 | for i in range(repeat_times):
47 | if i == 0:
48 | blocks += [BasicBlock(c_in, c_out, is_downsample=is_downsample), ]
49 | else:
50 | blocks += [BasicBlock(c_out, c_out), ]
51 | return nn.Sequential(*blocks)
52 |
53 |
54 | class Net(nn.Module):
55 | def __init__(self, num_classes=625, reid=False):
56 | super(Net, self).__init__()
57 | # 3 128 64
58 | self.conv = nn.Sequential(
59 | nn.Conv2d(3, 32, 3, stride=1, padding=1),
60 | nn.BatchNorm2d(32),
61 | nn.ELU(inplace=True),
62 | nn.Conv2d(32, 32, 3, stride=1, padding=1),
63 | nn.BatchNorm2d(32),
64 | nn.ELU(inplace=True),
65 | nn.MaxPool2d(3, 2, padding=1),
66 | )
67 | # 32 64 32
68 | self.layer1 = make_layers(32, 32, 2, False)
69 | # 32 64 32
70 | self.layer2 = make_layers(32, 64, 2, True)
71 | # 64 32 16
72 | self.layer3 = make_layers(64, 128, 2, True)
73 | # 128 16 8
74 | self.dense = nn.Sequential(
75 | nn.Dropout(p=0.6),
76 | nn.Linear(128*16*8, 128),
77 | nn.BatchNorm1d(128),
78 | nn.ELU(inplace=True)
79 | )
80 | # 256 1 1
81 | self.reid = reid
82 | self.batch_norm = nn.BatchNorm1d(128)
83 | self.classifier = nn.Sequential(
84 | nn.Linear(128, num_classes),
85 | )
86 |
87 | def forward(self, x):
88 | x = self.conv(x)
89 | x = self.layer1(x)
90 | x = self.layer2(x)
91 | x = self.layer3(x)
92 |
93 | x = x.view(x.size(0), -1)
94 | if self.reid:
95 | x = self.dense[0](x)
96 | x = self.dense[1](x)
97 | x = x.div(x.norm(p=2, dim=1, keepdim=True))
98 | return x
99 | x = self.dense(x)
100 | # B x 128
101 | # classifier
102 | x = self.classifier(x)
103 | return x
104 |
105 |
106 | if __name__ == '__main__':
107 | net = Net(reid=True)
108 | x = torch.randn(4, 3, 128, 64)
109 | y = net(x)
110 | import ipdb
111 | ipdb.set_trace()
112 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/evaluation.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 | import copy
4 | import motmetrics as mm
5 | mm.lap.default_solver = 'lap'
6 | from utils.io import read_results, unzip_objs
7 |
8 |
9 | class Evaluator(object):
10 |
11 | def __init__(self, data_root, seq_name, data_type):
12 | self.data_root = data_root
13 | self.seq_name = seq_name
14 | self.data_type = data_type
15 |
16 | self.load_annotations()
17 | self.reset_accumulator()
18 |
19 | def load_annotations(self):
20 | assert self.data_type == 'mot'
21 |
22 | gt_filename = os.path.join(self.data_root, self.seq_name, 'gt', 'gt.txt')
23 | self.gt_frame_dict = read_results(gt_filename, self.data_type, is_gt=True)
24 | self.gt_ignore_frame_dict = read_results(gt_filename, self.data_type, is_ignore=True)
25 |
26 | def reset_accumulator(self):
27 | self.acc = mm.MOTAccumulator(auto_id=True)
28 |
29 | def eval_frame(self, frame_id, trk_tlwhs, trk_ids, rtn_events=False):
30 | # results
31 | trk_tlwhs = np.copy(trk_tlwhs)
32 | trk_ids = np.copy(trk_ids)
33 |
34 | # gts
35 | gt_objs = self.gt_frame_dict.get(frame_id, [])
36 | gt_tlwhs, gt_ids = unzip_objs(gt_objs)[:2]
37 |
38 | # ignore boxes
39 | ignore_objs = self.gt_ignore_frame_dict.get(frame_id, [])
40 | ignore_tlwhs = unzip_objs(ignore_objs)[0]
41 |
42 |
43 | # remove ignored results
44 | keep = np.ones(len(trk_tlwhs), dtype=bool)
45 | iou_distance = mm.distances.iou_matrix(ignore_tlwhs, trk_tlwhs, max_iou=0.5)
46 | if len(iou_distance) > 0:
47 | match_is, match_js = mm.lap.linear_sum_assignment(iou_distance)
48 | match_is, match_js = map(lambda a: np.asarray(a, dtype=int), [match_is, match_js])
49 | match_ious = iou_distance[match_is, match_js]
50 |
51 | match_js = np.asarray(match_js, dtype=int)
52 | match_js = match_js[np.logical_not(np.isnan(match_ious))]
53 | keep[match_js] = False
54 | trk_tlwhs = trk_tlwhs[keep]
55 | trk_ids = trk_ids[keep]
56 |
57 | # get distance matrix
58 | iou_distance = mm.distances.iou_matrix(gt_tlwhs, trk_tlwhs, max_iou=0.5)
59 |
60 | # acc
61 | self.acc.update(gt_ids, trk_ids, iou_distance)
62 |
63 | if rtn_events and iou_distance.size > 0 and hasattr(self.acc, 'last_mot_events'):
64 | events = self.acc.last_mot_events # only supported by https://github.com/longcw/py-motmetrics
65 | else:
66 | events = None
67 | return events
68 |
69 | def eval_file(self, filename):
70 | self.reset_accumulator()
71 |
72 | result_frame_dict = read_results(filename, self.data_type, is_gt=False)
73 | frames = sorted(list(set(self.gt_frame_dict.keys()) | set(result_frame_dict.keys())))
74 | for frame_id in frames:
75 | trk_objs = result_frame_dict.get(frame_id, [])
76 | trk_tlwhs, trk_ids = unzip_objs(trk_objs)[:2]
77 | self.eval_frame(frame_id, trk_tlwhs, trk_ids, rtn_events=False)
78 |
79 | return self.acc
80 |
81 | @staticmethod
82 | def get_summary(accs, names, metrics=('mota', 'num_switches', 'idp', 'idr', 'idf1', 'precision', 'recall')):
83 | names = copy.deepcopy(names)
84 | if metrics is None:
85 | metrics = mm.metrics.motchallenge_metrics
86 | metrics = copy.deepcopy(metrics)
87 |
88 | mh = mm.metrics.create()
89 | summary = mh.compute_many(
90 | accs,
91 | metrics=metrics,
92 | names=names,
93 | generate_overall=True
94 | )
95 |
96 | return summary
97 |
98 | @staticmethod
99 | def save_summary(summary, filename):
100 | import pandas as pd
101 | writer = pd.ExcelWriter(filename)
102 | summary.to_excel(writer)
103 | writer.save()
104 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep_sort.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import torch
3 |
4 | from .deep.feature_extractor import Extractor
5 | from .sort.nn_matching import NearestNeighborDistanceMetric
6 | from .sort.detection import Detection
7 | from .sort.tracker import Tracker
8 |
9 |
10 | __all__ = ['DeepSort']
11 |
12 |
13 | class DeepSort(object):
14 | def __init__(self, model_path, max_dist=0.2, min_confidence=0.3, nms_max_overlap=1.0, max_iou_distance=0.7, max_age=70, n_init=3, nn_budget=100, use_cuda=True):
15 | self.min_confidence = min_confidence
16 | self.nms_max_overlap = nms_max_overlap
17 |
18 | self.extractor = Extractor(model_path, use_cuda=use_cuda)
19 |
20 | max_cosine_distance = max_dist
21 | metric = NearestNeighborDistanceMetric(
22 | "cosine", max_cosine_distance, nn_budget)
23 | self.tracker = Tracker(
24 | metric, max_iou_distance=max_iou_distance, max_age=max_age, n_init=n_init)
25 |
26 | def update(self, bbox_xywh, confidences, oids, ori_img):
27 | self.height, self.width = ori_img.shape[:2]
28 | # generate detections
29 | features = self._get_features(bbox_xywh, ori_img)
30 | bbox_tlwh = self._xywh_to_tlwh(bbox_xywh)
31 | detections = [Detection(bbox_tlwh[i], conf, features[i],oid) for i, (conf,oid) in enumerate(zip(confidences,oids)) if conf > self.min_confidence]
32 |
33 | # run on non-maximum supression
34 | boxes = np.array([d.tlwh for d in detections])
35 | scores = np.array([d.confidence for d in detections])
36 |
37 | # update tracker
38 | self.tracker.predict()
39 | self.tracker.update(detections)
40 |
41 | # output bbox identities
42 | outputs = []
43 | for track in self.tracker.tracks:
44 | if not track.is_confirmed() or track.time_since_update > 1:
45 | continue
46 | box = track.to_tlwh()
47 | x1, y1, x2, y2 = self._tlwh_to_xyxy(box)
48 | track_id = track.track_id
49 | track_oid = track.oid
50 | outputs.append(np.array([x1, y1, x2, y2, track_id, track_oid], dtype=int))
51 | if len(outputs) > 0:
52 | outputs = np.stack(outputs, axis=0)
53 | return outputs
54 |
55 | """
56 | TODO:
57 | Convert bbox from xc_yc_w_h to xtl_ytl_w_h
58 | Thanks JieChen91@github.com for reporting this bug!
59 | """
60 | @staticmethod
61 | def _xywh_to_tlwh(bbox_xywh):
62 | if isinstance(bbox_xywh, np.ndarray):
63 | bbox_tlwh = bbox_xywh.copy()
64 | elif isinstance(bbox_xywh, torch.Tensor):
65 | bbox_tlwh = bbox_xywh.clone()
66 | bbox_tlwh[:, 0] = bbox_xywh[:, 0] - bbox_xywh[:, 2] / 2.
67 | bbox_tlwh[:, 1] = bbox_xywh[:, 1] - bbox_xywh[:, 3] / 2.
68 | return bbox_tlwh
69 |
70 | def _xywh_to_xyxy(self, bbox_xywh):
71 | x, y, w, h = bbox_xywh
72 | x1 = max(int(x - w / 2), 0)
73 | x2 = min(int(x + w / 2), self.width - 1)
74 | y1 = max(int(y - h / 2), 0)
75 | y2 = min(int(y + h / 2), self.height - 1)
76 | return x1, y1, x2, y2
77 |
78 | def _tlwh_to_xyxy(self, bbox_tlwh):
79 | """
80 | TODO:
81 | Convert bbox from xtl_ytl_w_h to xc_yc_w_h
82 | Thanks JieChen91@github.com for reporting this bug!
83 | """
84 | x, y, w, h = bbox_tlwh
85 | x1 = max(int(x), 0)
86 | x2 = min(int(x+w), self.width - 1)
87 | y1 = max(int(y), 0)
88 | y2 = min(int(y+h), self.height - 1)
89 | return x1, y1, x2, y2
90 |
91 | def increment_ages(self):
92 | self.tracker.increment_ages()
93 |
94 | def _xyxy_to_tlwh(self, bbox_xyxy):
95 | x1, y1, x2, y2 = bbox_xyxy
96 |
97 | t = x1
98 | l = y1
99 | w = int(x2 - x1)
100 | h = int(y2 - y1)
101 | return t, l, w, h
102 |
103 | def _get_features(self, bbox_xywh, ori_img):
104 | im_crops = []
105 | for box in bbox_xywh:
106 | x1, y1, x2, y2 = self._xywh_to_xyxy(box)
107 | im = ori_img[y1:y2, x1:x2]
108 | im_crops.append(im)
109 | if im_crops:
110 | features = self.extractor(im_crops)
111 | else:
112 | features = np.array([])
113 | return features
114 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/io.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import Dict
3 | import numpy as np
4 |
5 | # from utils.log import get_logger
6 |
7 |
8 | def write_results(filename, results, data_type):
9 | if data_type == 'mot':
10 | save_format = '{frame},{id},{x1},{y1},{w},{h},-1,-1,-1,-1\n'
11 | elif data_type == 'kitti':
12 | save_format = '{frame} {id} pedestrian 0 0 -10 {x1} {y1} {x2} {y2} -10 -10 -10 -1000 -1000 -1000 -10\n'
13 | else:
14 | raise ValueError(data_type)
15 |
16 | with open(filename, 'w') as f:
17 | for frame_id, tlwhs, track_ids in results:
18 | if data_type == 'kitti':
19 | frame_id -= 1
20 | for tlwh, track_id in zip(tlwhs, track_ids):
21 | if track_id < 0:
22 | continue
23 | x1, y1, w, h = tlwh
24 | x2, y2 = x1 + w, y1 + h
25 | line = save_format.format(frame=frame_id, id=track_id, x1=x1, y1=y1, x2=x2, y2=y2, w=w, h=h)
26 | f.write(line)
27 |
28 |
29 | # def write_results(filename, results_dict: Dict, data_type: str):
30 | # if not filename:
31 | # return
32 | # path = os.path.dirname(filename)
33 | # if not os.path.exists(path):
34 | # os.makedirs(path)
35 |
36 | # if data_type in ('mot', 'mcmot', 'lab'):
37 | # save_format = '{frame},{id},{x1},{y1},{w},{h},1,-1,-1,-1\n'
38 | # elif data_type == 'kitti':
39 | # save_format = '{frame} {id} pedestrian -1 -1 -10 {x1} {y1} {x2} {y2} -1 -1 -1 -1000 -1000 -1000 -10 {score}\n'
40 | # else:
41 | # raise ValueError(data_type)
42 |
43 | # with open(filename, 'w') as f:
44 | # for frame_id, frame_data in results_dict.items():
45 | # if data_type == 'kitti':
46 | # frame_id -= 1
47 | # for tlwh, track_id in frame_data:
48 | # if track_id < 0:
49 | # continue
50 | # x1, y1, w, h = tlwh
51 | # x2, y2 = x1 + w, y1 + h
52 | # line = save_format.format(frame=frame_id, id=track_id, x1=x1, y1=y1, x2=x2, y2=y2, w=w, h=h, score=1.0)
53 | # f.write(line)
54 | # logger.info('Save results to {}'.format(filename))
55 |
56 |
57 | def read_results(filename, data_type: str, is_gt=False, is_ignore=False):
58 | if data_type in ('mot', 'lab'):
59 | read_fun = read_mot_results
60 | else:
61 | raise ValueError('Unknown data type: {}'.format(data_type))
62 |
63 | return read_fun(filename, is_gt, is_ignore)
64 |
65 |
66 | """
67 | labels={'ped', ... % 1
68 | 'person_on_vhcl', ... % 2
69 | 'car', ... % 3
70 | 'bicycle', ... % 4
71 | 'mbike', ... % 5
72 | 'non_mot_vhcl', ... % 6
73 | 'static_person', ... % 7
74 | 'distractor', ... % 8
75 | 'occluder', ... % 9
76 | 'occluder_on_grnd', ... %10
77 | 'occluder_full', ... % 11
78 | 'reflection', ... % 12
79 | 'crowd' ... % 13
80 | };
81 | """
82 |
83 |
84 | def read_mot_results(filename, is_gt, is_ignore):
85 | valid_labels = {1}
86 | ignore_labels = {2, 7, 8, 12}
87 | results_dict = dict()
88 | if os.path.isfile(filename):
89 | with open(filename, 'r') as f:
90 | for line in f.readlines():
91 | linelist = line.split(',')
92 | if len(linelist) < 7:
93 | continue
94 | fid = int(linelist[0])
95 | if fid < 1:
96 | continue
97 | results_dict.setdefault(fid, list())
98 |
99 | if is_gt:
100 | if 'MOT16-' in filename or 'MOT17-' in filename:
101 | label = int(float(linelist[7]))
102 | mark = int(float(linelist[6]))
103 | if mark == 0 or label not in valid_labels:
104 | continue
105 | score = 1
106 | elif is_ignore:
107 | if 'MOT16-' in filename or 'MOT17-' in filename:
108 | label = int(float(linelist[7]))
109 | vis_ratio = float(linelist[8])
110 | if label not in ignore_labels and vis_ratio >= 0:
111 | continue
112 | else:
113 | continue
114 | score = 1
115 | else:
116 | score = float(linelist[6])
117 |
118 | tlwh = tuple(map(float, linelist[2:6]))
119 | target_id = int(linelist[1])
120 |
121 | results_dict[fid].append((tlwh, target_id, score))
122 |
123 | return results_dict
124 |
125 |
126 | def unzip_objs(objs):
127 | if len(objs) > 0:
128 | tlwhs, ids, scores = zip(*objs)
129 | else:
130 | tlwhs, ids, scores = [], [], []
131 | tlwhs = np.asarray(tlwhs, dtype=float).reshape(-1, 4)
132 |
133 | return tlwhs, ids, scores
--------------------------------------------------------------------------------
/deep_sort_pytorch/README.md:
--------------------------------------------------------------------------------
1 | # Deep Sort with PyTorch
2 |
3 | 
4 |
5 | ## Update(1-1-2020)
6 | Changes
7 | - fix bugs
8 | - refactor code
9 | - accerate detection by adding nms on gpu
10 |
11 | ## Latest Update(07-22)
12 | Changes
13 | - bug fix (Thanks @JieChen91 and @yingsen1 for bug reporting).
14 | - using batch for feature extracting for each frame, which lead to a small speed up.
15 | - code improvement.
16 |
17 | Futher improvement direction
18 | - Train detector on specific dataset rather than the official one.
19 | - Retrain REID model on pedestrain dataset for better performance.
20 | - Replace YOLOv3 detector with advanced ones.
21 |
22 | **Any contributions to this repository is welcome!**
23 |
24 |
25 | ## Introduction
26 | This is an implement of MOT tracking algorithm deep sort. Deep sort is basicly the same with sort but added a CNN model to extract features in image of human part bounded by a detector. This CNN model is indeed a RE-ID model and the detector used in [PAPER](https://arxiv.org/abs/1703.07402) is FasterRCNN , and the original source code is [HERE](https://github.com/nwojke/deep_sort).
27 | However in original code, the CNN model is implemented with tensorflow, which I'm not familier with. SO I re-implemented the CNN feature extraction model with PyTorch, and changed the CNN model a little bit. Also, I use **YOLOv3** to generate bboxes instead of FasterRCNN.
28 |
29 | ## Dependencies
30 | - python 3 (python2 not sure)
31 | - numpy
32 | - scipy
33 | - opencv-python
34 | - sklearn
35 | - torch >= 0.4
36 | - torchvision >= 0.1
37 | - pillow
38 | - vizer
39 | - edict
40 |
41 | ## Quick Start
42 | 0. Check all dependencies installed
43 | ```bash
44 | pip install -r requirements.txt
45 | ```
46 | for user in china, you can specify pypi source to accelerate install like:
47 | ```bash
48 | pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
49 | ```
50 |
51 | 1. Clone this repository
52 | ```
53 | git clone git@github.com:ZQPei/deep_sort_pytorch.git
54 | ```
55 |
56 | 2. Download YOLOv3 parameters
57 | ```
58 | cd detector/YOLOv3/weight/
59 | wget https://pjreddie.com/media/files/yolov3.weights
60 | wget https://pjreddie.com/media/files/yolov3-tiny.weights
61 | cd ../../../
62 | ```
63 |
64 | 3. Download deepsort parameters ckpt.t7
65 | ```
66 | cd deep_sort/deep/checkpoint
67 | # download ckpt.t7 from
68 | https://drive.google.com/drive/folders/1xhG0kRH1EX5B9_Iz8gQJb7UNnn_riXi6 to this folder
69 | cd ../../../
70 | ```
71 |
72 | 4. Compile nms module
73 | ```bash
74 | cd detector/YOLOv3/nms
75 | sh build.sh
76 | cd ../../..
77 | ```
78 |
79 | Notice:
80 | If compiling failed, the simplist way is to **Upgrade your pytorch >= 1.1 and torchvision >= 0.3" and you can avoid the troublesome compiling problems which are most likely caused by either `gcc version too low` or `libraries missing`.
81 |
82 | 5. Run demo
83 | ```
84 | usage: python yolov3_deepsort.py VIDEO_PATH
85 | [--help]
86 | [--frame_interval FRAME_INTERVAL]
87 | [--config_detection CONFIG_DETECTION]
88 | [--config_deepsort CONFIG_DEEPSORT]
89 | [--display]
90 | [--display_width DISPLAY_WIDTH]
91 | [--display_height DISPLAY_HEIGHT]
92 | [--save_path SAVE_PATH]
93 | [--cpu]
94 |
95 | # yolov3 + deepsort
96 | python yolov3_deepsort.py [VIDEO_PATH]
97 |
98 | # yolov3_tiny + deepsort
99 | python yolov3_deepsort.py [VIDEO_PATH] --config_detection ./configs/yolov3_tiny.yaml
100 |
101 | # yolov3 + deepsort on webcam
102 | python3 yolov3_deepsort.py /dev/video0 --camera 0
103 |
104 | # yolov3_tiny + deepsort on webcam
105 | python3 yolov3_deepsort.py /dev/video0 --config_detection ./configs/yolov3_tiny.yaml --camera 0
106 | ```
107 | Use `--display` to enable display.
108 | Results will be saved to `./output/results.avi` and `./output/results.txt`.
109 |
110 | All files above can also be accessed from BaiduDisk!
111 | linker:[BaiduDisk](https://pan.baidu.com/s/1YJ1iPpdFTlUyLFoonYvozg)
112 | passwd:fbuw
113 |
114 | ## Training the RE-ID model
115 | The original model used in paper is in original_model.py, and its parameter here [original_ckpt.t7](https://drive.google.com/drive/folders/1xhG0kRH1EX5B9_Iz8gQJb7UNnn_riXi6).
116 |
117 | To train the model, first you need download [Market1501](http://www.liangzheng.com.cn/Project/project_reid.html) dataset or [Mars](http://www.liangzheng.com.cn/Project/project_mars.html) dataset.
118 |
119 | Then you can try [train.py](deep_sort/deep/train.py) to train your own parameter and evaluate it using [test.py](deep_sort/deep/test.py) and [evaluate.py](deep_sort/deep/evalute.py).
120 | 
121 |
122 | ## Demo videos and images
123 | [demo.avi](https://drive.google.com/drive/folders/1xhG0kRH1EX5B9_Iz8gQJb7UNnn_riXi6)
124 | [demo2.avi](https://drive.google.com/drive/folders/1xhG0kRH1EX5B9_Iz8gQJb7UNnn_riXi6)
125 |
126 | 
127 | 
128 |
129 |
130 | ## References
131 | - paper: [Simple Online and Realtime Tracking with a Deep Association Metric](https://arxiv.org/abs/1703.07402)
132 |
133 | - code: [nwojke/deep_sort](https://github.com/nwojke/deep_sort)
134 |
135 | - paper: [YOLOv3](https://pjreddie.com/media/files/papers/YOLOv3.pdf)
136 |
137 | - code: [Joseph Redmon/yolov3](https://pjreddie.com/darknet/yolo/)
138 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/track.py:
--------------------------------------------------------------------------------
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,oid,
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 | self.oid = oid
75 |
76 | self.state = TrackState.Tentative
77 | self.features = []
78 | if feature is not None:
79 | self.features.append(feature)
80 |
81 | self._n_init = n_init
82 | self._max_age = max_age
83 |
84 | def to_tlwh(self):
85 | """Get current position in bounding box format `(top left x, top left y,
86 | width, height)`.
87 |
88 | Returns
89 | -------
90 | ndarray
91 | The bounding box.
92 |
93 | """
94 | ret = self.mean[:4].copy()
95 | ret[2] *= ret[3]
96 | ret[:2] -= ret[2:] / 2
97 | return ret
98 |
99 | def to_tlbr(self):
100 | """Get current position in bounding box format `(min x, miny, max x,
101 | max y)`.
102 |
103 | Returns
104 | -------
105 | ndarray
106 | The bounding box.
107 |
108 | """
109 | ret = self.to_tlwh()
110 | ret[2:] = ret[:2] + ret[2:]
111 | return ret
112 |
113 | def increment_age(self):
114 | self.age += 1
115 | self.time_since_update += 1
116 |
117 | def predict(self, kf):
118 | """Propagate the state distribution to the current time step using a
119 | Kalman filter prediction step.
120 |
121 | Parameters
122 | ----------
123 | kf : kalman_filter.KalmanFilter
124 | The Kalman filter.
125 |
126 | """
127 | self.mean, self.covariance = kf.predict(self.mean, self.covariance)
128 | self.increment_age()
129 |
130 | def update(self, kf, detection):
131 | """Perform Kalman filter measurement update step and update the feature
132 | cache.
133 |
134 | Parameters
135 | ----------
136 | kf : kalman_filter.KalmanFilter
137 | The Kalman filter.
138 | detection : Detection
139 | The associated detection.
140 |
141 | """
142 | self.mean, self.covariance = kf.update(
143 | self.mean, self.covariance, detection.to_xyah())
144 | self.features.append(detection.feature)
145 |
146 | self.hits += 1
147 | self.time_since_update = 0
148 | if self.state == TrackState.Tentative and self.hits >= self._n_init:
149 | self.state = TrackState.Confirmed
150 |
151 | def mark_missed(self):
152 | """Mark this track as missed (no association at the current time step).
153 | """
154 | if self.state == TrackState.Tentative:
155 | self.state = TrackState.Deleted
156 | elif self.time_since_update > self._max_age:
157 | self.state = TrackState.Deleted
158 |
159 | def is_tentative(self):
160 | """Returns True if this track is tentative (unconfirmed).
161 | """
162 | return self.state == TrackState.Tentative
163 |
164 | def is_confirmed(self):
165 | """Returns True if this track is confirmed."""
166 | return self.state == TrackState.Confirmed
167 |
168 | def is_deleted(self):
169 | """Returns True if this track is dead and should be deleted."""
170 | return self.state == TrackState.Deleted
171 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/tracker.py:
--------------------------------------------------------------------------------
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=70, 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 increment_ages(self):
59 | for track in self.tracks:
60 | track.increment_age()
61 | track.mark_missed()
62 |
63 | def update(self, detections):
64 | """Perform measurement update and track management.
65 |
66 | Parameters
67 | ----------
68 | detections : List[deep_sort.detection.Detection]
69 | A list of detections at the current time step.
70 |
71 | """
72 | # Run matching cascade.
73 | matches, unmatched_tracks, unmatched_detections = \
74 | self._match(detections)
75 |
76 | # Update track set.
77 | for track_idx, detection_idx in matches:
78 | self.tracks[track_idx].update(
79 | self.kf, detections[detection_idx])
80 | for track_idx in unmatched_tracks:
81 | self.tracks[track_idx].mark_missed()
82 | for detection_idx in unmatched_detections:
83 | self._initiate_track(detections[detection_idx])
84 | self.tracks = [t for t in self.tracks if not t.is_deleted()]
85 |
86 | # Update distance metric.
87 | active_targets = [t.track_id for t in self.tracks if t.is_confirmed()]
88 | features, targets = [], []
89 | for track in self.tracks:
90 | if not track.is_confirmed():
91 | continue
92 | features += track.features
93 | targets += [track.track_id for _ in track.features]
94 | track.features = []
95 | self.metric.partial_fit(
96 | np.asarray(features), np.asarray(targets), active_targets)
97 |
98 | def _match(self, detections):
99 |
100 | def gated_metric(tracks, dets, track_indices, detection_indices):
101 | features = np.array([dets[i].feature for i in detection_indices])
102 | targets = np.array([tracks[i].track_id for i in track_indices])
103 | cost_matrix = self.metric.distance(features, targets)
104 | cost_matrix = linear_assignment.gate_cost_matrix(
105 | self.kf, cost_matrix, tracks, dets, track_indices,
106 | detection_indices)
107 |
108 | return cost_matrix
109 |
110 | # Split track set into confirmed and unconfirmed tracks.
111 | confirmed_tracks = [
112 | i for i, t in enumerate(self.tracks) if t.is_confirmed()]
113 | unconfirmed_tracks = [
114 | i for i, t in enumerate(self.tracks) if not t.is_confirmed()]
115 |
116 | # Associate confirmed tracks using appearance features.
117 | matches_a, unmatched_tracks_a, unmatched_detections = \
118 | linear_assignment.matching_cascade(
119 | gated_metric, self.metric.matching_threshold, self.max_age,
120 | self.tracks, detections, confirmed_tracks)
121 |
122 | # Associate remaining tracks together with unconfirmed tracks using IOU.
123 | iou_track_candidates = unconfirmed_tracks + [
124 | k for k in unmatched_tracks_a if
125 | self.tracks[k].time_since_update == 1]
126 | unmatched_tracks_a = [
127 | k for k in unmatched_tracks_a if
128 | self.tracks[k].time_since_update != 1]
129 | matches_b, unmatched_tracks_b, unmatched_detections = \
130 | linear_assignment.min_cost_matching(
131 | iou_matching.iou_cost, self.max_iou_distance, self.tracks,
132 | detections, iou_track_candidates, unmatched_detections)
133 |
134 | matches = matches_a + matches_b
135 | unmatched_tracks = list(set(unmatched_tracks_a + unmatched_tracks_b))
136 | return matches, unmatched_tracks, unmatched_detections
137 |
138 | def _initiate_track(self, detection):
139 | mean, covariance = self.kf.initiate(detection.to_xyah())
140 | self.tracks.append(Track(
141 | mean, covariance, self._next_id, self.n_init, self.max_age,detection.oid,
142 | detection.feature))
143 | self._next_id += 1
144 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/nn_matching.py:
--------------------------------------------------------------------------------
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 | if metric == "euclidean":
126 | self._metric = _nn_euclidean_distance
127 | elif metric == "cosine":
128 | self._metric = _nn_cosine_distance
129 | else:
130 | raise ValueError(
131 | "Invalid metric; must be either 'euclidean' or 'cosine'")
132 | self.matching_threshold = matching_threshold
133 | self.budget = budget
134 | self.samples = {}
135 |
136 | def partial_fit(self, features, targets, active_targets):
137 | """Update the distance metric with new data.
138 |
139 | Parameters
140 | ----------
141 | features : ndarray
142 | An NxM matrix of N features of dimensionality M.
143 | targets : ndarray
144 | An integer array of associated target identities.
145 | active_targets : List[int]
146 | A list of targets that are currently present in the scene.
147 |
148 | """
149 | for feature, target in zip(features, targets):
150 | self.samples.setdefault(target, []).append(feature)
151 | if self.budget is not None:
152 | self.samples[target] = self.samples[target][-self.budget:]
153 | self.samples = {k: self.samples[k] for k in active_targets}
154 |
155 | def distance(self, features, targets):
156 | """Compute distance between features and targets.
157 |
158 | Parameters
159 | ----------
160 | features : ndarray
161 | An NxM matrix of N features of dimensionality M.
162 | targets : List[int]
163 | A list of targets to match the given `features` against.
164 |
165 | Returns
166 | -------
167 | ndarray
168 | Returns a cost matrix of shape len(targets), len(features), where
169 | element (i, j) contains the closest squared distance between
170 | `targets[i]` and `features[j]`.
171 |
172 | """
173 | cost_matrix = np.zeros((len(targets), len(features)))
174 | for i, target in enumerate(targets):
175 | cost_matrix[i, :] = self._metric(self.samples[target], features)
176 | return cost_matrix
177 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/deep/train.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 | import time
4 |
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 | import torch
8 | import torch.backends.cudnn as cudnn
9 | import torchvision
10 |
11 | from model import Net
12 |
13 | parser = argparse.ArgumentParser(description="Train on market1501")
14 | parser.add_argument("--data-dir", default='data', type=str)
15 | parser.add_argument("--no-cuda", action="store_true")
16 | parser.add_argument("--gpu-id", default=0, type=int)
17 | parser.add_argument("--lr", default=0.1, type=float)
18 | parser.add_argument("--interval", '-i', default=20, type=int)
19 | parser.add_argument('--resume', '-r', action='store_true')
20 | args = parser.parse_args()
21 |
22 | # device
23 | device = "cuda:{}".format(
24 | args.gpu_id) if torch.cuda.is_available() and not args.no_cuda else "cpu"
25 | if torch.cuda.is_available() and not args.no_cuda:
26 | cudnn.benchmark = True
27 |
28 | # data loading
29 | root = args.data_dir
30 | train_dir = os.path.join(root, "train")
31 | test_dir = os.path.join(root, "test")
32 | transform_train = torchvision.transforms.Compose([
33 | torchvision.transforms.RandomCrop((128, 64), padding=4),
34 | torchvision.transforms.RandomHorizontalFlip(),
35 | torchvision.transforms.ToTensor(),
36 | torchvision.transforms.Normalize(
37 | [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
38 | ])
39 | transform_test = torchvision.transforms.Compose([
40 | torchvision.transforms.Resize((128, 64)),
41 | torchvision.transforms.ToTensor(),
42 | torchvision.transforms.Normalize(
43 | [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
44 | ])
45 | trainloader = torch.utils.data.DataLoader(
46 | torchvision.datasets.ImageFolder(train_dir, transform=transform_train),
47 | batch_size=64, shuffle=True
48 | )
49 | testloader = torch.utils.data.DataLoader(
50 | torchvision.datasets.ImageFolder(test_dir, transform=transform_test),
51 | batch_size=64, shuffle=True
52 | )
53 | num_classes = max(len(trainloader.dataset.classes),
54 | len(testloader.dataset.classes))
55 |
56 | # net definition
57 | start_epoch = 0
58 | net = Net(num_classes=num_classes)
59 | if args.resume:
60 | assert os.path.isfile(
61 | "./checkpoint/ckpt.t7"), "Error: no checkpoint file found!"
62 | print('Loading from checkpoint/ckpt.t7')
63 | checkpoint = torch.load("./checkpoint/ckpt.t7")
64 | # import ipdb; ipdb.set_trace()
65 | net_dict = checkpoint['net_dict']
66 | net.load_state_dict(net_dict)
67 | best_acc = checkpoint['acc']
68 | start_epoch = checkpoint['epoch']
69 | net.to(device)
70 |
71 | # loss and optimizer
72 | criterion = torch.nn.CrossEntropyLoss()
73 | optimizer = torch.optim.SGD(
74 | net.parameters(), args.lr, momentum=0.9, weight_decay=5e-4)
75 | best_acc = 0.
76 |
77 | # train function for each epoch
78 |
79 |
80 | def train(epoch):
81 | print("\nEpoch : %d" % (epoch+1))
82 | net.train()
83 | training_loss = 0.
84 | train_loss = 0.
85 | correct = 0
86 | total = 0
87 | interval = args.interval
88 | start = time.time()
89 | for idx, (inputs, labels) in enumerate(trainloader):
90 | # forward
91 | inputs, labels = inputs.to(device), labels.to(device)
92 | outputs = net(inputs)
93 | loss = criterion(outputs, labels)
94 |
95 | # backward
96 | optimizer.zero_grad()
97 | loss.backward()
98 | optimizer.step()
99 |
100 | # accumurating
101 | training_loss += loss.item()
102 | train_loss += loss.item()
103 | correct += outputs.max(dim=1)[1].eq(labels).sum().item()
104 | total += labels.size(0)
105 |
106 | # print
107 | if (idx+1) % interval == 0:
108 | end = time.time()
109 | print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format(
110 | 100.*(idx+1)/len(trainloader), end-start, training_loss /
111 | interval, correct, total, 100.*correct/total
112 | ))
113 | training_loss = 0.
114 | start = time.time()
115 |
116 | return train_loss/len(trainloader), 1. - correct/total
117 |
118 |
119 | def test(epoch):
120 | global best_acc
121 | net.eval()
122 | test_loss = 0.
123 | correct = 0
124 | total = 0
125 | start = time.time()
126 | with torch.no_grad():
127 | for idx, (inputs, labels) in enumerate(testloader):
128 | inputs, labels = inputs.to(device), labels.to(device)
129 | outputs = net(inputs)
130 | loss = criterion(outputs, labels)
131 |
132 | test_loss += loss.item()
133 | correct += outputs.max(dim=1)[1].eq(labels).sum().item()
134 | total += labels.size(0)
135 |
136 | print("Testing ...")
137 | end = time.time()
138 | print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format(
139 | 100.*(idx+1)/len(testloader), end-start, test_loss /
140 | len(testloader), correct, total, 100.*correct/total
141 | ))
142 |
143 | # saving checkpoint
144 | acc = 100.*correct/total
145 | if acc > best_acc:
146 | best_acc = acc
147 | print("Saving parameters to checkpoint/ckpt.t7")
148 | checkpoint = {
149 | 'net_dict': net.state_dict(),
150 | 'acc': acc,
151 | 'epoch': epoch,
152 | }
153 | if not os.path.isdir('checkpoint'):
154 | os.mkdir('checkpoint')
155 | torch.save(checkpoint, './checkpoint/ckpt.t7')
156 |
157 | return test_loss/len(testloader), 1. - correct/total
158 |
159 |
160 | # plot figure
161 | x_epoch = []
162 | record = {'train_loss': [], 'train_err': [], 'test_loss': [], 'test_err': []}
163 | fig = plt.figure()
164 | ax0 = fig.add_subplot(121, title="loss")
165 | ax1 = fig.add_subplot(122, title="top1err")
166 |
167 |
168 | def draw_curve(epoch, train_loss, train_err, test_loss, test_err):
169 | global record
170 | record['train_loss'].append(train_loss)
171 | record['train_err'].append(train_err)
172 | record['test_loss'].append(test_loss)
173 | record['test_err'].append(test_err)
174 |
175 | x_epoch.append(epoch)
176 | ax0.plot(x_epoch, record['train_loss'], 'bo-', label='train')
177 | ax0.plot(x_epoch, record['test_loss'], 'ro-', label='val')
178 | ax1.plot(x_epoch, record['train_err'], 'bo-', label='train')
179 | ax1.plot(x_epoch, record['test_err'], 'ro-', label='val')
180 | if epoch == 0:
181 | ax0.legend()
182 | ax1.legend()
183 | fig.savefig("train.jpg")
184 |
185 | # lr decay
186 |
187 |
188 | def lr_decay():
189 | global optimizer
190 | for params in optimizer.param_groups:
191 | params['lr'] *= 0.1
192 | lr = params['lr']
193 | print("Learning rate adjusted to {}".format(lr))
194 |
195 |
196 | def main():
197 | for epoch in range(start_epoch, start_epoch+40):
198 | train_loss, train_err = train(epoch)
199 | test_loss, test_err = test(epoch)
200 | draw_curve(epoch, train_loss, train_err, test_loss, test_err)
201 | if (epoch+1) % 20 == 0:
202 | lr_decay()
203 |
204 |
205 | if __name__ == '__main__':
206 | main()
207 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/linear_assignment.py:
--------------------------------------------------------------------------------
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 scipy.optimize import linear_sum_assignment as linear_assignment
6 | from . import kalman_filter
7 |
8 |
9 | INFTY_COST = 1e+5
10 |
11 |
12 | def min_cost_matching(
13 | distance_metric, max_distance, tracks, detections, track_indices=None,
14 | detection_indices=None):
15 | """Solve linear assignment problem.
16 |
17 | Parameters
18 | ----------
19 | distance_metric : Callable[List[Track], List[Detection], List[int], List[int]) -> ndarray
20 | The distance metric is given a list of tracks and detections as well as
21 | a list of N track indices and M detection indices. The metric should
22 | return the NxM dimensional cost matrix, where element (i, j) is the
23 | association cost between the i-th track in the given track indices and
24 | the j-th detection in the given detection_indices.
25 | max_distance : float
26 | Gating threshold. Associations with cost larger than this value are
27 | disregarded.
28 | tracks : List[track.Track]
29 | A list of predicted tracks at the current time step.
30 | detections : List[detection.Detection]
31 | A list of detections at the current time step.
32 | track_indices : List[int]
33 | List of track indices that maps rows in `cost_matrix` to tracks in
34 | `tracks` (see description above).
35 | detection_indices : List[int]
36 | List of detection indices that maps columns in `cost_matrix` to
37 | detections in `detections` (see description above).
38 |
39 | Returns
40 | -------
41 | (List[(int, int)], List[int], List[int])
42 | Returns a tuple with the following three entries:
43 | * A list of matched track and detection indices.
44 | * A list of unmatched track indices.
45 | * A list of unmatched detection indices.
46 |
47 | """
48 | if track_indices is None:
49 | track_indices = np.arange(len(tracks))
50 | if detection_indices is None:
51 | detection_indices = np.arange(len(detections))
52 |
53 | if len(detection_indices) == 0 or len(track_indices) == 0:
54 | return [], track_indices, detection_indices # Nothing to match.
55 |
56 | cost_matrix = distance_metric(
57 | tracks, detections, track_indices, detection_indices)
58 | cost_matrix[cost_matrix > max_distance] = max_distance + 1e-5
59 |
60 | row_indices, col_indices = linear_assignment(cost_matrix)
61 |
62 | matches, unmatched_tracks, unmatched_detections = [], [], []
63 | for col, detection_idx in enumerate(detection_indices):
64 | if col not in col_indices:
65 | unmatched_detections.append(detection_idx)
66 | for row, track_idx in enumerate(track_indices):
67 | if row not in row_indices:
68 | unmatched_tracks.append(track_idx)
69 | for row, col in zip(row_indices, col_indices):
70 | track_idx = track_indices[row]
71 | detection_idx = detection_indices[col]
72 | if cost_matrix[row, col] > max_distance:
73 | unmatched_tracks.append(track_idx)
74 | unmatched_detections.append(detection_idx)
75 | else:
76 | matches.append((track_idx, detection_idx))
77 | return matches, unmatched_tracks, unmatched_detections
78 |
79 |
80 | def matching_cascade(
81 | distance_metric, max_distance, cascade_depth, tracks, detections,
82 | track_indices=None, detection_indices=None):
83 | """Run matching cascade.
84 |
85 | Parameters
86 | ----------
87 | distance_metric : Callable[List[Track], List[Detection], List[int], List[int]) -> ndarray
88 | The distance metric is given a list of tracks and detections as well as
89 | a list of N track indices and M detection indices. The metric should
90 | return the NxM dimensional cost matrix, where element (i, j) is the
91 | association cost between the i-th track in the given track indices and
92 | the j-th detection in the given detection indices.
93 | max_distance : float
94 | Gating threshold. Associations with cost larger than this value are
95 | disregarded.
96 | cascade_depth: int
97 | The cascade depth, should be se to the maximum track age.
98 | tracks : List[track.Track]
99 | A list of predicted tracks at the current time step.
100 | detections : List[detection.Detection]
101 | A list of detections at the current time step.
102 | track_indices : Optional[List[int]]
103 | List of track indices that maps rows in `cost_matrix` to tracks in
104 | `tracks` (see description above). Defaults to all tracks.
105 | detection_indices : Optional[List[int]]
106 | List of detection indices that maps columns in `cost_matrix` to
107 | detections in `detections` (see description above). Defaults to all
108 | detections.
109 |
110 | Returns
111 | -------
112 | (List[(int, int)], List[int], List[int])
113 | Returns a tuple with the following three entries:
114 | * A list of matched track and detection indices.
115 | * A list of unmatched track indices.
116 | * A list of unmatched detection indices.
117 |
118 | """
119 | if track_indices is None:
120 | track_indices = list(range(len(tracks)))
121 | if detection_indices is None:
122 | detection_indices = list(range(len(detections)))
123 |
124 | unmatched_detections = detection_indices
125 | matches = []
126 | for level in range(cascade_depth):
127 | if len(unmatched_detections) == 0: # No detections left
128 | break
129 |
130 | track_indices_l = [
131 | k for k in track_indices
132 | if tracks[k].time_since_update == 1 + level
133 | ]
134 | if len(track_indices_l) == 0: # Nothing to match at this level
135 | continue
136 |
137 | matches_l, _, unmatched_detections = \
138 | min_cost_matching(
139 | distance_metric, max_distance, tracks, detections,
140 | track_indices_l, unmatched_detections)
141 | matches += matches_l
142 | unmatched_tracks = list(set(track_indices) - set(k for k, _ in matches))
143 | return matches, unmatched_tracks, unmatched_detections
144 |
145 |
146 | def gate_cost_matrix(
147 | kf, cost_matrix, tracks, detections, track_indices, detection_indices,
148 | gated_cost=INFTY_COST, only_position=False):
149 | """Invalidate infeasible entries in cost matrix based on the state
150 | distributions obtained by Kalman filtering.
151 |
152 | Parameters
153 | ----------
154 | kf : The Kalman filter.
155 | cost_matrix : ndarray
156 | The NxM dimensional cost matrix, where N is the number of track indices
157 | and M is the number of detection indices, such that entry (i, j) is the
158 | association cost between `tracks[track_indices[i]]` and
159 | `detections[detection_indices[j]]`.
160 | tracks : List[track.Track]
161 | A list of predicted tracks at the current time step.
162 | detections : List[detection.Detection]
163 | A list of detections at the current time step.
164 | track_indices : List[int]
165 | List of track indices that maps rows in `cost_matrix` to tracks in
166 | `tracks` (see description above).
167 | detection_indices : List[int]
168 | List of detection indices that maps columns in `cost_matrix` to
169 | detections in `detections` (see description above).
170 | gated_cost : Optional[float]
171 | Entries in the cost matrix corresponding to infeasible associations are
172 | set this value. Defaults to a very large value.
173 | only_position : Optional[bool]
174 | If True, only the x, y position of the state distribution is considered
175 | during gating. Defaults to False.
176 |
177 | Returns
178 | -------
179 | ndarray
180 | Returns the modified cost matrix.
181 |
182 | """
183 | gating_dim = 2 if only_position else 4
184 | gating_threshold = kalman_filter.chi2inv95[gating_dim]
185 | measurements = np.asarray(
186 | [detections[i].to_xyah() for i in detection_indices])
187 | for row, track_idx in enumerate(track_indices):
188 | track = tracks[track_idx]
189 | gating_distance = kf.gating_distance(
190 | track.mean, track.covariance, measurements, only_position)
191 | cost_matrix[row, gating_distance > gating_threshold] = gated_cost
192 | return cost_matrix
193 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/deep_sort/sort/kalman_filter.py:
--------------------------------------------------------------------------------
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
230 |
--------------------------------------------------------------------------------
/deep_sort_pytorch/utils/json_logger.py:
--------------------------------------------------------------------------------
1 | """
2 | References:
3 | https://medium.com/analytics-vidhya/creating-a-custom-logging-mechanism-for-real-time-object-detection-using-tdd-4ca2cfcd0a2f
4 | """
5 | import json
6 | from os import makedirs
7 | from os.path import exists, join
8 | from datetime import datetime
9 |
10 |
11 | class JsonMeta(object):
12 | HOURS = 3
13 | MINUTES = 59
14 | SECONDS = 59
15 | PATH_TO_SAVE = 'LOGS'
16 | DEFAULT_FILE_NAME = 'remaining'
17 |
18 |
19 | class BaseJsonLogger(object):
20 | """
21 | This is the base class that returns __dict__ of its own
22 | it also returns the dicts of objects in the attributes that are list instances
23 |
24 | """
25 |
26 | def dic(self):
27 | # returns dicts of objects
28 | out = {}
29 | for k, v in self.__dict__.items():
30 | if hasattr(v, 'dic'):
31 | out[k] = v.dic()
32 | elif isinstance(v, list):
33 | out[k] = self.list(v)
34 | else:
35 | out[k] = v
36 | return out
37 |
38 | @staticmethod
39 | def list(values):
40 | # applies the dic method on items in the list
41 | return [v.dic() if hasattr(v, 'dic') else v for v in values]
42 |
43 |
44 | class Label(BaseJsonLogger):
45 | """
46 | For each bounding box there are various categories with confidences. Label class keeps track of that information.
47 | """
48 |
49 | def __init__(self, category: str, confidence: float):
50 | self.category = category
51 | self.confidence = confidence
52 |
53 |
54 | class Bbox(BaseJsonLogger):
55 | """
56 | This module stores the information for each frame and use them in JsonParser
57 | Attributes:
58 | labels (list): List of label module.
59 | top (int):
60 | left (int):
61 | width (int):
62 | height (int):
63 |
64 | Args:
65 | bbox_id (float):
66 | top (int):
67 | left (int):
68 | width (int):
69 | height (int):
70 |
71 | References:
72 | Check Label module for better understanding.
73 |
74 |
75 | """
76 |
77 | def __init__(self, bbox_id, top, left, width, height):
78 | self.labels = []
79 | self.bbox_id = bbox_id
80 | self.top = top
81 | self.left = left
82 | self.width = width
83 | self.height = height
84 |
85 | def add_label(self, category, confidence):
86 | # adds category and confidence only if top_k is not exceeded.
87 | self.labels.append(Label(category, confidence))
88 |
89 | def labels_full(self, value):
90 | return len(self.labels) == value
91 |
92 |
93 | class Frame(BaseJsonLogger):
94 | """
95 | This module stores the information for each frame and use them in JsonParser
96 | Attributes:
97 | timestamp (float): The elapsed time of captured frame
98 | frame_id (int): The frame number of the captured video
99 | bboxes (list of Bbox objects): Stores the list of bbox objects.
100 |
101 | References:
102 | Check Bbox class for better information
103 |
104 | Args:
105 | timestamp (float):
106 | frame_id (int):
107 |
108 | """
109 |
110 | def __init__(self, frame_id: int, timestamp: float = None):
111 | self.frame_id = frame_id
112 | self.timestamp = timestamp
113 | self.bboxes = []
114 |
115 | def add_bbox(self, bbox_id: int, top: int, left: int, width: int, height: int):
116 | bboxes_ids = [bbox.bbox_id for bbox in self.bboxes]
117 | if bbox_id not in bboxes_ids:
118 | self.bboxes.append(Bbox(bbox_id, top, left, width, height))
119 | else:
120 | raise ValueError("Frame with id: {} already has a Bbox with id: {}".format(self.frame_id, bbox_id))
121 |
122 | def add_label_to_bbox(self, bbox_id: int, category: str, confidence: float):
123 | bboxes = {bbox.id: bbox for bbox in self.bboxes}
124 | if bbox_id in bboxes.keys():
125 | res = bboxes.get(bbox_id)
126 | res.add_label(category, confidence)
127 | else:
128 | raise ValueError('the bbox with id: {} does not exists!'.format(bbox_id))
129 |
130 |
131 | class BboxToJsonLogger(BaseJsonLogger):
132 | """
133 | ُ This module is designed to automate the task of logging jsons. An example json is used
134 | to show the contents of json file shortly
135 | Example:
136 | {
137 | "video_details": {
138 | "frame_width": 1920,
139 | "frame_height": 1080,
140 | "frame_rate": 20,
141 | "video_name": "/home/gpu/codes/MSD/pedestrian_2/project/public/camera1.avi"
142 | },
143 | "frames": [
144 | {
145 | "frame_id": 329,
146 | "timestamp": 3365.1254
147 | "bboxes": [
148 | {
149 | "labels": [
150 | {
151 | "category": "pedestrian",
152 | "confidence": 0.9
153 | }
154 | ],
155 | "bbox_id": 0,
156 | "top": 1257,
157 | "left": 138,
158 | "width": 68,
159 | "height": 109
160 | }
161 | ]
162 | }],
163 |
164 | Attributes:
165 | frames (dict): It's a dictionary that maps each frame_id to json attributes.
166 | video_details (dict): information about video file.
167 | top_k_labels (int): shows the allowed number of labels
168 | start_time (datetime object): we use it to automate the json output by time.
169 |
170 | Args:
171 | top_k_labels (int): shows the allowed number of labels
172 |
173 | """
174 |
175 | def __init__(self, top_k_labels: int = 1):
176 | self.frames = {}
177 | self.video_details = self.video_details = dict(frame_width=None, frame_height=None, frame_rate=None,
178 | video_name=None)
179 | self.top_k_labels = top_k_labels
180 | self.start_time = datetime.now()
181 |
182 | def set_top_k(self, value):
183 | self.top_k_labels = value
184 |
185 | def frame_exists(self, frame_id: int) -> bool:
186 | """
187 | Args:
188 | frame_id (int):
189 |
190 | Returns:
191 | bool: true if frame_id is recognized
192 | """
193 | return frame_id in self.frames.keys()
194 |
195 | def add_frame(self, frame_id: int, timestamp: float = None) -> None:
196 | """
197 | Args:
198 | frame_id (int):
199 | timestamp (float): opencv captured frame time property
200 |
201 | Raises:
202 | ValueError: if frame_id would not exist in class frames attribute
203 |
204 | Returns:
205 | None
206 |
207 | """
208 | if not self.frame_exists(frame_id):
209 | self.frames[frame_id] = Frame(frame_id, timestamp)
210 | else:
211 | raise ValueError("Frame id: {} already exists".format(frame_id))
212 |
213 | def bbox_exists(self, frame_id: int, bbox_id: int) -> bool:
214 | """
215 | Args:
216 | frame_id:
217 | bbox_id:
218 |
219 | Returns:
220 | bool: if bbox exists in frame bboxes list
221 | """
222 | bboxes = []
223 | if self.frame_exists(frame_id=frame_id):
224 | bboxes = [bbox.bbox_id for bbox in self.frames[frame_id].bboxes]
225 | return bbox_id in bboxes
226 |
227 | def find_bbox(self, frame_id: int, bbox_id: int):
228 | """
229 |
230 | Args:
231 | frame_id:
232 | bbox_id:
233 |
234 | Returns:
235 | bbox_id (int):
236 |
237 | Raises:
238 | ValueError: if bbox_id does not exist in the bbox list of specific frame.
239 | """
240 | if not self.bbox_exists(frame_id, bbox_id):
241 | raise ValueError("frame with id: {} does not contain bbox with id: {}".format(frame_id, bbox_id))
242 | bboxes = {bbox.bbox_id: bbox for bbox in self.frames[frame_id].bboxes}
243 | return bboxes.get(bbox_id)
244 |
245 | def add_bbox_to_frame(self, frame_id: int, bbox_id: int, top: int, left: int, width: int, height: int) -> None:
246 | """
247 |
248 | Args:
249 | frame_id (int):
250 | bbox_id (int):
251 | top (int):
252 | left (int):
253 | width (int):
254 | height (int):
255 |
256 | Returns:
257 | None
258 |
259 | Raises:
260 | ValueError: if bbox_id already exist in frame information with frame_id
261 | ValueError: if frame_id does not exist in frames attribute
262 | """
263 | if self.frame_exists(frame_id):
264 | frame = self.frames[frame_id]
265 | if not self.bbox_exists(frame_id, bbox_id):
266 | frame.add_bbox(bbox_id, top, left, width, height)
267 | else:
268 | raise ValueError(
269 | "frame with frame_id: {} already contains the bbox with id: {} ".format(frame_id, bbox_id))
270 | else:
271 | raise ValueError("frame with frame_id: {} does not exist".format(frame_id))
272 |
273 | def add_label_to_bbox(self, frame_id: int, bbox_id: int, category: str, confidence: float):
274 | """
275 | Args:
276 | frame_id:
277 | bbox_id:
278 | category:
279 | confidence: the confidence value returned from yolo detection
280 |
281 | Returns:
282 | None
283 |
284 | Raises:
285 | ValueError: if labels quota (top_k_labels) exceeds.
286 | """
287 | bbox = self.find_bbox(frame_id, bbox_id)
288 | if not bbox.labels_full(self.top_k_labels):
289 | bbox.add_label(category, confidence)
290 | else:
291 | raise ValueError("labels in frame_id: {}, bbox_id: {} is fulled".format(frame_id, bbox_id))
292 |
293 | def add_video_details(self, frame_width: int = None, frame_height: int = None, frame_rate: int = None,
294 | video_name: str = None):
295 | self.video_details['frame_width'] = frame_width
296 | self.video_details['frame_height'] = frame_height
297 | self.video_details['frame_rate'] = frame_rate
298 | self.video_details['video_name'] = video_name
299 |
300 | def output(self):
301 | output = {'video_details': self.video_details}
302 | result = list(self.frames.values())
303 | output['frames'] = [item.dic() for item in result]
304 | return output
305 |
306 | def json_output(self, output_name):
307 | """
308 | Args:
309 | output_name:
310 |
311 | Returns:
312 | None
313 |
314 | Notes:
315 | It creates the json output with `output_name` name.
316 | """
317 | if not output_name.endswith('.json'):
318 | output_name += '.json'
319 | with open(output_name, 'w') as file:
320 | json.dump(self.output(), file)
321 | file.close()
322 |
323 | def set_start(self):
324 | self.start_time = datetime.now()
325 |
326 | def schedule_output_by_time(self, output_dir=JsonMeta.PATH_TO_SAVE, hours: int = 0, minutes: int = 0,
327 | seconds: int = 60) -> None:
328 | """
329 | Notes:
330 | Creates folder and then periodically stores the jsons on that address.
331 |
332 | Args:
333 | output_dir (str): the directory where output files will be stored
334 | hours (int):
335 | minutes (int):
336 | seconds (int):
337 |
338 | Returns:
339 | None
340 |
341 | """
342 | end = datetime.now()
343 | interval = 0
344 | interval += abs(min([hours, JsonMeta.HOURS]) * 3600)
345 | interval += abs(min([minutes, JsonMeta.MINUTES]) * 60)
346 | interval += abs(min([seconds, JsonMeta.SECONDS]))
347 | diff = (end - self.start_time).seconds
348 |
349 | if diff > interval:
350 | output_name = self.start_time.strftime('%Y-%m-%d %H-%M-%S') + '.json'
351 | if not exists(output_dir):
352 | makedirs(output_dir)
353 | output = join(output_dir, output_name)
354 | self.json_output(output_name=output)
355 | self.frames = {}
356 | self.start_time = datetime.now()
357 |
358 | def schedule_output_by_frames(self, frames_quota, frame_counter, output_dir=JsonMeta.PATH_TO_SAVE):
359 | """
360 | saves as the number of frames quota increases higher.
361 | :param frames_quota:
362 | :param frame_counter:
363 | :param output_dir:
364 | :return:
365 | """
366 | pass
367 |
368 | def flush(self, output_dir):
369 | """
370 | Notes:
371 | We use this function to output jsons whenever possible.
372 | like the time that we exit the while loop of opencv.
373 |
374 | Args:
375 | output_dir:
376 |
377 | Returns:
378 | None
379 |
380 | """
381 | filename = self.start_time.strftime('%Y-%m-%d %H-%M-%S') + '-remaining.json'
382 | output = join(output_dir, filename)
383 | self.json_output(output_name=output)
384 |
--------------------------------------------------------------------------------
/vehicle_monitoring.py:
--------------------------------------------------------------------------------
1 | # Ultralytics YOLO 🚀, GPL-3.0 license
2 |
3 | import hydra
4 | import datetime
5 | import torch
6 | import argparse
7 | import pandas as pd
8 | import datetime
9 | from pathlib import Path
10 | import math
11 | import cv2
12 | import torch
13 | import torch.backends.cudnn as cudnn
14 | from numpy import random
15 | # from ocr import extract
16 | from ultralytics.yolo.engine.predictor import BasePredictor
17 | from ultralytics.yolo.utils import DEFAULT_CONFIG, ROOT, ops
18 | from ultralytics.yolo.utils.checks import check_imgsz
19 | from ultralytics.yolo.utils.plotting import Annotator, colors, save_one_box
20 | import cv2
21 | from deep_sort_pytorch.utils.parser import get_config
22 | from deep_sort_pytorch.deep_sort import DeepSort
23 | from collections import deque
24 | import numpy as np
25 |
26 |
27 | palette = (2 ** 11 - 1, 2 ** 15 - 1, 2 ** 20 - 1)
28 | data_deque = {}
29 | deepsort = None
30 | line = [(100, 500), (1050, 500)]
31 | speed_line_queue = {}
32 |
33 | def estimatespeed(Location1, Location2):
34 | #Euclidean Distance Formula
35 | d_pixel = math.sqrt(math.pow(Location2[0] - Location1[0], 2) + math.pow(Location2[1] - Location1[1], 2))
36 | # defining thr pixels per meter
37 | ppm = 8
38 | d_meters = d_pixel/ppm
39 | time_constant = 15*3.6
40 | #distance = speed/time
41 | speed = d_meters * time_constant
42 | return int(speed)
43 |
44 | def init_tracker():
45 | global deepsort
46 | cfg_deep = get_config()
47 | cfg_deep.merge_from_file("deep_sort_pytorch/configs/deep_sort.yaml")
48 |
49 | deepsort= DeepSort(cfg_deep.DEEPSORT.REID_CKPT,
50 | max_dist=cfg_deep.DEEPSORT.MAX_DIST, min_confidence=cfg_deep.DEEPSORT.MIN_CONFIDENCE,
51 | nms_max_overlap=cfg_deep.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distance=cfg_deep.DEEPSORT.MAX_IOU_DISTANCE,
52 | max_age=cfg_deep.DEEPSORT.MAX_AGE, n_init=cfg_deep.DEEPSORT.N_INIT, nn_budget=cfg_deep.DEEPSORT.NN_BUDGET,
53 | use_cuda=True)
54 |
55 | def xyxy_to_xywh(*xyxy):
56 | """" Calculates the relative bounding box from absolute pixel values. """
57 | bbox_left = min([xyxy[0].item(), xyxy[2].item()])
58 | bbox_top = min([xyxy[1].item(), xyxy[3].item()])
59 | bbox_w = abs(xyxy[0].item() - xyxy[2].item())
60 | bbox_h = abs(xyxy[1].item() - xyxy[3].item())
61 | x_c = (bbox_left + bbox_w / 2)
62 | y_c = (bbox_top + bbox_h / 2)
63 | w = bbox_w
64 | h = bbox_h
65 | return x_c, y_c, w, h
66 |
67 | def xyxy_to_tlwh(bbox_xyxy):
68 | tlwh_bboxs = []
69 | for i, box in enumerate(bbox_xyxy):
70 | x1, y1, x2, y2 = [int(i) for i in box]
71 | top = x1
72 | left = y1
73 | w = int(x2 - x1)
74 | h = int(y2 - y1)
75 | tlwh_obj = [top, left, w, h]
76 | tlwh_bboxs.append(tlwh_obj)
77 | return tlwh_bboxs
78 |
79 | def compute_color_for_labels(label):
80 | """Simple function that adds fixed color depending on the class"""
81 | if label == 2: # Car
82 | color = (222,82,175)
83 | elif label == 3: # Motobike
84 | color = (0, 204, 255)
85 | elif label == 5: # Bus
86 | color = (0, 149, 255)
87 | else:
88 | color = [int((p * (label ** 2 - label + 1)) % 255) for p in palette]
89 | return tuple(color)
90 |
91 | def draw_border(img, pt1, pt2, color, thickness, r, d):
92 | x1,y1 = pt1
93 | x2,y2 = pt2
94 | # Top left
95 | cv2.line(img, (x1 + r, y1), (x1 + r + d, y1), color, thickness)
96 | cv2.line(img, (x1, y1 + r), (x1, y1 + r + d), color, thickness)
97 | cv2.ellipse(img, (x1 + r, y1 + r), (r, r), 180, 0, 90, color, thickness)
98 | # Top right
99 | cv2.line(img, (x2 - r, y1), (x2 - r - d, y1), color, thickness)
100 | cv2.line(img, (x2, y1 + r), (x2, y1 + r + d), color, thickness)
101 | cv2.ellipse(img, (x2 - r, y1 + r), (r, r), 270, 0, 90, color, thickness)
102 | # Bottom left
103 | cv2.line(img, (x1 + r, y2), (x1 + r + d, y2), color, thickness)
104 | cv2.line(img, (x1, y2 - r), (x1, y2 - r - d), color, thickness)
105 | cv2.ellipse(img, (x1 + r, y2 - r), (r, r), 90, 0, 90, color, thickness)
106 | # Bottom right
107 | cv2.line(img, (x2 - r, y2), (x2 - r - d, y2), color, thickness)
108 | cv2.line(img, (x2, y2 - r), (x2, y2 - r - d), color, thickness)
109 | cv2.ellipse(img, (x2 - r, y2 - r), (r, r), 0, 0, 90, color, thickness)
110 |
111 | cv2.rectangle(img, (x1 + r, y1), (x2 - r, y2), color, -1, cv2.LINE_AA)
112 | cv2.rectangle(img, (x1, y1 + r), (x2, y2 - r - d), color, -1, cv2.LINE_AA)
113 |
114 | cv2.circle(img, (x1 +r, y1+r), 2, color, 12)
115 | cv2.circle(img, (x2 -r, y1+r), 2, color, 12)
116 | cv2.circle(img, (x1 +r, y2-r), 2, color, 12)
117 | cv2.circle(img, (x2 -r, y2-r), 2, color, 12)
118 |
119 | return img
120 |
121 | def UI_box(x, img, color=None, label=None, line_thickness=None):
122 | # Plots one bounding box on image img
123 | tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness
124 | color = color or [random.randint(0, 255) for _ in range(3)]
125 | c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
126 | cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
127 | if label:
128 | tf = max(tl - 1, 1) # font thickness
129 | t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
130 |
131 | img = draw_border(img, (c1[0], c1[1] - t_size[1] -3), (c1[0] + t_size[0], c1[1]+3), color, 1, 8, 2)
132 |
133 | cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
134 |
135 |
136 | def intersect(A,B,C,D):
137 | return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)
138 |
139 | def ccw(A,B,C):
140 | return (C[1]-A[1]) * (B[0]-A[0]) > (B[1]-A[1]) * (C[0]-A[0])
141 |
142 | def get_direction(start_point, end_point):
143 | """
144 | Function to determine the direction based on the start and end points of a line.
145 | Assumes start_point and end_point are tuples of (x, y) coordinates.
146 | Returns the direction as a string.
147 | """
148 | x_diff = end_point[0] - start_point[0]
149 | y_diff = end_point[1] - start_point[1]
150 |
151 | if abs(x_diff) > abs(y_diff):
152 | if x_diff > 0:
153 | return "East"
154 | else:
155 | return "West"
156 | else:
157 | if y_diff > 0:
158 | return "South"
159 | else:
160 | return "North"
161 |
162 |
163 | def select_lines(img):
164 | """
165 | Function to allow the user to select four lines for the directions north, south, east, and west by clicking on the image
166 | """
167 | lines = []
168 | direction_names = ['north', 'south', 'east', 'west']
169 | current_line = []
170 |
171 | # Create window to display the image
172 | cv2.namedWindow("Select ROI", cv2.WINDOW_NORMAL)
173 | cv2.imshow("Select ROI", img)
174 |
175 | # Mouse callback function to get the starting and ending points of each line
176 | def select_line(event, x, y, flags, param):
177 | nonlocal lines, current_line # Declare 'lines' and 'current_line' as nonlocal to access the outer variables
178 | if event == cv2.EVENT_LBUTTONDOWN:
179 | if len(lines) < 4:
180 | if len(current_line) == 0:
181 | cv2.putText(img, direction_names[len(lines)], (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
182 | (255, 255, 255), 2)
183 | cv2.circle(img, (x, y), 5, (0, 0, 255), -1)
184 | current_line.append((x, y)) # Add the starting point of the line
185 | else:
186 | cv2.line(img, (current_line[-1][0], current_line[-1][1]), (x, y), (0, 0, 255), 2) # Connect the previous point with the current point
187 | cv2.imshow("Select ROI", img)
188 | current_line.append((x, y)) # Add the ending point of the line
189 |
190 | if len(current_line) == 2:
191 | lines.append((direction_names[len(lines)], current_line)) # Add the completed line to the lines list
192 | print(lines)
193 | print("current line", current_line)
194 | current_line = [] # Reset the current line
195 |
196 |
197 | # Set mouse callback function
198 | cv2.namedWindow("Select ROI")
199 | cv2.setMouseCallback("Select ROI", select_line)
200 |
201 | # Wait for the user to select four lines
202 | while len(lines) < 4:
203 | cv2.imshow("Select ROI", img)
204 | key = cv2.waitKey(1)
205 |
206 | # Check if the user presses the 'q' key to quit the selection process
207 | if key == ord('q'):
208 | break
209 |
210 | # Close the window
211 | cv2.destroyAllWindows()
212 |
213 | return lines
214 |
215 |
216 | def find_center_point(line):
217 | x1, y1 = line[0]
218 | x2, y2 = line[1]
219 | center_x = (x1 + x2) / 2
220 | center_y = (y1 + y2) / 2
221 | return int(center_x), int(center_y)
222 |
223 |
224 | vehicle_entered = []
225 | vehicle_entries = {}
226 |
227 | def draw_boxes(img, bbox, names, object_id, identities=None, offset=(0, 0)):
228 |
229 | if draw_boxes.frame_count == 0: # Call select_lines method only for the first frame
230 | lines = select_lines(img)
231 | draw_boxes.lines = lines
232 | draw_boxes.frame_count += 1
233 | else:
234 | lines = draw_boxes.lines # Use the lines from the previous call
235 | print("lines length", len(lines))
236 | print("out")
237 | # Draw lines
238 | for direction, line in lines:
239 | print("line points", (line[0][0], line[0][1]), (line[1][0], line[1][1]))
240 | cv2.line(img, (line[0][0], line[0][1]), (line[1][0], line[1][1]), (6, 2, 255), 3)
241 |
242 | cx, cy = find_center_point(line)
243 | print(cx, cy)
244 | cv2.putText(img, direction, (cx,cy), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
245 | (255, 255, 255), 2)
246 |
247 | # extracted_date = extract(img)
248 |
249 | height, width, _ = img.shape
250 | # remove tracked point from buffer if object is lost
251 | for key in list(data_deque):
252 | if key not in identities:
253 | data_deque.pop(key)
254 |
255 | for i, box in enumerate(bbox):
256 | x1, y1, x2, y2 = [int(i) for i in box]
257 | x1 += offset[0]
258 | x2 += offset[0]
259 | y1 += offset[1]
260 | y2 += offset[1]
261 |
262 | # code to find center of bottom edge
263 | center = (int((x2+x1)/ 2), int((y2+y2)/2))
264 |
265 | # get ID of object
266 | id = int(identities[i]) if identities is not None else 0
267 |
268 | # create new buffer for new object
269 | if id not in data_deque:
270 | print("id", id)
271 | data_deque[id] = deque(maxlen= 64)
272 | speed_line_queue[id] = []
273 | color = compute_color_for_labels(object_id[i])
274 | obj_name = names[object_id[i]]
275 | print('obj_name', obj_name)
276 |
277 | # if obj_name == 'car':
278 | # obj_name = 'car/taxi'
279 | # elif obj_name == 'bicycle':
280 | # obj_name = 'light vehicle'
281 | # elif obj_name == 'motorcycle':
282 | # obj_name = 'light vehicle'
283 | # elif obj_name=='truck':
284 | # obj_name='truck'
285 | # elif obj_name=='bus':
286 | # obj_name='bus'
287 |
288 | label = '{}{:d}'.format("", id) + ":"+ '%s' % (obj_name)
289 |
290 | # add center to buffer
291 | data_deque[id].appendleft(center)
292 | print(data_deque[id])
293 | if len(data_deque[id]) >= 2:
294 | direction = get_direction(data_deque[id][0], data_deque[id][1])
295 | object_speed = estimatespeed(data_deque[id][1], data_deque[id][0])
296 | speed_line_queue[id].append(object_speed)
297 |
298 | for d, line in lines:
299 | if intersect(data_deque[id][0], data_deque[id][1], line[0], line[1]):
300 | if id in vehicle_entries:
301 | v = vehicle_entries[id]
302 | v['Exit Point'] = d
303 | else:
304 | v = {
305 | 'ID': id,
306 | 'Type': obj_name,
307 | 'Entry Point': d,
308 | 'Exit Point': None,
309 | 'Entry Time': datetime.datetime.now()
310 | }
311 | vehicle_entries[id] = v
312 |
313 | print("Vehicle Entry Data:")
314 | for vehicle_id, vehicle_entry in vehicle_entries.items():
315 | print(vehicle_entry)
316 |
317 | UI_box(box, img, label=label, color=color, line_thickness=2)
318 | # draw trail
319 | for i in range(1, len(data_deque[id])):
320 | # check if on buffer value is none
321 | if data_deque[id][i - 1] is None or data_deque[id][i] is None:
322 | continue
323 | # generate dynamic thickness of trails
324 | thickness = int(np.sqrt(64 / float(i + i)) * 1.5)
325 | # draw trails
326 | cv2.line(img, data_deque[id][i - 1], data_deque[id][i], color, thickness)
327 |
328 | print("vehicles entries", vehicle_entries)
329 |
330 | df = pd.DataFrame.from_dict(vehicle_entries, orient='index')
331 | filename = "output.csv"
332 | df.to_csv(filename, index=False)
333 |
334 | return img
335 |
336 |
337 | # Initialize frame_count and lines as attributes of the draw_boxes function
338 | draw_boxes.frame_count = 0
339 | draw_boxes.lines = []
340 |
341 | class DetectionPredictor(BasePredictor):
342 |
343 | def get_annotator(self, img):
344 | return Annotator(img, line_width=self.args.line_thickness, example=str(self.model.names))
345 |
346 | def preprocess(self, img):
347 |
348 | img = torch.from_numpy(img).to(self.model.device)
349 | img = img.half() if self.model.fp16 else img.float() # uint8 to fp16/32
350 | img /= 255 # 0 - 255 to 0.0 - 1.0
351 | return img
352 |
353 | def postprocess(self, preds, img, orig_img):
354 |
355 | preds = ops.non_max_suppression(preds,
356 | self.args.conf,
357 | self.args.iou,
358 | agnostic=self.args.agnostic_nms,
359 | max_det=self.args.max_det)
360 |
361 | for i, pred in enumerate(preds):
362 | shape = orig_img[i].shape if self.webcam else orig_img.shape
363 | pred[:, :4] = ops.scale_boxes(img.shape[2:], pred[:, :4], shape).round()
364 |
365 | return preds
366 |
367 | def write_results(self, idx, preds, batch):
368 |
369 | p, im, im0 = batch
370 | all_outputs = []
371 | log_string = ""
372 | if len(im.shape) == 3:
373 | im = im[None] # expand for batch dim
374 | self.seen += 1
375 | im0 = im0.copy()
376 | if self.webcam: # batch_size >= 1
377 | log_string += f'{idx}: '
378 | frame = self.dataset.count
379 | else:
380 | frame = getattr(self.dataset, 'frame', 0)
381 |
382 | self.data_path = p
383 | save_path = str(self.save_dir / p.name) # im.jpg
384 | self.txt_path = str(self.save_dir / 'labels' / p.stem) + ('' if self.dataset.mode == 'image' else f'_{frame}')
385 | log_string += '%gx%g ' % im.shape[2:] # print string
386 | self.annotator = self.get_annotator(im0)
387 |
388 | det = preds[idx]
389 | all_outputs.append(det)
390 | if len(det) == 0:
391 | print("log string", log_string)
392 | return log_string
393 | for c in det[:, 5].unique():
394 | n = (det[:, 5] == c).sum() # detections per class
395 | log_string += f"{n} {self.model.names[int(c)]}{'s' * (n > 1)}, "
396 | # write
397 | gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
398 | xywh_bboxs = []
399 | confs = []
400 | oids = []
401 | outputs = []
402 | for *xyxy, conf, cls in reversed(det):
403 | x_c, y_c, bbox_w, bbox_h = xyxy_to_xywh(*xyxy)
404 | xywh_obj = [x_c, y_c, bbox_w, bbox_h]
405 | xywh_bboxs.append(xywh_obj)
406 | confs.append([conf.item()])
407 | oids.append(int(cls))
408 | xywhs = torch.Tensor(xywh_bboxs)
409 | confss = torch.Tensor(confs)
410 |
411 | outputs = deepsort.update(xywhs, confss, oids, im0)
412 | if len(outputs) > 0:
413 | bbox_xyxy = outputs[:, :4]
414 | identities = outputs[:, -2]
415 | object_id = outputs[:, -1]
416 |
417 | draw_boxes(im0, bbox_xyxy, self.model.names, object_id,identities)
418 |
419 | return log_string
420 |
421 |
422 | @hydra.main(version_base=None, config_path=str(DEFAULT_CONFIG.parent), config_name=DEFAULT_CONFIG.name)
423 | def predict(cfg):
424 | init_tracker()
425 | cfg.model = "yolov8n.pt"
426 | cfg.imgsz = check_imgsz(cfg.imgsz, min_dim=2) # check image size
427 | cfg.source = "my_video.mp4"
428 | predictor = DetectionPredictor(cfg)
429 | predictor()
430 |
--------------------------------------------------------------------------------