├── .gitignore ├── Dockerfile ├── GarbageImageClassifier.py ├── LICENSE ├── README.md ├── cfg ├── garb.data ├── garb.names ├── garb_test.txt ├── garb_train.txt ├── garb_trainval.txt ├── yolov3_garb.cfg └── yolov3_garb_test.cfg ├── create_yolo_label_files_from_cvat_xmls.py ├── darknet.py ├── data └── garb_test.shapes ├── demo ├── demo_1.png ├── garb_demo_3.gif └── garb_demo_4.gif ├── detector_garb.py ├── docker-compose.yml ├── garbage_detection.py ├── garbage_detection_example.ipynb ├── loss.png ├── models.py ├── output └── input5_frame281.jpg ├── pallete ├── requirements.txt ├── samples ├── input5_frame0.jpg ├── input5_frame11.jpg ├── input5_frame123.jpg ├── input5_frame154.jpg ├── input5_frame186.jpg ├── input5_frame220.jpg ├── input5_frame249.jpg ├── input5_frame25.jpg ├── input5_frame273.jpg ├── input5_frame299.jpg ├── input5_frame41.jpg ├── input5_frame62.jpg └── input5_frame92.jpg ├── test.py ├── test_batch0.jpg ├── util.py └── utils ├── adabound.py ├── datasets.py ├── gcp.sh ├── google_utils.py ├── init.py ├── parse_config.py ├── torch_utils.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | env 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | data/garb.weights 108 | data/images 109 | data/labels 110 | weights 111 | detection 112 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-slim 2 | 3 | 4 | ADD requirements.txt /app/ 5 | WORKDIR /app 6 | RUN pip install --no-cache-dir -r requirements.txt 7 | 8 | COPY . /app 9 | WORKDIR /app 10 | 11 | RUN apt-get update 12 | RUN apt-get install -y libglib2.0-0 libsm6 libxext6 libxrender-dev 13 | 14 | cmd python detector_garb.py -i samples -o output --no-show -------------------------------------------------------------------------------- /GarbageImageClassifier.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import logging 4 | import sys 5 | import torch 6 | 7 | 8 | from darknet import Darknet 9 | 10 | 11 | class GarbageImageClassifier: 12 | """ 13 | Classification models 14 | 15 | Image to json output with detected objects 16 | """ 17 | 18 | def __init__(self): 19 | 20 | curScriptPath = os.path.dirname(os.path.abspath(__file__)) # needed to keep track of the current location of current script ( although it is included somewhere else ) 21 | 22 | parser = argparse.ArgumentParser(description='YOLOv3 object detection') 23 | parser.add_argument('-i', '--input', required=True, help='input image or directory or video') 24 | parser.add_argument('-t', '--obj-thresh', type=float, default=0.5, help='objectness threshold, DEFAULT: 0.5') 25 | parser.add_argument('-n', '--nms-thresh', type=float, default=0.4, help='non max suppression threshold, DEFAULT: 0.4') 26 | parser.add_argument('-o', '--outdir', default='detection', help='output directory, DEFAULT: detection/') 27 | parser.add_argument('-v', '--video', action='store_true', default=False, help='flag for detecting a video input') 28 | parser.add_argument('-w', '--webcam', action='store_true', default=False, help='flag for detecting from webcam. Specify webcam ID in the input. usually 0 for a single webcam connected') 29 | parser.add_argument('--cuda', action='store_true', default=False, help='flag for running on GPU') 30 | parser.add_argument('--no-show', action='store_true', default=False, help='do not show the detected video in real time') 31 | 32 | self.args = parser.parse_args() 33 | 34 | if self.args.cuda and not torch.cuda.is_available(): 35 | print("ERROR: cuda is not available, try running on CPU") 36 | sys.exit(1) 37 | 38 | print('Loading network...') 39 | self.model = Darknet(curScriptPath + "/cfg/yolov3_garb_test.cfg") 40 | self.model.load_weights(curScriptPath + '/weights/garb.weights') 41 | 42 | if self.args.cuda: 43 | self.model.cuda() 44 | 45 | self.model.eval() 46 | print('Network loaded') 47 | 48 | self.createLogger() 49 | self.logger.info("GarbageImageClassifier: Init") 50 | 51 | # ---- 52 | 53 | def createLogger(self): 54 | 55 | self.logger = logging.getLogger(__name__) 56 | self.logger.setLevel(level=logging.INFO) # SETTING: log level 57 | 58 | # logger handlers 59 | handler = logging.StreamHandler() 60 | # handler.setLevel(logging.DEBUG) 61 | formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)-4s %(message)s') 62 | handler.setFormatter(formatter) 63 | self.logger.addHandler(handler) 64 | 65 | def detect_image(self,path): 66 | 67 | print('Loading input image(s)...') 68 | input_size = [int(model.net_info['height']), int(model.net_info['width'])] 69 | batch_size = int(model.net_info['batch']) 70 | 71 | imlist, imgs = load_images(args.input) 72 | print('Input image(s) loaded') 73 | 74 | img_batches = create_batches(imgs, batch_size) 75 | 76 | # load colors and classes 77 | colors = pkl.load(open("pallete", "rb")) 78 | classes = load_classes("cfg/garb.names") 79 | 80 | if not osp.exists(args.outdir): 81 | os.makedirs(args.outdir) 82 | 83 | start_time = datetime.now() 84 | print('Detecting...') 85 | 86 | for batchi, img_batch in tqdm(enumerate(img_batches)): 87 | img_tensors = [cv_image2tensor(img, input_size) for img in img_batch] 88 | img_tensors = torch.stack(img_tensors) 89 | img_tensors = Variable(img_tensors) 90 | if args.cuda: 91 | img_tensors = img_tensors.cuda() 92 | detections = model(img_tensors, args.cuda).cpu() 93 | detections = process_result(detections, args.obj_thresh, args.nms_thresh) 94 | if len(detections) == 0: 95 | continue 96 | 97 | detections = transform_result(detections, img_batch, input_size) 98 | 99 | for detection in detections: 100 | draw_bbox(img_batch, detection, colors, classes,0,args.outdir) 101 | 102 | for i, img in enumerate(img_batch): 103 | save_path = osp.join(args.outdir, osp.basename(imlist[batchi*batch_size + i])) 104 | cv2.imwrite(save_path, img) 105 | print(save_path, 'saved') 106 | 107 | end_time = datetime.now() 108 | print('Detection finished in %s' % (end_time - start_time)) 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Maarten Sukel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## New version with yolov5: https://gitlab.com/odk/odk-frame-analyzer 2 | 3 | ![Demo 2](https://github.com/maartensukel/yolov3-pytorch-garbage-detection/raw/master/demo/garb_demo_3.gif) 4 | 5 | # Urban object detection using PyTorch and YoloV3 6 | 7 | For more information, look at [this](https://medium.com/maarten-sukel/garbage-object-detection-using-pytorch-and-yolov3-d6c4e0424a10) medium post. 8 | 9 | PyTorch implementation of an urban object detection model. This repository contains all code for predicting/detecting and evaulating the model. 10 | 11 | This repository combines elements from: 12 | * https://github.com/zhaoyanglijoey/yolov3 13 | * https://github.com/ultralytics/yolov3 14 | 15 | ![Demo 1](https://github.com/maartensukel/yolov3-pytorch-garbage-detection/raw/master/demo/demo_1.png) 16 | 17 | 18 | ## Installation 19 | 20 | To install all required libaries: 21 | ``` 22 | pip install -r requirements.txt 23 | ``` 24 | 25 | ## Predictions 26 | 27 | Several different weights and configs are available at: https://drive.google.com/open?id=1DjeNxdaF7AW3Nu54_3oRw_1SeYJtOvNL. Some also have the testing data. 28 | 29 | ### Pre trained weights 30 | 31 | | Name | Classes | Test data | 32 | | ------------- |:-------------:| -----:| 33 | | 3 classes| cardboard, garbage_bags and containers| Yes | 34 | | cigarettes | cigarette | Yes| 35 | | 12 classes| container_small, garbage_bag, cardboard, matras, christmas_tree, graffiti, pole, face_privacy_filter and license_plate_privacy_filter, construction_toilet, construction_container, construction_shed | No| 36 | 37 | 38 | ### Run predictions 39 | To run predictions, download the cfg and weights from https://drive.google.com/open?id=1DjeNxdaF7AW3Nu54_3oRw_1SeYJtOvNL and put them in the correct folders. 40 | 41 | Then for example run the following the make a prediction on a file using CPU: 42 | 43 | ``` 44 | python detector_garb.py -i samples/input5_frame11.jpg -o output 45 | ``` 46 | 47 | Or to realtime detect on your webcam using GPU: (CUDA must be installed) 48 | ``` 49 | python detector_garb.py -i 0 --webcam --video -o ./webcam_output/ --cuda 50 | ``` 51 | 52 | ### Docker 53 | 54 | To run code in docker 55 | ``` 56 | docker-compose build 57 | docker-compose up 58 | ``` 59 | 60 | ## Test 61 | 62 | For testing download data from: 63 | https://drive.google.com/drive/folders/1DjeNxdaF7AW3Nu54_3oRw_1SeYJtOvNL 64 | 65 | The garbage bags, containers and cardboard dataset contains 804 images and label files. A smaller dataset with annotations of cigarettes is also available. 66 | 67 | To run test execute the following code: 68 | 69 | ``` 70 | python test.py 71 | ``` 72 | 73 | | Class | Images | Targets | P | R | mAP | F1 | 74 | |-----------------|--------|---------|-------|-------|-------|-------| 75 | | all | 115 | 579 | 0.242 | 0.941 | 0.875 | 0.376 | 76 | | container_small | 115 | 180 | 0.38 | 0.989 | 0.979 | 0.549 | 77 | | garbage_bag | 115 | 223 | 0.212 | 0.964 | 0.875 | 0.348 | 78 | | cardboard | 115 | 176 | 0.122 | 0.869 | 0.77 | 0.231 | 79 | 80 | 81 | 82 | ![test_example](https://github.com/maartensukel/yolov3-pytorch-garbage-detection/raw/master/test_batch0.jpg) 83 | 84 | The model with 12 classes has been trained on a larger collection. The test results are below. 85 | 86 | | Class | Images | Targets | P | R | mAP | F1 | 87 | |------------------------------|--------|---------|-------|-------|-------|-------| 88 | | all | 179 | 706 | 0.263 | 0.873 | 0.811 | 0.392 | 89 | | container_small | 179 | 142 | 0.521 | 0.972 | 0.97 | 0.678 | 90 | | garbage_bag | 179 | 114 | 0.266 | 0.965 | 0.936 | 0.417 | 91 | | cardboard | 179 | 78 | 0.132 | 0.962 | 0.89 | 0.232 | 92 | | matras | 179 | 8 | 0.467 | 0.875 | 0.875 | 0.609 | 93 | | kerstboom | 179 | 10 | 0.278 | 1 | 1 | 0.435 | 94 | | graffiti | 179 | 73 | 0.185 | 0.932 | 0.885 | 0.308 | 95 | | amsterdammertje | 179 | 52 | 0.325 | 0.942 | 0.911 | 0.483 | 96 | | face_privacy_filter | 179 | 87 | 0.139 | 0.782 | 0.599 | 0.237 | 97 | | license_plate_privacy_filter | 179 | 103 | 0.186 | 0.845 | 0.713 | 0.304 | 98 | | construction_toilet | 179 | 7 | 0.211 | 0.571 | 0.524 | 0.308 | 99 | | construction_container | 179 | 21 | 0.173 | 0.905 | 0.842 | 0.29 | 100 | 101 | ## Training 102 | For training a new model look at: 103 | 104 | https://github.com/maartensukel/yolov3-garbage-object-detection-training 105 | 106 | This is the training loss of 1600 images with 12 classes: 107 | ![test_example](https://github.com/maartensukel/yolov3-pytorch-garbage-detection/raw/master/loss.png) 108 | -------------------------------------------------------------------------------- /cfg/garb.data: -------------------------------------------------------------------------------- 1 | classes=3 2 | train=./cfg/garb_train.txt 3 | valid=./cfg/garb_test.txt 4 | names=./cfg/garb.names 5 | backup=backup/ 6 | eval=coco 7 | -------------------------------------------------------------------------------- /cfg/garb.names: -------------------------------------------------------------------------------- 1 | container_small 2 | garbage_bag 3 | cardboard -------------------------------------------------------------------------------- /cfg/garb_test.txt: -------------------------------------------------------------------------------- 1 | ./data/images/mdc_photo_mdc_0708c0_cdv_photo_001_1490465629098.jpg 2 | ./data/images/mdc_photo_cdv_photo_007_1495923905972.jpg 3 | ./data/images/mdc_photo_cdv_photo_006_1470354707443.jpg 4 | ./data/images/input5_frame190.jpg 5 | ./data/images/mdc_photo_cdv_photo_006_1463255849844.jpg 6 | ./data/images/mdc_photo_cdv_photo_067_1462905709484.jpg 7 | ./data/images/mdc_photo_cdv_photo_001_1501100954743.jpg 8 | ./data/images/20190917_080611_frame500.jpg 9 | ./data/images/input5_frame283.jpg 10 | ./data/images/mdc_photo_cdv_photo_005_1468711128578.jpg 11 | ./data/images/mdc_photo_mdc_0ce08a_cdv_photo_001_1463679211471.jpg 12 | ./data/images/mdc_photo_mdc_b56d20_cdv_photo_001_1464285149331.jpg 13 | ./data/images/20190917_080611_frame420.jpg 14 | ./data/images/mdc_photo_cdv_photo_009_1498077179174.jpg 15 | ./data/images/20190917_090047_frame60.jpg 16 | ./data/images/mdc_photo_cdv_photo_007_1490207464703.jpg 17 | ./data/images/mdc_photo_cdv_photo_012_1471374803278.jpg 18 | ./data/images/input17_frame1185.jpg 19 | ./data/images/mdc_photo_cdv_photo_006_1501440507366.jpg 20 | ./data/images/mdc_photo_mdc_b39cfa_cdv_photo_001_1465928633557.jpg 21 | ./data/images/mdc_photo_cdv_photo_006_1503428316255.jpg 22 | ./data/images/mdc_photo_cdv_photo_010_1493495058510.jpg 23 | ./data/images/20190917_084749[2]_frame3440.jpg 24 | ./data/images/mdc_photo_cdv_photo_012_1461436821877.jpg 25 | ./data/images/mdc_photo_cdv_photo_009_1505759360938.jpg 26 | ./data/images/mdc_photo_cdv_photo_003_1470337032107.jpg 27 | ./data/images/20190917_075532_frame230.jpg 28 | ./data/images/mdc_photo_cdv_photo_010_1464462961538.jpg 29 | ./data/images/mdc_photo_cdv_photo_003_1473794087309.jpg 30 | ./data/images/20190917_090047_frame460.jpg 31 | ./data/images/20190917_081616_frame150.jpg 32 | ./data/images/mdc_photo_cdv_photo_007_1472756503953.jpg 33 | ./data/images/20190917_080611_frame380.jpg 34 | ./data/images/mdc_photo_cdv_photo_012_1470337809699.jpg 35 | ./data/images/input17_frame600.jpg 36 | ./data/images/input17_frame1035.jpg 37 | ./data/images/mdc_photo_mdc_67b8ba_cdv_photo_001_1501170743249.jpg 38 | ./data/images/mdc_photo_cdv_photo_136_1505850332236.jpg 39 | ./data/images/20190917_083728_frame130.jpg 40 | ./data/images/mdc_photo_mdc_ffd7ab_cdv_photo_002_1508949680691.jpg 41 | ./data/images/20190917_084749[2]_frame3720.jpg 42 | ./data/images/mdc_photo_cdv_photo_003_1471975960410.jpg 43 | ./data/images/mdc_photo_cdv_photo_010_1469567002076.jpg 44 | ./data/images/mdc_photo_mdc_117aa0_cdv_photo_001_1462468793719.jpg 45 | ./data/images/mdc_photo_mdc_213100_cdv_photo_001_1470330668717.jpg 46 | ./data/images/20190917_090047_frame450.jpg 47 | ./data/images/mdc_photo_cdv_photo_270_1464290002067.jpg 48 | ./data/images/20190917_083728_frame140.jpg 49 | ./data/images/20190917_080123_frame4470.jpg 50 | ./data/images/mdc_photo_cdv_photo_038_1473795927582.jpg 51 | ./data/images/mdc_photo_cdv_photo_001_1473966047995.jpg 52 | ./data/images/mdc_photo_cdv_photo_004_1472756357160.jpg 53 | ./data/images/mdc_photo_mdc_d74670_cdv_photo_014_1493577479197.jpg 54 | ./data/images/mdc_photo_cdv_photo_003_1465673706618.jpg 55 | ./data/images/mdc_photo_cdv_photo_154_1505934106804.jpg 56 | ./data/images/mdc_photo_cdv_photo_004_1463520381480.jpg 57 | ./data/images/20190917_084749[2]_frame3700.jpg 58 | ./data/images/mdc_photo_cdv_photo_001_1468167640069.jpg 59 | ./data/images/mdc_photo_cdv_photo_008_1473190743614.jpg 60 | ./data/images/mdc_photo_cdv_photo_005_1495744672457.jpg 61 | ./data/images/mdc_photo_cdv_photo_007_1472926540309.jpg 62 | ./data/images/20190917_080611_frame750.jpg 63 | ./data/images/mdc_photo_mdc_bd8d8a_cdv_photo_001_1467134287115.jpg 64 | ./data/images/mdc_photo_cdv_photo_010_1473533657193.jpg 65 | ./data/images/mdc_photo_mdc_0cbc90_cdv_photo_001_1493317645401.jpg 66 | ./data/images/mdc_photo_cdv_photo_003_1459870883316.jpg 67 | ./data/images/20190917_085838_frame40.jpg 68 | ./data/images/mdc_photo_mdc_80b680_cdv_photo_001_1470157369839.jpg 69 | ./data/images/20190917_090047_frame210.jpg 70 | ./data/images/mdc_photo_mdc_1bbd80_cdv_photo_001_1471542348053.jpg 71 | ./data/images/mdc_photo_cdv_photo_002_1468167676384.jpg 72 | ./data/images/mdc_photo_mdc_1a04a0_cdv_photo_001_1498322562829.jpg 73 | ./data/images/input5_frame143.jpg 74 | ./data/images/input5_frame212.jpg 75 | ./data/images/input5_frame281.jpg 76 | ./data/images/20190917_090441_frame20.jpg 77 | ./data/images/mdc_photo_cdv_photo_097_1493067391001.jpg 78 | ./data/images/mdc_photo_cdv_photo_001_1501261439181.jpg 79 | ./data/images/input17_frame975.jpg 80 | ./data/images/mdc_photo_mdc_24b38a_cdv_photo_002_1469127145804.jpg 81 | ./data/images/mdc_photo_cdv_photo_009_1497988338745.jpg 82 | ./data/images/mdc_photo_cdv_photo_019_1464295288016.jpg 83 | ./data/images/mdc_photo_cdv_photo_010_1476386810507.jpg 84 | ./data/images/input5_frame179.jpg 85 | ./data/images/mdc_photo_mdc_8b820a_cdv_photo_001_1467483033710.jpg 86 | ./data/images/mdc_photo_cdv_photo_007_1493494968122.jpg 87 | ./data/images/frame90.jpg 88 | ./data/images/20190917_084749[2]_frame2900.jpg 89 | ./data/images/frame480.jpg 90 | ./data/images/mdc_photo_mdc_17ca30_cdv_photo_001_1493233970622.jpg 91 | ./data/images/mdc_photo_cdv_photo_006_1505847420352.jpg 92 | ./data/images/input5_frame160.jpg 93 | ./data/images/20190917_080123_frame2770.jpg 94 | ./data/images/mdc_photo_cdv_photo_009_1470773800735.jpg 95 | ./data/images/mdc_photo_cdv_photo_096_1474742097585.jpg 96 | ./data/images/20190917_084749[2]_frame3260.jpg 97 | ./data/images/mdc_photo_mdc_0b7d3a_cdv_photo_001_1463678912005.jpg 98 | ./data/images/mdc_photo_cdv_photo_1004_1502048720004.jpg 99 | ./data/images/mdc_photo_mdc_38c7e0_cdv_photo_001_1465326014122.jpg 100 | ./data/images/mdc_photo_mdc_861a1a_cdv_photo_002_1472750828633.jpg 101 | ./data/images/mdc_photo_cdv_photo_009_1465501055929.jpg 102 | ./data/images/mdc_photo_cdv_photo_007_1468958666706.jpg 103 | ./data/images/mdc_photo_cdv_photo_001_1463873490426.jpg 104 | ./data/images/mdc_photo_cdv_photo_001_1503693826992.jpg 105 | ./data/images/mdc_photo_mdc_4900a0_cdv_photo_001_1508776143207.jpg 106 | ./data/images/20190917_080123_frame4490.jpg 107 | ./data/images/mdc_photo_mdc_2d034a_cdv_photo_001_1465671657222.jpg 108 | ./data/images/20190917_080123_frame1580.jpg 109 | ./data/images/mdc_photo_cdv_photo_009_1460663168553.jpg 110 | ./data/images/mdc_photo_cdv_photo_244_1475779098236.jpg 111 | ./data/images/mdc_photo_mdc_185c70_cdv_photo_243_1503513410269.jpg 112 | ./data/images/mdc_photo_cdv_photo_008_1503340707483.jpg 113 | ./data/images/20190917_081616_frame120.jpg 114 | ./data/images/mdc_photo_mdc_11d5e0_cdv_photo_001_1490290889342.jpg 115 | ./data/images/mdc_photo_mdc_5dc9aa_cdv_photo_001_1463252031345.jpg 116 | ./data/images/mdc_photo_cdv_photo_1009_1519251503463.jpg 117 | ./data/images/mdc_photo_cdv_photo_009_1472756645530.jpg 118 | ./data/images/input5_frame284.jpg 119 | ./data/images/mdc_photo_cdv_photo_011_1472325607816.jpg 120 | ./data/images/20190917_075532_frame390.jpg 121 | ./data/images/input5_frame192.jpg 122 | ./data/images/input17_frame1305.jpg 123 | ./data/images/mdc_photo_cdv_photo_540_1495996005304.jpg 124 | ./data/images/input5_frame236.jpg 125 | ./data/images/mdc_photo_mdc_2531e0_cdv_photo_001_1490549450970.jpg 126 | ./data/images/mdc_photo_cdv_photo_018_1495473551026.jpg 127 | ./data/images/20190917_084749[2]_frame3690.jpg 128 | ./data/images/mdc_photo_cdv_photo_003_1463855327689.jpg 129 | ./data/images/mdc_photo_cdv_photo_019_1461093692641.jpg 130 | ./data/images/frame150.jpg 131 | ./data/images/20190917_081028_frame910.jpg 132 | ./data/images/mdc_photo_mdc_0df250_cdv_photo_003_1501344836427.jpg 133 | ./data/images/mdc_photo_cdv_photo_002_1476213918662.jpg 134 | ./data/images/mdc_photo_mdc_623250_cdv_photo_001_1468088929059.jpg 135 | ./data/images/mdc_photo_cdv_photo_005_1498247608847.jpg 136 | ./data/images/20190917_084749[2]_frame3600.jpg 137 | ./data/images/20190917_084749[2]_frame3640.jpg 138 | ./data/images/20190917_080611_frame730.jpg 139 | ./data/images/input17_frame1155.jpg 140 | ./data/images/frame30.jpg 141 | ./data/images/mdc_photo_cdv_photo_006_1501099037764.jpg 142 | ./data/images/20190917_084749[2]_frame3520.jpg 143 | ./data/images/mdc_photo_cdv_photo_001_1493494517349.jpg 144 | ./data/images/input5_frame156.jpg 145 | ./data/images/20190917_080611_frame850.jpg 146 | ./data/images/input5_frame182.jpg 147 | ./data/images/20190917_090047_frame70.jpg 148 | ./data/images/mdc_photo_cdv_photo_008_1495655654694.jpg 149 | ./data/images/mdc_photo_cdv_photo_005_1476386393658.jpg 150 | ./data/images/20190917_084749[2]_frame2790.jpg 151 | ./data/images/20190917_081616_frame80.jpg 152 | ./data/images/mdc_photo_cdv_photo_035_1498328922028.jpg 153 | ./data/images/mdc_photo_mdc_b5ca00_cdv_photo_001_1468954021245.jpg 154 | ./data/images/mdc_photo_cdv_photo_986_1502047975427.jpg 155 | ./data/images/20190917_080611_frame520.jpg 156 | ./data/images/mdc_photo_cdv_photo_008_1501099308098.jpg 157 | ./data/images/mdc_photo_cdv_photo_007_1493319269028.jpg 158 | ./data/images/mdc_photo_mdc_47e080_cdv_photo_001_1460482109572.jpg 159 | ./data/images/20190917_084749[2]_frame3150.jpg 160 | ./data/images/mdc_photo_cdv_photo_003_1460662712176.jpg 161 | ./data/images/mdc_photo_cdv_photo_002_1473360807465.jpg 162 | -------------------------------------------------------------------------------- /cfg/garb_train.txt: -------------------------------------------------------------------------------- 1 | ./data/images/mdc_photo_cdv_photo_898_1518996501266.jpg 2 | ./data/images/input5_frame276.jpg 3 | ./data/images/20190917_080123_frame1550.jpg 4 | ./data/images/frame60.jpg 5 | ./data/images/20190917_080611_frame90.jpg 6 | ./data/images/mdc_photo_cdv_photo_027_1461870838272.jpg 7 | ./data/images/20190917_080611_frame340.jpg 8 | ./data/images/input17_frame675.jpg 9 | ./data/images/mdc_photo_cdv_photo_152_1505934061624.jpg 10 | ./data/images/mdc_photo_cdv_photo_009_1473966520384.jpg 11 | ./data/images/input5_frame165.jpg 12 | ./data/images/mdc_photo_mdc_3a8160_cdv_photo_001_1498325597808.jpg 13 | ./data/images/mdc_photo_cdv_photo_1018_1503425737905.jpg 14 | ./data/images/input5_frame55.jpg 15 | ./data/images/input17_frame225.jpg 16 | ./data/images/mdc_photo_cdv_photo_001_1472929405135.jpg 17 | ./data/images/mdc_photo_mdc_2d44c0_cdv_photo_001_1490548118429.jpg 18 | ./data/images/mdc_photo_cdv_photo_008_1471735264199.jpg 19 | ./data/images/mdc_photo_mdc_64082a_cdv_photo_001_1462470851772.jpg 20 | ./data/images/mdc_photo_cdv_photo_005_1493239937572.jpg 21 | ./data/images/20190917_090047_frame330.jpg 22 | ./data/images/input5_frame95.jpg 23 | ./data/images/mdc_photo_cdv_photo_002_1503340315254.jpg 24 | ./data/images/20190917_090441_frame10.jpg 25 | ./data/images/20190917_081028_frame830.jpg 26 | ./data/images/mdc_photo_cdv_photo_004_1472929502884.jpg 27 | ./data/images/mdc_photo_cdv_photo_005_1464462654820.jpg 28 | ./data/images/mdc_photo_cdv_photo_005_1503428189146.jpg 29 | ./data/images/mdc_photo_cdv_photo_002_1493494040535.jpg 30 | ./data/images/mdc_photo_cdv_photo_008_1469736595342.jpg 31 | ./data/images/mdc_photo_cdv_photo_006_1471012168560.jpg 32 | ./data/images/mdc_photo_mdc_211710_cdv_photo_001_1495903500282.jpg 33 | ./data/images/mdc_photo_cdv_photo_003_1467751772093.jpg 34 | ./data/images/input17_frame765.jpg 35 | ./data/images/mdc_photo_mdc_3a2cca_cdv_photo_078_1503687264734.jpg 36 | ./data/images/20190917_075532_frame210.jpg 37 | ./data/images/mdc_photo_cdv_photo_005_1468167937516.jpg 38 | ./data/images/20190917_083728_frame150.jpg 39 | ./data/images/20190917_084749[2]_frame3170.jpg 40 | ./data/images/20190917_075532_frame260.jpg 41 | ./data/images/input17_frame465.jpg 42 | ./data/images/input5_frame119.jpg 43 | ./data/images/mdc_photo_cdv_photo_017_1469134155821.jpg 44 | ./data/images/mdc_photo_cdv_photo_008_1462305259568.jpg 45 | ./data/images/input17_frame750.jpg 46 | ./data/images/mdc_photo_cdv_photo_009_1471548398552.jpg 47 | ./data/images/mdc_photo_mdc_29147a_cdv_photo_001_1493137509597.jpg 48 | ./data/images/20190917_080611_frame460.jpg 49 | ./data/images/20190917_084749[2]_frame3770.jpg 50 | ./data/images/mdc_photo_cdv_photo_002_1467491472082.jpg 51 | ./data/images/mdc_photo_cdv_photo_004_1503773854063.jpg 52 | ./data/images/mdc_photo_mdc_ffcc2a_cdv_photo_001_1459273950797.jpg 53 | ./data/images/20190917_084749[2]_frame3830.jpg 54 | ./data/images/20190917_084749[2]_frame3590.jpg 55 | ./data/images/20190917_081028_frame860.jpg 56 | ./data/images/20190917_081028_frame1060.jpg 57 | ./data/images/mdc_photo_cdv_photo_002_1464723011803.jpg 58 | ./data/images/mdc_photo_mdc_11422a_cdv_photo_001_1465494606266.jpg 59 | ./data/images/mdc_photo_cdv_photo_009_1461696568694.jpg 60 | ./data/images/20190917_075532_frame1710.jpg 61 | ./data/images/20190917_085838_frame10.jpg 62 | ./data/images/input5_frame194.jpg 63 | ./data/images/mdc_photo_cdv_photo_006_1497904406548.jpg 64 | ./data/images/mdc_photo_mdc_548c8a_cdv_photo_002_1470765204853.jpg 65 | ./data/images/20190917_081028_frame130.jpg 66 | ./data/images/mdc_photo_cdv_photo_009_1505847547329.jpg 67 | ./data/images/mdc_photo_mdc_cac9da_cdv_photo_011_1493317217214.jpg 68 | ./data/images/20190917_084749[2]_frame3380.jpg 69 | ./data/images/mdc_photo_mdc_c40703_cdv_photo_008_1508861999063.jpg 70 | ./data/images/mdc_photo_mdc_0434c0_cdv_photo_001_1495816722101.jpg 71 | ./data/images/mdc_photo_cdv_photo_002_1506016173310.jpg 72 | ./data/images/mdc_photo_mdc_244a60_cdv_photo_007_1460050117454.jpg 73 | ./data/images/input5_frame97.jpg 74 | ./data/images/mdc_photo_cdv_photo_003_1470512604186.jpg 75 | ./data/images/mdc_photo_mdc_229b0a_cdv_photo_001_1462036790769.jpg 76 | ./data/images/mdc_photo_cdv_photo_001_1493582197545.jpg 77 | ./data/images/20190917_080611_frame60.jpg 78 | ./data/images/20190917_084749[2]_frame2040.jpg 79 | ./data/images/mdc_photo_cdv_photo_006_1495999367869.jpg 80 | ./data/images/input17_frame1050.jpg 81 | ./data/images/20190917_081028_frame890.jpg 82 | ./data/images/20190917_080123_frame4500.jpg 83 | ./data/images/mdc_photo_cdv_photo_005_1503814645511.jpg 84 | ./data/images/mdc_photo_cdv_photo_001_1466104390856.jpg 85 | ./data/images/20190917_081616_frame70.jpg 86 | ./data/images/mdc_photo_mdc_ff6e00_cdv_photo_001_1466530219925.jpg 87 | ./data/images/mdc_photo_cdv_photo_005_1470338796537.jpg 88 | ./data/images/mdc_photo_mdc_08426a_cdv_photo_001_1464887577837.jpg 89 | ./data/images/mdc_photo_cdv_photo_001_1500918567515.jpg 90 | ./data/images/20190917_080611_frame550.jpg 91 | ./data/images/20190917_084749[2]_frame1300.jpg 92 | ./data/images/mdc_photo_cdv_photo_021_1498325721008.jpg 93 | ./data/images/input17_frame615.jpg 94 | ./data/images/mdc_photo_cdv_photo_011_1497904837350.jpg 95 | ./data/images/20190917_080123_frame2390.jpg 96 | ./data/images/mdc_photo_cdv_photo_004_1498075968256.jpg 97 | ./data/images/mdc_photo_cdv_photo_002_1503428008153.jpg 98 | ./data/images/mdc_photo_mdc_2cedb0_cdv_photo_004_1497892861355.jpg 99 | ./data/images/mdc_photo_mdc_244aa0_cdv_photo_251_1503593946485.jpg 100 | ./data/images/mdc_photo_cdv_photo_001_1469317219725.jpg 101 | ./data/images/mdc_photo_cdv_photo_001_1464718478334.jpg 102 | ./data/images/mdc_photo_mdc_0db3a0_cdv_photo_001_1498320762488.jpg 103 | ./data/images/input5_frame148.jpg 104 | ./data/images/20190917_084749[2]_frame1310.jpg 105 | ./data/images/20190917_075532_frame1770.jpg 106 | ./data/images/20190917_080611_frame530.jpg 107 | ./data/images/20190917_080611_frame450.jpg 108 | ./data/images/input17_frame705.jpg 109 | ./data/images/mdc_photo_cdv_photo_002_1501357223092.jpg 110 | ./data/images/input5_frame196.jpg 111 | ./data/images/frame0.jpg 112 | ./data/images/mdc_photo_mdc_196b8a_cdv_photo_003_1467141591169.jpg 113 | ./data/images/20190917_085838_frame20.jpg 114 | ./data/images/mdc_photo_cdv_photo_009_1475956043395.jpg 115 | ./data/images/mdc_photo_cdv_photo_010_1465934198271.jpg 116 | ./data/images/20190917_080611_frame10.jpg 117 | ./data/images/20190917_084749[2]_frame3550.jpg 118 | ./data/images/mdc_photo_cdv_photo_014_1506113694565.jpg 119 | ./data/images/20190917_084749[2]_frame3660.jpg 120 | ./data/images/mdc_photo_cdv_photo_004_1469133261414.jpg 121 | ./data/images/20190917_090047_frame480.jpg 122 | ./data/images/input5_frame218.jpg 123 | ./data/images/mdc_photo_cdv_photo_008_1501274375695.jpg 124 | ./data/images/20190917_080611_frame360.jpg 125 | ./data/images/input17_frame690.jpg 126 | ./data/images/mdc_photo_cdv_photo_240_1475778848400.jpg 127 | ./data/images/mdc_photo_mdc_079bea_cdv_photo_001_1464109464701.jpg 128 | ./data/images/mdc_photo_mdc_09500a_cdv_photo_001_1468953412529.jpg 129 | ./data/images/input5_frame108.jpg 130 | ./data/images/mdc_photo_cdv_photo_001_1495482439278.jpg 131 | ./data/images/mdc_photo_cdv_photo_007_1501440561003.jpg 132 | ./data/images/20190917_084749[2]_frame3010.jpg 133 | ./data/images/20190917_081616_frame60.jpg 134 | ./data/images/mdc_photo_mdc_c48db0_cdv_photo_001_1508861776523.jpg 135 | ./data/images/mdc_photo_cdv_photo_009_1463255932647.jpg 136 | ./data/images/mdc_photo_mdc_4bfaaa_cdv_photo_001_1476550827195.jpg 137 | ./data/images/20190917_080123_frame4440.jpg 138 | ./data/images/mdc_photo_cdv_photo_004_1471721662977.jpg 139 | ./data/images/mdc_photo_mdc_2c0670_cdv_photo_001_1488648246201.jpg 140 | ./data/images/20190917_080611_frame510.jpg 141 | ./data/images/mdc_photo_mdc_3bd090_cdv_photo_001_1465922220155.jpg 142 | ./data/images/mdc_photo_cdv_photo_007_1498076077703.jpg 143 | ./data/images/20190917_080611_frame710.jpg 144 | ./data/images/input5_frame202.jpg 145 | ./data/images/20190917_081028_frame50.jpg 146 | ./data/images/mdc_photo_mdc_4abd80_cdv_photo_001_1463251083405.jpg 147 | ./data/images/mdc_photo_cdv_photo_002_1475780748252.jpg 148 | ./data/images/mdc_photo_cdv_photo_015_1462908078586.jpg 149 | ./data/images/mdc_photo_mdc_1a2d6a_cdv_photo_001_1465931148435.jpg 150 | ./data/images/20190917_075532_frame1760.jpg 151 | ./data/images/mdc_photo_cdv_photo_009_1506280679670.jpg 152 | ./data/images/input5_frame37.jpg 153 | ./data/images/20190917_080611_frame660.jpg 154 | ./data/images/frame240.jpg 155 | ./data/images/mdc_photo_cdv_photo_007_1466276879108.jpg 156 | ./data/images/mdc_photo_cdv_photo_003_1509206257754.jpg 157 | ./data/images/mdc_photo_cdv_photo_214_1463859363752.jpg 158 | ./data/images/mdc_photo_mdc_1fcb8a_cdv_photo_004_1493225353072.jpg 159 | ./data/images/mdc_photo_cdv_photo_006_1475006555860.jpg 160 | ./data/images/mdc_photo_cdv_photo_005_1469566685415.jpg 161 | ./data/images/mdc_photo_cdv_photo_001_1493404146465.jpg 162 | ./data/images/mdc_photo_cdv_photo_142_1463527681218.jpg 163 | ./data/images/20190917_084749[2]_frame2870.jpg 164 | ./data/images/mdc_photo_mdc_3d672a_cdv_photo_001_1464112820398.jpg 165 | ./data/images/input5_frame137.jpg 166 | ./data/images/mdc_photo_mdc_7e2fd0_cdv_photo_001_1467306568584.jpg 167 | ./data/images/mdc_photo_cdv_photo_014_1464115797262.jpg 168 | ./data/images/20190917_081028_frame850.jpg 169 | ./data/images/20190917_081616_frame90.jpg 170 | ./data/images/mdc_photo_cdv_photo_005_1461093104296.jpg 171 | ./data/images/20190917_080611_frame570.jpg 172 | ./data/images/mdc_photo_cdv_photo_009_1475953550119.jpg 173 | ./data/images/input17_frame450.jpg 174 | ./data/images/20190917_081028_frame270.jpg 175 | ./data/images/mdc_photo_mdc_31626a_cdv_photo_001_1460917251939.jpg 176 | ./data/images/20190917_080611_frame940.jpg 177 | ./data/images/mdc_photo_cdv_photo_101_1498164437892.jpg 178 | ./data/images/mdc_photo_cdv_photo_006_1461437182733.jpg 179 | ./data/images/mdc_photo_cdv_photo_045_1462904271356.jpg 180 | ./data/images/20190917_084749[2]_frame2640.jpg 181 | ./data/images/mdc_photo_cdv_photo_016_1467314755130.jpg 182 | ./data/images/mdc_photo_cdv_photo_005_1503341447209.jpg 183 | ./data/images/mdc_photo_mdc_2d5f8a_cdv_photo_001_1495473761944.jpg 184 | ./data/images/mdc_photo_cdv_photo_011_1468711711046.jpg 185 | ./data/images/20190917_080123_frame1900.jpg 186 | ./data/images/mdc_photo_cdv_photo_001_1503602030078.jpg 187 | ./data/images/20190917_084749[2]_frame1380.jpg 188 | ./data/images/mdc_photo_cdv_photo_008_1475608149587.jpg 189 | ./data/images/mdc_photo_cdv_photo_013_1475953780299.jpg 190 | ./data/images/input17_frame480.jpg 191 | ./data/images/20190917_083728_frame190.jpg 192 | ./data/images/20190917_081028_frame250.jpg 193 | ./data/images/20190917_081028_frame1040.jpg 194 | ./data/images/20190917_083728_frame170.jpg 195 | ./data/images/mdc_photo_cdv_photo_005_1460486515503.jpg 196 | ./data/images/mdc_photo_cdv_photo_007_1466537090847.jpg 197 | ./data/images/mdc_photo_mdc_1dd360_cdv_photo_001_1473960614920.jpg 198 | ./data/images/mdc_photo_cdv_photo_007_1505758100074.jpg 199 | ./data/images/mdc_photo_mdc_3469ba_cdv_photo_001_1462643446847.jpg 200 | ./data/images/mdc_photo_cdv_photo_008_1474137932529.jpg 201 | ./data/images/mdc_photo_cdv_photo_004_1475607835461.jpg 202 | ./data/images/mdc_photo_cdv_photo_003_1473791587191.jpg 203 | ./data/images/mdc_photo_cdv_photo_005_1503774014005.jpg 204 | ./data/images/20190917_075532_frame290.jpg 205 | ./data/images/20190917_081028_frame230.jpg 206 | ./data/images/20190917_080611_frame760.jpg 207 | ./data/images/frame330.jpg 208 | ./data/images/20190917_084749[2]_frame2660.jpg 209 | ./data/images/20190917_080611_frame680.jpg 210 | ./data/images/mdc_photo_mdc_0f3520_cdv_photo_001_1498069404714.jpg 211 | ./data/images/mdc_photo_mdc_5bb920_cdv_photo_001_1467310154888.jpg 212 | ./data/images/20190917_084749[2]_frame3030.jpg 213 | ./data/images/mdc_photo_cdv_photo_010_1501010100205.jpg 214 | ./data/images/mdc_photo_cdv_photo_053_1493314024178.jpg 215 | ./data/images/mdc_photo_cdv_photo_008_1505847513347.jpg 216 | ./data/images/mdc_photo_mdc_09451a_cdv_photo_001_1469732306225.jpg 217 | ./data/images/20190917_090047_frame470.jpg 218 | ./data/images/mdc_photo_cdv_photo_003_1468694439045.jpg 219 | ./data/images/mdc_photo_mdc_cec690_IMG_20170925_021533_1506298547194.jpg 220 | ./data/images/mdc_photo_cdv_photo_002_1503602064643.jpg 221 | ./data/images/input5_frame188.jpg 222 | ./data/images/20190917_084749[2]_frame3190.jpg 223 | ./data/images/mdc_photo_mdc_a5651a_cdv_photo_001_1475072854053.jpg 224 | ./data/images/input17_frame1170.jpg 225 | ./data/images/20190917_075532_frame310.jpg 226 | ./data/images/mdc_photo_cdv_photo_010_1470510569938.jpg 227 | ./data/images/input5_frame200.jpg 228 | ./data/images/mdc_photo_cdv_photo_001_1466708809134.jpg 229 | ./data/images/mdc_photo_cdv_photo_011_1472930069665.jpg 230 | ./data/images/20190917_090047_frame540.jpg 231 | ./data/images/mdc_photo_cdv_photo_002_1477163037145.jpg 232 | ./data/images/mdc_photo_cdv_photo_004_1503693989094.jpg 233 | ./data/images/20190917_080611_frame590.jpg 234 | ./data/images/mdc_photo_cdv_photo_010_1470169357150.jpg 235 | ./data/images/mdc_photo_cdv_photo_003_1501183798219.jpg 236 | ./data/images/mdc_photo_cdv_photo_006_1469918399683.jpg 237 | ./data/images/20190917_080611_frame260.jpg 238 | ./data/images/frame210.jpg 239 | ./data/images/mdc_photo_cdv_photo_010_1470513307339.jpg 240 | ./data/images/20190917_090441_frame40.jpg 241 | ./data/images/mdc_photo_cdv_photo_1029_1503426148051.jpg 242 | ./data/images/input17_frame630.jpg 243 | ./data/images/mdc_photo_cdv_photo_861_1501094086320.jpg 244 | ./data/images/mdc_photo_cdv_photo_004_1498160510794.jpg 245 | ./data/images/mdc_photo_cdv_photo_004_1505757861096.jpg 246 | ./data/images/20190917_080611_frame400.jpg 247 | ./data/images/input17_frame1125.jpg 248 | ./data/images/mdc_photo_mdc_388f10_cdv_photo_001_1470163564481.jpg 249 | ./data/images/20190917_083728_frame230.jpg 250 | ./data/images/mdc_photo_mdc_272aaa_cdv_photo_001_1459445435074.jpg 251 | ./data/images/mdc_photo_cdv_photo_011_1471130658070.jpg 252 | ./data/images/mdc_photo_cdv_photo_007_1497987604967.jpg 253 | ./data/images/mdc_photo_cdv_photo_004_1461436582360.jpg 254 | ./data/images/input5_frame38.jpg 255 | ./data/images/input17_frame60.jpg 256 | ./data/images/mdc_photo_cdv_photo_007_1475006587066.jpg 257 | ./data/images/input17_frame720.jpg 258 | ./data/images/20190917_080611_frame790.jpg 259 | ./data/images/mdc_photo_cdv_photo_003_1501357304191.jpg 260 | ./data/images/mdc_photo_cdv_photo_004_1470773387223.jpg 261 | ./data/images/20190917_084749[2]_frame3140.jpg 262 | ./data/images/mdc_photo_cdv_photo_006_1465673803460.jpg 263 | ./data/images/20190917_075532_frame1740.jpg 264 | ./data/images/mdc_photo_cdv_photo_520_1466284377829.jpg 265 | ./data/images/mdc_photo_cdv_photo_002_1498160372608.jpg 266 | ./data/images/20190917_084749[2]_frame1290.jpg 267 | ./data/images/mdc_photo_cdv_photo_010_1475176731313.jpg 268 | ./data/images/mdc_photo_cdv_photo_060_1490548776513.jpg 269 | ./data/images/mdc_photo_cdv_photo_005_1475780892155.jpg 270 | ./data/images/20190917_081028_frame60.jpg 271 | ./data/images/mdc_photo_cdv_photo_010_1471012368707.jpg 272 | ./data/images/mdc_photo_cdv_photo_001_1503433226131.jpg 273 | ./data/images/mdc_photo_cdv_photo_001_1462038787967.jpg 274 | ./data/images/input5_frame131.jpg 275 | ./data/images/mdc_photo_cdv_photo_012_1471978842460.jpg 276 | ./data/images/mdc_photo_cdv_photo_001_1501440312374.jpg 277 | ./data/images/input5_frame166.jpg 278 | ./data/images/input5_frame217.jpg 279 | ./data/images/mdc_photo_cdv_photo_010_1501440712968.jpg 280 | ./data/images/20190917_080611_frame670.jpg 281 | ./data/images/mdc_photo_cdv_photo_005_1474575871850.jpg 282 | ./data/images/mdc_photo_cdv_photo_016_1472151801129.jpg 283 | ./data/images/mdc_photo_cdv_photo_004_1501438916862.jpg 284 | ./data/images/20190917_084749[2]_frame3350.jpg 285 | ./data/images/20190917_080611_frame720.jpg 286 | ./data/images/input17_frame240.jpg 287 | ./data/images/mdc_photo_mdc_2397da_cdv_photo_263_1503860165393.jpg 288 | ./data/images/mdc_photo_cdv_photo_002_1471978231131.jpg 289 | ./data/images/mdc_photo_cdv_photo_006_1472929568676.jpg 290 | ./data/images/frame450.jpg 291 | ./data/images/frame300.jpg 292 | ./data/images/input17_frame960.jpg 293 | ./data/images/mdc_photo_cdv_photo_002_1469918132623.jpg 294 | ./data/images/20190917_080611_frame840.jpg 295 | ./data/images/20190917_080611_frame350.jpg 296 | ./data/images/20190917_075532_frame1720.jpg 297 | ./data/images/mdc_photo_cdv_photo_005_1469736350324.jpg 298 | ./data/images/mdc_photo_cdv_photo_015_1474399499411.jpg 299 | ./data/images/mdc_photo_cdv_photo_006_1465500908646.jpg 300 | ./data/images/mdc_photo_cdv_photo_001_1501087061982.jpg 301 | ./data/images/20190917_084749[2]_frame3470.jpg 302 | ./data/images/input17_frame435.jpg 303 | ./data/images/input17_frame1065.jpg 304 | ./data/images/mdc_photo_mdc_fff970_cdv_photo_001_1459876256701.jpg 305 | ./data/images/mdc_photo_cdv_photo_213_1463859297945.jpg 306 | ./data/images/mdc_photo_mdc_0aafe0_cdv_photo_001_1488301286561.jpg 307 | ./data/images/mdc_photo_mdc_3bcca0_cdv_photo_006_1495992120146.jpg 308 | ./data/images/frame120.jpg 309 | ./data/images/20190917_075532_frame1700.jpg 310 | ./data/images/20190917_075532_frame190.jpg 311 | ./data/images/input5_frame161.jpg 312 | ./data/images/mdc_photo_cdv_photo_003_1471011901432.jpg 313 | ./data/images/mdc_photo_mdc_bd15e0_cdv_photo_001_1464459281518.jpg 314 | ./data/images/mdc_photo_cdv_photo_021_1501441170477.jpg 315 | ./data/images/20190917_080611_frame620.jpg 316 | ./data/images/mdc_photo_cdv_photo_006_1506017653242.jpg 317 | ./data/images/20190917_084749[2]_frame3240.jpg 318 | ./data/images/20190917_090441_frame60.jpg 319 | ./data/images/mdc_photo_cdv_photo_002_1513878149431.jpg 320 | ./data/images/mdc_photo_cdv_photo_013_1462475822367.jpg 321 | ./data/images/mdc_photo_mdc_579170_cdv_photo_150_1503336201169.jpg 322 | ./data/images/20190917_081616_frame110.jpg 323 | ./data/images/input5_frame151.jpg 324 | ./data/images/mdc_photo_cdv_photo_002_1495470096736.jpg 325 | ./data/images/input5_frame26.jpg 326 | ./data/images/mdc_photo_cdv_photo_008_1505928890147.jpg 327 | ./data/images/mdc_photo_cdv_photo_003_1471548101029.jpg 328 | ./data/images/20190917_081028_frame840.jpg 329 | ./data/images/20190917_081028_frame1030.jpg 330 | ./data/images/mdc_photo_mdc_3b2e20_cdv_photo_137_1503334161309.jpg 331 | ./data/images/mdc_photo_cdv_photo_016_1469134119857.jpg 332 | ./data/images/20190917_080611_frame480.jpg 333 | ./data/images/mdc_photo_mdc_13b3a0_cdv_photo_004_1497982014060.jpg 334 | ./data/images/mdc_photo_cdv_photo_009_1467924937474.jpg 335 | ./data/images/20190917_075532_frame1730.jpg 336 | ./data/images/mdc_photo_mdc_359b20_cdv_photo_001_1463511415182.jpg 337 | ./data/images/20190917_084749[2]_frame3160.jpg 338 | ./data/images/20190917_083728_frame220.jpg 339 | ./data/images/frame390.jpg 340 | ./data/images/mdc_photo_cdv_photo_052_1490548476884.jpg 341 | ./data/images/mdc_photo_cdv_photo_001_1463855171764.jpg 342 | ./data/images/20190917_084749[2]_frame3910.jpg 343 | ./data/images/mdc_photo_mdc_213d40_cdv_photo_001_1473958913272.jpg 344 | ./data/images/mdc_photo_mdc_1fcec0_cdv_photo_003_1473527572727.jpg 345 | ./data/images/input17_frame15.jpg 346 | ./data/images/mdc_photo_cdv_photo_006_1497986047822.jpg 347 | ./data/images/20190917_075532_frame270.jpg 348 | ./data/images/mdc_photo_mdc_c416ca_cdv_photo_052_1505839901202.jpg 349 | ./data/images/mdc_photo_mdc_268420_cdv_photo_001_1470937767930.jpg 350 | ./data/images/mdc_photo_cdv_photo_001_1472926248588.jpg 351 | ./data/images/20190917_090047_frame170.jpg 352 | ./data/images/mdc_photo_cdv_photo_018_1462305624033.jpg 353 | ./data/images/mdc_photo_mdc_0fa620_cdv_photo_004_1495733779520.jpg 354 | ./data/images/mdc_photo_mdc_41b170_cdv_photo_001_1460826003474.jpg 355 | ./data/images/mdc_photo_cdv_photo_001_1471974636460.jpg 356 | ./data/images/mdc_photo_cdv_photo_001_1471721549918.jpg 357 | ./data/images/mdc_photo_cdv_photo_005_1501348489635.jpg 358 | ./data/images/input5_frame203.jpg 359 | ./data/images/mdc_photo_mdc_1a9740_cdv_photo_005_1475946528658.jpg 360 | ./data/images/mdc_photo_cdv_photo_004_1467924603127.jpg 361 | ./data/images/mdc_photo_cdv_photo_002_1468364072441.jpg 362 | ./data/images/mdc_photo_cdv_photo_006_1466104665950.jpg 363 | ./data/images/mdc_photo_cdv_photo_365_1509038797545.jpg 364 | ./data/images/mdc_photo_cdv_photo_001_1474750037087.jpg 365 | ./data/images/mdc_photo_cdv_photo_002_1467475247630.jpg 366 | ./data/images/20190917_090047_frame80.jpg 367 | ./data/images/mdc_photo_mdc_4dc30a_cdv_photo_001_1473785877777.jpg 368 | ./data/images/mdc_photo_mdc_456d20_cdv_photo_001_1464288114589.jpg 369 | ./data/images/20190917_080611_frame390.jpg 370 | ./data/images/20190917_084749[2]_frame3480.jpg 371 | ./data/images/mdc_photo_mdc_046daa_cdv_photo_001_1473786887070.jpg 372 | ./data/images/mdc_photo_cdv_photo_007_1490376473040.jpg 373 | ./data/images/mdc_photo_mdc_aec72a_cdv_photo_001_1501438307383.jpg 374 | ./data/images/mdc_photo_cdv_photo_005_1471374390713.jpg 375 | ./data/images/mdc_photo_cdv_photo_003_1503602345750.jpg 376 | ./data/images/mdc_photo_cdv_photo_005_1471129001767.jpg 377 | ./data/images/mdc_photo_cdv_photo_004_1503428110502.jpg 378 | ./data/images/mdc_photo_cdv_photo_001_1503773604314.jpg 379 | ./data/images/mdc_photo_mdc_bd2c20_cdv_photo_011_1493396034185.jpg 380 | ./data/images/mdc_photo_mdc_5a100a_cdv_photo_001_1500921852078.jpg 381 | ./data/images/20190917_083728_frame240.jpg 382 | ./data/images/20190917_084749[2]_frame3800.jpg 383 | ./data/images/mdc_photo_mdc_ffb4a0_cdv_photo_001_1506270040778.jpg 384 | ./data/images/mdc_photo_cdv_photo_844_1501020636819.jpg 385 | ./data/images/input17_frame75.jpg 386 | ./data/images/input5_frame229.jpg 387 | ./data/images/mdc_photo_mdc_b9c180_cdv_photo_001_1469731079839.jpg 388 | ./data/images/20190917_075532_frame1780.jpg 389 | ./data/images/input17_frame1080.jpg 390 | ./data/images/mdc_photo_mdc_1b2aba_IMG_20170826_224608_1503780383883.jpg 391 | ./data/images/mdc_photo_cdv_photo_020_1493057921605.jpg 392 | ./data/images/mdc_photo_cdv_photo_528_1466284936902.jpg 393 | ./data/images/20190917_084749[2]_frame3730.jpg 394 | ./data/images/20190917_075532_frame220.jpg 395 | ./data/images/mdc_photo_cdv_photo_009_1498160813610.jpg 396 | ./data/images/mdc_photo_mdc_49c6ca_cdv_photo_001_1470334245336.jpg 397 | ./data/images/mdc_photo_cdv_photo_007_1503341511086.jpg 398 | ./data/images/20190917_090441_frame80.jpg 399 | ./data/images/mdc_photo_cdv_photo_606_1498015470764.jpg 400 | ./data/images/20190917_081028_frame1110.jpg 401 | ./data/images/mdc_photo_mdc_41109a_cdv_photo_001_1469293699771.jpg 402 | ./data/images/mdc_photo_mdc_084e20_cdv_photo_001_1495643766696.jpg 403 | ./data/images/20190917_081616_frame100.jpg 404 | ./data/images/20190917_084749[2]_frame3280.jpg 405 | ./data/images/20190917_080123_frame4410.jpg 406 | ./data/images/mdc_photo_cdv_photo_007_1490289131764.jpg 407 | ./data/images/mdc_photo_mdc_07e4f0_cdv_photo_001_1495728301282.jpg 408 | ./data/images/mdc_photo_cdv_photo_005_1475176327894.jpg 409 | ./data/images/20190917_080611_frame630.jpg 410 | ./data/images/mdc_photo_cdv_photo_049_1473185406886.jpg 411 | ./data/images/mdc_photo_cdv_photo_258_1464288825630.jpg 412 | ./data/images/mdc_photo_mdc_40db3a_cdv_photo_001_1462467225964.jpg 413 | ./data/images/mdc_photo_cdv_photo_005_1466536984059.jpg 414 | ./data/images/mdc_photo_mdc_02d220_cdv_photo_001_1463852113227.jpg 415 | ./data/images/mdc_photo_cdv_photo_990_1519250881181.jpg 416 | ./data/images/mdc_photo_cdv_photo_104_1495736976395.jpg 417 | ./data/images/mdc_photo_cdv_photo_004_1472325241757.jpg 418 | ./data/images/input17_frame1095.jpg 419 | ./data/images/20190917_080611_frame640.jpg 420 | ./data/images/20190917_080611_frame300.jpg 421 | ./data/images/20190917_075532_frame300.jpg 422 | ./data/images/20190917_081028_frame810.jpg 423 | ./data/images/mdc_photo_cdv_photo_009_1462044162794.jpg 424 | ./data/images/input5_frame197.jpg 425 | ./data/images/mdc_photo_cdv_photo_473_1495825124724.jpg 426 | ./data/images/mdc_photo_mdc_0bfd20_cdv_photo_003_1519232726844.jpg 427 | ./data/images/mdc_photo_cdv_photo_002_1475607760438.jpg 428 | ./data/images/input5_frame198.jpg 429 | ./data/images/mdc_photo_cdv_photo_005_1469133320808.jpg 430 | ./data/images/mdc_photo_cdv_photo_004_1476214024799.jpg 431 | ./data/images/20190917_081616_frame130.jpg 432 | ./data/images/20190917_090047_frame120.jpg 433 | ./data/images/mdc_photo_cdv_photo_002_1463855289367.jpg 434 | ./data/images/mdc_photo_cdv_photo_001_1503601429113.jpg 435 | ./data/images/mdc_photo_mdc_509ad0_cdv_photo_003_1495472977649.jpg 436 | ./data/images/20190917_084749[2]_frame3630.jpg 437 | ./data/images/input5_frame186.jpg 438 | ./data/images/20190917_080611_frame270.jpg 439 | ./data/images/mdc_photo_cdv_photo_001_1465669399567.jpg 440 | ./data/images/mdc_photo_cdv_photo_007_1506272632050.jpg 441 | ./data/images/mdc_photo_cdv_photo_003_1462043954490.jpg 442 | ./data/images/mdc_photo_cdv_photo_004_1468167892444.jpg 443 | ./data/images/mdc_photo_cdv_photo_003_1498237968256.jpg 444 | ./data/images/mdc_photo_cdv_photo_011_1462044232676.jpg 445 | ./data/images/mdc_photo_cdv_photo_019_1460238741348.jpg 446 | ./data/images/mdc_photo_mdc_0ff9e0_cdv_photo_001_1462298824317.jpg 447 | ./data/images/mdc_photo_cdv_photo_011_1466277186000.jpg 448 | ./data/images/mdc_photo_cdv_photo_144_1463527901853.jpg 449 | ./data/images/20190917_080611_frame470.jpg 450 | ./data/images/mdc_photo_cdv_photo_005_1470510388837.jpg 451 | ./data/images/mdc_photo_cdv_photo_001_1472150869816.jpg 452 | ./data/images/input5_frame183.jpg 453 | ./data/images/mdc_photo_cdv_photo_006_1471721733968.jpg 454 | ./data/images/mdc_photo_mdc_07f460_cdv_photo_001_1500998330702.jpg 455 | ./data/images/mdc_photo_cdv_photo_016_1493068421625.jpg 456 | ./data/images/mdc_photo_mdc_5354ca_cdv_photo_001_1470160457409.jpg 457 | ./data/images/20190917_075532_frame280.jpg 458 | ./data/images/mdc_photo_cdv_photo_006_1500923740082.jpg 459 | ./data/images/mdc_photo_mdc_0ba75a_cdv_photo_002_1503766618570.jpg 460 | ./data/images/mdc_photo_cdv_photo_001_1461092973963.jpg 461 | ./data/images/mdc_photo_cdv_photo_007_1462475531257.jpg 462 | ./data/images/mdc_photo_cdv_photo_003_1461436559990.jpg 463 | ./data/images/input5_frame105.jpg 464 | ./data/images/mdc_photo_mdc_6516f0_cdv_photo_002_1459616915961.jpg 465 | ./data/images/input5_frame162.jpg 466 | ./data/images/mdc_photo_cdv_photo_009_1471374676785.jpg 467 | ./data/images/mdc_photo_cdv_photo_014_1476214755621.jpg 468 | ./data/images/20190917_080611_frame650.jpg 469 | ./data/images/20190917_081616_frame140.jpg 470 | ./data/images/input17_frame735.jpg 471 | ./data/images/mdc_photo_cdv_photo_008_1468955877020.jpg 472 | ./data/images/mdc_photo_mdc_b8ba2a_cdv_photo_001_1464111259469.jpg 473 | ./data/images/input17_frame210.jpg 474 | ./data/images/20190917_090047_frame400.jpg 475 | ./data/images/mdc_photo_mdc_025a80_cdv_photo_001_1463246389999.jpg 476 | ./data/images/20190917_083728_frame160.jpg 477 | ./data/images/20190917_075532_frame250.jpg 478 | ./data/images/20190917_083728_frame210.jpg 479 | ./data/images/mdc_photo_mdc_3848d0_cdv_photo_002_1466532730218.jpg 480 | ./data/images/mdc_photo_cdv_photo_001_1468710870527.jpg 481 | ./data/images/mdc_photo_mdc_53605a_cdv_photo_005_1498411081174.jpg 482 | ./data/images/20190917_083728_frame110.jpg 483 | ./data/images/20190917_084749[2]_frame3540.jpg 484 | ./data/images/mdc_photo_cdv_photo_005_1468364228402.jpg 485 | ./data/images/input17_frame1455.jpg 486 | ./data/images/input5_frame181.jpg 487 | ./data/images/mdc_photo_cdv_photo_031_1490380114154.jpg 488 | ./data/images/mdc_photo_mdc_1a9cf0_cdv_photo_184_1470511048614.jpg 489 | ./data/images/mdc_photo_cdv_photo_001_1463859582814.jpg 490 | ./data/images/mdc_photo_cdv_photo_001_1505934187364.jpg 491 | ./data/images/mdc_photo_mdc_187f68_1519085873479_1519085878029.jpg 492 | ./data/images/mdc_photo_cdv_photo_007_1467493800964.jpg 493 | ./data/images/frame420.jpg 494 | ./data/images/mdc_photo_cdv_photo_008_1495828105544.jpg 495 | ./data/images/20190917_083728_frame180.jpg 496 | ./data/images/mdc_photo_cdv_photo_006_1503340702791.jpg 497 | ./data/images/mdc_photo_cdv_photo_901_1501209055390.jpg 498 | ./data/images/20190917_080611_frame610.jpg 499 | ./data/images/mdc_photo_mdc_c3e5ea_cdv_photo_005_1490551299344.jpg 500 | ./data/images/20190917_080611_frame560.jpg 501 | ./data/images/mdc_photo_cdv_photo_006_1470998115459.jpg 502 | ./data/images/mdc_photo_cdv_photo_004_1465500840407.jpg 503 | ./data/images/mdc_photo_cdv_photo_014_1497989156495.jpg 504 | ./data/images/mdc_photo_cdv_photo_006_1495923836647.jpg 505 | ./data/images/mdc_photo_cdv_photo_004_1474575808189.jpg 506 | ./data/images/mdc_photo_cdv_photo_002_1501261491594.jpg 507 | ./data/images/mdc_photo_cdv_photo_011_1471721986788.jpg 508 | ./data/images/20190917_084749[2]_frame3420.jpg 509 | ./data/images/mdc_photo_cdv_photo_004_1493582432449.jpg 510 | ./data/images/20190917_084749[2]_frame2630.jpg 511 | ./data/images/mdc_photo_cdv_photo_001_1464722926276.jpg 512 | ./data/images/input17_frame780.jpg 513 | ./data/images/frame360.jpg 514 | ./data/images/mdc_photo_cdv_photo_005_1468525310742.jpg 515 | ./data/images/mdc_photo_cdv_photo_001_1465933526925.jpg 516 | ./data/images/mdc_photo_cdv_photo_001_1470510148598.jpg 517 | ./data/images/20190917_075532_frame1750.jpg 518 | ./data/images/20190917_081028_frame1090.jpg 519 | ./data/images/mdc_photo_mdc_1c55a0_cdv_photo_001_1469903958447.jpg 520 | ./data/images/input5_frame158.jpg 521 | ./data/images/mdc_photo_cdv_photo_006_1501184005281.jpg 522 | ./data/images/mdc_photo_cdv_photo_314_1495560494099.jpg 523 | ./data/images/mdc_photo_mdc_22f360_cdv_photo_009_1490378214812.jpg 524 | ./data/images/20190917_083728_frame200.jpg 525 | ./data/images/input5_frame215.jpg 526 | ./data/images/20190917_090047_frame200.jpg 527 | ./data/images/mdc_photo_cdv_photo_002_1464114822535.jpg 528 | ./data/images/20190917_080123_frame4510.jpg 529 | ./data/images/mdc_photo_cdv_photo_001_1476213873250.jpg 530 | ./data/images/mdc_photo_cdv_photo_008_1469133548506.jpg 531 | ./data/images/20190917_075532_frame1680.jpg 532 | ./data/images/mdc_photo_cdv_photo_003_1466276658629.jpg 533 | ./data/images/20190917_080611_frame600.jpg 534 | ./data/images/mdc_photo_cdv_photo_398_1464899095810.jpg 535 | ./data/images/mdc_photo_cdv_photo_011_1473794481276.jpg 536 | ./data/images/20190917_084749[2]_frame3790.jpg 537 | ./data/images/mdc_photo_mdc_2bd22a_cdv_photo_001_1468516423802.jpg 538 | ./data/images/input5_frame159.jpg 539 | ./data/images/20190917_080611_frame330.jpg 540 | ./data/images/mdc_photo_cdv_photo_011_1495656041152.jpg 541 | ./data/images/mdc_photo_cdv_photo_001_1493146864293.jpg 542 | ./data/images/20190917_080611_frame80.jpg 543 | ./data/images/mdc_photo_cdv_photo_008_1521486999975.jpg 544 | ./data/images/mdc_photo_cdv_photo_015_1501440930691.jpg 545 | ./data/images/mdc_photo_mdc_3d2a00_cdv_photo_001_1498322746008.jpg 546 | ./data/images/mdc_photo_mdc_ffaaca_cdv_photo_002_1461692915345.jpg 547 | ./data/images/mdc_photo_mdc_1f6f60_cdv_photo_001_1474392383168.jpg 548 | ./data/images/20190917_075532_frame510.jpg 549 | ./data/images/frame180.jpg 550 | ./data/images/mdc_photo_cdv_photo_008_1470337464265.jpg 551 | ./data/images/mdc_photo_cdv_photo_005_1463520417244.jpg 552 | ./data/images/20190917_090047_frame530.jpg 553 | ./data/images/input5_frame169.jpg 554 | ./data/images/input5_frame2.jpg 555 | ./data/images/mdc_photo_mdc_4c51ba_cdv_photo_001_1459446089600.jpg 556 | ./data/images/20190917_080611_frame440.jpg 557 | ./data/images/mdc_photo_mdc_17ea6a_cdv_photo_001_1490376192449.jpg 558 | ./data/images/mdc_photo_mdc_50b8a0_cdv_photo_001_1501171836099.jpg 559 | ./data/images/20190917_080123_frame2750.jpg 560 | ./data/images/20190917_080611_frame860.jpg 561 | ./data/images/mdc_photo_cdv_photo_005_1503859297387.jpg 562 | ./data/images/mdc_photo_mdc_2c3d30_IMG_20170731_012731_1501457257428.jpg 563 | ./data/images/20190917_075532_frame200.jpg 564 | ./data/images/20190917_080123_frame1930.jpg 565 | ./data/images/input5_frame280.jpg 566 | ./data/images/20190917_080611_frame580.jpg 567 | ./data/images/input5_frame163.jpg 568 | ./data/images/mdc_photo_mdc_205d80_cdv_photo_001_1466534140811.jpg 569 | ./data/images/mdc_photo_cdv_photo_004_1474750144828.jpg 570 | ./data/images/input5_frame185.jpg 571 | ./data/images/mdc_photo_cdv_photo_268_1464289767720.jpg 572 | ./data/images/mdc_photo_mdc_065d00_cdv_photo_001_1467479817089.jpg 573 | ./data/images/mdc_photo_cdv_photo_003_1469317300313.jpg 574 | ./data/images/mdc_photo_mdc_d3bd40_IMG_20170919_203449_1505846094017.jpg 575 | ./data/images/mdc_photo_cdv_photo_014_1463520939570.jpg 576 | ./data/images/mdc_photo_mdc_172a90_cdv_photo_001_1475607129416.jpg 577 | ./data/images/20190917_081028_frame770.jpg 578 | ./data/images/mdc_photo_mdc_0ae72a_cdv_photo_002_1501005086661.jpg 579 | ./data/images/mdc_photo_mdc_249570_cdv_photo_001_1508859347747.jpg 580 | ./data/images/mdc_photo_cdv_photo_001_1473360745684.jpg 581 | ./data/images/mdc_photo_cdv_photo_009_1501357775639.jpg 582 | ./data/images/frame270.jpg 583 | ./data/images/mdc_photo_cdv_photo_001_1501098821655.jpg 584 | ./data/images/mdc_photo_cdv_photo_001_1470168704271.jpg 585 | ./data/images/mdc_photo_cdv_photo_642_1498071505522.jpg 586 | ./data/images/mdc_photo_mdc_189c17_cdv_photo_001_1521497057370.jpg 587 | ./data/images/20190917_084749[2]_frame3510.jpg 588 | ./data/images/mdc_photo_cdv_photo_003_1495568300566.jpg 589 | ./data/images/mdc_photo_mdc_1abe7a_cdv_photo_059_1505840819430.jpg 590 | ./data/images/mdc_photo_cdv_photo_006_1471978467692.jpg 591 | ./data/images/20190917_081028_frame820.jpg 592 | ./data/images/mdc_photo_cdv_photo_008_1480188846695.jpg 593 | ./data/images/20190917_084749[2]_frame3610.jpg 594 | ./data/images/20190917_090441_frame30.jpg 595 | ./data/images/input5_frame282.jpg 596 | ./data/images/mdc_photo_mdc_03b44a_cdv_photo_001_1498412543648.jpg 597 | ./data/images/20190917_090047_frame180.jpg 598 | ./data/images/mdc_photo_cdv_photo_006_1503774106183.jpg 599 | ./data/images/mdc_photo_cdv_photo_007_1503639410233.jpg 600 | ./data/images/mdc_photo_cdv_photo_008_1476214356127.jpg 601 | ./data/images/20190917_075532_frame240.jpg 602 | ./data/images/mdc_photo_mdc_83e7e0_cdv_photo_005_1469728642684.jpg 603 | ./data/images/mdc_photo_cdv_photo_076_1498088916071.jpg 604 | ./data/images/20190917_075532_frame1690.jpg 605 | ./data/images/mdc_photo_cdv_photo_013_1495914764252.jpg 606 | ./data/images/20190917_080611_frame490.jpg 607 | ./data/images/20190917_090441_frame50.jpg 608 | ./data/images/input5_frame96.jpg 609 | ./data/images/input5_frame173.jpg 610 | ./data/images/mdc_photo_mdc_a422b0_cdv_photo_002_1461427138656.jpg 611 | ./data/images/20190917_084749[2]_frame3820.jpg 612 | ./data/images/20190917_084749[2]_frame3100.jpg 613 | ./data/images/mdc_photo_mdc_04ff20_cdv_photo_001_1463073402540.jpg 614 | ./data/images/mdc_photo_cdv_photo_001_1505928426904.jpg 615 | ./data/images/20190917_080611_frame700.jpg 616 | ./data/images/input5_frame144.jpg 617 | ./data/images/input17_frame1110.jpg 618 | ./data/images/mdc_photo_cdv_photo_008_1467314166046.jpg 619 | ./data/images/mdc_photo_mdc_0bc2aa_cdv_photo_003_1473358861383.jpg 620 | ./data/images/mdc_photo_cdv_photo_002_1471116139837.jpg 621 | ./data/images/mdc_photo_cdv_photo_005_1473794814397.jpg 622 | ./data/images/20190917_085838_frame30.jpg 623 | ./data/images/mdc_photo_cdv_photo_896_1518996448195.jpg 624 | ./data/images/20190917_090047_frame190.jpg 625 | ./data/images/mdc_photo_cdv_photo_013_1465934409511.jpg 626 | ./data/images/input17_frame1440.jpg 627 | ./data/images/20190917_080611_frame430.jpg 628 | ./data/images/mdc_photo_mdc_4985aa_cdv_photo_001_1464721831091.jpg 629 | ./data/images/mdc_photo_cdv_photo_007_1460663101654.jpg 630 | ./data/images/mdc_photo_mdc_2bc400_cdv_photo_001_1501005327227.jpg 631 | ./data/images/input5_frame27.jpg 632 | ./data/images/input17_frame1140.jpg 633 | ./data/images/20190917_081028_frame870.jpg 634 | ./data/images/mdc_photo_cdv_photo_009_1503774259723.jpg 635 | ./data/images/mdc_photo_cdv_photo_008_1468525748342.jpg 636 | ./data/images/mdc_photo_mdc_51a2b0_cdv_photo_001_1474135365364.jpg 637 | ./data/images/mdc_photo_cdv_photo_010_1503694624103.jpg 638 | ./data/images/mdc_photo_mdc_0315c0_cdv_photo_001_1465502991364.jpg 639 | ./data/images/mdc_photo_mdc_5bac7a_cdv_photo_002_1501006247569.jpg 640 | ./data/images/mdc_photo_cdv_photo_005_1503340493518.jpg 641 | ./data/images/20190917_090047_frame440.jpg 642 | ./data/images/input17_frame645.jpg 643 | ./data/images/mdc_photo_cdv_photo_006_1490289096737.jpg 644 | -------------------------------------------------------------------------------- /cfg/yolov3_garb.cfg: -------------------------------------------------------------------------------- 1 | [net] 2 | # Testing 3 | # batch=1 4 | # subdivisions=1 5 | # Training 6 | batch=32 7 | subdivisions=16 8 | width=416 9 | height=416 10 | channels=3 11 | momentum=0.9 12 | decay=0.0005 13 | angle=0 14 | saturation = 1.5 15 | exposure = 1.5 16 | hue=.1 17 | 18 | learning_rate=0.001 19 | burn_in=1000 20 | max_batches = 500200 21 | policy=steps 22 | steps=400000,450000 23 | scales=.1,.1 24 | 25 | [convolutional] 26 | batch_normalize=1 27 | filters=32 28 | size=3 29 | stride=1 30 | pad=1 31 | activation=leaky 32 | 33 | # Downsample 34 | 35 | [convolutional] 36 | batch_normalize=1 37 | filters=64 38 | size=3 39 | stride=2 40 | pad=1 41 | activation=leaky 42 | 43 | [convolutional] 44 | batch_normalize=1 45 | filters=32 46 | size=1 47 | stride=1 48 | pad=1 49 | activation=leaky 50 | 51 | [convolutional] 52 | batch_normalize=1 53 | filters=64 54 | size=3 55 | stride=1 56 | pad=1 57 | activation=leaky 58 | 59 | [shortcut] 60 | from=-3 61 | activation=linear 62 | 63 | # Downsample 64 | 65 | [convolutional] 66 | batch_normalize=1 67 | filters=128 68 | size=3 69 | stride=2 70 | pad=1 71 | activation=leaky 72 | 73 | [convolutional] 74 | batch_normalize=1 75 | filters=64 76 | size=1 77 | stride=1 78 | pad=1 79 | activation=leaky 80 | 81 | [convolutional] 82 | batch_normalize=1 83 | filters=128 84 | size=3 85 | stride=1 86 | pad=1 87 | activation=leaky 88 | 89 | [shortcut] 90 | from=-3 91 | activation=linear 92 | 93 | [convolutional] 94 | batch_normalize=1 95 | filters=64 96 | size=1 97 | stride=1 98 | pad=1 99 | activation=leaky 100 | 101 | [convolutional] 102 | batch_normalize=1 103 | filters=128 104 | size=3 105 | stride=1 106 | pad=1 107 | activation=leaky 108 | 109 | [shortcut] 110 | from=-3 111 | activation=linear 112 | 113 | # Downsample 114 | 115 | [convolutional] 116 | batch_normalize=1 117 | filters=256 118 | size=3 119 | stride=2 120 | pad=1 121 | activation=leaky 122 | 123 | [convolutional] 124 | batch_normalize=1 125 | filters=128 126 | size=1 127 | stride=1 128 | pad=1 129 | activation=leaky 130 | 131 | [convolutional] 132 | batch_normalize=1 133 | filters=256 134 | size=3 135 | stride=1 136 | pad=1 137 | activation=leaky 138 | 139 | [shortcut] 140 | from=-3 141 | activation=linear 142 | 143 | [convolutional] 144 | batch_normalize=1 145 | filters=128 146 | size=1 147 | stride=1 148 | pad=1 149 | activation=leaky 150 | 151 | [convolutional] 152 | batch_normalize=1 153 | filters=256 154 | size=3 155 | stride=1 156 | pad=1 157 | activation=leaky 158 | 159 | [shortcut] 160 | from=-3 161 | activation=linear 162 | 163 | [convolutional] 164 | batch_normalize=1 165 | filters=128 166 | size=1 167 | stride=1 168 | pad=1 169 | activation=leaky 170 | 171 | [convolutional] 172 | batch_normalize=1 173 | filters=256 174 | size=3 175 | stride=1 176 | pad=1 177 | activation=leaky 178 | 179 | [shortcut] 180 | from=-3 181 | activation=linear 182 | 183 | [convolutional] 184 | batch_normalize=1 185 | filters=128 186 | size=1 187 | stride=1 188 | pad=1 189 | activation=leaky 190 | 191 | [convolutional] 192 | batch_normalize=1 193 | filters=256 194 | size=3 195 | stride=1 196 | pad=1 197 | activation=leaky 198 | 199 | [shortcut] 200 | from=-3 201 | activation=linear 202 | 203 | 204 | [convolutional] 205 | batch_normalize=1 206 | filters=128 207 | size=1 208 | stride=1 209 | pad=1 210 | activation=leaky 211 | 212 | [convolutional] 213 | batch_normalize=1 214 | filters=256 215 | size=3 216 | stride=1 217 | pad=1 218 | activation=leaky 219 | 220 | [shortcut] 221 | from=-3 222 | activation=linear 223 | 224 | [convolutional] 225 | batch_normalize=1 226 | filters=128 227 | size=1 228 | stride=1 229 | pad=1 230 | activation=leaky 231 | 232 | [convolutional] 233 | batch_normalize=1 234 | filters=256 235 | size=3 236 | stride=1 237 | pad=1 238 | activation=leaky 239 | 240 | [shortcut] 241 | from=-3 242 | activation=linear 243 | 244 | [convolutional] 245 | batch_normalize=1 246 | filters=128 247 | size=1 248 | stride=1 249 | pad=1 250 | activation=leaky 251 | 252 | [convolutional] 253 | batch_normalize=1 254 | filters=256 255 | size=3 256 | stride=1 257 | pad=1 258 | activation=leaky 259 | 260 | [shortcut] 261 | from=-3 262 | activation=linear 263 | 264 | [convolutional] 265 | batch_normalize=1 266 | filters=128 267 | size=1 268 | stride=1 269 | pad=1 270 | activation=leaky 271 | 272 | [convolutional] 273 | batch_normalize=1 274 | filters=256 275 | size=3 276 | stride=1 277 | pad=1 278 | activation=leaky 279 | 280 | [shortcut] 281 | from=-3 282 | activation=linear 283 | 284 | # Downsample 285 | 286 | [convolutional] 287 | batch_normalize=1 288 | filters=512 289 | size=3 290 | stride=2 291 | pad=1 292 | activation=leaky 293 | 294 | [convolutional] 295 | batch_normalize=1 296 | filters=256 297 | size=1 298 | stride=1 299 | pad=1 300 | activation=leaky 301 | 302 | [convolutional] 303 | batch_normalize=1 304 | filters=512 305 | size=3 306 | stride=1 307 | pad=1 308 | activation=leaky 309 | 310 | [shortcut] 311 | from=-3 312 | activation=linear 313 | 314 | 315 | [convolutional] 316 | batch_normalize=1 317 | filters=256 318 | size=1 319 | stride=1 320 | pad=1 321 | activation=leaky 322 | 323 | [convolutional] 324 | batch_normalize=1 325 | filters=512 326 | size=3 327 | stride=1 328 | pad=1 329 | activation=leaky 330 | 331 | [shortcut] 332 | from=-3 333 | activation=linear 334 | 335 | 336 | [convolutional] 337 | batch_normalize=1 338 | filters=256 339 | size=1 340 | stride=1 341 | pad=1 342 | activation=leaky 343 | 344 | [convolutional] 345 | batch_normalize=1 346 | filters=512 347 | size=3 348 | stride=1 349 | pad=1 350 | activation=leaky 351 | 352 | [shortcut] 353 | from=-3 354 | activation=linear 355 | 356 | 357 | [convolutional] 358 | batch_normalize=1 359 | filters=256 360 | size=1 361 | stride=1 362 | pad=1 363 | activation=leaky 364 | 365 | [convolutional] 366 | batch_normalize=1 367 | filters=512 368 | size=3 369 | stride=1 370 | pad=1 371 | activation=leaky 372 | 373 | [shortcut] 374 | from=-3 375 | activation=linear 376 | 377 | [convolutional] 378 | batch_normalize=1 379 | filters=256 380 | size=1 381 | stride=1 382 | pad=1 383 | activation=leaky 384 | 385 | [convolutional] 386 | batch_normalize=1 387 | filters=512 388 | size=3 389 | stride=1 390 | pad=1 391 | activation=leaky 392 | 393 | [shortcut] 394 | from=-3 395 | activation=linear 396 | 397 | 398 | [convolutional] 399 | batch_normalize=1 400 | filters=256 401 | size=1 402 | stride=1 403 | pad=1 404 | activation=leaky 405 | 406 | [convolutional] 407 | batch_normalize=1 408 | filters=512 409 | size=3 410 | stride=1 411 | pad=1 412 | activation=leaky 413 | 414 | [shortcut] 415 | from=-3 416 | activation=linear 417 | 418 | 419 | [convolutional] 420 | batch_normalize=1 421 | filters=256 422 | size=1 423 | stride=1 424 | pad=1 425 | activation=leaky 426 | 427 | [convolutional] 428 | batch_normalize=1 429 | filters=512 430 | size=3 431 | stride=1 432 | pad=1 433 | activation=leaky 434 | 435 | [shortcut] 436 | from=-3 437 | activation=linear 438 | 439 | [convolutional] 440 | batch_normalize=1 441 | filters=256 442 | size=1 443 | stride=1 444 | pad=1 445 | activation=leaky 446 | 447 | [convolutional] 448 | batch_normalize=1 449 | filters=512 450 | size=3 451 | stride=1 452 | pad=1 453 | activation=leaky 454 | 455 | [shortcut] 456 | from=-3 457 | activation=linear 458 | 459 | # Downsample 460 | 461 | [convolutional] 462 | batch_normalize=1 463 | filters=1024 464 | size=3 465 | stride=2 466 | pad=1 467 | activation=leaky 468 | 469 | [convolutional] 470 | batch_normalize=1 471 | filters=512 472 | size=1 473 | stride=1 474 | pad=1 475 | activation=leaky 476 | 477 | [convolutional] 478 | batch_normalize=1 479 | filters=1024 480 | size=3 481 | stride=1 482 | pad=1 483 | activation=leaky 484 | 485 | [shortcut] 486 | from=-3 487 | activation=linear 488 | 489 | [convolutional] 490 | batch_normalize=1 491 | filters=512 492 | size=1 493 | stride=1 494 | pad=1 495 | activation=leaky 496 | 497 | [convolutional] 498 | batch_normalize=1 499 | filters=1024 500 | size=3 501 | stride=1 502 | pad=1 503 | activation=leaky 504 | 505 | [shortcut] 506 | from=-3 507 | activation=linear 508 | 509 | [convolutional] 510 | batch_normalize=1 511 | filters=512 512 | size=1 513 | stride=1 514 | pad=1 515 | activation=leaky 516 | 517 | [convolutional] 518 | batch_normalize=1 519 | filters=1024 520 | size=3 521 | stride=1 522 | pad=1 523 | activation=leaky 524 | 525 | [shortcut] 526 | from=-3 527 | activation=linear 528 | 529 | [convolutional] 530 | batch_normalize=1 531 | filters=512 532 | size=1 533 | stride=1 534 | pad=1 535 | activation=leaky 536 | 537 | [convolutional] 538 | batch_normalize=1 539 | filters=1024 540 | size=3 541 | stride=1 542 | pad=1 543 | activation=leaky 544 | 545 | [shortcut] 546 | from=-3 547 | activation=linear 548 | 549 | ###################### 550 | 551 | [convolutional] 552 | batch_normalize=1 553 | filters=512 554 | size=1 555 | stride=1 556 | pad=1 557 | activation=leaky 558 | 559 | [convolutional] 560 | batch_normalize=1 561 | size=3 562 | stride=1 563 | pad=1 564 | filters=1024 565 | activation=leaky 566 | 567 | [convolutional] 568 | batch_normalize=1 569 | filters=512 570 | size=1 571 | stride=1 572 | pad=1 573 | activation=leaky 574 | 575 | [convolutional] 576 | batch_normalize=1 577 | size=3 578 | stride=1 579 | pad=1 580 | filters=1024 581 | activation=leaky 582 | 583 | [convolutional] 584 | batch_normalize=1 585 | filters=512 586 | size=1 587 | stride=1 588 | pad=1 589 | activation=leaky 590 | 591 | [convolutional] 592 | batch_normalize=1 593 | size=3 594 | stride=1 595 | pad=1 596 | filters=1024 597 | activation=leaky 598 | 599 | [convolutional] 600 | size=1 601 | stride=1 602 | pad=1 603 | filters=24 604 | activation=linear 605 | 606 | 607 | [yolo] 608 | mask = 6,7,8 609 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 610 | classes=3 611 | num=9 612 | jitter=.3 613 | ignore_thresh = .7 614 | truth_thresh = 1 615 | random=1 616 | 617 | 618 | [route] 619 | layers = -4 620 | 621 | [convolutional] 622 | batch_normalize=1 623 | filters=256 624 | size=1 625 | stride=1 626 | pad=1 627 | activation=leaky 628 | 629 | [upsample] 630 | stride=2 631 | 632 | [route] 633 | layers = -1, 61 634 | 635 | 636 | 637 | [convolutional] 638 | batch_normalize=1 639 | filters=256 640 | size=1 641 | stride=1 642 | pad=1 643 | activation=leaky 644 | 645 | [convolutional] 646 | batch_normalize=1 647 | size=3 648 | stride=1 649 | pad=1 650 | filters=512 651 | activation=leaky 652 | 653 | [convolutional] 654 | batch_normalize=1 655 | filters=256 656 | size=1 657 | stride=1 658 | pad=1 659 | activation=leaky 660 | 661 | [convolutional] 662 | batch_normalize=1 663 | size=3 664 | stride=1 665 | pad=1 666 | filters=512 667 | activation=leaky 668 | 669 | [convolutional] 670 | batch_normalize=1 671 | filters=256 672 | size=1 673 | stride=1 674 | pad=1 675 | activation=leaky 676 | 677 | [convolutional] 678 | batch_normalize=1 679 | size=3 680 | stride=1 681 | pad=1 682 | filters=512 683 | activation=leaky 684 | 685 | [convolutional] 686 | size=1 687 | stride=1 688 | pad=1 689 | filters=24 690 | activation=linear 691 | 692 | 693 | [yolo] 694 | mask = 3,4,5 695 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 696 | classes=3 697 | num=9 698 | jitter=.3 699 | ignore_thresh = .7 700 | truth_thresh = 1 701 | random=1 702 | 703 | 704 | 705 | [route] 706 | layers = -4 707 | 708 | [convolutional] 709 | batch_normalize=1 710 | filters=128 711 | size=1 712 | stride=1 713 | pad=1 714 | activation=leaky 715 | 716 | [upsample] 717 | stride=2 718 | 719 | [route] 720 | layers = -1, 36 721 | 722 | 723 | 724 | [convolutional] 725 | batch_normalize=1 726 | filters=128 727 | size=1 728 | stride=1 729 | pad=1 730 | activation=leaky 731 | 732 | [convolutional] 733 | batch_normalize=1 734 | size=3 735 | stride=1 736 | pad=1 737 | filters=256 738 | activation=leaky 739 | 740 | [convolutional] 741 | batch_normalize=1 742 | filters=128 743 | size=1 744 | stride=1 745 | pad=1 746 | activation=leaky 747 | 748 | [convolutional] 749 | batch_normalize=1 750 | size=3 751 | stride=1 752 | pad=1 753 | filters=256 754 | activation=leaky 755 | 756 | [convolutional] 757 | batch_normalize=1 758 | filters=128 759 | size=1 760 | stride=1 761 | pad=1 762 | activation=leaky 763 | 764 | [convolutional] 765 | batch_normalize=1 766 | size=3 767 | stride=1 768 | pad=1 769 | filters=256 770 | activation=leaky 771 | 772 | [convolutional] 773 | size=1 774 | stride=1 775 | pad=1 776 | filters=24 777 | activation=linear 778 | 779 | 780 | [yolo] 781 | mask = 0,1,2 782 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 783 | classes=3 784 | num=9 785 | jitter=.3 786 | ignore_thresh = .7 787 | truth_thresh = 1 788 | random=1 789 | 790 | -------------------------------------------------------------------------------- /cfg/yolov3_garb_test.cfg: -------------------------------------------------------------------------------- 1 | [net] 2 | # Testing 3 | batch=1 4 | subdivisions=1 5 | # Training 6 | #batch=32 7 | #subdivisions=16 8 | width=416 9 | height=416 10 | channels=3 11 | momentum=0.9 12 | decay=0.0005 13 | angle=0 14 | saturation = 1.5 15 | exposure = 1.5 16 | hue=.1 17 | 18 | learning_rate=0.001 19 | burn_in=1000 20 | max_batches = 500200 21 | policy=steps 22 | steps=400000,450000 23 | scales=.1,.1 24 | 25 | [convolutional] 26 | batch_normalize=1 27 | filters=32 28 | size=3 29 | stride=1 30 | pad=1 31 | activation=leaky 32 | 33 | # Downsample 34 | 35 | [convolutional] 36 | batch_normalize=1 37 | filters=64 38 | size=3 39 | stride=2 40 | pad=1 41 | activation=leaky 42 | 43 | [convolutional] 44 | batch_normalize=1 45 | filters=32 46 | size=1 47 | stride=1 48 | pad=1 49 | activation=leaky 50 | 51 | [convolutional] 52 | batch_normalize=1 53 | filters=64 54 | size=3 55 | stride=1 56 | pad=1 57 | activation=leaky 58 | 59 | [shortcut] 60 | from=-3 61 | activation=linear 62 | 63 | # Downsample 64 | 65 | [convolutional] 66 | batch_normalize=1 67 | filters=128 68 | size=3 69 | stride=2 70 | pad=1 71 | activation=leaky 72 | 73 | [convolutional] 74 | batch_normalize=1 75 | filters=64 76 | size=1 77 | stride=1 78 | pad=1 79 | activation=leaky 80 | 81 | [convolutional] 82 | batch_normalize=1 83 | filters=128 84 | size=3 85 | stride=1 86 | pad=1 87 | activation=leaky 88 | 89 | [shortcut] 90 | from=-3 91 | activation=linear 92 | 93 | [convolutional] 94 | batch_normalize=1 95 | filters=64 96 | size=1 97 | stride=1 98 | pad=1 99 | activation=leaky 100 | 101 | [convolutional] 102 | batch_normalize=1 103 | filters=128 104 | size=3 105 | stride=1 106 | pad=1 107 | activation=leaky 108 | 109 | [shortcut] 110 | from=-3 111 | activation=linear 112 | 113 | # Downsample 114 | 115 | [convolutional] 116 | batch_normalize=1 117 | filters=256 118 | size=3 119 | stride=2 120 | pad=1 121 | activation=leaky 122 | 123 | [convolutional] 124 | batch_normalize=1 125 | filters=128 126 | size=1 127 | stride=1 128 | pad=1 129 | activation=leaky 130 | 131 | [convolutional] 132 | batch_normalize=1 133 | filters=256 134 | size=3 135 | stride=1 136 | pad=1 137 | activation=leaky 138 | 139 | [shortcut] 140 | from=-3 141 | activation=linear 142 | 143 | [convolutional] 144 | batch_normalize=1 145 | filters=128 146 | size=1 147 | stride=1 148 | pad=1 149 | activation=leaky 150 | 151 | [convolutional] 152 | batch_normalize=1 153 | filters=256 154 | size=3 155 | stride=1 156 | pad=1 157 | activation=leaky 158 | 159 | [shortcut] 160 | from=-3 161 | activation=linear 162 | 163 | [convolutional] 164 | batch_normalize=1 165 | filters=128 166 | size=1 167 | stride=1 168 | pad=1 169 | activation=leaky 170 | 171 | [convolutional] 172 | batch_normalize=1 173 | filters=256 174 | size=3 175 | stride=1 176 | pad=1 177 | activation=leaky 178 | 179 | [shortcut] 180 | from=-3 181 | activation=linear 182 | 183 | [convolutional] 184 | batch_normalize=1 185 | filters=128 186 | size=1 187 | stride=1 188 | pad=1 189 | activation=leaky 190 | 191 | [convolutional] 192 | batch_normalize=1 193 | filters=256 194 | size=3 195 | stride=1 196 | pad=1 197 | activation=leaky 198 | 199 | [shortcut] 200 | from=-3 201 | activation=linear 202 | 203 | 204 | [convolutional] 205 | batch_normalize=1 206 | filters=128 207 | size=1 208 | stride=1 209 | pad=1 210 | activation=leaky 211 | 212 | [convolutional] 213 | batch_normalize=1 214 | filters=256 215 | size=3 216 | stride=1 217 | pad=1 218 | activation=leaky 219 | 220 | [shortcut] 221 | from=-3 222 | activation=linear 223 | 224 | [convolutional] 225 | batch_normalize=1 226 | filters=128 227 | size=1 228 | stride=1 229 | pad=1 230 | activation=leaky 231 | 232 | [convolutional] 233 | batch_normalize=1 234 | filters=256 235 | size=3 236 | stride=1 237 | pad=1 238 | activation=leaky 239 | 240 | [shortcut] 241 | from=-3 242 | activation=linear 243 | 244 | [convolutional] 245 | batch_normalize=1 246 | filters=128 247 | size=1 248 | stride=1 249 | pad=1 250 | activation=leaky 251 | 252 | [convolutional] 253 | batch_normalize=1 254 | filters=256 255 | size=3 256 | stride=1 257 | pad=1 258 | activation=leaky 259 | 260 | [shortcut] 261 | from=-3 262 | activation=linear 263 | 264 | [convolutional] 265 | batch_normalize=1 266 | filters=128 267 | size=1 268 | stride=1 269 | pad=1 270 | activation=leaky 271 | 272 | [convolutional] 273 | batch_normalize=1 274 | filters=256 275 | size=3 276 | stride=1 277 | pad=1 278 | activation=leaky 279 | 280 | [shortcut] 281 | from=-3 282 | activation=linear 283 | 284 | # Downsample 285 | 286 | [convolutional] 287 | batch_normalize=1 288 | filters=512 289 | size=3 290 | stride=2 291 | pad=1 292 | activation=leaky 293 | 294 | [convolutional] 295 | batch_normalize=1 296 | filters=256 297 | size=1 298 | stride=1 299 | pad=1 300 | activation=leaky 301 | 302 | [convolutional] 303 | batch_normalize=1 304 | filters=512 305 | size=3 306 | stride=1 307 | pad=1 308 | activation=leaky 309 | 310 | [shortcut] 311 | from=-3 312 | activation=linear 313 | 314 | 315 | [convolutional] 316 | batch_normalize=1 317 | filters=256 318 | size=1 319 | stride=1 320 | pad=1 321 | activation=leaky 322 | 323 | [convolutional] 324 | batch_normalize=1 325 | filters=512 326 | size=3 327 | stride=1 328 | pad=1 329 | activation=leaky 330 | 331 | [shortcut] 332 | from=-3 333 | activation=linear 334 | 335 | 336 | [convolutional] 337 | batch_normalize=1 338 | filters=256 339 | size=1 340 | stride=1 341 | pad=1 342 | activation=leaky 343 | 344 | [convolutional] 345 | batch_normalize=1 346 | filters=512 347 | size=3 348 | stride=1 349 | pad=1 350 | activation=leaky 351 | 352 | [shortcut] 353 | from=-3 354 | activation=linear 355 | 356 | 357 | [convolutional] 358 | batch_normalize=1 359 | filters=256 360 | size=1 361 | stride=1 362 | pad=1 363 | activation=leaky 364 | 365 | [convolutional] 366 | batch_normalize=1 367 | filters=512 368 | size=3 369 | stride=1 370 | pad=1 371 | activation=leaky 372 | 373 | [shortcut] 374 | from=-3 375 | activation=linear 376 | 377 | [convolutional] 378 | batch_normalize=1 379 | filters=256 380 | size=1 381 | stride=1 382 | pad=1 383 | activation=leaky 384 | 385 | [convolutional] 386 | batch_normalize=1 387 | filters=512 388 | size=3 389 | stride=1 390 | pad=1 391 | activation=leaky 392 | 393 | [shortcut] 394 | from=-3 395 | activation=linear 396 | 397 | 398 | [convolutional] 399 | batch_normalize=1 400 | filters=256 401 | size=1 402 | stride=1 403 | pad=1 404 | activation=leaky 405 | 406 | [convolutional] 407 | batch_normalize=1 408 | filters=512 409 | size=3 410 | stride=1 411 | pad=1 412 | activation=leaky 413 | 414 | [shortcut] 415 | from=-3 416 | activation=linear 417 | 418 | 419 | [convolutional] 420 | batch_normalize=1 421 | filters=256 422 | size=1 423 | stride=1 424 | pad=1 425 | activation=leaky 426 | 427 | [convolutional] 428 | batch_normalize=1 429 | filters=512 430 | size=3 431 | stride=1 432 | pad=1 433 | activation=leaky 434 | 435 | [shortcut] 436 | from=-3 437 | activation=linear 438 | 439 | [convolutional] 440 | batch_normalize=1 441 | filters=256 442 | size=1 443 | stride=1 444 | pad=1 445 | activation=leaky 446 | 447 | [convolutional] 448 | batch_normalize=1 449 | filters=512 450 | size=3 451 | stride=1 452 | pad=1 453 | activation=leaky 454 | 455 | [shortcut] 456 | from=-3 457 | activation=linear 458 | 459 | # Downsample 460 | 461 | [convolutional] 462 | batch_normalize=1 463 | filters=1024 464 | size=3 465 | stride=2 466 | pad=1 467 | activation=leaky 468 | 469 | [convolutional] 470 | batch_normalize=1 471 | filters=512 472 | size=1 473 | stride=1 474 | pad=1 475 | activation=leaky 476 | 477 | [convolutional] 478 | batch_normalize=1 479 | filters=1024 480 | size=3 481 | stride=1 482 | pad=1 483 | activation=leaky 484 | 485 | [shortcut] 486 | from=-3 487 | activation=linear 488 | 489 | [convolutional] 490 | batch_normalize=1 491 | filters=512 492 | size=1 493 | stride=1 494 | pad=1 495 | activation=leaky 496 | 497 | [convolutional] 498 | batch_normalize=1 499 | filters=1024 500 | size=3 501 | stride=1 502 | pad=1 503 | activation=leaky 504 | 505 | [shortcut] 506 | from=-3 507 | activation=linear 508 | 509 | [convolutional] 510 | batch_normalize=1 511 | filters=512 512 | size=1 513 | stride=1 514 | pad=1 515 | activation=leaky 516 | 517 | [convolutional] 518 | batch_normalize=1 519 | filters=1024 520 | size=3 521 | stride=1 522 | pad=1 523 | activation=leaky 524 | 525 | [shortcut] 526 | from=-3 527 | activation=linear 528 | 529 | [convolutional] 530 | batch_normalize=1 531 | filters=512 532 | size=1 533 | stride=1 534 | pad=1 535 | activation=leaky 536 | 537 | [convolutional] 538 | batch_normalize=1 539 | filters=1024 540 | size=3 541 | stride=1 542 | pad=1 543 | activation=leaky 544 | 545 | [shortcut] 546 | from=-3 547 | activation=linear 548 | 549 | ###################### 550 | 551 | [convolutional] 552 | batch_normalize=1 553 | filters=512 554 | size=1 555 | stride=1 556 | pad=1 557 | activation=leaky 558 | 559 | [convolutional] 560 | batch_normalize=1 561 | size=3 562 | stride=1 563 | pad=1 564 | filters=1024 565 | activation=leaky 566 | 567 | [convolutional] 568 | batch_normalize=1 569 | filters=512 570 | size=1 571 | stride=1 572 | pad=1 573 | activation=leaky 574 | 575 | [convolutional] 576 | batch_normalize=1 577 | size=3 578 | stride=1 579 | pad=1 580 | filters=1024 581 | activation=leaky 582 | 583 | [convolutional] 584 | batch_normalize=1 585 | filters=512 586 | size=1 587 | stride=1 588 | pad=1 589 | activation=leaky 590 | 591 | [convolutional] 592 | batch_normalize=1 593 | size=3 594 | stride=1 595 | pad=1 596 | filters=1024 597 | activation=leaky 598 | 599 | [convolutional] 600 | size=1 601 | stride=1 602 | pad=1 603 | filters=24 604 | activation=linear 605 | 606 | 607 | [yolo] 608 | mask = 6,7,8 609 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 610 | classes=3 611 | num=9 612 | jitter=.3 613 | ignore_thresh = .7 614 | truth_thresh = 1 615 | random=1 616 | 617 | 618 | [route] 619 | layers = -4 620 | 621 | [convolutional] 622 | batch_normalize=1 623 | filters=256 624 | size=1 625 | stride=1 626 | pad=1 627 | activation=leaky 628 | 629 | [upsample] 630 | stride=2 631 | 632 | [route] 633 | layers = -1, 61 634 | 635 | 636 | 637 | [convolutional] 638 | batch_normalize=1 639 | filters=256 640 | size=1 641 | stride=1 642 | pad=1 643 | activation=leaky 644 | 645 | [convolutional] 646 | batch_normalize=1 647 | size=3 648 | stride=1 649 | pad=1 650 | filters=512 651 | activation=leaky 652 | 653 | [convolutional] 654 | batch_normalize=1 655 | filters=256 656 | size=1 657 | stride=1 658 | pad=1 659 | activation=leaky 660 | 661 | [convolutional] 662 | batch_normalize=1 663 | size=3 664 | stride=1 665 | pad=1 666 | filters=512 667 | activation=leaky 668 | 669 | [convolutional] 670 | batch_normalize=1 671 | filters=256 672 | size=1 673 | stride=1 674 | pad=1 675 | activation=leaky 676 | 677 | [convolutional] 678 | batch_normalize=1 679 | size=3 680 | stride=1 681 | pad=1 682 | filters=512 683 | activation=leaky 684 | 685 | [convolutional] 686 | size=1 687 | stride=1 688 | pad=1 689 | filters=24 690 | activation=linear 691 | 692 | 693 | [yolo] 694 | mask = 3,4,5 695 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 696 | classes=3 697 | num=9 698 | jitter=.3 699 | ignore_thresh = .7 700 | truth_thresh = 1 701 | random=1 702 | 703 | 704 | 705 | [route] 706 | layers = -4 707 | 708 | [convolutional] 709 | batch_normalize=1 710 | filters=128 711 | size=1 712 | stride=1 713 | pad=1 714 | activation=leaky 715 | 716 | [upsample] 717 | stride=2 718 | 719 | [route] 720 | layers = -1, 36 721 | 722 | 723 | 724 | [convolutional] 725 | batch_normalize=1 726 | filters=128 727 | size=1 728 | stride=1 729 | pad=1 730 | activation=leaky 731 | 732 | [convolutional] 733 | batch_normalize=1 734 | size=3 735 | stride=1 736 | pad=1 737 | filters=256 738 | activation=leaky 739 | 740 | [convolutional] 741 | batch_normalize=1 742 | filters=128 743 | size=1 744 | stride=1 745 | pad=1 746 | activation=leaky 747 | 748 | [convolutional] 749 | batch_normalize=1 750 | size=3 751 | stride=1 752 | pad=1 753 | filters=256 754 | activation=leaky 755 | 756 | [convolutional] 757 | batch_normalize=1 758 | filters=128 759 | size=1 760 | stride=1 761 | pad=1 762 | activation=leaky 763 | 764 | [convolutional] 765 | batch_normalize=1 766 | size=3 767 | stride=1 768 | pad=1 769 | filters=256 770 | activation=leaky 771 | 772 | [convolutional] 773 | size=1 774 | stride=1 775 | pad=1 776 | filters=24 777 | activation=linear 778 | 779 | 780 | [yolo] 781 | mask = 0,1,2 782 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 783 | classes=3 784 | num=9 785 | jitter=.3 786 | ignore_thresh = .7 787 | truth_thresh = 1 788 | random=1 789 | 790 | -------------------------------------------------------------------------------- /create_yolo_label_files_from_cvat_xmls.py: -------------------------------------------------------------------------------- 1 | # utf-8 2 | 3 | # Script to convert .xml's that are outputted by cvat to yolov3 file format. 4 | 5 | import os 6 | from lxml import etree 7 | 8 | 9 | def process_cvat_xml(xml_file, image_dir, output_dir): 10 | """ 11 | Transforms a single XML in CVAT format to YOLO TXT files and download images when not in IMAGE_DIR 12 | 13 | :param xml_file: CVAT format XML 14 | :param image_dir: image directory of the dataset 15 | :param output_dir: directory of annotations with YOLO format 16 | :return: 17 | """ 18 | KNOWN_TAGS = {'box', 'image', 'attribute'} 19 | 20 | if (image_dir is None): 21 | image_dir=os.path.join(output_dir, "data/obj") 22 | os.makedirs(image_dir, exist_ok=True) 23 | 24 | os.makedirs(output_dir, exist_ok=True) 25 | cvat_xml = etree.parse(xml_file) 26 | basename = os.path.splitext( os.path.basename(xml_file))[0] 27 | current_labels = {k: v for v, k in enumerate([line.rstrip('\n') for line in open('/home/maarten/Documents/projecten/pytorch/new_yolo/yolov3/data/garb.names')])} 28 | 29 | current_labels = {'container_small':0,'garbage_bag':1,'cardboard':2} 30 | 31 | traintxt = "" 32 | 33 | tracks= cvat_xml.findall( './/image' ) 34 | 35 | 36 | 37 | for img_tag in cvat_xml.findall('image'): 38 | image_name = img_tag.get('name') 39 | width = int(img_tag.get('width')) 40 | height = int(img_tag.get('height')) 41 | image_path = os.path.join(image_dir, image_name) 42 | if not os.path.exists(image_path): 43 | log.warn('{} image cannot be found. Is `{}` image directory correct?'. 44 | format(image_path, image_dir)) 45 | 46 | unknown_tags = {x.tag for x in img_tag.iter()}.difference(KNOWN_TAGS) 47 | if unknown_tags: 48 | log.warn('Ignoring tags for image {}: {}'.format(image_path, unknown_tags)) 49 | 50 | _yoloAnnotationContent = "" 51 | 52 | for box in img_tag.findall('box'): 53 | label = box.get('label') 54 | xmin = float(box.get('xtl')) 55 | ymin = float(box.get('ytl')) 56 | xmax = float(box.get('xbr')) 57 | ymax = float(box.get('ybr')) 58 | 59 | if not label in current_labels: 60 | raise Exception('Unexpected label name {}'.format(label)) 61 | 62 | labelid = current_labels[label] 63 | yolo_x = (xmin + ((xmax-xmin)/2))/width 64 | yolo_y = (ymin + ((ymax-ymin)/2))/height 65 | yolo_w = (xmax - xmin) / width 66 | yolo_h = (ymax - ymin) / height 67 | 68 | if len(_yoloAnnotationContent) != 0: 69 | _yoloAnnotationContent += "\n" 70 | 71 | _yoloAnnotationContent += str(labelid)+" "+"{:.6f}".format(yolo_x) + " "+"{:.6f}".format( 72 | yolo_y) + " "+"{:.6f}".format(yolo_w) + " "+"{:.6f}".format(yolo_h) 73 | 74 | 75 | anno_name = os.path.basename(os.path.splitext(image_name)[0] + '.txt') 76 | anno_path = os.path.join(output_dir+'/labels/', anno_name) 77 | 78 | if len(_yoloAnnotationContent) == 0: 79 | _yoloAnnotationContent += "\n" 80 | 81 | print(anno_name,_yoloAnnotationContent) 82 | 83 | 84 | 85 | _yoloFile = open(anno_path, "w", newline="\n") 86 | _yoloFile.write(_yoloAnnotationContent) 87 | _yoloFile.close() 88 | 89 | 90 | # directory with .xml for cvat 91 | directory_xml_cvat = '' 92 | 93 | # directory with images 94 | directory_images = '' 95 | 96 | # directory where output files will be stored 97 | output_directory = '' 98 | 99 | for xml in os.listdir(directory_xml_cvat): 100 | process_cvat_xml(directory_xml_cvat+xml, directory_images, output_directory) 101 | -------------------------------------------------------------------------------- /darknet.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch 3 | import numpy as np 4 | from torch.autograd import Variable 5 | 6 | 7 | def parse_cfg(cfg): 8 | """ 9 | Parse the cfg file to blocks 10 | """ 11 | 12 | blocks = [] 13 | with open(cfg) as f: 14 | lines = f.read().split('\n') 15 | lines = [l.strip() for l in lines] 16 | lines = [l for l in lines if len(l) > 0 and l[0] != '#'] 17 | block = {} 18 | for line in lines: 19 | if line[0] == '[': 20 | if len(block) != 0: 21 | blocks.append(block) 22 | block = {} 23 | block['type'] = line[1:-1].strip() 24 | else: 25 | key, value = line.split('=') 26 | key = key.strip() 27 | value = value.strip() 28 | block[key] = value 29 | blocks.append(block) 30 | 31 | return blocks 32 | 33 | 34 | class ShortcutLayer(nn.Module): 35 | """ Add short cut from previou layer output 36 | """ 37 | def __init__(self, idx): 38 | super(ShortcutLayer, self).__init__() 39 | self.idx = idx 40 | 41 | def forward(self, x, outputs): 42 | return x + outputs[self.idx] 43 | 44 | 45 | class RouteLayer(nn.Module): 46 | def __init__(self, indices): 47 | super(RouteLayer, self).__init__() 48 | self.indices = indices 49 | 50 | def forward(self, outputs): 51 | out = [outputs[i] for i in self.indices] 52 | out = torch.cat(out, dim=1) 53 | return out 54 | 55 | 56 | class DetectionLayer(nn.Module): 57 | """Transform conv output to bounding boxes 58 | of [center_x, center_y, width, height, objectness score, class scores...] 59 | """ 60 | def __init__(self, anchors, num_classes, input_dim): 61 | super(DetectionLayer, self).__init__() 62 | self.anchors = torch.tensor(anchors, dtype=torch.float) 63 | self.num_classes = num_classes 64 | self.num_anchors = len(anchors) 65 | self.input_dim = input_dim 66 | 67 | def forward(self, x, cuda): 68 | batch_size = x.size(0) 69 | grid_size = x.size(2) 70 | stride = self.input_dim // grid_size 71 | 72 | detection = x.view( 73 | batch_size, self.num_anchors, self.num_classes + 5, 74 | grid_size, grid_size) 75 | # box centers 76 | detection[:, :, :2, :, :] = torch.sigmoid(detection[:, :, :2, :, :]) 77 | # objectness score and class scores 78 | detection[:, :, 4:, :, :] = torch.sigmoid(detection[:, :, 4:, :, :]) 79 | 80 | # add offset to box centers 81 | 82 | x_offset, y_offset = np.meshgrid( 83 | np.arange(grid_size), np.arange(grid_size), indexing='xy') 84 | x_offset = torch.from_numpy(x_offset).float() 85 | y_offset = torch.from_numpy(y_offset).float() 86 | 87 | if cuda: 88 | x_offset = x_offset.cuda() 89 | y_offset = y_offset.cuda() 90 | 91 | x_offset = x_offset.expand_as(detection[:, :, 0, :, :]) 92 | y_offset = y_offset.expand_as(detection[:, :, 1, :, :]) 93 | detection[:, :, 0, :, :] += x_offset 94 | detection[:, :, 1, :, :] += y_offset 95 | # rescale to original image dimention 96 | detection[:, :, :2, :, :] *= stride 97 | 98 | # box width and height 99 | anchors = self.anchors.unsqueeze(-1).unsqueeze(-1).expand_as(detection[:, :, 2:4, :, :]) 100 | if cuda: 101 | anchors = anchors.cuda() 102 | 103 | detection[:, :, 2:4, :, :] = torch.exp(detection[:, :, 2:4, :, :]) * anchors 104 | detection = detection.transpose(1, 2).contiguous().view(batch_size, self.num_classes+5, -1).transpose(1, 2) 105 | 106 | return detection 107 | 108 | 109 | def create_modules(blocks): 110 | net_info = blocks[0] # the first block is network info 111 | module_list = nn.ModuleList() 112 | in_channel = 3 113 | out_channel = in_channel 114 | # keep track of output channel for every 115 | # block for specifying conv layer input channels 116 | out_channels = [] 117 | 118 | for i, block in enumerate(blocks[1:]): 119 | block_type = block['type'] 120 | if block_type == 'convolutional': 121 | module = nn.Sequential() 122 | if 'batch_normalize' in block.keys(): 123 | bn = True 124 | bias = False 125 | else: 126 | bn = False 127 | bias = True 128 | filters = int(block['filters']) 129 | kernel_size = int(block['size']) 130 | stride = int(block['stride']) 131 | pad = int(block['pad']) 132 | activation = block['activation'] 133 | 134 | if pad: 135 | padding = (kernel_size-1) // 2 136 | else: 137 | padding = 0 138 | 139 | conv = nn.Conv2d(in_channels=in_channel, out_channels=filters, kernel_size=kernel_size, stride=stride, padding=padding, bias=bias) 140 | module.add_module('conv_%d' % (i), conv) 141 | 142 | if bn: 143 | module.add_module('batchnorm_%d' %(i), nn.BatchNorm2d(filters)) 144 | if activation == 'leaky': 145 | module.add_module('leaky_%d' % i, nn.LeakyReLU(0.1, inplace=True)) 146 | 147 | out_channel = filters 148 | 149 | elif block_type == 'shortcut': 150 | idx = int(block['from']) + i 151 | module = ShortcutLayer(idx) 152 | 153 | elif block_type == 'upsample': 154 | stride = int(block['stride']) 155 | module = nn.Upsample(scale_factor=stride, mode='bilinear') 156 | 157 | # route block could have one or two indices. Negative value means relative index. 158 | elif block_type == 'route': 159 | layer_indices = block['layers'].split(',') 160 | first_idx = int(layer_indices[0]) 161 | if first_idx < 0: 162 | first_idx = i + first_idx 163 | if len(layer_indices) > 1: 164 | second_idx = int(layer_indices[1]) 165 | if second_idx < 0: 166 | second_idx += i 167 | out_channel = out_channels[first_idx] + out_channels[second_idx] 168 | module = RouteLayer([first_idx, second_idx]) 169 | else: 170 | out_channel = out_channels[first_idx] 171 | module = RouteLayer([first_idx]) 172 | 173 | 174 | elif block_type == 'yolo': 175 | masks = block['mask'].split(',') 176 | masks = [int(mask) for mask in masks] 177 | anchors = block['anchors'].split(',') 178 | anchors = [[int(anchors[2*i]), int(anchors[2*i+1])] for i in masks] 179 | num_classes = int(block['classes']) 180 | input_dim = int(net_info['width']) 181 | module = DetectionLayer(anchors, num_classes, input_dim) 182 | 183 | out_channels.append(out_channel) 184 | in_channel = out_channel 185 | module_list.append(module) 186 | 187 | return (net_info, module_list) 188 | 189 | class Darknet(nn.Module): 190 | def __init__(self, cfg): 191 | super(Darknet, self).__init__() 192 | self.blocks = parse_cfg(cfg) 193 | self.net_info, self.module_list = create_modules(self.blocks) 194 | 195 | def forward(self, x, cuda): 196 | blocks = self.blocks[1:] 197 | outputs = [] 198 | detections = torch.tensor([], dtype=torch.float) 199 | detections = Variable(detections) 200 | if cuda: 201 | detections = detections.cuda() 202 | for i, module in enumerate(self.module_list): 203 | block_type = blocks[i]['type'] 204 | if block_type == 'convolutional' or block_type == 'upsample': 205 | x = module(x) 206 | elif block_type == 'shortcut': 207 | x = module(x, outputs) 208 | elif block_type == 'route': 209 | x = module(outputs) 210 | elif block_type == 'yolo': 211 | x = module(x, cuda) 212 | detections = torch.cat((x, detections), dim=1) 213 | 214 | outputs.append(x) 215 | 216 | return detections 217 | 218 | ''' 219 | Weights file structure: 220 | - header: 5 integers 221 | - weights of conv layers 222 | - conv layer with batch_norm: [bn_bias, bn_weight, bn_running_meanm, bn_running_var, conv_weight] 223 | - conv layer without batch_norm: [conv_bias, conv_weight] 224 | ''' 225 | def load_weights(self, file): 226 | with open(file, 'rb') as f: 227 | header = np.fromfile(f, np.int32, count=5) 228 | weights = np.fromfile(f, np.float32) 229 | self.header = torch.from_numpy(header) 230 | ptr = 0 231 | 232 | for i in range(len(self.module_list)): 233 | module = self.module_list[i] 234 | block_type = self.blocks[i+1]['type'] 235 | 236 | if block_type == 'convolutional': 237 | conv = module[0] 238 | if 'batch_normalize' in self.blocks[i+1].keys(): 239 | bn = module[1] 240 | num_weights = bn.weight.numel() 241 | 242 | bn_bias = torch.from_numpy(weights[ptr: ptr + num_weights]).view_as(bn.bias.data) 243 | ptr += num_weights 244 | bn_weight = torch.from_numpy(weights[ptr: ptr + num_weights]).view_as(bn.weight.data) 245 | ptr += num_weights 246 | 247 | bn_running_mean = torch.from_numpy(weights[ptr: ptr + num_weights]).view_as(bn.running_mean) 248 | ptr += num_weights 249 | bn_running_var = torch.from_numpy(weights[ptr: ptr + num_weights]).view_as(bn.running_var) 250 | ptr += num_weights 251 | 252 | bn.weight.data.copy_(bn_weight) 253 | bn.bias.data.copy_(bn_bias) 254 | bn.running_mean.copy_(bn_running_mean) 255 | bn.running_var.copy_(bn_running_var) 256 | else: 257 | num_bias = conv.bias.numel() 258 | conv_bias = torch.from_numpy(weights[ptr: ptr + num_bias]).view_as(conv.bias.data) 259 | ptr += num_bias 260 | conv.bias.data.copy_(conv_bias) 261 | 262 | num_weights = conv.weight.numel() 263 | conv_weight = torch.from_numpy(weights[ptr: ptr + num_weights]).view_as(conv.weight.data) 264 | ptr += num_weights 265 | conv.weight.data.copy_(conv_weight) 266 | 267 | -------------------------------------------------------------------------------- /data/garb_test.shapes: -------------------------------------------------------------------------------- 1 | 1936 2592 2 | 2448 3264 3 | 2448 3264 4 | 1920 1080 5 | 2448 3264 6 | 2592 1936 7 | 800 597 8 | 1280 720 9 | 1920 1080 10 | 2448 3264 11 | 800 597 12 | 800 597 13 | 1280 720 14 | 2448 3264 15 | 1280 720 16 | 1936 2592 17 | 2448 3264 18 | 1280 720 19 | 2448 3264 20 | 800 597 21 | 2448 3264 22 | 847 640 23 | 1280 720 24 | 2448 3264 25 | 2448 3264 26 | 2448 3264 27 | 1280 720 28 | 2448 3264 29 | 2448 3264 30 | 1280 720 31 | 1280 720 32 | 2448 3264 33 | 1280 720 34 | 1925 2692 35 | 1280 720 36 | 1280 720 37 | 597 800 38 | 1936 2592 39 | 1280 720 40 | 800 597 41 | 1280 720 42 | 1936 1936 43 | 2448 3264 44 | 597 800 45 | 800 597 46 | 1280 720 47 | 2592 1936 48 | 1280 720 49 | 1280 720 50 | 2448 3264 51 | 2448 3264 52 | 3264 2448 53 | 2592 1936 54 | 3264 2448 55 | 2592 1936 56 | 3264 2448 57 | 1280 720 58 | 2448 3264 59 | 2448 3264 60 | 2448 3264 61 | 1936 1936 62 | 1280 720 63 | 800 597 64 | 2448 3264 65 | 2592 1936 66 | 800 597 67 | 1280 720 68 | 1936 2592 69 | 1280 720 70 | 800 597 71 | 2448 3264 72 | 597 800 73 | 1920 1080 74 | 1920 1080 75 | 1920 1080 76 | 1280 720 77 | 1936 2592 78 | 2448 3264 79 | 1280 720 80 | 597 800 81 | 1936 2592 82 | 2448 3264 83 | 2448 3264 84 | 1920 1080 85 | 800 597 86 | 2448 3264 87 | 1920 1080 88 | 1280 720 89 | 1920 1080 90 | 1936 2592 91 | 2448 3264 92 | 1920 1080 93 | 1280 720 94 | 2448 3264 95 | 1936 2592 96 | 1280 720 97 | 800 597 98 | 1936 2592 99 | 800 600 100 | 800 597 101 | 3264 2448 102 | 2448 3264 103 | 3264 2448 104 | 2448 3264 105 | 800 597 106 | 1280 720 107 | 597 800 108 | 1280 720 109 | 3264 2448 110 | 1936 2592 111 | 800 597 112 | 2448 3264 113 | 1280 720 114 | 1936 2592 115 | 800 597 116 | 1936 2592 117 | 2448 3264 118 | 1920 1080 119 | 2448 3264 120 | 1280 720 121 | 1920 1080 122 | 1280 720 123 | 1936 2592 124 | 1920 1080 125 | 2592 1936 126 | 2592 1936 127 | 1280 720 128 | 2448 3264 129 | 2448 3264 130 | 1920 1080 131 | 1280 720 132 | 800 597 133 | 2448 3264 134 | 800 597 135 | 3264 2448 136 | 1280 720 137 | 1280 720 138 | 1280 720 139 | 1280 720 140 | 1920 1080 141 | 2448 3264 142 | 1280 720 143 | 2448 3264 144 | 1920 1080 145 | 1280 720 146 | 1920 1080 147 | 1280 720 148 | 2448 3264 149 | 2448 3264 150 | 1280 720 151 | 1280 720 152 | 1936 2592 153 | 800 597 154 | 1936 2592 155 | 1280 720 156 | 2448 2550 157 | 2448 3264 158 | 597 800 159 | 1280 720 160 | 2448 3264 161 | 2448 3264 162 | -------------------------------------------------------------------------------- /demo/demo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/demo/demo_1.png -------------------------------------------------------------------------------- /demo/garb_demo_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/demo/garb_demo_3.gif -------------------------------------------------------------------------------- /demo/garb_demo_4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/demo/garb_demo_4.gif -------------------------------------------------------------------------------- /detector_garb.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import cv2 3 | import numpy as np 4 | from torch.autograd import Variable 5 | from darknet import Darknet 6 | from util import process_result, load_images, resize_image, cv_image2tensor, transform_result 7 | import pickle as pkl 8 | import argparse 9 | import math 10 | import random 11 | import os.path as osp 12 | import os 13 | import sys 14 | from datetime import datetime 15 | from tqdm import tqdm 16 | 17 | def load_classes(namesfile): 18 | fp = open(namesfile, "r") 19 | names = fp.read().split("\n") 20 | return names 21 | 22 | def parse_args(): 23 | parser = argparse.ArgumentParser(description='YOLOv3 object detection') 24 | parser.add_argument('-i', '--input', required=True, help='input image or directory or video') 25 | parser.add_argument('-t', '--obj-thresh', type=float, default=0.5, help='objectness threshold, DEFAULT: 0.5') 26 | parser.add_argument('-n', '--nms-thresh', type=float, default=0.4, help='non max suppression threshold, DEFAULT: 0.4') 27 | parser.add_argument('-o', '--outdir', default='detection', help='output directory, DEFAULT: detection/') 28 | parser.add_argument('-v', '--video', action='store_true', default=False, help='flag for detecting a video input') 29 | parser.add_argument('-w', '--webcam', action='store_true', default=False, help='flag for detecting from webcam. Specify webcam ID in the input. usually 0 for a single webcam connected') 30 | parser.add_argument('--cuda', action='store_true', default=False, help='flag for running on GPU') 31 | parser.add_argument('--no-show', action='store_true', default=False, help='do not show the detected video in real time') 32 | 33 | args = parser.parse_args() 34 | 35 | return args 36 | 37 | def create_batches(imgs, batch_size): 38 | num_batches = math.ceil(len(imgs) // batch_size) 39 | batches = [imgs[i*batch_size : (i+1)*batch_size] for i in range(num_batches)] 40 | 41 | return batches 42 | 43 | def draw_bbox(imgs, bbox, colors, classes,read_frames,output_path): 44 | img = imgs[int(bbox[0])] 45 | 46 | label = classes[int(bbox[-1])] 47 | 48 | confidence = int(float(bbox[6])*100) 49 | 50 | label = label+' '+str(confidence)+'%' 51 | 52 | print(label) 53 | 54 | p1 = tuple(bbox[1:3].int()) 55 | p2 = tuple(bbox[3:5].int()) 56 | 57 | 58 | if 'privacy' in classes[int(bbox[-1])]: 59 | topLeft = p1 60 | bottomRight = p2 61 | x, y = topLeft[0], topLeft[1] 62 | w, h = bottomRight[0] - topLeft[0], bottomRight[1] - topLeft[1] 63 | 64 | # Grab ROI with Numpy slicing and blur 65 | ROI = img[y:y+h, x:x+w] 66 | blur = cv2.GaussianBlur(ROI, (51,51), 0) 67 | 68 | # Insert ROI back into image 69 | img[y:y+h, x:x+w] = blur 70 | else: 71 | 72 | color = colors[int(bbox[-1])] 73 | cv2.rectangle(img, p1, p2, color, 4) 74 | text_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 1, 1)[0] 75 | p3 = (p1[0], p1[1] - text_size[1] - 4) 76 | p4 = (p1[0] + text_size[0] + 4, p1[1]) 77 | cv2.rectangle(img, p3, p4, color, -1) 78 | 79 | cv2.putText(img, label, p1, cv2.FONT_HERSHEY_SIMPLEX, 1, [225, 255, 255], 1) 80 | 81 | 82 | def detect_video(model, args): 83 | 84 | input_size = [int(model.net_info['height']), int(model.net_info['width'])] 85 | 86 | 87 | colors = pkl.load(open("pallete", "rb")) 88 | classes = load_classes("cfg/garb.names") 89 | 90 | if args.webcam: 91 | cap = cv2.VideoCapture(0) 92 | output_path = osp.join(args.outdir, 'det_webcam.avi') 93 | else: 94 | cap = cv2.VideoCapture(args.input) 95 | output_path = args.outdir 96 | 97 | width, height = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) 98 | 99 | fps = cap.get(cv2.CAP_PROP_FPS) 100 | 101 | fourcc = cv2.VideoWriter_fourcc(*'XVID') 102 | out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) 103 | read_frames = 0 104 | 105 | start_time = datetime.now() 106 | print('Detecting...') 107 | while cap.isOpened(): 108 | retflag, frame = cap.read() 109 | read_frames += 1 110 | if read_frames>0: 111 | if retflag: 112 | frame_tensor = cv_image2tensor(frame, input_size).unsqueeze(0) 113 | frame_tensor = Variable(frame_tensor) 114 | 115 | if args.cuda: 116 | frame_tensor = frame_tensor.cuda() 117 | 118 | detections = model(frame_tensor, args.cuda).cpu() 119 | detections = process_result(detections, args.obj_thresh, args.nms_thresh) 120 | if len(detections) != 0: 121 | detections = transform_result(detections, [frame], input_size) 122 | 123 | for detection in detections: 124 | 125 | draw_bbox([frame], detection, colors, classes,read_frames,output_path) 126 | 127 | 128 | if not args.no_show: 129 | cv2.imshow('frame', frame) 130 | out.write(frame) 131 | if read_frames % 30 == 0: 132 | print('Number of frames processed:', read_frames) 133 | if not args.no_show and cv2.waitKey(1) & 0xFF == ord('q'): 134 | break 135 | else: 136 | break 137 | 138 | end_time = datetime.now() 139 | print('Detection finished in %s' % (end_time - start_time)) 140 | print('Total frames:', read_frames) 141 | cap.release() 142 | out.release() 143 | if not args.no_show: 144 | cv2.destroyAllWindows() 145 | 146 | print('Detected video saved to ' + output_path) 147 | 148 | return 149 | 150 | 151 | def detect_image(model, args): 152 | 153 | print('Loading input image(s)...') 154 | input_size = [int(model.net_info['height']), int(model.net_info['width'])] 155 | batch_size = int(model.net_info['batch']) 156 | 157 | imlist, imgs = load_images(args.input) 158 | print('Input image(s) loaded') 159 | 160 | img_batches = create_batches(imgs, batch_size) 161 | 162 | # load colors and classes 163 | colors = pkl.load(open("pallete", "rb")) 164 | classes = load_classes("cfg/garb.names") 165 | 166 | if not osp.exists(args.outdir): 167 | os.makedirs(args.outdir) 168 | 169 | start_time = datetime.now() 170 | print('Detecting...') 171 | 172 | for batchi, img_batch in tqdm(enumerate(img_batches)): 173 | img_tensors = [cv_image2tensor(img, input_size) for img in img_batch] 174 | img_tensors = torch.stack(img_tensors) 175 | img_tensors = Variable(img_tensors) 176 | if args.cuda: 177 | img_tensors = img_tensors.cuda() 178 | detections = model(img_tensors, args.cuda).cpu() 179 | detections = process_result(detections, args.obj_thresh, args.nms_thresh) 180 | if len(detections) == 0: 181 | continue 182 | 183 | detections = transform_result(detections, img_batch, input_size) 184 | 185 | for detection in detections: 186 | draw_bbox(img_batch, detection, colors, classes,0,args.outdir) 187 | 188 | for i, img in enumerate(img_batch): 189 | save_path = osp.join(args.outdir, osp.basename(imlist[batchi*batch_size + i])) 190 | cv2.imwrite(save_path, img) 191 | print(save_path, 'saved') 192 | 193 | end_time = datetime.now() 194 | print('Detection finished in %s' % (end_time - start_time)) 195 | 196 | return 197 | 198 | def main(): 199 | 200 | args = parse_args() 201 | 202 | if args.cuda and not torch.cuda.is_available(): 203 | print("ERROR: cuda is not available, try running on CPU") 204 | sys.exit(1) 205 | 206 | print('Loading network...') 207 | model = Darknet("cfg/yolov3_garb_9_test.cfg") 208 | model.load_weights('weights/yolov3_garb.backup') 209 | if args.cuda: 210 | model.cuda() 211 | 212 | model.eval() 213 | print('Network loaded') 214 | 215 | if args.video: 216 | detect_video(model, args) 217 | 218 | else: 219 | detect_image(model, args) 220 | 221 | 222 | 223 | if __name__ == '__main__': 224 | main() -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.0' 2 | services: 3 | 4 | pytorch-detection: 5 | build: . -------------------------------------------------------------------------------- /garbage_detection.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | from model.darknet import Darknet 4 | import torch 5 | import logging 6 | from model.util import process_result, load_images, resize_image, cv_image2tensor, transform_result, create_batches,create_output_json, load_data_frame 7 | import math 8 | import pickle as pkl 9 | import os.path as osp 10 | from datetime import datetime 11 | from torch.autograd import Variable 12 | 13 | class GarbageImageClassifier: 14 | 15 | """ 16 | 17 | Classification models 18 | 19 | Image to json output with detected objects 20 | 21 | """ 22 | 23 | def __init__(self,cuda,obj_thresh = 0.5, nms_thresh = 0.4): 24 | 25 | curScriptPath = os.path.dirname(os.path.abspath(__file__)) # needed to keep track of the current location of current script ( although it is included somewhere else ) 26 | 27 | self.cuda = cuda 28 | self.obj_thresh = obj_thresh 29 | self.nms_thresh = nms_thresh 30 | if cuda and not torch.cuda.is_available(): 31 | print("ERROR: cuda is not available, try running on CPU") 32 | sys.exit(1) 33 | 34 | print('Loading network...') 35 | self.model = Darknet(curScriptPath + "/cfg/yolov3_garb_test.cfg") 36 | self.model.load_weights(curScriptPath + '/weights/garb.weights') 37 | 38 | if self.cuda: 39 | self.model.cuda() 40 | 41 | self.model.eval() 42 | print('Network loaded') 43 | 44 | self.createLogger() 45 | self.logger.info("GarbageImageClassifier: Init") 46 | 47 | # ---- 48 | 49 | def createLogger(self): 50 | 51 | self.logger = logging.getLogger(__name__) 52 | self.logger.setLevel(level=logging.INFO) # SETTING: log level 53 | 54 | # logger handlers 55 | handler = logging.StreamHandler() 56 | # handler.setLevel(logging.DEBUG) 57 | formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)-4s %(message)s') 58 | handler.setFormatter(formatter) 59 | self.logger.addHandler(handler) 60 | 61 | # ---- 62 | 63 | def detect_image(self,path,colors=[(39, 129, 113), (164, 80, 133), (83, 122, 114)],classes=['container_small', 'garbage_bag', 'cardboard']): 64 | 65 | print('Loading input image(s)...') 66 | input_size = [int(self.model.net_info['height']), int(self.model.net_info['width'])] 67 | batch_size = int(self.model.net_info['batch']) 68 | 69 | imlist, imgs = load_images(path) 70 | print('Input image(s) loaded') 71 | 72 | img_batches = create_batches(imgs, batch_size) 73 | 74 | 75 | 76 | print('Detecting...') 77 | 78 | all_images_attributes = [] 79 | 80 | for batchi, img_batch in enumerate(img_batches): 81 | start_time = datetime.now() 82 | img_tensors = [cv_image2tensor(img, input_size) for img in img_batch] 83 | img_tensors = torch.stack(img_tensors) 84 | img_tensors = Variable(img_tensors) 85 | if self.cuda: 86 | img_tensors = img_tensors.cuda() 87 | detections = self.model(img_tensors, self.cuda).cpu() 88 | detections = process_result(detections, self.obj_thresh, self.nms_thresh) 89 | if len(detections) == 0: 90 | continue 91 | 92 | detections = transform_result(detections, img_batch, input_size) 93 | 94 | boxes = [] 95 | for detection in detections: 96 | boxes.append(create_output_json(img_batch, detection, colors, classes)) 97 | 98 | images_attributes = {} 99 | images_attributes['frameMeta'] = {'width':input_size[1],'height':input_size[0]} 100 | images_attributes['detectedObjects'] = boxes 101 | 102 | images_attributes['counts'] = {x:0 for x in classes} 103 | images_attributes['counts']['total'] = 0 104 | 105 | for box in boxes: 106 | images_attributes['counts'][box['detectedObjectType']] +=1 107 | images_attributes['counts']['total'] +=1 108 | end_time = datetime.now() 109 | print('Detection finished in %s' % (end_time - start_time)) 110 | images_attributes['mlDoneAt'] = str(end_time) 111 | images_attributes['mlTimeTaken'] = end_time - start_time 112 | 113 | all_images_attributes.append(images_attributes) 114 | 115 | return all_images_attributes 116 | 117 | # ---- 118 | 119 | def detect_image_data_frame(self,data_frame,colors=[(39, 129, 113), (164, 80, 133), (83, 122, 114)],classes=['container_small', 'garbage_bag', 'cardboard']): 120 | 121 | print('Loading input image(s)...') 122 | input_size = [int(self.model.net_info['height']), int(self.model.net_info['width'])] 123 | batch_size = int(self.model.net_info['batch']) 124 | 125 | imgs = [load_data_frame(data_frame)] 126 | print('Input image(s) loaded') 127 | 128 | img_batches = create_batches(imgs, batch_size) 129 | 130 | print('Detecting...') 131 | 132 | all_images_attributes = [] 133 | 134 | for batchi, img_batch in enumerate(img_batches): 135 | start_time = datetime.now() 136 | img_tensors = [cv_image2tensor(img, input_size) for img in img_batch] 137 | img_tensors = torch.stack(img_tensors) 138 | img_tensors = Variable(img_tensors) 139 | if self.cuda: 140 | img_tensors = img_tensors.cuda() 141 | detections = self.model(img_tensors, self.cuda).cpu() 142 | detections = process_result(detections, self.obj_thresh, self.nms_thresh) 143 | if len(detections) == 0: 144 | continue 145 | 146 | detections = transform_result(detections, img_batch, input_size) 147 | 148 | boxes = [] 149 | for detection in detections: 150 | boxes.append(create_output_json(img_batch, detection, colors, classes)) 151 | 152 | images_attributes = {} 153 | images_attributes['frameMeta'] = {'width':input_size[1],'height':input_size[0]} 154 | images_attributes['detectedObjects'] = boxes 155 | 156 | images_attributes['counts'] = {x:0 for x in classes} 157 | images_attributes['counts']['total'] = 0 158 | 159 | for box in boxes: 160 | images_attributes['counts'][box['detectedObjectType']] +=1 161 | images_attributes['counts']['total'] +=1 162 | end_time = datetime.now() 163 | print('Detection finished in %s' % (end_time - start_time)) 164 | images_attributes['mlDoneAt'] = str(end_time) 165 | images_attributes['mlTimeTaken'] = end_time - start_time 166 | 167 | all_images_attributes.append(images_attributes) 168 | 169 | 170 | 171 | 172 | return all_images_attributes 173 | -------------------------------------------------------------------------------- /loss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/loss.png -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | import torch.nn.functional as F 2 | 3 | from utils.parse_config import * 4 | from utils.utils import * 5 | from utils.google_utils import * 6 | 7 | ONNX_EXPORT = False 8 | 9 | 10 | def create_modules(module_defs, img_size, arc): 11 | # Constructs module list of layer blocks from module 12 | # configuration in module_defs 13 | 14 | hyperparams = module_defs.pop(0) 15 | output_filters = [int(hyperparams['channels'])] 16 | module_list = nn.ModuleList() 17 | routs = [] # list of layers which rout to deeper layes 18 | yolo_index = -1 19 | 20 | for i, mdef in enumerate(module_defs): 21 | modules = nn.Sequential() 22 | 23 | if mdef['type'] == 'convolutional': 24 | bn = int(mdef['batch_normalize']) 25 | filters = int(mdef['filters']) 26 | kernel_size = int(mdef['size']) 27 | pad = (kernel_size - 1) // 2 if int(mdef['pad']) else 0 28 | modules.add_module('Conv2d', nn.Conv2d(in_channels=output_filters[-1], 29 | out_channels=filters, 30 | kernel_size=kernel_size, 31 | stride=int(mdef['stride']), 32 | padding=pad, 33 | bias=not bn)) 34 | if bn: 35 | modules.add_module('BatchNorm2d', nn.BatchNorm2d(filters, momentum=0.1)) 36 | if mdef['activation'] == 'leaky': # TODO: activation study https://github.com/ultralytics/yolov3/issues/441 37 | modules.add_module('activation', nn.LeakyReLU(0.1, inplace=True)) 38 | # modules.add_module('activation', nn.PReLU(num_parameters=1, init=0.10)) 39 | # modules.add_module('activation', Swish()) 40 | 41 | elif mdef['type'] == 'maxpool': 42 | kernel_size = int(mdef['size']) 43 | stride = int(mdef['stride']) 44 | maxpool = nn.MaxPool2d(kernel_size=kernel_size, stride=stride, padding=int((kernel_size - 1) // 2)) 45 | if kernel_size == 2 and stride == 1: # yolov3-tiny 46 | modules.add_module('ZeroPad2d', nn.ZeroPad2d((0, 1, 0, 1))) 47 | modules.add_module('MaxPool2d', maxpool) 48 | else: 49 | modules = maxpool 50 | 51 | elif mdef['type'] == 'upsample': 52 | modules = nn.Upsample(scale_factor=int(mdef['stride']), mode='nearest') 53 | 54 | elif mdef['type'] == 'route': # nn.Sequential() placeholder for 'route' layer 55 | layers = [int(x) for x in mdef['layers'].split(',')] 56 | filters = sum([output_filters[i + 1 if i > 0 else i] for i in layers]) 57 | routs.extend([l if l > 0 else l + i for l in layers]) 58 | # if mdef[i+1]['type'] == 'reorg3d': 59 | # modules = nn.Upsample(scale_factor=1/float(mdef[i+1]['stride']), mode='nearest') # reorg3d 60 | 61 | elif mdef['type'] == 'shortcut': # nn.Sequential() placeholder for 'shortcut' layer 62 | filters = output_filters[int(mdef['from'])] 63 | layer = int(mdef['from']) 64 | routs.extend([i + layer if layer < 0 else layer]) 65 | 66 | elif mdef['type'] == 'reorg3d': # yolov3-spp-pan-scale 67 | # torch.Size([16, 128, 104, 104]) 68 | # torch.Size([16, 64, 208, 208]) <-- # stride 2 interpolate dimensions 2 and 3 to cat with prior layer 69 | pass 70 | 71 | elif mdef['type'] == 'yolo': 72 | yolo_index += 1 73 | mask = [int(x) for x in mdef['mask'].split(',')] # anchor mask 74 | modules = YOLOLayer(anchors=mdef['anchors'][mask], # anchor list 75 | nc=int(mdef['classes']), # number of classes 76 | img_size=img_size, # (416, 416) 77 | yolo_index=yolo_index, # 0, 1 or 2 78 | arc=arc) # yolo architecture 79 | 80 | # Initialize preceding Conv2d() bias (https://arxiv.org/pdf/1708.02002.pdf section 3.3) 81 | try: 82 | if arc == 'defaultpw': # default with positive weights 83 | b = [-4, -3.6] # obj, cls 84 | elif arc == 'default': # default no pw (40 cls, 80 obj) 85 | b = [-5.5, -4.0] 86 | elif arc == 'uBCE': # unified BCE (80 classes) 87 | b = [0, -8.5] 88 | elif arc == 'uCE': # unified CE (1 background + 80 classes) 89 | b = [10, -0.1] 90 | elif arc == 'Fdefault': # Focal default no pw (28 cls, 21 obj, no pw) 91 | b = [-2.1, -1.8] 92 | elif arc == 'uFBCE': # unified FocalBCE (5120 obj, 80 classes) 93 | b = [0, -6.5] 94 | elif arc == 'uFCE': # unified FocalCE (64 cls, 1 background + 80 classes) 95 | b = [7.7, -1.1] 96 | 97 | bias = module_list[-1][0].bias.view(len(mask), -1) # 255 to 3x85 98 | bias[:, 4] += b[0] - bias[:, 4].mean() # obj 99 | bias[:, 5:] += b[1] - bias[:, 5:].mean() # cls 100 | # bias = torch.load('weights/yolov3-spp.bias.pt')[yolo_index] # list of tensors [3x85, 3x85, 3x85] 101 | module_list[-1][0].bias = torch.nn.Parameter(bias.view(-1)) 102 | # utils.print_model_biases(model) 103 | except: 104 | print('WARNING: smart bias initialization failure.') 105 | 106 | else: 107 | print('Warning: Unrecognized Layer Type: ' + mdef['type']) 108 | 109 | # Register module list and number of output filters 110 | module_list.append(modules) 111 | output_filters.append(filters) 112 | 113 | return module_list, routs 114 | 115 | 116 | class Swish(nn.Module): 117 | def __init__(self): 118 | super(Swish, self).__init__() 119 | 120 | def forward(self, x): 121 | return x * torch.sigmoid(x) 122 | 123 | 124 | class YOLOLayer(nn.Module): 125 | def __init__(self, anchors, nc, img_size, yolo_index, arc): 126 | super(YOLOLayer, self).__init__() 127 | 128 | self.anchors = torch.Tensor(anchors) 129 | self.na = len(anchors) # number of anchors (3) 130 | self.nc = nc # number of classes (80) 131 | self.nx = 0 # initialize number of x gridpoints 132 | self.ny = 0 # initialize number of y gridpoints 133 | self.arc = arc 134 | 135 | if ONNX_EXPORT: # grids must be computed in __init__ 136 | stride = [32, 16, 8][yolo_index] # stride of this layer 137 | nx = int(img_size[1] / stride) # number x grid points 138 | ny = int(img_size[0] / stride) # number y grid points 139 | create_grids(self, img_size, (nx, ny)) 140 | 141 | def forward(self, p, img_size, var=None): 142 | if ONNX_EXPORT: 143 | bs = 1 # batch size 144 | else: 145 | bs, ny, nx = p.shape[0], p.shape[-2], p.shape[-1] 146 | if (self.nx, self.ny) != (nx, ny): 147 | create_grids(self, img_size, (nx, ny), p.device, p.dtype) 148 | 149 | # p.view(bs, 255, 13, 13) -- > (bs, 3, 13, 13, 85) # (bs, anchors, grid, grid, classes + xywh) 150 | p = p.view(bs, self.na, self.nc + 5, self.ny, self.nx).permute(0, 1, 3, 4, 2).contiguous() # prediction 151 | 152 | if self.training: 153 | return p 154 | 155 | elif ONNX_EXPORT: 156 | # Constants CAN NOT BE BROADCAST, ensure correct shape! 157 | ngu = self.ng.repeat((1, self.na * self.nx * self.ny, 1)) 158 | grid_xy = self.grid_xy.repeat((1, self.na, 1, 1, 1)).view((1, -1, 2)) 159 | anchor_wh = self.anchor_wh.repeat((1, 1, self.nx, self.ny, 1)).view((1, -1, 2)) / ngu 160 | 161 | p = p.view(-1, 5 + self.nc) 162 | xy = torch.sigmoid(p[..., 0:2]) + grid_xy[0] # x, y 163 | wh = torch.exp(p[..., 2:4]) * anchor_wh[0] # width, height 164 | p_conf = torch.sigmoid(p[:, 4:5]) # Conf 165 | p_cls = F.softmax(p[:, 5:85], 1) * p_conf # SSD-like conf 166 | return torch.cat((xy / ngu[0], wh, p_conf, p_cls), 1).t() 167 | 168 | # p = p.view(1, -1, 5 + self.nc) 169 | # xy = torch.sigmoid(p[..., 0:2]) + grid_xy # x, y 170 | # wh = torch.exp(p[..., 2:4]) * anchor_wh # width, height 171 | # p_conf = torch.sigmoid(p[..., 4:5]) # Conf 172 | # p_cls = p[..., 5:5 + self.nc] 173 | # # Broadcasting only supported on first dimension in CoreML. See onnx-coreml/_operators.py 174 | # # p_cls = F.softmax(p_cls, 2) * p_conf # SSD-like conf 175 | # p_cls = torch.exp(p_cls).permute((2, 1, 0)) 176 | # p_cls = p_cls / p_cls.sum(0).unsqueeze(0) * p_conf.permute((2, 1, 0)) # F.softmax() equivalent 177 | # p_cls = p_cls.permute(2, 1, 0) 178 | # return torch.cat((xy / ngu, wh, p_conf, p_cls), 2).squeeze().t() 179 | 180 | else: # inference 181 | # s = 1.5 # scale_xy (pxy = pxy * s - (s - 1) / 2) 182 | io = p.clone() # inference output 183 | io[..., 0:2] = torch.sigmoid(io[..., 0:2]) + self.grid_xy # xy 184 | io[..., 2:4] = torch.exp(io[..., 2:4]) * self.anchor_wh # wh yolo method 185 | # io[..., 2:4] = ((torch.sigmoid(io[..., 2:4]) * 2) ** 3) * self.anchor_wh # wh power method 186 | io[..., :4] *= self.stride 187 | 188 | if 'default' in self.arc: # seperate obj and cls 189 | torch.sigmoid_(io[..., 4:]) 190 | elif 'BCE' in self.arc: # unified BCE (80 classes) 191 | torch.sigmoid_(io[..., 5:]) 192 | io[..., 4] = 1 193 | elif 'CE' in self.arc: # unified CE (1 background + 80 classes) 194 | io[..., 4:] = F.softmax(io[..., 4:], dim=4) 195 | io[..., 4] = 1 196 | 197 | if self.nc == 1: 198 | io[..., 5] = 1 # single-class model https://github.com/ultralytics/yolov3/issues/235 199 | 200 | # reshape from [1, 3, 13, 13, 85] to [1, 507, 85] 201 | return io.view(bs, -1, 5 + self.nc), p 202 | 203 | 204 | class Darknet(nn.Module): 205 | # YOLOv3 object detection model 206 | 207 | def __init__(self, cfg, img_size=(416, 416), arc='default'): 208 | super(Darknet, self).__init__() 209 | 210 | self.module_defs = parse_model_cfg(cfg) 211 | self.module_list, self.routs = create_modules(self.module_defs, img_size, arc) 212 | self.yolo_layers = get_yolo_layers(self) 213 | 214 | # Darknet Header https://github.com/AlexeyAB/darknet/issues/2914#issuecomment-496675346 215 | self.version = np.array([0, 2, 5], dtype=np.int32) # (int32) version info: major, minor, revision 216 | self.seen = np.array([0], dtype=np.int64) # (int64) number of images seen during training 217 | 218 | def forward(self, x, var=None): 219 | img_size = x.shape[-2:] 220 | layer_outputs = [] 221 | output = [] 222 | 223 | for i, (mdef, module) in enumerate(zip(self.module_defs, self.module_list)): 224 | mtype = mdef['type'] 225 | if mtype in ['convolutional', 'upsample', 'maxpool']: 226 | x = module(x) 227 | elif mtype == 'route': 228 | layers = [int(x) for x in mdef['layers'].split(',')] 229 | if len(layers) == 1: 230 | x = layer_outputs[layers[0]] 231 | else: 232 | try: 233 | x = torch.cat([layer_outputs[i] for i in layers], 1) 234 | except: # apply stride 2 for darknet reorg layer 235 | layer_outputs[layers[1]] = F.interpolate(layer_outputs[layers[1]], scale_factor=[0.5, 0.5]) 236 | x = torch.cat([layer_outputs[i] for i in layers], 1) 237 | # print(''), [print(layer_outputs[i].shape) for i in layers], print(x.shape) 238 | elif mtype == 'shortcut': 239 | x = x + layer_outputs[int(mdef['from'])] 240 | elif mtype == 'yolo': 241 | x = module(x, img_size) 242 | output.append(x) 243 | layer_outputs.append(x if i in self.routs else []) 244 | 245 | if self.training: 246 | return output 247 | elif ONNX_EXPORT: 248 | output = torch.cat(output, 1) # cat 3 layers 85 x (507, 2028, 8112) to 85 x 10647 249 | nc = self.module_list[self.yolo_layers[0]].nc # number of classes 250 | return output[5:5 + nc].t(), output[:4].t() # ONNX scores, boxes 251 | else: 252 | io, p = list(zip(*output)) # inference output, training output 253 | return torch.cat(io, 1), p 254 | 255 | def fuse(self): 256 | # Fuse Conv2d + BatchNorm2d layers throughout model 257 | fused_list = nn.ModuleList() 258 | for a in list(self.children())[0]: 259 | if isinstance(a, nn.Sequential): 260 | for i, b in enumerate(a): 261 | if isinstance(b, nn.modules.batchnorm.BatchNorm2d): 262 | # fuse this bn layer with the previous conv2d layer 263 | conv = a[i - 1] 264 | fused = torch_utils.fuse_conv_and_bn(conv, b) 265 | a = nn.Sequential(fused, *list(a.children())[i + 1:]) 266 | break 267 | fused_list.append(a) 268 | self.module_list = fused_list 269 | # model_info(self) # yolov3-spp reduced from 225 to 152 layers 270 | 271 | 272 | def get_yolo_layers(model): 273 | return [i for i, x in enumerate(model.module_defs) if x['type'] == 'yolo'] # [82, 94, 106] for yolov3 274 | 275 | 276 | def create_grids(self, img_size=416, ng=(13, 13), device='cpu', type=torch.float32): 277 | nx, ny = ng # x and y grid size 278 | self.img_size = max(img_size) 279 | self.stride = self.img_size / max(ng) 280 | 281 | # build xy offsets 282 | yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)]) 283 | self.grid_xy = torch.stack((xv, yv), 2).to(device).type(type).view((1, 1, ny, nx, 2)) 284 | 285 | # build wh gains 286 | self.anchor_vec = self.anchors.to(device) / self.stride 287 | self.anchor_wh = self.anchor_vec.view(1, self.na, 1, 1, 2).to(device).type(type) 288 | self.ng = torch.Tensor(ng).to(device) 289 | self.nx = nx 290 | self.ny = ny 291 | 292 | 293 | def load_darknet_weights(self, weights, cutoff=-1): 294 | # Parses and loads the weights stored in 'weights' 295 | # cutoff: save layers between 0 and cutoff (if cutoff = -1 all are saved) 296 | file = Path(weights).name 297 | 298 | # Try to download weights if not available locally 299 | msg = weights + ' missing, download from https://drive.google.com/open?id=1DjeNxdaF7AW3Nu54_3oRw_1SeYJtOvNL' 300 | if not os.path.isfile(weights): 301 | if file == 'yolov3-spp.weights': 302 | gdrive_download(id='1oPCHKsM2JpM-zgyepQciGli9X0MTsJCO', name=weights) 303 | elif file == 'darknet53.conv.74': 304 | gdrive_download(id='18xqvs_uwAqfTXp-LJCYLYNHBOcrwbrp0', name=weights) 305 | else: 306 | try: # download from pjreddie.com 307 | url = 'https://pjreddie.com/media/files/' + file 308 | print('Downloading ' + url) 309 | os.system('curl -f ' + url + ' -o ' + weights) 310 | except IOError: 311 | print(msg) 312 | os.system('rm ' + weights) # remove partial downloads 313 | assert os.path.exists(weights), msg # download missing weights from Google Drive 314 | 315 | # Establish cutoffs 316 | if file == 'darknet53.conv.74': 317 | cutoff = 75 318 | elif file == 'yolov3-tiny.conv.15': 319 | cutoff = 15 320 | 321 | # Read weights file 322 | with open(weights, 'rb') as f: 323 | # Read Header https://github.com/AlexeyAB/darknet/issues/2914#issuecomment-496675346 324 | self.version = np.fromfile(f, dtype=np.int32, count=3) # (int32) version info: major, minor, revision 325 | self.seen = np.fromfile(f, dtype=np.int64, count=1) # (int64) number of images seen during training 326 | 327 | weights = np.fromfile(f, dtype=np.float32) # The rest are weights 328 | 329 | ptr = 0 330 | for i, (mdef, module) in enumerate(zip(self.module_defs[:cutoff], self.module_list[:cutoff])): 331 | if mdef['type'] == 'convolutional': 332 | conv_layer = module[0] 333 | if mdef['batch_normalize']: 334 | # Load BN bias, weights, running mean and running variance 335 | bn_layer = module[1] 336 | num_b = bn_layer.bias.numel() # Number of biases 337 | # Bias 338 | bn_b = torch.from_numpy(weights[ptr:ptr + num_b]).view_as(bn_layer.bias) 339 | bn_layer.bias.data.copy_(bn_b) 340 | ptr += num_b 341 | # Weight 342 | bn_w = torch.from_numpy(weights[ptr:ptr + num_b]).view_as(bn_layer.weight) 343 | bn_layer.weight.data.copy_(bn_w) 344 | ptr += num_b 345 | # Running Mean 346 | bn_rm = torch.from_numpy(weights[ptr:ptr + num_b]).view_as(bn_layer.running_mean) 347 | bn_layer.running_mean.data.copy_(bn_rm) 348 | ptr += num_b 349 | # Running Var 350 | bn_rv = torch.from_numpy(weights[ptr:ptr + num_b]).view_as(bn_layer.running_var) 351 | bn_layer.running_var.data.copy_(bn_rv) 352 | ptr += num_b 353 | else: 354 | # Load conv. bias 355 | num_b = conv_layer.bias.numel() 356 | conv_b = torch.from_numpy(weights[ptr:ptr + num_b]).view_as(conv_layer.bias) 357 | conv_layer.bias.data.copy_(conv_b) 358 | ptr += num_b 359 | # Load conv. weights 360 | num_w = conv_layer.weight.numel() 361 | conv_w = torch.from_numpy(weights[ptr:ptr + num_w]).view_as(conv_layer.weight) 362 | conv_layer.weight.data.copy_(conv_w) 363 | ptr += num_w 364 | 365 | return cutoff 366 | 367 | 368 | def save_weights(self, path='model.weights', cutoff=-1): 369 | # Converts a PyTorch model to Darket format (*.pt to *.weights) 370 | # Note: Does not work if model.fuse() is applied 371 | with open(path, 'wb') as f: 372 | # Write Header https://github.com/AlexeyAB/darknet/issues/2914#issuecomment-496675346 373 | self.version.tofile(f) # (int32) version info: major, minor, revision 374 | self.seen.tofile(f) # (int64) number of images seen during training 375 | 376 | # Iterate through layers 377 | for i, (mdef, module) in enumerate(zip(self.module_defs[:cutoff], self.module_list[:cutoff])): 378 | if mdef['type'] == 'convolutional': 379 | conv_layer = module[0] 380 | # If batch norm, load bn first 381 | if mdef['batch_normalize']: 382 | bn_layer = module[1] 383 | bn_layer.bias.data.cpu().numpy().tofile(f) 384 | bn_layer.weight.data.cpu().numpy().tofile(f) 385 | bn_layer.running_mean.data.cpu().numpy().tofile(f) 386 | bn_layer.running_var.data.cpu().numpy().tofile(f) 387 | # Load conv bias 388 | else: 389 | conv_layer.bias.data.cpu().numpy().tofile(f) 390 | # Load conv weights 391 | conv_layer.weight.data.cpu().numpy().tofile(f) 392 | 393 | 394 | def convert(cfg='cfg/yolov3-spp.cfg', weights='weights/yolov3-spp.weights'): 395 | # Converts between PyTorch and Darknet format per extension (i.e. *.weights convert to *.pt and vice versa) 396 | # from models import *; convert('cfg/yolov3-spp.cfg', 'weights/yolov3-spp.weights') 397 | 398 | # Initialize model 399 | model = Darknet(cfg) 400 | 401 | # Load weights and save 402 | if weights.endswith('.pt'): # if PyTorch format 403 | model.load_state_dict(torch.load(weights, map_location='cpu')['model']) 404 | save_weights(model, path='converted.weights', cutoff=-1) 405 | print("Success: converted '%s' to 'converted.weights'" % weights) 406 | 407 | elif weights.endswith('.weights'): # darknet format 408 | _ = load_darknet_weights(model, weights) 409 | 410 | chkpt = {'epoch': -1, 411 | 'best_fitness': None, 412 | 'training_results': None, 413 | 'model': model.state_dict(), 414 | 'optimizer': None} 415 | 416 | torch.save(chkpt, 'converted.pt') 417 | print("Success: converted '%s' to 'converted.pt'" % weights) 418 | 419 | else: 420 | print('Error: extension not supported.') 421 | -------------------------------------------------------------------------------- /output/input5_frame281.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/output/input5_frame281.jpg -------------------------------------------------------------------------------- /pallete: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/pallete -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cycler==0.10.0 2 | kiwisolver==1.1.0 3 | matplotlib==3.1.1 4 | numpy==1.17.2 5 | opencv-python==4.1.1.26 6 | Pillow 7 | pyparsing==2.4.2 8 | python-dateutil==2.8.0 9 | six==1.12.0 10 | torch==1.2.0 11 | tqdm==4.36.1 12 | -------------------------------------------------------------------------------- /samples/input5_frame0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame0.jpg -------------------------------------------------------------------------------- /samples/input5_frame11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame11.jpg -------------------------------------------------------------------------------- /samples/input5_frame123.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame123.jpg -------------------------------------------------------------------------------- /samples/input5_frame154.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame154.jpg -------------------------------------------------------------------------------- /samples/input5_frame186.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame186.jpg -------------------------------------------------------------------------------- /samples/input5_frame220.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame220.jpg -------------------------------------------------------------------------------- /samples/input5_frame249.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame249.jpg -------------------------------------------------------------------------------- /samples/input5_frame25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame25.jpg -------------------------------------------------------------------------------- /samples/input5_frame273.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame273.jpg -------------------------------------------------------------------------------- /samples/input5_frame299.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame299.jpg -------------------------------------------------------------------------------- /samples/input5_frame41.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame41.jpg -------------------------------------------------------------------------------- /samples/input5_frame62.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame62.jpg -------------------------------------------------------------------------------- /samples/input5_frame92.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/samples/input5_frame92.jpg -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | 4 | from torch.utils.data import DataLoader 5 | 6 | from models import * 7 | from utils.datasets import * 8 | from utils.utils import * 9 | 10 | 11 | def test(cfg, 12 | data, 13 | weights=None, 14 | batch_size=16, 15 | img_size=416, 16 | iou_thres=0.5, 17 | conf_thres=0.001, 18 | nms_thres=0.5, 19 | save_json=False, 20 | model=None): 21 | # Initialize/load model and set device 22 | if model is None: 23 | device = torch_utils.select_device() 24 | verbose = True 25 | 26 | # Initialize model 27 | model = Darknet(cfg, img_size).to(device) 28 | 29 | # Load weights 30 | if weights.endswith('.pt'): # pytorch format 31 | model.load_state_dict(torch.load(weights, map_location=device)['model']) 32 | else: # darknet format 33 | _ = load_darknet_weights(model, weights) 34 | 35 | if torch.cuda.device_count() > 1: 36 | model = nn.DataParallel(model) 37 | else: 38 | device = next(model.parameters()).device # get model device 39 | verbose = False 40 | 41 | # Configure run 42 | data = parse_data_cfg(data) 43 | nc = int(data['classes']) # number of classes 44 | print(data['classes']) 45 | test_path = data['valid'] # path to test images 46 | names = load_classes(data['names']) # class names 47 | 48 | # Dataloader 49 | dataset = LoadImagesAndLabels(test_path, img_size, batch_size) 50 | dataloader = DataLoader(dataset, 51 | batch_size=batch_size, 52 | num_workers=min(os.cpu_count(), batch_size), 53 | pin_memory=True, 54 | collate_fn=dataset.collate_fn) 55 | 56 | seen = 0 57 | model.eval() 58 | coco91class = coco80_to_coco91_class() 59 | s = ('%20s' + '%10s' * 6) % ('Class', 'Images', 'Targets', 'P', 'R', 'mAP', 'F1') 60 | p, r, f1, mp, mr, map, mf1 = 0., 0., 0., 0., 0., 0., 0. 61 | loss = torch.zeros(3) 62 | jdict, stats, ap, ap_class = [], [], [], [] 63 | for batch_i, (imgs, targets, paths, shapes) in enumerate(tqdm(dataloader, desc=s)): 64 | targets = targets.to(device) 65 | imgs = imgs.to(device) 66 | _, _, height, width = imgs.shape # batch size, channels, height, width 67 | 68 | # Plot images with bounding boxes 69 | if batch_i == 0: 70 | plot_images(imgs=imgs, targets=targets, paths=paths, fname='test_batch0.jpg') 71 | 72 | # Run model 73 | inf_out, train_out = model(imgs) # inference and training outputs 74 | 75 | # Compute loss 76 | if hasattr(model, 'hyp'): # if model has loss hyperparameters 77 | loss += compute_loss(train_out, targets, model)[1][:3].cpu() # GIoU, obj, cls 78 | 79 | # Run NMS 80 | output = non_max_suppression(inf_out, conf_thres=conf_thres, nms_thres=nms_thres) 81 | 82 | # Statistics per image 83 | for si, pred in enumerate(output): 84 | labels = targets[targets[:, 0] == si, 1:] 85 | nl = len(labels) 86 | tcls = labels[:, 0].tolist() if nl else [] # target class 87 | seen += 1 88 | 89 | if pred is None: 90 | if nl: 91 | stats.append(([], torch.Tensor(), torch.Tensor(), tcls)) 92 | continue 93 | 94 | # Append to text file 95 | # with open('test.txt', 'a') as file: 96 | # [file.write('%11.5g' * 7 % tuple(x) + '\n') for x in pred] 97 | 98 | # Append to pycocotools JSON dictionary 99 | if save_json: 100 | # [{"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}, ... 101 | image_id = int(Path(paths[si]).stem.split('_')[-1]) 102 | box = pred[:, :4].clone() # xyxy 103 | scale_coords(imgs[si].shape[1:], box, shapes[si]) # to original shape 104 | box = xyxy2xywh(box) # xywh 105 | box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner 106 | for di, d in enumerate(pred): 107 | jdict.append({'image_id': image_id, 108 | 'category_id': coco91class[int(d[6])], 109 | 'bbox': [floatn(x, 3) for x in box[di]], 110 | 'score': floatn(d[4], 5)}) 111 | 112 | # Clip boxes to image bounds 113 | clip_coords(pred, (height, width)) 114 | 115 | # Assign all predictions as incorrect 116 | correct = [0] * len(pred) 117 | if nl: 118 | detected = [] 119 | tcls_tensor = labels[:, 0] 120 | 121 | # target boxes 122 | tbox = xywh2xyxy(labels[:, 1:5]) 123 | tbox[:, [0, 2]] *= width 124 | tbox[:, [1, 3]] *= height 125 | 126 | # Search for correct predictions 127 | for i, (*pbox, pconf, pcls_conf, pcls) in enumerate(pred): 128 | 129 | # Break if all targets already located in image 130 | if len(detected) == nl: 131 | break 132 | 133 | # Continue if predicted class not among image classes 134 | if pcls.item() not in tcls: 135 | continue 136 | 137 | # Best iou, index between pred and targets 138 | m = (pcls == tcls_tensor).nonzero().view(-1) 139 | iou, bi = bbox_iou(pbox, tbox[m]).max(0) 140 | 141 | # If iou > threshold and class is correct mark as correct 142 | if iou > iou_thres and m[bi] not in detected: # and pcls == tcls[bi]: 143 | correct[i] = 1 144 | detected.append(m[bi]) 145 | 146 | # Append statistics (correct, conf, pcls, tcls) 147 | stats.append((correct, pred[:, 4].cpu(), pred[:, 6].cpu(), tcls)) 148 | 149 | # Compute statistics 150 | stats = [np.concatenate(x, 0) for x in list(zip(*stats))] # to numpy 151 | if len(stats): 152 | p, r, ap, f1, ap_class = ap_per_class(*stats) 153 | mp, mr, map, mf1 = p.mean(), r.mean(), ap.mean(), f1.mean() 154 | nt = np.bincount(stats[3].astype(np.int64), minlength=nc) # number of targets per class 155 | else: 156 | nt = torch.zeros(1) 157 | 158 | # Print results 159 | pf = '%20s' + '%10.3g' * 6 # print format 160 | print(pf % ('all', seen, nt.sum(), mp, mr, map, mf1)) 161 | 162 | # Print results per class 163 | if verbose and nc > 1 and len(stats): 164 | for i, c in enumerate(ap_class): 165 | print(pf % (names[c], seen, nt[c], p[i], r[i], ap[i], f1[i])) 166 | 167 | # Save JSON 168 | if save_json and map and len(jdict): 169 | try: 170 | imgIds = [int(Path(x).stem.split('_')[-1]) for x in dataset.img_files] 171 | with open('results.json', 'w') as file: 172 | json.dump(jdict, file) 173 | 174 | from pycocotools.coco import COCO 175 | from pycocotools.cocoeval import COCOeval 176 | 177 | # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb 178 | cocoGt = COCO('../coco/annotations/instances_val2014.json') # initialize COCO ground truth api 179 | cocoDt = cocoGt.loadRes('results.json') # initialize COCO pred api 180 | 181 | cocoEval = COCOeval(cocoGt, cocoDt, 'bbox') 182 | cocoEval.params.imgIds = imgIds # [:32] # only evaluate these images 183 | cocoEval.evaluate() 184 | cocoEval.accumulate() 185 | cocoEval.summarize() 186 | map = cocoEval.stats[1] # update mAP to pycocotools mAP 187 | except: 188 | print('WARNING: missing dependency pycocotools from requirements.txt. Can not compute official COCO mAP.') 189 | 190 | # Return results 191 | maps = np.zeros(nc) + map 192 | for i, c in enumerate(ap_class): 193 | maps[c] = ap[i] 194 | return (mp, mr, map, mf1, *(loss / len(dataloader)).tolist()), maps 195 | 196 | 197 | if __name__ == '__main__': 198 | parser = argparse.ArgumentParser(prog='test.py') 199 | parser.add_argument('--cfg', type=str, default='cfg/yolov3_garb_test.cfg', help='cfg file path') 200 | parser.add_argument('--data', type=str, default='cfg/garb.data', help='garb.data file path') 201 | parser.add_argument('--weights', type=str, default='weights/garb.weights', help='path to weights file') 202 | parser.add_argument('--batch-size', type=int, default=1, help='size of each image batch') 203 | parser.add_argument('--img-size', type=int, default=416, help='inference size (pixels)') 204 | parser.add_argument('--iou-thres', type=float, default=0.5, help='iou threshold required to qualify as detected') 205 | parser.add_argument('--conf-thres', type=float, default=0.001, help='object confidence threshold') 206 | parser.add_argument('--nms-thres', type=float, default=0.5, help='iou threshold for non-maximum suppression') 207 | parser.add_argument('--save-json', action='store_true', help='save a cocoapi-compatible JSON results file') 208 | opt = parser.parse_args() 209 | print(opt) 210 | 211 | with torch.no_grad(): 212 | test(opt.cfg, 213 | opt.data, 214 | opt.weights, 215 | opt.batch_size, 216 | opt.img_size, 217 | opt.iou_thres, 218 | opt.conf_thres, 219 | opt.nms_thres, 220 | opt.save_json) 221 | -------------------------------------------------------------------------------- /test_batch0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/test_batch0.jpg -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import os.path as osp 3 | import os 4 | import sys 5 | import cv2 6 | import numpy as np 7 | import math 8 | import json 9 | from imageio import imread 10 | import io 11 | import base64 12 | 13 | # conduct objectness score filtering and non max supperssion 14 | def process_result(detection, obj_threshhold, nms_threshhold): 15 | detection = to_corner(detection) 16 | output = torch.tensor([], dtype=torch.float) 17 | 18 | # do it batchwise and classwise 19 | for batchi in range(detection.size(0)): 20 | bboxes = detection[batchi] 21 | bboxes = bboxes[bboxes[:, 4] > obj_threshhold] 22 | 23 | if len(bboxes) == 0: 24 | continue 25 | 26 | # attributes of each bounding box: x1, y1, x2, y2, objectness score, prediction score, prediction index 27 | pred_score, pred_index = torch.max(bboxes[:, 5:], 1) 28 | pred_score = pred_score.unsqueeze(-1) 29 | pred_index = pred_index.float().unsqueeze(-1) 30 | bboxes = torch.cat((bboxes[:, :5], pred_score, pred_index), dim=1) 31 | pred_classes = torch.unique(bboxes[:, -1]) 32 | 33 | # non max suppression for each predicted class 34 | 35 | for cls in pred_classes: 36 | bboxes_cls = bboxes[bboxes[:, -1] == cls] # select boxes that predict the class 37 | _, sort_indices = torch.sort(bboxes_cls[:, 4], descending=True) 38 | bboxes_cls = bboxes_cls[sort_indices] # sort by objectness score 39 | 40 | # select the box with the highest score and get rid of intercepting boxes with big IOU 41 | boxi = 0 42 | while boxi + 1 < bboxes_cls.size(0): 43 | ious = compute_ious(bboxes_cls[boxi], bboxes_cls[boxi+1:]) 44 | bboxes_cls = torch.cat([bboxes_cls[:boxi+1], bboxes_cls[boxi+1:][ious < nms_threshhold]]) 45 | boxi += 1 46 | 47 | # add batch index as the first attribute 48 | batch_idx_add = torch.full((bboxes_cls.size(0), 1), batchi) 49 | bboxes_cls = torch.cat((batch_idx_add, bboxes_cls), dim=1) 50 | output = torch.cat((output, bboxes_cls)) 51 | 52 | return output 53 | 54 | def to_corner(bboxes): 55 | newbboxes = bboxes.clone() 56 | newbboxes[:, :, 0] = bboxes[:, :, 0] - bboxes[:, :, 2] / 2 57 | newbboxes[:, :, 1] = bboxes[:, :, 1] - bboxes[:, :, 3] / 2 58 | newbboxes[:, :, 2] = bboxes[:, :, 0] + bboxes[:, :, 2] / 2 59 | newbboxes[:, :, 3] = bboxes[:, :, 1] + bboxes[:, :, 3] / 2 60 | return newbboxes 61 | 62 | def compute_ious(target_box, comp_boxes): 63 | targetx1, targety1, targetx2, targety2 = target_box[:4] 64 | compx1s, compy1s, compx2s, compy2s = comp_boxes[:, :4].transpose(0, 1) 65 | 66 | interceptx1s = torch.max(targetx1, compx1s) 67 | intercepty1s = torch.max(targety1, compy1s) 68 | interceptx2s = torch.min(targetx2, compx2s) 69 | intercepty2s = torch.min(targety2, compy2s) 70 | 71 | intercept_areas = torch.clamp(interceptx2s - interceptx1s + 1, 0) * torch.clamp(intercepty2s - intercepty1s + 1, 0) 72 | 73 | target_area = (targetx2 - targetx1 + 1) * (targety2 - targety1 + 1) 74 | comp_areas = (compx2s - compx1s + 1) * (compy2s - compy1s + 1) 75 | 76 | union_areas = comp_areas + target_area - intercept_areas 77 | 78 | ious = intercept_areas / union_areas 79 | return ious 80 | 81 | def load_images(impath): 82 | if osp.isdir(impath): 83 | imlist = [osp.join(impath, img) for img in os.listdir(impath)] 84 | elif osp.isfile(impath): 85 | imlist = [impath] 86 | else: 87 | print('%s is not a valid path' % impath) 88 | sys.exit(1) 89 | imgs = [cv2.imread(path) for path in imlist] 90 | return imlist, imgs 91 | 92 | def load_data_frame(data_frame): 93 | ''' 94 | Turn dataframe with base64 image into an opencv image 95 | ''' 96 | 97 | data_frame = json.loads(data_frame) 98 | 99 | b64_string = str(data_frame['img']).split(',')[1] 100 | 101 | # reconstruct image as an numpy array 102 | img = imread(io.BytesIO(base64.b64decode(b64_string))) 103 | 104 | 105 | 106 | # finally convert RGB image to BGR for opencv 107 | # and save result 108 | cv2_img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) 109 | 110 | return img 111 | 112 | def cv_image2tensor(img, size): 113 | img = resize_image(img, size) 114 | img = img[:, :, ::-1].transpose((2, 0, 1)).copy() 115 | img = torch.from_numpy(img).float() / 255.0 116 | 117 | return img 118 | 119 | # resize_image by scaling while preserving aspect ratio and then padding remaining area with gray pixels 120 | def resize_image(img, size): 121 | h, w = img.shape[0:2] 122 | newh, neww = size 123 | scale = min(newh / h, neww / w) 124 | img_h, img_w = int(h * scale), int(w * scale) 125 | img = cv2.resize(img, (img_w, img_h), interpolation=cv2.INTER_CUBIC) 126 | 127 | canvas = np.full((newh, neww, 3), 128.0) 128 | canvas[(newh - img_h) // 2 : (newh - img_h) // 2 + img_h, (neww - img_w) // 2 : (neww-img_w) // 2 + img_w, :] = img 129 | 130 | return canvas 131 | 132 | # transform bouning box position in the resized image(input image to the network) to the corresponding position in the original image 133 | def transform_result(detections, imgs, input_size): 134 | # get the original image dimensions 135 | img_dims = [[img.shape[0], img.shape[1]] for img in imgs] 136 | img_dims = torch.tensor(img_dims, dtype=torch.float) 137 | img_dims = torch.index_select(img_dims, 0, detections[:, 0].long()) 138 | 139 | input_size = torch.tensor(input_size, dtype=torch.float) 140 | 141 | scale_factors = torch.min(input_size / img_dims, 1)[0].unsqueeze(-1) 142 | detections[:, [1, 3]] -= (input_size[1] - scale_factors * img_dims[:, 1].unsqueeze(-1)) / 2 143 | detections[:, [2, 4]] -= (input_size[0] - scale_factors * img_dims[:, 0].unsqueeze(-1)) / 2 144 | 145 | detections[:, 1:5] /= scale_factors 146 | 147 | # clipping 148 | detections[:, 1:5] = torch.clamp(detections[:, 1:5], 0) 149 | detections[:, [1, 3]] = torch.min(detections[:, [1, 3]], img_dims[:, 1].unsqueeze(-1)) 150 | detections[:, [2, 4]] = torch.min(detections[:, [2, 4]], img_dims[:, 0].unsqueeze(-1)) 151 | 152 | return detections 153 | 154 | # create batches out of imgs 155 | def create_batches(imgs, batch_size): 156 | num_batches = math.ceil(len(imgs) // batch_size) 157 | batches = [imgs[i*batch_size : (i+1)*batch_size] for i in range(num_batches)] 158 | 159 | return batches 160 | 161 | # draw a bbox 162 | def draw_bbox(imgs, bbox, colors, classes,read_frames,output_path): 163 | img = imgs[int(bbox[0])] 164 | 165 | label = classes[int(bbox[-1])] 166 | 167 | confidence = int(float(bbox[6])*100) 168 | 169 | label = label+' '+str(confidence)+'%' 170 | 171 | p1 = tuple(bbox[1:3].int()) 172 | p2 = tuple(bbox[3:5].int()) 173 | 174 | color = colors[int(bbox[-1])] 175 | cv2.rectangle(img, p1, p2, color, 4) 176 | text_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 1, 1)[0] 177 | p3 = (p1[0], p1[1] - text_size[1] - 4) 178 | p4 = (p1[0] + text_size[0] + 4, p1[1]) 179 | cv2.rectangle(img, p3, p4, color, -1) 180 | 181 | cv2.putText(img, label, p1, cv2.FONT_HERSHEY_SIMPLEX, 1, [225, 255, 255], 1) 182 | 183 | # create_output_json 184 | def create_output_json(img, bbox, colors, classes): 185 | 186 | label = classes[int(bbox[-1])] 187 | 188 | confidence = int(float(bbox[6])*100) 189 | 190 | p1 = [int(x) for x in tuple(bbox[1:3].int())] 191 | p2 = [int(x) for x in tuple(bbox[3:5].int())] 192 | 193 | return {'detectedObjectType':label,'confidence':confidence,'bbox':{'coordinate1':p1,'coordinate2':p2}} 194 | -------------------------------------------------------------------------------- /utils/adabound.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import torch 4 | from torch.optim import Optimizer 5 | 6 | 7 | class AdaBound(Optimizer): 8 | """Implements AdaBound algorithm. 9 | It has been proposed in `Adaptive Gradient Methods with Dynamic Bound of Learning Rate`_. 10 | Arguments: 11 | params (iterable): iterable of parameters to optimize or dicts defining 12 | parameter groups 13 | lr (float, optional): Adam learning rate (default: 1e-3) 14 | betas (Tuple[float, float], optional): coefficients used for computing 15 | running averages of gradient and its square (default: (0.9, 0.999)) 16 | final_lr (float, optional): final (SGD) learning rate (default: 0.1) 17 | gamma (float, optional): convergence speed of the bound functions (default: 1e-3) 18 | eps (float, optional): term added to the denominator to improve 19 | numerical stability (default: 1e-8) 20 | weight_decay (float, optional): weight decay (L2 penalty) (default: 0) 21 | amsbound (boolean, optional): whether to use the AMSBound variant of this algorithm 22 | .. Adaptive Gradient Methods with Dynamic Bound of Learning Rate: 23 | https://openreview.net/forum?id=Bkg3g2R9FX 24 | """ 25 | 26 | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), final_lr=0.1, gamma=1e-3, 27 | eps=1e-8, weight_decay=0, amsbound=False): 28 | if not 0.0 <= lr: 29 | raise ValueError("Invalid learning rate: {}".format(lr)) 30 | if not 0.0 <= eps: 31 | raise ValueError("Invalid epsilon value: {}".format(eps)) 32 | if not 0.0 <= betas[0] < 1.0: 33 | raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0])) 34 | if not 0.0 <= betas[1] < 1.0: 35 | raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1])) 36 | if not 0.0 <= final_lr: 37 | raise ValueError("Invalid final learning rate: {}".format(final_lr)) 38 | if not 0.0 <= gamma < 1.0: 39 | raise ValueError("Invalid gamma parameter: {}".format(gamma)) 40 | defaults = dict(lr=lr, betas=betas, final_lr=final_lr, gamma=gamma, eps=eps, 41 | weight_decay=weight_decay, amsbound=amsbound) 42 | super(AdaBound, self).__init__(params, defaults) 43 | 44 | self.base_lrs = list(map(lambda group: group['lr'], self.param_groups)) 45 | 46 | def __setstate__(self, state): 47 | super(AdaBound, self).__setstate__(state) 48 | for group in self.param_groups: 49 | group.setdefault('amsbound', False) 50 | 51 | def step(self, closure=None): 52 | """Performs a single optimization step. 53 | Arguments: 54 | closure (callable, optional): A closure that reevaluates the model 55 | and returns the loss. 56 | """ 57 | loss = None 58 | if closure is not None: 59 | loss = closure() 60 | 61 | for group, base_lr in zip(self.param_groups, self.base_lrs): 62 | for p in group['params']: 63 | if p.grad is None: 64 | continue 65 | grad = p.grad.data 66 | if grad.is_sparse: 67 | raise RuntimeError( 68 | 'Adam does not support sparse gradients, please consider SparseAdam instead') 69 | amsbound = group['amsbound'] 70 | 71 | state = self.state[p] 72 | 73 | # State initialization 74 | if len(state) == 0: 75 | state['step'] = 0 76 | # Exponential moving average of gradient values 77 | state['exp_avg'] = torch.zeros_like(p.data) 78 | # Exponential moving average of squared gradient values 79 | state['exp_avg_sq'] = torch.zeros_like(p.data) 80 | if amsbound: 81 | # Maintains max of all exp. moving avg. of sq. grad. values 82 | state['max_exp_avg_sq'] = torch.zeros_like(p.data) 83 | 84 | exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq'] 85 | if amsbound: 86 | max_exp_avg_sq = state['max_exp_avg_sq'] 87 | beta1, beta2 = group['betas'] 88 | 89 | state['step'] += 1 90 | 91 | if group['weight_decay'] != 0: 92 | grad = grad.add(group['weight_decay'], p.data) 93 | 94 | # Decay the first and second moment running average coefficient 95 | exp_avg.mul_(beta1).add_(1 - beta1, grad) 96 | exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad) 97 | if amsbound: 98 | # Maintains the maximum of all 2nd moment running avg. till now 99 | torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq) 100 | # Use the max. for normalizing running avg. of gradient 101 | denom = max_exp_avg_sq.sqrt().add_(group['eps']) 102 | else: 103 | denom = exp_avg_sq.sqrt().add_(group['eps']) 104 | 105 | bias_correction1 = 1 - beta1 ** state['step'] 106 | bias_correction2 = 1 - beta2 ** state['step'] 107 | step_size = group['lr'] * math.sqrt(bias_correction2) / bias_correction1 108 | 109 | # Applies bounds on actual learning rate 110 | # lr_scheduler cannot affect final_lr, this is a workaround to apply lr decay 111 | final_lr = group['final_lr'] * group['lr'] / base_lr 112 | lower_bound = final_lr * (1 - 1 / (group['gamma'] * state['step'] + 1)) 113 | upper_bound = final_lr * (1 + 1 / (group['gamma'] * state['step'])) 114 | step_size = torch.full_like(denom, step_size) 115 | step_size.div_(denom).clamp_(lower_bound, upper_bound).mul_(exp_avg) 116 | 117 | p.data.add_(-step_size) 118 | 119 | return loss 120 | 121 | 122 | class AdaBoundW(Optimizer): 123 | """Implements AdaBound algorithm with Decoupled Weight Decay (arxiv.org/abs/1711.05101) 124 | It has been proposed in `Adaptive Gradient Methods with Dynamic Bound of Learning Rate`_. 125 | Arguments: 126 | params (iterable): iterable of parameters to optimize or dicts defining 127 | parameter groups 128 | lr (float, optional): Adam learning rate (default: 1e-3) 129 | betas (Tuple[float, float], optional): coefficients used for computing 130 | running averages of gradient and its square (default: (0.9, 0.999)) 131 | final_lr (float, optional): final (SGD) learning rate (default: 0.1) 132 | gamma (float, optional): convergence speed of the bound functions (default: 1e-3) 133 | eps (float, optional): term added to the denominator to improve 134 | numerical stability (default: 1e-8) 135 | weight_decay (float, optional): weight decay (L2 penalty) (default: 0) 136 | amsbound (boolean, optional): whether to use the AMSBound variant of this algorithm 137 | .. Adaptive Gradient Methods with Dynamic Bound of Learning Rate: 138 | https://openreview.net/forum?id=Bkg3g2R9FX 139 | """ 140 | 141 | def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), final_lr=0.1, gamma=1e-3, 142 | eps=1e-8, weight_decay=0, amsbound=False): 143 | if not 0.0 <= lr: 144 | raise ValueError("Invalid learning rate: {}".format(lr)) 145 | if not 0.0 <= eps: 146 | raise ValueError("Invalid epsilon value: {}".format(eps)) 147 | if not 0.0 <= betas[0] < 1.0: 148 | raise ValueError("Invalid beta parameter at index 0: {}".format(betas[0])) 149 | if not 0.0 <= betas[1] < 1.0: 150 | raise ValueError("Invalid beta parameter at index 1: {}".format(betas[1])) 151 | if not 0.0 <= final_lr: 152 | raise ValueError("Invalid final learning rate: {}".format(final_lr)) 153 | if not 0.0 <= gamma < 1.0: 154 | raise ValueError("Invalid gamma parameter: {}".format(gamma)) 155 | defaults = dict(lr=lr, betas=betas, final_lr=final_lr, gamma=gamma, eps=eps, 156 | weight_decay=weight_decay, amsbound=amsbound) 157 | super(AdaBoundW, self).__init__(params, defaults) 158 | 159 | self.base_lrs = list(map(lambda group: group['lr'], self.param_groups)) 160 | 161 | def __setstate__(self, state): 162 | super(AdaBoundW, self).__setstate__(state) 163 | for group in self.param_groups: 164 | group.setdefault('amsbound', False) 165 | 166 | def step(self, closure=None): 167 | """Performs a single optimization step. 168 | Arguments: 169 | closure (callable, optional): A closure that reevaluates the model 170 | and returns the loss. 171 | """ 172 | loss = None 173 | if closure is not None: 174 | loss = closure() 175 | 176 | for group, base_lr in zip(self.param_groups, self.base_lrs): 177 | for p in group['params']: 178 | if p.grad is None: 179 | continue 180 | grad = p.grad.data 181 | if grad.is_sparse: 182 | raise RuntimeError( 183 | 'Adam does not support sparse gradients, please consider SparseAdam instead') 184 | amsbound = group['amsbound'] 185 | 186 | state = self.state[p] 187 | 188 | # State initialization 189 | if len(state) == 0: 190 | state['step'] = 0 191 | # Exponential moving average of gradient values 192 | state['exp_avg'] = torch.zeros_like(p.data) 193 | # Exponential moving average of squared gradient values 194 | state['exp_avg_sq'] = torch.zeros_like(p.data) 195 | if amsbound: 196 | # Maintains max of all exp. moving avg. of sq. grad. values 197 | state['max_exp_avg_sq'] = torch.zeros_like(p.data) 198 | 199 | exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq'] 200 | if amsbound: 201 | max_exp_avg_sq = state['max_exp_avg_sq'] 202 | beta1, beta2 = group['betas'] 203 | 204 | state['step'] += 1 205 | 206 | # Decay the first and second moment running average coefficient 207 | exp_avg.mul_(beta1).add_(1 - beta1, grad) 208 | exp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad) 209 | if amsbound: 210 | # Maintains the maximum of all 2nd moment running avg. till now 211 | torch.max(max_exp_avg_sq, exp_avg_sq, out=max_exp_avg_sq) 212 | # Use the max. for normalizing running avg. of gradient 213 | denom = max_exp_avg_sq.sqrt().add_(group['eps']) 214 | else: 215 | denom = exp_avg_sq.sqrt().add_(group['eps']) 216 | 217 | bias_correction1 = 1 - beta1 ** state['step'] 218 | bias_correction2 = 1 - beta2 ** state['step'] 219 | step_size = group['lr'] * math.sqrt(bias_correction2) / bias_correction1 220 | 221 | # Applies bounds on actual learning rate 222 | # lr_scheduler cannot affect final_lr, this is a workaround to apply lr decay 223 | final_lr = group['final_lr'] * group['lr'] / base_lr 224 | lower_bound = final_lr * (1 - 1 / (group['gamma'] * state['step'] + 1)) 225 | upper_bound = final_lr * (1 + 1 / (group['gamma'] * state['step'])) 226 | step_size = torch.full_like(denom, step_size) 227 | step_size.div_(denom).clamp_(lower_bound, upper_bound).mul_(exp_avg) 228 | 229 | if group['weight_decay'] != 0: 230 | decayed_weights = torch.mul(p.data, group['weight_decay']) 231 | p.data.add_(-step_size) 232 | p.data.sub_(decayed_weights) 233 | else: 234 | p.data.add_(-step_size) 235 | 236 | return loss 237 | -------------------------------------------------------------------------------- /utils/datasets.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import math 3 | import os 4 | import random 5 | import shutil 6 | from pathlib import Path 7 | 8 | import cv2 9 | import numpy as np 10 | import torch 11 | from PIL import Image, ExifTags 12 | from torch.utils.data import Dataset 13 | from tqdm import tqdm 14 | 15 | from utils.utils import xyxy2xywh, xywh2xyxy 16 | 17 | img_formats = ['.bmp', '.jpg', '.jpeg', '.png', '.tif'] 18 | vid_formats = ['.mov', '.avi', '.mp4'] 19 | 20 | # Get orientation exif tag 21 | for orientation in ExifTags.TAGS.keys(): 22 | if ExifTags.TAGS[orientation] == 'Orientation': 23 | break 24 | 25 | 26 | def exif_size(img): 27 | # Returns exif-corrected PIL size 28 | s = img.size # (width, height) 29 | try: 30 | rotation = dict(img._getexif().items())[orientation] 31 | if rotation == 6: # rotation 270 32 | s = (s[1], s[0]) 33 | elif rotation == 8: # rotation 90 34 | s = (s[1], s[0]) 35 | except: 36 | None 37 | 38 | return s 39 | 40 | 41 | class LoadImages: # for inference 42 | def __init__(self, path, img_size=416, half=False): 43 | path = str(Path(path)) # os-agnostic 44 | files = [] 45 | if os.path.isdir(path): 46 | files = sorted(glob.glob(os.path.join(path, '*.*'))) 47 | elif os.path.isfile(path): 48 | files = [path] 49 | 50 | images = [x for x in files if os.path.splitext(x)[-1].lower() in img_formats] 51 | videos = [x for x in files if os.path.splitext(x)[-1].lower() in vid_formats] 52 | nI, nV = len(images), len(videos) 53 | 54 | self.img_size = img_size 55 | self.files = images + videos 56 | self.nF = nI + nV # number of files 57 | self.video_flag = [False] * nI + [True] * nV 58 | self.mode = 'images' 59 | self.half = half # half precision fp16 images 60 | if any(videos): 61 | self.new_video(videos[0]) # new video 62 | else: 63 | self.cap = None 64 | assert self.nF > 0, 'No images or videos found in ' + path 65 | 66 | def __iter__(self): 67 | self.count = 0 68 | return self 69 | 70 | def __next__(self): 71 | if self.count == self.nF: 72 | raise StopIteration 73 | path = self.files[self.count] 74 | 75 | if self.video_flag[self.count]: 76 | # Read video 77 | self.mode = 'video' 78 | ret_val, img0 = self.cap.read() 79 | if not ret_val: 80 | self.count += 1 81 | self.cap.release() 82 | if self.count == self.nF: # last video 83 | raise StopIteration 84 | else: 85 | path = self.files[self.count] 86 | self.new_video(path) 87 | ret_val, img0 = self.cap.read() 88 | 89 | self.frame += 1 90 | print('video %g/%g (%g/%g) %s: ' % (self.count + 1, self.nF, self.frame, self.nframes, path), end='') 91 | 92 | else: 93 | # Read image 94 | self.count += 1 95 | img0 = cv2.imread(path) # BGR 96 | assert img0 is not None, 'Image Not Found ' + path 97 | print('image %g/%g %s: ' % (self.count, self.nF, path), end='') 98 | 99 | # Padded resize 100 | img, *_ = letterbox(img0, new_shape=self.img_size) 101 | 102 | # Normalize RGB 103 | img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB 104 | img = np.ascontiguousarray(img, dtype=np.float16 if self.half else np.float32) # uint8 to fp16/fp32 105 | img /= 255.0 # 0 - 255 to 0.0 - 1.0 106 | 107 | # cv2.imwrite(path + '.letterbox.jpg', 255 * img.transpose((1, 2, 0))[:, :, ::-1]) # save letterbox image 108 | return path, img, img0, self.cap 109 | 110 | def new_video(self, path): 111 | self.frame = 0 112 | self.cap = cv2.VideoCapture(path) 113 | self.nframes = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT)) 114 | 115 | def __len__(self): 116 | return self.nF # number of files 117 | 118 | 119 | class LoadWebcam: # for inference 120 | def __init__(self, img_size=416, half=False): 121 | self.img_size = img_size 122 | self.half = half # half precision fp16 images 123 | 124 | pipe = 0 # local camera 125 | # pipe = 'rtsp://192.168.1.64/1' # IP camera 126 | # pipe = 'rtsp://username:password@192.168.1.64/1' # IP camera with login 127 | 128 | # https://answers.opencv.org/question/215996/changing-gstreamer-pipeline-to-opencv-in-pythonsolved/ 129 | # pipe = '"rtspsrc location="rtsp://username:password@192.168.1.64/1" latency=10 ! appsink' # GStreamer 130 | 131 | # https://answers.opencv.org/question/200787/video-acceleration-gstremer-pipeline-in-videocapture/ 132 | # https://stackoverflow.com/questions/54095699/install-gstreamer-support-for-opencv-python-package # install help 133 | # pipe = "rtspsrc location=rtsp://root:root@192.168.0.91:554/axis-media/media.amp?videocodec=h264&resolution=3840x2160 protocols=GST_RTSP_LOWER_TRANS_TCP ! rtph264depay ! queue ! vaapih264dec ! videoconvert ! appsink" # GStreamer 134 | 135 | self.cap = cv2.VideoCapture(pipe) # video capture object 136 | 137 | def __iter__(self): 138 | self.count = -1 139 | return self 140 | 141 | def __next__(self): 142 | self.count += 1 143 | if cv2.waitKey(1) == 27: # esc to quit 144 | cv2.destroyAllWindows() 145 | raise StopIteration 146 | 147 | # Read image 148 | ret_val, img0 = self.cap.read() 149 | assert ret_val, 'Webcam Error' 150 | img_path = 'webcam_%g.jpg' % self.count 151 | img0 = cv2.flip(img0, 1) # flip left-right 152 | print('webcam %g: ' % self.count, end='') 153 | 154 | # Padded resize 155 | img, *_ = letterbox(img0, new_shape=self.img_size) 156 | 157 | # Normalize RGB 158 | img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB 159 | img = np.ascontiguousarray(img, dtype=np.float16 if self.half else np.float32) # uint8 to fp16/fp32 160 | img /= 255.0 # 0 - 255 to 0.0 - 1.0 161 | 162 | return img_path, img, img0, None 163 | 164 | def __len__(self): 165 | return 0 166 | 167 | 168 | class LoadImagesAndLabels(Dataset): # for training/testing 169 | def __init__(self, path, img_size=416, batch_size=16, augment=False, hyp=None, rect=True, image_weights=False, 170 | cache_images=False): 171 | 172 | print(path) 173 | 174 | self.img_files = [line.rstrip('\n') for line in open(path)] 175 | print("images loaded: ",len(self.img_files)) 176 | print(self.img_files[:5]) 177 | 178 | n = len(self.img_files) 179 | bi = np.floor(np.arange(n) / batch_size).astype(np.int) # batch index 180 | nb = bi[-1] + 1 # number of batches 181 | assert n > 0, 'No images found in %s' % path 182 | 183 | self.n = n 184 | self.batch = bi # batch index of image 185 | self.img_size = img_size 186 | self.augment = augment 187 | self.hyp = hyp 188 | self.image_weights = image_weights 189 | self.rect = False if image_weights else rect 190 | 191 | 192 | # Define labels 193 | self.label_files = [x.replace('images', 'labels').replace('jpg', 'txt') for x in self.img_files] 194 | print(self.img_files) 195 | print("labels loaded: ",len(self.label_files)) 196 | print(self.label_files[:5]) 197 | 198 | # Rectangular Training https://github.com/ultralytics/yolov3/issues/232 199 | if self.rect: 200 | # Read image shapes 201 | sp = 'data' + os.sep + path.replace('.txt', '.shapes').split(os.sep)[-1] # shapefile path 202 | try: 203 | with open(sp, 'r') as f: # read existing shapefile 204 | s = [x.split() for x in f.read().splitlines()] 205 | assert len(s) == n, 'Shapefile out of sync' 206 | except: 207 | s = [exif_size(Image.open(f)) for f in tqdm(self.img_files, desc='Reading image shapes')] 208 | np.savetxt(sp, s, fmt='%g') # overwrites existing (if any) 209 | 210 | # Sort by aspect ratio 211 | s = np.array(s, dtype=np.float64) 212 | ar = s[:, 1] / s[:, 0] # aspect ratio 213 | i = ar.argsort() 214 | self.img_files = [self.img_files[i] for i in i] 215 | self.label_files = [self.label_files[i] for i in i] 216 | self.shapes = s[i] 217 | ar = ar[i] 218 | 219 | # Set training image shapes 220 | shapes = [[1, 1]] * nb 221 | for i in range(nb): 222 | ari = ar[bi == i] 223 | mini, maxi = ari.min(), ari.max() 224 | if maxi < 1: 225 | shapes[i] = [maxi, 1] 226 | elif mini > 1: 227 | shapes[i] = [1, 1 / mini] 228 | 229 | self.batch_shapes = np.ceil(np.array(shapes) * img_size / 32.).astype(np.int) * 32 230 | 231 | # Preload labels (required for weighted CE training) 232 | self.imgs = [None] * n 233 | self.labels = [None] * n 234 | if augment or image_weights: # cache labels for faster training 235 | self.labels = [np.zeros((0, 5))] * n 236 | extract_bounding_boxes = False 237 | pbar = tqdm(self.label_files, desc='Reading labels') 238 | nm, nf, ne = 0, 0, 0 # number missing, number found, number empty 239 | for i, file in enumerate(pbar): 240 | try: 241 | with open(file, 'r') as f: 242 | l = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32) 243 | except: 244 | nm += 1 # print('missing labels for image %s' % self.img_files[i]) # file missing 245 | continue 246 | 247 | if l.shape[0]: 248 | assert l.shape[1] == 5, '> 5 label columns: %s' % file 249 | assert (l >= 0).all(), 'negative labels: %s' % file 250 | assert (l[:, 1:] <= 1).all(), 'non-normalized or out of bounds coordinate labels: %s' % file 251 | self.labels[i] = l 252 | nf += 1 # file found 253 | 254 | # Extract object detection boxes for a second stage classifier 255 | if extract_bounding_boxes: 256 | p = Path(self.img_files[i]) 257 | img = cv2.imread(str(p)) 258 | h, w, _ = img.shape 259 | for j, x in enumerate(l): 260 | f = '%s%sclassifier%s%g_%g_%s' % (p.parent.parent, os.sep, os.sep, x[0], j, p.name) 261 | if not os.path.exists(Path(f).parent): 262 | os.makedirs(Path(f).parent) # make new output folder 263 | box = xywh2xyxy(x[1:].reshape(-1, 4)).ravel() 264 | b = np.clip(box, 0, 1) # clip boxes outside of image 265 | ret_val = cv2.imwrite(f, img[int(b[1] * h):int(b[3] * h), int(b[0] * w):int(b[2] * w)]) 266 | assert ret_val, 'Failure extracting classifier boxes' 267 | else: 268 | ne += 1 # file empty 269 | 270 | pbar.desc = 'Reading labels (%g found, %g missing, %g empty for %g images)' % (nf, nm, ne, n) 271 | assert nf > 0, 'No labels found. Recommend correcting image and label paths.' 272 | 273 | # Cache images into memory for faster training (~5GB) 274 | if cache_images and augment: # if training 275 | for i in tqdm(range(min(len(self.img_files), 10000)), desc='Reading images'): # max 10k images 276 | img_path = self.img_files[i] 277 | img = cv2.imread(img_path) # BGR 278 | assert img is not None, 'Image Not Found ' + img_path 279 | r = self.img_size / max(img.shape) # size ratio 280 | if self.augment and r < 1: # if training (NOT testing), downsize to inference shape 281 | h, w, _ = img.shape 282 | img = cv2.resize(img, (int(w * r), int(h * r)), interpolation=cv2.INTER_LINEAR) # or INTER_AREA 283 | self.imgs[i] = img 284 | 285 | # Detect corrupted images https://medium.com/joelthchao/programmatically-detect-corrupted-image-8c1b2006c3d3 286 | detect_corrupted_images = False 287 | if detect_corrupted_images: 288 | from skimage import io # conda install -c conda-forge scikit-image 289 | for file in tqdm(self.img_files, desc='Detecting corrupted images'): 290 | try: 291 | _ = io.imread(file) 292 | except: 293 | print('Corrupted image detected: %s' % file) 294 | 295 | def __len__(self): 296 | return len(self.img_files) 297 | 298 | # def __iter__(self): 299 | # self.count = -1 300 | # print('ran dataset iter') 301 | # #self.shuffled_vector = np.random.permutation(self.nF) if self.augment else np.arange(self.nF) 302 | # return self 303 | 304 | def __getitem__(self, index): 305 | if self.image_weights: 306 | index = self.indices[index] 307 | 308 | img_path = self.img_files[index] 309 | label_path = self.label_files[index] 310 | hyp = self.hyp 311 | 312 | # Load image 313 | img = self.imgs[index] 314 | if img is None: 315 | img = cv2.imread(img_path) # BGR 316 | assert img is not None, 'Image Not Found ' + img_path 317 | r = self.img_size / max(img.shape) # size ratio 318 | if self.augment and r < 1: # if training (NOT testing), downsize to inference shape 319 | h, w, _ = img.shape 320 | img = cv2.resize(img, (int(w * r), int(h * r)), interpolation=cv2.INTER_LINEAR) # INTER_LINEAR fastest 321 | 322 | # Augment colorspace 323 | augment_hsv = True 324 | if self.augment and augment_hsv: 325 | # SV augmentation by 50% 326 | img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # hue, sat, val 327 | S = img_hsv[:, :, 1].astype(np.float32) # saturation 328 | V = img_hsv[:, :, 2].astype(np.float32) # value 329 | 330 | a = random.uniform(-1, 1) * hyp['hsv_s'] + 1 331 | b = random.uniform(-1, 1) * hyp['hsv_v'] + 1 332 | S *= a 333 | V *= b 334 | 335 | img_hsv[:, :, 1] = S if a < 1 else S.clip(None, 255) 336 | img_hsv[:, :, 2] = V if b < 1 else V.clip(None, 255) 337 | cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img) 338 | 339 | # Letterbox 340 | h, w, _ = img.shape 341 | if self.rect: 342 | shape = self.batch_shapes[self.batch[index]] 343 | img, ratiow, ratioh, padw, padh = letterbox(img, new_shape=shape, mode='rect') 344 | else: 345 | shape = self.img_size 346 | img, ratiow, ratioh, padw, padh = letterbox(img, new_shape=shape, mode='square') 347 | 348 | # Load labels 349 | labels = [] 350 | if os.path.isfile(label_path): 351 | x = self.labels[index] 352 | if x is None: # labels not preloaded 353 | with open(label_path, 'r') as f: 354 | x = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32) 355 | 356 | if x.size > 0: 357 | # Normalized xywh to pixel xyxy format 358 | labels = x.copy() 359 | labels[:, 1] = ratiow * w * (x[:, 1] - x[:, 3] / 2) + padw 360 | labels[:, 2] = ratioh * h * (x[:, 2] - x[:, 4] / 2) + padh 361 | labels[:, 3] = ratiow * w * (x[:, 1] + x[:, 3] / 2) + padw 362 | labels[:, 4] = ratioh * h * (x[:, 2] + x[:, 4] / 2) + padh 363 | 364 | # Augment image and labels 365 | if self.augment: 366 | img, labels = random_affine(img, labels, 367 | degrees=hyp['degrees'], 368 | translate=hyp['translate'], 369 | scale=hyp['scale'], 370 | shear=hyp['shear']) 371 | 372 | nL = len(labels) # number of labels 373 | if nL: 374 | # convert xyxy to xywh 375 | labels[:, 1:5] = xyxy2xywh(labels[:, 1:5]) 376 | 377 | # Normalize coordinates 0 - 1 378 | labels[:, [2, 4]] /= img.shape[0] # height 379 | labels[:, [1, 3]] /= img.shape[1] # width 380 | 381 | if self.augment: 382 | # random left-right flip 383 | lr_flip = True 384 | if lr_flip and random.random() > 0.5: 385 | img = np.fliplr(img) 386 | if nL: 387 | labels[:, 1] = 1 - labels[:, 1] 388 | 389 | # random up-down flip 390 | ud_flip = False 391 | if ud_flip and random.random() > 0.5: 392 | img = np.flipud(img) 393 | if nL: 394 | labels[:, 2] = 1 - labels[:, 2] 395 | 396 | labels_out = torch.zeros((nL, 6)) 397 | if nL: 398 | labels_out[:, 1:] = torch.from_numpy(labels) 399 | 400 | # Normalize 401 | img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416 402 | img = np.ascontiguousarray(img, dtype=np.float32) # uint8 to float32 403 | img /= 255.0 # 0 - 255 to 0.0 - 1.0 404 | 405 | return torch.from_numpy(img), labels_out, img_path, (h, w) 406 | 407 | @staticmethod 408 | def collate_fn(batch): 409 | img, label, path, hw = list(zip(*batch)) # transposed 410 | for i, l in enumerate(label): 411 | l[:, 0] = i # add target image index for build_targets() 412 | return torch.stack(img, 0), torch.cat(label, 0), path, hw 413 | 414 | 415 | def letterbox(img, new_shape=416, color=(128, 128, 128), mode='auto'): 416 | # Resize a rectangular image to a 32 pixel multiple rectangle 417 | # https://github.com/ultralytics/yolov3/issues/232 418 | shape = img.shape[:2] # current shape [height, width] 419 | 420 | new_shape = 640 421 | if isinstance(new_shape, int): 422 | ratio = float(new_shape) / max(shape) 423 | else: 424 | 425 | ratio = new_shape / max(shape) # ratio = new / old 426 | ratiow, ratioh = ratio, ratio 427 | new_unpad = (int(round(shape[1] * ratio)), int(round(shape[0] * ratio))) 428 | 429 | # Compute padding https://github.com/ultralytics/yolov3/issues/232 430 | if mode is 'auto': # minimum rectangle 431 | dw = np.mod(new_shape - new_unpad[0], 32) / 2 # width padding 432 | dh = np.mod(new_shape - new_unpad[1], 32) / 2 # height padding 433 | elif mode is 'square': # square 434 | dw = (new_shape - new_unpad[0]) / 2 # width padding 435 | dh = (new_shape - new_unpad[1]) / 2 # height padding 436 | elif mode is 'rect': # square 437 | 438 | dw = (new_shape - new_unpad[0]) / 2 # width padding 439 | dh = (new_shape - new_unpad[1]) / 2 # height padding 440 | elif mode is 'scaleFill': 441 | dw, dh = 0.0, 0.0 442 | new_unpad = (new_shape, new_shape) 443 | ratiow, ratioh = new_shape / shape[1], new_shape / shape[0] 444 | 445 | if shape[::-1] != new_unpad: # resize 446 | img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_AREA) # INTER_AREA is better, INTER_LINEAR is faster 447 | top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) 448 | left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) 449 | img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border 450 | return img, ratiow, ratioh, dw, dh 451 | 452 | 453 | def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10): 454 | # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10)) 455 | # https://medium.com/uruvideo/dataset-augmentation-with-random-homographies-a8f4b44830d4 456 | 457 | if targets is None: 458 | targets = [] 459 | border = 0 # width of added border (optional) 460 | height = img.shape[0] + border * 2 461 | width = img.shape[1] + border * 2 462 | 463 | # Rotation and Scale 464 | R = np.eye(3) 465 | a = random.uniform(-degrees, degrees) 466 | # a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations 467 | s = random.uniform(1 - scale, 1 + scale) 468 | R[:2] = cv2.getRotationMatrix2D(angle=a, center=(img.shape[1] / 2, img.shape[0] / 2), scale=s) 469 | 470 | # Translation 471 | T = np.eye(3) 472 | T[0, 2] = random.uniform(-translate, translate) * img.shape[0] + border # x translation (pixels) 473 | T[1, 2] = random.uniform(-translate, translate) * img.shape[1] + border # y translation (pixels) 474 | 475 | # Shear 476 | S = np.eye(3) 477 | S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg) 478 | S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg) 479 | 480 | M = S @ T @ R # Combined rotation matrix. ORDER IS IMPORTANT HERE!! 481 | imw = cv2.warpAffine(img, M[:2], dsize=(width, height), flags=cv2.INTER_AREA, 482 | borderValue=(128, 128, 128)) # BGR order borderValue 483 | 484 | # Return warped points also 485 | if len(targets) > 0: 486 | n = targets.shape[0] 487 | points = targets[:, 1:5].copy() 488 | area0 = (points[:, 2] - points[:, 0]) * (points[:, 3] - points[:, 1]) 489 | 490 | # warp points 491 | xy = np.ones((n * 4, 3)) 492 | xy[:, :2] = points[:, [0, 1, 2, 3, 0, 3, 2, 1]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1 493 | xy = (xy @ M.T)[:, :2].reshape(n, 8) 494 | 495 | # create new boxes 496 | x = xy[:, [0, 2, 4, 6]] 497 | y = xy[:, [1, 3, 5, 7]] 498 | xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T 499 | 500 | # # apply angle-based reduction of bounding boxes 501 | # radians = a * math.pi / 180 502 | # reduction = max(abs(math.sin(radians)), abs(math.cos(radians))) ** 0.5 503 | # x = (xy[:, 2] + xy[:, 0]) / 2 504 | # y = (xy[:, 3] + xy[:, 1]) / 2 505 | # w = (xy[:, 2] - xy[:, 0]) * reduction 506 | # h = (xy[:, 3] - xy[:, 1]) * reduction 507 | # xy = np.concatenate((x - w / 2, y - h / 2, x + w / 2, y + h / 2)).reshape(4, n).T 508 | 509 | # reject warped points outside of image 510 | xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width) 511 | xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height) 512 | w = xy[:, 2] - xy[:, 0] 513 | h = xy[:, 3] - xy[:, 1] 514 | area = w * h 515 | ar = np.maximum(w / (h + 1e-16), h / (w + 1e-16)) 516 | i = (w > 4) & (h > 4) & (area / (area0 + 1e-16) > 0.1) & (ar < 10) 517 | 518 | targets = targets[i] 519 | targets[:, 1:5] = xy[i] 520 | 521 | return imw, targets 522 | 523 | 524 | def convert_images2bmp(): 525 | # cv2.imread() jpg at 230 img/s, *.bmp at 400 img/s 526 | for path in ['../coco/images/val2014/', '../coco/images/train2014/']: 527 | folder = os.sep + Path(path).name 528 | output = path.replace(folder, folder + 'bmp') 529 | if os.path.exists(output): 530 | shutil.rmtree(output) # delete output folder 531 | os.makedirs(output) # make new output folder 532 | 533 | for f in tqdm(glob.glob('%s*.jpg' % path)): 534 | save_name = f.replace('.jpg', '.bmp').replace(folder, folder + 'bmp') 535 | cv2.imwrite(save_name, cv2.imread(f)) 536 | 537 | for label_path in ['../coco/trainvalno5k.txt', '../coco/5k.txt']: 538 | with open(label_path, 'r') as file: 539 | lines = file.read() 540 | lines = lines.replace('2014/', '2014bmp/').replace('.jpg', '.bmp').replace( 541 | '/Users/glennjocher/PycharmProjects/', '../') 542 | with open(label_path.replace('5k', '5k_bmp'), 'w') as file: 543 | file.write(lines) 544 | -------------------------------------------------------------------------------- /utils/gcp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # New VM 4 | rm -rf sample_data yolov3 darknet apex coco cocoapi knife knifec 5 | git clone https://github.com/ultralytics/yolov3 6 | # git clone https://github.com/AlexeyAB/darknet && cd darknet && make GPU=1 CUDNN=1 CUDNN_HALF=1 OPENCV=0 && wget -c https://pjreddie.com/media/files/darknet53.conv.74 && cd .. 7 | git clone https://github.com/NVIDIA/apex && cd apex && pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" . --user && cd .. && rm -rf apex 8 | # git clone https://github.com/cocodataset/cocoapi && cd cocoapi/PythonAPI && make && cd ../.. && cp -r cocoapi/PythonAPI/pycocotools yolov3 9 | sudo conda install -y -c conda-forge scikit-image tensorboard pycocotools 10 | python3 -c " 11 | from yolov3.utils.google_utils import gdrive_download 12 | gdrive_download('1HaXkef9z6y5l4vUnCYgdmEAj61c6bfWO','coco.zip')" 13 | sudo shutdown 14 | 15 | # Re-clone 16 | rm -rf yolov3 # Warning: remove existing 17 | git clone https://github.com/ultralytics/yolov3 && cd yolov3 # master 18 | # git clone -b test --depth 1 https://github.com/ultralytics/yolov3 test # branch 19 | python3 train.py --img-size 320 --weights weights/darknet53.conv.74 --epochs 27 --batch-size 64 --accumulate 1 20 | 21 | # Train 22 | python3 train.py 23 | 24 | # Resume 25 | python3 train.py --resume 26 | 27 | # Detect 28 | python3 detect.py 29 | 30 | # Test 31 | python3 test.py --save-json 32 | 33 | # Evolve 34 | for i in {0..500} 35 | do 36 | python3 train.py --data data/coco.data --img-size 320 --epochs 1 --batch-size 64 --accumulate 1 --evolve --bucket yolov4 37 | done 38 | 39 | # Git pull 40 | git pull https://github.com/ultralytics/yolov3 # master 41 | git pull https://github.com/ultralytics/yolov3 test # branch 42 | 43 | # Test Darknet training 44 | python3 test.py --weights ../darknet/backup/yolov3.backup 45 | 46 | # Copy last.pt TO bucket 47 | gsutil cp yolov3/weights/last1gpu.pt gs://ultralytics 48 | 49 | # Copy last.pt FROM bucket 50 | gsutil cp gs://ultralytics/last.pt yolov3/weights/last.pt 51 | wget https://storage.googleapis.com/ultralytics/yolov3/last_v1_0.pt -O weights/last_v1_0.pt 52 | wget https://storage.googleapis.com/ultralytics/yolov3/best_v1_0.pt -O weights/best_v1_0.pt 53 | 54 | # Reproduce tutorials 55 | rm results*.txt # WARNING: removes existing results 56 | python3 train.py --nosave --data data/coco_1img.data && mv results.txt results0r_1img.txt 57 | python3 train.py --nosave --data data/coco_10img.data && mv results.txt results0r_10img.txt 58 | python3 train.py --nosave --data data/coco_100img.data && mv results.txt results0r_100img.txt 59 | # python3 train.py --nosave --data data/coco_100img.data --transfer && mv results.txt results3_100imgTL.txt 60 | python3 -c "from utils import utils; utils.plot_results()" 61 | # gsutil cp results*.txt gs://ultralytics 62 | gsutil cp results.png gs://ultralytics 63 | sudo shutdown 64 | 65 | # Reproduce mAP 66 | python3 test.py --save-json --img-size 608 67 | python3 test.py --save-json --img-size 416 68 | python3 test.py --save-json --img-size 320 69 | sudo shutdown 70 | 71 | # Benchmark script 72 | git clone https://github.com/ultralytics/yolov3 # clone our repo 73 | git clone https://github.com/NVIDIA/apex && cd apex && pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" . --user && cd .. && rm -rf apex # install nvidia apex 74 | python3 -c "from yolov3.utils.google_utils import gdrive_download; gdrive_download('1HaXkef9z6y5l4vUnCYgdmEAj61c6bfWO','coco.zip')" # download coco dataset (20GB) 75 | cd yolov3 && clear && python3 train.py --epochs 1 # run benchmark (~30 min) 76 | 77 | # Unit tests 78 | python3 detect.py # detect 2 persons, 1 tie 79 | python3 test.py --data data/coco_32img.data # test mAP = 0.8 80 | python3 train.py --data data/coco_32img.data --epochs 5 --nosave # train 5 epochs 81 | python3 train.py --data data/coco_1cls.data --epochs 5 --nosave # train 5 epochs 82 | python3 train.py --data data/coco_1img.data --epochs 5 --nosave # train 5 epochs 83 | 84 | # AlexyAB Darknet 85 | gsutil cp -r gs://sm6/supermarket2 . # dataset from bucket 86 | rm -rf darknet && git clone https://github.com/AlexeyAB/darknet && cd darknet && wget -c https://pjreddie.com/media/files/darknet53.conv.74 # sudo apt install libopencv-dev && make 87 | ./darknet detector calc_anchors data/coco_img64.data -num_of_clusters 9 -width 320 -height 320 # kmeans anchor calculation 88 | ./darknet detector train ../supermarket2/supermarket2.data ../yolo_v3_spp_pan_scale.cfg darknet53.conv.74 -map -dont_show # train spp 89 | ./darknet detector train ../yolov3/data/coco.data ../yolov3-spp.cfg darknet53.conv.74 -map -dont_show # train spp coco 90 | 91 | ./darknet detector train data/coco.data ../yolov3-spp.cfg darknet53.conv.74 -map -dont_show # train spp 92 | gsutil cp -r backup/*5000.weights gs://sm6/weights 93 | sudo shutdown 94 | 95 | 96 | ./darknet detector train ../supermarket2/supermarket2.data ../yolov3-tiny-sm2-1cls.cfg yolov3-tiny.conv.15 -map -dont_show # train tiny 97 | ./darknet detector train ../supermarket2/supermarket2.data cfg/yolov3-spp-sm2-1cls.cfg backup/yolov3-spp-sm2-1cls_last.weights # resume 98 | python3 train.py --data ../supermarket2/supermarket2.data --cfg ../yolov3-spp-sm2-1cls.cfg --epochs 100 --num-workers 8 --img-size 320 --nosave # train ultralytics 99 | python3 test.py --data ../supermarket2/supermarket2.data --weights ../darknet/backup/yolov3-spp-sm2-1cls_5000.weights --cfg cfg/yolov3-spp-sm2-1cls.cfg # test 100 | gsutil cp -r backup/*.weights gs://sm6/weights # weights to bucket 101 | 102 | python3 test.py --data ../supermarket2/supermarket2.data --weights weights/yolov3-spp-sm2-1cls_5000.weights --cfg ../yolov3-spp-sm2-1cls.cfg --img-size 320 --conf-thres 0.2 # test 103 | python3 test.py --data ../supermarket2/supermarket2.data --weights weights/yolov3-spp-sm2-1cls-scalexy_125_5000.weights --cfg ../yolov3-spp-sm2-1cls-scalexy_125.cfg --img-size 320 --conf-thres 0.2 # test 104 | python3 test.py --data ../supermarket2/supermarket2.data --weights weights/yolov3-spp-sm2-1cls-scalexy_150_5000.weights --cfg ../yolov3-spp-sm2-1cls-scalexy_150.cfg --img-size 320 --conf-thres 0.2 # test 105 | python3 test.py --data ../supermarket2/supermarket2.data --weights weights/yolov3-spp-sm2-1cls-scalexy_200_5000.weights --cfg ../yolov3-spp-sm2-1cls-scalexy_200.cfg --img-size 320 --conf-thres 0.2 # test 106 | python3 test.py --data ../supermarket2/supermarket2.data --weights ../darknet/backup/yolov3-spp-sm2-1cls-scalexy_variable_5000.weights --cfg ../yolov3-spp-sm2-1cls-scalexy_variable.cfg --img-size 320 --conf-thres 0.2 # test 107 | 108 | python3 train.py --img-size 320 --epochs 27 --batch-size 64 --accumulate 1 --nosave --notest && python3 test.py --weights weights/last.pt --img-size 320 --save-json && sudo shutdown 109 | 110 | # Debug/Development 111 | python3 train.py --data data/coco.data --img-size 320 --single-scale --batch-size 64 --accumulate 1 --epochs 1 --evolve --giou 112 | python3 test.py --weights weights/last.pt --cfg cfg/yolov3-spp.cfg --img-size 320 113 | 114 | gsutil cp evolve.txt gs://ultralytics 115 | sudo shutdown 116 | -------------------------------------------------------------------------------- /utils/google_utils.py: -------------------------------------------------------------------------------- 1 | # This file contains google utils: https://cloud.google.com/storage/docs/reference/libraries 2 | # pip install --upgrade google-cloud-storage 3 | 4 | import os 5 | import time 6 | 7 | 8 | # from google.cloud import storage 9 | 10 | 11 | def gdrive_download(id='1HaXkef9z6y5l4vUnCYgdmEAj61c6bfWO', name='coco.zip'): 12 | # https://gist.github.com/tanaikech/f0f2d122e05bf5f971611258c22c110f 13 | # Downloads a file from Google Drive, accepting presented query 14 | # from utils.google_utils import *; gdrive_download() 15 | t = time.time() 16 | 17 | print('Downloading https://drive.google.com/uc?export=download&id=%s as %s... ' % (id, name), end='') 18 | if os.path.exists(name): # remove existing 19 | os.remove(name) 20 | 21 | # Attempt large file download 22 | s = ["curl -c ./cookie -s -L \"https://drive.google.com/uc?export=download&id=%s\" > /dev/null" % id, 23 | "curl -Lb ./cookie -s \"https://drive.google.com/uc?export=download&confirm=`awk '/download/ {print $NF}' ./cookie`&id=%s\" -o %s" % ( 24 | id, name), 25 | 'rm ./cookie'] 26 | [os.system(x) for x in s] # run commands 27 | 28 | # Attempt small file download 29 | if not os.path.exists(name): # file size < 40MB 30 | s = 'curl -f -L -o %s https://drive.google.com/uc?export=download&id=%s' % (name, id) 31 | os.system(s) 32 | 33 | # Unzip if archive 34 | if name.endswith('.zip'): 35 | print('unzipping... ', end='') 36 | os.system('unzip -q %s' % name) # unzip 37 | os.remove(name) # remove zip to free space 38 | 39 | print('Done (%.1fs)' % (time.time() - t)) 40 | 41 | 42 | def upload_blob(bucket_name, source_file_name, destination_blob_name): 43 | # Uploads a file to a bucket 44 | # https://cloud.google.com/storage/docs/uploading-objects#storage-upload-object-python 45 | 46 | storage_client = storage.Client() 47 | bucket = storage_client.get_bucket(bucket_name) 48 | blob = bucket.blob(destination_blob_name) 49 | 50 | blob.upload_from_filename(source_file_name) 51 | 52 | print('File {} uploaded to {}.'.format( 53 | source_file_name, 54 | destination_blob_name)) 55 | 56 | 57 | def download_blob(bucket_name, source_blob_name, destination_file_name): 58 | # Uploads a blob from a bucket 59 | storage_client = storage.Client() 60 | bucket = storage_client.get_bucket(bucket_name) 61 | blob = bucket.blob(source_blob_name) 62 | 63 | blob.download_to_filename(destination_file_name) 64 | 65 | print('Blob {} downloaded to {}.'.format( 66 | source_blob_name, 67 | destination_file_name)) 68 | -------------------------------------------------------------------------------- /utils/init.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maartensukel/urban-object-detection/3f47472746f2b2c79094aa9c2894fb4c2d779f79/utils/init.py -------------------------------------------------------------------------------- /utils/parse_config.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def parse_model_cfg(path): 5 | # Parses the yolo-v3 layer configuration file and returns module definitions 6 | file = open(path, 'r') 7 | lines = file.read().split('\n') 8 | lines = [x for x in lines if x and not x.startswith('#')] 9 | lines = [x.rstrip().lstrip() for x in lines] # get rid of fringe whitespaces 10 | mdefs = [] # module definitions 11 | for line in lines: 12 | if line.startswith('['): # This marks the start of a new block 13 | mdefs.append({}) 14 | mdefs[-1]['type'] = line[1:-1].rstrip() 15 | if mdefs[-1]['type'] == 'convolutional': 16 | mdefs[-1]['batch_normalize'] = 0 # pre-populate with zeros (may be overwritten later) 17 | else: 18 | key, val = line.split("=") 19 | key = key.rstrip() 20 | 21 | if 'anchors' in key: 22 | mdefs[-1][key] = np.array([float(x) for x in val.split(',')]).reshape((-1, 2)) # np anchors 23 | else: 24 | mdefs[-1][key] = val.strip() 25 | 26 | return mdefs 27 | 28 | 29 | def parse_data_cfg(path): 30 | # Parses the data configuration file 31 | options = dict() 32 | with open(path, 'r') as fp: 33 | lines = fp.readlines() 34 | 35 | for line in lines: 36 | line = line.strip() 37 | if line == '' or line.startswith('#'): 38 | continue 39 | key, val = line.split('=') 40 | options[key.strip()] = val.strip() 41 | 42 | return options 43 | -------------------------------------------------------------------------------- /utils/torch_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import torch 4 | 5 | 6 | def init_seeds(seed=0): 7 | torch.cuda.empty_cache() 8 | torch.manual_seed(seed) 9 | torch.cuda.manual_seed(seed) 10 | torch.cuda.manual_seed_all(seed) 11 | 12 | # Remove randomness (may be slower on Tesla GPUs) # https://pytorch.org/docs/stable/notes/randomness.html 13 | if seed == 0: 14 | torch.backends.cudnn.deterministic = True 15 | torch.backends.cudnn.benchmark = False 16 | 17 | 18 | def select_device(device=None, apex=False): 19 | if device == 'cpu': 20 | pass 21 | elif device: # Set environment variable if device is specified 22 | os.environ['CUDA_VISIBLE_DEVICES'] = device 23 | 24 | # apex if mixed precision training https://github.com/NVIDIA/apex 25 | cuda = False if device == 'cpu' else torch.cuda.is_available() 26 | device = torch.device('cuda:0' if cuda else 'cpu') 27 | 28 | if not cuda: 29 | print('Using CPU') 30 | if cuda: 31 | c = 1024 ** 2 # bytes to MB 32 | ng = torch.cuda.device_count() 33 | x = [torch.cuda.get_device_properties(i) for i in range(ng)] 34 | cuda_str = 'Using CUDA ' + ('Apex ' if apex else '') 35 | for i in range(0, ng): 36 | if i == 1: 37 | # torch.cuda.set_device(0) # OPTIONAL: Set GPU ID 38 | cuda_str = ' ' * len(cuda_str) 39 | print("%sdevice%g _CudaDeviceProperties(name='%s', total_memory=%dMB)" % 40 | (cuda_str, i, x[i].name, x[i].total_memory / c)) 41 | 42 | print('') # skip a line 43 | return device 44 | 45 | 46 | def fuse_conv_and_bn(conv, bn): 47 | # https://tehnokv.com/posts/fusing-batchnorm-and-conv/ 48 | with torch.no_grad(): 49 | # init 50 | fusedconv = torch.nn.Conv2d(conv.in_channels, 51 | conv.out_channels, 52 | kernel_size=conv.kernel_size, 53 | stride=conv.stride, 54 | padding=conv.padding, 55 | bias=True) 56 | 57 | # prepare filters 58 | w_conv = conv.weight.clone().view(conv.out_channels, -1) 59 | w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps + bn.running_var))) 60 | fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.size())) 61 | 62 | # prepare spatial bias 63 | if conv.bias is not None: 64 | b_conv = conv.bias 65 | else: 66 | b_conv = torch.zeros(conv.weight.size(0)) 67 | b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps)) 68 | fusedconv.bias.copy_(b_conv + b_bn) 69 | 70 | return fusedconv 71 | --------------------------------------------------------------------------------