├── deep ├── __init__.py ├── checkpoint │ ├── .gitkeep │ └── ckpt.pth ├── __pycache__ │ ├── model.cpython-36.pyc │ ├── model.cpython-37.pyc │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-37.pyc │ ├── dataset.cpython-37.pyc │ ├── ghost_net.cpython-37.pyc │ ├── ShuffleNetV2.cpython-37.pyc │ ├── feature_extractor.cpython-36.pyc │ └── feature_extractor.cpython-37.pyc ├── evaluate.py ├── feature_extractor.py ├── dataset.py ├── test.py ├── model.py ├── original_model.py ├── ghost_net.py ├── train.py └── ShuffleNetV2.py ├── sort ├── __init__.py ├── __pycache__ │ ├── track.cpython-36.pyc │ ├── track.cpython-37.pyc │ ├── __init__.cpython-36.pyc │ ├── __init__.cpython-37.pyc │ ├── detection.cpython-36.pyc │ ├── detection.cpython-37.pyc │ ├── tracker.cpython-36.pyc │ ├── tracker.cpython-37.pyc │ ├── nn_matching.cpython-36.pyc │ ├── nn_matching.cpython-37.pyc │ ├── iou_matching.cpython-36.pyc │ ├── iou_matching.cpython-37.pyc │ ├── kalman_filter.cpython-36.pyc │ ├── kalman_filter.cpython-37.pyc │ ├── preprocessing.cpython-36.pyc │ ├── preprocessing.cpython-37.pyc │ ├── linear_assignment.cpython-36.pyc │ └── linear_assignment.cpython-37.pyc ├── detection.py ├── preprocessing.py ├── iou_matching.py ├── track.py ├── tracker.py ├── nn_matching.py ├── linear_assignment.py └── kalman_filter.py ├── train.jpg ├── README.md ├── global_settings.py ├── __init__.py ├── data_process.py ├── deep_sort.py └── trainer.py /deep/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sort/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deep/checkpoint/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /train.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/train.jpg -------------------------------------------------------------------------------- /deep/checkpoint/ckpt.pth: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/checkpoint/ckpt.pth -------------------------------------------------------------------------------- /deep/__pycache__/model.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/model.cpython-36.pyc -------------------------------------------------------------------------------- /deep/__pycache__/model.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/model.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/track.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/track.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/track.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/track.cpython-37.pyc -------------------------------------------------------------------------------- /deep/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /deep/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /deep/__pycache__/dataset.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/dataset.cpython-37.pyc -------------------------------------------------------------------------------- /deep/__pycache__/ghost_net.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/ghost_net.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/detection.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/detection.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/detection.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/detection.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/tracker.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/tracker.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/tracker.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/tracker.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/nn_matching.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/nn_matching.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/nn_matching.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/nn_matching.cpython-37.pyc -------------------------------------------------------------------------------- /deep/__pycache__/ShuffleNetV2.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/ShuffleNetV2.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/iou_matching.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/iou_matching.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/iou_matching.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/iou_matching.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/kalman_filter.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/kalman_filter.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/kalman_filter.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/kalman_filter.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/preprocessing.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/preprocessing.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/preprocessing.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/preprocessing.cpython-37.pyc -------------------------------------------------------------------------------- /deep/__pycache__/feature_extractor.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/feature_extractor.cpython-36.pyc -------------------------------------------------------------------------------- /deep/__pycache__/feature_extractor.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/deep/__pycache__/feature_extractor.cpython-37.pyc -------------------------------------------------------------------------------- /sort/__pycache__/linear_assignment.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/linear_assignment.cpython-36.pyc -------------------------------------------------------------------------------- /sort/__pycache__/linear_assignment.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1079863482/ShuffleNet-deepsort/HEAD/sort/__pycache__/linear_assignment.cpython-37.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deep Sort 2 | 3 | This is the implemention of deep sort with pytorch. 4 | 5 | 使用了shufflenetV2对deepsort特征提取部分进行重训练,最后模型只有2.5M,保持着不错的精度。 6 | 7 | 8 | ## 代码使用请参考,博客连接:https://blog.csdn.net/qq_39056987/article/details/114441917 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /global_settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | 4 | # directory to save weights file 5 | CHECKPOINT_PATH = 'checkpoint' 6 | 7 | # total training epoches 8 | EPOCH = 50 9 | MILESTONES = [20, 40] 10 | 11 | # initial learning rate 12 | # INIT_LR = 0.1 13 | 14 | # time of we run the script 15 | TIME_NOW = datetime.now().isoformat() 16 | 17 | # tensorboard log dir 18 | LOG_DIR = 'runs' 19 | 20 | # save weights file per SAVE_EPOCH epoch 21 | SAVE_EPOCH = 10 -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .deep_sort import DeepSort 2 | 3 | 4 | __all__ = ['DeepSort', 'build_tracker'] 5 | 6 | 7 | def build_tracker(cfg, use_cuda): 8 | return DeepSort(cfg.DEEPSORT.REID_CKPT, 9 | max_dist=cfg.DEEPSORT.MAX_DIST, min_confidence=cfg.DEEPSORT.MIN_CONFIDENCE, 10 | nms_max_overlap=cfg.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE, 11 | max_age=cfg.DEEPSORT.MAX_AGE, n_init=cfg.DEEPSORT.N_INIT, nn_budget=cfg.DEEPSORT.NN_BUDGET, use_cuda=use_cuda) 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /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/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 | def _preprocess(self, im_crops): 26 | """ 27 | TODO: 28 | 1. to float with scale from 0 to 1 29 | 2. resize to (64, 128) as Market1501 dataset did 30 | 3. concatenate to a numpy array 31 | 3. to torch Tensor 32 | 4. normalize 33 | """ 34 | def _resize(im, size): 35 | return cv2.resize(im.astype(np.float32)/255., size) 36 | 37 | im_batch = torch.cat([self.norm(_resize(im, self.size)).unsqueeze(0) for im in im_crops], dim=0).float() 38 | return im_batch 39 | 40 | 41 | def __call__(self, im_crops): 42 | im_batch = self._preprocess(im_crops) 43 | with torch.no_grad(): 44 | im_batch = im_batch.to(self.device) 45 | features = self.net(im_batch) 46 | return features.cpu().numpy() 47 | 48 | 49 | if __name__ == '__main__': 50 | img = cv2.imread("demo.jpg")[:,:,(2,1,0)] 51 | extr = Extractor("checkpoint/ckpt.t7") 52 | feature = extr(img) 53 | print(feature.shape) 54 | 55 | -------------------------------------------------------------------------------- /deep/dataset.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import os 4 | from torch.utils import data 5 | from PIL import Image 6 | import cv2 7 | import random 8 | import torchvision 9 | 10 | class Datasets(data.Dataset): 11 | def __init__(self,path,train = False): 12 | self.img_path = [] 13 | self.label_data = [] 14 | self.train = train 15 | self.transforms_train = torchvision.transforms.Compose([ 16 | torchvision.transforms.RandomCrop((128,64),padding=4), 17 | torchvision.transforms.RandomHorizontalFlip(), 18 | torchvision.transforms.ToTensor(), 19 | torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 20 | ]) 21 | self.transforms_test = torchvision.transforms.Compose([ 22 | torchvision.transforms.Resize((128,64)), 23 | torchvision.transforms.ToTensor(), 24 | torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 25 | ]) 26 | count = 0 27 | for dir in os.listdir(path): 28 | for img_path in os.listdir(os.path.join(path,dir)): 29 | self.img_path.append(os.path.join(path,dir,img_path)) 30 | self.label_data.append(count) 31 | count +=1 32 | 33 | def __len__(self): 34 | return len(self.img_path) 35 | 36 | def __getitem__(self, index): 37 | img_path = self.img_path[index] 38 | img_data = Image.open(img_path) 39 | label = self.label_data[index] 40 | if self.train: 41 | img_data = self.transforms_train(img_data) 42 | else: 43 | img_data = self.transforms_test(img_data) 44 | 45 | return img_data,label 46 | 47 | 48 | if __name__=="__main__": 49 | train_path = r"F:\BaiduNetdiskDownload\Market-1501-v15.09.15\pytorch\val" 50 | 51 | train_Data = Datasets(train_path,True) 52 | 53 | train = data.DataLoader(train_Data,batch_size=64,shuffle=False) 54 | 55 | for i,(x,y) in enumerate(train): 56 | print(x.size()) 57 | print(y) 58 | break -------------------------------------------------------------------------------- /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/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 deep.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") -------------------------------------------------------------------------------- /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/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 | print(y.size()) 103 | # import ipdb; ipdb.set_trace() 104 | 105 | 106 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /data_process.py: -------------------------------------------------------------------------------- 1 | import os 2 | from shutil import copyfile 3 | 4 | # You only need to change this line to your dataset download path 5 | download_path = 'F:\BaiduNetdiskDownload\Market-1501-v15.09.15' 6 | 7 | if not os.path.isdir(download_path): 8 | print('please change the download_path') 9 | 10 | save_path = download_path + '/pytorch' 11 | if not os.path.isdir(save_path): 12 | os.mkdir(save_path) 13 | #----------------------------------------- 14 | #query 15 | query_path = download_path + '/query' 16 | query_save_path = download_path + '/pytorch/query' 17 | if not os.path.isdir(query_save_path): 18 | os.mkdir(query_save_path) 19 | 20 | for root, dirs, files in os.walk(query_path, topdown=True): 21 | for name in files: 22 | if not name[-3:]=='jpg': 23 | continue 24 | ID = name.split('_') 25 | src_path = query_path + '/' + name 26 | dst_path = query_save_path + '/' + ID[0] 27 | if not os.path.isdir(dst_path): 28 | os.mkdir(dst_path) 29 | copyfile(src_path, dst_path + '/' + name) 30 | 31 | #----------------------------------------- 32 | #multi-query 33 | query_path = download_path + '/gt_bbox' 34 | # for dukemtmc-reid, we do not need multi-query 35 | if os.path.isdir(query_path): 36 | query_save_path = download_path + '/pytorch/multi-query' 37 | if not os.path.isdir(query_save_path): 38 | os.mkdir(query_save_path) 39 | 40 | for root, dirs, files in os.walk(query_path, topdown=True): 41 | for name in files: 42 | if not name[-3:]=='jpg': 43 | continue 44 | ID = name.split('_') 45 | src_path = query_path + '/' + name 46 | dst_path = query_save_path + '/' + ID[0] 47 | if not os.path.isdir(dst_path): 48 | os.mkdir(dst_path) 49 | copyfile(src_path, dst_path + '/' + name) 50 | 51 | #----------------------------------------- 52 | #gallery 53 | gallery_path = download_path + '/bounding_box_test' 54 | gallery_save_path = download_path + '/pytorch/gallery' 55 | if not os.path.isdir(gallery_save_path): 56 | os.mkdir(gallery_save_path) 57 | 58 | for root, dirs, files in os.walk(gallery_path, topdown=True): 59 | for name in files: 60 | if not name[-3:]=='jpg': 61 | continue 62 | ID = name.split('_') 63 | src_path = gallery_path + '/' + name 64 | dst_path = gallery_save_path + '/' + ID[0] 65 | if not os.path.isdir(dst_path): 66 | os.mkdir(dst_path) 67 | copyfile(src_path, dst_path + '/' + name) 68 | 69 | #--------------------------------------- 70 | #train_all 71 | train_path = download_path + '/bounding_box_train' 72 | train_save_path = download_path + '/pytorch/train_all' 73 | if not os.path.isdir(train_save_path): 74 | os.mkdir(train_save_path) 75 | 76 | for root, dirs, files in os.walk(train_path, topdown=True): 77 | for name in files: 78 | if not name[-3:]=='jpg': 79 | continue 80 | ID = name.split('_') 81 | src_path = train_path + '/' + name 82 | dst_path = train_save_path + '/' + ID[0] 83 | if not os.path.isdir(dst_path): 84 | os.mkdir(dst_path) 85 | copyfile(src_path, dst_path + '/' + name) 86 | 87 | 88 | #--------------------------------------- 89 | #train_val 90 | train_path = download_path + '/bounding_box_train' 91 | train_save_path = download_path + '/pytorch/train' 92 | val_save_path = download_path + '/pytorch/val' 93 | if not os.path.isdir(train_save_path): 94 | os.mkdir(train_save_path) 95 | os.mkdir(val_save_path) 96 | 97 | for root, dirs, files in os.walk(train_path, topdown=True): 98 | for name in files: 99 | if not name[-3:]=='jpg': 100 | continue 101 | ID = name.split('_') 102 | src_path = train_path + '/' + name 103 | dst_path = train_save_path + '/' + ID[0] 104 | if not os.path.isdir(dst_path): 105 | os.mkdir(dst_path) 106 | dst_path = val_save_path + '/' + ID[0] #first image is used as val image 107 | os.mkdir(dst_path) 108 | copyfile(src_path, dst_path + '/' + name) 109 | -------------------------------------------------------------------------------- /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 | self.height, self.width = ori_img.shape[:2] 28 | # generate detections 29 | features = self._get_features(bbox_xywh, ori_img) 30 | bbox_tlwh = self._xywh_to_tlwh(bbox_xywh) 31 | detections = [Detection(bbox_tlwh[i], conf, features[i]) for i,conf in enumerate(confidences) if conf>self.min_confidence] 32 | 33 | # run on non-maximum supression 34 | boxes = np.array([d.tlwh for d in detections]) 35 | scores = np.array([d.confidence for d in detections]) 36 | indices = non_max_suppression(boxes, self.nms_max_overlap, scores) 37 | detections = [detections[i] for i in indices] 38 | 39 | # update tracker 40 | self.tracker.predict() 41 | self.tracker.update(detections) 42 | 43 | # output bbox identities 44 | outputs = [] 45 | for track in self.tracker.tracks: 46 | if not track.is_confirmed() or track.time_since_update > 1: 47 | continue 48 | box = track.to_tlwh() 49 | x1,y1,x2,y2 = self._tlwh_to_xyxy(box) 50 | track_id = track.track_id 51 | outputs.append(np.array([x1,y1,x2,y2,track_id], dtype=np.int)) 52 | if len(outputs) > 0: 53 | outputs = np.stack(outputs,axis=0) 54 | return outputs 55 | 56 | 57 | """ 58 | TODO: 59 | Convert bbox from xc_yc_w_h to xtl_ytl_w_h 60 | Thanks JieChen91@github.com for reporting this bug! 61 | """ 62 | @staticmethod 63 | def _xywh_to_tlwh(bbox_xywh): 64 | if isinstance(bbox_xywh, np.ndarray): 65 | bbox_tlwh = bbox_xywh.copy() 66 | elif isinstance(bbox_xywh, torch.Tensor): 67 | bbox_tlwh = bbox_xywh.clone() 68 | bbox_tlwh[:,0] = bbox_xywh[:,0] - bbox_xywh[:,2]/2. 69 | bbox_tlwh[:,1] = bbox_xywh[:,1] - bbox_xywh[:,3]/2. 70 | return bbox_tlwh 71 | 72 | 73 | def _xywh_to_xyxy(self, bbox_xywh): 74 | x,y,w,h = bbox_xywh 75 | x1 = max(int(x-w/2),0) 76 | x2 = min(int(x+w/2),self.width-1) 77 | y1 = max(int(y-h/2),0) 78 | y2 = min(int(y+h/2),self.height-1) 79 | return x1,y1,x2,y2 80 | 81 | def _tlwh_to_xyxy(self, bbox_tlwh): 82 | """ 83 | TODO: 84 | Convert bbox from xtl_ytl_w_h to xc_yc_w_h 85 | Thanks JieChen91@github.com for reporting this bug! 86 | """ 87 | x,y,w,h = bbox_tlwh 88 | x1 = max(int(x),0) 89 | x2 = min(int(x+w),self.width-1) 90 | y1 = max(int(y),0) 91 | y2 = min(int(y+h),self.height-1) 92 | return x1,y1,x2,y2 93 | 94 | def _xyxy_to_tlwh(self, bbox_xyxy): 95 | x1,y1,x2,y2 = bbox_xyxy 96 | 97 | t = x1 98 | l = y1 99 | w = int(x2-x1) 100 | h = int(y2-y1) 101 | return t,l,w,h 102 | 103 | def _get_features(self, bbox_xywh, ori_img): 104 | im_crops = [] 105 | for box in bbox_xywh: 106 | x1,y1,x2,y2 = self._xywh_to_xyxy(box) 107 | im = ori_img[y1:y2,x1:x2] 108 | im_crops.append(im) 109 | if im_crops: 110 | features = self.extractor(im_crops) 111 | else: 112 | features = np.array([]) 113 | return features 114 | 115 | 116 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/ghost_net.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creates a GhostNet Model as defined in: 3 | GhostNet: More Features from Cheap Operations By Kai Han, Yunhe Wang, Qi Tian, Jianyuan Guo, Chunjing Xu, Chang Xu. 4 | https://arxiv.org/abs/1911.11907 5 | Modified from https://github.com/d-li14/mobilenetv3.pytorch 6 | """ 7 | import torch 8 | import torch.nn as nn 9 | import math 10 | 11 | 12 | __all__ = ['ghost_net'] 13 | 14 | 15 | def _make_divisible(v, divisor, min_value=None): 16 | """ 17 | This function is taken from the original tf repo. 18 | It ensures that all layers have a channel number that is divisible by 8 19 | It can be seen here: 20 | https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py 21 | """ 22 | if min_value is None: 23 | min_value = divisor 24 | new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) 25 | # Make sure that round down does not go down by more than 10%. 26 | if new_v < 0.9 * v: 27 | new_v += divisor 28 | return new_v 29 | 30 | 31 | class SELayer(nn.Module): 32 | def __init__(self, channel, reduction=4): 33 | super(SELayer, self).__init__() 34 | self.avg_pool = nn.AdaptiveAvgPool2d(1) 35 | self.fc = nn.Sequential( 36 | nn.Linear(channel, channel // reduction), 37 | nn.ReLU(inplace=True), 38 | nn.Linear(channel // reduction, channel), ) 39 | 40 | def forward(self, x): 41 | b, c, _, _ = x.size() 42 | y = self.avg_pool(x).view(b, c) 43 | y = self.fc(y).view(b, c, 1, 1) 44 | y = torch.clamp(y, 0, 1) 45 | return x * y 46 | 47 | 48 | def depthwise_conv(inp, oup, kernel_size=3, stride=1, relu=False): 49 | return nn.Sequential( 50 | nn.Conv2d(inp, oup, kernel_size, stride, kernel_size//2, groups=inp, bias=False), 51 | nn.BatchNorm2d(oup), 52 | nn.ReLU(inplace=True) if relu else nn.Sequential(), 53 | ) 54 | 55 | class GhostModule(nn.Module): 56 | def __init__(self, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, relu=True): 57 | super(GhostModule, self).__init__() 58 | self.oup = oup 59 | init_channels = math.ceil(oup / ratio) 60 | new_channels = init_channels*(ratio-1) 61 | 62 | self.primary_conv = nn.Sequential( 63 | nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False), 64 | nn.BatchNorm2d(init_channels), 65 | nn.ReLU(inplace=True) if relu else nn.Sequential(), 66 | ) 67 | 68 | self.cheap_operation = nn.Sequential( 69 | nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False), 70 | nn.BatchNorm2d(new_channels), 71 | nn.ReLU(inplace=True) if relu else nn.Sequential(), 72 | ) 73 | 74 | def forward(self, x): 75 | x1 = self.primary_conv(x) 76 | x2 = self.cheap_operation(x1) 77 | out = torch.cat([x1,x2], dim=1) 78 | return out[:,:self.oup,:,:] 79 | 80 | 81 | class GhostBottleneck(nn.Module): 82 | def __init__(self, inp, hidden_dim, oup, kernel_size, stride, use_se): 83 | super(GhostBottleneck, self).__init__() 84 | assert stride in [1, 2] 85 | 86 | self.conv = nn.Sequential( 87 | # pw 88 | GhostModule(inp, hidden_dim, kernel_size=1, relu=True), 89 | # dw 90 | depthwise_conv(hidden_dim, hidden_dim, kernel_size, stride, relu=False) if stride==2 else nn.Sequential(), 91 | # Squeeze-and-Excite 92 | SELayer(hidden_dim) if use_se else nn.Sequential(), 93 | # pw-linear 94 | GhostModule(hidden_dim, oup, kernel_size=1, relu=False), 95 | ) 96 | 97 | if stride == 1 and inp == oup: 98 | self.shortcut = nn.Sequential() 99 | else: 100 | self.shortcut = nn.Sequential( 101 | depthwise_conv(inp, inp, kernel_size, stride, relu=False), 102 | nn.Conv2d(inp, oup, 1, 1, 0, bias=False), 103 | nn.BatchNorm2d(oup), 104 | ) 105 | 106 | def forward(self, x): 107 | return self.conv(x) + self.shortcut(x) 108 | 109 | 110 | class GhostNet(nn.Module): 111 | def __init__(self, cfgs, num_classes=1000, width_mult=1.,reid=False): 112 | super(GhostNet, self).__init__() 113 | # setting of inverted residual blocks 114 | self.cfgs = cfgs 115 | self.reid = reid 116 | 117 | # building first layer 118 | output_channel = _make_divisible(16 * width_mult, 4) 119 | layers = [nn.Sequential( 120 | nn.Conv2d(3, output_channel, 3, 2, 1, bias=False), 121 | nn.BatchNorm2d(output_channel), 122 | nn.ReLU(inplace=True) 123 | )] 124 | input_channel = output_channel 125 | 126 | # building inverted residual blocks 127 | block = GhostBottleneck 128 | for k, exp_size, c, use_se, s in self.cfgs: 129 | output_channel = _make_divisible(c * width_mult, 4) 130 | hidden_channel = _make_divisible(exp_size * width_mult, 4) 131 | layers.append(block(input_channel, hidden_channel, output_channel, k, s, use_se)) 132 | input_channel = output_channel 133 | self.features = nn.Sequential(*layers) 134 | 135 | # building last several layers 136 | output_channel = _make_divisible(exp_size * width_mult, 4) 137 | self.squeeze = nn.Sequential( 138 | nn.Conv2d(input_channel, output_channel, 1, 1, 0, bias=False), 139 | nn.BatchNorm2d(output_channel), 140 | nn.ReLU(inplace=True), 141 | nn.AdaptiveAvgPool2d((1, 1)), 142 | ) 143 | input_channel = output_channel 144 | 145 | output_channel = 1280 146 | self.classifier = nn.Sequential( 147 | nn.Linear(input_channel, output_channel, bias=False), 148 | nn.BatchNorm1d(output_channel), 149 | nn.ReLU(inplace=True), 150 | nn.Dropout(0.2), 151 | nn.Linear(output_channel, num_classes), 152 | ) 153 | 154 | self._initialize_weights() 155 | 156 | def forward(self, x): 157 | x = self.features(x) 158 | x = self.squeeze(x) 159 | x = x.view(x.size(0), -1) 160 | if self.reid: 161 | x = x.div(x.norm(p=2,dim=1,keepdim=True)) 162 | return x 163 | x = self.classifier(x) 164 | return x 165 | 166 | def _initialize_weights(self): 167 | for m in self.modules(): 168 | if isinstance(m, nn.Conv2d): 169 | nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 170 | elif isinstance(m, nn.BatchNorm2d): 171 | m.weight.data.fill_(1) 172 | m.bias.data.zero_() 173 | 174 | 175 | def ghost_net(**kwargs): 176 | """ 177 | Constructs a GhostNet model 178 | """ 179 | cfgs = [ 180 | # k, t, c, SE, s 181 | [3, 16, 16, 0, 1], 182 | [3, 48, 24, 0, 2], 183 | [3, 72, 24, 0, 1], 184 | [5, 72, 40, 1, 2], 185 | [5, 120, 40, 1, 1], 186 | [3, 240, 80, 0, 2], 187 | [3, 200, 80, 0, 1], 188 | [3, 184, 80, 0, 1], 189 | [3, 184, 80, 0, 1], 190 | [3, 480, 112, 1, 1], 191 | [3, 672, 112, 1, 1], 192 | [5, 672, 160, 1, 2], 193 | [5, 960, 160, 0, 1], 194 | [5, 960, 160, 1, 1], 195 | [5, 960, 160, 0, 1], 196 | [5, 960, 160, 1, 1] 197 | ] 198 | return GhostNet(cfgs, **kwargs) 199 | 200 | 201 | if __name__=='__main__': 202 | model = ghost_net() 203 | model.eval() 204 | input = torch.randn(1, 3, 32, 280) 205 | y = model(input) 206 | print(y.size()) 207 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | from mobilenet import MobileNetv2 13 | from ghost_net import ghost_net 14 | from ShuffleNetV2 import shufflenet_v2_x0_5 15 | 16 | 17 | parser = argparse.ArgumentParser(description="Train on market1501") 18 | parser.add_argument("--data-dir",default='data',type=str) 19 | parser.add_argument("--no-cuda",action="store_true") 20 | parser.add_argument("--gpu-id",default=0,type=int) 21 | parser.add_argument("--lr",default=0.1, type=float) 22 | parser.add_argument("--interval",'-i',default=20,type=int) 23 | parser.add_argument('--resume', '-r',action='store_true') 24 | args = parser.parse_args() 25 | 26 | # device 27 | device = "cuda:{}".format(args.gpu_id) if torch.cuda.is_available() and not args.no_cuda else "cpu" 28 | if torch.cuda.is_available() and not args.no_cuda: 29 | cudnn.benchmark = True 30 | 31 | def load_model(model_path,net,gpu_id=None): # 加载预训练权重 32 | if gpu_id is not None and isinstance(gpu_id, int) and torch.cuda.is_available(): 33 | device = torch.device("cuda:{}".format(gpu_id)) 34 | else: 35 | device = torch.device("cpu") 36 | if model_path is not None: 37 | 38 | pretrained_params = torch.load(model_path,map_location=device) 39 | 40 | pretrained_params= \ 41 | {k: v for k, v in pretrained_params.items() if 42 | k in net.state_dict().keys() and net.state_dict()[k].numel() == v.numel()} 43 | net.load_state_dict(pretrained_params, strict=False) 44 | 45 | 46 | # data loading 47 | root = args.data_dir 48 | train_dir = os.path.join(root,"train") 49 | test_dir = os.path.join(root,"val") 50 | transform_train = torchvision.transforms.Compose([ 51 | torchvision.transforms.RandomCrop((128,64),padding=4), 52 | torchvision.transforms.RandomHorizontalFlip(), 53 | torchvision.transforms.ToTensor(), 54 | torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 55 | ]) 56 | transform_test = torchvision.transforms.Compose([ 57 | torchvision.transforms.Resize((128,64)), 58 | torchvision.transforms.ToTensor(), 59 | torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 60 | ]) 61 | trainloader = torch.utils.data.DataLoader( 62 | torchvision.datasets.ImageFolder(train_dir, transform=transform_train), 63 | batch_size=64,shuffle=True 64 | ) 65 | testloader = torch.utils.data.DataLoader( 66 | torchvision.datasets.ImageFolder(test_dir, transform=transform_test), 67 | batch_size=64,shuffle=True 68 | ) 69 | num_classes = max(len(trainloader.dataset.classes), len(testloader.dataset.classes)) 70 | 71 | # net definition 72 | start_epoch = 0 73 | # net = Net(num_classes=num_classes) 74 | # net = MobileNetv2(num_classes=num_classes) 75 | net = ghost_net(num_classes=num_classes) 76 | 77 | # if args.resume: 78 | # assert os.path.isfile("./checkpoint/ckpt.t7"), "Error: no checkpoint file found!" 79 | # print('Loading from checkpoint/ckpt.t7') 80 | # checkpoint = torch.load("./checkpoint/ckpt.t7") 81 | # # import ipdb; ipdb.set_trace() 82 | # net_dict = checkpoint['net_dict'] 83 | # net.load_state_dict(net_dict) 84 | # best_acc = checkpoint['acc'] 85 | # start_epoch = checkpoint['epoch'] 86 | 87 | net.to(device) 88 | 89 | # model_path = r"deep/checkpoint/mobilenet_v2-b0353104.pth" 90 | # load_model(model_path, net, 0) 91 | 92 | # loss and optimizer 93 | criterion = torch.nn.CrossEntropyLoss() 94 | optimizer = torch.optim.SGD(net.parameters(), args.lr, momentum=0.9, weight_decay=5e-4) 95 | best_acc = 0. 96 | 97 | # train function for each epoch 98 | def train(epoch): 99 | print("\nEpoch : %d"%(epoch+1)) 100 | net.train() 101 | training_loss = 0. 102 | train_loss = 0. 103 | correct = 0 104 | total = 0 105 | interval = args.interval 106 | start = time.time() 107 | for idx, (inputs, labels) in enumerate(trainloader): 108 | # forward 109 | inputs,labels = inputs.to(device),labels.to(device) 110 | outputs = net(inputs) 111 | loss = criterion(outputs, labels) 112 | 113 | # backward 114 | optimizer.zero_grad() 115 | loss.backward() 116 | optimizer.step() 117 | 118 | # accumurating 119 | training_loss += loss.item() 120 | train_loss += loss.item() 121 | correct += outputs.max(dim=1)[1].eq(labels).sum().item() 122 | total += labels.size(0) 123 | 124 | # print 125 | if (idx+1)%interval == 0: 126 | end = time.time() 127 | print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format( 128 | 100.*(idx+1)/len(trainloader), end-start, training_loss/interval, correct, total, 100.*correct/total 129 | )) 130 | training_loss = 0. 131 | start = time.time() 132 | 133 | net.eval() 134 | test_loss = 0. 135 | correct = 0 136 | total = 0 137 | start = time.time() 138 | with torch.no_grad(): 139 | for id, (inputs, labels) in enumerate(testloader): 140 | inputs, labels = inputs.to(device), labels.to(device) 141 | outputs = net(inputs) 142 | loss = criterion(outputs, labels) 143 | test_loss += loss.item() 144 | correct += outputs.max(dim=1)[1].eq(labels).sum().item() 145 | total += labels.size(0) 146 | 147 | print("Testing ...") 148 | end = time.time() 149 | print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format( 150 | 100. * (id + 1) / len(testloader), end - start, test_loss / len(testloader), correct, total, 151 | 100. * correct / total 152 | )) 153 | 154 | return train_loss/len(trainloader), 1.- correct/total 155 | 156 | def test(epoch): 157 | global best_acc 158 | net.eval() 159 | test_loss = 0. 160 | correct = 0 161 | total = 0 162 | start = time.time() 163 | with torch.no_grad(): 164 | for idx, (inputs, labels) in enumerate(testloader): 165 | inputs, labels = inputs.to(device), labels.to(device) 166 | outputs = net(inputs) 167 | 168 | loss = criterion(outputs, labels) 169 | 170 | test_loss += loss.item() 171 | correct += outputs.max(dim=1)[1].eq(labels).sum().item() 172 | total += labels.size(0) 173 | 174 | print("Testing ...") 175 | end = time.time() 176 | print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format( 177 | 100.*(idx+1)/len(testloader), end-start, test_loss/len(testloader), correct, total, 100.*correct/total 178 | )) 179 | 180 | # saving checkpoint 181 | acc = 100.*correct/total 182 | if acc > best_acc: 183 | best_acc = acc 184 | print("Saving parameters to checkpoint/ckpt.t8") 185 | checkpoint = { 186 | 'net_dict':net.state_dict(), 187 | 'acc':acc, 188 | 'epoch':epoch, 189 | } 190 | if not os.path.isdir('checkpoint'): 191 | os.mkdir('checkpoint') 192 | torch.save(checkpoint, './checkpoint/ckpt.t8') 193 | 194 | return test_loss/len(testloader), 1.- correct/total 195 | 196 | # plot figure 197 | x_epoch = [] 198 | record = {'train_loss':[], 'train_err':[], 'test_loss':[], 'test_err':[]} 199 | fig = plt.figure() 200 | ax0 = fig.add_subplot(121, title="loss") 201 | ax1 = fig.add_subplot(122, title="top1err") 202 | def draw_curve(epoch, train_loss, train_err, test_loss, test_err): 203 | global record 204 | record['train_loss'].append(train_loss) 205 | record['train_err'].append(train_err) 206 | record['test_loss'].append(test_loss) 207 | record['test_err'].append(test_err) 208 | 209 | x_epoch.append(epoch) 210 | ax0.plot(x_epoch, record['train_loss'], 'bo-', label='train') 211 | ax0.plot(x_epoch, record['test_loss'], 'ro-', label='val') 212 | ax1.plot(x_epoch, record['train_err'], 'bo-', label='train') 213 | ax1.plot(x_epoch, record['test_err'], 'ro-', label='val') 214 | if epoch == 0: 215 | ax0.legend() 216 | ax1.legend() 217 | fig.savefig("train.jpg") 218 | 219 | # lr decay 220 | def lr_decay(): 221 | global optimizer 222 | for params in optimizer.param_groups: 223 | params['lr'] *= 0.1 224 | lr = params['lr'] 225 | print("Learning rate adjusted to {}".format(lr)) 226 | 227 | def main(): 228 | for epoch in range(start_epoch, start_epoch+40): 229 | train_loss, train_err = train(epoch) 230 | test_loss, test_err = test(epoch) 231 | draw_curve(epoch, train_loss, train_err, test_loss, test_err) 232 | if (epoch+1)%20==0: 233 | lr_decay() 234 | 235 | 236 | if __name__ == '__main__': 237 | main() 238 | -------------------------------------------------------------------------------- /deep/ShuffleNetV2.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | # from torchvision.models.utils import load_state_dict_from_url 4 | 5 | 6 | __all__ = [ 7 | 'ShuffleNetV2', 'shufflenet_v2_x0_5', 'shufflenet_v2_x1_0', 8 | 'shufflenet_v2_x1_5', 'shufflenet_v2_x2_0' 9 | ] 10 | 11 | model_urls = { 12 | 'shufflenetv2_x0.5': 'https://download.pytorch.org/models/shufflenetv2_x0.5-f707e7126e.pth', 13 | 'shufflenetv2_x1.0': 'https://download.pytorch.org/models/shufflenetv2_x1-5666bf0f80.pth', 14 | 'shufflenetv2_x1.5': None, 15 | 'shufflenetv2_x2.0': None, 16 | } 17 | 18 | # 19 | # def channel_shuffle(x, groups): 20 | # batchsize, num_channels, height, width = x.data.size() 21 | # channels_per_group = num_channels // groups 22 | # 23 | # # reshape 24 | # x = x.view(batchsize, groups, 25 | # channels_per_group, height, width) 26 | # 27 | # x = torch.transpose(x, 1, 2).contiguous() 28 | # 29 | # # flatten 30 | # x = x.view(batchsize, -1, height, width) 31 | # 32 | # return x 33 | 34 | 35 | def channel_shuffle(x, groups): 36 | 37 | g = groups 38 | # n, c, h, w = x.size() 39 | # x = x.view(n, g, c // g, h, w).permute( 40 | # 0, 2, 1, 3, 4).contiguous().view(n, c, h, w) 41 | 42 | x = x.reshape(x.shape[0], g, x.shape[1] // g, x.shape[2], x.shape[3]) 43 | x = x.permute(0, 2, 1, 3, 4) 44 | x = x.reshape(x.shape[0], -1, x.shape[3], x.shape[4]) 45 | # x_proj = x[:, :(x.shape[1] // 2), :, :] 46 | # x = x[:, (x.shape[1] // 2):, :, :] 47 | return x 48 | 49 | class InvertedResidual(nn.Module): 50 | def __init__(self, inp, oup, stride): 51 | super(InvertedResidual, self).__init__() 52 | 53 | if not (1 <= stride <= 3): 54 | raise ValueError('illegal stride value') 55 | self.stride = stride 56 | 57 | branch_features = oup // 2 58 | assert (self.stride != 1) or (inp == branch_features << 1) 59 | 60 | if self.stride > 1: 61 | self.branch1 = nn.Sequential( 62 | self.depthwise_conv(inp, inp, kernel_size=3, stride=self.stride, padding=1), 63 | nn.BatchNorm2d(inp), 64 | nn.Conv2d(inp, branch_features, kernel_size=1, stride=1, padding=0, bias=False), 65 | nn.BatchNorm2d(branch_features), 66 | nn.ReLU(inplace=True), 67 | ) 68 | 69 | self.branch2 = nn.Sequential( 70 | nn.Conv2d(inp if (self.stride > 1) else branch_features, 71 | branch_features, kernel_size=1, stride=1, padding=0, bias=False), 72 | nn.BatchNorm2d(branch_features), 73 | nn.ReLU(inplace=True), 74 | self.depthwise_conv(branch_features, branch_features, kernel_size=3, stride=self.stride, padding=1), 75 | nn.BatchNorm2d(branch_features), 76 | nn.Conv2d(branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False), 77 | nn.BatchNorm2d(branch_features), 78 | nn.ReLU(inplace=True), 79 | ) 80 | 81 | @staticmethod 82 | def depthwise_conv(i, o, kernel_size, stride=1, padding=0, bias=False): 83 | return nn.Conv2d(i, o, kernel_size, stride, padding, bias=bias, groups=i) 84 | 85 | def forward(self, x): 86 | if self.stride == 1: 87 | x1, x2 = x.chunk(2, dim=1) 88 | out = torch.cat((x1, self.branch2(x2)), dim=1) 89 | else: 90 | out = torch.cat((self.branch1(x), self.branch2(x)), dim=1) 91 | 92 | out = channel_shuffle(out, 2) 93 | 94 | return out 95 | 96 | 97 | class ShuffleNetV2(nn.Module): 98 | def __init__(self, stages_repeats, stages_out_channels, num_classes=1000,reid = False): 99 | super(ShuffleNetV2, self).__init__() 100 | 101 | if len(stages_repeats) != 3: 102 | raise ValueError('expected stages_repeats as list of 3 positive ints') 103 | if len(stages_out_channels) != 5: 104 | raise ValueError('expected stages_out_channels as list of 5 positive ints') 105 | self._stage_out_channels = stages_out_channels 106 | self.reid = reid 107 | 108 | input_channels = 3 109 | output_channels = self._stage_out_channels[0] 110 | self.conv1 = nn.Sequential( 111 | nn.Conv2d(input_channels, output_channels, 3, 2, 1, bias=False), 112 | nn.BatchNorm2d(output_channels), 113 | nn.ReLU(inplace=True), 114 | ) 115 | input_channels = output_channels 116 | 117 | self.maxpool = nn.MaxPool2d(kernel_size=3, stride=(1,2), padding=1) 118 | 119 | stage_names = ['stage{}'.format(i) for i in [2, 3, 4]] 120 | for name, repeats, output_channels in zip( 121 | stage_names, stages_repeats, self._stage_out_channels[1:]): 122 | seq = [InvertedResidual(input_channels, output_channels, 2)] 123 | for i in range(repeats - 1): 124 | seq.append(InvertedResidual(output_channels, output_channels, 1)) 125 | setattr(self, name, nn.Sequential(*seq)) 126 | input_channels = output_channels 127 | 128 | output_channels = self._stage_out_channels[-1] 129 | self.conv5 = nn.Sequential( 130 | nn.Conv2d(input_channels, output_channels, 1, 1, 0, bias=False), 131 | nn.BatchNorm2d(output_channels), 132 | nn.ReLU(inplace=True), 133 | ) 134 | 135 | self.class_out = nn.Linear(output_channels, num_classes) 136 | 137 | def forward(self, x): 138 | x = self.conv1(x) 139 | x = self.maxpool(x) 140 | x = self.stage2(x) 141 | x = self.stage3(x) 142 | x = self.stage4(x) 143 | x = self.conv5(x) 144 | x = x.mean([2, 3]) # globalpool 145 | if self.reid: 146 | x = x.div(x.norm(p=2,dim=1,keepdim=True)) 147 | return x 148 | x = self.class_out(x) 149 | 150 | return x 151 | 152 | 153 | def _shufflenetv2(arch, pretrained, progress, *args, **kwargs): 154 | model = ShuffleNetV2(*args, **kwargs) 155 | 156 | # if pretrained: 157 | # model_url = model_urls[arch] 158 | # if model_url is None: 159 | # raise NotImplementedError('pretrained {} is not supported as of now'.format(arch)) 160 | # else: 161 | # state_dict = load_state_dict_from_url(model_url, progress=progress) 162 | # model.load_state_dict(state_dict,strict = False) 163 | 164 | return model 165 | 166 | 167 | def shufflenet_v2_x0_5(pretrained=False, progress=True, **kwargs): 168 | """ 169 | Constructs a ShuffleNetV2 with 0.5x output channels, as described in 170 | `"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" 171 | `_. 172 | 173 | Args: 174 | pretrained (bool): If True, returns a model pre-trained on ImageNet 175 | progress (bool): If True, displays a progress bar of the download to stderr 176 | """ 177 | return _shufflenetv2('shufflenetv2_x0.5', pretrained, progress, 178 | [4, 8, 4], [24, 48, 96, 192, 512], **kwargs) 179 | 180 | 181 | def shufflenet_v2_x1_0(pretrained=False, progress=True, **kwargs): 182 | """ 183 | Constructs a ShuffleNetV2 with 1.0x output channels, as described in 184 | `"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" 185 | `_. 186 | 187 | Args: 188 | pretrained (bool): If True, returns a model pre-trained on ImageNet 189 | progress (bool): If True, displays a progress bar of the download to stderr 190 | """ 191 | return _shufflenetv2('shufflenetv2_x1.0', pretrained, progress, 192 | [4, 8, 4], [24, 116, 232, 464, 1024], **kwargs) 193 | 194 | 195 | def shufflenet_v2_x1_5(pretrained=False, progress=True, **kwargs): 196 | """ 197 | Constructs a ShuffleNetV2 with 1.5x output channels, as described in 198 | `"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" 199 | `_. 200 | 201 | Args: 202 | pretrained (bool): If True, returns a model pre-trained on ImageNet 203 | progress (bool): If True, displays a progress bar of the download to stderr 204 | """ 205 | return _shufflenetv2('shufflenetv2_x1.5', pretrained, progress, 206 | [4, 8, 4], [24, 176, 352, 704, 1024], **kwargs) 207 | 208 | 209 | def shufflenet_v2_x2_0(pretrained=False, progress=True, **kwargs): 210 | """ 211 | Constructs a ShuffleNetV2 with 2.0x output channels, as described in 212 | `"ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design" 213 | `_. 214 | 215 | Args: 216 | pretrained (bool): If True, returns a model pre-trained on ImageNet 217 | progress (bool): If True, displays a progress bar of the download to stderr 218 | """ 219 | return _shufflenetv2('shufflenetv2_x2.0', pretrained, progress, 220 | [4, 8, 4], [24, 244, 488, 976, 2048], **kwargs) 221 | 222 | 223 | 224 | if __name__=="__main__": 225 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 226 | 227 | net = shufflenet_v2_x0_5(num_classes=4, pretrained=False) 228 | 229 | x = torch.rand((3,3,224,124)).to(device) 230 | print(x.size()) 231 | torch.save(net.state_dict(), "best.pt") -------------------------------------------------------------------------------- /trainer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import torch 4 | import torch.nn as nn 5 | import numpy as np 6 | import torch.optim as optim 7 | 8 | from deep.dataset import Datasets 9 | from torch.utils.data import DataLoader 10 | from tensorboardX import SummaryWriter 11 | import global_settings as settings 12 | from torch.optim.lr_scheduler import _LRScheduler 13 | from deep.mobilenet import MobileNetv2 14 | from deep.model import Net 15 | from deep.ghost_net import ghost_net 16 | from deep.ShuffleNetV2 import shufflenet_v2_x0_5 17 | 18 | class WarmUpLR(_LRScheduler): 19 | """warmup_training learning rate scheduler 20 | Args: 21 | optimizer: optimzier(e.g. SGD) 22 | total_iters: totoal_iters of warmup phase 23 | """ 24 | 25 | def __init__(self, optimizer, total_iters, last_epoch=-1): 26 | self.total_iters = total_iters 27 | super().__init__(optimizer, last_epoch) 28 | 29 | def get_lr(self): 30 | """we will use the first m batches, and set the learning 31 | rate to base_lr * m / total_iters 32 | """ 33 | return [base_lr * self.last_epoch / (self.total_iters + 1e-8) for base_lr in self.base_lrs] 34 | 35 | 36 | 37 | def train(epoch): # 开启训练 38 | net.train() 39 | 40 | loss_sum = 0.0 41 | correct = 0.0 42 | 43 | for batch_index,(images,labels) in enumerate(train_set): 44 | if epoch <= args.warm: # 第一回合使用warmup学习率策略 45 | warmup_scheduler.step() 46 | images = images.to(device) 47 | labels = labels.to(device) 48 | 49 | optimizer.zero_grad() 50 | outputs = net(images) # 前向 51 | 52 | outputs_pred = torch.softmax(outputs,1) 53 | _, preds = outputs_pred.max(1) 54 | correct += preds.eq(labels).sum() 55 | 56 | loss = loss_function(outputs,labels) # 损失 57 | loss.backward() 58 | optimizer.step() 59 | loss_sum += loss.item() 60 | 61 | n_iter = (epoch - 1) * len(train_set) + batch_index + 1 62 | 63 | last_layer = list(net.children())[-1] 64 | for name, para in last_layer.named_parameters(): 65 | if 'weight' in name: 66 | writer.add_scalar('LastLayerGradients/grad_norm2_weights', para.grad.norm(), n_iter) 67 | if 'bias' in name: 68 | writer.add_scalar('LastLayerGradients/grad_norm2_bias', para.grad.norm(), n_iter) 69 | 70 | print('Training Epoch: {epoch} [{trained_samples}/{total_samples}]\tLoss: {:0.4f}\tLR: {:0.6f}'.format( 71 | loss.item(), 72 | optimizer.param_groups[0]['lr'], 73 | epoch=epoch, 74 | trained_samples=batch_index * args.b + len(images), 75 | total_samples=len(train_set.dataset) 76 | )) 77 | 78 | # update training loss for each iteration 79 | writer.add_scalar('Train/loss', loss.item(), n_iter) 80 | 81 | for name, param in net.named_parameters(): 82 | layer, attr = os.path.splitext(name) 83 | attr = attr[1:] 84 | writer.add_histogram("{}/{}".format(layer, attr), param, epoch) 85 | 86 | return loss_sum/(len(train_set.dataset)/args.b),correct.float() / len(train_set.dataset) 87 | 88 | def eval_training(epoch): # 测试集验证 89 | net.eval() 90 | 91 | test_loss = 0.0 # cost function error 92 | correct = 0.0 93 | 94 | for (images, labels) in test_set: 95 | images = images.to(device) 96 | labels = labels.to(device) 97 | with torch.no_grad(): 98 | outputs = net(images) 99 | loss = loss_function(outputs, labels) 100 | outputs = torch.softmax(outputs,1) 101 | test_loss += loss.item() 102 | _, preds = outputs.max(1) 103 | correct += preds.eq(labels).sum() 104 | 105 | print('Test set: Average loss: {:.4f}, Adduracy: {:.4f}'.format( 106 | test_loss / (len(test_set.dataset)/32), 107 | correct.float() / len(test_set.dataset) 108 | )) 109 | 110 | # add informations to tensorboard 111 | writer.add_scalar('Test/Average loss', test_loss / len(test_set.dataset), epoch) 112 | writer.add_scalar('Test/Adduracy', correct.float() / len(test_set.dataset), epoch) 113 | 114 | return test_loss / (len(test_set.dataset)/32),correct.float() / len(test_set.dataset) 115 | 116 | 117 | def load_model(model_path,net,gpu_id=None): # 加载预训练权重 118 | if gpu_id is not None and isinstance(gpu_id, int) and torch.cuda.is_available(): 119 | device = torch.device("cuda:{}".format(gpu_id)) 120 | else: 121 | device = torch.device("cpu") 122 | if model_path is not None: 123 | 124 | pretrained_params = torch.load(model_path,map_location=device) 125 | 126 | pretrained_params= \ 127 | {k: v for k, v in pretrained_params.items() if 128 | k in net.state_dict().keys() and net.state_dict()[k].numel() == v.numel()} 129 | net.load_state_dict(pretrained_params, strict=False) 130 | 131 | # for k, v in pretrained_params.items(): 132 | # print(k) 133 | # print(v) 134 | # net.load_state_dict(pretrained_params, strict=False) 135 | 136 | # net.load_state_dict(torch.load(model_path, map_location=device)) 137 | 138 | print('Angle device:', device) 139 | # if net is not None: 140 | # # 如果网络计算图和参数是分开保存的,就执行参数加载 141 | # net = net.to(device) 142 | # 143 | # try: 144 | # sk = {} 145 | # for k in net: 146 | # sk[k[7:]] = net[k] 147 | # 148 | # net.load_state_dict(sk) 149 | # except: 150 | # net.load_state_dict(net) 151 | 152 | # net = net 153 | # print('load model') 154 | 155 | 156 | if __name__=="__main__": 157 | 158 | parser = argparse.ArgumentParser() 159 | parser.add_argument('-net', type=str, default="shufflenet", help='net type') 160 | # parser.add_argument('-net', type=str, default="ghost_net_320_8_new", help='net type') 161 | parser.add_argument('-gpu', type=bool, default=True, help='use gpu or not') 162 | parser.add_argument('-w', type=int, default=2, help='number of workers for dataloader') 163 | parser.add_argument('-b', type=int, default=32, help='batch size for dataloader') 164 | parser.add_argument('-s', type=bool, default=True, help='whether shuffle the dataset') 165 | parser.add_argument('-warm', type=int, default=3, help='warm up training phase') 166 | parser.add_argument('-lr', type=float, default=0.01, help='initial learning rate') 167 | args = parser.parse_args() 168 | 169 | device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 170 | 171 | # net = ghost_net(num_classes=751).to(device) 172 | net = shufflenet_v2_x0_5(num_classes=751).to(device) 173 | # net = MobileNetv2(num_classes=751).to(device) 174 | # net = Net(num_classes=751).to(device) 175 | 176 | # model_path = r"deep/checkpoint/mobilenet_v2-b0353104.pth" 177 | 178 | # load_model(model_path,net,0) 179 | 180 | train_path = r"F:\BaiduNetdiskDownload\Market-1501-v15.09.15\pytorch\train_all" # 数据集存放信息 181 | test_path = r"F:\BaiduNetdiskDownload\Market-1501-v15.09.15\pytorch\val" 182 | 183 | train_set = Datasets(train_path,True) # 加载数据集 184 | test_set = Datasets(test_path) 185 | 186 | train_set = DataLoader(train_set, shuffle=True, num_workers=min([os.cpu_count(), 32, 4]), batch_size=32) 187 | test_set = DataLoader(test_set, shuffle=True, num_workers=min([os.cpu_count(), 32, 4]), batch_size=32) 188 | 189 | loss_function = nn.CrossEntropyLoss() # 交叉熵做损失 190 | 191 | optimizer = optim.SGD(net.parameters(), lr=args.lr, momentum=0.9, weight_decay=5e-4) 192 | train_scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=settings.MILESTONES, 193 | gamma=0.1) # learning rate decay 194 | 195 | iter_per_epoch = len(train_set) 196 | warmup_scheduler = WarmUpLR(optimizer, iter_per_epoch * args.warm) 197 | checkpoint_path = os.path.join(settings.CHECKPOINT_PATH, args.net, 'person') 198 | results_file = os.path.join(checkpoint_path ,'results.txt') 199 | 200 | # use tensorboard 201 | if not os.path.exists(settings.LOG_DIR): 202 | os.mkdir(settings.LOG_DIR) 203 | log_dir = os.path.join( 204 | settings.LOG_DIR, args.net, 'person') 205 | print(log_dir) 206 | writer = SummaryWriter(log_dir) 207 | print("done") 208 | # writer.add_graph(net, Variable(input_tensor, requires_grad=True)) 209 | 210 | # 创建模型保存的文件夹 211 | if not os.path.exists(checkpoint_path): 212 | os.makedirs(checkpoint_path) 213 | 214 | best_path = os.path.join(checkpoint_path, '{type}.pth') 215 | checkpoint_path = os.path.join(checkpoint_path, '{net}-{epoch}-{type}.pth') 216 | 217 | file = open(results_file,"w") 218 | file.write("\t\tepoch\t\t\ttrain_loss\t\ttest_loss\t\ttrain_acc\t\ttest_acc\t\tval_acc\t\tbest_acc") 219 | file.write("\n") 220 | file.close() 221 | 222 | best_acc = 0.0 223 | for epoch in range(1, settings.EPOCH): 224 | if epoch > args.warm: 225 | train_scheduler.step(epoch) 226 | 227 | train_loss,train_acc = train(epoch) # 返回训练集平均损失 228 | test_loss,acc = eval_training(epoch) # 返回测试集损失和精度 229 | print("test_set in epoch:{} acc is :{}".format(epoch,acc)) 230 | print() 231 | val_acc = 0 # 返回验证集损失 232 | 233 | # start to save best performance model after learning rate decay to 0.01 234 | if best_acc <= acc: # 按照测试集精度保存 235 | 236 | # checkpoint = { 237 | # 'net_dict': net.state_dict(), 238 | # 'acc': acc, 239 | # 'epoch': epoch, 240 | # } 241 | # if not os.path.isdir('checkpoint'): 242 | # os.mkdir('checkpoint') 243 | # torch.save(checkpoint, './checkpoint/ckpt.t8') 244 | 245 | torch.save(net.state_dict(), best_path.format(type='best')) 246 | best_acc = acc 247 | 248 | if not epoch % settings.SAVE_EPOCH: # 每十个轮次保存一次 249 | torch.save(net.state_dict(), checkpoint_path.format(net=args.net, epoch=epoch, type='regular')) 250 | 251 | f = open(results_file,"a") 252 | f.write("\t\t{}/{}\t\t\t{:.4f}\t\t\t{:.4f}\t\t\t{:.4f}\t\t\t{:.4f}\t\t\t{:.4f}\t\t{:.4f}".format(epoch,settings.EPOCH,train_loss,test_loss,train_acc,acc,val_acc,best_acc)) 253 | # f.write("\t\t"+str(epoch)+"\t\t"+str(train_loss)+"\t\t"+str(test_loss)+"\t\t"+str(train_acc)+"\t\t"+str(acc)+"\t\t"+str(val_acc)+"\t\t"+str(best_acc)) 254 | f.write("\n") 255 | f.close() 256 | writer.close() --------------------------------------------------------------------------------