From 320129602543af0e47b40a883102a2b878922275 Mon Sep 17 00:00:00 2001 From: Jiachenyin1 <542241677@qq.com> Date: Fri, 4 Dec 2020 17:59:19 +0800 Subject: [PATCH] first commit --- .gitignore | 13 + README.md | 1 + configs/deep_sort.yaml | 11 + configs/yolov3.yaml | 7 + configs/yolov3_tiny.yaml | 7 + configs/yolov4_onnx.yaml | 7 + configs/yolov4_trt.yaml | 7 + dataset.py | 111 +++ deep_sort/README.md | 3 + deep_sort/__init__.py | 24 + deep_sort/deep/__init__.py | 0 deep_sort/deep/checkpoint/.gitkeep | 0 deep_sort/deep/evaluate.py | 15 + deep_sort/deep/feature_extractor.py | 55 ++ deep_sort/deep/model.py | 104 ++ deep_sort/deep/original_model.py | 106 +++ deep_sort/deep/test.py | 77 ++ deep_sort/deep/train.jpg | Bin 0 -> 60349 bytes deep_sort/deep/train.py | 189 ++++ deep_sort/deep_sort.py | 118 +++ deep_sort/sort/__init__.py | 0 deep_sort/sort/detection.py | 49 + deep_sort/sort/iou_matching.py | 81 ++ deep_sort/sort/kalman_filter.py | 241 +++++ deep_sort/sort/linear_assignment.py | 193 ++++ deep_sort/sort/nn_matching.py | 178 ++++ deep_sort/sort/preprocessing.py | 73 ++ deep_sort/sort/track.py | 172 ++++ deep_sort/sort/tracker.py | 141 +++ detector/YOLOv3/README.md | 11 + detector/YOLOv3/__init__.py | 9 + detector/YOLOv3/cfg.py | 248 +++++ detector/YOLOv3/cfg/coco.data | 5 + detector/YOLOv3/cfg/coco.names | 80 ++ detector/YOLOv3/cfg/darknet19_448.cfg | 200 ++++ detector/YOLOv3/cfg/tiny-yolo-voc.cfg | 134 +++ detector/YOLOv3/cfg/tiny-yolo.cfg | 140 +++ detector/YOLOv3/cfg/voc.data | 5 + detector/YOLOv3/cfg/voc.names | 20 + detector/YOLOv3/cfg/voc_gaotie.data | 5 + detector/YOLOv3/cfg/yolo-voc.cfg | 258 +++++ detector/YOLOv3/cfg/yolo.cfg | 258 +++++ detector/YOLOv3/cfg/yolo_v3.cfg | 789 ++++++++++++++++ detector/YOLOv3/cfg/yolov3-tiny.cfg | 182 ++++ detector/YOLOv3/cfg/yolov4-tiny.cfg | 281 ++++++ detector/YOLOv3/cfg/yolov4.cfg | 1157 +++++++++++++++++++++++ detector/YOLOv3/darknet.py | 453 +++++++++ detector/YOLOv3/demo/004545.jpg | Bin 0 -> 123072 bytes detector/YOLOv3/demo/results/004545.jpg | Bin 0 -> 125682 bytes detector/YOLOv3/detect.py | 131 +++ detector/YOLOv3/detector.py | 107 +++ detector/YOLOv3/nms/__init__.py | 1 + detector/YOLOv3/nms/build.sh | 5 + detector/YOLOv3/nms/ext/__init__.py | 0 detector/YOLOv3/nms/ext/build.py | 58 ++ detector/YOLOv3/nms/ext/cpu/nms_cpu.cpp | 75 ++ detector/YOLOv3/nms/ext/cpu/vision.h | 7 + detector/YOLOv3/nms/ext/cuda/nms.cu | 131 +++ detector/YOLOv3/nms/ext/cuda/vision.h | 7 + detector/YOLOv3/nms/ext/nms.h | 28 + detector/YOLOv3/nms/ext/vision.cpp | 7 + detector/YOLOv3/nms/nms.py | 34 + detector/YOLOv3/nms/python_nms.py | 59 ++ detector/YOLOv3/region_layer.py | 185 ++++ detector/YOLOv3/weight/.gitkeep | 0 detector/YOLOv3/yolo_layer.py | 181 ++++ detector/YOLOv3/yolo_utils.py | 589 ++++++++++++ detector/__init__.py | 132 +++ detector/trt.py | 212 +++++ utils/__init__.py | 0 utils/asserts.py | 13 + utils/draw.py | 56 ++ utils/evaluation.py | 103 ++ utils/io.py | 133 +++ utils/json_logger.py | 383 ++++++++ utils/log.py | 17 + utils/parser.py | 38 + utils/tools.py | 39 + yolov4_deepsort.py | 141 +++ 79 files changed, 9090 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 configs/deep_sort.yaml create mode 100644 configs/yolov3.yaml create mode 100644 configs/yolov3_tiny.yaml create mode 100644 configs/yolov4_onnx.yaml create mode 100644 configs/yolov4_trt.yaml create mode 100644 dataset.py create mode 100644 deep_sort/README.md create mode 100644 deep_sort/__init__.py create mode 100644 deep_sort/deep/__init__.py create mode 100644 deep_sort/deep/checkpoint/.gitkeep create mode 100644 deep_sort/deep/evaluate.py create mode 100644 deep_sort/deep/feature_extractor.py create mode 100644 deep_sort/deep/model.py create mode 100644 deep_sort/deep/original_model.py create mode 100644 deep_sort/deep/test.py create mode 100644 deep_sort/deep/train.jpg create mode 100644 deep_sort/deep/train.py create mode 100644 deep_sort/deep_sort.py create mode 100644 deep_sort/sort/__init__.py create mode 100644 deep_sort/sort/detection.py create mode 100644 deep_sort/sort/iou_matching.py create mode 100644 deep_sort/sort/kalman_filter.py create mode 100644 deep_sort/sort/linear_assignment.py create mode 100644 deep_sort/sort/nn_matching.py create mode 100644 deep_sort/sort/preprocessing.py create mode 100644 deep_sort/sort/track.py create mode 100644 deep_sort/sort/tracker.py create mode 100644 detector/YOLOv3/README.md create mode 100644 detector/YOLOv3/__init__.py create mode 100644 detector/YOLOv3/cfg.py create mode 100644 detector/YOLOv3/cfg/coco.data create mode 100644 detector/YOLOv3/cfg/coco.names create mode 100644 detector/YOLOv3/cfg/darknet19_448.cfg create mode 100644 detector/YOLOv3/cfg/tiny-yolo-voc.cfg create mode 100644 detector/YOLOv3/cfg/tiny-yolo.cfg create mode 100644 detector/YOLOv3/cfg/voc.data create mode 100644 detector/YOLOv3/cfg/voc.names create mode 100644 detector/YOLOv3/cfg/voc_gaotie.data create mode 100644 detector/YOLOv3/cfg/yolo-voc.cfg create mode 100644 detector/YOLOv3/cfg/yolo.cfg create mode 100644 detector/YOLOv3/cfg/yolo_v3.cfg create mode 100644 detector/YOLOv3/cfg/yolov3-tiny.cfg create mode 100644 detector/YOLOv3/cfg/yolov4-tiny.cfg create mode 100644 detector/YOLOv3/cfg/yolov4.cfg create mode 100644 detector/YOLOv3/darknet.py create mode 100644 detector/YOLOv3/demo/004545.jpg create mode 100644 detector/YOLOv3/demo/results/004545.jpg create mode 100644 detector/YOLOv3/detect.py create mode 100644 detector/YOLOv3/detector.py create mode 100644 detector/YOLOv3/nms/__init__.py create mode 100644 detector/YOLOv3/nms/build.sh create mode 100644 detector/YOLOv3/nms/ext/__init__.py create mode 100644 detector/YOLOv3/nms/ext/build.py create mode 100644 detector/YOLOv3/nms/ext/cpu/nms_cpu.cpp create mode 100644 detector/YOLOv3/nms/ext/cpu/vision.h create mode 100644 detector/YOLOv3/nms/ext/cuda/nms.cu create mode 100644 detector/YOLOv3/nms/ext/cuda/vision.h create mode 100644 detector/YOLOv3/nms/ext/nms.h create mode 100644 detector/YOLOv3/nms/ext/vision.cpp create mode 100644 detector/YOLOv3/nms/nms.py create mode 100644 detector/YOLOv3/nms/python_nms.py create mode 100644 detector/YOLOv3/region_layer.py create mode 100644 detector/YOLOv3/weight/.gitkeep create mode 100644 detector/YOLOv3/yolo_layer.py create mode 100644 detector/YOLOv3/yolo_utils.py create mode 100644 detector/__init__.py create mode 100644 detector/trt.py create mode 100644 utils/__init__.py create mode 100644 utils/asserts.py create mode 100644 utils/draw.py create mode 100644 utils/evaluation.py create mode 100644 utils/io.py create mode 100644 utils/json_logger.py create mode 100644 utils/log.py create mode 100644 utils/parser.py create mode 100644 utils/tools.py create mode 100644 yolov4_deepsort.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37ed2f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Folders +__pycache__/ +build/ +*.egg-info + + +# Files +*.weights +*.t7 +*.mp4 +*.avi +*.so +*.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2fccb5 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# deep_count diff --git a/configs/deep_sort.yaml b/configs/deep_sort.yaml new file mode 100644 index 0000000..8aa24ad --- /dev/null +++ b/configs/deep_sort.yaml @@ -0,0 +1,11 @@ +DEEPSORT: + REID_CKPT: "./deep_sort/deep/checkpoint/ckpt.t7" + REID_CKPT_Car: "./deep_sort/deep/checkpoint/ckpt_car.t7" + 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 + \ No newline at end of file diff --git a/configs/yolov3.yaml b/configs/yolov3.yaml new file mode 100644 index 0000000..a46e474 --- /dev/null +++ b/configs/yolov3.yaml @@ -0,0 +1,7 @@ +YOLOV3: + CFG: "./detector/YOLOv3/cfg/yolov4.cfg" + WEIGHT: "./detector/YOLOv3/weight/yolov4.weights" + CLASS_NAMES: "./detector/YOLOv3/cfg/coco.names" + + SCORE_THRESH: 0.1 + NMS_THRESH: 0.4 diff --git a/configs/yolov3_tiny.yaml b/configs/yolov3_tiny.yaml new file mode 100644 index 0000000..1261e68 --- /dev/null +++ b/configs/yolov3_tiny.yaml @@ -0,0 +1,7 @@ +YOLOV3: + CFG: "./detector/YOLOv3/cfg/yolov3-tiny.cfg" + WEIGHT: "./detector/YOLOv3/weight/yolov3-tiny.weights" + CLASS_NAMES: "./detector/YOLOv3/cfg/coco.names" + + SCORE_THRESH: 0.5 + NMS_THRESH: 0.4 \ No newline at end of file diff --git a/configs/yolov4_onnx.yaml b/configs/yolov4_onnx.yaml new file mode 100644 index 0000000..d63eb08 --- /dev/null +++ b/configs/yolov4_onnx.yaml @@ -0,0 +1,7 @@ +YOLOV4: + CFG: "./detector/YOLOv3/cfg/yolov4.cfg" + WEIGHT: "./detector/YOLOv3/weight/yolov4_1_3_416_416_static.onnx" + CLASS_NAMES: "./detector/YOLOv3/cfg/coco.names" + + SCORE_THRESH: 0.1 + NMS_THRESH: 0.4 diff --git a/configs/yolov4_trt.yaml b/configs/yolov4_trt.yaml new file mode 100644 index 0000000..367509d --- /dev/null +++ b/configs/yolov4_trt.yaml @@ -0,0 +1,7 @@ +YOLOV4: + CFG: "./detector/YOLOv3/cfg/yolov4.cfg" + WEIGHT: "./detector/YOLOv3/weight/yolov4.engine" + CLASS_NAMES: "./detector/YOLOv3/cfg/coco.names" + + SCORE_THRESH: 0.4 + NMS_THRESH: 0.4 diff --git a/dataset.py b/dataset.py new file mode 100644 index 0000000..5cfdf29 --- /dev/null +++ b/dataset.py @@ -0,0 +1,111 @@ +import cv2 +import numpy as np +from threading import Thread +import time +import os +class LoadStreams: # multiple IP or RTSP cameras + def __init__(self, sources='streams.txt', img_size=640): + self.mode = 'images' + self.img_size = img_size + sources = [sources] + + n = len(sources) + self.imgs = [None] * n + self.sources = sources + for i, s in enumerate(sources): + # Start the thread to read frames from the video stream + print('%g/%g: %s... ' % (i + 1, n, s), end='') + cap = cv2.VideoCapture(0 if s == '0' else s) + assert cap.isOpened(), 'Failed to open %s' % s + w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + fps = cap.get(cv2.CAP_PROP_FPS) % 100 + _, self.imgs[i] = cap.read() # guarantee first frame + thread = Thread(target=self.update, args=([i, cap]), daemon=True) + print(' success (%gx%g at %.2f FPS).' % (w, h, fps)) + thread.start() + print('') # newline + + def update(self, index, cap): + # Read next stream frame in a daemon thread + n = 0 + while cap.isOpened(): + n += 1 + # _, self.imgs[index] = cap.read() + cap.grab() + if n == 4: # read every 4th frame + _, self.imgs[index] = cap.retrieve() + n = 0 + time.sleep(0.01) # wait time + + def __iter__(self): + self.count = -1 + return self + + def __next__(self): + self.count += 1 + img0 = self.imgs.copy() + if cv2.waitKey(1) == ord('q'): # q to quit + cv2.destroyAllWindows() + raise StopIteration + return self.sources, img0, None + + def __len__(self): + return 0 # 1E12 frames = 32 streams at 30 FPS for 30 years + + + +class datasets: # multiple IP or RTSP cameras + def __init__(self, sources='streams.txt', img_size=640): + self.mode = 'images' + self.img_size = img_size + + if os.path.isfile(sources): + with open(sources, 'r') as f: + sources = [x.strip() for x in f.read().splitlines() if len(x.strip())] + else: + sources = [sources] + + n = len(sources) + self.imgs = [None] * n + self.sources = sources + for i, s in enumerate(sources): + # Start the thread to read frames from the video stream + print('%g/%g: %s... ' % (i + 1, n, s), end='') + cap = cv2.VideoCapture(0 if s == '0' else s) + assert cap.isOpened(), 'Failed to open %s' % s + w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + fps = cap.get(cv2.CAP_PROP_FPS) % 100 + _, self.imgs[i] = cap.read() # guarantee first frame + thread = Thread(target=self.update, args=([i, cap]), daemon=True) + print(' success (%gx%g at %.2f FPS).' % (w, h, fps)) + thread.start() + print('') # newline + + def update(self, index, cap): + # Read next stream frame in a daemon thread + n = 0 + while cap.isOpened(): + n += 1 + # _, self.imgs[index] = cap.read() + cap.grab() + if n == 1: # read every 4th frame + _, self.imgs[index] = cap.retrieve() + n = 0 + time.sleep(0.01) # wait time + + def __iter__(self): + self.count = -1 + return self + + def __next__(self): + self.count += 1 + img0 = self.imgs.copy() + if cv2.waitKey(1) == ord('q'): # q to quit + cv2.destroyAllWindows() + raise StopIteration + return self.sources, img0, None + + def __len__(self): + return 0 # 1E12 frames = 32 streams at 30 FPS for 30 years \ No newline at end of file diff --git a/deep_sort/README.md b/deep_sort/README.md new file mode 100644 index 0000000..e89c9b3 --- /dev/null +++ b/deep_sort/README.md @@ -0,0 +1,3 @@ +# Deep Sort + +This is the implemention of deep sort with pytorch. \ No newline at end of file diff --git a/deep_sort/__init__.py b/deep_sort/__init__.py new file mode 100644 index 0000000..8bb81a7 --- /dev/null +++ b/deep_sort/__init__.py @@ -0,0 +1,24 @@ +from deep_sort.deep_sort import DeepSort #在我们执行import时,当前目录是不会变的(就算是执行子目录的文件),还是需要完整的包名。 + + +__all__ = ['DeepSort', 'build_tracker','build_tracker_car'] ##import * 的时候,防止导入过多的变量,把导入的限制在__all__中 + +def build_tracker(cfg, use_cuda): + return DeepSort(cfg.DEEPSORT.REID_CKPT, + max_dist=cfg.DEEPSORT.MAX_DIST, min_confidence=cfg.DEEPSORT.MIN_CONFIDENCE, + nms_max_overlap=cfg.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE, + max_age=cfg.DEEPSORT.MAX_AGE, n_init=cfg.DEEPSORT.N_INIT, nn_budget=cfg.DEEPSORT.NN_BUDGET, use_cuda=use_cuda) + + +def build_tracker_car(cfg, use_cuda): + return DeepSort(cfg.DEEPSORT.REID_CKPT_Car, + max_dist=cfg.DEEPSORT.MAX_DIST, min_confidence=cfg.DEEPSORT.MIN_CONFIDENCE, + nms_max_overlap=cfg.DEEPSORT.NMS_MAX_OVERLAP, max_iou_distance=cfg.DEEPSORT.MAX_IOU_DISTANCE, + max_age=cfg.DEEPSORT.MAX_AGE, n_init=cfg.DEEPSORT.N_INIT, nn_budget=cfg.DEEPSORT.NN_BUDGET, use_cuda=use_cuda,num_class = 685) + + + + + + + diff --git a/deep_sort/deep/__init__.py b/deep_sort/deep/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deep_sort/deep/checkpoint/.gitkeep b/deep_sort/deep/checkpoint/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/deep_sort/deep/evaluate.py b/deep_sort/deep/evaluate.py new file mode 100644 index 0000000..85eaa6f --- /dev/null +++ b/deep_sort/deep/evaluate.py @@ -0,0 +1,15 @@ +import torch + +features = torch.load("features.pth") +qf = features["qf"] +ql = features["ql"] +gf = features["gf"] +gl = features["gl"] + +scores = qf.mm(gf.t()) +res = scores.topk(5, dim=1)[1][:,0] +top1correct = gl[res].eq(ql).sum().item() + +print("Acc top1:{:.3f}".format(top1correct/ql.size(0))) + + diff --git a/deep_sort/deep/feature_extractor.py b/deep_sort/deep/feature_extractor.py new file mode 100644 index 0000000..1fb8ea2 --- /dev/null +++ b/deep_sort/deep/feature_extractor.py @@ -0,0 +1,55 @@ +import torch +import torchvision.transforms as transforms +import numpy as np +import cv2 +import logging + +from deep_sort.deep.model import Net + +class Extractor(object): + def __init__(self, model_path, use_cuda=True,num_class = 751): + self.net = Net(reid=True,num_classes = num_class) + self.device = "cuda" if torch.cuda.is_available() and use_cuda else "cpu" + state_dict = torch.load(model_path, map_location=lambda storage, loc: storage)['net_dict'] + self.net.load_state_dict(state_dict) + logger = logging.getLogger("root.tracker") + logger.info("Loading weights from {}... Done!".format(model_path)) + self.net.to(self.device) + self.size = (64, 128) + self.norm = transforms.Compose([ + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + ]) + + + + def _preprocess(self, im_crops): + """ + TODO: + 1. to float with scale from 0 to 1 + 2. resize to (64, 128) as Market1501 dataset did + 3. concatenate to a numpy array + 3. to torch Tensor + 4. normalize + """ + def _resize(im, size): + return cv2.resize(im.astype(np.float32)/255., size) + + im_batch = torch.cat([self.norm(_resize(im, self.size)).unsqueeze(0) for im in im_crops], dim=0).float() + return im_batch + + + def __call__(self, im_crops): + im_batch = self._preprocess(im_crops) + with torch.no_grad(): + im_batch = im_batch.to(self.device) + features = self.net(im_batch) + return features.cpu().numpy() + + +if __name__ == '__main__': + img = cv2.imread("demo.jpg")[:,:,(2,1,0)] + extr = Extractor("checkpoint/ckpt.t7") + feature = extr(img) + print(feature.shape) + diff --git a/deep_sort/deep/model.py b/deep_sort/deep/model.py new file mode 100644 index 0000000..0427f71 --- /dev/null +++ b/deep_sort/deep/model.py @@ -0,0 +1,104 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +class BasicBlock(nn.Module): + def __init__(self, c_in, c_out,is_downsample=False): + super(BasicBlock,self).__init__() + self.is_downsample = is_downsample + if is_downsample: + self.conv1 = nn.Conv2d(c_in, c_out, 3, stride=2, padding=1, bias=False) + else: + self.conv1 = nn.Conv2d(c_in, c_out, 3, stride=1, padding=1, bias=False) + self.bn1 = nn.BatchNorm2d(c_out) + self.relu = nn.ReLU(True) + self.conv2 = nn.Conv2d(c_out,c_out,3,stride=1,padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(c_out) + if is_downsample: + self.downsample = nn.Sequential( + nn.Conv2d(c_in, c_out, 1, stride=2, bias=False), + nn.BatchNorm2d(c_out) + ) + elif c_in != c_out: + self.downsample = nn.Sequential( + nn.Conv2d(c_in, c_out, 1, stride=1, bias=False), + nn.BatchNorm2d(c_out) + ) + self.is_downsample = True + + def forward(self,x): + y = self.conv1(x) + y = self.bn1(y) + y = self.relu(y) + y = self.conv2(y) + y = self.bn2(y) + if self.is_downsample: + x = self.downsample(x) + return F.relu(x.add(y),True) ##残差网络在这 + +def make_layers(c_in,c_out,repeat_times, is_downsample=False): + blocks = [] + for i in range(repeat_times): + if i ==0: + blocks += [BasicBlock(c_in,c_out, is_downsample=is_downsample),] + else: + blocks += [BasicBlock(c_out,c_out),] + return nn.Sequential(*blocks) + +class Net(nn.Module): + def __init__(self, num_classes=751 ,reid=False): + super(Net,self).__init__() + # 3 128 64 + self.conv = nn.Sequential( + nn.Conv2d(3,64,3,stride=1,padding=1), + nn.BatchNorm2d(64), + nn.ReLU(inplace=True), + # nn.Conv2d(32,32,3,stride=1,padding=1), + # nn.BatchNorm2d(32), + # nn.ReLU(inplace=True), + nn.MaxPool2d(3,2,padding=1), + ) + # 32 64 32 + self.layer1 = make_layers(64,64,2,False) + # 32 64 32 + self.layer2 = make_layers(64,128,2,True) + # 64 32 16 + self.layer3 = make_layers(128,256,2,True) + # 128 16 8 + self.layer4 = make_layers(256,512,2,True) + # 256 8 4 + self.avgpool = nn.AvgPool2d((8,4),1) + # 256 1 1 + self.reid = reid + self.classifier = nn.Sequential( + nn.Linear(512, 256), + nn.BatchNorm1d(256), + nn.ReLU(inplace=True), + nn.Dropout(), + nn.Linear(256, num_classes), + ) + + def forward(self, x): + x = self.conv(x) + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + x = self.avgpool(x) + x = x.view(x.size(0),-1) + # B x 128 + if self.reid: + x = x.div(x.norm(p=2,dim=1,keepdim=True)) + return x + # classifier + x = self.classifier(x) + return x + + +if __name__ == '__main__': + net = Net() + x = torch.randn(4,3,128,64) + y = net(x) + # import ipdb; ipdb.set_trace() + + diff --git a/deep_sort/deep/original_model.py b/deep_sort/deep/original_model.py new file mode 100644 index 0000000..72453a6 --- /dev/null +++ b/deep_sort/deep/original_model.py @@ -0,0 +1,106 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F + +class BasicBlock(nn.Module): + def __init__(self, c_in, c_out,is_downsample=False): + super(BasicBlock,self).__init__() + self.is_downsample = is_downsample + if is_downsample: + self.conv1 = nn.Conv2d(c_in, c_out, 3, stride=2, padding=1, bias=False) + else: + self.conv1 = nn.Conv2d(c_in, c_out, 3, stride=1, padding=1, bias=False) + self.bn1 = nn.BatchNorm2d(c_out) + self.relu = nn.ReLU(True) + self.conv2 = nn.Conv2d(c_out,c_out,3,stride=1,padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(c_out) + if is_downsample: + self.downsample = nn.Sequential( + nn.Conv2d(c_in, c_out, 1, stride=2, bias=False), + nn.BatchNorm2d(c_out) + ) + elif c_in != c_out: + self.downsample = nn.Sequential( + nn.Conv2d(c_in, c_out, 1, stride=1, bias=False), + nn.BatchNorm2d(c_out) + ) + self.is_downsample = True + + def forward(self,x): + y = self.conv1(x) + y = self.bn1(y) + y = self.relu(y) + y = self.conv2(y) + y = self.bn2(y) + if self.is_downsample: + x = self.downsample(x) + return F.relu(x.add(y),True) + +def make_layers(c_in,c_out,repeat_times, is_downsample=False): + blocks = [] + for i in range(repeat_times): + if i ==0: + blocks += [BasicBlock(c_in,c_out, is_downsample=is_downsample),] + else: + blocks += [BasicBlock(c_out,c_out),] + return nn.Sequential(*blocks) + +class Net(nn.Module): + def __init__(self, num_classes=625 ,reid=False): + super(Net,self).__init__() + # 3 128 64 + self.conv = nn.Sequential( + nn.Conv2d(3,32,3,stride=1,padding=1), + nn.BatchNorm2d(32), + nn.ELU(inplace=True), + nn.Conv2d(32,32,3,stride=1,padding=1), + nn.BatchNorm2d(32), + nn.ELU(inplace=True), + nn.MaxPool2d(3,2,padding=1), + ) + # 32 64 32 + self.layer1 = make_layers(32,32,2,False) + # 32 64 32 + self.layer2 = make_layers(32,64,2,True) + # 64 32 16 + self.layer3 = make_layers(64,128,2,True) + # 128 16 8 + self.dense = nn.Sequential( + nn.Dropout(p=0.6), + nn.Linear(128*16*8, 128), + nn.BatchNorm1d(128), + nn.ELU(inplace=True) + ) + # 256 1 1 + self.reid = reid + self.batch_norm = nn.BatchNorm1d(128) + self.classifier = nn.Sequential( + nn.Linear(128, num_classes), + ) + + def forward(self, x): + x = self.conv(x) + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + + x = x.view(x.size(0),-1) + if self.reid: + x = self.dense[0](x) + x = self.dense[1](x) + x = x.div(x.norm(p=2,dim=1,keepdim=True)) + return x + x = self.dense(x) + # B x 128 + # classifier + x = self.classifier(x) + return x + + +if __name__ == '__main__': + net = Net(reid=True) + x = torch.randn(4,3,128,64) + y = net(x) + import ipdb; ipdb.set_trace() + + diff --git a/deep_sort/deep/test.py b/deep_sort/deep/test.py new file mode 100644 index 0000000..ecac0ad --- /dev/null +++ b/deep_sort/deep/test.py @@ -0,0 +1,77 @@ +import torch +import torch.backends.cudnn as cudnn +import torchvision + +import argparse +import os + +from model import Net + +parser = argparse.ArgumentParser(description="Train on market1501") +parser.add_argument("--data-dir",default='data',type=str) +parser.add_argument("--no-cuda",action="store_true") +parser.add_argument("--gpu-id",default=0,type=int) +args = parser.parse_args() + +# device +device = "cuda:{}".format(args.gpu_id) if torch.cuda.is_available() and not args.no_cuda else "cpu" +if torch.cuda.is_available() and not args.no_cuda: + cudnn.benchmark = True + +# data loader +root = args.data_dir +query_dir = os.path.join(root,"query") +gallery_dir = os.path.join(root,"gallery") +transform = torchvision.transforms.Compose([ + torchvision.transforms.Resize((128,64)), + torchvision.transforms.ToTensor(), + torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) +]) +queryloader = torch.utils.data.DataLoader( + torchvision.datasets.ImageFolder(query_dir, transform=transform), + batch_size=64, shuffle=False +) +galleryloader = torch.utils.data.DataLoader( + torchvision.datasets.ImageFolder(gallery_dir, transform=transform), + batch_size=64, shuffle=False +) + +# net definition +net = Net(reid=True) +assert os.path.isfile("./checkpoint/ckpt.t7"), "Error: no checkpoint file found!" +print('Loading from checkpoint/ckpt.t7') +checkpoint = torch.load("./checkpoint/ckpt.t7") +net_dict = checkpoint['net_dict'] +net.load_state_dict(net_dict, strict=False) +net.eval() +net.to(device) + +# compute features +query_features = torch.tensor([]).float() +query_labels = torch.tensor([]).long() +gallery_features = torch.tensor([]).float() +gallery_labels = torch.tensor([]).long() + +with torch.no_grad(): + for idx,(inputs,labels) in enumerate(queryloader): + inputs = inputs.to(device) + features = net(inputs).cpu() + query_features = torch.cat((query_features, features), dim=0) + query_labels = torch.cat((query_labels, labels)) + + for idx,(inputs,labels) in enumerate(galleryloader): + inputs = inputs.to(device) + features = net(inputs).cpu() + gallery_features = torch.cat((gallery_features, features), dim=0) + gallery_labels = torch.cat((gallery_labels, labels)) + +gallery_labels -= 2 + +# save features +features = { + "qf": query_features, + "ql": query_labels, + "gf": gallery_features, + "gl": gallery_labels +} +torch.save(features,"features.pth") \ No newline at end of file diff --git a/deep_sort/deep/train.jpg b/deep_sort/deep/train.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3635a614738828b880aa862bc52423848ac8e472 GIT binary patch literal 60349 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 Tr2qf>|HwN2uiOuY-_!pONQn{Y literal 0 HcmV?d00001 diff --git a/deep_sort/deep/train.py b/deep_sort/deep/train.py new file mode 100644 index 0000000..5322af9 --- /dev/null +++ b/deep_sort/deep/train.py @@ -0,0 +1,189 @@ +import argparse +import os +import time + +import numpy as np +import matplotlib.pyplot as plt +import torch +import torch.backends.cudnn as cudnn +import torchvision + +from model import Net + +parser = argparse.ArgumentParser(description="Train on market1501") +parser.add_argument("--data-dir",default='/home/hncr/workspace/MOT_TRACKING/deep_sort_pytorch-master/deep_sort/deep/data',type=str) +parser.add_argument("--no-cuda",action="store_true") +parser.add_argument("--gpu-id",default=0,type=int) +parser.add_argument("--lr",default=0.01, type=float) +parser.add_argument("--interval",'-i',default=20,type=int) +parser.add_argument('--resume', '-r',default=False,action='store_true') +args = parser.parse_args() + +# device +device = "cuda:{}".format(args.gpu_id) if torch.cuda.is_available() and not args.no_cuda else "cpu" +if torch.cuda.is_available() and not args.no_cuda: + cudnn.benchmark = True + +# data loading +root = args.data_dir +train_dir = os.path.join(root,"train/") +test_dir = os.path.join(root,"test/") +transform_train = torchvision.transforms.Compose([ + torchvision.transforms.RandomCrop((128,64),padding=4), + torchvision.transforms.RandomHorizontalFlip(), + torchvision.transforms.ToTensor(), + torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) +]) +transform_test = torchvision.transforms.Compose([ + torchvision.transforms.Resize((128,64)), + torchvision.transforms.ToTensor(), + torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) +]) +trainloader = torch.utils.data.DataLoader( + torchvision.datasets.ImageFolder(train_dir, transform=transform_train), + batch_size=64,shuffle=True +) +testloader = torch.utils.data.DataLoader( + torchvision.datasets.ImageFolder(test_dir, transform=transform_test), + batch_size=64,shuffle=True +) +num_classes = max(len(trainloader.dataset.classes), len(testloader.dataset.classes)) + +# net definition +start_epoch = 0 +net = Net(num_classes=num_classes) +if args.resume: + assert os.path.isfile("/home/hncr/workspace/MOT_TRACKING/deep_sort_pytorch-master/checkpoint/ckpt.t7"), "Error: no checkpoint file found!" + print('Loading from checkpoint/ckpt.t7') + checkpoint = torch.load("/home/hncr/workspace/MOT_TRACKING/deep_sort_pytorch-master/checkpoint/ckpt.t7") + # import ipdb; ipdb.set_trace() + net_dict = checkpoint['net_dict'] + net.load_state_dict(net_dict) + best_acc = checkpoint['acc'] + start_epoch = checkpoint['epoch'] +net.to(device) + +# loss and optimizer +criterion = torch.nn.CrossEntropyLoss() +optimizer = torch.optim.SGD(net.parameters(), args.lr, momentum=0.9, weight_decay=5e-4) +best_acc = 0. + +# train function for each epoch +def train(epoch): + print("\nEpoch : %d"%(epoch+1)) + net.train() + training_loss = 0. + train_loss = 0. + correct = 0 + total = 0 + interval = args.interval + start = time.time() + for idx, (inputs, labels) in enumerate(trainloader): + # forward + inputs,labels = inputs.to(device),labels.to(device) + outputs = net(inputs) + loss = criterion(outputs, labels) + + # backward + optimizer.zero_grad() + loss.backward() + optimizer.step() + + # accumurating + training_loss += loss.item() + train_loss += loss.item() + correct += outputs.max(dim=1)[1].eq(labels).sum().item() + total += labels.size(0) + + # print + if (idx+1)%interval == 0: + end = time.time() + print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format( + 100.*(idx+1)/len(trainloader), end-start, training_loss/interval, correct, total, 100.*correct/total + )) + training_loss = 0. + start = time.time() + + return train_loss/len(trainloader), 1.- correct/total + +def test(epoch): + global best_acc + net.eval() + test_loss = 0. + correct = 0 + total = 0 + start = time.time() + with torch.no_grad(): + for idx, (inputs, labels) in enumerate(testloader): + inputs, labels = inputs.to(device), labels.to(device) + outputs = net(inputs) + loss = criterion(outputs, labels) + + test_loss += loss.item() + correct += outputs.max(dim=1)[1].eq(labels).sum().item() + total += labels.size(0) + + print("Testing ...") + end = time.time() + print("[progress:{:.1f}%]time:{:.2f}s Loss:{:.5f} Correct:{}/{} Acc:{:.3f}%".format( + 100.*(idx+1)/len(testloader), end-start, test_loss/len(testloader), correct, total, 100.*correct/total + )) + + # saving checkpoint + acc = 100.*correct/total + if acc > best_acc: + best_acc = acc + print("Saving parameters to checkpoint/ckpt.t7") + checkpoint = { + 'net_dict':net.state_dict(), + 'acc':acc, + 'epoch':epoch, + } + if not os.path.isdir('checkpoint'): + os.mkdir('checkpoint') + torch.save(checkpoint, './checkpoint/ckpt.t7') + + return test_loss/len(testloader), 1.- correct/total + +# plot figure +x_epoch = [] +record = {'train_loss':[], 'train_err':[], 'test_loss':[], 'test_err':[]} +fig = plt.figure() +ax0 = fig.add_subplot(121, title="loss") +ax1 = fig.add_subplot(122, title="top1err") +def draw_curve(epoch, train_loss, train_err, test_loss, test_err): + global record + record['train_loss'].append(train_loss) + record['train_err'].append(train_err) + record['test_loss'].append(test_loss) + record['test_err'].append(test_err) + + x_epoch.append(epoch) + ax0.plot(x_epoch, record['train_loss'], 'bo-', label='train') + ax0.plot(x_epoch, record['test_loss'], 'ro-', label='val') + ax1.plot(x_epoch, record['train_err'], 'bo-', label='train') + ax1.plot(x_epoch, record['test_err'], 'ro-', label='val') + if epoch == 0: + ax0.legend() + ax1.legend() + fig.savefig("train.jpg") + +# lr decay +def lr_decay(): + global optimizer + for params in optimizer.param_groups: + params['lr'] *= 0.1 + lr = params['lr'] + print("Learning rate adjusted to {}".format(lr)) + +def main(): + for epoch in range(start_epoch, start_epoch+70): + train_loss, train_err = train(epoch) + test_loss, test_err = test(epoch) + draw_curve(epoch, train_loss, train_err, test_loss, test_err) + if (epoch+1)%20==0: + lr_decay() + + +if __name__ == '__main__': + main() diff --git a/deep_sort/deep_sort.py b/deep_sort/deep_sort.py new file mode 100644 index 0000000..e5476f6 --- /dev/null +++ b/deep_sort/deep_sort.py @@ -0,0 +1,118 @@ +import numpy as np +import torch + +from deep_sort.deep.feature_extractor import Extractor +from deep_sort.sort.nn_matching import NearestNeighborDistanceMetric +from deep_sort.sort.preprocessing import non_max_suppression +from deep_sort.sort.detection import Detection +from deep_sort.sort.tracker import Tracker + + +__all__ = ['DeepSort'] + + +class DeepSort(object): + 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,num_class = 751): + self.min_confidence = min_confidence + self.nms_max_overlap = nms_max_overlap + + self.extractor = Extractor(model_path, use_cuda=use_cuda,num_class=num_class) + + max_cosine_distance = max_dist + nn_budget = 100 + metric = NearestNeighborDistanceMetric("cosine", max_cosine_distance, nn_budget) + self.tracker = Tracker(metric, max_iou_distance=max_iou_distance, max_age=max_age, n_init=n_init) + + def update(self, bbox_xywh, confidences, ori_img,count): + self.height, self.width = ori_img.shape[:2] + # generate detections + features = self._get_features(bbox_xywh, ori_img)##reid 得到512的 行人embamding + bbox_tlwh = self._xywh_to_tlwh(bbox_xywh) ## xywh 转成 左上角 wh + detections = [Detection(bbox_tlwh[i], conf, features[i]) for i,conf in enumerate(confidences) if conf>self.min_confidence] + + # run on non-maximum supression + boxes = np.array([d.tlwh for d in detections]) + scores = np.array([d.confidence for d in detections]) + indices = non_max_suppression(boxes, self.nms_max_overlap, scores) + detections = [detections[i] for i in indices] + + # update tracker + self.tracker.predict() + self.tracker.update(detections) + + # output bbox identities + outputs = [] + detection_id = len(bbox_xywh) + + for track in self.tracker.tracks: + if not track.is_confirmed() or track.time_since_update > 1: + continue + box = track.to_tlwh() + x1,y1,x2,y2 = self._tlwh_to_xyxy(box) + track_id = track.track_id + count.append(int(track_id)) + outputs.append(np.array([x1,y1,x2,y2,track_id], dtype=np.int)) + if len(outputs) > 0: + outputs = np.stack(outputs,axis=0) + return outputs,count,detection_id + + + """ + TODO: + Convert bbox from xc_yc_w_h to xtl_ytl_w_h + Thanks JieChen91@github.com for reporting this bug! + """ + @staticmethod ##该方法不强制要求传递参数,可以不需要实例化: + def _xywh_to_tlwh(bbox_xywh): + if isinstance(bbox_xywh, np.ndarray): + bbox_tlwh = bbox_xywh.copy() + elif isinstance(bbox_xywh, torch.Tensor): + bbox_tlwh = bbox_xywh.clone() + bbox_tlwh[:,0] = bbox_xywh[:,0] - bbox_xywh[:,2]/2. + bbox_tlwh[:,1] = bbox_xywh[:,1] - bbox_xywh[:,3]/2. + return bbox_tlwh + + + def _xywh_to_xyxy(self, bbox_xywh): + x,y,w,h = bbox_xywh + x1 = max(int(x-w/2),0) + x2 = min(int(x+w/2),self.width-1) + y1 = max(int(y-h/2),0) + y2 = min(int(y+h/2),self.height-1) + return x1,y1,x2,y2 + + def _tlwh_to_xyxy(self, bbox_tlwh): + """ + TODO: + Convert bbox from xtl_ytl_w_h to xc_yc_w_h + Thanks JieChen91@github.com for reporting this bug! + """ + x,y,w,h = bbox_tlwh + x1 = max(int(x),0) + x2 = min(int(x+w),self.width-1) + y1 = max(int(y),0) + y2 = min(int(y+h),self.height-1) + return x1,y1,x2,y2 + + def _xyxy_to_tlwh(self, bbox_xyxy): + x1,y1,x2,y2 = bbox_xyxy + + t = x1 + l = y1 + w = int(x2-x1) + h = int(y2-y1) + return t,l,w,h + + def _get_features(self, bbox_xywh, ori_img): + im_crops = [] + for box in bbox_xywh: + x1,y1,x2,y2 = self._xywh_to_xyxy(box) + im = ori_img[y1:y2,x1:x2] + im_crops.append(im) + if im_crops: + features = self.extractor(im_crops) + else: + features = np.array([]) + return features + + diff --git a/deep_sort/sort/__init__.py b/deep_sort/sort/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deep_sort/sort/detection.py b/deep_sort/sort/detection.py new file mode 100644 index 0000000..87fc5fd --- /dev/null +++ b/deep_sort/sort/detection.py @@ -0,0 +1,49 @@ +# vim: expandtab:ts=4:sw=4 +import numpy as np + + +class Detection(object): + """ + This class represents a bounding box detection in a single image. + + Parameters + ---------- + tlwh : array_like + Bounding box in format `(x, y, w, h)`. + confidence : float + Detector confidence score. + feature : array_like + A feature vector that describes the object contained in this image. + + Attributes + ---------- + tlwh : ndarray + Bounding box in format `(top left x, top left y, width, height)`. + confidence : ndarray + Detector confidence score. + feature : ndarray | NoneType + A feature vector that describes the object contained in this image. + + """ + + def __init__(self, tlwh, confidence, feature): + self.tlwh = np.asarray(tlwh, dtype=np.float) + self.confidence = float(confidence) + self.feature = np.asarray(feature, dtype=np.float32) + + def to_tlbr(self): + """Convert bounding box to format `(min x, min y, max x, max y)`, i.e., + `(top left, bottom right)`. + """ + ret = self.tlwh.copy() + ret[2:] += ret[:2] + return ret + + def to_xyah(self): + """Convert bounding box to format `(center x, center y, aspect ratio, + height)`, where the aspect ratio is `width / height`. + """ + ret = self.tlwh.copy() + ret[:2] += ret[2:] / 2 + ret[2] /= ret[3] + return ret diff --git a/deep_sort/sort/iou_matching.py b/deep_sort/sort/iou_matching.py new file mode 100644 index 0000000..481e930 --- /dev/null +++ b/deep_sort/sort/iou_matching.py @@ -0,0 +1,81 @@ +# vim: expandtab:ts=4:sw=4 +from __future__ import absolute_import +import numpy as np +from . import linear_assignment + + +def iou(bbox, candidates): + """Computer intersection over union. + + Parameters + ---------- + bbox : ndarray + A bounding box in format `(top left x, top left y, width, height)`. + candidates : ndarray + A matrix of candidate bounding boxes (one per row) in the same format + as `bbox`. + + Returns + ------- + ndarray + The intersection over union in [0, 1] between the `bbox` and each + candidate. A higher score means a larger fraction of the `bbox` is + occluded by the candidate. + + """ + bbox_tl, bbox_br = bbox[:2], bbox[:2] + bbox[2:] + candidates_tl = candidates[:, :2] + candidates_br = candidates[:, :2] + candidates[:, 2:] + + tl = np.c_[np.maximum(bbox_tl[0], candidates_tl[:, 0])[:, np.newaxis], + np.maximum(bbox_tl[1], candidates_tl[:, 1])[:, np.newaxis]] + br = np.c_[np.minimum(bbox_br[0], candidates_br[:, 0])[:, np.newaxis], + np.minimum(bbox_br[1], candidates_br[:, 1])[:, np.newaxis]] + wh = np.maximum(0., br - tl) + + area_intersection = wh.prod(axis=1) + area_bbox = bbox[2:].prod() + area_candidates = candidates[:, 2:].prod(axis=1) + return area_intersection / (area_bbox + area_candidates - area_intersection) + + +def iou_cost(tracks, detections, track_indices=None, + detection_indices=None): + """An intersection over union distance metric. + + Parameters + ---------- + tracks : List[deep_sort.track.Track] + A list of tracks. + detections : List[deep_sort.detection.Detection] + A list of detections. + track_indices : Optional[List[int]] + A list of indices to tracks that should be matched. Defaults to + all `tracks`. + detection_indices : Optional[List[int]] + A list of indices to detections that should be matched. Defaults + to all `detections`. + + Returns + ------- + ndarray + Returns a cost matrix of shape + len(track_indices), len(detection_indices) where entry (i, j) is + `1 - iou(tracks[track_indices[i]], detections[detection_indices[j]])`. + + """ + if track_indices is None: + track_indices = np.arange(len(tracks)) + if detection_indices is None: + detection_indices = np.arange(len(detections)) + + cost_matrix = np.zeros((len(track_indices), len(detection_indices))) + for row, track_idx in enumerate(track_indices): + if tracks[track_idx].time_since_update > 1: + cost_matrix[row, :] = linear_assignment.INFTY_COST + continue + + bbox = tracks[track_idx].to_tlwh() + candidates = np.asarray([detections[i].tlwh for i in detection_indices]) + cost_matrix[row, :] = 1. - iou(bbox, candidates) + return cost_matrix diff --git a/deep_sort/sort/kalman_filter.py b/deep_sort/sort/kalman_filter.py new file mode 100644 index 0000000..1e50a7c --- /dev/null +++ b/deep_sort/sort/kalman_filter.py @@ -0,0 +1,241 @@ +# vim: expandtab:ts=4:sw=4 +import numpy as np +import scipy.linalg + + +""" +Table for the 0.95 quantile of the chi-square distribution with N degrees of +freedom (contains values for N=1, ..., 9). Taken from MATLAB/Octave's chi2inv +function and used as Mahalanobis gating threshold. +""" +chi2inv95 = { + 1: 3.8415, + 2: 5.9915, + 3: 7.8147, + 4: 9.4877, + 5: 11.070, + 6: 12.592, + 7: 14.067, + 8: 15.507, + 9: 16.919} + + +class KalmanFilter(object): + """ + A simple Kalman filter for tracking bounding boxes in image space. + + The 8-dimensional state space + + x, y, a, h, vx, vy, va, vh + + contains the bounding box center position (x, y), aspect ratio a, height h, + and their respective velocities. + + Object motion follows a constant velocity model. The bounding box location + (x, y, a, h) is taken as direct observation of the state space (linear + observation model). + + """ + + def __init__(self): + ndim, dt = 4, 1. + + # Create Kalman filter model matrices. + self._motion_mat = np.eye(2 * ndim, 2 * ndim) + for i in range(ndim): + self._motion_mat[i, ndim + i] = dt + self._update_mat = np.eye(ndim, 2 * ndim) + + # Motion and observation uncertainty are chosen relative to the current + # state estimate. These weights control the amount of uncertainty in + # the model. This is a bit hacky. + self._std_weight_position = 1. / 20 + self._std_weight_velocity = 1. / 160 + + def initiate(self, measurement): + """Create track from unassociated measurement. + + Parameters + ---------- + measurement : ndarray + Bounding box coordinates (x, y, a, h) with center position (x, y), + aspect ratio a, and height h. + + Returns + ------- + (ndarray, ndarray) + Returns the mean vector (8 dimensional) and covariance matrix (8x8 + dimensional) of the new track. Unobserved velocities are initialized + to 0 mean. + + """ + mean_pos = measurement + mean_vel = np.zeros_like(mean_pos) + mean = np.r_[mean_pos, mean_vel] + + std = [ + 2 * self._std_weight_position * measurement[3], + 2 * self._std_weight_position * measurement[3], + 1e-2, + 2 * self._std_weight_position * measurement[3], + 10 * self._std_weight_velocity * measurement[3], + 10 * self._std_weight_velocity * measurement[3], + 1e-5, + 10 * self._std_weight_velocity * measurement[3]] + covariance = np.diag(np.square(std)) + return mean, covariance + + def predict(self, mean, covariance): + """Run Kalman filter prediction step. + + Parameters + ---------- + mean : ndarray + The 8 dimensional mean vector of the object state at the previous + time step. + covariance : ndarray + The 8x8 dimensional covariance matrix of the object state at the + previous time step. + + Returns + ------- + (ndarray, ndarray) + Returns the mean vector and covariance matrix of the predicted + state. Unobserved velocities are initialized to 0 mean. + + """ + std_pos = [ + self._std_weight_position * mean[3], + self._std_weight_position * mean[3], + 1e-2, + self._std_weight_position * mean[3]] + std_vel = [ + self._std_weight_velocity * mean[3], + self._std_weight_velocity * mean[3], + 1e-5, + self._std_weight_velocity * mean[3]] + # np.r_ 按列连接两个矩阵 + # 初始化噪声矩阵 Q 对角矩阵 + motion_cov = np.diag(np.square(np.r_[std_pos, std_vel])) + ## x' = Fx + mean = np.dot(self._motion_mat, mean) + # P' = FPF^T+Q + covariance = np.linalg.multi_dot(( + self._motion_mat, covariance, self._motion_mat.T)) + motion_cov + + return mean, covariance + + def project(self, mean, covariance): + """Project state distribution to measurement space. + + Parameters + ---------- + mean : ndarray + The state's mean vector (8 dimensional array). + covariance : ndarray + The state's covariance matrix (8x8 dimensional). + + Returns + ------- + (ndarray, ndarray) + Returns the projected mean and covariance matrix of the given state + estimate. + + """ + std = [ + self._std_weight_position * mean[3], + self._std_weight_position * mean[3], + 1e-1, + self._std_weight_position * mean[3]] + innovation_cov = np.diag(np.square(std)) + # 将均值向量映射到检测空间,即 Hx' + mean = np.dot(self._update_mat, mean) + covariance = np.linalg.multi_dot(( + self._update_mat, covariance, self._update_mat.T)) + return mean, covariance + innovation_cov + + def update(self, mean, covariance, measurement): + """Run Kalman filter correction step. + + Parameters + ---------- + mean : ndarray + The predicted state's mean vector (8 dimensional). + covariance : ndarray + The state's covariance matrix (8x8 dimensional). + measurement : ndarray + The 4 dimensional measurement vector (x, y, a, h), where (x, y) + is the center position, a the aspect ratio, and h the height of the + bounding box. + + Returns + ------- + (ndarray, ndarray) + Returns the measurement-corrected state distribution. + + """ + #y = z − Hx ′ + #S = HP ′ H T + R + #K = P ′ H T S −1 + #x = x ′ + Ky + #P = (I − KH)P ′ + # 将均值和协方差映射到检测空间,得到 Hx' 和 S + projected_mean, projected_cov = self.project(mean, covariance) + # 矩阵分解 + chol_factor, lower = scipy.linalg.cho_factor( + projected_cov, lower=True, check_finite=False) + # 计算卡尔曼增益 K + kalman_gain = scipy.linalg.cho_solve( + (chol_factor, lower), np.dot(covariance, self._update_mat.T).T, + check_finite=False).T + # z - Hx' + innovation = measurement - projected_mean + #x = x ′ + Ky + new_mean = mean + np.dot(innovation, kalman_gain.T) + #P = (I − KH)P ′ ???? + new_covariance = covariance - np.linalg.multi_dot(( + kalman_gain, projected_cov, kalman_gain.T)) + return new_mean, new_covariance + + def gating_distance(self, mean, covariance, measurements, + only_position=False): + """Compute gating distance between state distribution and measurements. + + A suitable distance threshold can be obtained from `chi2inv95`. If + `only_position` is False, the chi-square distribution has 4 degrees of + freedom, otherwise 2. + + Parameters + ---------- + mean : ndarray + Mean vector over the state distribution (8 dimensional). + covariance : ndarray + Covariance of the state distribution (8x8 dimensional). + measurements : ndarray + An Nx4 dimensional matrix of N measurements, each in + format (x, y, a, h) where (x, y) is the bounding box center + position, a the aspect ratio, and h the height. + only_position : Optional[bool] + If True, distance computation is done with respect to the bounding + box center position only. + + Returns + ------- + ndarray + Returns an array of length N, where the i-th element contains the + squared Mahalanobis distance between (mean, covariance) and + `measurements[i]`. + + """ + mean, covariance = self.project(mean, covariance) + if only_position: + mean, covariance = mean[:2], covariance[:2, :2] + measurements = measurements[:, :2] + + cholesky_factor = np.linalg.cholesky(covariance) + d = measurements - mean + z = scipy.linalg.solve_triangular( + cholesky_factor, d.T, lower=True, check_finite=False, + overwrite_b=True) + squared_maha = np.sum(z * z, axis=0) + return squared_maha diff --git a/deep_sort/sort/linear_assignment.py b/deep_sort/sort/linear_assignment.py new file mode 100644 index 0000000..f1238dc --- /dev/null +++ b/deep_sort/sort/linear_assignment.py @@ -0,0 +1,193 @@ +# vim: expandtab:ts=4:sw=4 +from __future__ import absolute_import +import numpy as np +# from sklearn.utils.linear_assignment_ import linear_assignment +from scipy.optimize import linear_sum_assignment as linear_assignment +from . import kalman_filter + + +INFTY_COST = 1e+5 + + +def min_cost_matching( + distance_metric, max_distance, tracks, detections, track_indices=None, + detection_indices=None): + """Solve linear assignment problem. + + Parameters + ---------- + distance_metric : Callable[List[Track], List[Detection], List[int], List[int]) -> ndarray + The distance metric is given a list of tracks and detections as well as + a list of N track indices and M detection indices. The metric should + return the NxM dimensional cost matrix, where element (i, j) is the + association cost between the i-th track in the given track indices and + the j-th detection in the given detection_indices. + max_distance : float + Gating threshold. Associations with cost larger than this value are + disregarded. + tracks : List[track.Track] + A list of predicted tracks at the current time step. + detections : List[detection.Detection] + A list of detections at the current time step. + track_indices : List[int] + List of track indices that maps rows in `cost_matrix` to tracks in + `tracks` (see description above). + detection_indices : List[int] + List of detection indices that maps columns in `cost_matrix` to + detections in `detections` (see description above). + + Returns + ------- + (List[(int, int)], List[int], List[int]) + Returns a tuple with the following three entries: + * A list of matched track and detection indices. + * A list of unmatched track indices. + * A list of unmatched detection indices. + + """ + if track_indices is None: + track_indices = np.arange(len(tracks)) + if detection_indices is None: + detection_indices = np.arange(len(detections)) + + if len(detection_indices) == 0 or len(track_indices) == 0: + return [], track_indices, detection_indices # Nothing to match. + + cost_matrix = distance_metric( + tracks, detections, track_indices, detection_indices) + cost_matrix[cost_matrix > max_distance] = max_distance + 1e-5 + + ##匈牙利或者KM匹配 + row_indices, col_indices = linear_assignment(cost_matrix) + + matches, unmatched_tracks, unmatched_detections = [], [], [] + for col, detection_idx in enumerate(detection_indices): + if col not in col_indices: + unmatched_detections.append(detection_idx) + for row, track_idx in enumerate(track_indices): + if row not in row_indices: + unmatched_tracks.append(track_idx) + for row, col in zip(row_indices, col_indices): + track_idx = track_indices[row] + detection_idx = detection_indices[col] + if cost_matrix[row, col] > max_distance: + unmatched_tracks.append(track_idx) + unmatched_detections.append(detection_idx) + else: + matches.append((track_idx, detection_idx)) + return matches, unmatched_tracks, unmatched_detections + + +def matching_cascade( + distance_metric, max_distance, cascade_depth, tracks, detections, + track_indices=None, detection_indices=None): + """Run matching cascade. + + Parameters + ---------- + distance_metric : Callable[List[Track], List[Detection], List[int], List[int]) -> ndarray + The distance metric is given a list of tracks and detections as well as + a list of N track indices and M detection indices. The metric should + return the NxM dimensional cost matrix, where element (i, j) is the + association cost between the i-th track in the given track indices and + the j-th detection in the given detection indices. + max_distance : float + Gating threshold. Associations with cost larger than this value are + disregarded. + cascade_depth: int + The cascade depth, should be se to the maximum track age. + tracks : List[track.Track] + A list of predicted tracks at the current time step. + detections : List[detection.Detection] + A list of detections at the current time step. + track_indices : Optional[List[int]] + List of track indices that maps rows in `cost_matrix` to tracks in + `tracks` (see description above). Defaults to all tracks. + detection_indices : Optional[List[int]] + List of detection indices that maps columns in `cost_matrix` to + detections in `detections` (see description above). Defaults to all + detections. + + Returns + ------- + (List[(int, int)], List[int], List[int]) + Returns a tuple with the following three entries: + * A list of matched track and detection indices. + * A list of unmatched track indices. + * A list of unmatched detection indices. + + """ + if track_indices is None: + track_indices = list(range(len(tracks))) + if detection_indices is None: + detection_indices = list(range(len(detections))) + + unmatched_detections = detection_indices + matches = [] + for level in range(cascade_depth): + if len(unmatched_detections) == 0: # No detections left + break + + track_indices_l = [ + k for k in track_indices + if tracks[k].time_since_update == 1 + level #按照匹配先后顺序进行匹配,首先是level=0,因为每次update time_since_update归0 + ] + if len(track_indices_l) == 0: # Nothing to match at this level 不同level 保留的track_indices_l不同 + continue + + matches_l, _, unmatched_detections = \ + min_cost_matching( + distance_metric, max_distance, tracks, detections, + track_indices_l, unmatched_detections) + matches += matches_l + unmatched_tracks = list(set(track_indices) - set(k for k, _ in matches)) + return matches, unmatched_tracks, unmatched_detections + + +def gate_cost_matrix( + kf, cost_matrix, tracks, detections, track_indices, detection_indices, + gated_cost=INFTY_COST, only_position=False): + """Invalidate infeasible entries in cost matrix based on the state + distributions obtained by Kalman filtering. + + Parameters + ---------- + kf : The Kalman filter. + cost_matrix : ndarray + The NxM dimensional cost matrix, where N is the number of track indices + and M is the number of detection indices, such that entry (i, j) is the + association cost between `tracks[track_indices[i]]` and + `detections[detection_indices[j]]`. + tracks : List[track.Track] + A list of predicted tracks at the current time step. + detections : List[detection.Detection] + A list of detections at the current time step. + track_indices : List[int] + List of track indices that maps rows in `cost_matrix` to tracks in + `tracks` (see description above). + detection_indices : List[int] + List of detection indices that maps columns in `cost_matrix` to + detections in `detections` (see description above). + gated_cost : Optional[float] + Entries in the cost matrix corresponding to infeasible associations are + set this value. Defaults to a very large value. + only_position : Optional[bool] + If True, only the x, y position of the state distribution is considered + during gating. Defaults to False. + + Returns + ------- + ndarray + Returns the modified cost matrix. + + """ + gating_dim = 2 if only_position else 4 + gating_threshold = kalman_filter.chi2inv95[gating_dim] + measurements = np.asarray( + [detections[i].to_xyah() for i in detection_indices]) + for row, track_idx in enumerate(track_indices): + track = tracks[track_idx] + gating_distance = kf.gating_distance( + track.mean, track.covariance, measurements, only_position) + cost_matrix[row, gating_distance > gating_threshold] = gated_cost + return cost_matrix diff --git a/deep_sort/sort/nn_matching.py b/deep_sort/sort/nn_matching.py new file mode 100644 index 0000000..6c7d68c --- /dev/null +++ b/deep_sort/sort/nn_matching.py @@ -0,0 +1,178 @@ +# vim: expandtab:ts=4:sw=4 +import numpy as np + + +def _pdist(a, b): + """Compute pair-wise squared distance between points in `a` and `b`. + + Parameters + ---------- + a : array_like + An NxM matrix of N samples of dimensionality M. + b : array_like + An LxM matrix of L samples of dimensionality M. + + Returns + ------- + ndarray + Returns a matrix of size len(a), len(b) such that eleement (i, j) + contains the squared distance between `a[i]` and `b[j]`. + + """ + a, b = np.asarray(a), np.asarray(b) + if len(a) == 0 or len(b) == 0: + return np.zeros((len(a), len(b))) + a2, b2 = np.square(a).sum(axis=1), np.square(b).sum(axis=1) + r2 = -2. * np.dot(a, b.T) + a2[:, None] + b2[None, :] + r2 = np.clip(r2, 0., float(np.inf)) + return r2 + + +def _cosine_distance(a, b, data_is_normalized=False): + """Compute pair-wise cosine distance between points in `a` and `b`. + + Parameters + ---------- + a : array_like + An NxM matrix of N samples of dimensionality M. + b : array_like + An LxM matrix of L samples of dimensionality M. + data_is_normalized : Optional[bool] + If True, assumes rows in a and b are unit length vectors. + Otherwise, a and b are explicitly normalized to lenght 1. + + Returns + ------- + ndarray + Returns a matrix of size len(a), len(b) such that eleement (i, j) + contains the squared distance between `a[i]` and `b[j]`. + + """ + if not data_is_normalized: + a = np.asarray(a) / np.linalg.norm(a, axis=1, keepdims=True) ##求取二范数 + b = np.asarray(b) / np.linalg.norm(b, axis=1, keepdims=True) + return 1. - np.dot(a, b.T) + + +def _nn_euclidean_distance(x, y): + """ Helper function for nearest neighbor distance metric (Euclidean). + + Parameters + ---------- + x : ndarray + A matrix of N row-vectors (sample points). + y : ndarray + A matrix of M row-vectors (query points). + + Returns + ------- + ndarray + A vector of length M that contains for each entry in `y` the + smallest Euclidean distance to a sample in `x`. + + """ + distances = _pdist(x, y) + return np.maximum(0.0, distances.min(axis=0)) + + +def _nn_cosine_distance(x, y): + """ Helper function for nearest neighbor distance metric (cosine). + + Parameters + ---------- + x : ndarray + A matrix of N row-vectors (sample points). + y : ndarray + A matrix of M row-vectors (query points). + + Returns + ------- + ndarray + A vector of length M that contains for each entry in `y` the + smallest cosine distance to a sample in `x`. + + """ + distances = _cosine_distance(x, y) + #smallest cosine distance to a sample in `x` [1 , ...] + return distances.min(axis=0) + + +class NearestNeighborDistanceMetric(object): + """ + A nearest neighbor distance metric that, for each target, returns + the closest distance to any sample that has been observed so far. + + Parameters + ---------- + metric : str + Either "euclidean" or "cosine". + matching_threshold: float + The matching threshold. Samples with larger distance are considered an + invalid match. + budget : Optional[int] + If not None, fix samples per class to at most this number. Removes + the oldest samples when the budget is reached. + + Attributes + ---------- + samples : Dict[int -> List[ndarray]] + A dictionary that maps from target identities to the list of samples + that have been observed so far. + + """ + + def __init__(self, metric, matching_threshold, budget=None): + + + if metric == "euclidean": + self._metric = _nn_euclidean_distance + elif metric == "cosine": + self._metric = _nn_cosine_distance + else: + raise ValueError( + "Invalid metric; must be either 'euclidean' or 'cosine'") + self.matching_threshold = matching_threshold + self.budget = budget + self.samples = {} + + def partial_fit(self, features, targets, active_targets): + """Update the distance metric with new data. + + Parameters + ---------- + features : ndarray + An NxM matrix of N features of dimensionality M. + targets : ndarray + An integer array of associated target identities. + active_targets : List[int] + A list of targets that are currently present in the scene. + + """ + for feature, target in zip(features, targets): + self.samples.setdefault(target, []).append(feature) + if self.budget is not None: + self.samples[target] = self.samples[target][-self.budget:] # 取前100个features + self.samples = {k: self.samples[k] for k in active_targets} + + def distance(self, features, targets): + """Compute distance between features and targets. + + Parameters + ---------- + features : ndarray + An NxM matrix of N features of dimensionality M. + targets : List[int]gated_metric + A list of targets to match the given `features` against. + + Returns + ------- + ndarray + Returns a cost matrix of shape len(targets), len(features), where + element (i, j) contains the closest squared distance between + `targets[i]` and `features[j]`. + + """ + cost_matrix = np.zeros((len(targets), len(features))) + for i, target in enumerate(targets): + cost_matrix[i, :] = self._metric(self.samples[target], features) + return cost_matrix diff --git a/deep_sort/sort/preprocessing.py b/deep_sort/sort/preprocessing.py new file mode 100644 index 0000000..de31eea --- /dev/null +++ b/deep_sort/sort/preprocessing.py @@ -0,0 +1,73 @@ +# vim: expandtab:ts=4:sw=4 +import numpy as np +import cv2 + + +def non_max_suppression(boxes, max_bbox_overlap, scores=None): + """Suppress overlapping detections. + + Original code from [1]_ has been adapted to include confidence score. + + .. [1] http://www.pyimagesearch.com/2015/02/16/ + faster-non-maximum-suppression-python/ + + Examples + -------- + + >>> boxes = [d.roi for d in detections] + >>> scores = [d.confidence for d in detections] + >>> indices = non_max_suppression(boxes, max_bbox_overlap, scores) + >>> detections = [detections[i] for i in indices] + + Parameters + ---------- + boxes : ndarray + Array of ROIs (x, y, width, height). + max_bbox_overlap : float + ROIs that overlap more than this values are suppressed. + scores : Optional[array_like] + Detector confidence score. + + Returns + ------- + List[int] + Returns indices of detections that have survived non-maxima suppression. + + """ + if len(boxes) == 0: + return [] + + boxes = boxes.astype(np.float) + pick = [] + + x1 = boxes[:, 0] + y1 = boxes[:, 1] + x2 = boxes[:, 2] + boxes[:, 0] + y2 = boxes[:, 3] + boxes[:, 1] + + area = (x2 - x1 + 1) * (y2 - y1 + 1) + if scores is not None: + idxs = np.argsort(scores) + else: + idxs = np.argsort(y2) + + while len(idxs) > 0: + last = len(idxs) - 1 + i = idxs[last] + pick.append(i) + + xx1 = np.maximum(x1[i], x1[idxs[:last]]) + yy1 = np.maximum(y1[i], y1[idxs[:last]]) + xx2 = np.minimum(x2[i], x2[idxs[:last]]) + yy2 = np.minimum(y2[i], y2[idxs[:last]]) + + w = np.maximum(0, xx2 - xx1 + 1) + h = np.maximum(0, yy2 - yy1 + 1) + + overlap = (w * h) / area[idxs[:last]] + + idxs = np.delete( + idxs, np.concatenate( + ([last], np.where(overlap > max_bbox_overlap)[0]))) + + return pick diff --git a/deep_sort/sort/track.py b/deep_sort/sort/track.py new file mode 100644 index 0000000..550f9ca --- /dev/null +++ b/deep_sort/sort/track.py @@ -0,0 +1,172 @@ +# vim: expandtab:ts=4:sw=4 + + +class TrackState: + """ + Enumeration type for the single target track state. Newly created tracks are + classified as `tentative` until enough evidence has been collected. Then, + the track state is changed to `confirmed`. Tracks that are no longer alive + are classified as `deleted` to mark them for removal from the set of active + tracks. + + """ + + Tentative = 1 + Confirmed = 2 + Deleted = 3 + + +class Track: + """ + A single target track with state space `(x, y, a, h)` and associated + velocities, where `(x, y)` is the center of the bounding box, `a` is the + aspect ratio and `h` is the height. + + Parameters + ---------- + mean : ndarray + Mean vector of the initial state distribution. + covariance : ndarray + Covariance matrix of the initial state distribution. + track_id : int + A unique track identifier. + n_init : int + Number of consecutive detections before the track is confirmed. The + track state is set to `Deleted` if a miss occurs within the first + `n_init` frames. + max_age : int + The maximum number of consecutive misses before the track state is + set to `Deleted`. + feature : Optional[ndarray] + Feature vector of the detection this track originates from. If not None, + this feature is added to the `features` cache. + + Attributes + ---------- + mean : ndarray + Mean vector of the initial state distribution. + covariance : ndarray + Covariance matrix of the initial state distribution. + track_id : int + A unique track identifier. + hits : int + Total number of measurement updates. + age : int + Total number of frames since first occurance. + time_since_update : int + Total number of frames since last measurement update. + state : TrackState + The current track state. + features : List[ndarray] + A cache of features. On each measurement update, the associated feature + vector is added to this list. + + """ + + def __init__(self, mean, covariance, track_id, n_init, max_age, + feature=None): + self.mean = mean + self.covariance = covariance + self.track_id = track_id + # hits 和 n_init 进行比较 + # hits 每次 update 的时候进行一次更新(只有 match 的时候才进行 update ) + # hits 代表匹配上了多少次,匹配次数超过 n_init 就会设置为 confirmed 状态 + self.hits = 1 + self.age = 1 # 没有用到,和 time_since_update 功能重复 + # 每次调用 predict 函数的时候就会 +1 + # 每次调用 update 函数的时候就会设置为 0 + self.time_since_update = 0 + + self.state = TrackState.Tentative ##Newly created tracks areclassified as `tentative` until enough evidence has been collected. + # 每个 track 对应多个 features, 每次更新都将最新的 feature 添加到列表中 + self.features = [] + if feature is not None: + self.features.append(feature) + + self._n_init = n_init + self._max_age = max_age + + def to_tlwh(self): + """Get current position in bounding box format `(top left x, top left y, + width, height)`. + + Returns + ------- + ndarray + The bounding box. + + """ + ret = self.mean[:4].copy() + ret[2] *= ret[3] + ret[:2] -= ret[2:] / 2 + return ret + + def to_tlbr(self): + """Get current position in bounding box format `(min x, miny, max x, + max y)`. + + Returns + ------- + ndarray + The bounding box. + + """ + ret = self.to_tlwh() + ret[2:] = ret[:2] + ret[2:] + return ret + + def predict(self, kf): + """Propagate the state distribution to the current time step using a + Kalman filter prediction step. + + Parameters + ---------- + kf : kalman_filter.KalmanFilter + The Kalman filter. + + """ + self.mean, self.covariance = kf.predict(self.mean, self.covariance) + self.age += 1 + self.time_since_update += 1 + + def update(self, kf, detection): + """Perform Kalman filter measurement update step and update the feature + cache. + + Parameters + ---------- + kf : kalman_filter.KalmanFilter + The Kalman filter. + detection : Detection + The associated detection. + + """ + self.mean, self.covariance = kf.update( + self.mean, self.covariance, detection.to_xyah()) + self.features.append(detection.feature) + + self.hits += 1 + self.time_since_update = 0 + if self.state == TrackState.Tentative and self.hits >= self._n_init: + self.state = TrackState.Confirmed + + def mark_missed(self): + """Mark this track as missed (no association at the current time step). + """ + if self.state == TrackState.Tentative: ##如果track 序列 没有被detction匹配过,则删除,这种概率很少! + self.state = TrackState.Deleted + elif self.time_since_update > self._max_age: ##如果track 序列 以前被detction匹配过,但时间过长没有被匹配,则删除 + self.state = TrackState.Deleted + + def is_tentative(self): + """Returns True if this track is tentative (unconfirmed). + """ + return self.state == TrackState.Tentative + + def is_confirmed(self): + """Returns True if this track is confirmed.""" + return self.state == TrackState.Confirmed + + def is_deleted(self): + """Returns True if this track is dead and should be deleted.""" + return self.state == TrackState.Deleted diff --git a/deep_sort/sort/tracker.py b/deep_sort/sort/tracker.py new file mode 100644 index 0000000..6eb0a82 --- /dev/null +++ b/deep_sort/sort/tracker.py @@ -0,0 +1,141 @@ +# vim: expandtab:ts=4:sw=4 +from __future__ import absolute_import +import numpy as np +from . import kalman_filter +from . import linear_assignment +from . import iou_matching +from .track import Track + + +class Tracker: + """ + This is the multi-target tracker. + + Parameters + ---------- + metric : nn_matching.NearestNeighborDistanceMetric + A distance metric for measurement-to-track association. + max_age : int + Maximum number of missed misses before a track is deleted. + n_init : int + Number of consecutive detections before the track is confirmed. The + track state is set to `Deleted` if a miss occurs within the first + `n_init` frames. + + Attributes + ---------- + metric : nn_matching.NearestNeighborDistanceMetric + The distance metric used for measurement to track association. + max_age : int + Maximum number of missed misses before a track is deleted. + n_init : int + Number of frames that a track remains in initialization phase. + kf : kalman_filter.KalmanFilter + A Kalman filter to filter target trajectories in image space. + tracks : List[Track] + The list of active tracks at the current time step. + + """ + + def __init__(self, metric, max_iou_distance=0.7, max_age=70, n_init=3): + # metric 是一个类,用于计算距离 ( 余弦距离或马氏距离 ) + self.metric = metric + self.max_iou_distance = max_iou_distance + self.max_age = max_age + self.n_init = n_init + + self.kf = kalman_filter.KalmanFilter() + self.tracks = [] + self._next_id = 1 + + def predict(self): + """Propagate track state distributions one time step forward. + + This function should be called once every time step, before `update`. + """ + for track in self.tracks: + track.predict(self.kf) + + def update(self, detections): + """Perform measurement update and track management. + + Parameters + ---------- + detections : List[deep_sort.detection.Detection] + A list of detections at the current time step. + + """ + # Run matching cascade. + matches, unmatched_tracks, unmatched_detections = \ + self._match(detections) + + # Update track set. + for track_idx, detection_idx in matches: + self.tracks[track_idx].update( + self.kf, detections[detection_idx]) + for track_idx in unmatched_tracks: + self.tracks[track_idx].mark_missed() + for detection_idx in unmatched_detections: + self._initiate_track(detections[detection_idx]) + self.tracks = [t for t in self.tracks if not t.is_deleted()] + + # Update distance metric. + active_targets = [t.track_id for t in self.tracks if t.is_confirmed()] + features, targets = [], [] + for track in self.tracks: + if not track.is_confirmed(): + continue + features += track.features + targets += [track.track_id for _ in track.features] + track.features = [] ## 先将track.features置空,下次update时候,feature可以进来 + self.metric.partial_fit( + np.asarray(features), np.asarray(targets), active_targets) + + def _match(self, detections): + + def gated_metric(tracks, dets, track_indices, detection_indices): + features = np.array([dets[i].feature for i in detection_indices]) + targets = np.array([tracks[i].track_id for i in track_indices]) + # 1. 通过最近邻计算出代价矩阵 cosine distance 每个框的embading进行cost_matrix计算,外观状态 + cost_matrix = self.metric.distance(features, targets) + # 2. 计算马氏距离 , 得到新的状态矩阵,运动状态 + cost_matrix = linear_assignment.gate_cost_matrix( + self.kf, cost_matrix, tracks, dets, track_indices, + detection_indices) + + return cost_matrix + + # Split track set into confirmed and unconfirmed tracks. + confirmed_tracks = [ + i for i, t in enumerate(self.tracks) if t.is_confirmed()] + unconfirmed_tracks = [ + i for i, t in enumerate(self.tracks) if not t.is_confirmed()] + + # Associate confirmed tracks using appearance features. 这里的gated_metric不带括号直接引用函数本身,在min里面实现 + matches_a, unmatched_tracks_a, unmatched_detections = \ + linear_assignment.matching_cascade( + gated_metric, self.metric.matching_threshold, self.max_age, + self.tracks, detections, confirmed_tracks) + + # Associate remaining tracks together with unconfirmed tracks using IOU. + iou_track_candidates = unconfirmed_tracks + [ + k for k in unmatched_tracks_a if + self.tracks[k].time_since_update == 1] + unmatched_tracks_a = [ + k for k in unmatched_tracks_a if + self.tracks[k].time_since_update != 1] + matches_b, unmatched_tracks_b, unmatched_detections = \ + linear_assignment.min_cost_matching( + iou_matching.iou_cost, self.max_iou_distance, self.tracks, + detections, iou_track_candidates, unmatched_detections) + + matches = matches_a + matches_b + unmatched_tracks = list(set(unmatched_tracks_a + unmatched_tracks_b)) + return matches, unmatched_tracks, unmatched_detections + + def _initiate_track(self, detection): + mean, covariance = self.kf.initiate(detection.to_xyah()) + self.tracks.append(Track( + mean, covariance, self._next_id, self.n_init, self.max_age, + detection.feature)) + self._next_id += 1 diff --git a/detector/YOLOv3/README.md b/detector/YOLOv3/README.md new file mode 100644 index 0000000..ef8e168 --- /dev/null +++ b/detector/YOLOv3/README.md @@ -0,0 +1,11 @@ +# YOLOv3 for detection + +This is an implemention of YOLOv3 with only the forward part. + +If you want to train YOLOv3 on your custom dataset, please search `YOLOv3` on github. + +## Quick forward +```bash +cd YOLOv3 +python +``` \ No newline at end of file diff --git a/detector/YOLOv3/__init__.py b/detector/YOLOv3/__init__.py new file mode 100644 index 0000000..fff6a61 --- /dev/null +++ b/detector/YOLOv3/__init__.py @@ -0,0 +1,9 @@ +import sys +sys.path.append("detector/YOLOv3") + + +from .detector import YOLOv3 +__all__ = ['YOLOv3'] + + + diff --git a/detector/YOLOv3/cfg.py b/detector/YOLOv3/cfg.py new file mode 100644 index 0000000..9b2a0e7 --- /dev/null +++ b/detector/YOLOv3/cfg.py @@ -0,0 +1,248 @@ +import torch +from .yolo_utils import convert2cpu + + +def parse_cfg(cfgfile): + blocks = [] + fp = open(cfgfile) + block = None + line = fp.readline() + while line != '': + line = line.rstrip() + if line == '' or line[0] == '#': + line = fp.readline() + continue + elif line[0] == '[': + if block: + blocks.append(block) + block = dict() + block['type'] = line.lstrip('[').rstrip(']') + # set default value + if block['type'] == 'convolutional': + block['batch_normalize'] = 0 + else: + key, value = line.split('=') + key = key.strip() + if key == 'type': + key = '_type' + value = value.strip() + block[key] = value + line = fp.readline() + + if block: + blocks.append(block) + fp.close() + return blocks + + +def print_cfg(blocks): + print('layer filters size input output') + prev_width = 416 + prev_height = 416 + prev_filters = 3 + out_filters = [] + out_widths = [] + out_heights = [] + ind = -2 + for block in blocks: + ind += 1 + if block['type'] == 'net': + prev_width = int(block['width']) + prev_height = int(block['height']) + continue + elif block['type'] == 'convolutional': + filters = int(block['filters']) + kernel_size = int(block['size']) + stride = int(block['stride']) + is_pad = int(block['pad']) + pad = (kernel_size - 1) // 2 if is_pad else 0 + width = (prev_width + 2 * pad - kernel_size) // stride + 1 + height = (prev_height + 2 * pad - kernel_size) // stride + 1 + print('%5d %-6s %4d %d x %d / %d %3d x %3d x%4d -> %3d x %3d x%4d' % ( + ind, 'conv', filters, kernel_size, kernel_size, stride, prev_width, prev_height, prev_filters, width, + height, filters)) + prev_width = width + prev_height = height + prev_filters = filters + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'maxpool': + pool_size = int(block['size']) + stride = int(block['stride']) + width = prev_width // stride + height = prev_height // stride + print('%5d %-6s %d x %d / %d %3d x %3d x%4d -> %3d x %3d x%4d' % ( + ind, 'max', pool_size, pool_size, stride, prev_width, prev_height, prev_filters, width, height, filters)) + prev_width = width + prev_height = height + prev_filters = filters + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'avgpool': + width = 1 + height = 1 + print('%5d %-6s %3d x %3d x%4d -> %3d' % ( + ind, 'avg', prev_width, prev_height, prev_filters, prev_filters)) + prev_width = width + prev_height = height + prev_filters = filters + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'softmax': + print('%5d %-6s -> %3d' % (ind, 'softmax', prev_filters)) + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'cost': + print('%5d %-6s -> %3d' % (ind, 'cost', prev_filters)) + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'reorg': + stride = int(block['stride']) + filters = stride * stride * prev_filters + width = prev_width // stride + height = prev_height // stride + print('%5d %-6s / %d %3d x %3d x%4d -> %3d x %3d x%4d' % ( + ind, 'reorg', stride, prev_width, prev_height, prev_filters, width, height, filters)) + prev_width = width + prev_height = height + prev_filters = filters + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'upsample': + stride = int(block['stride']) + filters = prev_filters + width = prev_width * stride + height = prev_height * stride + print('%5d %-6s * %d %3d x %3d x%4d -> %3d x %3d x%4d' % ( + ind, 'upsample', stride, prev_width, prev_height, prev_filters, width, height, filters)) + prev_width = width + prev_height = height + prev_filters = filters + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'route': + layers = block['layers'].split(',') + layers = [int(i) if int(i) > 0 else int(i) + ind for i in layers] + if len(layers) == 1: + print('%5d %-6s %d' % (ind, 'route', layers[0])) + prev_width = out_widths[layers[0]] + prev_height = out_heights[layers[0]] + prev_filters = out_filters[layers[0]] + elif len(layers) == 2: + print('%5d %-6s %d %d' % (ind, 'route', layers[0], layers[1])) + prev_width = out_widths[layers[0]] + prev_height = out_heights[layers[0]] + assert (prev_width == out_widths[layers[1]]) + assert (prev_height == out_heights[layers[1]]) + prev_filters = out_filters[layers[0]] + out_filters[layers[1]] + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] in ['region', 'yolo']: + print('%5d %-6s' % (ind, 'detection')) + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'shortcut': + from_id = int(block['from']) + from_id = from_id if from_id > 0 else from_id + ind + print('%5d %-6s %d' % (ind, 'shortcut', from_id)) + prev_width = out_widths[from_id] + prev_height = out_heights[from_id] + prev_filters = out_filters[from_id] + out_widths.append(prev_width) + out_heights.append(prev_height) + out_filters.append(prev_filters) + elif block['type'] == 'connected': + filters = int(block['output']) + print('%5d %-6s %d -> %3d' % (ind, 'connected', prev_filters, filters)) + prev_filters = filters + out_widths.append(1) + out_heights.append(1) + out_filters.append(prev_filters) + else: + print('unknown type %s' % (block['type'])) + + +def load_conv(buf, start, conv_model): + num_w = conv_model.weight.numel() + num_b = conv_model.bias.numel() + # print("start: {}, num_w: {}, num_b: {}".format(start, num_w, num_b)) + # by ysyun, use .view_as() + conv_model.bias.data.copy_(torch.from_numpy(buf[start:start + num_b]).view_as(conv_model.bias.data)); + start = start + num_b + conv_model.weight.data.copy_(torch.from_numpy(buf[start:start + num_w]).view_as(conv_model.weight.data)); + start = start + num_w + return start + + +def save_conv(fp, conv_model): + if conv_model.bias.is_cuda: + convert2cpu(conv_model.bias.data).numpy().tofile(fp) + convert2cpu(conv_model.weight.data).numpy().tofile(fp) + else: + conv_model.bias.data.numpy().tofile(fp) + conv_model.weight.data.numpy().tofile(fp) + + +def load_conv_bn(buf, start, conv_model, bn_model): + num_w = conv_model.weight.numel() + num_b = bn_model.bias.numel() + bn_model.bias.data.copy_(torch.from_numpy(buf[start:start + num_b])); + start = start + num_b + bn_model.weight.data.copy_(torch.from_numpy(buf[start:start + num_b])); + start = start + num_b + bn_model.running_mean.copy_(torch.from_numpy(buf[start:start + num_b])); + start = start + num_b + bn_model.running_var.copy_(torch.from_numpy(buf[start:start + num_b])); + start = start + num_b + # conv_model.weight.data.copy_(torch.from_numpy(buf[start:start+num_w])); start = start + num_w + conv_model.weight.data.copy_(torch.from_numpy(buf[start:start + num_w]).view_as(conv_model.weight.data)); + start = start + num_w + return start + + +def save_conv_bn(fp, conv_model, bn_model): + if bn_model.bias.is_cuda: + convert2cpu(bn_model.bias.data).numpy().tofile(fp) + convert2cpu(bn_model.weight.data).numpy().tofile(fp) + convert2cpu(bn_model.running_mean).numpy().tofile(fp) + convert2cpu(bn_model.running_var).numpy().tofile(fp) + convert2cpu(conv_model.weight.data).numpy().tofile(fp) + else: + bn_model.bias.data.numpy().tofile(fp) + bn_model.weight.data.numpy().tofile(fp) + bn_model.running_mean.numpy().tofile(fp) + bn_model.running_var.numpy().tofile(fp) + conv_model.weight.data.numpy().tofile(fp) + + +def load_fc(buf, start, fc_model): + num_w = fc_model.weight.numel() + num_b = fc_model.bias.numel() + fc_model.bias.data.copy_(torch.from_numpy(buf[start:start + num_b])); + start = start + num_b + fc_model.weight.data.copy_(torch.from_numpy(buf[start:start + num_w])); + start = start + num_w + return start + + +def save_fc(fp, fc_model): + fc_model.bias.data.numpy().tofile(fp) + fc_model.weight.data.numpy().tofile(fp) + + +if __name__ == '__main__': + import sys + + blocks = parse_cfg('cfg/yolo.cfg') + if len(sys.argv) == 2: + blocks = parse_cfg(sys.argv[1]) + print_cfg(blocks) diff --git a/detector/YOLOv3/cfg/coco.data b/detector/YOLOv3/cfg/coco.data new file mode 100644 index 0000000..b7e31be --- /dev/null +++ b/detector/YOLOv3/cfg/coco.data @@ -0,0 +1,5 @@ +train = coco_train.txt +valid = coco_test.txt +names = data/coco.names +backup = backup +gpus = 0,1,2,3 diff --git a/detector/YOLOv3/cfg/coco.names b/detector/YOLOv3/cfg/coco.names new file mode 100644 index 0000000..ca76c80 --- /dev/null +++ b/detector/YOLOv3/cfg/coco.names @@ -0,0 +1,80 @@ +person +bicycle +car +motorbike +aeroplane +bus +train +truck +boat +traffic light +fire hydrant +stop sign +parking meter +bench +bird +cat +dog +horse +sheep +cow +elephant +bear +zebra +giraffe +backpack +umbrella +handbag +tie +suitcase +frisbee +skis +snowboard +sports ball +kite +baseball bat +baseball glove +skateboard +surfboard +tennis racket +bottle +wine glass +cup +fork +knife +spoon +bowl +banana +apple +sandwich +orange +broccoli +carrot +hot dog +pizza +donut +cake +chair +sofa +pottedplant +bed +diningtable +toilet +tvmonitor +laptop +mouse +remote +keyboard +cell phone +microwave +oven +toaster +sink +refrigerator +book +clock +vase +scissors +teddy bear +hair drier +toothbrush diff --git a/detector/YOLOv3/cfg/darknet19_448.cfg b/detector/YOLOv3/cfg/darknet19_448.cfg new file mode 100644 index 0000000..133c688 --- /dev/null +++ b/detector/YOLOv3/cfg/darknet19_448.cfg @@ -0,0 +1,200 @@ +[net] +batch=128 +subdivisions=4 +height=448 +width=448 +max_crop=512 +channels=3 +momentum=0.9 +decay=0.0005 + +learning_rate=0.001 +policy=poly +power=4 +max_batches=100000 + +angle=7 +hue = .1 +saturation=.75 +exposure=.75 +aspect=.75 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +filters=1000 +size=1 +stride=1 +pad=1 +activation=linear + +[avgpool] + +[softmax] +groups=1 + +[cost] +type=sse + diff --git a/detector/YOLOv3/cfg/tiny-yolo-voc.cfg b/detector/YOLOv3/cfg/tiny-yolo-voc.cfg new file mode 100644 index 0000000..ab2c066 --- /dev/null +++ b/detector/YOLOv3/cfg/tiny-yolo-voc.cfg @@ -0,0 +1,134 @@ +[net] +batch=64 +subdivisions=8 +width=416 +height=416 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.001 +max_batches = 40200 +policy=steps +steps=-1,100,20000,30000 +scales=.1,10,.1,.1 + +[convolutional] +batch_normalize=1 +filters=16 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=1 + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +########### + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=125 +activation=linear + +[region] +anchors = 1.08,1.19, 3.42,4.41, 6.63,11.38, 9.42,5.11, 16.62,10.52 +bias_match=1 +classes=20 +coords=4 +num=5 +softmax=1 +jitter=.2 +rescore=1 + +object_scale=5 +noobject_scale=1 +class_scale=1 +coord_scale=1 + +absolute=1 +thresh = .6 +random=1 diff --git a/detector/YOLOv3/cfg/tiny-yolo.cfg b/detector/YOLOv3/cfg/tiny-yolo.cfg new file mode 100644 index 0000000..ac5770e --- /dev/null +++ b/detector/YOLOv3/cfg/tiny-yolo.cfg @@ -0,0 +1,140 @@ +[net] +# Training +# batch=64 +# subdivisions=2 +# Testing +batch=1 +subdivisions=1 +width=416 +height=416 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.001 +burn_in=1000 +max_batches = 500200 +policy=steps +steps=400000,450000 +scales=.1,.1 + +[convolutional] +batch_normalize=1 +filters=16 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=1 + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +########### + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=425 +activation=linear + +[region] +anchors = 0.57273, 0.677385, 1.87446, 2.06253, 3.33843, 5.47434, 7.88282, 3.52778, 9.77052, 9.16828 +bias_match=1 +classes=80 +coords=4 +num=5 +softmax=1 +jitter=.2 +rescore=0 + +object_scale=5 +noobject_scale=1 +class_scale=1 +coord_scale=1 + +absolute=1 +thresh = .6 +random=1 + diff --git a/detector/YOLOv3/cfg/voc.data b/detector/YOLOv3/cfg/voc.data new file mode 100644 index 0000000..3329357 --- /dev/null +++ b/detector/YOLOv3/cfg/voc.data @@ -0,0 +1,5 @@ +train = data/voc_train.txt +valid = data/2007_test.txt +names = data/voc.names +backup = backup +gpus = 3 diff --git a/detector/YOLOv3/cfg/voc.names b/detector/YOLOv3/cfg/voc.names new file mode 100644 index 0000000..8420ab3 --- /dev/null +++ b/detector/YOLOv3/cfg/voc.names @@ -0,0 +1,20 @@ +aeroplane +bicycle +bird +boat +bottle +bus +car +cat +chair +cow +diningtable +dog +horse +motorbike +person +pottedplant +sheep +sofa +train +tvmonitor diff --git a/detector/YOLOv3/cfg/voc_gaotie.data b/detector/YOLOv3/cfg/voc_gaotie.data new file mode 100644 index 0000000..66495ec --- /dev/null +++ b/detector/YOLOv3/cfg/voc_gaotie.data @@ -0,0 +1,5 @@ +train = data/gaotie_trainval.txt +valid = data/gaotie_test.txt +names = data/voc.names +backup = backup +gpus = 3 \ No newline at end of file diff --git a/detector/YOLOv3/cfg/yolo-voc.cfg b/detector/YOLOv3/cfg/yolo-voc.cfg new file mode 100644 index 0000000..d5bdfc1 --- /dev/null +++ b/detector/YOLOv3/cfg/yolo-voc.cfg @@ -0,0 +1,258 @@ +[net] +# Testing +batch=64 +subdivisions=8 +# Training +# batch=64 +# subdivisions=8 +height=416 +width=416 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.001 +burn_in=1000 +max_batches = 80200 +policy=steps +steps=-1,500,40000,60000 +scales=0.1,10,.1,.1 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + + +####### + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[route] +layers=-9 + +[convolutional] +batch_normalize=1 +size=1 +stride=1 +pad=1 +filters=64 +activation=leaky + +[reorg] +stride=2 + +[route] +layers=-1,-4 + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=125 +activation=linear + + +[region] +anchors = 1.3221, 1.73145, 3.19275, 4.00944, 5.05587, 8.09892, 9.47112, 4.84053, 11.2364, 10.0071 +bias_match=1 +classes=20 +coords=4 +num=5 +softmax=1 +jitter=.3 +rescore=1 + +object_scale=5 +noobject_scale=1 +class_scale=1 +coord_scale=1 + +absolute=1 +thresh = .6 +random=1 diff --git a/detector/YOLOv3/cfg/yolo.cfg b/detector/YOLOv3/cfg/yolo.cfg new file mode 100644 index 0000000..2a0cd98 --- /dev/null +++ b/detector/YOLOv3/cfg/yolo.cfg @@ -0,0 +1,258 @@ +[net] +# Testing +batch=1 +subdivisions=1 +# Training +# batch=64 +# subdivisions=8 +width=416 +height=416 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.001 +burn_in=1000 +max_batches = 500200 +policy=steps +steps=400000,450000 +scales=.1,.1 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + + +####### + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[route] +layers=-9 + +[convolutional] +batch_normalize=1 +size=1 +stride=1 +pad=1 +filters=64 +activation=leaky + +[reorg] +stride=2 + +[route] +layers=-1,-4 + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=425 +activation=linear + + +[region] +anchors = 0.57273, 0.677385, 1.87446, 2.06253, 3.33843, 5.47434, 7.88282, 3.52778, 9.77052, 9.16828 +bias_match=1 +classes=80 +coords=4 +num=5 +softmax=1 +jitter=.3 +rescore=1 + +object_scale=5 +noobject_scale=1 +class_scale=1 +coord_scale=1 + +absolute=1 +thresh = .6 +random=1 diff --git a/detector/YOLOv3/cfg/yolo_v3.cfg b/detector/YOLOv3/cfg/yolo_v3.cfg new file mode 100644 index 0000000..f6a3d22 --- /dev/null +++ b/detector/YOLOv3/cfg/yolo_v3.cfg @@ -0,0 +1,789 @@ +[net] +# Testing +#batch=1 +#subdivisions=1 +# Training +batch=16 +subdivisions=4 +width=608 +height=608 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.001 +burn_in=1000 +max_batches = 500200 +policy=steps +steps=20,25 +scales=.1,.1 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +# Downsample + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=32 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +# Downsample + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +# Downsample + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +# Downsample + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +# Downsample + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +[shortcut] +from=-3 +activation=linear + +###################### + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 6,7,8 +anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .5 +truth_thresh = 1 +random=1 + + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = -1, 61 + + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 3,4,5 +anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .5 +truth_thresh = 1 +random=1 + + + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = -1, 36 + + + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 0,1,2 +anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .5 +truth_thresh = 1 +random=1 + diff --git a/detector/YOLOv3/cfg/yolov3-tiny.cfg b/detector/YOLOv3/cfg/yolov3-tiny.cfg new file mode 100644 index 0000000..cfca3cf --- /dev/null +++ b/detector/YOLOv3/cfg/yolov3-tiny.cfg @@ -0,0 +1,182 @@ +[net] +# Testing +batch=1 +subdivisions=1 +# Training +# batch=64 +# subdivisions=2 +width=416 +height=416 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.001 +burn_in=1000 +max_batches = 500200 +policy=steps +steps=400000,450000 +scales=.1,.1 + +[convolutional] +batch_normalize=1 +filters=16 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=1 + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +########### + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + + +[yolo] +mask = 3,4,5 +anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 +classes=80 +num=6 +jitter=.3 +ignore_thresh = .7 +truth_thresh = 1 +random=1 + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = -1, 8 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + +[yolo] +mask = 0,1,2 +anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 +classes=80 +num=6 +jitter=.3 +ignore_thresh = .7 +truth_thresh = 1 +random=1 diff --git a/detector/YOLOv3/cfg/yolov4-tiny.cfg b/detector/YOLOv3/cfg/yolov4-tiny.cfg new file mode 100644 index 0000000..dc6f5bf --- /dev/null +++ b/detector/YOLOv3/cfg/yolov4-tiny.cfg @@ -0,0 +1,281 @@ +[net] +# Testing +#batch=1 +#subdivisions=1 +# Training +batch=64 +subdivisions=1 +width=416 +height=416 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.00261 +burn_in=1000 +max_batches = 500200 +policy=steps +steps=400000,450000 +scales=.1,.1 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=2 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers=-1 +groups=2 +group_id=1 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -1,-2 + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -6,-1 + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers=-1 +groups=2 +group_id=1 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -1,-2 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -6,-1 + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers=-1 +groups=2 +group_id=1 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -1,-2 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -6,-1 + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +################################## + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + + +[yolo] +mask = 3,4,5 +anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 +classes=80 +num=6 +jitter=.3 +scale_x_y = 1.05 +cls_normalizer=1.0 +iou_normalizer=0.07 +iou_loss=ciou +ignore_thresh = .7 +truth_thresh = 1 +random=0 +resize=1.5 +nms_kind=greedynms +beta_nms=0.6 + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = -1, 23 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + +[yolo] +mask = 1,2,3 +anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 +classes=80 +num=6 +jitter=.3 +scale_x_y = 1.05 +cls_normalizer=1.0 +iou_normalizer=0.07 +iou_loss=ciou +ignore_thresh = .7 +truth_thresh = 1 +random=0 +resize=1.5 +nms_kind=greedynms +beta_nms=0.6 diff --git a/detector/YOLOv3/cfg/yolov4.cfg b/detector/YOLOv3/cfg/yolov4.cfg new file mode 100644 index 0000000..2a1d171 --- /dev/null +++ b/detector/YOLOv3/cfg/yolov4.cfg @@ -0,0 +1,1157 @@ +[net] +batch=64 +subdivisions=8 +# Training +#width=512 +#height=512 +width=416 +height=416 +channels=3 +momentum=0.949 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.0013 +burn_in=1000 +max_batches = 500500 +policy=steps +steps=400000,450000 +scales=.1,.1 + +#cutmix=1 +mosaic=1 + +#:104x104 54:52x52 85:26x26 104:13x13 for 416 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=mish + +# Downsample + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=2 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -2 + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=32 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -1,-7 + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +# Downsample + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=2 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -2 + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=64 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -1,-10 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +# Downsample + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=2 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -2 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -1,-28 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +# Downsample + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=2 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -2 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -1,-28 + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=mish + +# Downsample + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=2 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -2 + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=mish + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=mish + +[shortcut] +from=-3 +activation=linear + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=mish + +[route] +layers = -1,-16 + +[convolutional] +batch_normalize=1 +filters=1024 +size=1 +stride=1 +pad=1 +activation=mish + +########################## + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +### SPP ### +[maxpool] +stride=1 +size=5 + +[route] +layers=-2 + +[maxpool] +stride=1 +size=9 + +[route] +layers=-4 + +[maxpool] +stride=1 +size=13 + +[route] +layers=-1,-3,-5,-6 +### End SPP ### + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = 85 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -1, -3 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[upsample] +stride=2 + +[route] +layers = 54 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[route] +layers = -1, -3 + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=128 +size=1 +stride=1 +pad=1 +activation=leaky + +########################## + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=256 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 0,1,2 +anchors = 12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .7 +truth_thresh = 1 +scale_x_y = 1.2 +iou_thresh=0.213 +cls_normalizer=1.0 +iou_normalizer=0.07 +iou_loss=ciou +nms_kind=greedynms +beta_nms=0.6 +max_delta=5 + + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +size=3 +stride=2 +pad=1 +filters=256 +activation=leaky + +[route] +layers = -1, -16 + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=256 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 3,4,5 +anchors = 12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .7 +truth_thresh = 1 +scale_x_y = 1.1 +iou_thresh=0.213 +cls_normalizer=1.0 +iou_normalizer=0.07 +iou_loss=ciou +nms_kind=greedynms +beta_nms=0.6 +max_delta=5 + + +[route] +layers = -4 + +[convolutional] +batch_normalize=1 +size=3 +stride=2 +pad=1 +filters=512 +activation=leaky + +[route] +layers = -1, -37 + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +batch_normalize=1 +filters=512 +size=1 +stride=1 +pad=1 +activation=leaky + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=1024 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=255 +activation=linear + + +[yolo] +mask = 6,7,8 +anchors = 12, 16, 19, 36, 40, 28, 36, 75, 76, 55, 72, 146, 142, 110, 192, 243, 459, 401 +classes=80 +num=9 +jitter=.3 +ignore_thresh = .7 +truth_thresh = 1 +random=1 +scale_x_y = 1.05 +iou_thresh=0.213 +cls_normalizer=1.0 +iou_normalizer=0.07 +iou_loss=ciou +nms_kind=greedynms +beta_nms=0.6 +max_delta=5 diff --git a/detector/YOLOv3/darknet.py b/detector/YOLOv3/darknet.py new file mode 100644 index 0000000..9cef048 --- /dev/null +++ b/detector/YOLOv3/darknet.py @@ -0,0 +1,453 @@ +import torch.nn as nn +import torch.nn.functional as F +import numpy as np +from .cfg import * +from .region_layer import RegionLayer +from .yolo_layer import YoloLayer + +class MaxPoolStride1(nn.Module): + def __init__(self): + super(MaxPoolStride1, self).__init__() + + def forward(self, x): + x = F.max_pool2d(F.pad(x, (0,1,0,1), mode='replicate'), 2, stride=1) + return x + +class Upsample(nn.Module): + def __init__(self, stride=2): + super(Upsample, self).__init__() + self.stride = stride + def forward(self, x): + stride = self.stride + assert(x.data.dim() == 4) + B = x.data.size(0) + C = x.data.size(1) + H = x.data.size(2) + W = x.data.size(3) + ws = stride + hs = stride + x = x.view(B, C, H, 1, W, 1).expand(B, C, H, hs, W, ws).contiguous().view(B, C, H*hs, W*ws) + return x + +class Reorg(nn.Module): + def __init__(self, stride=2): + super(Reorg, self).__init__() + self.stride = stride + def forward(self, x): + stride = self.stride + assert(x.data.dim() == 4) + B = x.data.size(0) + C = x.data.size(1) + H = x.data.size(2) + W = x.data.size(3) + assert(H % stride == 0) + assert(W % stride == 0) + ws = stride + hs = stride + x = x.view(B, C, H//hs, hs, W//ws, ws).transpose(3,4).contiguous() + x = x.view(B, C, (H//hs)*(W//ws), hs*ws).transpose(2,3).contiguous() + x = x.view(B, C, hs*ws, H//hs, W//ws).transpose(1,2).contiguous() + x = x.view(B, hs*ws*C, H//hs, W//ws) + return x + +class GlobalAvgPool2d(nn.Module): + def __init__(self): + super(GlobalAvgPool2d, self).__init__() + + def forward(self, x): + N = x.data.size(0) + C = x.data.size(1) + H = x.data.size(2) + W = x.data.size(3) + x = F.avg_pool2d(x, (H, W)) + x = x.view(N, C) + return x + +# for route and shortcut +class EmptyModule(nn.Module): + def __init__(self): + super(EmptyModule, self).__init__() + + def forward(self, x): + return x + +class Mish(nn.Module): + def __init__(self): + super(Mish , self).__init__() + def forward(self , x): + return x * torch.tanh(F.softplus(x)) +# support route shortcut and reorg + +class Darknet(nn.Module): + def getLossLayers(self): + loss_layers = [] + for m in self.models: + if isinstance(m, RegionLayer) or isinstance(m, YoloLayer): + loss_layers.append(m) + return loss_layers + + def __init__(self, cfgfile, use_cuda=True): + super(Darknet, self).__init__() + self.use_cuda = use_cuda + self.blocks = parse_cfg(cfgfile) + self.models = self.create_network(self.blocks) # merge conv, bn,leaky + self.loss_layers = self.getLossLayers() + + #self.width = int(self.blocks[0]['width']) + #self.height = int(self.blocks[0]['height']) + + if len(self.loss_layers) > 0: + last = len(self.loss_layers)-1 + self.anchors = self.loss_layers[last].anchors + self.num_anchors = self.loss_layers[last].num_anchors + self.anchor_step = self.loss_layers[last].anchor_step + self.num_classes = self.loss_layers[last].num_classes + + # default format : major=0, minor=1 + self.header = torch.IntTensor([0,1,0,0]) + self.seen = 0 + + def forward(self, x): + ind = -2 + self.loss_layers = None + outputs = dict() + out_boxes = dict() + outno = 0 + for block in self.blocks: + ind = ind + 1 + + if block['type'] == 'net': + continue + elif block['type'] in ['convolutional', 'maxpool', 'reorg', 'upsample', 'avgpool', 'softmax', 'connected']: + x = self.models[ind](x) + outputs[ind] = x + elif block['type'] == 'route': + layers = block['layers'].split(',') + layers = [int(i) if int(i) > 0 else int(i)+ind for i in layers] + if len(layers) == 1: + x = outputs[layers[0]] + elif len(layers) == 2: + x1 = outputs[layers[0]] + x2 = outputs[layers[1]] + x = torch.cat((x1,x2),1) + elif (len(layers) == 4): + x1 = outputs[layers[0]] + x2 = outputs[layers[1]] + x3 = outputs[layers[2]] + x4 = outputs[layers[3]] + x = torch.cat((x1, x2, x3 , x4), 1) # 在channel 维度进行cat + outputs[ind] = x + outputs[ind] = x + elif block['type'] == 'shortcut': + from_layer = int(block['from']) + activation = block['activation'] + from_layer = from_layer if from_layer > 0 else from_layer + ind + x1 = outputs[from_layer] + x2 = outputs[ind-1] + x = x1 + x2 + if activation == 'leaky': + x = F.leaky_relu(x, 0.1, inplace=True) + elif activation == 'relu': + x = F.relu(x, inplace=True) + outputs[ind] = x + elif block['type'] in [ 'region', 'yolo']: + boxes = self.models[ind].get_mask_boxes(x) + out_boxes[outno]= boxes + outno += 1 + outputs[ind] = None + elif block['type'] == 'cost': + continue + else: + print('unknown type %s' % (block['type'])) + return x if outno == 0 else out_boxes + + def print_network(self): + print_cfg(self.blocks) + + def create_network(self, blocks): + models = nn.ModuleList() + + prev_filters = 3 + out_filters =[] + prev_stride = 1 + out_strides = [] + conv_id = 0 + ind = -2 + for block in blocks: + ind += 1 + if block['type'] == 'net': + prev_filters = int(block['channels']) + self.width = int(block['width']) + self.height = int(block['height']) + continue + elif block['type'] == 'convolutional': + conv_id = conv_id + 1 + batch_normalize = int(block['batch_normalize']) + filters = int(block['filters']) + kernel_size = int(block['size']) + stride = int(block['stride']) + is_pad = int(block['pad']) + pad = (kernel_size-1)//2 if is_pad else 0 + activation = block['activation'] + model = nn.Sequential() + if batch_normalize: + model.add_module('conv{0}'.format(conv_id), nn.Conv2d(prev_filters, filters, kernel_size, stride, pad, bias=False)) + model.add_module('bn{0}'.format(conv_id), nn.BatchNorm2d(filters)) + #model.add_module('bn{0}'.format(conv_id), BN2d(filters)) + else: + model.add_module('conv{0}'.format(conv_id), nn.Conv2d(prev_filters, filters, kernel_size, stride, pad)) + if activation == 'leaky': + model.add_module('leaky{0}'.format(conv_id), nn.LeakyReLU(0.1, inplace=True)) + elif activation == 'relu': + model.add_module('relu{0}'.format(conv_id), nn.ReLU(inplace=True)) + elif activation == 'mish': + model.add_module("mish{0}".format(conv_id), Mish()) + prev_filters = filters + out_filters.append(prev_filters) + prev_stride = stride * prev_stride + out_strides.append(prev_stride) + models.append(model) + elif block['type'] == 'maxpool': + pool_size = int(block['size']) + stride = int(block['stride']) + if stride > 1: + model = nn.MaxPool2d(pool_size, stride) + else: + model = MaxPoolStride1() + out_filters.append(prev_filters) + prev_stride = stride * prev_stride + out_strides.append(prev_stride) + models.append(model) + elif block['type'] == 'avgpool': + model = GlobalAvgPool2d() + out_filters.append(prev_filters) + models.append(model) + elif block['type'] == 'softmax': + model = nn.Softmax() + out_strides.append(prev_stride) + out_filters.append(prev_filters) + models.append(model) + elif block['type'] == 'cost': + if block['_type'] == 'sse': + model = nn.MSELoss(size_average=True) + elif block['_type'] == 'L1': + model = nn.L1Loss(size_average=True) + elif block['_type'] == 'smooth': + model = nn.SmoothL1Loss(size_average=True) + out_filters.append(1) + out_strides.append(prev_stride) + models.append(model) + elif block['type'] == 'reorg': + stride = int(block['stride']) + prev_filters = stride * stride * prev_filters + out_filters.append(prev_filters) + prev_stride = prev_stride * stride + out_strides.append(prev_stride) + models.append(Reorg(stride)) + elif block['type'] == 'upsample': + stride = int(block['stride']) + out_filters.append(prev_filters) + prev_stride = prev_stride / stride + out_strides.append(prev_stride) + #models.append(nn.Upsample(scale_factor=stride, mode='nearest')) + models.append(Upsample(stride)) + elif block['type'] == 'route': + layers = block['layers'].split(',') + ind = len(models) + layers = [int(i) if int(i) > 0 else int(i)+ind for i in layers] + if len(layers) == 1: + prev_filters = out_filters[layers[0]] + prev_stride = out_strides[layers[0]] + elif len(layers) == 2: + assert(layers[0] == ind - 1) + prev_filters = out_filters[layers[0]] + out_filters[layers[1]] + prev_stride = out_strides[layers[0]] + if (len(layers) == 4): + prev_filters = out_filters[layers[0]] + \ + out_filters[layers[1]] +out_filters[layers[2]] +out_filters[layers[3]] # 这里是filter 相加 + prev_stride = out_strides[ind-1] + out_filters.append(prev_filters) + out_strides.append(prev_stride) + models.append(EmptyModule()) + elif block['type'] == 'shortcut': + ind = len(models) + prev_filters = out_filters[ind-1] + out_filters.append(prev_filters) + prev_stride = out_strides[ind-1] + out_strides.append(prev_stride) + models.append(EmptyModule()) + elif block['type'] == 'connected': + filters = int(block['output']) + if block['activation'] == 'linear': + model = nn.Linear(prev_filters, filters) + elif block['activation'] == 'leaky': + model = nn.Sequential( + nn.Linear(prev_filters, filters), + nn.LeakyReLU(0.1, inplace=True)) + elif block['activation'] == 'relu': + model = nn.Sequential( + nn.Linear(prev_filters, filters), + nn.ReLU(inplace=True)) + prev_filters = filters + out_filters.append(prev_filters) + out_strides.append(prev_stride) + models.append(model) + elif block['type'] == 'region': + region_layer = RegionLayer(use_cuda=self.use_cuda) + anchors = block['anchors'].split(',') + region_layer.anchors = [float(i) for i in anchors] + region_layer.num_classes = int(block['classes']) + region_layer.num_anchors = int(block['num']) + region_layer.anchor_step = len(region_layer.anchors)//region_layer.num_anchors + region_layer.rescore = int(block['rescore']) + region_layer.object_scale = float(block['object_scale']) + region_layer.noobject_scale = float(block['noobject_scale']) + region_layer.class_scale = float(block['class_scale']) + region_layer.coord_scale = float(block['coord_scale']) + region_layer.thresh = float(block['thresh']) + out_filters.append(prev_filters) + out_strides.append(prev_stride) + models.append(region_layer) + elif block['type'] == 'yolo': + yolo_layer = YoloLayer(use_cuda=self.use_cuda) + anchors = block['anchors'].split(',') + anchor_mask = block['mask'].split(',') + yolo_layer.anchor_mask = [int(i) for i in anchor_mask] + yolo_layer.anchors = [float(i) for i in anchors] + yolo_layer.num_classes = int(block['classes']) + yolo_layer.num_anchors = int(block['num']) + yolo_layer.anchor_step = len(yolo_layer.anchors)//yolo_layer.num_anchors + try: + yolo_layer.rescore = int(block['rescore']) + except: + pass + yolo_layer.ignore_thresh = float(block['ignore_thresh']) + yolo_layer.truth_thresh = float(block['truth_thresh']) + yolo_layer.stride = prev_stride + yolo_layer.nth_layer = ind + yolo_layer.net_width = self.width + yolo_layer.net_height = self.height + out_filters.append(prev_filters) + out_strides.append(prev_stride) + models.append(yolo_layer) + else: + print('unknown type %s' % (block['type'])) + + return models + + def load_binfile(self, weightfile): + fp = open(weightfile, 'rb') + + version = np.fromfile(fp, count=3, dtype=np.int32) + version = [int(i) for i in version] + if version[0]*10+version[1] >=2 and version[0] < 1000 and version[1] < 1000: + seen = np.fromfile(fp, count=1, dtype=np.int64) + else: + seen = np.fromfile(fp, count=1, dtype=np.int32) + self.header = torch.from_numpy(np.concatenate((version, seen), axis=0)) + self.seen = int(seen) + body = np.fromfile(fp, dtype=np.float32) + fp.close() + return body + + def load_weights(self, weightfile): + buf = self.load_binfile(weightfile) + + start = 0 + ind = -2 + for block in self.blocks: + if start >= buf.size: + break + ind = ind + 1 + if block['type'] == 'net': + continue + elif block['type'] == 'convolutional': + model = self.models[ind] + batch_normalize = int(block['batch_normalize']) + if batch_normalize: + start = load_conv_bn(buf, start, model[0], model[1]) + else: + start = load_conv(buf, start, model[0]) + elif block['type'] == 'connected': + model = self.models[ind] + if block['activation'] != 'linear': + start = load_fc(buf, start, model[0]) + else: + start = load_fc(buf, start, model) + elif block['type'] == 'maxpool': + pass + elif block['type'] == 'reorg': + pass + elif block['type'] == 'upsample': + pass + elif block['type'] == 'route': + pass + elif block['type'] == 'shortcut': + pass + elif block['type'] == 'region': + pass + elif block['type'] == 'yolo': + pass + elif block['type'] == 'avgpool': + pass + elif block['type'] == 'softmax': + pass + elif block['type'] == 'cost': + pass + else: + print('unknown type %s' % (block['type'])) + + def save_weights(self, outfile, cutoff=0): + if cutoff <= 0: + cutoff = len(self.blocks)-1 + + fp = open(outfile, 'wb') + self.header[3] = self.seen + header = np.array(self.header[0:3].numpy(), np.int32) + header.tofile(fp) + if (self.header[0]*10+self.header[1]) >= 2: + seen = np.array(self.seen, np.int64) + else: + seen = np.array(self.seen, np.int32) + seen.tofile(fp) + + ind = -1 + for blockId in range(1, cutoff+1): + ind = ind + 1 + block = self.blocks[blockId] + if block['type'] == 'convolutional': + model = self.models[ind] + batch_normalize = int(block['batch_normalize']) + if batch_normalize: + save_conv_bn(fp, model[0], model[1]) + else: + save_conv(fp, model[0]) + elif block['type'] == 'connected': + model = self.models[ind] + if block['activation'] != 'linear': + save_fc(fc, model) + else: + save_fc(fc, model[0]) + elif block['type'] == 'maxpool': + pass + elif block['type'] == 'reorg': + pass + elif block['type'] == 'upsample': + pass + elif block['type'] == 'route': + pass + elif block['type'] == 'shortcut': + pass + elif block['type'] == 'region': + pass + elif block['type'] == 'yolo': + pass + elif block['type'] == 'avgpool': + pass + elif block['type'] == 'softmax': + pass + elif block['type'] == 'cost': + pass + else: + print('unknown type %s' % (block['type'])) + fp.close() diff --git a/detector/YOLOv3/demo/004545.jpg b/detector/YOLOv3/demo/004545.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4e06c202ba5a2ed96ba07804b804c59a9b3e0aef GIT binary patch literal 123072 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

