├── Dockerfile ├── LICENSE ├── MOT16-03.mp4 ├── README.md ├── __pycache__ ├── person_detect_yolov5.cpython-36.pyc └── person_detect_yolov5.cpython-38.pyc ├── configs ├── deep_sort.yaml └── simkai.ttf ├── deep_sort ├── README.md ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-37.pyc │ ├── __init__.cpython-38.pyc │ ├── deep_reid.cpython-36.pyc │ ├── deep_reid.cpython-38.pyc │ ├── deep_sort.cpython-36.pyc │ ├── deep_sort.cpython-37.pyc │ └── deep_sort.cpython-38.pyc ├── deep │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-36.pyc │ │ ├── __init__.cpython-37.pyc │ │ ├── __init__.cpython-38.pyc │ │ ├── feature_extractor.cpython-36.pyc │ │ ├── feature_extractor.cpython-37.pyc │ │ ├── feature_extractor.cpython-38.pyc │ │ ├── model.cpython-36.pyc │ │ ├── model.cpython-37.pyc │ │ └── model.cpython-38.pyc │ ├── checkpoint │ │ ├── .gitkeep │ │ ├── ckpt.t7 │ │ └── original_ckpt.t7 │ ├── evaluate.py │ ├── feature_extractor.py │ ├── model.py │ ├── original_model.py │ ├── test.py │ ├── train.jpg │ └── train.py ├── deep_reid.py ├── deep_sort.py └── sort │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-37.pyc │ ├── __init__.cpython-38.pyc │ ├── detection.cpython-36.pyc │ ├── detection.cpython-37.pyc │ ├── detection.cpython-38.pyc │ ├── iou_matching.cpython-36.pyc │ ├── iou_matching.cpython-37.pyc │ ├── iou_matching.cpython-38.pyc │ ├── kalman_filter.cpython-36.pyc │ ├── kalman_filter.cpython-37.pyc │ ├── kalman_filter.cpython-38.pyc │ ├── linear_assignment.cpython-36.pyc │ ├── linear_assignment.cpython-37.pyc │ ├── linear_assignment.cpython-38.pyc │ ├── nn_matching.cpython-36.pyc │ ├── nn_matching.cpython-37.pyc │ ├── nn_matching.cpython-38.pyc │ ├── preprocessing.cpython-36.pyc │ ├── preprocessing.cpython-37.pyc │ ├── preprocessing.cpython-38.pyc │ ├── track.cpython-36.pyc │ ├── track.cpython-37.pyc │ ├── track.cpython-38.pyc │ ├── tracker.cpython-36.pyc │ ├── tracker.cpython-37.pyc │ └── tracker.cpython-38.pyc │ ├── detection.py │ ├── iou_matching.py │ ├── kalman_filter.py │ ├── linear_assignment.py │ ├── nn_matching.py │ ├── preprocessing.py │ ├── track.py │ └── tracker.py ├── detect.py ├── hubconf.py ├── inference └── images │ ├── bus.jpg │ └── zidane.jpg ├── models ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-38.pyc │ ├── common.cpython-36.pyc │ ├── common.cpython-38.pyc │ ├── experimental.cpython-36.pyc │ ├── experimental.cpython-38.pyc │ ├── yolo.cpython-36.pyc │ └── yolo.cpython-38.pyc ├── common.py ├── experimental.py ├── export.py ├── hub │ ├── yolov3-spp.yaml │ ├── yolov5-fpn.yaml │ └── yolov5-panet.yaml ├── yolo.py ├── yolov5l.yaml ├── yolov5m.yaml ├── yolov5s.yaml └── yolov5x.yaml ├── person_count.py ├── person_detect_yolov5.py ├── requirements.txt ├── test.py ├── train.py ├── tutorial.ipynb ├── utils ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-38.pyc │ ├── datasets.cpython-36.pyc │ ├── datasets.cpython-38.pyc │ ├── draw.cpython-36.pyc │ ├── draw.cpython-38.pyc │ ├── general.cpython-36.pyc │ ├── general.cpython-38.pyc │ ├── google_utils.cpython-36.pyc │ ├── google_utils.cpython-38.pyc │ ├── log.cpython-36.pyc │ ├── log.cpython-38.pyc │ ├── parser.cpython-36.pyc │ ├── parser.cpython-38.pyc │ ├── torch_utils.cpython-36.pyc │ └── torch_utils.cpython-38.pyc ├── activations.py ├── datasets.py ├── draw.py ├── general.py ├── google_utils.py ├── log.py ├── parser.py └── torch_utils.py └── weights ├── download_weights.sh └── yolov5s.pt /Dockerfile: -------------------------------------------------------------------------------- 1 | # Start FROM Nvidia PyTorch image https://ngc.nvidia.com/catalog/containers/nvidia:pytorch 2 | FROM nvcr.io/nvidia/pytorch:20.03-py3 3 | 4 | # Install dependencies 5 | COPY requirements.txt . 6 | RUN pip install -r requirements.txt gsutil 7 | 8 | # Create working directory 9 | RUN mkdir -p /usr/src/app 10 | WORKDIR /usr/src/app 11 | 12 | # Copy contents 13 | COPY . /usr/src/app 14 | 15 | # Copy weights 16 | #RUN python3 -c "from models import *; \ 17 | #attempt_download('weights/yolov5s.pt'); \ 18 | #attempt_download('weights/yolov5m.pt'); \ 19 | #attempt_download('weights/yolov5l.pt')" 20 | 21 | 22 | # --------------------------------------------------- Extras Below --------------------------------------------------- 23 | 24 | # Build and Push 25 | # t=ultralytics/yolov5:latest && sudo docker build -t $t . && sudo docker push $t 26 | 27 | # Pull and Run 28 | # t=ultralytics/yolov5:latest && sudo docker pull $t && sudo docker run -it --ipc=host $t 29 | 30 | # Pull and Run with local directory access 31 | # t=ultralytics/yolov5:latest && sudo docker pull $t && sudo docker run -it --ipc=host --gpus all -v "$(pwd)"/coco:/usr/src/coco $t 32 | 33 | # Kill all 34 | # sudo docker kill "$(sudo docker ps -q)" 35 | 36 | # Kill all image-based 37 | # sudo docker kill $(sudo docker ps -a -q --filter ancestor=ultralytics/yolov5:latest) 38 | 39 | # Bash into running container 40 | # sudo docker container exec -it ba65811811ab bash 41 | 42 | # Bash into stopped container 43 | # sudo docker commit 092b16b25c5b usr/resume && sudo docker run -it --gpus all --ipc=host -v "$(pwd)"/coco:/usr/src/coco --entrypoint=sh usr/resume 44 | 45 | # Send weights to GCP 46 | # python -c "from utils.general import *; strip_optimizer('runs/exp0_*/weights/last.pt', 'tmp.pt')" && gsutil cp tmp.pt gs://* 47 | 48 | # Clean up 49 | # docker system prune -a --volumes 50 | -------------------------------------------------------------------------------- /MOT16-03.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/MOT16-03.mp4 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yolov5 + deepsort实现了行人计数功能, 统计摄像头内出现过的总人数,以及对穿越自定义黄线行人计数效果如下 2 | 3 | 运行 python person_count.py 4 | 5 | 详细见本人博客: https://blog.csdn.net/zengwubbb/article/details/113422048 123 6 | -------------------------------------------------------------------------------- /__pycache__/person_detect_yolov5.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/__pycache__/person_detect_yolov5.cpython-36.pyc -------------------------------------------------------------------------------- /__pycache__/person_detect_yolov5.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/__pycache__/person_detect_yolov5.cpython-38.pyc -------------------------------------------------------------------------------- /configs/deep_sort.yaml: -------------------------------------------------------------------------------- 1 | DEEPSORT: 2 | REID_CKPT: "./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 | -------------------------------------------------------------------------------- /configs/simkai.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/configs/simkai.ttf -------------------------------------------------------------------------------- /deep_sort/README.md: -------------------------------------------------------------------------------- 1 | # Deep Sort 2 | 3 | This is the implemention of deep sort with pytorch. -------------------------------------------------------------------------------- /deep_sort/__init__.py: -------------------------------------------------------------------------------- 1 | from .deep_sort import DeepSort 2 | # from .deep_reid import DeepReid 3 | 4 | 5 | __all__ = ['DeepSort', 'build_tracker', 'DeepReid'] 6 | 7 | 8 | def build_tracker(cfg, sort, use_cuda): 9 | if sort: 10 | return DeepSort(cfg.DEEPSORT.REID_CKPT, 11 | max_dist=cfg.DEEPSORT.MAX_DIST, min_confidence=cfg.DEEPSORT.MIN_CONFIDENCE, 12 | nms_max_overlap=cfg.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE, 13 | max_age=cfg.DEEPSORT.MAX_AGE, n_init=cfg.DEEPSORT.N_INIT, nn_budget=cfg.DEEPSORT.NN_BUDGET, use_cuda=use_cuda) 14 | # else: 15 | # return DeepReid(cfg.DEEPSORT.REID_CKPT, 16 | # max_dist=cfg.DEEPSORT.MAX_DIST, min_confidence=cfg.DEEPSORT.MIN_CONFIDENCE, 17 | # nms_max_overlap=cfg.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE, 18 | # max_age=cfg.DEEPSORT.MAX_AGE, n_init=cfg.DEEPSORT.N_INIT, nn_budget=cfg.DEEPSORT.NN_BUDGET, use_cuda=use_cuda) 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /deep_sort/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/__pycache__/deep_reid.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/__pycache__/deep_reid.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/__pycache__/deep_reid.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/__pycache__/deep_reid.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/__pycache__/deep_sort.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/__pycache__/deep_sort.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/__pycache__/deep_sort.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/__pycache__/deep_sort.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/__pycache__/deep_sort.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/__pycache__/deep_sort.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__init__.py -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/feature_extractor.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/feature_extractor.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/feature_extractor.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/feature_extractor.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/feature_extractor.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/feature_extractor.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/model.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/model.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/model.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/model.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/deep/__pycache__/model.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/__pycache__/model.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/deep/checkpoint/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/checkpoint/.gitkeep -------------------------------------------------------------------------------- /deep_sort/deep/checkpoint/ckpt.t7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/checkpoint/ckpt.t7 -------------------------------------------------------------------------------- /deep_sort/deep/checkpoint/original_ckpt.t7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/checkpoint/original_ckpt.t7 -------------------------------------------------------------------------------- /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 | 15 | 16 | -------------------------------------------------------------------------------- /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 | class Extractor(object): 10 | def __init__(self, model_path, use_cuda=True): 11 | self.net = Net(reid=True) 12 | self.device = "cuda" if torch.cuda.is_available() and use_cuda else "cpu" 13 | state_dict = torch.load(model_path, map_location=lambda storage, loc: storage)['net_dict'] 14 | self.net.load_state_dict(state_dict) 15 | logger = logging.getLogger("root.tracker") 16 | logger.info("Loading weights from {}... Done!".format(model_path)) 17 | self.net.to(self.device) 18 | self.size = (64, 128) 19 | self.norm = transforms.Compose([ 20 | transforms.ToTensor(), 21 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), 22 | ]) 23 | 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(0) for im in im_crops], dim=0).float() 39 | return im_batch 40 | 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 | 56 | -------------------------------------------------------------------------------- /deep_sort/deep/model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | class BasicBlock(nn.Module): 6 | def __init__(self, c_in, c_out,is_downsample=False): 7 | super(BasicBlock,self).__init__() 8 | self.is_downsample = is_downsample 9 | if is_downsample: 10 | self.conv1 = nn.Conv2d(c_in, c_out, 3, stride=2, padding=1, bias=False) 11 | else: 12 | self.conv1 = nn.Conv2d(c_in, c_out, 3, stride=1, padding=1, bias=False) 13 | self.bn1 = nn.BatchNorm2d(c_out) 14 | self.relu = nn.ReLU(True) 15 | self.conv2 = nn.Conv2d(c_out,c_out,3,stride=1,padding=1, bias=False) 16 | self.bn2 = nn.BatchNorm2d(c_out) 17 | if is_downsample: 18 | self.downsample = nn.Sequential( 19 | nn.Conv2d(c_in, c_out, 1, stride=2, bias=False), 20 | nn.BatchNorm2d(c_out) 21 | ) 22 | elif c_in != c_out: 23 | self.downsample = nn.Sequential( 24 | nn.Conv2d(c_in, c_out, 1, stride=1, bias=False), 25 | nn.BatchNorm2d(c_out) 26 | ) 27 | self.is_downsample = True 28 | 29 | def forward(self,x): 30 | y = self.conv1(x) 31 | y = self.bn1(y) 32 | y = self.relu(y) 33 | y = self.conv2(y) 34 | y = self.bn2(y) 35 | if self.is_downsample: 36 | x = self.downsample(x) 37 | return F.relu(x.add(y),True) 38 | 39 | def make_layers(c_in,c_out,repeat_times, is_downsample=False): 40 | blocks = [] 41 | for i in range(repeat_times): 42 | if i ==0: 43 | blocks += [BasicBlock(c_in,c_out, is_downsample=is_downsample),] 44 | else: 45 | blocks += [BasicBlock(c_out,c_out),] 46 | return nn.Sequential(*blocks) 47 | 48 | class Net(nn.Module): 49 | def __init__(self, num_classes=751 ,reid=False): 50 | super(Net,self).__init__() 51 | # 3 128 64 52 | self.conv = nn.Sequential( 53 | nn.Conv2d(3,64,3,stride=1,padding=1), 54 | nn.BatchNorm2d(64), 55 | nn.ReLU(inplace=True), 56 | # nn.Conv2d(32,32,3,stride=1,padding=1), 57 | # nn.BatchNorm2d(32), 58 | # nn.ReLU(inplace=True), 59 | nn.MaxPool2d(3,2,padding=1), 60 | ) 61 | # 32 64 32 62 | self.layer1 = make_layers(64,64,2,False) 63 | # 32 64 32 64 | self.layer2 = make_layers(64,128,2,True) 65 | # 64 32 16 66 | self.layer3 = make_layers(128,256,2,True) 67 | # 128 16 8 68 | self.layer4 = make_layers(256,512,2,True) 69 | # 256 8 4 70 | self.avgpool = nn.AvgPool2d((8,4),1) 71 | # 256 1 1 72 | self.reid = reid 73 | self.classifier = nn.Sequential( 74 | nn.Linear(512, 256), 75 | nn.BatchNorm1d(256), 76 | nn.ReLU(inplace=True), 77 | nn.Dropout(), 78 | nn.Linear(256, num_classes), 79 | ) 80 | 81 | def forward(self, x): 82 | x = self.conv(x) 83 | x = self.layer1(x) 84 | x = self.layer2(x) 85 | x = self.layer3(x) 86 | x = self.layer4(x) 87 | x = self.avgpool(x) 88 | x = x.view(x.size(0),-1) 89 | # B x 128 90 | if self.reid: 91 | x = x.div(x.norm(p=2,dim=1,keepdim=True)) 92 | return x 93 | # classifier 94 | x = self.classifier(x) 95 | return x 96 | 97 | 98 | if __name__ == '__main__': 99 | net = Net() 100 | x = torch.randn(4,3,128,64) 101 | y = net(x) 102 | import ipdb; ipdb.set_trace() 103 | 104 | 105 | -------------------------------------------------------------------------------- /deep_sort/deep/original_model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | class BasicBlock(nn.Module): 6 | def __init__(self, c_in, c_out,is_downsample=False): 7 | super(BasicBlock,self).__init__() 8 | self.is_downsample = is_downsample 9 | if is_downsample: 10 | self.conv1 = nn.Conv2d(c_in, c_out, 3, stride=2, padding=1, bias=False) 11 | else: 12 | self.conv1 = nn.Conv2d(c_in, c_out, 3, stride=1, padding=1, bias=False) 13 | self.bn1 = nn.BatchNorm2d(c_out) 14 | self.relu = nn.ReLU(True) 15 | self.conv2 = nn.Conv2d(c_out,c_out,3,stride=1,padding=1, bias=False) 16 | self.bn2 = nn.BatchNorm2d(c_out) 17 | if is_downsample: 18 | self.downsample = nn.Sequential( 19 | nn.Conv2d(c_in, c_out, 1, stride=2, bias=False), 20 | nn.BatchNorm2d(c_out) 21 | ) 22 | elif c_in != c_out: 23 | self.downsample = nn.Sequential( 24 | nn.Conv2d(c_in, c_out, 1, stride=1, bias=False), 25 | nn.BatchNorm2d(c_out) 26 | ) 27 | self.is_downsample = True 28 | 29 | def forward(self,x): 30 | y = self.conv1(x) 31 | y = self.bn1(y) 32 | y = self.relu(y) 33 | y = self.conv2(y) 34 | y = self.bn2(y) 35 | if self.is_downsample: 36 | x = self.downsample(x) 37 | return F.relu(x.add(y),True) 38 | 39 | def make_layers(c_in,c_out,repeat_times, is_downsample=False): 40 | blocks = [] 41 | for i in range(repeat_times): 42 | if i ==0: 43 | blocks += [BasicBlock(c_in,c_out, is_downsample=is_downsample),] 44 | else: 45 | blocks += [BasicBlock(c_out,c_out),] 46 | return nn.Sequential(*blocks) 47 | 48 | class Net(nn.Module): 49 | def __init__(self, num_classes=625 ,reid=False): 50 | super(Net,self).__init__() 51 | # 3 128 64 52 | self.conv = nn.Sequential( 53 | nn.Conv2d(3,32,3,stride=1,padding=1), 54 | nn.BatchNorm2d(32), 55 | nn.ELU(inplace=True), 56 | nn.Conv2d(32,32,3,stride=1,padding=1), 57 | nn.BatchNorm2d(32), 58 | nn.ELU(inplace=True), 59 | nn.MaxPool2d(3,2,padding=1), 60 | ) 61 | # 32 64 32 62 | self.layer1 = make_layers(32,32,2,False) 63 | # 32 64 32 64 | self.layer2 = make_layers(32,64,2,True) 65 | # 64 32 16 66 | self.layer3 = make_layers(64,128,2,True) 67 | # 128 16 8 68 | self.dense = nn.Sequential( 69 | nn.Dropout(p=0.6), 70 | nn.Linear(128*16*8, 128), 71 | nn.BatchNorm1d(128), 72 | nn.ELU(inplace=True) 73 | ) 74 | # 256 1 1 75 | self.reid = reid 76 | self.batch_norm = nn.BatchNorm1d(128) 77 | self.classifier = nn.Sequential( 78 | nn.Linear(128, num_classes), 79 | ) 80 | 81 | def forward(self, x): 82 | x = self.conv(x) 83 | x = self.layer1(x) 84 | x = self.layer2(x) 85 | x = self.layer3(x) 86 | 87 | x = x.view(x.size(0),-1) 88 | if self.reid: 89 | x = self.dense[0](x) 90 | x = self.dense[1](x) 91 | x = x.div(x.norm(p=2,dim=1,keepdim=True)) 92 | return x 93 | x = self.dense(x) 94 | # B x 128 95 | # classifier 96 | x = self.classifier(x) 97 | return x 98 | 99 | 100 | if __name__ == '__main__': 101 | net = Net(reid=True) 102 | x = torch.randn(4,3,128,64) 103 | y = net(x) 104 | import ipdb; ipdb.set_trace() 105 | 106 | 107 | -------------------------------------------------------------------------------- /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(args.gpu_id) if torch.cuda.is_available() and not args.no_cuda else "cpu" 18 | if torch.cuda.is_available() and not args.no_cuda: 19 | cudnn.benchmark = True 20 | 21 | # data loader 22 | root = args.data_dir 23 | query_dir = os.path.join(root,"query") 24 | gallery_dir = os.path.join(root,"gallery") 25 | transform = torchvision.transforms.Compose([ 26 | torchvision.transforms.Resize((128,64)), 27 | torchvision.transforms.ToTensor(), 28 | torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 29 | ]) 30 | queryloader = torch.utils.data.DataLoader( 31 | torchvision.datasets.ImageFolder(query_dir, transform=transform), 32 | batch_size=64, shuffle=False 33 | ) 34 | galleryloader = torch.utils.data.DataLoader( 35 | torchvision.datasets.ImageFolder(gallery_dir, transform=transform), 36 | batch_size=64, shuffle=False 37 | ) 38 | 39 | # net definition 40 | net = Net(reid=True) 41 | assert os.path.isfile("./checkpoint/ckpt.t7"), "Error: no checkpoint file found!" 42 | print('Loading from checkpoint/ckpt.t7') 43 | checkpoint = torch.load("./checkpoint/ckpt.t7") 44 | net_dict = checkpoint['net_dict'] 45 | net.load_state_dict(net_dict, strict=False) 46 | net.eval() 47 | net.to(device) 48 | 49 | # compute features 50 | query_features = torch.tensor([]).float() 51 | query_labels = torch.tensor([]).long() 52 | gallery_features = torch.tensor([]).float() 53 | gallery_labels = torch.tensor([]).long() 54 | 55 | with torch.no_grad(): 56 | for idx,(inputs,labels) in enumerate(queryloader): 57 | inputs = inputs.to(device) 58 | features = net(inputs).cpu() 59 | query_features = torch.cat((query_features, features), dim=0) 60 | query_labels = torch.cat((query_labels, labels)) 61 | 62 | for idx,(inputs,labels) in enumerate(galleryloader): 63 | inputs = inputs.to(device) 64 | features = net(inputs).cpu() 65 | gallery_features = torch.cat((gallery_features, features), dim=0) 66 | gallery_labels = torch.cat((gallery_labels, labels)) 67 | 68 | gallery_labels -= 2 69 | 70 | # save features 71 | features = { 72 | "qf": query_features, 73 | "ql": query_labels, 74 | "gf": gallery_features, 75 | "gl": gallery_labels 76 | } 77 | torch.save(features,"features.pth") -------------------------------------------------------------------------------- /deep_sort/deep/train.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/deep/train.jpg -------------------------------------------------------------------------------- /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(args.gpu_id) if torch.cuda.is_available() and not args.no_cuda else "cpu" 24 | if torch.cuda.is_available() and not args.no_cuda: 25 | cudnn.benchmark = True 26 | 27 | # data loading 28 | root = args.data_dir 29 | train_dir = os.path.join(root,"train") 30 | test_dir = os.path.join(root,"test") 31 | transform_train = torchvision.transforms.Compose([ 32 | torchvision.transforms.RandomCrop((128,64),padding=4), 33 | torchvision.transforms.RandomHorizontalFlip(), 34 | torchvision.transforms.ToTensor(), 35 | torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 36 | ]) 37 | transform_test = torchvision.transforms.Compose([ 38 | torchvision.transforms.Resize((128,64)), 39 | torchvision.transforms.ToTensor(), 40 | torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 41 | ]) 42 | trainloader = torch.utils.data.DataLoader( 43 | torchvision.datasets.ImageFolder(train_dir, transform=transform_train), 44 | batch_size=64,shuffle=True 45 | ) 46 | testloader = torch.utils.data.DataLoader( 47 | torchvision.datasets.ImageFolder(test_dir, transform=transform_test), 48 | batch_size=64,shuffle=True 49 | ) 50 | num_classes = max(len(trainloader.dataset.classes), len(testloader.dataset.classes)) 51 | 52 | # net definition 53 | start_epoch = 0 54 | net = Net(num_classes=num_classes) 55 | if args.resume: 56 | assert os.path.isfile("./checkpoint/ckpt.t7"), "Error: no checkpoint file found!" 57 | print('Loading from checkpoint/ckpt.t7') 58 | checkpoint = torch.load("./checkpoint/ckpt.t7") 59 | # import ipdb; ipdb.set_trace() 60 | net_dict = checkpoint['net_dict'] 61 | net.load_state_dict(net_dict) 62 | best_acc = checkpoint['acc'] 63 | start_epoch = checkpoint['epoch'] 64 | net.to(device) 65 | 66 | # loss and optimizer 67 | criterion = torch.nn.CrossEntropyLoss() 68 | optimizer = torch.optim.SGD(net.parameters(), args.lr, momentum=0.9, weight_decay=5e-4) 69 | best_acc = 0. 70 | 71 | # train function for each epoch 72 | def train(epoch): 73 | print("\nEpoch : %d"%(epoch+1)) 74 | net.train() 75 | training_loss = 0. 76 | train_loss = 0. 77 | correct = 0 78 | total = 0 79 | interval = args.interval 80 | start = time.time() 81 | for idx, (inputs, labels) in enumerate(trainloader): 82 | # forward 83 | inputs,labels = inputs.to(device),labels.to(device) 84 | outputs = net(inputs) 85 | loss = criterion(outputs, labels) 86 | 87 | # backward 88 | optimizer.zero_grad() 89 | loss.backward() 90 | optimizer.step() 91 | 92 | # accumurating 93 | training_loss += loss.item() 94 | train_loss += loss.item() 95 | correct += outputs.max(dim=1)[1].eq(labels).sum().item() 96 | total += labels.size(0) 97 | 98 | # print 99 | if (idx+1)%interval == 0: 100 | end = time.time() 101 | print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format( 102 | 100.*(idx+1)/len(trainloader), end-start, training_loss/interval, correct, total, 100.*correct/total 103 | )) 104 | training_loss = 0. 105 | start = time.time() 106 | 107 | return train_loss/len(trainloader), 1.- correct/total 108 | 109 | def test(epoch): 110 | global best_acc 111 | net.eval() 112 | test_loss = 0. 113 | correct = 0 114 | total = 0 115 | start = time.time() 116 | with torch.no_grad(): 117 | for idx, (inputs, labels) in enumerate(testloader): 118 | inputs, labels = inputs.to(device), labels.to(device) 119 | outputs = net(inputs) 120 | loss = criterion(outputs, labels) 121 | 122 | test_loss += loss.item() 123 | correct += outputs.max(dim=1)[1].eq(labels).sum().item() 124 | total += labels.size(0) 125 | 126 | print("Testing ...") 127 | end = time.time() 128 | print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format( 129 | 100.*(idx+1)/len(testloader), end-start, test_loss/len(testloader), correct, total, 100.*correct/total 130 | )) 131 | 132 | # saving checkpoint 133 | acc = 100.*correct/total 134 | if acc > best_acc: 135 | best_acc = acc 136 | print("Saving parameters to checkpoint/ckpt.t7") 137 | checkpoint = { 138 | 'net_dict':net.state_dict(), 139 | 'acc':acc, 140 | 'epoch':epoch, 141 | } 142 | if not os.path.isdir('checkpoint'): 143 | os.mkdir('checkpoint') 144 | torch.save(checkpoint, './checkpoint/ckpt.t7') 145 | 146 | return test_loss/len(testloader), 1.- correct/total 147 | 148 | # plot figure 149 | x_epoch = [] 150 | record = {'train_loss':[], 'train_err':[], 'test_loss':[], 'test_err':[]} 151 | fig = plt.figure() 152 | ax0 = fig.add_subplot(121, title="loss") 153 | ax1 = fig.add_subplot(122, title="top1err") 154 | def draw_curve(epoch, train_loss, train_err, test_loss, test_err): 155 | global record 156 | record['train_loss'].append(train_loss) 157 | record['train_err'].append(train_err) 158 | record['test_loss'].append(test_loss) 159 | record['test_err'].append(test_err) 160 | 161 | x_epoch.append(epoch) 162 | ax0.plot(x_epoch, record['train_loss'], 'bo-', label='train') 163 | ax0.plot(x_epoch, record['test_loss'], 'ro-', label='val') 164 | ax1.plot(x_epoch, record['train_err'], 'bo-', label='train') 165 | ax1.plot(x_epoch, record['test_err'], 'ro-', label='val') 166 | if epoch == 0: 167 | ax0.legend() 168 | ax1.legend() 169 | fig.savefig("train.jpg") 170 | 171 | # lr decay 172 | def lr_decay(): 173 | global optimizer 174 | for params in optimizer.param_groups: 175 | params['lr'] *= 0.1 176 | lr = params['lr'] 177 | print("Learning rate adjusted to {}".format(lr)) 178 | 179 | def main(): 180 | for epoch in range(start_epoch, start_epoch+40): 181 | train_loss, train_err = train(epoch) 182 | test_loss, test_err = test(epoch) 183 | draw_curve(epoch, train_loss, train_err, test_loss, test_err) 184 | if (epoch+1)%20==0: 185 | lr_decay() 186 | 187 | 188 | if __name__ == '__main__': 189 | main() 190 | -------------------------------------------------------------------------------- /deep_sort/deep_reid.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | import cv2 4 | import time 5 | from .deep.feature_extractor import Extractor 6 | from .sort.nn_matching import NearestNeighborDistanceMetric 7 | from .sort.preprocessing import non_max_suppression 8 | from .sort.detection import Detection 9 | from .sort.tracker import Tracker 10 | 11 | import sys 12 | sys.path.append('./fast_reid/demo') 13 | sys.path.append('./fast_reid') 14 | from demo import Reid_feature 15 | # from predictor import FeatureExtractionDemo 16 | 17 | 18 | 19 | 20 | __all__ = ['DeepReid'] 21 | 22 | 23 | class DeepReid(object): 24 | 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): 25 | self.min_confidence = min_confidence 26 | self.nms_max_overlap = nms_max_overlap 27 | 28 | # self.extractor = Extractor(model_path, use_cuda=use_cuda) 29 | self.extractor = Reid_feature() 30 | max_cosine_distance = max_dist 31 | nn_budget = 100 32 | metric = NearestNeighborDistanceMetric("cosine", max_cosine_distance, nn_budget) 33 | self.tracker = Tracker(metric, max_iou_distance=max_iou_distance, max_age=max_age, n_init=n_init) 34 | 35 | def update(self, bbox_xywh, confidences, ori_img): 36 | outputs = [] 37 | if not len(confidences): 38 | return outputs 39 | else: 40 | self.height, self.width = ori_img.shape[:2] 41 | # generate detections 42 | 43 | features = self._get_features(bbox_xywh, ori_img) 44 | 45 | bbox_tlwh = self._xywh_to_tlwh(bbox_xywh) 46 | detections = [Detection(bbox_tlwh[i], conf, features[i]) for i, conf in enumerate(confidences) if conf>self.min_confidence] 47 | 48 | # run on non-maximum supression 49 | boxes = np.array([d.tlwh for d in detections]) 50 | scores = np.array([d.confidence for d in detections]) 51 | indices = non_max_suppression(boxes, self.nms_max_overlap, scores) 52 | detections = [detections[i] for i in indices] 53 | 54 | # update tracker 55 | self.tracker.predict() 56 | self.tracker.update(detections) 57 | 58 | # output bbox identities 59 | 60 | for track in self.tracker.tracks: 61 | if not track.is_confirmed() or track.time_since_update > 1: 62 | continue 63 | box = track.to_tlwh() 64 | x1, y1, x2, y2 = self._tlwh_to_xyxy(box) 65 | track_id = track.track_id 66 | outputs.append(np.array([x1, y1, x2, y2, track_id], dtype=np.int)) 67 | if len(outputs) > 0: 68 | outputs = np.stack(outputs, axis=0) 69 | return outputs, features 70 | 71 | 72 | """ 73 | TODO: 74 | Convert bbox from xc_yc_w_h to xtl_ytl_w_h 75 | Thanks JieChen91@github.com for reporting this bug! 76 | """ 77 | @staticmethod 78 | def _xywh_to_tlwh(bbox_xywh): 79 | if isinstance(bbox_xywh, np.ndarray): 80 | bbox_tlwh = bbox_xywh.copy() 81 | elif isinstance(bbox_xywh, torch.Tensor): 82 | bbox_tlwh = bbox_xywh.clone() 83 | bbox_tlwh[:,0] = bbox_xywh[:,0] - bbox_xywh[:,2]/2. 84 | bbox_tlwh[:,1] = bbox_xywh[:,1] - bbox_xywh[:,3]/2. 85 | return bbox_tlwh 86 | 87 | 88 | def _xywh_to_xyxy(self, bbox_xywh): 89 | x,y,w,h = bbox_xywh 90 | x1 = max(int(x-w/2),0) 91 | x2 = min(int(x+w/2),self.width-1) 92 | y1 = max(int(y-h/2),0) 93 | y2 = min(int(y+h/2),self.height-1) 94 | return x1,y1,x2,y2 95 | 96 | def _tlwh_to_xyxy(self, bbox_tlwh): 97 | """ 98 | TODO: 99 | Convert bbox from xtl_ytl_w_h to xc_yc_w_h 100 | Thanks JieChen91@github.com for reporting this bug! 101 | """ 102 | x,y,w,h = bbox_tlwh 103 | x1 = max(int(x),0) 104 | x2 = min(int(x+w),self.width-1) 105 | y1 = max(int(y),0) 106 | y2 = min(int(y+h),self.height-1) 107 | return x1,y1,x2,y2 108 | 109 | def _xyxy_to_tlwh(self, bbox_xyxy): 110 | x1,y1,x2,y2 = bbox_xyxy 111 | 112 | t = x1 113 | l = y1 114 | w = int(x2-x1) 115 | h = int(y2-y1) 116 | return t,l,w,h 117 | 118 | def _get_features(self, bbox_xywh, ori_img): 119 | im_crops = [] 120 | t2 = time.time() 121 | for box in bbox_xywh: 122 | x1,y1,x2,y2 = self._xywh_to_xyxy(box) 123 | im = ori_img[y1:y2,x1:x2] 124 | 125 | im = im[:, :, ::-1] # reid 前处理 126 | im = cv2.resize(im, (128, 256), interpolation=cv2.INTER_CUBIC) 127 | im_crops.append(torch.as_tensor(im.astype("float32").transpose(2, 0, 1))[None]) 128 | # print('pre time:', time.time() - t2) 129 | if im_crops: 130 | t1 = time.time() 131 | features = self.extractor(im_crops) 132 | # print('reid features time:', time.time() - t1, features.shape) 133 | else: 134 | features = np.array([]) 135 | return features 136 | 137 | 138 | -------------------------------------------------------------------------------- /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.preprocessing import non_max_suppression 7 | from .sort.detection import Detection 8 | from .sort.tracker import Tracker 9 | 10 | 11 | __all__ = ['DeepSort'] 12 | 13 | 14 | class DeepSort(object): 15 | 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): 16 | self.min_confidence = min_confidence 17 | self.nms_max_overlap = nms_max_overlap 18 | 19 | self.extractor = Extractor(model_path, use_cuda=use_cuda) 20 | 21 | max_cosine_distance = max_dist 22 | nn_budget = 100 23 | metric = NearestNeighborDistanceMetric("cosine", max_cosine_distance, nn_budget) 24 | self.tracker = Tracker(metric, max_iou_distance=max_iou_distance, max_age=max_age, n_init=n_init) 25 | 26 | def update(self, bbox_xywh, confidences, ori_img): 27 | outputs = [] 28 | if not len(confidences): 29 | return outputs 30 | else: 31 | self.height, self.width = ori_img.shape[:2] 32 | # generate detections 33 | features = self._get_features(bbox_xywh, ori_img) 34 | # print(features.shape) 35 | bbox_tlwh = self._xywh_to_tlwh(bbox_xywh) 36 | detections = [Detection(bbox_tlwh[i], conf, features[i]) for i, conf in enumerate(confidences) if conf>self.min_confidence] 37 | 38 | # run on non-maximum supression 39 | boxes = np.array([d.tlwh for d in detections]) 40 | scores = np.array([d.confidence for d in detections]) 41 | indices = non_max_suppression(boxes, self.nms_max_overlap, scores) 42 | detections = [detections[i] for i in indices] 43 | 44 | # update tracker 45 | self.tracker.predict() 46 | self.tracker.update(detections) 47 | 48 | # output bbox identities 49 | 50 | for track in self.tracker.tracks: 51 | if not track.is_confirmed() or track.time_since_update > 1: 52 | continue 53 | box = track.to_tlwh() 54 | x1, y1, x2, y2 = self._tlwh_to_xyxy(box) 55 | track_id = track.track_id 56 | outputs.append(np.array([x1, y1, x2, y2, track_id], dtype=np.int)) 57 | if len(outputs) > 0: 58 | outputs = np.stack(outputs, axis=0) 59 | return outputs 60 | 61 | 62 | """ 63 | TODO: 64 | Convert bbox from xc_yc_w_h to xtl_ytl_w_h 65 | Thanks JieChen91@github.com for reporting this bug! 66 | """ 67 | @staticmethod 68 | def _xywh_to_tlwh(bbox_xywh): 69 | if isinstance(bbox_xywh, np.ndarray): 70 | bbox_tlwh = bbox_xywh.copy() 71 | elif isinstance(bbox_xywh, torch.Tensor): 72 | bbox_tlwh = bbox_xywh.clone() 73 | bbox_tlwh[:,0] = bbox_xywh[:,0] - bbox_xywh[:,2]/2. 74 | bbox_tlwh[:,1] = bbox_xywh[:,1] - bbox_xywh[:,3]/2. 75 | return bbox_tlwh 76 | 77 | 78 | def _xywh_to_xyxy(self, bbox_xywh): 79 | x,y,w,h = bbox_xywh 80 | x1 = max(int(x-w/2),0) 81 | x2 = min(int(x+w/2),self.width-1) 82 | y1 = max(int(y-h/2),0) 83 | y2 = min(int(y+h/2),self.height-1) 84 | return x1,y1,x2,y2 85 | 86 | def _tlwh_to_xyxy(self, bbox_tlwh): 87 | """ 88 | TODO: 89 | Convert bbox from xtl_ytl_w_h to xc_yc_w_h 90 | Thanks JieChen91@github.com for reporting this bug! 91 | """ 92 | x,y,w,h = bbox_tlwh 93 | x1 = max(int(x),0) 94 | x2 = min(int(x+w),self.width-1) 95 | y1 = max(int(y),0) 96 | y2 = min(int(y+h),self.height-1) 97 | return x1,y1,x2,y2 98 | 99 | def _xyxy_to_tlwh(self, bbox_xyxy): 100 | x1,y1,x2,y2 = bbox_xyxy 101 | 102 | t = x1 103 | l = y1 104 | w = int(x2-x1) 105 | h = int(y2-y1) 106 | return t,l,w,h 107 | 108 | def _get_features(self, bbox_xywh, ori_img): 109 | im_crops = [] 110 | for box in bbox_xywh: 111 | x1,y1,x2,y2 = self._xywh_to_xyxy(box) 112 | im = ori_img[y1:y2,x1:x2] 113 | im_crops.append(im) 114 | if im_crops: 115 | features = self.extractor(im_crops) 116 | else: 117 | features = np.array([]) 118 | return features 119 | 120 | 121 | -------------------------------------------------------------------------------- /deep_sort/sort/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__init__.py -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/detection.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/detection.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/detection.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/detection.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/detection.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/detection.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/iou_matching.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/iou_matching.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/iou_matching.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/iou_matching.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/iou_matching.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/iou_matching.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/kalman_filter.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/kalman_filter.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/kalman_filter.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/kalman_filter.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/kalman_filter.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/kalman_filter.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/linear_assignment.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/linear_assignment.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/linear_assignment.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/linear_assignment.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/linear_assignment.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/linear_assignment.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/nn_matching.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/nn_matching.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/nn_matching.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/nn_matching.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/nn_matching.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/nn_matching.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/preprocessing.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/preprocessing.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/preprocessing.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/preprocessing.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/preprocessing.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/preprocessing.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/track.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/track.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/track.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/track.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/track.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/track.cpython-38.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/tracker.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/tracker.cpython-36.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/tracker.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/tracker.cpython-37.pyc -------------------------------------------------------------------------------- /deep_sort/sort/__pycache__/tracker.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/deep_sort/sort/__pycache__/tracker.cpython-38.pyc -------------------------------------------------------------------------------- /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): 30 | self.tlwh = np.asarray(tlwh, dtype=np.float) 31 | self.confidence = float(confidence) 32 | self.feature = np.asarray(feature, dtype=np.float32) 33 | 34 | def to_tlbr(self): 35 | """Convert bounding box to format `(min x, min y, max x, max y)`, i.e., 36 | `(top left, bottom right)`. 37 | """ 38 | ret = self.tlwh.copy() 39 | ret[2:] += ret[:2] 40 | return ret 41 | 42 | def to_xyah(self): 43 | """Convert bounding box to format `(center x, center y, aspect ratio, 44 | height)`, where the aspect ratio is `width / height`. 45 | """ 46 | ret = self.tlwh.copy() 47 | ret[:2] += ret[2:] / 2 48 | ret[2] /= ret[3] 49 | return ret 50 | -------------------------------------------------------------------------------- /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([detections[i].tlwh for i in detection_indices]) 80 | cost_matrix[row, :] = 1. - iou(bbox, candidates) 81 | return cost_matrix 82 | -------------------------------------------------------------------------------- /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/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/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 | 126 | if metric == "euclidean": 127 | self._metric = _nn_euclidean_distance 128 | elif metric == "cosine": 129 | self._metric = _nn_cosine_distance 130 | else: 131 | raise ValueError( 132 | "Invalid metric; must be either 'euclidean' or 'cosine'") 133 | self.matching_threshold = matching_threshold 134 | self.budget = budget 135 | self.samples = {} 136 | 137 | def partial_fit(self, features, targets, active_targets): 138 | """Update the distance metric with new data. 139 | 140 | Parameters 141 | ---------- 142 | features : ndarray 143 | An NxM matrix of N features of dimensionality M. 144 | targets : ndarray 145 | An integer array of associated target identities. 146 | active_targets : List[int] 147 | A list of targets that are currently present in the scene. 148 | 149 | """ 150 | for feature, target in zip(features, targets): 151 | self.samples.setdefault(target, []).append(feature) 152 | if self.budget is not None: 153 | self.samples[target] = self.samples[target][-self.budget:] 154 | self.samples = {k: self.samples[k] for k in active_targets} 155 | 156 | def distance(self, features, targets): 157 | """Compute distance between features and targets. 158 | 159 | Parameters 160 | ---------- 161 | features : ndarray 162 | An NxM matrix of N features of dimensionality M. 163 | targets : List[int] 164 | A list of targets to match the given `features` against. 165 | 166 | Returns 167 | ------- 168 | ndarray 169 | Returns a cost matrix of shape len(targets), len(features), where 170 | element (i, j) contains the closest squared distance between 171 | `targets[i]` and `features[j]`. 172 | 173 | """ 174 | cost_matrix = np.zeros((len(targets), len(features))) 175 | for i, target in enumerate(targets): 176 | cost_matrix[i, :] = self._metric(self.samples[target], features) 177 | return cost_matrix 178 | -------------------------------------------------------------------------------- /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/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, 67 | feature=None): 68 | self.mean = mean 69 | self.covariance = covariance 70 | self.track_id = track_id 71 | self.hits = 1 72 | self.age = 1 73 | self.time_since_update = 0 74 | 75 | self.state = TrackState.Tentative 76 | self.features = [] 77 | if feature is not None: 78 | self.features.append(feature) 79 | 80 | self._n_init = n_init 81 | self._max_age = max_age 82 | 83 | def to_tlwh(self): 84 | """Get current position in bounding box format `(top left x, top left y, 85 | width, height)`. 86 | 87 | Returns 88 | ------- 89 | ndarray 90 | The bounding box. 91 | 92 | """ 93 | ret = self.mean[:4].copy() 94 | ret[2] *= ret[3] 95 | ret[:2] -= ret[2:] / 2 96 | return ret 97 | 98 | def to_tlbr(self): 99 | """Get current position in bounding box format `(min x, miny, max x, 100 | max y)`. 101 | 102 | Returns 103 | ------- 104 | ndarray 105 | The bounding box. 106 | 107 | """ 108 | ret = self.to_tlwh() 109 | ret[2:] = ret[:2] + ret[2:] 110 | return ret 111 | 112 | def predict(self, kf): 113 | """Propagate the state distribution to the current time step using a 114 | Kalman filter prediction step. 115 | 116 | Parameters 117 | ---------- 118 | kf : kalman_filter.KalmanFilter 119 | The Kalman filter. 120 | 121 | """ 122 | self.mean, self.covariance = kf.predict(self.mean, self.covariance) 123 | self.age += 1 124 | self.time_since_update += 1 125 | 126 | def update(self, kf, detection): 127 | """Perform Kalman filter measurement update step and update the feature 128 | cache. 129 | 130 | Parameters 131 | ---------- 132 | kf : kalman_filter.KalmanFilter 133 | The Kalman filter. 134 | detection : Detection 135 | The associated detection. 136 | 137 | """ 138 | self.mean, self.covariance = kf.update( 139 | self.mean, self.covariance, detection.to_xyah()) 140 | self.features.append(detection.feature) 141 | 142 | self.hits += 1 143 | self.time_since_update = 0 144 | if self.state == TrackState.Tentative and self.hits >= self._n_init: 145 | self.state = TrackState.Confirmed 146 | 147 | def mark_missed(self): 148 | """Mark this track as missed (no association at the current time step). 149 | """ 150 | if self.state == TrackState.Tentative: 151 | self.state = TrackState.Deleted 152 | elif self.time_since_update > self._max_age: 153 | self.state = TrackState.Deleted 154 | 155 | def is_tentative(self): 156 | """Returns True if this track is tentative (unconfirmed). 157 | """ 158 | return self.state == TrackState.Tentative 159 | 160 | def is_confirmed(self): 161 | """Returns True if this track is confirmed.""" 162 | return self.state == TrackState.Confirmed 163 | 164 | def is_deleted(self): 165 | """Returns True if this track is dead and should be deleted.""" 166 | return self.state == TrackState.Deleted 167 | -------------------------------------------------------------------------------- /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 update(self, detections): 59 | """Perform measurement update and track management. 60 | 61 | Parameters 62 | ---------- 63 | detections : List[deep_sort.detection.Detection] 64 | A list of detections at the current time step. 65 | 66 | """ 67 | # Run matching cascade. 68 | matches, unmatched_tracks, unmatched_detections = \ 69 | self._match(detections) 70 | 71 | # Update track set. 72 | for track_idx, detection_idx in matches: 73 | self.tracks[track_idx].update( 74 | self.kf, detections[detection_idx]) 75 | for track_idx in unmatched_tracks: 76 | self.tracks[track_idx].mark_missed() 77 | for detection_idx in unmatched_detections: 78 | self._initiate_track(detections[detection_idx]) 79 | self.tracks = [t for t in self.tracks if not t.is_deleted()] 80 | 81 | # Update distance metric. 82 | active_targets = [t.track_id for t in self.tracks if t.is_confirmed()] 83 | features, targets = [], [] 84 | for track in self.tracks: 85 | if not track.is_confirmed(): 86 | continue 87 | features += track.features 88 | targets += [track.track_id for _ in track.features] 89 | track.features = [] 90 | self.metric.partial_fit( 91 | np.asarray(features), np.asarray(targets), active_targets) 92 | 93 | def _match(self, detections): 94 | 95 | def gated_metric(tracks, dets, track_indices, detection_indices): 96 | features = np.array([dets[i].feature for i in detection_indices]) 97 | targets = np.array([tracks[i].track_id for i in track_indices]) 98 | cost_matrix = self.metric.distance(features, targets) 99 | cost_matrix = linear_assignment.gate_cost_matrix( 100 | self.kf, cost_matrix, tracks, dets, track_indices, 101 | detection_indices) 102 | 103 | return cost_matrix 104 | 105 | # Split track set into confirmed and unconfirmed tracks. 106 | confirmed_tracks = [ 107 | i for i, t in enumerate(self.tracks) if t.is_confirmed()] 108 | unconfirmed_tracks = [ 109 | i for i, t in enumerate(self.tracks) if not t.is_confirmed()] 110 | 111 | # Associate confirmed tracks using appearance features. 112 | matches_a, unmatched_tracks_a, unmatched_detections = \ 113 | linear_assignment.matching_cascade( 114 | gated_metric, self.metric.matching_threshold, self.max_age, 115 | self.tracks, detections, confirmed_tracks) 116 | 117 | # Associate remaining tracks together with unconfirmed tracks using IOU. 118 | iou_track_candidates = unconfirmed_tracks + [ 119 | k for k in unmatched_tracks_a if 120 | self.tracks[k].time_since_update == 1] 121 | unmatched_tracks_a = [ 122 | k for k in unmatched_tracks_a if 123 | self.tracks[k].time_since_update != 1] 124 | matches_b, unmatched_tracks_b, unmatched_detections = \ 125 | linear_assignment.min_cost_matching( 126 | iou_matching.iou_cost, self.max_iou_distance, self.tracks, 127 | detections, iou_track_candidates, unmatched_detections) 128 | 129 | matches = matches_a + matches_b 130 | unmatched_tracks = list(set(unmatched_tracks_a + unmatched_tracks_b)) 131 | return matches, unmatched_tracks, unmatched_detections 132 | 133 | def _initiate_track(self, detection): 134 | mean, covariance = self.kf.initiate(detection.to_xyah()) 135 | self.tracks.append(Track( 136 | mean, covariance, self._next_id, self.n_init, self.max_age, 137 | detection.feature)) 138 | self._next_id += 1 139 | -------------------------------------------------------------------------------- /detect.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import platform 4 | import shutil 5 | import time 6 | from pathlib import Path 7 | 8 | import cv2 9 | import torch 10 | import torch.backends.cudnn as cudnn 11 | from numpy import random 12 | 13 | from models.experimental import attempt_load 14 | from utils.datasets import LoadStreams, LoadImages 15 | from utils.general import ( 16 | check_img_size, non_max_suppression, apply_classifier, scale_coords, 17 | xyxy2xywh, plot_one_box, strip_optimizer, set_logging) 18 | from utils.torch_utils import select_device, load_classifier, time_synchronized 19 | 20 | 21 | def detect(save_img=False): 22 | out, source, weights, view_img, save_txt, imgsz = \ 23 | opt.output, opt.source, opt.weights, opt.view_img, opt.save_txt, opt.img_size 24 | webcam = source == '0' or source.startswith('rtsp') or source.startswith('http') or source.endswith('.txt') 25 | 26 | # Initialize 27 | set_logging() 28 | device = select_device(opt.device) 29 | if os.path.exists(out): 30 | shutil.rmtree(out) # delete output folder 31 | os.makedirs(out) # make new output folder 32 | half = device.type != 'cpu' # half precision only supported on CUDA 33 | 34 | # Load model 35 | model = attempt_load(weights, map_location=device) # load FP32 model 36 | imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size 37 | if half: 38 | model.half() # to FP16 39 | 40 | # Second-stage classifier 41 | classify = False 42 | if classify: 43 | modelc = load_classifier(name='resnet101', n=2) # initialize 44 | modelc.load_state_dict(torch.load('weights/resnet101.pt', map_location=device)['model']) # load weights 45 | modelc.to(device).eval() 46 | 47 | # Set Dataloader 48 | vid_path, vid_writer = None, None 49 | if webcam: 50 | view_img = True 51 | cudnn.benchmark = True # set True to speed up constant image size inference 52 | dataset = LoadStreams(source, img_size=imgsz) 53 | else: 54 | save_img = True 55 | view_img = True 56 | dataset = LoadImages(source, img_size=imgsz) 57 | 58 | # Get names and colors 59 | names = model.module.names if hasattr(model, 'module') else model.names 60 | colors = [[random.randint(0, 255) for _ in range(3)] for _ in range(len(names))] 61 | 62 | # Run inference 63 | t0 = time.time() 64 | img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img 65 | _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once 66 | for path, img, im0s, vid_cap in dataset: 67 | img = torch.from_numpy(img).to(device) 68 | img = img.half() if half else img.float() # uint8 to fp16/32 69 | img /= 255.0 # 0 - 255 to 0.0 - 1.0 70 | if img.ndimension() == 3: 71 | img = img.unsqueeze(0) 72 | 73 | # Inference 74 | t1 = time_synchronized() 75 | pred = model(img, augment=opt.augment)[0] 76 | 77 | # Apply NMS 78 | print(opt.classes) 79 | pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, classes=opt.classes, agnostic=opt.agnostic_nms) 80 | t2 = time_synchronized() 81 | 82 | # Apply Classifier 83 | if classify: 84 | pred = apply_classifier(pred, modelc, img, im0s) 85 | 86 | # Process detections 87 | for i, det in enumerate(pred): # detections per image 88 | if webcam: # batch_size >= 1 89 | p, s, im0 = path[i], '%g: ' % i, im0s[i].copy() 90 | else: 91 | p, s, im0 = path, '', im0s 92 | 93 | save_path = str(Path(out) / Path(p).name) 94 | txt_path = str(Path(out) / Path(p).stem) + ('_%g' % dataset.frame if dataset.mode == 'video' else '') 95 | s += '%gx%g ' % img.shape[2:] # print string 96 | gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh 97 | if det is not None and len(det): 98 | # Rescale boxes from img_size to im0 size 99 | det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round() 100 | 101 | # Print results 102 | for c in det[:, -1].unique(): 103 | n = (det[:, -1] == c).sum() # detections per class 104 | s += '%g %ss, ' % (n, names[int(c)]) # add to string 105 | 106 | # Write results 107 | for *xyxy, conf, cls in reversed(det): 108 | if save_txt: # Write to file 109 | xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh 110 | with open(txt_path + '.txt', 'a') as f: 111 | f.write(('%g ' * 5 + '\n') % (cls, *xywh)) # label format 112 | 113 | if save_img or view_img: # Add bbox to image 114 | # if names[int(cls)] == 'person': 115 | label = '%s %.2f' % (names[int(cls)], conf) 116 | plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3) 117 | 118 | # Print time (inference + NMS) 119 | print('%sDone. (%.3fs)' % (s, t2 - t1)) 120 | 121 | # Stream results 122 | if view_img: 123 | cv2.imshow(p, im0) 124 | if cv2.waitKey(1) == ord('q'): # q to quit 125 | raise StopIteration 126 | 127 | # Save results (image with detections) 128 | if save_img: 129 | if dataset.mode == 'images': 130 | cv2.imwrite(save_path, im0) 131 | else: 132 | if vid_path != save_path: # new video 133 | vid_path = save_path 134 | if isinstance(vid_writer, cv2.VideoWriter): 135 | vid_writer.release() # release previous video writer 136 | 137 | fourcc = 'mp4v' # output video codec 138 | fps = vid_cap.get(cv2.CAP_PROP_FPS) 139 | w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) 140 | h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) 141 | vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*fourcc), fps, (w, h)) 142 | vid_writer.write(im0) 143 | 144 | if save_txt or save_img: 145 | print('Results saved to %s' % Path(out)) 146 | if platform.system() == 'Darwin' and not opt.update: # MacOS 147 | os.system('open ' + save_path) 148 | 149 | print('Done. (%.3fs)' % (time.time() - t0)) 150 | 151 | 152 | if __name__ == '__main__': 153 | parser = argparse.ArgumentParser() 154 | parser.add_argument('--weights', nargs='+', type=str, default='./weights/yolov5s.pt', help='model.pt path(s)') 155 | parser.add_argument('--source', type=str, default='/media/zengwb/PC/Dataset/ReID-dataset/channel1/1.mp4', help='source') # file/folder, 0 for webcam 156 | parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder 157 | parser.add_argument('--img-size', type=int, default=960, help='inference size (pixels)') 158 | parser.add_argument('--conf-thres', type=float, default=0.4, help='object confidence threshold') 159 | parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS') 160 | parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') 161 | parser.add_argument('--view-img', action='store_true', help='display results') 162 | parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') 163 | parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3') 164 | parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') 165 | parser.add_argument('--augment', action='store_true', help='augmented inference') 166 | parser.add_argument('--update', action='store_true', help='update all models') 167 | opt = parser.parse_args() 168 | print(opt) 169 | 170 | with torch.no_grad(): 171 | if opt.update: # update all models (to fix SourceChangeWarning) 172 | for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt']: 173 | detect() 174 | strip_optimizer(opt.weights) 175 | else: 176 | detect() 177 | -------------------------------------------------------------------------------- /hubconf.py: -------------------------------------------------------------------------------- 1 | """File for accessing YOLOv5 via PyTorch Hub https://pytorch.org/hub/ 2 | 3 | Usage: 4 | import torch 5 | model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True, channels=3, classes=80) 6 | """ 7 | 8 | dependencies = ['torch', 'yaml'] 9 | import os 10 | 11 | import torch 12 | 13 | from models.yolo import Model 14 | from utils.google_utils import attempt_download 15 | 16 | 17 | def create(name, pretrained, channels, classes): 18 | """Creates a specified YOLOv5 model 19 | 20 | Arguments: 21 | name (str): name of model, i.e. 'yolov5s' 22 | pretrained (bool): load pretrained weights into the model 23 | channels (int): number of input channels 24 | classes (int): number of model classes 25 | 26 | Returns: 27 | pytorch model 28 | """ 29 | config = os.path.join(os.path.dirname(__file__), 'models', '%s.yaml' % name) # model.yaml path 30 | try: 31 | model = Model(config, channels, classes) 32 | if pretrained: 33 | ckpt = '%s.pt' % name # checkpoint filename 34 | attempt_download(ckpt) # download if not found locally 35 | state_dict = torch.load(ckpt, map_location=torch.device('cpu'))['model'].float().state_dict() # to FP32 36 | state_dict = {k: v for k, v in state_dict.items() if model.state_dict()[k].shape == v.shape} # filter 37 | model.load_state_dict(state_dict, strict=False) # load 38 | return model 39 | 40 | except Exception as e: 41 | help_url = 'https://github.com/ultralytics/yolov5/issues/36' 42 | s = 'Cache maybe be out of date, deleting cache and retrying may solve this. See %s for help.' % help_url 43 | raise Exception(s) from e 44 | 45 | 46 | def yolov5s(pretrained=False, channels=3, classes=80): 47 | """YOLOv5-small model from https://github.com/ultralytics/yolov5 48 | 49 | Arguments: 50 | pretrained (bool): load pretrained weights into the model, default=False 51 | channels (int): number of input channels, default=3 52 | classes (int): number of model classes, default=80 53 | 54 | Returns: 55 | pytorch model 56 | """ 57 | return create('yolov5s', pretrained, channels, classes) 58 | 59 | 60 | def yolov5m(pretrained=False, channels=3, classes=80): 61 | """YOLOv5-medium model from https://github.com/ultralytics/yolov5 62 | 63 | Arguments: 64 | pretrained (bool): load pretrained weights into the model, default=False 65 | channels (int): number of input channels, default=3 66 | classes (int): number of model classes, default=80 67 | 68 | Returns: 69 | pytorch model 70 | """ 71 | return create('yolov5m', pretrained, channels, classes) 72 | 73 | 74 | def yolov5l(pretrained=False, channels=3, classes=80): 75 | """YOLOv5-large model from https://github.com/ultralytics/yolov5 76 | 77 | Arguments: 78 | pretrained (bool): load pretrained weights into the model, default=False 79 | channels (int): number of input channels, default=3 80 | classes (int): number of model classes, default=80 81 | 82 | Returns: 83 | pytorch model 84 | """ 85 | return create('yolov5l', pretrained, channels, classes) 86 | 87 | 88 | def yolov5x(pretrained=False, channels=3, classes=80): 89 | """YOLOv5-xlarge model from https://github.com/ultralytics/yolov5 90 | 91 | Arguments: 92 | pretrained (bool): load pretrained weights into the model, default=False 93 | channels (int): number of input channels, default=3 94 | classes (int): number of model classes, default=80 95 | 96 | Returns: 97 | pytorch model 98 | """ 99 | return create('yolov5x', pretrained, channels, classes) 100 | -------------------------------------------------------------------------------- /inference/images/bus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/inference/images/bus.jpg -------------------------------------------------------------------------------- /inference/images/zidane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/inference/images/zidane.jpg -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__init__.py -------------------------------------------------------------------------------- /models/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /models/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /models/__pycache__/common.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__pycache__/common.cpython-36.pyc -------------------------------------------------------------------------------- /models/__pycache__/common.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__pycache__/common.cpython-38.pyc -------------------------------------------------------------------------------- /models/__pycache__/experimental.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__pycache__/experimental.cpython-36.pyc -------------------------------------------------------------------------------- /models/__pycache__/experimental.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__pycache__/experimental.cpython-38.pyc -------------------------------------------------------------------------------- /models/__pycache__/yolo.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__pycache__/yolo.cpython-36.pyc -------------------------------------------------------------------------------- /models/__pycache__/yolo.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/models/__pycache__/yolo.cpython-38.pyc -------------------------------------------------------------------------------- /models/common.py: -------------------------------------------------------------------------------- 1 | # This file contains modules common to various models 2 | import math 3 | 4 | import torch 5 | import torch.nn as nn 6 | 7 | 8 | def autopad(k, p=None): # kernel, padding 9 | # Pad to 'same' 10 | if p is None: 11 | p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad 12 | return p 13 | 14 | 15 | def DWConv(c1, c2, k=1, s=1, act=True): 16 | # Depthwise convolution 17 | return Conv(c1, c2, k, s, g=math.gcd(c1, c2), act=act) 18 | 19 | 20 | class Conv(nn.Module): 21 | # Standard convolution 22 | def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups 23 | super(Conv, self).__init__() 24 | self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False) 25 | self.bn = nn.BatchNorm2d(c2) 26 | self.act = nn.LeakyReLU(0.1, inplace=True) if act else nn.Identity() 27 | 28 | def forward(self, x): 29 | return self.act(self.bn(self.conv(x))) 30 | 31 | def fuseforward(self, x): 32 | return self.act(self.conv(x)) 33 | 34 | 35 | class Bottleneck(nn.Module): 36 | # Standard bottleneck 37 | def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion 38 | super(Bottleneck, self).__init__() 39 | c_ = int(c2 * e) # hidden channels 40 | self.cv1 = Conv(c1, c_, 1, 1) 41 | self.cv2 = Conv(c_, c2, 3, 1, g=g) 42 | self.add = shortcut and c1 == c2 43 | 44 | def forward(self, x): 45 | return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) 46 | 47 | 48 | class BottleneckCSP(nn.Module): 49 | # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks 50 | def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion 51 | super(BottleneckCSP, self).__init__() 52 | c_ = int(c2 * e) # hidden channels 53 | self.cv1 = Conv(c1, c_, 1, 1) 54 | self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) 55 | self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) 56 | self.cv4 = Conv(2 * c_, c2, 1, 1) 57 | self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3) 58 | self.act = nn.LeakyReLU(0.1, inplace=True) 59 | self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)]) 60 | 61 | def forward(self, x): 62 | y1 = self.cv3(self.m(self.cv1(x))) 63 | y2 = self.cv2(x) 64 | return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1)))) 65 | 66 | 67 | class SPP(nn.Module): 68 | # Spatial pyramid pooling layer used in YOLOv3-SPP 69 | def __init__(self, c1, c2, k=(5, 9, 13)): 70 | super(SPP, self).__init__() 71 | c_ = c1 // 2 # hidden channels 72 | self.cv1 = Conv(c1, c_, 1, 1) 73 | self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1) 74 | self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k]) 75 | 76 | def forward(self, x): 77 | x = self.cv1(x) 78 | return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1)) 79 | 80 | 81 | class Focus(nn.Module): 82 | # Focus wh information into c-space 83 | def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups 84 | super(Focus, self).__init__() 85 | self.conv = Conv(c1 * 4, c2, k, s, p, g, act) 86 | 87 | def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2) 88 | return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)) 89 | 90 | 91 | class Concat(nn.Module): 92 | # Concatenate a list of tensors along dimension 93 | def __init__(self, dimension=1): 94 | super(Concat, self).__init__() 95 | self.d = dimension 96 | 97 | def forward(self, x): 98 | return torch.cat(x, self.d) 99 | 100 | 101 | class Flatten(nn.Module): 102 | # Use after nn.AdaptiveAvgPool2d(1) to remove last 2 dimensions 103 | @staticmethod 104 | def forward(x): 105 | return x.view(x.size(0), -1) 106 | 107 | 108 | class Classify(nn.Module): 109 | # Classification head, i.e. x(b,c1,20,20) to x(b,c2) 110 | def __init__(self, c1, c2, k=1, s=1, p=None, g=1): # ch_in, ch_out, kernel, stride, padding, groups 111 | super(Classify, self).__init__() 112 | self.aap = nn.AdaptiveAvgPool2d(1) # to x(b,c1,1,1) 113 | self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False) # to x(b,c2,1,1) 114 | self.flat = Flatten() 115 | 116 | def forward(self, x): 117 | z = torch.cat([self.aap(y) for y in (x if isinstance(x, list) else [x])], 1) # cat if list 118 | return self.flat(self.conv(z)) # flatten to x(b,c2) 119 | -------------------------------------------------------------------------------- /models/experimental.py: -------------------------------------------------------------------------------- 1 | # This file contains experimental modules 2 | 3 | import numpy as np 4 | import torch 5 | import torch.nn as nn 6 | 7 | from models.common import Conv, DWConv 8 | from utils.google_utils import attempt_download 9 | 10 | 11 | class CrossConv(nn.Module): 12 | # Cross Convolution Downsample 13 | def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False): 14 | # ch_in, ch_out, kernel, stride, groups, expansion, shortcut 15 | super(CrossConv, self).__init__() 16 | c_ = int(c2 * e) # hidden channels 17 | self.cv1 = Conv(c1, c_, (1, k), (1, s)) 18 | self.cv2 = Conv(c_, c2, (k, 1), (s, 1), g=g) 19 | self.add = shortcut and c1 == c2 20 | 21 | def forward(self, x): 22 | return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) 23 | 24 | 25 | class C3(nn.Module): 26 | # Cross Convolution CSP 27 | def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion 28 | super(C3, self).__init__() 29 | c_ = int(c2 * e) # hidden channels 30 | self.cv1 = Conv(c1, c_, 1, 1) 31 | self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) 32 | self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) 33 | self.cv4 = Conv(2 * c_, c2, 1, 1) 34 | self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3) 35 | self.act = nn.LeakyReLU(0.1, inplace=True) 36 | self.m = nn.Sequential(*[CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)]) 37 | 38 | def forward(self, x): 39 | y1 = self.cv3(self.m(self.cv1(x))) 40 | y2 = self.cv2(x) 41 | return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1)))) 42 | 43 | 44 | class Sum(nn.Module): 45 | # Weighted sum of 2 or more layers https://arxiv.org/abs/1911.09070 46 | def __init__(self, n, weight=False): # n: number of inputs 47 | super(Sum, self).__init__() 48 | self.weight = weight # apply weights boolean 49 | self.iter = range(n - 1) # iter object 50 | if weight: 51 | self.w = nn.Parameter(-torch.arange(1., n) / 2, requires_grad=True) # layer weights 52 | 53 | def forward(self, x): 54 | y = x[0] # no weight 55 | if self.weight: 56 | w = torch.sigmoid(self.w) * 2 57 | for i in self.iter: 58 | y = y + x[i + 1] * w[i] 59 | else: 60 | for i in self.iter: 61 | y = y + x[i + 1] 62 | return y 63 | 64 | 65 | class GhostConv(nn.Module): 66 | # Ghost Convolution https://github.com/huawei-noah/ghostnet 67 | def __init__(self, c1, c2, k=1, s=1, g=1, act=True): # ch_in, ch_out, kernel, stride, groups 68 | super(GhostConv, self).__init__() 69 | c_ = c2 // 2 # hidden channels 70 | self.cv1 = Conv(c1, c_, k, s, g, act) 71 | self.cv2 = Conv(c_, c_, 5, 1, c_, act) 72 | 73 | def forward(self, x): 74 | y = self.cv1(x) 75 | return torch.cat([y, self.cv2(y)], 1) 76 | 77 | 78 | class GhostBottleneck(nn.Module): 79 | # Ghost Bottleneck https://github.com/huawei-noah/ghostnet 80 | def __init__(self, c1, c2, k, s): 81 | super(GhostBottleneck, self).__init__() 82 | c_ = c2 // 2 83 | self.conv = nn.Sequential(GhostConv(c1, c_, 1, 1), # pw 84 | DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # dw 85 | GhostConv(c_, c2, 1, 1, act=False)) # pw-linear 86 | self.shortcut = nn.Sequential(DWConv(c1, c1, k, s, act=False), 87 | Conv(c1, c2, 1, 1, act=False)) if s == 2 else nn.Identity() 88 | 89 | def forward(self, x): 90 | return self.conv(x) + self.shortcut(x) 91 | 92 | 93 | class MixConv2d(nn.Module): 94 | # Mixed Depthwise Conv https://arxiv.org/abs/1907.09595 95 | def __init__(self, c1, c2, k=(1, 3), s=1, equal_ch=True): 96 | super(MixConv2d, self).__init__() 97 | groups = len(k) 98 | if equal_ch: # equal c_ per group 99 | i = torch.linspace(0, groups - 1E-6, c2).floor() # c2 indices 100 | c_ = [(i == g).sum() for g in range(groups)] # intermediate channels 101 | else: # equal weight.numel() per group 102 | b = [c2] + [0] * groups 103 | a = np.eye(groups + 1, groups, k=-1) 104 | a -= np.roll(a, 1, axis=1) 105 | a *= np.array(k) ** 2 106 | a[0] = 1 107 | c_ = np.linalg.lstsq(a, b, rcond=None)[0].round() # solve for equal weight indices, ax = b 108 | 109 | self.m = nn.ModuleList([nn.Conv2d(c1, int(c_[g]), k[g], s, k[g] // 2, bias=False) for g in range(groups)]) 110 | self.bn = nn.BatchNorm2d(c2) 111 | self.act = nn.LeakyReLU(0.1, inplace=True) 112 | 113 | def forward(self, x): 114 | return x + self.act(self.bn(torch.cat([m(x) for m in self.m], 1))) 115 | 116 | 117 | class Ensemble(nn.ModuleList): 118 | # Ensemble of models 119 | def __init__(self): 120 | super(Ensemble, self).__init__() 121 | 122 | def forward(self, x, augment=False): 123 | y = [] 124 | for module in self: 125 | y.append(module(x, augment)[0]) 126 | # y = torch.stack(y).max(0)[0] # max ensemble 127 | # y = torch.cat(y, 1) # nms ensemble 128 | y = torch.stack(y).mean(0) # mean ensemble 129 | return y, None # inference, train output 130 | 131 | 132 | def attempt_load(weights, map_location=None): 133 | # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a 134 | model = Ensemble() 135 | for w in weights if isinstance(weights, list) else [weights]: 136 | attempt_download(w) 137 | model.append(torch.load(w, map_location=map_location)['model'].float().fuse().eval()) # load FP32 model 138 | 139 | if len(model) == 1: 140 | return model[-1] # return model 141 | else: 142 | print('Ensemble created with %s\n' % weights) 143 | for k in ['names', 'stride']: 144 | setattr(model, k, getattr(model[-1], k)) 145 | return model # return ensemble 146 | -------------------------------------------------------------------------------- /models/export.py: -------------------------------------------------------------------------------- 1 | """Exports a YOLOv5 *.pt model to ONNX and TorchScript formats 2 | 3 | Usage: 4 | $ export PYTHONPATH="$PWD" && python models/export.py --weights ./weights/yolov5s.pt --img 640 --batch 1 5 | """ 6 | 7 | import argparse 8 | 9 | import torch 10 | 11 | from utils.google_utils import attempt_download 12 | from utils.general import set_logging 13 | 14 | if __name__ == '__main__': 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') 17 | parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') 18 | parser.add_argument('--batch-size', type=int, default=1, help='batch size') 19 | opt = parser.parse_args() 20 | opt.img_size *= 2 if len(opt.img_size) == 1 else 1 # expand 21 | print(opt) 22 | set_logging() 23 | 24 | # Input 25 | img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size(1,3,320,192) iDetection 26 | 27 | # Load PyTorch model 28 | attempt_download(opt.weights) 29 | model = torch.load(opt.weights, map_location=torch.device('cpu'))['model'].float() 30 | model.eval() 31 | model.model[-1].export = True # set Detect() layer export=True 32 | y = model(img) # dry run 33 | 34 | # TorchScript export 35 | try: 36 | print('\nStarting TorchScript export with torch %s...' % torch.__version__) 37 | f = opt.weights.replace('.pt', '.torchscript.pt') # filename 38 | ts = torch.jit.trace(model, img) 39 | ts.save(f) 40 | print('TorchScript export success, saved as %s' % f) 41 | except Exception as e: 42 | print('TorchScript export failure: %s' % e) 43 | 44 | # ONNX export 45 | try: 46 | import onnx 47 | 48 | print('\nStarting ONNX export with onnx %s...' % onnx.__version__) 49 | f = opt.weights.replace('.pt', '.onnx') # filename 50 | model.fuse() # only for ONNX 51 | torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'], 52 | output_names=['classes', 'boxes'] if y is None else ['output']) 53 | 54 | # Checks 55 | onnx_model = onnx.load(f) # load onnx model 56 | onnx.checker.check_model(onnx_model) # check onnx model 57 | print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable model 58 | print('ONNX export success, saved as %s' % f) 59 | except Exception as e: 60 | print('ONNX export failure: %s' % e) 61 | 62 | # CoreML export 63 | try: 64 | import coremltools as ct 65 | 66 | print('\nStarting CoreML export with coremltools %s...' % ct.__version__) 67 | # convert model from torchscript and apply pixel scaling as per detect.py 68 | model = ct.convert(ts, inputs=[ct.ImageType(name='images', shape=img.shape, scale=1 / 255.0, bias=[0, 0, 0])]) 69 | f = opt.weights.replace('.pt', '.mlmodel') # filename 70 | model.save(f) 71 | print('CoreML export success, saved as %s' % f) 72 | except Exception as e: 73 | print('CoreML export failure: %s' % e) 74 | 75 | # Finish 76 | print('\nExport complete. Visualize with https://github.com/lutzroeder/netron.') 77 | -------------------------------------------------------------------------------- /models/hub/yolov3-spp.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.0 # model depth multiple 4 | width_multiple: 1.0 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # darknet53 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Conv, [32, 3, 1]], # 0 16 | [-1, 1, Conv, [64, 3, 2]], # 1-P1/2 17 | [-1, 1, Bottleneck, [64]], 18 | [-1, 1, Conv, [128, 3, 2]], # 3-P2/4 19 | [-1, 2, Bottleneck, [128]], 20 | [-1, 1, Conv, [256, 3, 2]], # 5-P3/8 21 | [-1, 8, Bottleneck, [256]], 22 | [-1, 1, Conv, [512, 3, 2]], # 7-P4/16 23 | [-1, 8, Bottleneck, [512]], 24 | [-1, 1, Conv, [1024, 3, 2]], # 9-P5/32 25 | [-1, 4, Bottleneck, [1024]], # 10 26 | ] 27 | 28 | # YOLOv3-SPP head 29 | head: 30 | [[-1, 1, Bottleneck, [1024, False]], 31 | [-1, 1, SPP, [512, [5, 9, 13]]], 32 | [-1, 1, Conv, [1024, 3, 1]], 33 | [-1, 1, Conv, [512, 1, 1]], 34 | [-1, 1, Conv, [1024, 3, 1]], # 15 (P5/32-large) 35 | 36 | [-2, 1, Conv, [256, 1, 1]], 37 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 38 | [[-1, 8], 1, Concat, [1]], # cat backbone P4 39 | [-1, 1, Bottleneck, [512, False]], 40 | [-1, 1, Bottleneck, [512, False]], 41 | [-1, 1, Conv, [256, 1, 1]], 42 | [-1, 1, Conv, [512, 3, 1]], # 22 (P4/16-medium) 43 | 44 | [-2, 1, Conv, [128, 1, 1]], 45 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 46 | [[-1, 6], 1, Concat, [1]], # cat backbone P3 47 | [-1, 1, Bottleneck, [256, False]], 48 | [-1, 2, Bottleneck, [256, False]], # 27 (P3/8-small) 49 | 50 | [[27, 22, 15], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 51 | ] 52 | -------------------------------------------------------------------------------- /models/hub/yolov5-fpn.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.0 # model depth multiple 4 | width_multiple: 1.0 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, Bottleneck, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 6, BottleneckCSP, [1024]], # 9 25 | ] 26 | 27 | # YOLOv5 FPN head 28 | head: 29 | [[-1, 3, BottleneckCSP, [1024, False]], # 10 (P5/32-large) 30 | 31 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 32 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 33 | [-1, 1, Conv, [512, 1, 1]], 34 | [-1, 3, BottleneckCSP, [512, False]], # 14 (P4/16-medium) 35 | 36 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 37 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 38 | [-1, 1, Conv, [256, 1, 1]], 39 | [-1, 3, BottleneckCSP, [256, False]], # 18 (P3/8-small) 40 | 41 | [[18, 14, 10], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 42 | ] 43 | -------------------------------------------------------------------------------- /models/hub/yolov5-panet.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.0 # model depth multiple 4 | width_multiple: 1.0 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [116,90, 156,198, 373,326] # P5/32 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [10,13, 16,30, 33,23] # P3/8 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 PANet head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P5, P4, P3) 48 | ] 49 | -------------------------------------------------------------------------------- /models/yolo.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import math 3 | import logging 4 | from copy import deepcopy 5 | from pathlib import Path 6 | 7 | import torch 8 | import torch.nn as nn 9 | 10 | from models.common import Conv, Bottleneck, SPP, DWConv, Focus, BottleneckCSP, Concat 11 | from models.experimental import MixConv2d, CrossConv, C3 12 | from utils.general import check_anchor_order, make_divisible, check_file, set_logging 13 | from utils.torch_utils import ( 14 | time_synchronized, fuse_conv_and_bn, model_info, scale_img, initialize_weights, select_device) 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | class Detect(nn.Module): 19 | def __init__(self, nc=80, anchors=(), ch=()): # detection layer 20 | super(Detect, self).__init__() 21 | self.stride = None # strides computed during build 22 | self.nc = nc # number of classes 23 | self.no = nc + 5 # number of outputs per anchor 24 | self.nl = len(anchors) # number of detection layers 25 | self.na = len(anchors[0]) // 2 # number of anchors 26 | self.grid = [torch.zeros(1)] * self.nl # init grid 27 | a = torch.tensor(anchors).float().view(self.nl, -1, 2) 28 | self.register_buffer('anchors', a) # shape(nl,na,2) 29 | self.register_buffer('anchor_grid', a.clone().view(self.nl, 1, -1, 1, 1, 2)) # shape(nl,1,na,1,1,2) 30 | self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch) # output conv 31 | self.export = False # onnx export 32 | 33 | def forward(self, x): 34 | # x = x.copy() # for profiling 35 | z = [] # inference output 36 | self.training |= self.export 37 | for i in range(self.nl): 38 | x[i] = self.m[i](x[i]) # conv 39 | bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85) 40 | x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous() 41 | 42 | if not self.training: # inference 43 | if self.grid[i].shape[2:4] != x[i].shape[2:4]: 44 | self.grid[i] = self._make_grid(nx, ny).to(x[i].device) 45 | 46 | y = x[i].sigmoid() 47 | y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy 48 | y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh 49 | z.append(y.view(bs, -1, self.no)) 50 | 51 | return x if self.training else (torch.cat(z, 1), x) 52 | 53 | @staticmethod 54 | def _make_grid(nx=20, ny=20): 55 | yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)]) 56 | return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float() 57 | 58 | 59 | class Model(nn.Module): 60 | def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None): # model, input channels, number of classes 61 | super(Model, self).__init__() 62 | if isinstance(cfg, dict): 63 | self.yaml = cfg # model dict 64 | else: # is *.yaml 65 | import yaml # for torch hub 66 | self.yaml_file = Path(cfg).name 67 | with open(cfg) as f: 68 | self.yaml = yaml.load(f, Loader=yaml.FullLoader) # model dict 69 | 70 | # Define model 71 | if nc and nc != self.yaml['nc']: 72 | print('Overriding %s nc=%g with nc=%g' % (cfg, self.yaml['nc'], nc)) 73 | self.yaml['nc'] = nc # override yaml value 74 | self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch]) # model, savelist, ch_out 75 | # print([x.shape for x in self.forward(torch.zeros(1, ch, 64, 64))]) 76 | 77 | # Build strides, anchors 78 | m = self.model[-1] # Detect() 79 | if isinstance(m, Detect): 80 | s = 128 # 2x min stride 81 | m.stride = torch.tensor([s / x.shape[-2] for x in self.forward(torch.zeros(1, ch, s, s))]) # forward 82 | m.anchors /= m.stride.view(-1, 1, 1) 83 | check_anchor_order(m) 84 | self.stride = m.stride 85 | self._initialize_biases() # only run once 86 | # print('Strides: %s' % m.stride.tolist()) 87 | 88 | # Init weights, biases 89 | initialize_weights(self) 90 | self.info() 91 | print('') 92 | 93 | def forward(self, x, augment=False, profile=False): 94 | if augment: 95 | img_size = x.shape[-2:] # height, width 96 | s = [1, 0.83, 0.67] # scales 97 | f = [None, 3, None] # flips (2-ud, 3-lr) 98 | y = [] # outputs 99 | for si, fi in zip(s, f): 100 | xi = scale_img(x.flip(fi) if fi else x, si) 101 | yi = self.forward_once(xi)[0] # forward 102 | # cv2.imwrite('img%g.jpg' % s, 255 * xi[0].numpy().transpose((1, 2, 0))[:, :, ::-1]) # save 103 | yi[..., :4] /= si # de-scale 104 | if fi == 2: 105 | yi[..., 1] = img_size[0] - yi[..., 1] # de-flip ud 106 | elif fi == 3: 107 | yi[..., 0] = img_size[1] - yi[..., 0] # de-flip lr 108 | y.append(yi) 109 | return torch.cat(y, 1), None # augmented inference, train 110 | else: 111 | return self.forward_once(x, profile) # single-scale inference, train 112 | 113 | def forward_once(self, x, profile=False): 114 | y, dt = [], [] # outputs 115 | for m in self.model: 116 | if m.f != -1: # if not from previous layer 117 | x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers 118 | 119 | if profile: 120 | try: 121 | import thop 122 | o = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 # FLOPS 123 | except: 124 | o = 0 125 | t = time_synchronized() 126 | for _ in range(10): 127 | _ = m(x) 128 | dt.append((time_synchronized() - t) * 100) 129 | print('%10.1f%10.0f%10.1fms %-40s' % (o, m.np, dt[-1], m.type)) 130 | 131 | x = m(x) # run 132 | y.append(x if m.i in self.save else None) # save output 133 | 134 | if profile: 135 | print('%.1fms total' % sum(dt)) 136 | return x 137 | 138 | def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is class frequency 139 | # cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1. 140 | m = self.model[-1] # Detect() module 141 | for mi, s in zip(m.m, m.stride): # from 142 | b = mi.bias.view(m.na, -1) # conv.bias(255) to (3,85) 143 | b[:, 4] += math.log(8 / (640 / s) ** 2) # obj (8 objects per 640 image) 144 | b[:, 5:] += math.log(0.6 / (m.nc - 0.99)) if cf is None else torch.log(cf / cf.sum()) # cls 145 | mi.bias = torch.nn.Parameter(b.view(-1), requires_grad=True) 146 | 147 | def _print_biases(self): 148 | m = self.model[-1] # Detect() module 149 | for mi in m.m: # from 150 | b = mi.bias.detach().view(m.na, -1).T # conv.bias(255) to (3,85) 151 | print(('%6g Conv2d.bias:' + '%10.3g' * 6) % (mi.weight.shape[1], *b[:5].mean(1).tolist(), b[5:].mean())) 152 | 153 | # def _print_weights(self): 154 | # for m in self.model.modules(): 155 | # if type(m) is Bottleneck: 156 | # print('%10.3g' % (m.w.detach().sigmoid() * 2)) # shortcut weights 157 | 158 | def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers 159 | print('Fusing layers... ') 160 | for m in self.model.modules(): 161 | if type(m) is Conv: 162 | m._non_persistent_buffers_set = set() # pytorch 1.6.0 compatability 163 | m.conv = fuse_conv_and_bn(m.conv, m.bn) # update conv 164 | m.bn = None # remove batchnorm 165 | m.forward = m.fuseforward # update forward 166 | self.info() 167 | return self 168 | 169 | def info(self): # print model information 170 | model_info(self) 171 | 172 | 173 | def parse_model(d, ch): # model_dict, input_channels(3) 174 | logger.info('\n%3s%18s%3s%10s %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments')) 175 | anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple'] 176 | na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors 177 | no = na * (nc + 5) # number of outputs = anchors * (classes + 5) 178 | 179 | layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out 180 | for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args 181 | m = eval(m) if isinstance(m, str) else m # eval strings 182 | for j, a in enumerate(args): 183 | try: 184 | args[j] = eval(a) if isinstance(a, str) else a # eval strings 185 | except: 186 | pass 187 | 188 | n = max(round(n * gd), 1) if n > 1 else n # depth gain 189 | if m in [nn.Conv2d, Conv, Bottleneck, SPP, DWConv, MixConv2d, Focus, CrossConv, BottleneckCSP, C3]: 190 | c1, c2 = ch[f], args[0] 191 | 192 | # Normal 193 | # if i > 0 and args[0] != no: # channel expansion factor 194 | # ex = 1.75 # exponential (default 2.0) 195 | # e = math.log(c2 / ch[1]) / math.log(2) 196 | # c2 = int(ch[1] * ex ** e) 197 | # if m != Focus: 198 | 199 | c2 = make_divisible(c2 * gw, 8) if c2 != no else c2 200 | 201 | # Experimental 202 | # if i > 0 and args[0] != no: # channel expansion factor 203 | # ex = 1 + gw # exponential (default 2.0) 204 | # ch1 = 32 # ch[1] 205 | # e = math.log(c2 / ch1) / math.log(2) # level 1-n 206 | # c2 = int(ch1 * ex ** e) 207 | # if m != Focus: 208 | # c2 = make_divisible(c2, 8) if c2 != no else c2 209 | 210 | args = [c1, c2, *args[1:]] 211 | if m in [BottleneckCSP, C3]: 212 | args.insert(2, n) 213 | n = 1 214 | elif m is nn.BatchNorm2d: 215 | args = [ch[f]] 216 | elif m is Concat: 217 | c2 = sum([ch[-1 if x == -1 else x + 1] for x in f]) 218 | elif m is Detect: 219 | args.append([ch[x + 1] for x in f]) 220 | if isinstance(args[1], int): # number of anchors 221 | args[1] = [list(range(args[1] * 2))] * len(f) 222 | else: 223 | c2 = ch[f] 224 | 225 | m_ = nn.Sequential(*[m(*args) for _ in range(n)]) if n > 1 else m(*args) # module 226 | t = str(m)[8:-2].replace('__main__.', '') # module type 227 | np = sum([x.numel() for x in m_.parameters()]) # number params 228 | m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params 229 | logger.info('%3s%18s%3s%10.0f %-40s%-30s' % (i, f, n, np, t, args)) # print 230 | save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist 231 | layers.append(m_) 232 | ch.append(c2) 233 | return nn.Sequential(*layers), sorted(save) 234 | 235 | 236 | if __name__ == '__main__': 237 | parser = argparse.ArgumentParser() 238 | parser.add_argument('--cfg', type=str, default='yolov5s.yaml', help='model.yaml') 239 | parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') 240 | opt = parser.parse_args() 241 | opt.cfg = check_file(opt.cfg) # check file 242 | set_logging() 243 | device = select_device(opt.device) 244 | 245 | # Create model 246 | model = Model(opt.cfg).to(device) 247 | model.train() 248 | 249 | # Profile 250 | # img = torch.rand(8 if torch.cuda.is_available() else 1, 3, 640, 640).to(device) 251 | # y = model(img, profile=True) 252 | 253 | # ONNX export 254 | # model.model[-1].export = True 255 | # torch.onnx.export(model, img, opt.cfg.replace('.yaml', '.onnx'), verbose=True, opset_version=11) 256 | 257 | # Tensorboard 258 | # from torch.utils.tensorboard import SummaryWriter 259 | # tb_writer = SummaryWriter() 260 | # print("Run 'tensorboard --logdir=models/runs' to view tensorboard at http://localhost:6006/") 261 | # tb_writer.add_graph(model.model, img) # add model to tensorboard 262 | # tb_writer.add_image('test', img[0], dataformats='CWH') # add model to tensorboard 263 | -------------------------------------------------------------------------------- /models/yolov5l.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.0 # model depth multiple 4 | width_multiple: 1.0 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 48 | ] 49 | -------------------------------------------------------------------------------- /models/yolov5m.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 0.67 # model depth multiple 4 | width_multiple: 0.75 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 48 | ] 49 | -------------------------------------------------------------------------------- /models/yolov5s.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 0.33 # model depth multiple 4 | width_multiple: 0.50 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 48 | ] 49 | -------------------------------------------------------------------------------- /models/yolov5x.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.33 # model depth multiple 4 | width_multiple: 1.25 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 48 | ] 49 | -------------------------------------------------------------------------------- /person_count.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2021/1/18 下午6:02 4 | # @Author : zengwb 5 | 6 | import os 7 | import cv2 8 | import torch 9 | import warnings 10 | import argparse 11 | import numpy as np 12 | import onnxruntime as ort 13 | from utils.datasets import LoadStreams, LoadImages 14 | from utils.draw import draw_boxes 15 | from utils.general import check_img_size 16 | from utils.torch_utils import time_synchronized 17 | from person_detect_yolov5 import Person_detect 18 | from deep_sort import build_tracker 19 | from utils.parser import get_config 20 | from utils.log import get_logger 21 | from utils.torch_utils import select_device, load_classifier, time_synchronized 22 | # count 23 | from collections import Counter 24 | from collections import deque 25 | import math 26 | from PIL import Image, ImageDraw, ImageFont 27 | 28 | def tlbr_midpoint(box): 29 | minX, minY, maxX, maxY = box 30 | midpoint = (int((minX + maxX) / 2), int((minY + maxY) / 2)) # minus y coordinates to get proper xy format 31 | return midpoint 32 | 33 | 34 | def intersect(A, B, C, D): 35 | return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D) 36 | 37 | 38 | def ccw(A, B, C): 39 | return (C[1] - A[1]) * (B[0] - A[0]) > (B[1] - A[1]) * (C[0] - A[0]) 40 | 41 | 42 | def vector_angle(midpoint, previous_midpoint): 43 | x = midpoint[0] - previous_midpoint[0] 44 | y = midpoint[1] - previous_midpoint[1] 45 | return math.degrees(math.atan2(y, x)) 46 | 47 | 48 | def get_size_with_pil(label,size=25): 49 | font = ImageFont.truetype("./configs/simkai.ttf", size, encoding="utf-8") # simhei.ttf 50 | return font.getsize(label) 51 | 52 | 53 | #为了支持中文,用pil 54 | def put_text_to_cv2_img_with_pil(cv2_img,label,pt,color): 55 | pil_img = cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB) # cv2和PIL中颜色的hex码的储存顺序不同,需转RGB模式 56 | pilimg = Image.fromarray(pil_img) # Image.fromarray()将数组类型转成图片格式,与np.array()相反 57 | draw = ImageDraw.Draw(pilimg) # PIL图片上打印汉字 58 | font = ImageFont.truetype("./configs/simkai.ttf", 25, encoding="utf-8") #simhei.ttf 59 | draw.text(pt, label, color,font=font) 60 | return cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR) # 将图片转成cv2.imshow()可以显示的数组格式 61 | 62 | 63 | colors = np.array([ 64 | [1,0,1], 65 | [0,0,1], 66 | [0,1,1], 67 | [0,1,0], 68 | [1,1,0], 69 | [1,0,0] 70 | ]); 71 | 72 | def get_color(c, x, max): 73 | ratio = (x / max) * 5; 74 | i = math.floor(ratio); 75 | j = math.ceil(ratio); 76 | ratio -= i; 77 | r = (1 - ratio) * colors[i][c] + ratio * colors[j][c]; 78 | return r; 79 | 80 | def compute_color_for_labels(class_id,class_total=80): 81 | offset = (class_id + 0) * 123457 % class_total; 82 | red = get_color(2, offset, class_total); 83 | green = get_color(1, offset, class_total); 84 | blue = get_color(0, offset, class_total); 85 | return (int(red*256),int(green*256),int(blue*256)) 86 | 87 | 88 | class yolo_reid(): 89 | def __init__(self, cfg, args, path): 90 | self.logger = get_logger("root") 91 | self.args = args 92 | self.video_path = path 93 | use_cuda = args.use_cuda and torch.cuda.is_available() 94 | if not use_cuda: 95 | warnings.warn("Running in cpu mode which maybe very slow!", UserWarning) 96 | 97 | self.person_detect = Person_detect(self.args, self.video_path) 98 | imgsz = check_img_size(args.img_size, s=32) # self.model.stride.max()) # check img_size 99 | self.dataset = LoadImages(self.video_path, img_size=imgsz) 100 | self.deepsort = build_tracker(cfg, args.sort, use_cuda=use_cuda) 101 | 102 | def deep_sort(self): 103 | idx_frame = 0 104 | results = [] 105 | paths = {} 106 | track_cls = 0 107 | last_track_id = -1 108 | total_track = 0 109 | angle = -1 110 | total_counter = 0 111 | up_count = 0 112 | down_count = 0 113 | class_counter = Counter() # store counts of each detected class 114 | already_counted = deque(maxlen=50) # temporary memory for storing counted IDs 115 | for video_path, img, ori_img, vid_cap in self.dataset: 116 | idx_frame += 1 117 | # print('aaaaaaaa', video_path, img.shape, im0s.shape, vid_cap) 118 | t1 = time_synchronized() 119 | 120 | # yolo detection 121 | bbox_xywh, cls_conf, cls_ids, xy = self.person_detect.detect(video_path, img, ori_img, vid_cap) 122 | 123 | # do tracking 124 | outputs = self.deepsort.update(bbox_xywh, cls_conf, ori_img) 125 | 126 | # 1.视频中间画行黄线 127 | line = [(0, int(0.48 * ori_img.shape[0])), (int(ori_img.shape[1]), int(0.48 * ori_img.shape[0]))] 128 | cv2.line(ori_img, line[0], line[1], (0, 255, 255), 4) 129 | 130 | # 2. 统计人数 131 | for track in outputs: 132 | bbox = track[:4] 133 | track_id = track[-1] 134 | midpoint = tlbr_midpoint(bbox) 135 | origin_midpoint = (midpoint[0], ori_img.shape[0] - midpoint[1]) # get midpoint respective to botton-left 136 | 137 | if track_id not in paths: 138 | paths[track_id] = deque(maxlen=2) 139 | total_track = track_id 140 | paths[track_id].append(midpoint) 141 | previous_midpoint = paths[track_id][0] 142 | origin_previous_midpoint = (previous_midpoint[0], ori_img.shape[0] - previous_midpoint[1]) 143 | 144 | if intersect(midpoint, previous_midpoint, line[0], line[1]) and track_id not in already_counted: 145 | class_counter[track_cls] += 1 146 | total_counter += 1 147 | last_track_id = track_id; 148 | # draw red line 149 | cv2.line(ori_img, line[0], line[1], (0, 0, 255), 10) 150 | 151 | already_counted.append(track_id) # Set already counted for ID to true. 152 | 153 | angle = vector_angle(origin_midpoint, origin_previous_midpoint) 154 | 155 | if angle > 0: 156 | up_count += 1 157 | if angle < 0: 158 | down_count += 1 159 | 160 | if len(paths) > 50: 161 | del paths[list(paths)[0]] 162 | 163 | # 3. 绘制人员 164 | if len(outputs) > 0: 165 | bbox_tlwh = [] 166 | bbox_xyxy = outputs[:, :4] 167 | identities = outputs[:, -1] 168 | ori_img = draw_boxes(ori_img, bbox_xyxy, identities) 169 | 170 | for bb_xyxy in bbox_xyxy: 171 | bbox_tlwh.append(self.deepsort._xyxy_to_tlwh(bb_xyxy)) 172 | 173 | # results.append((idx_frame - 1, bbox_tlwh, identities)) 174 | print("yolo+deepsort:", time_synchronized() - t1) 175 | 176 | # 4. 绘制统计信息 177 | label = "客流总数: {}".format(str(total_track)) 178 | t_size = get_size_with_pil(label, 25) 179 | x1 = 20 180 | y1 = 50 181 | color = compute_color_for_labels(2) 182 | cv2.rectangle(ori_img, (x1 - 1, y1), (x1 + t_size[0] + 10, y1 - t_size[1]), color, 2) 183 | ori_img = put_text_to_cv2_img_with_pil(ori_img, label, (x1 + 5, y1 - t_size[1] - 2), (0, 0, 0)) 184 | 185 | label = "穿过黄线人数: {} ({} 向上, {} 向下)".format(str(total_counter), str(up_count), str(down_count)) 186 | t_size = get_size_with_pil(label, 25) 187 | x1 = 20 188 | y1 = 100 189 | color = compute_color_for_labels(2) 190 | cv2.rectangle(ori_img, (x1 - 1, y1), (x1 + t_size[0] + 10, y1 - t_size[1]), color, 2) 191 | ori_img = put_text_to_cv2_img_with_pil(ori_img, label, (x1 + 5, y1 - t_size[1] - 2), (0, 0, 0)) 192 | 193 | if last_track_id >= 0: 194 | label = "最新: 行人{}号{}穿过黄线".format(str(last_track_id), str("向上") if angle >= 0 else str('向下')) 195 | t_size = get_size_with_pil(label, 25) 196 | x1 = 20 197 | y1 = 150 198 | color = compute_color_for_labels(2) 199 | cv2.rectangle(ori_img, (x1 - 1, y1), (x1 + t_size[0] + 10, y1 - t_size[1]), color, 2) 200 | ori_img = put_text_to_cv2_img_with_pil(ori_img, label, (x1 + 5, y1 - t_size[1] - 2), (255, 0, 0)) 201 | 202 | end = time_synchronized() 203 | 204 | if self.args.display: 205 | cv2.imshow("test", ori_img) 206 | if cv2.waitKey(1) & 0xFF == ord('q'): 207 | break 208 | 209 | self.logger.info("{}/time: {:.03f}s, fps: {:.03f}, detection numbers: {}, tracking numbers: {}" \ 210 | .format(idx_frame, end - t1, 1 / (end - t1), 211 | bbox_xywh.shape[0], len(outputs))) 212 | 213 | def parse_args(): 214 | parser = argparse.ArgumentParser() 215 | parser.add_argument("--video_path", default='./MOT16-03.mp4', type=str) 216 | parser.add_argument("--camera", action="store", dest="cam", type=int, default="-1") 217 | parser.add_argument('--device', default='cuda:0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') 218 | # yolov5 219 | parser.add_argument('--weights', nargs='+', type=str, default='./weights/yolov5s.pt', help='model.pt path(s)') 220 | parser.add_argument('--img-size', type=int, default=960, help='inference size (pixels)') 221 | parser.add_argument('--conf-thres', type=float, default=0.4, help='object confidence threshold') 222 | parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS') 223 | parser.add_argument('--classes', default=[0], type=int, help='filter by class: --class 0, or --class 0 2 3') 224 | parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') 225 | parser.add_argument('--augment', action='store_true', help='augmented inference') 226 | 227 | # deep_sort 228 | parser.add_argument("--sort", default=True, help='True: sort model, False: reid model') 229 | parser.add_argument("--config_deepsort", type=str, default="./configs/deep_sort.yaml") 230 | parser.add_argument("--display", default=True, help='show resule') 231 | parser.add_argument("--frame_interval", type=int, default=1) 232 | parser.add_argument("--cpu", dest="use_cuda", action="store_false", default=True) 233 | 234 | return parser.parse_args() 235 | 236 | 237 | if __name__ == '__main__': 238 | args = parse_args() 239 | cfg = get_config() 240 | cfg.merge_from_file(args.config_deepsort) 241 | 242 | yolo_reid = yolo_reid(cfg, args, path=args.video_path) 243 | with torch.no_grad(): 244 | yolo_reid.deep_sort() 245 | -------------------------------------------------------------------------------- /person_detect_yolov5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2021/1/18 下午6:02 4 | # @Author : zengwb 5 | 6 | import argparse 7 | import os 8 | import time 9 | import platform 10 | import shutil 11 | from pathlib import Path 12 | 13 | import cv2 14 | import torch 15 | import torch.backends.cudnn as cudnn 16 | import numpy as np 17 | 18 | from models.experimental import attempt_load 19 | from utils.datasets import LoadStreams, LoadImages 20 | from utils.general import ( 21 | check_img_size, non_max_suppression, apply_classifier, scale_coords, 22 | xyxy2xywh, plot_one_box, strip_optimizer, set_logging) 23 | from utils.torch_utils import select_device, load_classifier, time_synchronized 24 | 25 | 26 | def set_parser(): 27 | parser = argparse.ArgumentParser() 28 | # parser.add_argument('--source', type=str, default='/media/zengwb/PC/Dataset/ReID-dataset/channel1/1.mp4', 29 | # help='source') # file/folder, 0 for webcam 30 | # parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder 31 | # parser.add_argument('--img-size', type=int, default=960, help='inference size (pixels)') 32 | parser.add_argument('--conf-thres', type=float, default=0.4, help='object confidence threshold') 33 | parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS') 34 | 35 | parser.add_argument('--view-img', default=True, help='display results') 36 | parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') 37 | 38 | 39 | parser.add_argument('--augment', action='store_true', help='augmented inference') 40 | parser.add_argument('--update', action='store_true', help='update all models') 41 | 42 | return parser.parse_args() 43 | 44 | 45 | def bbox_r(width, height, *xyxy): 46 | """" Calculates the relative bounding box from absolute pixel values. """ 47 | bbox_left = min([xyxy[0].item(), xyxy[2].item()]) 48 | bbox_top = min([xyxy[1].item(), xyxy[3].item()]) 49 | bbox_w = abs(xyxy[0].item() - xyxy[2].item()) 50 | bbox_h = abs(xyxy[1].item() - xyxy[3].item()) 51 | x_c = (bbox_left + bbox_w / 2) 52 | y_c = (bbox_top + bbox_h / 2) 53 | w = bbox_w 54 | h = bbox_h 55 | return x_c, y_c, w, h 56 | 57 | 58 | class Person_detect(): 59 | def __init__(self, opt, source): 60 | 61 | # Initialize 62 | self.device = opt.device if torch.cuda.is_available() else 'cpu' 63 | self.half = self.device != 'cpu' # half precision only supported on CUDA 64 | self.augment = opt.augment 65 | self.conf_thres = opt.conf_thres 66 | self.iou_thres = opt.iou_thres 67 | self.classes = opt.classes 68 | self.agnostic_nms = opt.agnostic_nms 69 | self.webcam = opt.cam 70 | # Load model 71 | self.model = attempt_load(opt.weights, map_location=self.device) # load FP32 model 72 | print('111111111111111111111111111111111111111', self.model.stride.max()) 73 | if self.half: 74 | self.model.half() # to FP16 75 | 76 | # Get names and colors 77 | self.names = self.model.module.names if hasattr(self.model, 'module') else self.model.names 78 | self.colors = [[np.random.randint(0, 255) for _ in range(3)] for _ in range(len(self.names))] 79 | 80 | def detect(self, path, img, im0s, vid_cap): 81 | 82 | half = self.device != 'cpu' # half precision only supported on CUDA 83 | 84 | # print('444444444444444444444444444444444') 85 | # Run inference 86 | # print('55555555555555555555555555555') 87 | img = torch.from_numpy(img).to(self.device) 88 | img = img.half() if half else img.float() # uint8 to fp16/32 89 | img /= 255.0 # 0 - 255 to 0.0 - 1.0 90 | if img.ndimension() == 3: 91 | img = img.unsqueeze(0) 92 | 93 | # Inference 94 | t1 = time_synchronized() 95 | pred = self.model(img, augment=self.augment)[0] 96 | 97 | # Apply NMS 98 | pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, classes=self.classes, 99 | agnostic=self.agnostic_nms) 100 | 101 | # Process detections 102 | bbox_xywh = [] 103 | confs = [] 104 | clas = [] 105 | xy = [] 106 | for i, det in enumerate(pred): # detections per image 107 | # if self.webcam: # batch_size >= 1 108 | # p, s, im0 = path[i], '%g: ' % i, im0s[i].copy() 109 | # else: 110 | # p, s, im0 = path, '', im0s 111 | if det is not None and len(det): 112 | # Rescale boxes from img_size to im0 size 113 | det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0s.shape).round() 114 | 115 | # Write results 116 | for *xyxy, conf, cls in reversed(det): 117 | img_h, img_w, _ = im0s.shape # get image shape 118 | x_c, y_c, bbox_w, bbox_h = bbox_r(img_w, img_h, *xyxy) 119 | obj = [x_c, y_c, bbox_w, bbox_h] 120 | # if cls == opt.classes: # detct classes id 121 | if not conf.item() > 0.3: 122 | continue 123 | bbox_xywh.append(obj) 124 | confs.append(conf.item()) 125 | clas.append(cls.item()) 126 | xy.append(xyxy) 127 | # print('jjjjjjjjjjjjjjjjjjjj', confs) 128 | return np.array(bbox_xywh), confs, clas, xy 129 | 130 | 131 | if __name__ == '__main__': 132 | person_detect = Person_detect(source='/media/zengwb/PC/Dataset/ReID-dataset/channel1/1.mp4') 133 | with torch.no_grad(): 134 | person_detect.detect() 135 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # pip install -r requirements.txt 2 | Cython 3 | matplotlib>=3.2.2 4 | numpy>=1.18.5 5 | opencv-python>=4.1.2 6 | pillow 7 | # pycocotools>=2.0 8 | PyYAML>=5.3 9 | scipy>=1.4.1 10 | tensorboard>=2.2 11 | torch>=1.6.0 12 | torchvision>=0.7.0 13 | tqdm>=4.41.0 14 | 15 | # Conda commands (in place of pip) --------------------------------------------- 16 | # conda update -yn base -c defaults conda 17 | # conda install -yc anaconda numpy opencv matplotlib tqdm pillow ipython 18 | # conda install -yc conda-forge scikit-image pycocotools tensorboard 19 | # conda install -yc spyder-ide spyder-line-profiler 20 | # conda install -yc pytorch pytorch torchvision 21 | # conda install -yc conda-forge protobuf numpy && pip install onnx==1.6.0 # https://github.com/onnx/onnx#linux-and-macos 22 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import glob 3 | import json 4 | import os 5 | import shutil 6 | from pathlib import Path 7 | 8 | import numpy as np 9 | import torch 10 | import yaml 11 | from tqdm import tqdm 12 | 13 | from models.experimental import attempt_load 14 | from utils.datasets import create_dataloader 15 | from utils.general import ( 16 | coco80_to_coco91_class, check_dataset, check_file, check_img_size, compute_loss, non_max_suppression, scale_coords, 17 | xyxy2xywh, clip_coords, plot_images, xywh2xyxy, box_iou, output_to_target, ap_per_class, set_logging) 18 | from utils.torch_utils import select_device, time_synchronized 19 | 20 | 21 | def test(data, 22 | weights=None, 23 | batch_size=16, 24 | imgsz=640, 25 | conf_thres=0.001, 26 | iou_thres=0.6, # for NMS 27 | save_json=False, 28 | single_cls=False, 29 | augment=False, 30 | verbose=False, 31 | model=None, 32 | dataloader=None, 33 | save_dir='', 34 | merge=False, 35 | save_txt=False): 36 | # Initialize/load model and set device 37 | training = model is not None 38 | if training: # called by train.py 39 | device = next(model.parameters()).device # get model device 40 | 41 | else: # called directly 42 | set_logging() 43 | device = select_device(opt.device, batch_size=batch_size) 44 | merge, save_txt = opt.merge, opt.save_txt # use Merge NMS, save *.txt labels 45 | if save_txt: 46 | out = Path('inference/output') 47 | if os.path.exists(out): 48 | shutil.rmtree(out) # delete output folder 49 | os.makedirs(out) # make new output folder 50 | 51 | # Remove previous 52 | for f in glob.glob(str(Path(save_dir) / 'test_batch*.jpg')): 53 | os.remove(f) 54 | 55 | # Load model 56 | model = attempt_load(weights, map_location=device) # load FP32 model 57 | imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size 58 | 59 | # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 60 | # if device.type != 'cpu' and torch.cuda.device_count() > 1: 61 | # model = nn.DataParallel(model) 62 | 63 | # Half 64 | half = device.type != 'cpu' # half precision only supported on CUDA 65 | if half: 66 | model.half() 67 | 68 | # Configure 69 | model.eval() 70 | with open(data) as f: 71 | data = yaml.load(f, Loader=yaml.FullLoader) # model dict 72 | check_dataset(data) # check 73 | nc = 1 if single_cls else int(data['nc']) # number of classes 74 | iouv = torch.linspace(0.5, 0.95, 10).to(device) # iou vector for mAP@0.5:0.95 75 | niou = iouv.numel() 76 | 77 | # Dataloader 78 | if not training: 79 | img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img 80 | _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once 81 | path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images 82 | dataloader = create_dataloader(path, imgsz, batch_size, model.stride.max(), opt, 83 | hyp=None, augment=False, cache=False, pad=0.5, rect=True)[0] 84 | 85 | seen = 0 86 | names = model.names if hasattr(model, 'names') else model.module.names 87 | coco91class = coco80_to_coco91_class() 88 | s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Targets', 'P', 'R', 'mAP@.5', 'mAP@.5:.95') 89 | p, r, f1, mp, mr, map50, map, t0, t1 = 0., 0., 0., 0., 0., 0., 0., 0., 0. 90 | loss = torch.zeros(3, device=device) 91 | jdict, stats, ap, ap_class = [], [], [], [] 92 | for batch_i, (img, targets, paths, shapes) in enumerate(tqdm(dataloader, desc=s)): 93 | img = img.to(device, non_blocking=True) 94 | img = img.half() if half else img.float() # uint8 to fp16/32 95 | img /= 255.0 # 0 - 255 to 0.0 - 1.0 96 | targets = targets.to(device) 97 | nb, _, height, width = img.shape # batch size, channels, height, width 98 | whwh = torch.Tensor([width, height, width, height]).to(device) 99 | 100 | # Disable gradients 101 | with torch.no_grad(): 102 | # Run model 103 | t = time_synchronized() 104 | inf_out, train_out = model(img, augment=augment) # inference and training outputs 105 | t0 += time_synchronized() - t 106 | 107 | # Compute loss 108 | if training: # if model has loss hyperparameters 109 | loss += compute_loss([x.float() for x in train_out], targets, model)[1][:3] # GIoU, obj, cls 110 | 111 | # Run NMS 112 | t = time_synchronized() 113 | output = non_max_suppression(inf_out, conf_thres=conf_thres, iou_thres=iou_thres, merge=merge) 114 | t1 += time_synchronized() - t 115 | 116 | # Statistics per image 117 | for si, pred in enumerate(output): 118 | labels = targets[targets[:, 0] == si, 1:] 119 | nl = len(labels) 120 | tcls = labels[:, 0].tolist() if nl else [] # target class 121 | seen += 1 122 | 123 | if pred is None: 124 | if nl: 125 | stats.append((torch.zeros(0, niou, dtype=torch.bool), torch.Tensor(), torch.Tensor(), tcls)) 126 | continue 127 | 128 | # Append to text file 129 | if save_txt: 130 | gn = torch.tensor(shapes[si][0])[[1, 0, 1, 0]] # normalization gain whwh 131 | txt_path = str(out / Path(paths[si]).stem) 132 | pred[:, :4] = scale_coords(img[si].shape[1:], pred[:, :4], shapes[si][0], shapes[si][1]) # to original 133 | for *xyxy, conf, cls in pred: 134 | xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh 135 | with open(txt_path + '.txt', 'a') as f: 136 | f.write(('%g ' * 5 + '\n') % (cls, *xywh)) # label format 137 | 138 | # Clip boxes to image bounds 139 | clip_coords(pred, (height, width)) 140 | 141 | # Append to pycocotools JSON dictionary 142 | if save_json: 143 | # [{"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}, ... 144 | image_id = Path(paths[si]).stem 145 | box = pred[:, :4].clone() # xyxy 146 | scale_coords(img[si].shape[1:], box, shapes[si][0], shapes[si][1]) # to original shape 147 | box = xyxy2xywh(box) # xywh 148 | box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner 149 | for p, b in zip(pred.tolist(), box.tolist()): 150 | jdict.append({'image_id': int(image_id) if image_id.isnumeric() else image_id, 151 | 'category_id': coco91class[int(p[5])], 152 | 'bbox': [round(x, 3) for x in b], 153 | 'score': round(p[4], 5)}) 154 | 155 | # Assign all predictions as incorrect 156 | correct = torch.zeros(pred.shape[0], niou, dtype=torch.bool, device=device) 157 | if nl: 158 | detected = [] # target indices 159 | tcls_tensor = labels[:, 0] 160 | 161 | # target boxes 162 | tbox = xywh2xyxy(labels[:, 1:5]) * whwh 163 | 164 | # Per target class 165 | for cls in torch.unique(tcls_tensor): 166 | ti = (cls == tcls_tensor).nonzero(as_tuple=False).view(-1) # prediction indices 167 | pi = (cls == pred[:, 5]).nonzero(as_tuple=False).view(-1) # target indices 168 | 169 | # Search for detections 170 | if pi.shape[0]: 171 | # Prediction to target ious 172 | ious, i = box_iou(pred[pi, :4], tbox[ti]).max(1) # best ious, indices 173 | 174 | # Append detections 175 | for j in (ious > iouv[0]).nonzero(as_tuple=False): 176 | d = ti[i[j]] # detected target 177 | if d not in detected: 178 | detected.append(d) 179 | correct[pi[j]] = ious[j] > iouv # iou_thres is 1xn 180 | if len(detected) == nl: # all targets already located in image 181 | break 182 | 183 | # Append statistics (correct, conf, pcls, tcls) 184 | stats.append((correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls)) 185 | 186 | # Plot images 187 | if batch_i < 1: 188 | f = Path(save_dir) / ('test_batch%g_gt.jpg' % batch_i) # filename 189 | plot_images(img, targets, paths, str(f), names) # ground truth 190 | f = Path(save_dir) / ('test_batch%g_pred.jpg' % batch_i) 191 | plot_images(img, output_to_target(output, width, height), paths, str(f), names) # predictions 192 | 193 | # Compute statistics 194 | stats = [np.concatenate(x, 0) for x in zip(*stats)] # to numpy 195 | if len(stats) and stats[0].any(): 196 | p, r, ap, f1, ap_class = ap_per_class(*stats) 197 | p, r, ap50, ap = p[:, 0], r[:, 0], ap[:, 0], ap.mean(1) # [P, R, AP@0.5, AP@0.5:0.95] 198 | mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean() 199 | nt = np.bincount(stats[3].astype(np.int64), minlength=nc) # number of targets per class 200 | else: 201 | nt = torch.zeros(1) 202 | 203 | # Print results 204 | pf = '%20s' + '%12.3g' * 6 # print format 205 | print(pf % ('all', seen, nt.sum(), mp, mr, map50, map)) 206 | 207 | # Print results per class 208 | if verbose and nc > 1 and len(stats): 209 | for i, c in enumerate(ap_class): 210 | print(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i])) 211 | 212 | # Print speeds 213 | t = tuple(x / seen * 1E3 for x in (t0, t1, t0 + t1)) + (imgsz, imgsz, batch_size) # tuple 214 | if not training: 215 | print('Speed: %.1f/%.1f/%.1f ms inference/NMS/total per %gx%g image at batch-size %g' % t) 216 | 217 | # Save JSON 218 | if save_json and len(jdict): 219 | f = 'detections_val2017_%s_results.json' % \ 220 | (weights.split(os.sep)[-1].replace('.pt', '') if isinstance(weights, str) else '') # filename 221 | print('\nCOCO mAP with pycocotools... saving %s...' % f) 222 | with open(f, 'w') as file: 223 | json.dump(jdict, file) 224 | 225 | try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb 226 | from pycocotools.coco import COCO 227 | from pycocotools.cocoeval import COCOeval 228 | 229 | imgIds = [int(Path(x).stem) for x in dataloader.dataset.img_files] 230 | cocoGt = COCO(glob.glob('../coco/annotations/instances_val*.json')[0]) # initialize COCO ground truth api 231 | cocoDt = cocoGt.loadRes(f) # initialize COCO pred api 232 | cocoEval = COCOeval(cocoGt, cocoDt, 'bbox') 233 | cocoEval.params.imgIds = imgIds # image IDs to evaluate 234 | cocoEval.evaluate() 235 | cocoEval.accumulate() 236 | cocoEval.summarize() 237 | map, map50 = cocoEval.stats[:2] # update results (mAP@0.5:0.95, mAP@0.5) 238 | except Exception as e: 239 | print('ERROR: pycocotools unable to run: %s' % e) 240 | 241 | # Return results 242 | model.float() # for training 243 | maps = np.zeros(nc) + map 244 | for i, c in enumerate(ap_class): 245 | maps[c] = ap[i] 246 | return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t 247 | 248 | 249 | if __name__ == '__main__': 250 | parser = argparse.ArgumentParser(prog='test.py') 251 | parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)') 252 | parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path') 253 | parser.add_argument('--batch-size', type=int, default=32, help='size of each image batch') 254 | parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') 255 | parser.add_argument('--conf-thres', type=float, default=0.001, help='object confidence threshold') 256 | parser.add_argument('--iou-thres', type=float, default=0.65, help='IOU threshold for NMS') 257 | parser.add_argument('--save-json', action='store_true', help='save a cocoapi-compatible JSON results file') 258 | parser.add_argument('--task', default='val', help="'val', 'test', 'study'") 259 | parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') 260 | parser.add_argument('--single-cls', action='store_true', help='treat as single-class dataset') 261 | parser.add_argument('--augment', action='store_true', help='augmented inference') 262 | parser.add_argument('--merge', action='store_true', help='use Merge NMS') 263 | parser.add_argument('--verbose', action='store_true', help='report mAP by class') 264 | parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') 265 | opt = parser.parse_args() 266 | opt.save_json |= opt.data.endswith('coco.yaml') 267 | opt.data = check_file(opt.data) # check file 268 | print(opt) 269 | 270 | if opt.task in ['val', 'test']: # run normally 271 | test(opt.data, 272 | opt.weights, 273 | opt.batch_size, 274 | opt.img_size, 275 | opt.conf_thres, 276 | opt.iou_thres, 277 | opt.save_json, 278 | opt.single_cls, 279 | opt.augment, 280 | opt.verbose) 281 | 282 | elif opt.task == 'study': # run over a range of settings and save/plot 283 | for weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: 284 | f = 'study_%s_%s.txt' % (Path(opt.data).stem, Path(weights).stem) # filename to save to 285 | x = list(range(352, 832, 64)) # x axis 286 | y = [] # y axis 287 | for i in x: # img-size 288 | print('\nRunning %s point %s...' % (f, i)) 289 | r, _, t = test(opt.data, weights, opt.batch_size, i, opt.conf_thres, opt.iou_thres, opt.save_json) 290 | y.append(r + t) # results and times 291 | np.savetxt(f, y, fmt='%10.4g') # save 292 | os.system('zip -r study.zip study_*.txt') 293 | # plot_study_txt(f, x) # plot 294 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__init__.py -------------------------------------------------------------------------------- /utils/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /utils/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /utils/__pycache__/datasets.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/datasets.cpython-36.pyc -------------------------------------------------------------------------------- /utils/__pycache__/datasets.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/datasets.cpython-38.pyc -------------------------------------------------------------------------------- /utils/__pycache__/draw.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/draw.cpython-36.pyc -------------------------------------------------------------------------------- /utils/__pycache__/draw.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/draw.cpython-38.pyc -------------------------------------------------------------------------------- /utils/__pycache__/general.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/general.cpython-36.pyc -------------------------------------------------------------------------------- /utils/__pycache__/general.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/general.cpython-38.pyc -------------------------------------------------------------------------------- /utils/__pycache__/google_utils.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/google_utils.cpython-36.pyc -------------------------------------------------------------------------------- /utils/__pycache__/google_utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/google_utils.cpython-38.pyc -------------------------------------------------------------------------------- /utils/__pycache__/log.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/log.cpython-36.pyc -------------------------------------------------------------------------------- /utils/__pycache__/log.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/log.cpython-38.pyc -------------------------------------------------------------------------------- /utils/__pycache__/parser.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/parser.cpython-36.pyc -------------------------------------------------------------------------------- /utils/__pycache__/parser.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/parser.cpython-38.pyc -------------------------------------------------------------------------------- /utils/__pycache__/torch_utils.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/torch_utils.cpython-36.pyc -------------------------------------------------------------------------------- /utils/__pycache__/torch_utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/utils/__pycache__/torch_utils.cpython-38.pyc -------------------------------------------------------------------------------- /utils/activations.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | 6 | # Swish https://arxiv.org/pdf/1905.02244.pdf --------------------------------------------------------------------------- 7 | class Swish(nn.Module): # 8 | @staticmethod 9 | def forward(x): 10 | return x * torch.sigmoid(x) 11 | 12 | 13 | class HardSwish(nn.Module): 14 | @staticmethod 15 | def forward(x): 16 | return x * F.hardtanh(x + 3, 0., 6., True) / 6. 17 | 18 | 19 | class MemoryEfficientSwish(nn.Module): 20 | class F(torch.autograd.Function): 21 | @staticmethod 22 | def forward(ctx, x): 23 | ctx.save_for_backward(x) 24 | return x * torch.sigmoid(x) 25 | 26 | @staticmethod 27 | def backward(ctx, grad_output): 28 | x = ctx.saved_tensors[0] 29 | sx = torch.sigmoid(x) 30 | return grad_output * (sx * (1 + x * (1 - sx))) 31 | 32 | def forward(self, x): 33 | return self.F.apply(x) 34 | 35 | 36 | # Mish https://github.com/digantamisra98/Mish -------------------------------------------------------------------------- 37 | class Mish(nn.Module): 38 | @staticmethod 39 | def forward(x): 40 | return x * F.softplus(x).tanh() 41 | 42 | 43 | class MemoryEfficientMish(nn.Module): 44 | class F(torch.autograd.Function): 45 | @staticmethod 46 | def forward(ctx, x): 47 | ctx.save_for_backward(x) 48 | return x.mul(torch.tanh(F.softplus(x))) # x * tanh(ln(1 + exp(x))) 49 | 50 | @staticmethod 51 | def backward(ctx, grad_output): 52 | x = ctx.saved_tensors[0] 53 | sx = torch.sigmoid(x) 54 | fx = F.softplus(x).tanh() 55 | return grad_output * (fx + x * sx * (1 - fx * fx)) 56 | 57 | def forward(self, x): 58 | return self.F.apply(x) 59 | 60 | 61 | # FReLU https://arxiv.org/abs/2007.11824 ------------------------------------------------------------------------------- 62 | class FReLU(nn.Module): 63 | def __init__(self, c1, k=3): # ch_in, kernel 64 | super().__init__() 65 | self.conv = nn.Conv2d(c1, c1, k, 1, 1, groups=c1) 66 | self.bn = nn.BatchNorm2d(c1) 67 | 68 | def forward(self, x): 69 | return torch.max(x, self.bn(self.conv(x))) 70 | -------------------------------------------------------------------------------- /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:", 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( 29 | img, (x1, y1), (x1 + t_size[0] + 3, y1 + t_size[1] + 4), color, -1) 30 | cv2.putText(img, label, (x1, y1 + 31 | t_size[1] + 4), cv2.FONT_HERSHEY_PLAIN, 2, [255, 255, 255], 2) 32 | return img 33 | 34 | 35 | def draw_person(img, bbox_xyxy, reid_results, names, identities=None, offset=(0, 0)): 36 | for i, x in enumerate(bbox_xyxy): 37 | person_name = names[reid_results[i]] 38 | t_size = cv2.getTextSize(person_name, cv2.FONT_HERSHEY_PLAIN, 1, 1)[0] 39 | color = compute_color_for_labels(0) 40 | c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3])) 41 | # 截取行人用来erson_bank.py获取行人特征 42 | ''' 43 | p = img[int(x[1]):int(x[3]), int(x[0]):int(x[2])] 44 | cv2.imwrite('/home/zengwb/Documents/yolov5-fastreid/fast_reid/query/a1/a1.jpg', p) 45 | cv2.imshow('p', p) 46 | cv2.waitKey(0) 47 | ''' 48 | cv2.rectangle(img, c1, c2, color, lineType=cv2.LINE_AA) 49 | cv2.rectangle( 50 | img, (c2[0] - t_size[0]-3, c2[1]-t_size[1] - 4), c2, color, -1) 51 | cv2.putText(img, person_name, (c2[0] - t_size[0]-3, c2[1]), cv2.FONT_HERSHEY_PLAIN, 2, [255, 255, 255], 2) 52 | # cv2.rectangle( 53 | # img, (c1[0] + 2*t_size[0], c1[1]), (c1[0] + 2*t_size[0] + 3, c1[1] + t_size[1] + 4), color, -1) 54 | # cv2.putText(img, person_name, (c1[0], c1[1] + 55 | # t_size[1] + 4), cv2.FONT_HERSHEY_PLAIN, 2, [255, 255, 255], 2) 56 | # cv2.rectangle(img, (c2[0]+t_size[0] +3, c2[1]+t_size[1]+4), (c2[0]+2*t_size[0] +3, c2[1]+2*t_size[1]+4), color, -1) 57 | # cv2.putText(img, person_name, (c2[0]+t_size[0] +3, c2[1]+2*t_size[1]+4), cv2.FONT_HERSHEY_PLAIN, 2, [255, 255, 255], 2) 58 | # if label: 59 | # tf = max(tl - 1, 1) # font thickness 60 | # t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0] 61 | # c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3 62 | # cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled 63 | # cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA) 64 | return img 65 | 66 | 67 | if __name__ == '__main__': 68 | for i in range(82): 69 | print(compute_color_for_labels(i)) 70 | -------------------------------------------------------------------------------- /utils/google_utils.py: -------------------------------------------------------------------------------- 1 | # This file contains google utils: https://cloud.google.com/storage/docs/reference/libraries 2 | # pip install --upgrade google-cloud-storage 3 | # from google.cloud import storage 4 | 5 | import os 6 | import platform 7 | import time 8 | from pathlib import Path 9 | import torch 10 | 11 | 12 | def attempt_download(weights): 13 | # Attempt to download pretrained weights if not found locally 14 | weights = weights.strip().replace("'", '') 15 | file = Path(weights).name 16 | 17 | msg = weights + ' missing, try downloading from https://github.com/ultralytics/yolov5/releases/' 18 | models = ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt'] # available models 19 | 20 | if file in models and not os.path.isfile(weights): 21 | # Google Drive 22 | # d = {'yolov5s.pt': '1R5T6rIyy3lLwgFXNms8whc-387H0tMQO', 23 | # 'yolov5m.pt': '1vobuEExpWQVpXExsJ2w-Mbf3HJjWkQJr', 24 | # 'yolov5l.pt': '1hrlqD1Wdei7UT4OgT785BEk1JwnSvNEV', 25 | # 'yolov5x.pt': '1mM8aZJlWTxOg7BZJvNUMrTnA2AbeCVzS'} 26 | # r = gdrive_download(id=d[file], name=weights) if file in d else 1 27 | # if r == 0 and os.path.exists(weights) and os.path.getsize(weights) > 1E6: # check 28 | # return 29 | 30 | try: # GitHub 31 | url = 'https://github.com/ultralytics/yolov5/releases/download/v2.0/' + file 32 | print('Downloading %s to %s...' % (url, weights)) 33 | if platform.system() == 'Darwin': # avoid MacOS python requests certificate error 34 | r = os.system('curl -L %s -o %s' % (url, weights)) 35 | else: 36 | torch.hub.download_url_to_file(url, weights) 37 | assert os.path.exists(weights) and os.path.getsize(weights) > 1E6 # check 38 | except Exception as e: # GCP 39 | print('Download error: %s' % e) 40 | url = 'https://storage.googleapis.com/ultralytics/yolov5/ckpt/' + file 41 | print('Downloading %s to %s...' % (url, weights)) 42 | r = os.system('curl -L %s -o %s' % (url, weights)) # torch.hub.download_url_to_file(url, weights) 43 | finally: 44 | if not (os.path.exists(weights) and os.path.getsize(weights) > 1E6): # check 45 | os.remove(weights) if os.path.exists(weights) else None # remove partial downloads 46 | print('ERROR: Download failure: %s' % msg) 47 | print('') 48 | return 49 | 50 | 51 | def gdrive_download(id='1n_oKgR81BJtqk75b00eAjdv03qVCQn2f', name='coco128.zip'): 52 | # Downloads a file from Google Drive. from utils.google_utils import *; gdrive_download() 53 | t = time.time() 54 | 55 | print('Downloading https://drive.google.com/uc?export=download&id=%s as %s... ' % (id, name), end='') 56 | os.remove(name) if os.path.exists(name) else None # remove existing 57 | os.remove('cookie') if os.path.exists('cookie') else None 58 | 59 | # Attempt file download 60 | out = "NUL" if platform.system() == "Windows" else "/dev/null" 61 | os.system('curl -c ./cookie -s -L "drive.google.com/uc?export=download&id=%s" > %s ' % (id, out)) 62 | if os.path.exists('cookie'): # large file 63 | s = 'curl -Lb ./cookie "drive.google.com/uc?export=download&confirm=%s&id=%s" -o %s' % (get_token(), id, name) 64 | else: # small file 65 | s = 'curl -s -L -o %s "drive.google.com/uc?export=download&id=%s"' % (name, id) 66 | r = os.system(s) # execute, capture return 67 | os.remove('cookie') if os.path.exists('cookie') else None 68 | 69 | # Error check 70 | if r != 0: 71 | os.remove(name) if os.path.exists(name) else None # remove partial 72 | print('Download error ') # raise Exception('Download error') 73 | return r 74 | 75 | # Unzip if archive 76 | if name.endswith('.zip'): 77 | print('unzipping... ', end='') 78 | os.system('unzip -q %s' % name) # unzip 79 | os.remove(name) # remove zip to free space 80 | 81 | print('Done (%.1fs)' % (time.time() - t)) 82 | return r 83 | 84 | 85 | def get_token(cookie="./cookie"): 86 | with open(cookie) as f: 87 | for line in f: 88 | if "download" in line: 89 | return line.split()[-1] 90 | return "" 91 | 92 | # def upload_blob(bucket_name, source_file_name, destination_blob_name): 93 | # # Uploads a file to a bucket 94 | # # https://cloud.google.com/storage/docs/uploading-objects#storage-upload-object-python 95 | # 96 | # storage_client = storage.Client() 97 | # bucket = storage_client.get_bucket(bucket_name) 98 | # blob = bucket.blob(destination_blob_name) 99 | # 100 | # blob.upload_from_filename(source_file_name) 101 | # 102 | # print('File {} uploaded to {}.'.format( 103 | # source_file_name, 104 | # destination_blob_name)) 105 | # 106 | # 107 | # def download_blob(bucket_name, source_blob_name, destination_file_name): 108 | # # Uploads a blob from a bucket 109 | # storage_client = storage.Client() 110 | # bucket = storage_client.get_bucket(bucket_name) 111 | # blob = bucket.blob(source_blob_name) 112 | # 113 | # blob.download_to_filename(destination_file_name) 114 | # 115 | # print('Blob {} downloaded to {}.'.format( 116 | # source_blob_name, 117 | # destination_file_name)) 118 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils/parser.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | from easydict import EasyDict as edict 4 | 5 | class YamlParser(edict): 6 | """ 7 | This is yaml parser based on EasyDict. 8 | """ 9 | def __init__(self, cfg_dict=None, config_file=None): 10 | if cfg_dict is None: 11 | cfg_dict = {} 12 | 13 | if config_file is not None: 14 | assert(os.path.isfile(config_file)) 15 | with open(config_file, 'r') as fo: 16 | cfg_dict.update(yaml.load(fo.read())) 17 | 18 | super(YamlParser, self).__init__(cfg_dict) 19 | 20 | 21 | def merge_from_file(self, config_file): 22 | with open(config_file, 'r') as fo: 23 | self.update(yaml.load(fo.read())) 24 | 25 | 26 | def merge_from_dict(self, config_dict): 27 | self.update(config_dict) 28 | 29 | 30 | def get_config(config_file=None): 31 | return YamlParser(config_file=config_file) 32 | 33 | 34 | if __name__ == "__main__": 35 | cfg = YamlParser(config_file="../configs/yolov3.yaml") 36 | cfg.merge_from_file("../configs/deep_sort.yaml") 37 | 38 | import ipdb; ipdb.set_trace() -------------------------------------------------------------------------------- /utils/torch_utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import time 4 | import logging 5 | from copy import deepcopy 6 | 7 | import torch 8 | import torch.backends.cudnn as cudnn 9 | import torch.nn as nn 10 | import torch.nn.functional as F 11 | import torchvision.models as models 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | def init_seeds(seed=0): 16 | torch.manual_seed(seed) 17 | 18 | # Speed-reproducibility tradeoff https://pytorch.org/docs/stable/notes/randomness.html 19 | if seed == 0: # slower, more reproducible 20 | cudnn.deterministic = True 21 | cudnn.benchmark = False 22 | else: # faster, less reproducible 23 | cudnn.deterministic = False 24 | cudnn.benchmark = True 25 | 26 | 27 | def select_device(device='', batch_size=None): 28 | # device = 'cpu' or '0' or '0,1,2,3' 29 | cpu_request = device.lower() == 'cpu' 30 | if device and not cpu_request: # if device requested other than 'cpu' 31 | os.environ['CUDA_VISIBLE_DEVICES'] = device # set environment variable 32 | assert torch.cuda.is_available(), 'CUDA unavailable, invalid device %s requested' % device # check availablity 33 | 34 | cuda = False if cpu_request else torch.cuda.is_available() 35 | if cuda: 36 | c = 1024 ** 2 # bytes to MB 37 | ng = torch.cuda.device_count() 38 | if ng > 1 and batch_size: # check that batch_size is compatible with device_count 39 | assert batch_size % ng == 0, 'batch-size %g not multiple of GPU count %g' % (batch_size, ng) 40 | x = [torch.cuda.get_device_properties(i) for i in range(ng)] 41 | s = 'Using CUDA ' 42 | for i in range(0, ng): 43 | if i == 1: 44 | s = ' ' * len(s) 45 | logger.info("%sdevice%g _CudaDeviceProperties(name='%s', total_memory=%dMB)" % 46 | (s, i, x[i].name, x[i].total_memory / c)) 47 | else: 48 | logger.info('Using CPU') 49 | 50 | logger.info('') # skip a line 51 | return torch.device('cuda:0' if cuda else 'cpu') 52 | 53 | 54 | def time_synchronized(): 55 | torch.cuda.synchronize() if torch.cuda.is_available() else None 56 | return time.time() 57 | 58 | 59 | def is_parallel(model): 60 | return type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) 61 | 62 | 63 | def intersect_dicts(da, db, exclude=()): 64 | # Dictionary intersection of matching keys and shapes, omitting 'exclude' keys, using da values 65 | return {k: v for k, v in da.items() if k in db and not any(x in k for x in exclude) and v.shape == db[k].shape} 66 | 67 | 68 | def initialize_weights(model): 69 | for m in model.modules(): 70 | t = type(m) 71 | if t is nn.Conv2d: 72 | pass # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 73 | elif t is nn.BatchNorm2d: 74 | m.eps = 1e-3 75 | m.momentum = 0.03 76 | elif t in [nn.LeakyReLU, nn.ReLU, nn.ReLU6]: 77 | m.inplace = True 78 | 79 | 80 | def find_modules(model, mclass=nn.Conv2d): 81 | # Finds layer indices matching module class 'mclass' 82 | return [i for i, m in enumerate(model.module_list) if isinstance(m, mclass)] 83 | 84 | 85 | def sparsity(model): 86 | # Return global model sparsity 87 | a, b = 0., 0. 88 | for p in model.parameters(): 89 | a += p.numel() 90 | b += (p == 0).sum() 91 | return b / a 92 | 93 | 94 | def prune(model, amount=0.3): 95 | # Prune model to requested global sparsity 96 | import torch.nn.utils.prune as prune 97 | print('Pruning model... ', end='') 98 | for name, m in model.named_modules(): 99 | if isinstance(m, nn.Conv2d): 100 | prune.l1_unstructured(m, name='weight', amount=amount) # prune 101 | prune.remove(m, 'weight') # make permanent 102 | print(' %.3g global sparsity' % sparsity(model)) 103 | 104 | 105 | def fuse_conv_and_bn(conv, bn): 106 | # https://tehnokv.com/posts/fusing-batchnorm-and-conv/ 107 | with torch.no_grad(): 108 | # init 109 | fusedconv = nn.Conv2d(conv.in_channels, 110 | conv.out_channels, 111 | kernel_size=conv.kernel_size, 112 | stride=conv.stride, 113 | padding=conv.padding, 114 | bias=True).to(conv.weight.device) 115 | 116 | # prepare filters 117 | w_conv = conv.weight.clone().view(conv.out_channels, -1) 118 | w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps + bn.running_var))) 119 | fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.size())) 120 | 121 | # prepare spatial bias 122 | b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) if conv.bias is None else conv.bias 123 | b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps)) 124 | fusedconv.bias.copy_(torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn) 125 | 126 | return fusedconv 127 | 128 | 129 | def model_info(model, verbose=False): 130 | # Plots a line-by-line description of a PyTorch model 131 | n_p = sum(x.numel() for x in model.parameters()) # number parameters 132 | n_g = sum(x.numel() for x in model.parameters() if x.requires_grad) # number gradients 133 | if verbose: 134 | print('%5s %40s %9s %12s %20s %10s %10s' % ('layer', 'name', 'gradient', 'parameters', 'shape', 'mu', 'sigma')) 135 | for i, (name, p) in enumerate(model.named_parameters()): 136 | name = name.replace('module_list.', '') 137 | print('%5g %40s %9s %12g %20s %10.3g %10.3g' % 138 | (i, name, p.requires_grad, p.numel(), list(p.shape), p.mean(), p.std())) 139 | 140 | try: # FLOPS 141 | from thop import profile 142 | flops = profile(deepcopy(model), inputs=(torch.zeros(1, 3, 64, 64),), verbose=False)[0] / 1E9 * 2 143 | fs = ', %.1f GFLOPS' % (flops * 100) # 640x640 FLOPS 144 | except: 145 | fs = '' 146 | 147 | logger.info('Model Summary: %g layers, %g parameters, %g gradients%s' % (len(list(model.parameters())), n_p, n_g, fs)) 148 | 149 | 150 | def load_classifier(name='resnet101', n=2): 151 | # Loads a pretrained model reshaped to n-class output 152 | model = models.__dict__[name](pretrained=True) 153 | 154 | # Display model properties 155 | input_size = [3, 224, 224] 156 | input_space = 'RGB' 157 | input_range = [0, 1] 158 | mean = [0.485, 0.456, 0.406] 159 | std = [0.229, 0.224, 0.225] 160 | for x in [input_size, input_space, input_range, mean, std]: 161 | print(x + ' =', eval(x)) 162 | 163 | # Reshape output to n classes 164 | filters = model.fc.weight.shape[1] 165 | model.fc.bias = nn.Parameter(torch.zeros(n), requires_grad=True) 166 | model.fc.weight = nn.Parameter(torch.zeros(n, filters), requires_grad=True) 167 | model.fc.out_features = n 168 | return model 169 | 170 | 171 | def scale_img(img, ratio=1.0, same_shape=False): # img(16,3,256,416), r=ratio 172 | # scales img(bs,3,y,x) by ratio 173 | if ratio == 1.0: 174 | return img 175 | else: 176 | h, w = img.shape[2:] 177 | s = (int(h * ratio), int(w * ratio)) # new size 178 | img = F.interpolate(img, size=s, mode='bilinear', align_corners=False) # resize 179 | if not same_shape: # pad/crop img 180 | gs = 32 # (pixels) grid size 181 | h, w = [math.ceil(x * ratio / gs) * gs for x in (h, w)] 182 | return F.pad(img, [0, w - s[1], 0, h - s[0]], value=0.447) # value = imagenet mean 183 | 184 | 185 | def copy_attr(a, b, include=(), exclude=()): 186 | # Copy attributes from b to a, options to only include [...] and to exclude [...] 187 | for k, v in b.__dict__.items(): 188 | if (len(include) and k not in include) or k.startswith('_') or k in exclude: 189 | continue 190 | else: 191 | setattr(a, k, v) 192 | 193 | 194 | class ModelEMA: 195 | """ Model Exponential Moving Average from https://github.com/rwightman/pytorch-image-models 196 | Keep a moving average of everything in the model state_dict (parameters and buffers). 197 | This is intended to allow functionality like 198 | https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage 199 | A smoothed version of the weights is necessary for some training schemes to perform well. 200 | This class is sensitive where it is initialized in the sequence of model init, 201 | GPU assignment and distributed training wrappers. 202 | """ 203 | 204 | def __init__(self, model, decay=0.9999, updates=0): 205 | # Create EMA 206 | self.ema = deepcopy(model.module if is_parallel(model) else model).eval() # FP32 EMA 207 | # if next(model.parameters()).device.type != 'cpu': 208 | # self.ema.half() # FP16 EMA 209 | self.updates = updates # number of EMA updates 210 | self.decay = lambda x: decay * (1 - math.exp(-x / 2000)) # decay exponential ramp (to help early epochs) 211 | for p in self.ema.parameters(): 212 | p.requires_grad_(False) 213 | 214 | def update(self, model): 215 | # Update EMA parameters 216 | with torch.no_grad(): 217 | self.updates += 1 218 | d = self.decay(self.updates) 219 | 220 | msd = model.module.state_dict() if is_parallel(model) else model.state_dict() # model state_dict 221 | for k, v in self.ema.state_dict().items(): 222 | if v.dtype.is_floating_point: 223 | v *= d 224 | v += (1. - d) * msd[k].detach() 225 | 226 | def update_attr(self, model, include=(), exclude=('process_group', 'reducer')): 227 | # Update EMA attributes 228 | copy_attr(self.ema, model, include, exclude) 229 | -------------------------------------------------------------------------------- /weights/download_weights.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Download common models 3 | 4 | python -c " 5 | from utils.google_utils import *; 6 | attempt_download('weights/yolov5s.pt'); 7 | attempt_download('weights/yolov5m.pt'); 8 | attempt_download('weights/yolov5l.pt'); 9 | attempt_download('weights/yolov5x.pt') 10 | " 11 | -------------------------------------------------------------------------------- /weights/yolov5s.pt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zengwb-lx/yolov5-deepsort-pedestrian-counting/5ab2fc344d42c79d12b364ebbf29ba5a9fa204c0/weights/yolov5s.pt --------------------------------------------------------------------------------