4olGb?ZQcfF>jXrWf!@FvJtfb6l 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%0AP+zGwIEBI!}oqSZ#v5o=4jJ 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

ui!U9JT-<+ZkZQf}@}~4OSuW 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≈?ca_KtMlwR~Rc?6O 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

X;#TpU}Y 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%0AI*)&5pErH;Z>lBo~5>1!UD@q@%av^w~+R`5$& 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ⅅQuYCNJ9&K{YCYC)K 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 literal 0 HcmV?d00001 diff --git a/detector/YOLOv3/detect.py b/detector/YOLOv3/detect.py new file mode 100644 index 0000000..9a091a3 --- /dev/null +++ b/detector/YOLOv3/detect.py @@ -0,0 +1,131 @@ +import sys +import time +from PIL import Image, ImageDraw +#from models.tiny_yolo import TinyYoloNet +from yolo_utils import * +from darknet import Darknet + +import cv2 + +namesfile=None +def detect(cfgfile, weightfile, imgfolder): + m = Darknet(cfgfile) + + #m.print_network() + m.load_weights(weightfile) + print('Loading weights from %s... Done!' % (weightfile)) + + # if m.num_classes == 20: + # namesfile = 'data/voc.names' + # elif m.num_classes == 80: + # namesfile = 'data/coco.names' + # else: + # namesfile = 'data/names' + + use_cuda = True + if use_cuda: + m.cuda() + + imgfiles = [x for x in os.listdir(imgfolder) if x[-4:] == '.jpg'] + imgfiles.sort() + for imgname in imgfiles: + imgfile = os.path.join(imgfolder,imgname) + + img = Image.open(imgfile).convert('RGB') + sized = img.resize((m.width, m.height)) + + #for i in range(2): + start = time.time() + boxes = do_detect(m, sized, 0.5, 0.4, use_cuda) + finish = time.time() + #if i == 1: + print('%s: Predicted in %f seconds.' % (imgfile, (finish-start))) + + class_names = load_class_names(namesfile) + img = plot_boxes(img, boxes, 'result/{}'.format(os.path.basename(imgfile)), class_names) + img = np.array(img) + cv2.imshow('{}'.format(os.path.basename(imgfolder)), img) + cv2.resizeWindow('{}'.format(os.path.basename(imgfolder)), 1000,800) + cv2.waitKey(1000) + +def detect_cv2(cfgfile, weightfile, imgfile): + import cv2 + m = Darknet(cfgfile) + + m.print_network() + m.load_weights(weightfile) + print('Loading weights from %s... Done!' % (weightfile)) + + if m.num_classes == 20: + namesfile = 'data/voc.names' + elif m.num_classes == 80: + namesfile = 'data/coco.names' + else: + namesfile = 'data/names' + + use_cuda = True + if use_cuda: + m.cuda() + + img = cv2.imread(imgfile) + sized = cv2.resize(img, (m.width, m.height)) + sized = cv2.cvtColor(sized, cv2.COLOR_BGR2RGB) + + for i in range(2): + start = time.time() + boxes = do_detect(m, sized, 0.5, 0.4, use_cuda) + finish = time.time() + if i == 1: + print('%s: Predicted in %f seconds.' % (imgfile, (finish-start))) + + class_names = load_class_names(namesfile) + plot_boxes_cv2(img, boxes, savename='predictions.jpg', class_names=class_names) + +def detect_skimage(cfgfile, weightfile, imgfile): + from skimage import io + from skimage.transform import resize + m = Darknet(cfgfile) + + m.print_network() + m.load_weights(weightfile) + print('Loading weights from %s... Done!' % (weightfile)) + + if m.num_classes == 20: + namesfile = 'data/voc.names' + elif m.num_classes == 80: + namesfile = 'data/coco.names' + else: + namesfile = 'data/names' + + use_cuda = True + if use_cuda: + m.cuda() + + img = io.imread(imgfile) + sized = resize(img, (m.width, m.height)) * 255 + + for i in range(2): + start = time.time() + boxes = do_detect(m, sized, 0.5, 0.4, use_cuda) + finish = time.time() + if i == 1: + print('%s: Predicted in %f seconds.' % (imgfile, (finish-start))) + + class_names = load_class_names(namesfile) + plot_boxes_cv2(img, boxes, savename='predictions.jpg', class_names=class_names) + +if __name__ == '__main__': + if len(sys.argv) == 5: + cfgfile = sys.argv[1] + weightfile = sys.argv[2] + imgfolder = sys.argv[3] + cv2.namedWindow('{}'.format(os.path.basename(imgfolder)), cv2.WINDOW_NORMAL ) + cv2.resizeWindow('{}'.format(os.path.basename(imgfolder)), 1000,800) + globals()["namesfile"] = sys.argv[4] + detect(cfgfile, weightfile, imgfolder) + #detect_cv2(cfgfile, weightfile, imgfile) + #detect_skimage(cfgfile, weightfile, imgfile) + else: + print('Usage: ') + print(' python detect.py cfgfile weightfile imgfolder names') + #detect('cfg/tiny-yolo-voc.cfg', 'tiny-yolo-voc.weights', 'data/person.jpg', version=1) diff --git a/detector/YOLOv3/detector.py b/detector/YOLOv3/detector.py new file mode 100644 index 0000000..1dcbb93 --- /dev/null +++ b/detector/YOLOv3/detector.py @@ -0,0 +1,107 @@ +import torch +import logging +import numpy as np +import cv2 + +from .darknet import Darknet +from .yolo_utils import get_all_boxes, nms, post_process, xywh_to_xyxy, xyxy_to_xywh +from .nms import boxes_nms +import time + +class YOLOv3(object): + def __init__(self, cfgfile, weightfile, namesfile, score_thresh=0.7, conf_thresh=0.01, nms_thresh=0.45, + is_xywh=False, use_cuda=True): + # net definition + self.net = Darknet(cfgfile) + self.net.load_weights(weightfile) + logger = logging.getLogger("root.detector") + logger.info('Loading weights from %s... Done!' % (weightfile)) + self.device = "cuda" if use_cuda else "cpu" + self.net.eval() + + self.net.to(self.device) + + # constants + self.size = self.net.width, self.net.height + self.score_thresh = score_thresh + self.conf_thresh = conf_thresh + self.nms_thresh = nms_thresh + self.use_cuda = use_cuda + self.is_xywh = is_xywh + self.num_classes = self.net.num_classes + self.class_names = self.load_class_names(namesfile) + + def __call__(self, ori_img): + # img to tensor + assert isinstance(ori_img, np.ndarray), "input must be a numpy array!" + img = ori_img.astype(np.float) / 255. + + img = cv2.resize(img, self.size) + + img = torch.from_numpy(img).float().permute(2, 0, 1).unsqueeze(0) ##BGR 去推理 + + # forward + with torch.no_grad(): + img = img.to(self.device) + # t5 = time.time() + out_boxes = self.net(img) + # t6 = time.time() + # print(' -------------infer----------------: %f' % (t5 - t6)) + boxes = get_all_boxes(out_boxes, self.conf_thresh, self.num_classes, + use_cuda=self.use_cuda) # batch size is 1 + # boxes = nms(boxes, self.nms_thresh) + + boxes = post_process(boxes, self.net.num_classes, self.conf_thresh, self.nms_thresh)[0].cpu() + boxes = boxes[boxes[:, -2] > self.score_thresh, :] # bbox xmin ymin xmax ymax + + if len(boxes) == 0: + bbox = torch.FloatTensor([]).reshape([0, 4]) + cls_conf = torch.FloatTensor([]) + cls_ids = torch.LongTensor([]) + else: + height, width = ori_img.shape[:2] + bbox = boxes[:, :4] + if self.is_xywh: + # bbox x y w h + bbox = xyxy_to_xywh(bbox) + + bbox *= torch.FloatTensor([[width, height, width, height]]) + cls_conf = boxes[:, 5] + cls_ids = boxes[:, 6].long() + return bbox.numpy(), cls_conf.numpy(), cls_ids.numpy() + + def load_class_names(self, namesfile): + with open(namesfile, 'r', encoding='utf8') as fp: + class_names = [line.strip() for line in fp.readlines()] + return class_names + + +def demo(): + import os + from vizer.draw import draw_boxes + + yolo = YOLOv3("cfg/yolo_v3.cfg", "weight/yolov3.weights", "cfg/coco.names") + print("yolo.size =", yolo.size) + root = "./demo" + resdir = os.path.join(root, "results") + os.makedirs(resdir, exist_ok=True) + files = [os.path.join(root, file) for file in os.listdir(root) if file.endswith('.jpg')] + files.sort() + for filename in files: + img = cv2.imread(filename) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + bbox, cls_conf, cls_ids = yolo(img) + + if bbox is not None: + img = draw_boxes(img, bbox, cls_ids, cls_conf, class_name_map=yolo.class_names) + # save results + cv2.imwrite(os.path.join(resdir, os.path.basename(filename)), img[:, :, (2, 1, 0)]) + # imshow + # cv2.namedWindow("yolo", cv2.WINDOW_NORMAL) + # cv2.resizeWindow("yolo", 600,600) + # cv2.imshow("yolo",res[:,:,(2,1,0)]) + # cv2.waitKey(0) + + +if __name__ == "__main__": + demo() diff --git a/detector/YOLOv3/nms/__init__.py b/detector/YOLOv3/nms/__init__.py new file mode 100644 index 0000000..4da7007 --- /dev/null +++ b/detector/YOLOv3/nms/__init__.py @@ -0,0 +1 @@ +from .nms import boxes_nms \ No newline at end of file diff --git a/detector/YOLOv3/nms/build.sh b/detector/YOLOv3/nms/build.sh new file mode 100644 index 0000000..44766a2 --- /dev/null +++ b/detector/YOLOv3/nms/build.sh @@ -0,0 +1,5 @@ +cd ext + +python build.py build_ext develop + +cd .. diff --git a/detector/YOLOv3/nms/ext/__init__.py b/detector/YOLOv3/nms/ext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/detector/YOLOv3/nms/ext/build.py b/detector/YOLOv3/nms/ext/build.py new file mode 100644 index 0000000..66973bc --- /dev/null +++ b/detector/YOLOv3/nms/ext/build.py @@ -0,0 +1,58 @@ +import glob +import os + +import torch +from setuptools import setup +from torch.utils.cpp_extension import CUDA_HOME +from torch.utils.cpp_extension import CppExtension +from torch.utils.cpp_extension import CUDAExtension + +requirements = ["torch"] + + +def get_extensions(): + extensions_dir = os.path.dirname(os.path.abspath(__file__)) + + main_file = glob.glob(os.path.join(extensions_dir, "*.cpp")) + source_cpu = glob.glob(os.path.join(extensions_dir, "cpu", "*.cpp")) + source_cuda = glob.glob(os.path.join(extensions_dir, "cuda", "*.cu")) + + sources = main_file + source_cpu + extension = CppExtension + + extra_compile_args = {"cxx": []} + define_macros = [] + + if torch.cuda.is_available() and CUDA_HOME is not None: + extension = CUDAExtension + sources += source_cuda + define_macros += [("WITH_CUDA", None)] + extra_compile_args["nvcc"] = [ + "-DCUDA_HAS_FP16=1", + "-D__CUDA_NO_HALF_OPERATORS__", + "-D__CUDA_NO_HALF_CONVERSIONS__", + "-D__CUDA_NO_HALF2_OPERATORS__", + ] + + sources = [os.path.join(extensions_dir, s) for s in sources] + + include_dirs = [extensions_dir] + + ext_modules = [ + extension( + "torch_extension", + sources, + include_dirs=include_dirs, + define_macros=define_macros, + extra_compile_args=extra_compile_args, + ) + ] + + return ext_modules + + +setup( + name="torch_extension", + version="0.1", + ext_modules=get_extensions(), + cmdclass={"build_ext": torch.utils.cpp_extension.BuildExtension}) diff --git a/detector/YOLOv3/nms/ext/cpu/nms_cpu.cpp b/detector/YOLOv3/nms/ext/cpu/nms_cpu.cpp new file mode 100644 index 0000000..5b3f93c --- /dev/null +++ b/detector/YOLOv3/nms/ext/cpu/nms_cpu.cpp @@ -0,0 +1,75 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +#include "cpu/vision.h" + + +template +at::Tensor nms_cpu_kernel(const at::Tensor& dets, + const at::Tensor& scores, + const float threshold) { + AT_ASSERTM(!dets.type().is_cuda(), "dets must be a CPU tensor"); + AT_ASSERTM(!scores.type().is_cuda(), "scores must be a CPU tensor"); + AT_ASSERTM(dets.type() == scores.type(), "dets should have the same type as scores"); + + if (dets.numel() == 0) { + return at::empty({0}, dets.options().dtype(at::kLong).device(at::kCPU)); + } + + auto x1_t = dets.select(1, 0).contiguous(); + auto y1_t = dets.select(1, 1).contiguous(); + auto x2_t = dets.select(1, 2).contiguous(); + auto y2_t = dets.select(1, 3).contiguous(); + + at::Tensor areas_t = (x2_t - x1_t) * (y2_t - y1_t); + + auto order_t = std::get<1>(scores.sort(0, /* descending=*/true)); + + auto ndets = dets.size(0); + at::Tensor suppressed_t = at::zeros({ndets}, dets.options().dtype(at::kByte).device(at::kCPU)); + + auto suppressed = suppressed_t.data(); + auto order = order_t.data(); + auto x1 = x1_t.data(); + auto y1 = y1_t.data(); + auto x2 = x2_t.data(); + auto y2 = y2_t.data(); + auto areas = areas_t.data(); + + for (int64_t _i = 0; _i < ndets; _i++) { + auto i = order[_i]; + if (suppressed[i] == 1) + continue; + auto ix1 = x1[i]; + auto iy1 = y1[i]; + auto ix2 = x2[i]; + auto iy2 = y2[i]; + auto iarea = areas[i]; + + for (int64_t _j = _i + 1; _j < ndets; _j++) { + auto j = order[_j]; + if (suppressed[j] == 1) + continue; + auto xx1 = std::max(ix1, x1[j]); + auto yy1 = std::max(iy1, y1[j]); + auto xx2 = std::min(ix2, x2[j]); + auto yy2 = std::min(iy2, y2[j]); + + auto w = std::max(static_cast(0), xx2 - xx1); + auto h = std::max(static_cast(0), yy2 - yy1); + auto inter = w * h; + auto ovr = inter / (iarea + areas[j] - inter); + if (ovr >= threshold) + suppressed[j] = 1; + } + } + return at::nonzero(suppressed_t == 0).squeeze(1); +} + +at::Tensor nms_cpu(const at::Tensor& dets, + const at::Tensor& scores, + const float threshold) { + at::Tensor result; + AT_DISPATCH_FLOATING_TYPES(dets.type(), "nms", [&] { + result = nms_cpu_kernel(dets, scores, threshold); + }); + return result; +} \ No newline at end of file diff --git a/detector/YOLOv3/nms/ext/cpu/vision.h b/detector/YOLOv3/nms/ext/cpu/vision.h new file mode 100644 index 0000000..b3529ad --- /dev/null +++ b/detector/YOLOv3/nms/ext/cpu/vision.h @@ -0,0 +1,7 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +#pragma once +#include + +at::Tensor nms_cpu(const at::Tensor& dets, + const at::Tensor& scores, + const float threshold); diff --git a/detector/YOLOv3/nms/ext/cuda/nms.cu b/detector/YOLOv3/nms/ext/cuda/nms.cu new file mode 100644 index 0000000..2eb4525 --- /dev/null +++ b/detector/YOLOv3/nms/ext/cuda/nms.cu @@ -0,0 +1,131 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +#include +#include + +#include +#include + +#include +#include + +int const threadsPerBlock = sizeof(unsigned long long) * 8; + +__device__ inline float devIoU(float const * const a, float const * const b) { + float left = max(a[0], b[0]), right = min(a[2], b[2]); + float top = max(a[1], b[1]), bottom = min(a[3], b[3]); + float width = max(right - left, 0.f), height = max(bottom - top, 0.f); + float interS = width * height; + float Sa = (a[2] - a[0]) * (a[3] - a[1]); + float Sb = (b[2] - b[0]) * (b[3] - b[1]); + return interS / (Sa + Sb - interS); +} + +__global__ void nms_kernel(const int n_boxes, const float nms_overlap_thresh, + const float *dev_boxes, unsigned long long *dev_mask) { + const int row_start = blockIdx.y; + const int col_start = blockIdx.x; + + // if (row_start > col_start) return; + + const int row_size = + min(n_boxes - row_start * threadsPerBlock, threadsPerBlock); + const int col_size = + min(n_boxes - col_start * threadsPerBlock, threadsPerBlock); + + __shared__ float block_boxes[threadsPerBlock * 5]; + if (threadIdx.x < col_size) { + block_boxes[threadIdx.x * 5 + 0] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 0]; + block_boxes[threadIdx.x * 5 + 1] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 1]; + block_boxes[threadIdx.x * 5 + 2] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 2]; + block_boxes[threadIdx.x * 5 + 3] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 3]; + block_boxes[threadIdx.x * 5 + 4] = + dev_boxes[(threadsPerBlock * col_start + threadIdx.x) * 5 + 4]; + } + __syncthreads(); + + if (threadIdx.x < row_size) { + const int cur_box_idx = threadsPerBlock * row_start + threadIdx.x; + const float *cur_box = dev_boxes + cur_box_idx * 5; + int i = 0; + unsigned long long t = 0; + int start = 0; + if (row_start == col_start) { + start = threadIdx.x + 1; + } + for (i = start; i < col_size; i++) { + if (devIoU(cur_box, block_boxes + i * 5) > nms_overlap_thresh) { + t |= 1ULL << i; + } + } + const int col_blocks = THCCeilDiv(n_boxes, threadsPerBlock); + dev_mask[cur_box_idx * col_blocks + col_start] = t; + } +} + +// boxes is a N x 5 tensor +at::Tensor nms_cuda(const at::Tensor boxes, float nms_overlap_thresh) { + using scalar_t = float; + AT_ASSERTM(boxes.type().is_cuda(), "boxes must be a CUDA tensor"); + auto scores = boxes.select(1, 4); + auto order_t = std::get<1>(scores.sort(0, /* descending=*/true)); + auto boxes_sorted = boxes.index_select(0, order_t); + + int boxes_num = boxes.size(0); + + const int col_blocks = THCCeilDiv(boxes_num, threadsPerBlock); + + scalar_t* boxes_dev = boxes_sorted.data(); + + THCState *state = at::globalContext().lazyInitCUDA(); // TODO replace with getTHCState + + unsigned long long* mask_dev = NULL; + //THCudaCheck(THCudaMalloc(state, (void**) &mask_dev, + // boxes_num * col_blocks * sizeof(unsigned long long))); + + mask_dev = (unsigned long long*) THCudaMalloc(state, boxes_num * col_blocks * sizeof(unsigned long long)); + + dim3 blocks(THCCeilDiv(boxes_num, threadsPerBlock), + THCCeilDiv(boxes_num, threadsPerBlock)); + dim3 threads(threadsPerBlock); + nms_kernel<<>>(boxes_num, + nms_overlap_thresh, + boxes_dev, + mask_dev); + + std::vector mask_host(boxes_num * col_blocks); + THCudaCheck(cudaMemcpy(&mask_host[0], + mask_dev, + sizeof(unsigned long long) * boxes_num * col_blocks, + cudaMemcpyDeviceToHost)); + + std::vector remv(col_blocks); + memset(&remv[0], 0, sizeof(unsigned long long) * col_blocks); + + at::Tensor keep = at::empty({boxes_num}, boxes.options().dtype(at::kLong).device(at::kCPU)); + int64_t* keep_out = keep.data(); + + int num_to_keep = 0; + for (int i = 0; i < boxes_num; i++) { + int nblock = i / threadsPerBlock; + int inblock = i % threadsPerBlock; + + if (!(remv[nblock] & (1ULL << inblock))) { + keep_out[num_to_keep++] = i; + unsigned long long *p = &mask_host[0] + i * col_blocks; + for (int j = nblock; j < col_blocks; j++) { + remv[j] |= p[j]; + } + } + } + + THCudaFree(state, mask_dev); + // TODO improve this part + return std::get<0>(order_t.index({ + keep.narrow(/*dim=*/0, /*start=*/0, /*length=*/num_to_keep).to( + order_t.device(), keep.scalar_type()) + }).sort(0, false)); +} \ No newline at end of file diff --git a/detector/YOLOv3/nms/ext/cuda/vision.h b/detector/YOLOv3/nms/ext/cuda/vision.h new file mode 100644 index 0000000..b5bd907 --- /dev/null +++ b/detector/YOLOv3/nms/ext/cuda/vision.h @@ -0,0 +1,7 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +#pragma once +#include + +at::Tensor nms_cuda(const at::Tensor boxes, float nms_overlap_thresh); + + diff --git a/detector/YOLOv3/nms/ext/nms.h b/detector/YOLOv3/nms/ext/nms.h new file mode 100644 index 0000000..312fed4 --- /dev/null +++ b/detector/YOLOv3/nms/ext/nms.h @@ -0,0 +1,28 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +#pragma once +#include "cpu/vision.h" + +#ifdef WITH_CUDA +#include "cuda/vision.h" +#endif + + +at::Tensor nms(const at::Tensor& dets, + const at::Tensor& scores, + const float threshold) { + + if (dets.type().is_cuda()) { +#ifdef WITH_CUDA + // TODO raise error if not compiled with CUDA + if (dets.numel() == 0) + return at::empty({0}, dets.options().dtype(at::kLong).device(at::kCPU)); + auto b = at::cat({dets, scores.unsqueeze(1)}, 1); + return nms_cuda(b, threshold); +#else + AT_ERROR("Not compiled with GPU support"); +#endif + } + + at::Tensor result = nms_cpu(dets, scores, threshold); + return result; +} diff --git a/detector/YOLOv3/nms/ext/vision.cpp b/detector/YOLOv3/nms/ext/vision.cpp new file mode 100644 index 0000000..726b77b --- /dev/null +++ b/detector/YOLOv3/nms/ext/vision.cpp @@ -0,0 +1,7 @@ +// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. +#include "nms.h" + + +PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { + m.def("nms", &nms, "non-maximum suppression"); +} diff --git a/detector/YOLOv3/nms/nms.py b/detector/YOLOv3/nms/nms.py new file mode 100644 index 0000000..1b4a2db --- /dev/null +++ b/detector/YOLOv3/nms/nms.py @@ -0,0 +1,34 @@ +import warnings +import torchvision + +try: + import torch + import torch_extension + + _nms = torch_extension.nms +except ImportError: + if torchvision.__version__ >= '0.3.0': + _nms = torchvision.ops.nms + else: + from .python_nms import python_nms + + _nms = python_nms + warnings.warn('You are using python version NMS, which is very very slow. Try compile c++ NMS ' + 'using `cd ext & python build.py build_ext develop`') + + +def boxes_nms(boxes, scores, nms_thresh, max_count=-1): + """ Performs non-maximum suppression, run on GPU or CPU according to + boxes's device. + Args: + boxes(Tensor): `xyxy` mode boxes, use absolute coordinates(or relative coordinates), shape is (n, 4) + scores(Tensor): scores, shape is (n, ) + nms_thresh(float): thresh + max_count (int): if > 0, then only the top max_proposals are kept after non-maximum suppression + Returns: + indices kept. + """ + keep = _nms(boxes, scores, nms_thresh) + if max_count > 0: + keep = keep[:max_count] + return keep diff --git a/detector/YOLOv3/nms/python_nms.py b/detector/YOLOv3/nms/python_nms.py new file mode 100644 index 0000000..bd8a4ba --- /dev/null +++ b/detector/YOLOv3/nms/python_nms.py @@ -0,0 +1,59 @@ +import torch +import numpy as np + + +def python_nms(boxes, scores, nms_thresh): + """ Performs non-maximum suppression using numpy + Args: + boxes(Tensor): `xyxy` mode boxes, use absolute coordinates(not support relative coordinates), + shape is (n, 4) + scores(Tensor): scores, shape is (n, ) + nms_thresh(float): thresh + Returns: + indices kept. + """ + if boxes.numel() == 0: + return torch.empty((0,), dtype=torch.long) + # Use numpy to run nms. Running nms in PyTorch code on CPU is really slow. + origin_device = boxes.device + cpu_device = torch.device('cpu') + boxes = boxes.to(cpu_device).numpy() + scores = scores.to(cpu_device).numpy() + + x1 = boxes[:, 0] + y1 = boxes[:, 1] + x2 = boxes[:, 2] + y2 = boxes[:, 3] + areas = (x2 - x1) * (y2 - y1) + order = np.argsort(scores)[::-1] + num_detections = boxes.shape[0] + suppressed = np.zeros((num_detections,), dtype=np.bool) + for _i in range(num_detections): + i = order[_i] + if suppressed[i]: + continue + ix1 = x1[i] + iy1 = y1[i] + ix2 = x2[i] + iy2 = y2[i] + iarea = areas[i] + + for _j in range(_i + 1, num_detections): + j = order[_j] + if suppressed[j]: + continue + + xx1 = max(ix1, x1[j]) + yy1 = max(iy1, y1[j]) + xx2 = min(ix2, x2[j]) + yy2 = min(iy2, y2[j]) + w = max(0, xx2 - xx1) + h = max(0, yy2 - yy1) + + inter = w * h + ovr = inter / (iarea + areas[j] - inter) + if ovr >= nms_thresh: + suppressed[j] = True + keep = np.nonzero(suppressed == 0)[0] + keep = torch.from_numpy(keep).to(origin_device) + return keep diff --git a/detector/YOLOv3/region_layer.py b/detector/YOLOv3/region_layer.py new file mode 100644 index 0000000..c55ef37 --- /dev/null +++ b/detector/YOLOv3/region_layer.py @@ -0,0 +1,185 @@ +import math +import sys +import time +import torch +import torch.nn as nn +from .yolo_utils import bbox_iou, multi_bbox_ious, convert2cpu + + +class RegionLayer(nn.Module): + def __init__(self, num_classes=0, anchors=[], num_anchors=1, use_cuda=None): + super(RegionLayer, self).__init__() + use_cuda = torch.cuda.is_available() and (True if use_cuda is None else use_cuda) + self.device = torch.device("cuda" if use_cuda else "cpu") + self.num_classes = num_classes + self.num_anchors = num_anchors + self.anchor_step = len(anchors) // num_anchors + # self.anchors = torch.stack(torch.FloatTensor(anchors).split(self.anchor_step)).to(self.device) + self.anchors = torch.FloatTensor(anchors).view(self.num_anchors, self.anchor_step).to(self.device) + self.rescore = 1 + self.coord_scale = 1 + self.noobject_scale = 1 + self.object_scale = 5 + self.class_scale = 1 + self.thresh = 0.6 + self.seen = 0 + + def build_targets(self, pred_boxes, target, nH, nW): + nB = target.size(0) + nA = self.num_anchors + conf_mask = torch.ones(nB, nA, nH, nW) * self.noobject_scale + coord_mask = torch.zeros(nB, nA, nH, nW) + cls_mask = torch.zeros(nB, nA, nH, nW) + tcoord = torch.zeros(4, nB, nA, nH, nW) + tconf = torch.zeros(nB, nA, nH, nW) + tcls = torch.zeros(nB, nA, nH, nW) + + nAnchors = nA * nH * nW + nPixels = nH * nW + nGT = 0 # number of ground truth + nRecall = 0 + # it works faster on CPU than on GPU. + anchors = self.anchors.to("cpu") + + if self.seen < 12800: + tcoord[0].fill_(0.5) + tcoord[1].fill_(0.5) + coord_mask.fill_(1) + + for b in range(nB): + cur_pred_boxes = pred_boxes[b * nAnchors:(b + 1) * nAnchors].t() + cur_ious = torch.zeros(nAnchors) + tbox = target[b].view(-1, 5).to("cpu") + for t in range(50): + if tbox[t][1] == 0: + break + gx, gw = [i * nW for i in (tbox[t][1], tbox[t][3])] + gy, gh = [i * nH for i in (tbox[t][2], tbox[t][4])] + cur_gt_boxes = torch.FloatTensor([gx, gy, gw, gh]).repeat(nAnchors, 1).t() + cur_ious = torch.max(cur_ious, multi_bbox_ious(cur_pred_boxes, cur_gt_boxes, x1y1x2y2=False)) + ignore_ix = cur_ious > self.thresh + conf_mask[b][ignore_ix.view(nA, nH, nW)] = 0 + + for t in range(50): + if tbox[t][1] == 0: + break + nGT += 1 + gx, gw = [i * nW for i in (tbox[t][1], tbox[t][3])] + gy, gh = [i * nH for i in (tbox[t][2], tbox[t][4])] + gw, gh = gw.float(), gh.float() + gi, gj = int(gx), int(gy) + + tmp_gt_boxes = torch.FloatTensor([0, 0, gw, gh]).repeat(nA, 1).t() + anchor_boxes = torch.cat((torch.zeros(nA, 2), anchors), 1).t() + tmp_ious = multi_bbox_ious(tmp_gt_boxes, anchor_boxes, x1y1x2y2=False) + best_iou, best_n = torch.max(tmp_ious, 0) + + if self.anchor_step == 4: # this part is not tested. + tmp_ious_mask = (tmp_ious == best_iou) + if tmp_ious_mask.sum() > 0: + gt_pos = torch.FloatTensor([gi, gj, gx, gy]).repeat(nA, 1).t() + an_pos = anchor_boxes[4:6] # anchor_boxes are consisted of [0 0 aw ah ax ay] + dist = pow(((gt_pos[0] + an_pos[0]) - gt_pos[2]), 2) + pow( + ((gt_pos[1] + an_pos[1]) - gt_pos[3]), 2) + dist[1 - tmp_ious_mask] = 10000 # set the large number for the small ious + _, best_n = torch.min(dist, 0) + + gt_box = torch.FloatTensor([gx, gy, gw, gh]) + pred_box = pred_boxes[b * nAnchors + best_n * nPixels + gj * nW + gi] + iou = bbox_iou(gt_box, pred_box, x1y1x2y2=False) + + coord_mask[b][best_n][gj][gi] = 1 + cls_mask[b][best_n][gj][gi] = 1 + conf_mask[b][best_n][gj][gi] = self.object_scale + tcoord[0][b][best_n][gj][gi] = gx - gi + tcoord[1][b][best_n][gj][gi] = gy - gj + tcoord[2][b][best_n][gj][gi] = math.log(gw / anchors[best_n][0]) + tcoord[3][b][best_n][gj][gi] = math.log(gh / anchors[best_n][1]) + tcls[b][best_n][gj][gi] = tbox[t][0] + tconf[b][best_n][gj][gi] = iou if self.rescore else 1. + if iou > 0.5: + nRecall += 1 + + return nGT, nRecall, coord_mask, conf_mask, cls_mask, tcoord, tconf, tcls + + def get_mask_boxes(self, output): + if not isinstance(self.anchors, torch.Tensor): + self.anchors = torch.FloatTensor(self.anchors).view(self.num_anchors, self.anchor_step).to(self.device) + masked_anchors = self.anchors.view(-1) + num_anchors = torch.IntTensor([self.num_anchors]).to(self.device) + return {'x': output, 'a': masked_anchors, 'n': num_anchors} + + def forward(self, output, target): + # output : BxAs*(4+1+num_classes)*H*W + t0 = time.time() + nB = output.data.size(0) # batch size + nA = self.num_anchors + nC = self.num_classes + nH = output.data.size(2) + nW = output.data.size(3) + cls_anchor_dim = nB * nA * nH * nW + + if not isinstance(self.anchors, torch.Tensor): + self.anchors = torch.FloatTensor(self.anchors).view(self.num_anchors, self.anchor_step).to(self.device) + + output = output.view(nB, nA, (5 + nC), nH, nW) + cls_grid = torch.linspace(5, 5 + nC - 1, nC).long().to(self.device) + ix = torch.LongTensor(range(0, 5)).to(self.device) + pred_boxes = torch.FloatTensor(4, cls_anchor_dim).to(self.device) + + coord = output.index_select(2, ix[0:4]).view(nB * nA, -1, nH * nW).transpose(0, 1).contiguous().view(-1, + cls_anchor_dim) # x, y, w, h + coord[0:2] = coord[0:2].sigmoid() # x, y + conf = output.index_select(2, ix[4]).view(nB, nA, nH, nW).sigmoid() + cls = output.index_select(2, cls_grid) + cls = cls.view(nB * nA, nC, nH * nW).transpose(1, 2).contiguous().view(cls_anchor_dim, nC) + + t1 = time.time() + grid_x = torch.linspace(0, nW - 1, nW).repeat(nB * nA, nH, 1).view(cls_anchor_dim).to(self.device) + grid_y = torch.linspace(0, nH - 1, nH).repeat(nW, 1).t().repeat(nB * nA, 1, 1).view(cls_anchor_dim).to( + self.device) + anchor_w = self.anchors.index_select(1, ix[0]).repeat(1, nB * nH * nW).view(cls_anchor_dim) + anchor_h = self.anchors.index_select(1, ix[1]).repeat(1, nB * nH * nW).view(cls_anchor_dim) + + pred_boxes[0] = coord[0] + grid_x + pred_boxes[1] = coord[1] + grid_y + pred_boxes[2] = coord[2].exp() * anchor_w + pred_boxes[3] = coord[3].exp() * anchor_h + # for build_targets. it works faster on CPU than on GPU + pred_boxes = convert2cpu(pred_boxes.transpose(0, 1).contiguous().view(-1, 4)).detach() + + t2 = time.time() + nGT, nRecall, coord_mask, conf_mask, cls_mask, tcoord, tconf, tcls = \ + self.build_targets(pred_boxes, target.detach(), nH, nW) + + cls_mask = (cls_mask == 1) + tcls = tcls[cls_mask].long().view(-1) + cls_mask = cls_mask.view(-1, 1).repeat(1, nC).to(self.device) + cls = cls[cls_mask].view(-1, nC) + + nProposals = int((conf > 0.25).sum()) + + tcoord = tcoord.view(4, cls_anchor_dim).to(self.device) + tconf, tcls = tconf.to(self.device), tcls.to(self.device) + coord_mask, conf_mask = coord_mask.view(cls_anchor_dim).to(self.device), conf_mask.sqrt().to(self.device) + + t3 = time.time() + loss_coord = self.coord_scale * nn.MSELoss(size_average=False)(coord * coord_mask, tcoord * coord_mask) / 2 + # sqrt(object_scale)/2 is almost equal to 1. + loss_conf = nn.MSELoss(size_average=False)(conf * conf_mask, tconf * conf_mask) / 2 + loss_cls = self.class_scale * nn.CrossEntropyLoss(size_average=False)(cls, tcls) if cls.size(0) > 0 else 0 + loss = loss_coord + loss_conf + loss_cls + t4 = time.time() + if False: + print('-' * 30) + print(' activation : %f' % (t1 - t0)) + print(' create pred_boxes : %f' % (t2 - t1)) + print(' build targets : %f' % (t3 - t2)) + print(' create loss : %f' % (t4 - t3)) + print(' total : %f' % (t4 - t0)) + print('%d: nGT %3d, nRC %3d, nPP %3d, loss: box %6.3f, conf %6.3f, class %6.3f, total %7.3f' + % (self.seen, nGT, nRecall, nProposals, loss_coord, loss_conf, loss_cls, loss)) + if math.isnan(loss.item()): + print(conf, tconf) + sys.exit(0) + return loss diff --git a/detector/YOLOv3/weight/.gitkeep b/detector/YOLOv3/weight/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/detector/YOLOv3/yolo_layer.py b/detector/YOLOv3/yolo_layer.py new file mode 100644 index 0000000..578969f --- /dev/null +++ b/detector/YOLOv3/yolo_layer.py @@ -0,0 +1,181 @@ +import math +import sys +import time +import torch +import torch.nn as nn +from .yolo_utils import bbox_iou, multi_bbox_ious, convert2cpu + + +class YoloLayer(nn.Module): + def __init__(self, anchor_mask=[], num_classes=0, anchors=[], num_anchors=1, use_cuda=None): + super(YoloLayer, self).__init__() + use_cuda = torch.cuda.is_available() and (True if use_cuda is None else use_cuda) + self.device = torch.device("cuda" if use_cuda else "cpu") + + self.anchor_mask = anchor_mask + self.num_classes = num_classes + self.anchors = anchors + self.num_anchors = num_anchors + self.anchor_step = len(anchors) // num_anchors + self.rescore = 0 + self.ignore_thresh = 0.5 + self.truth_thresh = 1. + self.stride = 32 + self.nth_layer = 0 + self.seen = 0 + self.net_width = 0 + self.net_height = 0 + + def get_mask_boxes(self, output): + masked_anchors = [] + for m in self.anchor_mask: + masked_anchors += self.anchors[m * self.anchor_step:(m + 1) * self.anchor_step] + masked_anchors = [anchor / self.stride for anchor in masked_anchors] + + masked_anchors = torch.FloatTensor(masked_anchors).to(self.device) + num_anchors = torch.IntTensor([len(self.anchor_mask)]).to(self.device) + return {'x': output, 'a': masked_anchors, 'n': num_anchors} + + def build_targets(self, pred_boxes, target, anchors, nA, nH, nW): + nB = target.size(0) + anchor_step = anchors.size(1) # anchors[nA][anchor_step] + conf_mask = torch.ones(nB, nA, nH, nW) + coord_mask = torch.zeros(nB, nA, nH, nW) + cls_mask = torch.zeros(nB, nA, nH, nW) + tcoord = torch.zeros(4, nB, nA, nH, nW) + tconf = torch.zeros(nB, nA, nH, nW) + tcls = torch.zeros(nB, nA, nH, nW) + twidth, theight = self.net_width / self.stride, self.net_height / self.stride + + nAnchors = nA * nH * nW + nPixels = nH * nW + nGT = 0 + nRecall = 0 + nRecall75 = 0 + + # it works faster on CPU than on GPU. + anchors = anchors.to("cpu") + + for b in range(nB): + cur_pred_boxes = pred_boxes[b * nAnchors:(b + 1) * nAnchors].t() + cur_ious = torch.zeros(nAnchors) + tbox = target[b].view(-1, 5).to("cpu") + for t in range(50): + if tbox[t][1] == 0: + break + gx, gy = tbox[t][1] * nW, tbox[t][2] * nH + gw, gh = tbox[t][3] * twidth, tbox[t][4] * theight + cur_gt_boxes = torch.FloatTensor([gx, gy, gw, gh]).repeat(nAnchors, 1).t() + cur_ious = torch.max(cur_ious, multi_bbox_ious(cur_pred_boxes, cur_gt_boxes, x1y1x2y2=False)) + ignore_ix = cur_ious > self.ignore_thresh + conf_mask[b][ignore_ix.view(nA, nH, nW)] = 0 + + for t in range(50): + if tbox[t][1] == 0: + break + nGT += 1 + gx, gy = tbox[t][1] * nW, tbox[t][2] * nH + gw, gh = tbox[t][3] * twidth, tbox[t][4] * theight + gw, gh = gw.float(), gh.float() + gi, gj = int(gx), int(gy) + + tmp_gt_boxes = torch.FloatTensor([0, 0, gw, gh]).repeat(nA, 1).t() + anchor_boxes = torch.cat((torch.zeros(nA, anchor_step), anchors), 1).t() + _, best_n = torch.max(multi_bbox_ious(tmp_gt_boxes, anchor_boxes, x1y1x2y2=False), 0) + + gt_box = torch.FloatTensor([gx, gy, gw, gh]) + pred_box = pred_boxes[b * nAnchors + best_n * nPixels + gj * nW + gi] + iou = bbox_iou(gt_box, pred_box, x1y1x2y2=False) + + coord_mask[b][best_n][gj][gi] = 1 + cls_mask[b][best_n][gj][gi] = 1 + conf_mask[b][best_n][gj][gi] = 1 + tcoord[0][b][best_n][gj][gi] = gx - gi + tcoord[1][b][best_n][gj][gi] = gy - gj + tcoord[2][b][best_n][gj][gi] = math.log(gw / anchors[best_n][0]) + tcoord[3][b][best_n][gj][gi] = math.log(gh / anchors[best_n][1]) + tcls[b][best_n][gj][gi] = tbox[t][0] + tconf[b][best_n][gj][gi] = iou if self.rescore else 1. + + if iou > 0.5: + nRecall += 1 + if iou > 0.75: + nRecall75 += 1 + + return nGT, nRecall, nRecall75, coord_mask, conf_mask, cls_mask, tcoord, tconf, tcls + + def forward(self, output, target): + # output : BxAs*(4+1+num_classes)*H*W + mask_tuple = self.get_mask_boxes(output) + t0 = time.time() + nB = output.data.size(0) # batch size + nA = mask_tuple['n'].item() # num_anchors + nC = self.num_classes + nH = output.data.size(2) + nW = output.data.size(3) + anchor_step = mask_tuple['a'].size(0) // nA + anchors = mask_tuple['a'].view(nA, anchor_step).to(self.device) + cls_anchor_dim = nB * nA * nH * nW + + output = output.view(nB, nA, (5 + nC), nH, nW) + cls_grid = torch.linspace(5, 5 + nC - 1, nC).long().to(self.device) + ix = torch.LongTensor(range(0, 5)).to(self.device) + pred_boxes = torch.FloatTensor(4, cls_anchor_dim).to(self.device) + + coord = output.index_select(2, ix[0:4]).view(nB * nA, -1, nH * nW).transpose(0, 1).contiguous().view(-1, + cls_anchor_dim) # x, y, w, h + coord[0:2] = coord[0:2].sigmoid() # x, y + conf = output.index_select(2, ix[4]).view(nB, nA, nH, nW).sigmoid() + cls = output.index_select(2, cls_grid) + cls = cls.view(nB * nA, nC, nH * nW).transpose(1, 2).contiguous().view(cls_anchor_dim, nC) + + t1 = time.time() + grid_x = torch.linspace(0, nW - 1, nW).repeat(nB * nA, nH, 1).view(cls_anchor_dim).to(self.device) + grid_y = torch.linspace(0, nH - 1, nH).repeat(nW, 1).t().repeat(nB * nA, 1, 1).view(cls_anchor_dim).to( + self.device) + anchor_w = anchors.index_select(1, ix[0]).repeat(1, nB * nH * nW).view(cls_anchor_dim) + anchor_h = anchors.index_select(1, ix[1]).repeat(1, nB * nH * nW).view(cls_anchor_dim) + + pred_boxes[0] = coord[0] + grid_x + pred_boxes[1] = coord[1] + grid_y + pred_boxes[2] = coord[2].exp() * anchor_w + pred_boxes[3] = coord[3].exp() * anchor_h + # for build_targets. it works faster on CPU than on GPU + pred_boxes = convert2cpu(pred_boxes.transpose(0, 1).contiguous().view(-1, 4)).detach() + + t2 = time.time() + nGT, nRecall, nRecall75, coord_mask, conf_mask, cls_mask, tcoord, tconf, tcls = \ + self.build_targets(pred_boxes, target.detach(), anchors.detach(), nA, nH, nW) + + cls_mask = (cls_mask == 1) + tcls = tcls[cls_mask].long().view(-1) + cls_mask = cls_mask.view(-1, 1).repeat(1, nC).to(self.device) + cls = cls[cls_mask].view(-1, nC) + + nProposals = int((conf > 0.25).sum()) + + tcoord = tcoord.view(4, cls_anchor_dim).to(self.device) + tconf, tcls = tconf.to(self.device), tcls.to(self.device) + coord_mask, conf_mask = coord_mask.view(cls_anchor_dim).to(self.device), conf_mask.to(self.device) + + t3 = time.time() + loss_coord = nn.MSELoss(size_average=False)(coord * coord_mask, tcoord * coord_mask) / 2 + loss_conf = nn.MSELoss(size_average=False)(conf * conf_mask, tconf * conf_mask) + loss_cls = nn.CrossEntropyLoss(size_average=False)(cls, tcls) if cls.size(0) > 0 else 0 + loss = loss_coord + loss_conf + loss_cls + + t4 = time.time() + if False: + print('-' * 30) + print(' activation : %f' % (t1 - t0)) + print(' create pred_boxes : %f' % (t2 - t1)) + print(' build targets : %f' % (t3 - t2)) + print(' create loss : %f' % (t4 - t3)) + print(' total : %f' % (t4 - t0)) + print( + '%d: Layer(%03d) nGT %3d, nRC %3d, nRC75 %3d, nPP %3d, loss: box %6.3f, conf %6.3f, class %6.3f, total %7.3f' + % (self.seen, self.nth_layer, nGT, nRecall, nRecall75, nProposals, loss_coord, loss_conf, loss_cls, loss)) + if math.isnan(loss.item()): + print(conf, tconf) + sys.exit(0) + return loss diff --git a/detector/YOLOv3/yolo_utils.py b/detector/YOLOv3/yolo_utils.py new file mode 100644 index 0000000..b546eef --- /dev/null +++ b/detector/YOLOv3/yolo_utils.py @@ -0,0 +1,589 @@ +import os +import time +import math +import torch +import numpy as np +from PIL import Image, ImageDraw +import struct # get_image_size +import imghdr # get_image_size + + +def sigmoid(x): + return 1.0 / (math.exp(-x) + 1.) + + +def softmax(x): + x = torch.exp(x - torch.max(x)) + x /= x.sum() + return x + + +def bbox_iou(box1, box2, x1y1x2y2=True): + if x1y1x2y2: + x1_min = min(box1[0], box2[0]) + x2_max = max(box1[2], box2[2]) + y1_min = min(box1[1], box2[1]) + y2_max = max(box1[3], box2[3]) + w1, h1 = box1[2] - box1[0], box1[3] - box1[1] + w2, h2 = box2[2] - box2[0], box2[3] - box2[1] + else: + w1, h1 = box1[2], box1[3] + w2, h2 = box2[2], box2[3] + x1_min = min(box1[0] - w1 / 2.0, box2[0] - w2 / 2.0) + x2_max = max(box1[0] + w1 / 2.0, box2[0] + w2 / 2.0) + y1_min = min(box1[1] - h1 / 2.0, box2[1] - h2 / 2.0) + y2_max = max(box1[1] + h1 / 2.0, box2[1] + h2 / 2.0) + + w_union = x2_max - x1_min + h_union = y2_max - y1_min + w_cross = w1 + w2 - w_union + h_cross = h1 + h2 - h_union + carea = 0 + if w_cross <= 0 or h_cross <= 0: + return 0.0 + + area1 = w1 * h1 + area2 = w2 * h2 + carea = w_cross * h_cross + uarea = area1 + area2 - carea + return float(carea / uarea) + + +def multi_bbox_ious(boxes1, boxes2, x1y1x2y2=True): + if x1y1x2y2: + x1_min = torch.min(boxes1[0], boxes2[0]) + x2_max = torch.max(boxes1[2], boxes2[2]) + y1_min = torch.min(boxes1[1], boxes2[1]) + y2_max = torch.max(boxes1[3], boxes2[3]) + w1, h1 = boxes1[2] - boxes1[0], boxes1[3] - boxes1[1] + w2, h2 = boxes2[2] - boxes2[0], boxes2[3] - boxes2[1] + else: + w1, h1 = boxes1[2], boxes1[3] + w2, h2 = boxes2[2], boxes2[3] + x1_min = torch.min(boxes1[0] - w1 / 2.0, boxes2[0] - w2 / 2.0) + x2_max = torch.max(boxes1[0] + w1 / 2.0, boxes2[0] + w2 / 2.0) + y1_min = torch.min(boxes1[1] - h1 / 2.0, boxes2[1] - h2 / 2.0) + y2_max = torch.max(boxes1[1] + h1 / 2.0, boxes2[1] + h2 / 2.0) + + w_union = x2_max - x1_min + h_union = y2_max - y1_min + w_cross = w1 + w2 - w_union + h_cross = h1 + h2 - h_union + mask = (((w_cross <= 0) + (h_cross <= 0)) > 0) + area1 = w1 * h1 + area2 = w2 * h2 + carea = w_cross * h_cross + carea[mask] = 0 + uarea = area1 + area2 - carea + return carea / uarea + + +from .nms import boxes_nms + + +def post_process(boxes, num_classes, conf_thresh=0.01, nms_thresh=0.45, obj_thresh=0.3): + batch_size = boxes.size(0) + + # nms + results_boxes = [] + for batch_id in range(batch_size): + processed_boxes = [] + for cls_id in range(num_classes): + mask = (boxes[batch_id, :, -1] == cls_id) * (boxes[batch_id, :, 4] > obj_thresh) + masked_boxes = boxes[batch_id, mask] + + keep = boxes_nms(masked_boxes[:, :4], masked_boxes[:, 5], nms_thresh) + + nmsed_boxes = masked_boxes[keep, :] + + processed_boxes.append(nmsed_boxes) + processed_boxes = torch.cat(processed_boxes, dim=0) + + results_boxes.append(processed_boxes) + + return results_boxes + + +def xywh_to_xyxy(boxes_xywh): + boxes_xyxy = boxes_xywh.copy() + boxes_xyxy[:, 0] = boxes_xywh[:, 0] - boxes_xywh[:, 2] / 2. + boxes_xyxy[:, 0] = boxes_xywh[:, 0] - boxes_xywh[:, 2] / 2. + boxes_xyxy[:, 0] = boxes_xywh[:, 0] - boxes_xywh[:, 2] / 2. + boxes_xyxy[:, 0] = boxes_xywh[:, 0] - boxes_xywh[:, 2] / 2. + + return boxes_xyxy + + +def xyxy_to_xywh(boxes_xyxy): + if isinstance(boxes_xyxy, torch.Tensor): + boxes_xywh = boxes_xyxy.clone() + elif isinstance(boxes_xyxy, np.ndarray): + boxes_xywh = boxes_xyxy.copy() + + boxes_xywh[:, 0] = (boxes_xyxy[:, 0] + boxes_xyxy[:, 2]) / 2. + boxes_xywh[:, 1] = (boxes_xyxy[:, 1] + boxes_xyxy[:, 3]) / 2. + boxes_xywh[:, 2] = boxes_xyxy[:, 2] - boxes_xyxy[:, 0] + boxes_xywh[:, 3] = boxes_xyxy[:, 3] - boxes_xyxy[:, 1] + + return boxes_xywh + + +def nms(boxes, nms_thresh): + if len(boxes) == 0: + return boxes + + det_confs = torch.zeros(len(boxes)) + for i in range(len(boxes)): + det_confs[i] = boxes[i][4] + + _, sortIds = torch.sort(det_confs, descending=True) + out_boxes = [] + for i in range(len(boxes)): + box_i = boxes[sortIds[i]] + if box_i[4] > 0: + out_boxes.append(box_i) + for j in range(i + 1, len(boxes)): + box_j = boxes[sortIds[j]] + if bbox_iou(box_i, box_j, x1y1x2y2=False) > nms_thresh: + # print(box_i, box_j, bbox_iou(box_i, box_j, x1y1x2y2=False)) + box_j[4] = 0 + return out_boxes + + +def convert2cpu(gpu_matrix): + return torch.FloatTensor(gpu_matrix.size()).copy_(gpu_matrix) + + +def convert2cpu_long(gpu_matrix): + return torch.LongTensor(gpu_matrix.size()).copy_(gpu_matrix) + + +def get_all_boxes(output, conf_thresh, num_classes, only_objectness=1, validation=False, use_cuda=True): + # total number of inputs (batch size) + # first element (x) for first tuple (x, anchor_mask, num_anchor) + batchsize = output[0]['x'].data.size(0) + + all_boxes = [] + for i in range(len(output)): + pred, anchors, num_anchors = output[i]['x'].data, output[i]['a'], output[i]['n'].item() + boxes = get_region_boxes(pred, conf_thresh, num_classes, anchors, num_anchors, \ + only_objectness=only_objectness, validation=validation, use_cuda=use_cuda) + + all_boxes.append(boxes) + return torch.cat(all_boxes, dim=1) + + +def get_region_boxes(output, obj_thresh, num_classes, anchors, num_anchors, only_objectness=1, validation=False, + use_cuda=True): + device = torch.device("cuda" if use_cuda else "cpu") + anchors = anchors.to(device) + anchor_step = anchors.size(0) // num_anchors + if output.dim() == 3: + output = output.unsqueeze(0) + batch = output.size(0) + assert (output.size(1) == (5 + num_classes) * num_anchors) + h = output.size(2) + w = output.size(3) + cls_anchor_dim = batch * num_anchors * h * w + + # all_boxes = [] + output = output.view(batch * num_anchors, 5 + num_classes, h * w).transpose(0, 1).contiguous().view(5 + num_classes, + cls_anchor_dim) + + grid_x = torch.linspace(0, w - 1, w).repeat(batch * num_anchors, h, 1).view(cls_anchor_dim).to(device) + grid_y = torch.linspace(0, h - 1, h).repeat(w, 1).t().repeat(batch * num_anchors, 1, 1).view(cls_anchor_dim).to( + device) + ix = torch.LongTensor(range(0, 2)).to(device) + anchor_w = anchors.view(num_anchors, anchor_step).index_select(1, ix[0]).repeat(1, batch, h * w).view( + cls_anchor_dim) + anchor_h = anchors.view(num_anchors, anchor_step).index_select(1, ix[1]).repeat(1, batch, h * w).view( + cls_anchor_dim) + + xs, ys = torch.sigmoid(output[0]) + grid_x, torch.sigmoid(output[1]) + grid_y + ws, hs = torch.exp(output[2]) * anchor_w.detach(), torch.exp(output[3]) * anchor_h.detach() + det_confs = torch.sigmoid(output[4]) + + # by ysyun, dim=1 means input is 2D or even dimension else dim=0 + cls_confs = torch.nn.Softmax(dim=1)(output[5:5 + num_classes].transpose(0, 1)).detach() + cls_max_confs, cls_max_ids = torch.max(cls_confs, 1) + cls_max_confs = cls_max_confs.view(-1) + cls_max_ids = cls_max_ids.view(-1).float() + + # sz_hw = h*w + # sz_hwa = sz_hw*num_anchors + # det_confs = convert2cpu(det_confs) + # cls_max_confs = convert2cpu(cls_max_confs) + # cls_max_ids = convert2cpu_long(cls_max_ids) + # xs, ys = convert2cpu(xs), convert2cpu(ys) + # ws, hs = convert2cpu(ws), convert2cpu(hs) + + cls_confs = det_confs * cls_max_confs + + # boxes = [xs/w, ys/h, ws/w, hs/h, det_confs, cls_confs, cls_max_ids] + xs, ys, ws, hs = xs / w, ys / h, ws / w, hs / h + x1, y1, x2, y2 = torch.clamp_min(xs - ws / 2., 0.), torch.clamp_min(ys - hs / 2., 0.), torch.clamp_max(xs + ws / 2., + 1.), torch.clamp_max( + ys + hs / 2., 1.) + boxes = [x1, y1, x2, y2, det_confs, cls_confs, cls_max_ids] + boxes = list(map(lambda x: x.view(batch, -1), boxes)) + boxes = torch.stack(boxes, dim=2) + + # for b in range(batch): + # boxes = [] + # for cy in range(h): + # for cx in range(w): + # for i in range(num_anchors): + # ind = b*sz_hwa + i*sz_hw + cy*w + cx + # det_conf = det_confs[ind] + # if only_objectness: + # conf = det_confs[ind] + # else: + # conf = det_confs[ind] * cls_max_confs[ind] + + # if conf > conf_thresh: + # bcx = xs[ind] + # bcy = ys[ind] + # bw = ws[ind] + # bh = hs[ind] + # cls_max_conf = cls_max_confs[ind] + # cls_max_id = cls_max_ids[ind] + # box = [bcx/w, bcy/h, bw/w, bh/h, det_conf, cls_max_conf, cls_max_id] + + # boxes.append(box) + # all_boxes.append(boxes) + return boxes + + +# def get_all_boxes(output, conf_thresh, num_classes, only_objectness=1, validation=False, use_cuda=True): +# # total number of inputs (batch size) +# # first element (x) for first tuple (x, anchor_mask, num_anchor) +# tot = output[0]['x'].data.size(0) +# all_boxes = [[] for i in range(tot)] +# for i in range(len(output)): +# pred, anchors, num_anchors = output[i]['x'].data, output[i]['a'], output[i]['n'].item() +# b = get_region_boxes(pred, conf_thresh, num_classes, anchors, num_anchors, \ +# only_objectness=only_objectness, validation=validation, use_cuda=use_cuda) +# for t in range(tot): +# all_boxes[t] += b[t] +# return all_boxes + +# def get_region_boxes(output, conf_thresh, num_classes, anchors, num_anchors, only_objectness=1, validation=False, use_cuda=True): +# device = torch.device("cuda" if use_cuda else "cpu") +# anchors = anchors.to(device) +# anchor_step = anchors.size(0)//num_anchors +# if output.dim() == 3: +# output = output.unsqueeze(0) +# batch = output.size(0) +# assert(output.size(1) == (5+num_classes)*num_anchors) +# h = output.size(2) +# w = output.size(3) +# cls_anchor_dim = batch*num_anchors*h*w + +# t0 = time.time() +# all_boxes = [] +# output = output.view(batch*num_anchors, 5+num_classes, h*w).transpose(0,1).contiguous().view(5+num_classes, cls_anchor_dim) + +# grid_x = torch.linspace(0, w-1, w).repeat(batch*num_anchors, h, 1).view(cls_anchor_dim).to(device) +# grid_y = torch.linspace(0, h-1, h).repeat(w,1).t().repeat(batch*num_anchors, 1, 1).view(cls_anchor_dim).to(device) +# ix = torch.LongTensor(range(0,2)).to(device) +# anchor_w = anchors.view(num_anchors, anchor_step).index_select(1, ix[0]).repeat(1, batch, h*w).view(cls_anchor_dim) +# anchor_h = anchors.view(num_anchors, anchor_step).index_select(1, ix[1]).repeat(1, batch, h*w).view(cls_anchor_dim) + +# xs, ys = torch.sigmoid(output[0]) + grid_x, torch.sigmoid(output[1]) + grid_y +# ws, hs = torch.exp(output[2]) * anchor_w.detach(), torch.exp(output[3]) * anchor_h.detach() +# det_confs = torch.sigmoid(output[4]) + +# # by ysyun, dim=1 means input is 2D or even dimension else dim=0 +# cls_confs = torch.nn.Softmax(dim=1)(output[5:5+num_classes].transpose(0,1)).detach() +# cls_max_confs, cls_max_ids = torch.max(cls_confs, 1) +# cls_max_confs = cls_max_confs.view(-1) +# cls_max_ids = cls_max_ids.view(-1) +# t1 = time.time() + +# sz_hw = h*w +# sz_hwa = sz_hw*num_anchors +# det_confs = convert2cpu(det_confs) +# cls_max_confs = convert2cpu(cls_max_confs) +# cls_max_ids = convert2cpu_long(cls_max_ids) +# xs, ys = convert2cpu(xs), convert2cpu(ys) +# ws, hs = convert2cpu(ws), convert2cpu(hs) +# if validation: +# cls_confs = convert2cpu(cls_confs.view(-1, num_classes)) + +# t2 = time.time() +# for b in range(batch): +# boxes = [] +# for cy in range(h): +# for cx in range(w): +# for i in range(num_anchors): +# ind = b*sz_hwa + i*sz_hw + cy*w + cx +# det_conf = det_confs[ind] +# if only_objectness: +# conf = det_confs[ind] +# else: +# conf = det_confs[ind] * cls_max_confs[ind] + +# if conf > conf_thresh: +# bcx = xs[ind] +# bcy = ys[ind] +# bw = ws[ind] +# bh = hs[ind] +# cls_max_conf = cls_max_confs[ind] +# cls_max_id = cls_max_ids[ind] +# box = [bcx/w, bcy/h, bw/w, bh/h, det_conf, cls_max_conf, cls_max_id] +# if (not only_objectness) and validation: +# for c in range(num_classes): +# tmp_conf = cls_confs[ind][c] +# if c != cls_max_id and det_confs[ind]*tmp_conf > conf_thresh: +# box.append(tmp_conf) +# box.append(c) +# boxes.append(box) +# all_boxes.append(boxes) +# t3 = time.time() +# if False: +# print('---------------------------------') +# print('matrix computation : %f' % (t1-t0)) +# print(' gpu to cpu : %f' % (t2-t1)) +# print(' boxes filter : %f' % (t3-t2)) +# print('---------------------------------') +# return all_boxes + +def plot_boxes_cv2(img, boxes, savename=None, class_names=None, color=None): + import cv2 + colors = torch.FloatTensor([[1, 0, 1], [0, 0, 1], [0, 1, 1], [0, 1, 0], [1, 1, 0], [1, 0, 0]]) + + def get_color(c, x, max_val): + ratio = float(x) / max_val * 5 + i = int(math.floor(ratio)) + j = int(math.ceil(ratio)) + ratio -= i + r = (1 - ratio) * colors[i][c] + ratio * colors[j][c] + return int(r * 255) + + width = img.shape[1] + height = img.shape[0] + for i in range(len(boxes)): + box = boxes[i] + x1 = int(round((box[0] - box[2] / 2.0) * width)) + y1 = int(round((box[1] - box[3] / 2.0) * height)) + x2 = int(round((box[0] + box[2] / 2.0) * width)) + y2 = int(round((box[1] + box[3] / 2.0) * height)) + + if color: + rgb = color + else: + rgb = (255, 0, 0) + if len(box) >= 7 and class_names: + cls_conf = box[5] + cls_id = box[6] + # print('%s: %f' % (class_names[cls_id], cls_conf)) + classes = len(class_names) + offset = cls_id * 123457 % classes + red = get_color(2, offset, classes) + green = get_color(1, offset, classes) + blue = get_color(0, offset, classes) + if color is None: + rgb = (red, green, blue) + img = cv2.putText(img, class_names[cls_id], (x1, y1), cv2.FONT_HERSHEY_SIMPLEX, 1.2, rgb, 1) + img = cv2.rectangle(img, (x1, y1), (x2, y2), rgb, 1) + if savename: + print("save plot results to %s" % savename) + cv2.imwrite(savename, img) + return img + + +def plot_boxes(img, boxes, savename=None, class_names=None): + colors = torch.FloatTensor([[1, 0, 1], [0, 0, 1], [0, 1, 1], [0, 1, 0], [1, 1, 0], [1, 0, 0]]) + + def get_color(c, x, max_val): + ratio = float(x) / max_val * 5 + i = int(math.floor(ratio)) + j = int(math.ceil(ratio)) + ratio -= i + r = (1 - ratio) * colors[i][c] + ratio * colors[j][c] + return int(r * 255) + + width = img.width + height = img.height + draw = ImageDraw.Draw(img) + print("%d box(es) is(are) found" % len(boxes)) + for i in range(len(boxes)): + box = boxes[i] + x1 = (box[0] - box[2] / 2.0) * width + y1 = (box[1] - box[3] / 2.0) * height + x2 = (box[0] + box[2] / 2.0) * width + y2 = (box[1] + box[3] / 2.0) * height + + rgb = (255, 0, 0) + if len(box) >= 7 and class_names: + cls_conf = box[5] + cls_id = box[6] + print('%s: %f' % (class_names[cls_id], cls_conf)) + classes = len(class_names) + offset = cls_id * 123457 % classes + red = get_color(2, offset, classes) + green = get_color(1, offset, classes) + blue = get_color(0, offset, classes) + rgb = (red, green, blue) + draw.text((x1, y1), class_names[cls_id], fill=rgb) + draw.rectangle([x1, y1, x2, y2], outline=rgb) + if savename: + print("save plot results to %s" % savename) + img.save(savename) + return img + + +def read_truths(lab_path): + if not os.path.exists(lab_path): + return np.array([]) + if os.path.getsize(lab_path): + truths = np.loadtxt(lab_path) + truths = truths.reshape(truths.size // 5, 5) # to avoid single truth problem + return truths + else: + return np.array([]) + + +def read_truths_args(lab_path, min_box_scale): + truths = read_truths(lab_path) + new_truths = [] + for i in range(truths.shape[0]): + if truths[i][3] < min_box_scale: + continue + new_truths.append([truths[i][0], truths[i][1], truths[i][2], truths[i][3], truths[i][4]]) + return np.array(new_truths) + + +def load_class_names(namesfile): + class_names = [] + with open(namesfile, 'r', encoding='utf8') as fp: + lines = fp.readlines() + for line in lines: + class_names.append(line.strip()) + return class_names + + +def image2torch(img): + if isinstance(img, Image.Image): + width = img.width + height = img.height + img = torch.ByteTensor(torch.ByteStorage.from_buffer(img.tobytes())) + img = img.view(height, width, 3).transpose(0, 1).transpose(0, 2).contiguous() + img = img.view(1, 3, height, width) + img = img.float().div(255.0) + elif type(img) == np.ndarray: # cv2 image + img = torch.from_numpy(img.transpose(2, 0, 1)).float().div(255.0).unsqueeze(0) + else: + print("unknown image type") + exit(-1) + return img + + +def do_detect(model, img, conf_thresh, nms_thresh, use_cuda=True): + model.eval() + t0 = time.time() + img = image2torch(img) + t1 = time.time() + + img = img.to(torch.device("cuda" if use_cuda else "cpu")) + t2 = time.time() + + out_boxes = model(img) + boxes = get_all_boxes(out_boxes, conf_thresh, model.num_classes, use_cuda=use_cuda)[0] + + t3 = time.time() + boxes = nms(boxes, nms_thresh) + t4 = time.time() + + if False: + print('-----------------------------------') + print(' image to tensor : %f' % (t1 - t0)) + print(' tensor to cuda : %f' % (t2 - t1)) + print(' predict : %f' % (t3 - t2)) + print(' nms : %f' % (t4 - t3)) + print(' total : %f' % (t4 - t0)) + print('-----------------------------------') + return boxes + + +def read_data_cfg(datacfg): + options = dict() + options['gpus'] = '0,1,2,3' + options['num_workers'] = '10' + with open(datacfg) as fp: + lines = fp.readlines() + + for line in lines: + line = line.strip() + if line == '': + continue + key, value = line.split('=') + key = key.strip() + value = value.strip() + options[key] = value + return options + + +def scale_bboxes(bboxes, width, height): + import copy + dets = copy.deepcopy(bboxes) + for i in range(len(dets)): + dets[i][0] = dets[i][0] * width + dets[i][1] = dets[i][1] * height + dets[i][2] = dets[i][2] * width + dets[i][3] = dets[i][3] * height + return dets + + +def file_lines(thefilepath): + count = 0 + thefile = open(thefilepath, 'rb') + while True: + buffer = thefile.read(8192 * 1024) + if not buffer: + break + count += buffer.count(b'\n') + thefile.close() + return count + + +def get_image_size(fname): + """ + Determine the image type of fhandle and return its size. + from draco + """ + with open(fname, 'rb') as fhandle: + head = fhandle.read(24) + if len(head) != 24: + return + if imghdr.what(fname) == 'png': + check = struct.unpack('>i', head[4:8])[0] + if check != 0x0d0a1a0a: + return + width, height = struct.unpack('>ii', head[16:24]) + elif imghdr.what(fname) == 'gif': + width, height = struct.unpack('H', fhandle.read(2))[0] - 2 + # We are at a SOFn block + fhandle.seek(1, 1) # Skip `precision' byte. + height, width = struct.unpack('>HH', fhandle.read(4)) + except Exception: # IGNORE:W0703 + return + else: + return + return width, height + + +def logging(message): + print('%s %s' % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), message)) diff --git a/detector/__init__.py b/detector/__init__.py new file mode 100644 index 0000000..0e57c92 --- /dev/null +++ b/detector/__init__.py @@ -0,0 +1,132 @@ +from .YOLOv3 import YOLOv3 +import onnxruntime +import numpy as np +import time +import cv2 +import torch +from detector.YOLOv3.nms import boxes_nms +__all__ = ['build_detector','build_onnx'] + +def xyxy_to_xywh(boxes_xyxy): + if isinstance(boxes_xyxy, torch.Tensor): + boxes_xywh = boxes_xyxy.clone() + elif isinstance(boxes_xyxy, np.ndarray): + boxes_xywh = boxes_xyxy.copy() + + boxes_xywh[:, 0] = (boxes_xyxy[:, 0] + boxes_xyxy[:, 2]) / 2. + boxes_xywh[:, 1] = (boxes_xyxy[:, 1] + boxes_xyxy[:, 3]) / 2. + boxes_xywh[:, 2] = boxes_xyxy[:, 2] - boxes_xyxy[:, 0] + boxes_xywh[:, 3] = boxes_xyxy[:, 3] - boxes_xyxy[:, 1] + + return boxes_xywh + +def build_detector(cfg, use_cuda): + return YOLOv3(cfg.YOLOV3.CFG, cfg.YOLOV3.WEIGHT, cfg.YOLOV3.CLASS_NAMES, + score_thresh=cfg.YOLOV3.SCORE_THRESH, nms_thresh=cfg.YOLOV3.NMS_THRESH, + is_xywh=True, use_cuda=use_cuda) + + +class build_onnx(): + def __init__(self , cfg): + + self.session = onnxruntime.InferenceSession(cfg.YOLOV4.WEIGHT) + # session = onnx.load(onnx_path) + print("The model expects input shape: ", self.session.get_inputs()[0].shape) + self.class_names = self.load_class_names(cfg.YOLOV4.CLASS_NAMES) + + + def forward(self,img,video_width,video_height): + IN_IMAGE_H = self.session.get_inputs()[0].shape[2] + IN_IMAGE_W = self.session.get_inputs()[0].shape[3] + + # Input + resized = cv2.resize(img, (IN_IMAGE_W, IN_IMAGE_H), interpolation=cv2.INTER_LINEAR) + img_in = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB) + img_in = np.transpose(img_in, (2, 0, 1)).astype(np.float32) + img_in = np.expand_dims(img_in, axis=0) + img_in /= 255.0 + print("Shape of the network input: ", img_in.shape) + + # Compute + input_name = self.session.get_inputs()[0].name + t5 = time.time() + outputs = self.session.run(None, {input_name: img_in}) + t6 = time.time() + print(' -------------infer----------------: %f' % (t5 - t6)) + + + self.boxes = np.array(self.post_processing(img_in, 0.4, 0.6, outputs))[0] + self.box =xyxy_to_xywh(self.boxes[:,0:4]) + self.box = self.box * np.array([video_width, video_height, video_width, video_height]) + + self.cls = self.boxes[:,5] + self.id = self.boxes[:,6] + return self.box , self.cls , self.id + + def load_class_names(self , namesfile): + with open(namesfile, 'r', encoding='utf8') as fp: + class_names = [line.strip() for line in fp.readlines()] + return class_names + + def post_processing(self,img, conf_thresh, nms_thresh, output): + + # [batch, num, 1, 4] + box_array = output[0] + # [batch, num, num_classes] + confs = output[1] + + t1 = time.time() + + if type(box_array).__name__ != 'ndarray': + box_array = box_array.cpu().detach().numpy() + confs = confs.cpu().detach().numpy() + + num_classes = confs.shape[2] + + # [batch, num, 4] + box_array = box_array[:, :, 0] + + # [batch, num, num_classes] --> [batch, num] + max_conf = np.max(confs, axis=2) + max_id = np.argmax(confs, axis=2) + + t2 = time.time() + + bboxes_batch = [] + for i in range(box_array.shape[0]): + + argwhere = max_conf[i] > conf_thresh + l_box_array = box_array[i, argwhere, :] + l_max_conf = max_conf[i, argwhere] + l_max_id = max_id[i, argwhere] + + bboxes = [] + # nms for each class + for j in range(num_classes): + + cls_argwhere = l_max_id == j + ll_box_array = l_box_array[cls_argwhere, :] + ll_max_conf = l_max_conf[cls_argwhere] + ll_max_id = l_max_id[cls_argwhere] + + keep = np.array(boxes_nms(torch.tensor(ll_box_array), torch.tensor(ll_max_conf), nms_thresh)) + + if (keep.size > 0): + ll_box_array = ll_box_array[keep, :] + ll_max_conf = ll_max_conf[keep] + ll_max_id = ll_max_id[keep] + + for k in range(ll_box_array.shape[0]): + bboxes.append([ll_box_array[k, 0], ll_box_array[k, 1], ll_box_array[k, 2], ll_box_array[k, 3], ll_max_conf[k], ll_max_conf[k], ll_max_id[k]]) + + bboxes_batch.append(bboxes) + + t3 = time.time() + + print('-----------------------------------') + print(' max and argmax : %f' % (t2 - t1)) + print(' nms : %f' % (t3 - t2)) + print('Post processing total : %f' % (t3 - t1)) + print('-----------------------------------') + + return bboxes_batch diff --git a/detector/trt.py b/detector/trt.py new file mode 100644 index 0000000..746ca8f --- /dev/null +++ b/detector/trt.py @@ -0,0 +1,212 @@ +import sys +import os +import time +import argparse +import numpy as np +import cv2 +# from PIL import Image +import tensorrt as trt +import pycuda.driver as cuda +import pycuda.autoinit +from detector.YOLOv3.nms import boxes_nms +import torch + +try: + # Sometimes python2 does not understand FileNotFoundError + FileNotFoundError +except NameError: + FileNotFoundError = IOError + +# __all__ = ['trt'] + +class tensorrt(): + def __init__(self , cfg, img_size = [416,416]): + self.cfg = cfg + self.engine = self.get_engine(cfg.YOLOV4.WEIGHT) + self.context = self.engine.create_execution_context() + self.buffers = self.allocate_buffers(self.engine, 1) + IN_IMAGE_H, IN_IMAGE_W = img_size + self.context.set_binding_shape(0, (1, 3, IN_IMAGE_H, IN_IMAGE_W)) + self.num_classes = 80 + self.image_size = img_size + # return context , buffers + + def get_engine(self,engine_path): + # If a serialized engine exists, use it instead of building an engine. + print("Reading engine from file {}".format(engine_path)) + TRT_LOGGER = trt.Logger() + with open(engine_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime: + return runtime.deserialize_cuda_engine(f.read()) + + def detect(self,context , buffers , image_src,video_width=416,video_height=416): + IN_IMAGE_H, IN_IMAGE_W = self.image_size + ta = time.time() + # Input + # image_src = cv2.imread(image_src) + resized = cv2.resize(image_src, (IN_IMAGE_W, IN_IMAGE_H), interpolation=cv2.INTER_LINEAR) + img_in = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB) + img_in = np.transpose(img_in, (2, 0, 1)).astype(np.float32) + img_in = np.expand_dims(img_in, axis=0) + img_in /= 255.0 + img_in = np.ascontiguousarray(img_in) + print("Shape of the network input: ", img_in.shape) + # print(img_in) + + inputs, outputs, bindings, stream = buffers + # print('Length of inputs: ', len(inputs)) + inputs[0].host = img_in + + trt_outputs = self.do_inference(context, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream) + + # print('Len of outputs: ', len(trt_outputs)) + + trt_outputs[0] = trt_outputs[0].reshape(1, -1, 1, 4) + trt_outputs[1] = trt_outputs[1].reshape(1, -1, self.num_classes) + + tb = time.time() + + # print('-----------------------------------') + # print(' TRT inference time: %f' % (tb - ta)) + # print('-----------------------------------') + + # boxes = post_processing(img_in, 0.4, 0.6, trt_outputs) + self.boxes = np.array(self.post_processing(img_in, self.cfg.YOLOV4.SCORE_THRESH, self.cfg.YOLOV4.NMS_THRESH, trt_outputs))[0] + # assert self.boxes[:,0:4] + self.box =self.xyxy_to_xywh(self.boxes[:,0:4]) + self.box = self.box * np.array([video_width, video_height, video_width, video_height]) + + self.cls = self.boxes[:,5] + self.id = self.boxes[:,6] + return self.box , self.cls , self.id + + def post_processing(self,img, conf_thresh, nms_thresh, output): + + box_array = output[0] + # [batch, num, num_classes] + confs = output[1] + + t1 = time.time() + + if type(box_array).__name__ != 'ndarray': + box_array = box_array.cpu().detach().numpy() + confs = confs.cpu().detach().numpy() + + num_classes = confs.shape[2] + + # [batch, num, 4] + box_array = box_array[:, :, 0] + + # [batch, num, num_classes] --> [batch, num] + max_conf = np.max(confs, axis=2) + max_id = np.argmax(confs, axis=2) + + t2 = time.time() + + bboxes_batch = [] + for i in range(box_array.shape[0]): + + argwhere = max_conf[i] > conf_thresh + l_box_array = box_array[i, argwhere, :] + l_max_conf = max_conf[i, argwhere] + l_max_id = max_id[i, argwhere] + + bboxes = [] + # nms for each class + for j in range(num_classes): + + cls_argwhere = l_max_id == j + ll_box_array = l_box_array[cls_argwhere, :] + ll_max_conf = l_max_conf[cls_argwhere] + ll_max_id = l_max_id[cls_argwhere] + + keep = np.array(boxes_nms(torch.tensor(ll_box_array), torch.tensor(ll_max_conf), nms_thresh)) + + if (keep.size > 0): + ll_box_array = ll_box_array[keep, :] + ll_max_conf = ll_max_conf[keep] + ll_max_id = ll_max_id[keep] + + for k in range(ll_box_array.shape[0]): + bboxes.append([ll_box_array[k, 0], ll_box_array[k, 1], ll_box_array[k, 2], ll_box_array[k, 3], ll_max_conf[k], ll_max_conf[k], ll_max_id[k]]) + + bboxes_batch.append(bboxes) + + t3 = time.time() + + # print('-----------------------------------') + # print(' max and argmax : %f' % (t2 - t1)) + # print(' nms : %f' % (t3 - t2)) + # print('Post processing total : %f' % (t3 - t1)) + # print('-----------------------------------') + + return bboxes_batch + + # Allocates all buffers required for an engine, i.e. host/device inputs/outputs. + # Simple helper data class that's a little nicer to use than a 2-tuple. + class HostDeviceMem(object): + def __init__(self, host_mem, device_mem): + self.host = host_mem + self.device = device_mem + + def __str__(self): + return "Host:\n" + str(self.host) + "\nDevice:\n" + str(self.device) + + def __repr__(self): + return self.__str__() + + def allocate_buffers(self,engine, batch_size): + inputs = [] + outputs = [] + bindings = [] + stream = cuda.Stream() + for binding in engine: + + size = trt.volume(engine.get_binding_shape(binding)) * batch_size + dims = engine.get_binding_shape(binding) + + # in case batch dimension is -1 (dynamic) + if dims[0] < 0: + size *= -1 + + dtype = trt.nptype(engine.get_binding_dtype(binding)) + # Allocate host and device buffers + host_mem = cuda.pagelocked_empty(size, dtype) + device_mem = cuda.mem_alloc(host_mem.nbytes) + # Append the device buffer to device bindings. + bindings.append(int(device_mem)) + # Append to the appropriate list. + if engine.binding_is_input(binding): + inputs.append(self.HostDeviceMem(host_mem, device_mem)) + else: + outputs.append(self.HostDeviceMem(host_mem, device_mem)) + return inputs, outputs, bindings, stream + +# This function is generalized for multiple inputs/outputs. +# inputs and outputs are expected to be lists of HostDeviceMem objects. + def do_inference(self,context, bindings, inputs, outputs, stream): + # Transfer input data to the GPU. + [cuda.memcpy_htod_async(inp.device, inp.host, stream) for inp in inputs] + # Run inference. + context.execute_async(bindings=bindings, stream_handle=stream.handle) + # Transfer predictions back from the GPU. + [cuda.memcpy_dtoh_async(out.host, out.device, stream) for out in outputs] + # Synchronize the stream + stream.synchronize() + # Return only the host outputs. + return [out.host for out in outputs] + def GiB(self,val): + return val * 1 << 30 + + def xyxy_to_xywh(self,boxes_xyxy): + if isinstance(boxes_xyxy, torch.Tensor): + boxes_xywh = boxes_xyxy.clone() + elif isinstance(boxes_xyxy, np.ndarray): + boxes_xywh = boxes_xyxy.copy() + + boxes_xywh[:, 0] = (boxes_xyxy[:, 0] + boxes_xyxy[:, 2]) / 2. + boxes_xywh[:, 1] = (boxes_xyxy[:, 1] + boxes_xyxy[:, 3]) / 2. + boxes_xywh[:, 2] = boxes_xyxy[:, 2] - boxes_xyxy[:, 0] + boxes_xywh[:, 3] = boxes_xyxy[:, 3] - boxes_xyxy[:, 1] + + return boxes_xywh + diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/asserts.py b/utils/asserts.py new file mode 100644 index 0000000..59a73cc --- /dev/null +++ b/utils/asserts.py @@ -0,0 +1,13 @@ +from os import environ + + +def assert_in(file, files_to_check): + if file not in files_to_check: + raise AssertionError("{} does not exist in the list".format(str(file))) + return True + + +def assert_in_env(check_list: list): + for item in check_list: + assert_in(item, environ.keys()) + return True diff --git a/utils/draw.py b/utils/draw.py new file mode 100644 index 0000000..3366048 --- /dev/null +++ b/utils/draw.py @@ -0,0 +1,56 @@ +import numpy as np +import cv2 + +palette = (2 ** 11 - 1, 2 ** 15 - 1, 2 ** 20 - 1) + + +def compute_color_for_labels(label): + """ + Simple function that adds fixed color depending on the class + """ + color = [int((p * (label ** 2 - label + 1)) % 255) for p in palette] + return tuple(color) + + +def draw_boxes(img, output=None, count = [] , detection_id=0,Type='car',offset=(0,0) ): + track_num = len(set(count)) + if len(output) != 0: + bbox = output[:, :4] + identities = output[:, -1] + detection_id = len(identities) + for i,box in enumerate(bbox): + x1,y1,x2,y2 = [int(i) for i in box] + x1 += offset[0] + x2 += offset[0] + y1 += offset[1] + y2 += offset[1] + # box text and bar + id = int(identities[i]) if identities is not None else 0 + color = compute_color_for_labels(id) + label = '{}{:d}'.format("", id) + t_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_PLAIN, 2 , 2)[0] + cv2.rectangle(img,(x1, y1),(x2,y2),color,3) + cv2.rectangle(img,(x1, y1),(x1+t_size[0]+3,y1+t_size[1]+4), color,-1) ##填充 + cv2.putText(img,label,(x1,y1+t_size[1]+4), cv2.FONT_HERSHEY_PLAIN, 2, [255,255,255], 2) + ## 将track 跟踪数量放到图上 + else: + detection_id = 0 + puttxt_height=img.shape[0] + puttxt_width=img.shape[1] + if Type=='car': + cv2.putText(img, "Total Car: " + str(track_num), (int(20), int(120)), 0, 5e-3 * 200, (0, 255, 0), 2) + cv2.putText(img, "Current Car Counter: " + str(detection_id), (int(20), int(80)), 0, 5e-3 * 200, (0, 255, 0), 2) + else: + cv2.putText(img, "Total Person: " + str(track_num), (int(4), int(25)), 0, 1, (255, 0, 255), 2) + cv2.putText(img, "Current Person Counter: " + str(detection_id), (int(4), int(50)), 0, 1, (255, 0, 255), 2) + # cv2.putText(img, "FPS: %.2f" % (fps), (int(20), int(40)), 0, 5e-3 * 200, (0, 255, 0), 3) + return img,track_num,detection_id + + + + + + +if __name__ == '__main__': + for i in range(82): + print(compute_color_for_labels(i)) diff --git a/utils/evaluation.py b/utils/evaluation.py new file mode 100644 index 0000000..1001794 --- /dev/null +++ b/utils/evaluation.py @@ -0,0 +1,103 @@ +import os +import numpy as np +import copy +import motmetrics as mm +mm.lap.default_solver = 'lap' +from utils.io import read_results, unzip_objs + + +class Evaluator(object): + + def __init__(self, data_root, seq_name, data_type): + self.data_root = data_root + self.seq_name = seq_name + self.data_type = data_type + + self.load_annotations() + self.reset_accumulator() + + def load_annotations(self): + assert self.data_type == 'mot' + + gt_filename = os.path.join(self.data_root, self.seq_name, 'gt', 'gt.txt') + self.gt_frame_dict = read_results(gt_filename, self.data_type, is_gt=True) + self.gt_ignore_frame_dict = read_results(gt_filename, self.data_type, is_ignore=True) + + def reset_accumulator(self): + self.acc = mm.MOTAccumulator(auto_id=True) + + def eval_frame(self, frame_id, trk_tlwhs, trk_ids, rtn_events=False): + # results + trk_tlwhs = np.copy(trk_tlwhs) + trk_ids = np.copy(trk_ids) + + # gts + gt_objs = self.gt_frame_dict.get(frame_id, []) + gt_tlwhs, gt_ids = unzip_objs(gt_objs)[:2] + + # ignore boxes + ignore_objs = self.gt_ignore_frame_dict.get(frame_id, []) + ignore_tlwhs = unzip_objs(ignore_objs)[0] + + + # remove ignored results + keep = np.ones(len(trk_tlwhs), dtype=bool) + iou_distance = mm.distances.iou_matrix(ignore_tlwhs, trk_tlwhs, max_iou=0.5) + if len(iou_distance) > 0: + match_is, match_js = mm.lap.linear_sum_assignment(iou_distance) + match_is, match_js = map(lambda a: np.asarray(a, dtype=int), [match_is, match_js]) + match_ious = iou_distance[match_is, match_js] + + match_js = np.asarray(match_js, dtype=int) + match_js = match_js[np.logical_not(np.isnan(match_ious))] + keep[match_js] = False + trk_tlwhs = trk_tlwhs[keep] + trk_ids = trk_ids[keep] + + # get distance matrix + iou_distance = mm.distances.iou_matrix(gt_tlwhs, trk_tlwhs, max_iou=0.5) + + # acc + self.acc.update(gt_ids, trk_ids, iou_distance) + + if rtn_events and iou_distance.size > 0 and hasattr(self.acc, 'last_mot_events'): + events = self.acc.last_mot_events # only supported by https://github.com/longcw/py-motmetrics + else: + events = None + return events + + def eval_file(self, filename): + self.reset_accumulator() + + result_frame_dict = read_results(filename, self.data_type, is_gt=False) + frames = sorted(list(set(self.gt_frame_dict.keys()) | set(result_frame_dict.keys()))) + for frame_id in frames: + trk_objs = result_frame_dict.get(frame_id, []) + trk_tlwhs, trk_ids = unzip_objs(trk_objs)[:2] + self.eval_frame(frame_id, trk_tlwhs, trk_ids, rtn_events=False) + + return self.acc + + @staticmethod + def get_summary(accs, names, metrics=('mota', 'num_switches', 'idp', 'idr', 'idf1', 'precision', 'recall')): + names = copy.deepcopy(names) + if metrics is None: + metrics = mm.metrics.motchallenge_metrics + metrics = copy.deepcopy(metrics) + + mh = mm.metrics.create() + summary = mh.compute_many( + accs, + metrics=metrics, + names=names, + generate_overall=True + ) + + return summary + + @staticmethod + def save_summary(summary, filename): + import pandas as pd + writer = pd.ExcelWriter(filename) + summary.to_excel(writer) + writer.save() diff --git a/utils/io.py b/utils/io.py new file mode 100644 index 0000000..2dc9afd --- /dev/null +++ b/utils/io.py @@ -0,0 +1,133 @@ +import os +from typing import Dict +import numpy as np + +# from utils.log import get_logger + + +def write_results(filename, results, data_type): + if data_type == 'mot': + save_format = '{frame},{id},{x1},{y1},{w},{h},-1,-1,-1,-1\n' + elif data_type == 'kitti': + save_format = '{frame} {id} pedestrian 0 0 -10 {x1} {y1} {x2} {y2} -10 -10 -10 -1000 -1000 -1000 -10\n' + else: + raise ValueError(data_type) + + with open(filename, 'w') as f: + for frame_id, tlwhs, track_ids in results: + if data_type == 'kitti': + frame_id -= 1 + for tlwh, track_id in zip(tlwhs, track_ids): + if track_id < 0: + continue + x1, y1, w, h = tlwh + x2, y2 = x1 + w, y1 + h + line = save_format.format(frame=frame_id, id=track_id, x1=x1, y1=y1, x2=x2, y2=y2, w=w, h=h) + f.write(line) + + +# def write_results(filename, results_dict: Dict, data_type: str): +# if not filename: +# return +# path = os.path.dirname(filename) +# if not os.path.exists(path): +# os.makedirs(path) + +# if data_type in ('mot', 'mcmot', 'lab'): +# save_format = '{frame},{id},{x1},{y1},{w},{h},1,-1,-1,-1\n' +# elif data_type == 'kitti': +# save_format = '{frame} {id} pedestrian -1 -1 -10 {x1} {y1} {x2} {y2} -1 -1 -1 -1000 -1000 -1000 -10 {score}\n' +# else: +# raise ValueError(data_type) + +# with open(filename, 'w') as f: +# for frame_id, frame_data in results_dict.items(): +# if data_type == 'kitti': +# frame_id -= 1 +# for tlwh, track_id in frame_data: +# if track_id < 0: +# continue +# x1, y1, w, h = tlwh +# x2, y2 = x1 + w, y1 + h +# line = save_format.format(frame=frame_id, id=track_id, x1=x1, y1=y1, x2=x2, y2=y2, w=w, h=h, score=1.0) +# f.write(line) +# logger.info('Save results to {}'.format(filename)) + + +def read_results(filename, data_type: str, is_gt=False, is_ignore=False): + if data_type in ('mot', 'lab'): + read_fun = read_mot_results + else: + raise ValueError('Unknown data type: {}'.format(data_type)) + + return read_fun(filename, is_gt, is_ignore) + + +""" +labels={'ped', ... % 1 +'person_on_vhcl', ... % 2 +'car', ... % 3 +'bicycle', ... % 4 +'mbike', ... % 5 +'non_mot_vhcl', ... % 6 +'static_person', ... % 7 +'distractor', ... % 8 +'occluder', ... % 9 +'occluder_on_grnd', ... %10 +'occluder_full', ... % 11 +'reflection', ... % 12 +'crowd' ... % 13 +}; +""" + + +def read_mot_results(filename, is_gt, is_ignore): + valid_labels = {1} + ignore_labels = {2, 7, 8, 12} + results_dict = dict() + if os.path.isfile(filename): + with open(filename, 'r') as f: + for line in f.readlines(): + linelist = line.split(',') + if len(linelist) < 7: + continue + fid = int(linelist[0]) + if fid < 1: + continue + results_dict.setdefault(fid, list()) + + if is_gt: + if 'MOT16-' in filename or 'MOT17-' in filename: + label = int(float(linelist[7])) + mark = int(float(linelist[6])) + if mark == 0 or label not in valid_labels: + continue + score = 1 + elif is_ignore: + if 'MOT16-' in filename or 'MOT17-' in filename: + label = int(float(linelist[7])) + vis_ratio = float(linelist[8]) + if label not in ignore_labels and vis_ratio >= 0: + continue + else: + continue + score = 1 + else: + score = float(linelist[6]) + + tlwh = tuple(map(float, linelist[2:6])) + target_id = int(linelist[1]) + + results_dict[fid].append((tlwh, target_id, score)) + + return results_dict + + +def unzip_objs(objs): + if len(objs) > 0: + tlwhs, ids, scores = zip(*objs) + else: + tlwhs, ids, scores = [], [], [] + tlwhs = np.asarray(tlwhs, dtype=float).reshape(-1, 4) + + return tlwhs, ids, scores \ No newline at end of file diff --git a/utils/json_logger.py b/utils/json_logger.py new file mode 100644 index 0000000..0afd0b4 --- /dev/null +++ b/utils/json_logger.py @@ -0,0 +1,383 @@ +""" +References: + https://medium.com/analytics-vidhya/creating-a-custom-logging-mechanism-for-real-time-object-detection-using-tdd-4ca2cfcd0a2f +""" +import json +from os import makedirs +from os.path import exists, join +from datetime import datetime + + +class JsonMeta(object): + HOURS = 3 + MINUTES = 59 + SECONDS = 59 + PATH_TO_SAVE = 'LOGS' + DEFAULT_FILE_NAME = 'remaining' + + +class BaseJsonLogger(object): + """ + This is the base class that returns __dict__ of its own + it also returns the dicts of objects in the attributes that are list instances + + """ + + def dic(self): + # returns dicts of objects + out = {} + for k, v in self.__dict__.items(): + if hasattr(v, 'dic'): + out[k] = v.dic() + elif isinstance(v, list): + out[k] = self.list(v) + else: + out[k] = v + return out + + @staticmethod + def list(values): + # applies the dic method on items in the list + return [v.dic() if hasattr(v, 'dic') else v for v in values] + + +class Label(BaseJsonLogger): + """ + For each bounding box there are various categories with confidences. Label class keeps track of that information. + """ + + def __init__(self, category: str, confidence: float): + self.category = category + self.confidence = confidence + + +class Bbox(BaseJsonLogger): + """ + This module stores the information for each frame and use them in JsonParser + Attributes: + labels (list): List of label module. + top (int): + left (int): + width (int): + height (int): + + Args: + bbox_id (float): + top (int): + left (int): + width (int): + height (int): + + References: + Check Label module for better understanding. + + + """ + + def __init__(self, bbox_id, top, left, width, height): + self.labels = [] + self.bbox_id = bbox_id + self.top = top + self.left = left + self.width = width + self.height = height + + def add_label(self, category, confidence): + # adds category and confidence only if top_k is not exceeded. + self.labels.append(Label(category, confidence)) + + def labels_full(self, value): + return len(self.labels) == value + + +class Frame(BaseJsonLogger): + """ + This module stores the information for each frame and use them in JsonParser + Attributes: + timestamp (float): The elapsed time of captured frame + frame_id (int): The frame number of the captured video + bboxes (list of Bbox objects): Stores the list of bbox objects. + + References: + Check Bbox class for better information + + Args: + timestamp (float): + frame_id (int): + + """ + + def __init__(self, frame_id: int, timestamp: float = None): + self.frame_id = frame_id + self.timestamp = timestamp + self.bboxes = [] + + def add_bbox(self, bbox_id: int, top: int, left: int, width: int, height: int): + bboxes_ids = [bbox.bbox_id for bbox in self.bboxes] + if bbox_id not in bboxes_ids: + self.bboxes.append(Bbox(bbox_id, top, left, width, height)) + else: + raise ValueError("Frame with id: {} already has a Bbox with id: {}".format(self.frame_id, bbox_id)) + + def add_label_to_bbox(self, bbox_id: int, category: str, confidence: float): + bboxes = {bbox.id: bbox for bbox in self.bboxes} + if bbox_id in bboxes.keys(): + res = bboxes.get(bbox_id) + res.add_label(category, confidence) + else: + raise ValueError('the bbox with id: {} does not exists!'.format(bbox_id)) + + +class BboxToJsonLogger(BaseJsonLogger): + """ + ُ This module is designed to automate the task of logging jsons. An example json is used + to show the contents of json file shortly + Example: + { + "video_details": { + "frame_width": 1920, + "frame_height": 1080, + "frame_rate": 20, + "video_name": "/home/gpu/codes/MSD/pedestrian_2/project/public/camera1.avi" + }, + "frames": [ + { + "frame_id": 329, + "timestamp": 3365.1254 + "bboxes": [ + { + "labels": [ + { + "category": "pedestrian", + "confidence": 0.9 + } + ], + "bbox_id": 0, + "top": 1257, + "left": 138, + "width": 68, + "height": 109 + } + ] + }], + + Attributes: + frames (dict): It's a dictionary that maps each frame_id to json attributes. + video_details (dict): information about video file. + top_k_labels (int): shows the allowed number of labels + start_time (datetime object): we use it to automate the json output by time. + + Args: + top_k_labels (int): shows the allowed number of labels + + """ + + def __init__(self, top_k_labels: int = 1): + self.frames = {} + self.video_details = self.video_details = dict(frame_width=None, frame_height=None, frame_rate=None, + video_name=None) + self.top_k_labels = top_k_labels + self.start_time = datetime.now() + + def set_top_k(self, value): + self.top_k_labels = value + + def frame_exists(self, frame_id: int) -> bool: + """ + Args: + frame_id (int): + + Returns: + bool: true if frame_id is recognized + """ + return frame_id in self.frames.keys() + + def add_frame(self, frame_id: int, timestamp: float = None) -> None: + """ + Args: + frame_id (int): + timestamp (float): opencv captured frame time property + + Raises: + ValueError: if frame_id would not exist in class frames attribute + + Returns: + None + + """ + if not self.frame_exists(frame_id): + self.frames[frame_id] = Frame(frame_id, timestamp) + else: + raise ValueError("Frame id: {} already exists".format(frame_id)) + + def bbox_exists(self, frame_id: int, bbox_id: int) -> bool: + """ + Args: + frame_id: + bbox_id: + + Returns: + bool: if bbox exists in frame bboxes list + """ + bboxes = [] + if self.frame_exists(frame_id=frame_id): + bboxes = [bbox.bbox_id for bbox in self.frames[frame_id].bboxes] + return bbox_id in bboxes + + def find_bbox(self, frame_id: int, bbox_id: int): + """ + + Args: + frame_id: + bbox_id: + + Returns: + bbox_id (int): + + Raises: + ValueError: if bbox_id does not exist in the bbox list of specific frame. + """ + if not self.bbox_exists(frame_id, bbox_id): + raise ValueError("frame with id: {} does not contain bbox with id: {}".format(frame_id, bbox_id)) + bboxes = {bbox.bbox_id: bbox for bbox in self.frames[frame_id].bboxes} + return bboxes.get(bbox_id) + + def add_bbox_to_frame(self, frame_id: int, bbox_id: int, top: int, left: int, width: int, height: int) -> None: + """ + + Args: + frame_id (int): + bbox_id (int): + top (int): + left (int): + width (int): + height (int): + + Returns: + None + + Raises: + ValueError: if bbox_id already exist in frame information with frame_id + ValueError: if frame_id does not exist in frames attribute + """ + if self.frame_exists(frame_id): + frame = self.frames[frame_id] + if not self.bbox_exists(frame_id, bbox_id): + frame.add_bbox(bbox_id, top, left, width, height) + else: + raise ValueError( + "frame with frame_id: {} already contains the bbox with id: {} ".format(frame_id, bbox_id)) + else: + raise ValueError("frame with frame_id: {} does not exist".format(frame_id)) + + def add_label_to_bbox(self, frame_id: int, bbox_id: int, category: str, confidence: float): + """ + Args: + frame_id: + bbox_id: + category: + confidence: the confidence value returned from yolo detection + + Returns: + None + + Raises: + ValueError: if labels quota (top_k_labels) exceeds. + """ + bbox = self.find_bbox(frame_id, bbox_id) + if not bbox.labels_full(self.top_k_labels): + bbox.add_label(category, confidence) + else: + raise ValueError("labels in frame_id: {}, bbox_id: {} is fulled".format(frame_id, bbox_id)) + + def add_video_details(self, frame_width: int = None, frame_height: int = None, frame_rate: int = None, + video_name: str = None): + self.video_details['frame_width'] = frame_width + self.video_details['frame_height'] = frame_height + self.video_details['frame_rate'] = frame_rate + self.video_details['video_name'] = video_name + + def output(self): + output = {'video_details': self.video_details} + result = list(self.frames.values()) + output['frames'] = [item.dic() for item in result] + return output + + def json_output(self, output_name): + """ + Args: + output_name: + + Returns: + None + + Notes: + It creates the json output with `output_name` name. + """ + if not output_name.endswith('.json'): + output_name += '.json' + with open(output_name, 'w') as file: + json.dump(self.output(), file) + file.close() + + def set_start(self): + self.start_time = datetime.now() + + def schedule_output_by_time(self, output_dir=JsonMeta.PATH_TO_SAVE, hours: int = 0, minutes: int = 0, + seconds: int = 60) -> None: + """ + Notes: + Creates folder and then periodically stores the jsons on that address. + + Args: + output_dir (str): the directory where output files will be stored + hours (int): + minutes (int): + seconds (int): + + Returns: + None + + """ + end = datetime.now() + interval = 0 + interval += abs(min([hours, JsonMeta.HOURS]) * 3600) + interval += abs(min([minutes, JsonMeta.MINUTES]) * 60) + interval += abs(min([seconds, JsonMeta.SECONDS])) + diff = (end - self.start_time).seconds + + if diff > interval: + output_name = self.start_time.strftime('%Y-%m-%d %H-%M-%S') + '.json' + if not exists(output_dir): + makedirs(output_dir) + output = join(output_dir, output_name) + self.json_output(output_name=output) + self.frames = {} + self.start_time = datetime.now() + + def schedule_output_by_frames(self, frames_quota, frame_counter, output_dir=JsonMeta.PATH_TO_SAVE): + """ + saves as the number of frames quota increases higher. + :param frames_quota: + :param frame_counter: + :param output_dir: + :return: + """ + pass + + def flush(self, output_dir): + """ + Notes: + We use this function to output jsons whenever possible. + like the time that we exit the while loop of opencv. + + Args: + output_dir: + + Returns: + None + + """ + filename = self.start_time.strftime('%Y-%m-%d %H-%M-%S') + '-remaining.json' + output = join(output_dir, filename) + self.json_output(output_name=output) diff --git a/utils/log.py b/utils/log.py new file mode 100644 index 0000000..5b8c940 --- /dev/null +++ b/utils/log.py @@ -0,0 +1,17 @@ +import logging + + +def get_logger(name='root'): + formatter = logging.Formatter( + # fmt='%(asctime)s [%(levelname)s]: %(filename)s(%(funcName)s:%(lineno)s) >> %(message)s') + fmt='%(asctime)s [%(levelname)s]: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + + handler = logging.StreamHandler() + handler.setFormatter(formatter) + + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + logger.addHandler(handler) + return logger + + diff --git a/utils/parser.py b/utils/parser.py new file mode 100644 index 0000000..27fcf50 --- /dev/null +++ b/utils/parser.py @@ -0,0 +1,38 @@ +import os +import yaml +from easydict import EasyDict as edict + +class YamlParser(edict): + """ + This is yaml parser based on EasyDict. + """ + def __init__(self, cfg_dict=None, config_file=None): + if cfg_dict is None: + cfg_dict = {} + + if config_file is not None: + assert(os.path.isfile(config_file)) + with open(config_file, 'r') as fo: + cfg_dict.update(yaml.load(fo.read())) + + super(YamlParser, self).__init__(cfg_dict) + + + def merge_from_file(self, config_file): + with open(config_file, 'r') as fo: + self.update(yaml.load(fo.read())) + + + def merge_from_dict(self, config_dict): + self.update(config_dict) + + +def get_config(config_file=None): + return YamlParser(config_file=config_file) + + +if __name__ == "__main__": + cfg = YamlParser(config_file="../configs/yolov3.yaml") + cfg.merge_from_file("../configs/deep_sort.yaml") + + import ipdb; ipdb.set_trace() \ No newline at end of file diff --git a/utils/tools.py b/utils/tools.py new file mode 100644 index 0000000..965fb69 --- /dev/null +++ b/utils/tools.py @@ -0,0 +1,39 @@ +from functools import wraps +from time import time + + +def is_video(ext: str): + """ + Returns true if ext exists in + allowed_exts for video files. + + Args: + ext: + + Returns: + + """ + + allowed_exts = ('.mp4', '.webm', '.ogg', '.avi', '.wmv', '.mkv', '.3gp') + return any((ext.endswith(x) for x in allowed_exts)) + + +def tik_tok(func): + """ + keep track of time for each process. + Args: + func: + + Returns: + + """ + @wraps(func) + def _time_it(*args, **kwargs): + start = time() + try: + return func(*args, **kwargs) + finally: + end_ = time() + print("time: {:.03f}s, fps: {:.03f}".format(end_ - start, 1 / (end_ - start))) + + return _time_it diff --git a/yolov4_deepsort.py b/yolov4_deepsort.py new file mode 100644 index 0000000..20cadf1 --- /dev/null +++ b/yolov4_deepsort.py @@ -0,0 +1,141 @@ +import os +import cv2 +import time +import argparse +import torch +import warnings +import numpy as np + +from detector import build_detector,build_onnx +from deep_sort import build_tracker,build_tracker_car +from utils.draw import draw_boxes +from utils.parser import get_config +from utils.log import get_logger +from utils.io import write_results +# from threading import Thread +from dataset import LoadStreams +from detector.trt import tensorrt +import shutil + +class VideoTracker(object): + def __init__(self, cfg, args, video_path): + self.cfg = cfg + self.args = args + self.video_path = video_path + self.logger = get_logger("root") + self.cuda_ctx = None + + use_cuda = args.use_cuda and torch.cuda.is_available() + if not use_cuda: + warnings.warn("Running in cpu mode which maybe very slow!", UserWarning) + + if args.display: + cv2.namedWindow("test", cv2.WINDOW_NORMAL) + cv2.resizeWindow("test", args.display_width, args.display_height) + + if args.cam != -1: + print("Using webcam " + str(args.cam)) + self.datasets = LoadStreams(args.cam) + self.cap = cv2.VideoCapture(args.cam) + else: + self.datasets = LoadStreams(args.VIDEO_PATH) + self.cap = cv2.VideoCapture() + self.deepsort_person= build_tracker(cfg, use_cuda=use_cuda) + + def __enter__(self): #__enter__(self):当with开始运行的时候触发此方法的运行 + if isinstance(self.args.cam , int): + if self.args.cam != -1: + ret, frame = self.cap.read() + assert ret, "Error: Camera error" + self.im_width = frame.shape[0] + self.im_height = frame.shape[1] + else: + assert os.path.isfile(self.video_path), "Path error" + self.cap.open(self.video_path) + self.im_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + self.im_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + assert self.cap.isOpened() + elif isinstance(self.args.cam , str): + self.cap.open(self.args.cam) + self.im_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + self.im_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + assert self.cap.isOpened() + + + if self.args.save_path: + os.makedirs(self.args.save_path, exist_ok=True) + # path of saved video and results + self.save_video_path = os.path.join(self.args.save_path, "results.avi") + self.save_results_path = os.path.join(self.args.save_path, "results.txt") + # create video writer + fourcc = cv2.VideoWriter_fourcc(*'MJPG') + self.writer = cv2.VideoWriter(self.save_video_path, fourcc, 20, (self.im_width, self.im_height)) + # logging + self.logger.info("Save results to {}".format(self.args.save_path)) + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + if exc_type: + print(exc_type, exc_value, exc_traceback) + + def run(self): + count_P = [] + trt_person= tensorrt(self.cfg,[416,416]) + + while self.cap.grab(): + ##socket + for _, im0s, _ in self.datasets: ##dataset 进行了__next__ 方法 + start = time.time() + # _, ori_im = self.cap.retrieve() + ori_im = im0s[0] + im = cv2.cvtColor(ori_im, cv2.COLOR_BGR2RGB) + # bbox_xywh, cls_conf, cls_ids = self.session.forward(ori_im,self.im_width,self.im_height) + ##这里非常重要, context , buffer 全部应该在一个线程内实现, + bbox_xywh, cls_conf, cls_ids = trt_person.detect(trt_person.context , trt_person.buffers,ori_im,self.im_width,self.im_height) + # select person class TODO + class_det_P = [0] + save_id = [] + for i, id in enumerate(cls_ids): + if id not in class_det_P: + save_id.append(i) + ##numpy array 进行delete + bbox_xywh_P = np.delete(bbox_xywh , [save_id],axis=0) + cls_conf_P = np.delete(cls_conf ,[save_id]) + outputs_P ,count_num_P,detection_id_P= self.deepsort_person.update(bbox_xywh_P, cls_conf_P, im,count_P) + + # if len(outputs_P) > 0: + ori_im,track_num,detection_id = draw_boxes(ori_im, outputs_P,count_num_P,detection_id_P,Type='person' ) + + end = time.time() + fps = 1 / (end - start) + cv2.putText(ori_im, "FPS: %.2f" % (fps), (int(1050), int(200)), 0, 10e-3 * 200, (0, 255, 0), 2) + + if self.args.display: + cv2.imshow("test", ori_im) + wirte_img = cv2.resize(ori_im,(800,600)) + cv2.waitKey(10) + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--VIDEO_PATH", type=str,default='MOT16-03.mp4') + parser.add_argument("--config_detection", type=str, default="./configs/yolov4_trt.yaml") + parser.add_argument("--config_deepsort", type=str, default="./configs/deep_sort.yaml") + parser.add_argument("--ignore_display", dest="display", action="store_false", default=True) + parser.add_argument("--display", action="store_true",default=True) + parser.add_argument("--frame_interval", type=int, default=2) + parser.add_argument("--display_width", type=int, default=800) + parser.add_argument("--display_height", type=int, default=600) + parser.add_argument("--save_path", type=str, default="./output/") + parser.add_argument("--cpu", dest="use_cuda", action="store_false", default=True) + parser.add_argument("--camera", action="store", dest="cam", type=int,default='-1') + # parser.add_argument("--camera", action="store", dest="cam", type=str, default="rtsp://admin:abc12345@192.168.1.64/ch2/main/av_stream") + return parser.parse_args() + +if __name__ == "__main__": + args = parse_args() + cfg = get_config() + cfg.merge_from_file(args.config_detection) + cfg.merge_from_file(args.config_deepsort) + + with VideoTracker(cfg, args, video_path=args.VIDEO_PATH) as vdo_trk: + vdo_trk.run()