Commit
·
32865f3
1
Parent(s):
962ea6f
init model
Browse files- README.md +148 -0
- coco.names +80 -0
- coco2017.data +4 -0
- general_json2yolo.py +179 -0
- onnx_inference.py +151 -0
- onnx_test.py +1247 -0
- requirements.txt +29 -0
- utils.py +213 -0
- yolov3-8.onnx +3 -0
- yolov3.cfg +788 -0
README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
datasets:
|
| 4 |
+
- COCO
|
| 5 |
+
metrics:
|
| 6 |
+
- mAP
|
| 7 |
+
language:
|
| 8 |
+
- en
|
| 9 |
+
tags:
|
| 10 |
+
- RyzenAI
|
| 11 |
+
- object-detection
|
| 12 |
+
- vision
|
| 13 |
+
- YOLO
|
| 14 |
+
- Pytorch
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
# YOLOv3 model trained on COCO
|
| 18 |
+
|
| 19 |
+
YOLOv3 is trained on COCO object detection (118k annotated images) at resolution 416x416. It was released in https://github.com/ultralytics/yolov3/tree/v8.
|
| 20 |
+
|
| 21 |
+
We develop a modified version that could be supported by [AMD Ryzen AI](https://ryzenai.docs.amd.com).
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
## Model description
|
| 25 |
+
|
| 26 |
+
YOLOv3 🚀 is the world's most loved vision AI, representing Ultralytics open-source research into future vision AI methods, incorporating lessons learned and best practices evolved over thousands of hours of research and development.
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
## Intended uses & limitations
|
| 30 |
+
|
| 31 |
+
You can use the raw model for object detection. See the [model hub](https://huggingface.co/models?search=amd/yolov3) to look for all available YOLOv3 models.
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
## How to use
|
| 35 |
+
|
| 36 |
+
### Installation
|
| 37 |
+
|
| 38 |
+
Follow [Ryzen AI Installation](https://ryzenai.docs.amd.com/en/latest/inst.html) to prepare the environment for Ryzen AI.
|
| 39 |
+
Run the following script to install pre-requisites for this model.
|
| 40 |
+
```bash
|
| 41 |
+
pip install -r requirements.txt
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
### Data Preparation (optional: for accuracy evaluation)
|
| 46 |
+
|
| 47 |
+
The dataset MSCOCO2017 contains 118287 images for training and 5000 images for validation.
|
| 48 |
+
|
| 49 |
+
1. Download COCO dataset
|
| 50 |
+
2. Run general_json2yolo.py to generate the labels folder and val2017.txt
|
| 51 |
+
```sh
|
| 52 |
+
python general_json2yolo.py
|
| 53 |
+
```
|
| 54 |
+
Finally, COCO dataset should look like this:
|
| 55 |
+
```plain
|
| 56 |
+
+ coco/
|
| 57 |
+
+ annotations/
|
| 58 |
+
+ instance_val2017.json
|
| 59 |
+
+ ...
|
| 60 |
+
+ images/
|
| 61 |
+
+ val2017/
|
| 62 |
+
+ 000000000139.jpg
|
| 63 |
+
+ 000000000285.jpg
|
| 64 |
+
+ ...
|
| 65 |
+
+ labels/
|
| 66 |
+
+ val2017/
|
| 67 |
+
+ 000000000139.txt
|
| 68 |
+
+ 000000000285.txt
|
| 69 |
+
+ ...
|
| 70 |
+
+ val2017.txt
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
### Test & Evaluation
|
| 76 |
+
|
| 77 |
+
- Code snippet from [`onnx_inference.py`](onnx_inference.py) on how to use
|
| 78 |
+
```python
|
| 79 |
+
onnx_path = "yolov3-8.onnx"
|
| 80 |
+
onnx_model = onnxruntime.InferenceSession(
|
| 81 |
+
onnx_path, providers=providers, provider_options=provider_options)
|
| 82 |
+
|
| 83 |
+
path = opt.img
|
| 84 |
+
new_path = os.path.join(opt.out, "demo_infer.jpg")
|
| 85 |
+
|
| 86 |
+
conf_thres, iou_thres, classes, agnostic_nms, max_det = 0.25, \
|
| 87 |
+
0.45, None, False, 1000
|
| 88 |
+
|
| 89 |
+
img0 = cv2.imread(path)
|
| 90 |
+
img = pre_process(img0)
|
| 91 |
+
onnx_input = {onnx_model.get_inputs()[0].name: img}
|
| 92 |
+
onnx_output = onnx_model.run(None, onnx_input)
|
| 93 |
+
onnx_output = post_process(onnx_output)
|
| 94 |
+
|
| 95 |
+
pred = non_max_suppression(
|
| 96 |
+
onnx_output[0],
|
| 97 |
+
conf_thres,
|
| 98 |
+
iou_thres,
|
| 99 |
+
multi_label=False,
|
| 100 |
+
classes=classes,
|
| 101 |
+
agnostic=agnostic_nms)
|
| 102 |
+
|
| 103 |
+
colors = [[random.randint(0, 255) for _ in range(3)]
|
| 104 |
+
for _ in range(len(names))]
|
| 105 |
+
det = pred[0]
|
| 106 |
+
im0 = img0.copy()
|
| 107 |
+
|
| 108 |
+
if len(det):
|
| 109 |
+
# Rescale boxes from imgsz to im0 size
|
| 110 |
+
det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()
|
| 111 |
+
|
| 112 |
+
# Write results
|
| 113 |
+
for *xyxy, conf, cls in reversed(det):
|
| 114 |
+
label = '%s %.2f' % (names[int(cls)], conf)
|
| 115 |
+
plot_one_box(xyxy, im0, label=label, color=colors[int(cls)])
|
| 116 |
+
|
| 117 |
+
# Stream results
|
| 118 |
+
cv2.imwrite(new_path, im0)
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
- Run inference for a single image
|
| 122 |
+
```sh
|
| 123 |
+
python onnx_inference.py --img INPUT_IMG_PATH --out OUTPUT_DIR --ipu --provider_config Path\To\vaip_config.json
|
| 124 |
+
```
|
| 125 |
+
*Note: __vaip_config.json__ is located at the setup package of Ryzen AI (refer to [Installation](#installation))*
|
| 126 |
+
|
| 127 |
+
- Test accuracy of the quantized model
|
| 128 |
+
```sh
|
| 129 |
+
python onnx_test.py --ipu --provider_config Path\To\vaip_config.json
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
### Performance
|
| 133 |
+
|
| 134 |
+
|Metric |Accuracy on IPU|
|
| 135 |
+
| :----: | :----: |
|
| 136 |
+
|AP\@0.50:0.95|0.389|
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
```bibtex
|
| 140 |
+
@misc{redmon2018yolov3,
|
| 141 |
+
title={YOLOv3: An Incremental Improvement},
|
| 142 |
+
author={Joseph Redmon and Ali Farhadi},
|
| 143 |
+
year={2018},
|
| 144 |
+
eprint={1804.02767},
|
| 145 |
+
archivePrefix={arXiv},
|
| 146 |
+
primaryClass={cs.CV}
|
| 147 |
+
}
|
| 148 |
+
```
|
coco.names
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
person
|
| 2 |
+
bicycle
|
| 3 |
+
car
|
| 4 |
+
motorcycle
|
| 5 |
+
airplane
|
| 6 |
+
bus
|
| 7 |
+
train
|
| 8 |
+
truck
|
| 9 |
+
boat
|
| 10 |
+
traffic light
|
| 11 |
+
fire hydrant
|
| 12 |
+
stop sign
|
| 13 |
+
parking meter
|
| 14 |
+
bench
|
| 15 |
+
bird
|
| 16 |
+
cat
|
| 17 |
+
dog
|
| 18 |
+
horse
|
| 19 |
+
sheep
|
| 20 |
+
cow
|
| 21 |
+
elephant
|
| 22 |
+
bear
|
| 23 |
+
zebra
|
| 24 |
+
giraffe
|
| 25 |
+
backpack
|
| 26 |
+
umbrella
|
| 27 |
+
handbag
|
| 28 |
+
tie
|
| 29 |
+
suitcase
|
| 30 |
+
frisbee
|
| 31 |
+
skis
|
| 32 |
+
snowboard
|
| 33 |
+
sports ball
|
| 34 |
+
kite
|
| 35 |
+
baseball bat
|
| 36 |
+
baseball glove
|
| 37 |
+
skateboard
|
| 38 |
+
surfboard
|
| 39 |
+
tennis racket
|
| 40 |
+
bottle
|
| 41 |
+
wine glass
|
| 42 |
+
cup
|
| 43 |
+
fork
|
| 44 |
+
knife
|
| 45 |
+
spoon
|
| 46 |
+
bowl
|
| 47 |
+
banana
|
| 48 |
+
apple
|
| 49 |
+
sandwich
|
| 50 |
+
orange
|
| 51 |
+
broccoli
|
| 52 |
+
carrot
|
| 53 |
+
hot dog
|
| 54 |
+
pizza
|
| 55 |
+
donut
|
| 56 |
+
cake
|
| 57 |
+
chair
|
| 58 |
+
couch
|
| 59 |
+
potted plant
|
| 60 |
+
bed
|
| 61 |
+
dining table
|
| 62 |
+
toilet
|
| 63 |
+
tv
|
| 64 |
+
laptop
|
| 65 |
+
mouse
|
| 66 |
+
remote
|
| 67 |
+
keyboard
|
| 68 |
+
cell phone
|
| 69 |
+
microwave
|
| 70 |
+
oven
|
| 71 |
+
toaster
|
| 72 |
+
sink
|
| 73 |
+
refrigerator
|
| 74 |
+
book
|
| 75 |
+
clock
|
| 76 |
+
vase
|
| 77 |
+
scissors
|
| 78 |
+
teddy bear
|
| 79 |
+
hair drier
|
| 80 |
+
toothbrush
|
coco2017.data
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
classes=80
|
| 2 |
+
train=coco/train2017.txt
|
| 3 |
+
valid=coco/val2017.txt
|
| 4 |
+
names=coco.names
|
general_json2yolo.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from tqdm import tqdm
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import json
|
| 5 |
+
from collections import defaultdict
|
| 6 |
+
import sys
|
| 7 |
+
import pathlib
|
| 8 |
+
|
| 9 |
+
CURRENT_DIR = pathlib.Path(__file__).parent
|
| 10 |
+
sys.path.append(str(CURRENT_DIR))
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def make_dirs(path='coco'):
|
| 14 |
+
# Create folders
|
| 15 |
+
path = Path(path)
|
| 16 |
+
for p in [path / 'labels']:
|
| 17 |
+
p.mkdir(parents=True, exist_ok=True) # make dir
|
| 18 |
+
return path
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def coco91_to_coco80_class(): # converts 80-index (val2014) to 91-index (paper)
|
| 22 |
+
# https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
|
| 23 |
+
x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, None, 11, 12, 13, 14, 15, 16, 17,
|
| 24 |
+
18, 19, 20, 21, 22, 23, None, 24, 25, None, None, 26, 27, 28, 29,
|
| 25 |
+
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, None, 40, 41, 42, 43, 44,
|
| 26 |
+
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, None,
|
| 27 |
+
60, None, None, 61, None, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
|
| 28 |
+
72, None, 73, 74, 75, 76, 77, 78, 79, None]
|
| 29 |
+
return x
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def convert_coco_json(
|
| 33 |
+
json_dir='coco/annotations/',
|
| 34 |
+
use_segments=False,
|
| 35 |
+
cls91to80=False):
|
| 36 |
+
save_dir = make_dirs() # output directory
|
| 37 |
+
coco80 = coco91_to_coco80_class()
|
| 38 |
+
"""Convert raw COCO dataset to YOLO style
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
# Import json
|
| 42 |
+
for json_file in sorted(Path(json_dir).resolve().glob('instances_val2017.json')):
|
| 43 |
+
fn = Path(save_dir) / 'labels' / \
|
| 44 |
+
json_file.stem.replace('instances_', '') # folder name
|
| 45 |
+
fn.mkdir()
|
| 46 |
+
with open(json_file) as f:
|
| 47 |
+
data = json.load(f)
|
| 48 |
+
|
| 49 |
+
# Create image dict
|
| 50 |
+
images = {'%g' % x['id']: x for x in data['images']}
|
| 51 |
+
# Create image-annotations dict
|
| 52 |
+
imgToAnns = defaultdict(list)
|
| 53 |
+
for ann in data['annotations']:
|
| 54 |
+
imgToAnns[ann['image_id']].append(ann)
|
| 55 |
+
|
| 56 |
+
txt_file = open(Path(save_dir / 'val2017').
|
| 57 |
+
with_suffix('.txt'), 'a')
|
| 58 |
+
# Write labels file
|
| 59 |
+
for img_id, anns in tqdm(
|
| 60 |
+
imgToAnns.items(), desc=f'Annotations {json_file}'):
|
| 61 |
+
img = images['%g' % img_id]
|
| 62 |
+
h, w, f = img['height'], img['width'], img['file_name']
|
| 63 |
+
bboxes = []
|
| 64 |
+
segments = []
|
| 65 |
+
|
| 66 |
+
txt_file.write(
|
| 67 |
+
'./images/' + '/'.
|
| 68 |
+
join(img['coco_url'].split('/')[-2:]) + '\n')
|
| 69 |
+
for ann in anns:
|
| 70 |
+
if ann['iscrowd']:
|
| 71 |
+
continue
|
| 72 |
+
# The COCO box format is
|
| 73 |
+
# [top left x, top left y, width,
|
| 74 |
+
# height]
|
| 75 |
+
box = np.array(ann['bbox'], dtype=np.float64)
|
| 76 |
+
box[:2] += box[2:] / 2 # xy top-left corner to center
|
| 77 |
+
box[[0, 2]] /= w # normalize x
|
| 78 |
+
box[[1, 3]] /= h # normalize y
|
| 79 |
+
if box[2] <= 0 or box[3] <= 0: # if w <= 0 and h <= 0
|
| 80 |
+
continue
|
| 81 |
+
cls = coco80[ann['category_id'] - 1] \
|
| 82 |
+
if cls91to80 else ann['category_id'] - 1 # class
|
| 83 |
+
box = [cls] + box.tolist()
|
| 84 |
+
if box not in bboxes:
|
| 85 |
+
bboxes.append(box)
|
| 86 |
+
# Segments
|
| 87 |
+
if use_segments:
|
| 88 |
+
if len(ann['segmentation']) > 1:
|
| 89 |
+
s = merge_multi_segment(ann['segmentation'])
|
| 90 |
+
s = (np.concatenate(s, axis=0) /
|
| 91 |
+
np.array([w, h])).reshape(-1).tolist()
|
| 92 |
+
else:
|
| 93 |
+
s = [j for i in ann['segmentation']
|
| 94 |
+
for j in i] # all segments concatenated
|
| 95 |
+
s = (np.array(s).reshape(-1, 2) /
|
| 96 |
+
np.array([w, h])).reshape(-1).tolist()
|
| 97 |
+
s = [cls] + s
|
| 98 |
+
if s not in segments:
|
| 99 |
+
segments.append(s)
|
| 100 |
+
|
| 101 |
+
# Write
|
| 102 |
+
with open((fn / f).with_suffix('.txt'), 'a') as file:
|
| 103 |
+
for i in range(len(bboxes)):
|
| 104 |
+
# cls, box or segments
|
| 105 |
+
line = *(segments[i] if
|
| 106 |
+
use_segments else bboxes[i]),
|
| 107 |
+
file.write(('%g ' * len(line)).
|
| 108 |
+
rstrip() % line + '\n')
|
| 109 |
+
txt_file.close()
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def min_index(arr1, arr2):
|
| 113 |
+
"""Find a pair of indexes with the shortest distance.
|
| 114 |
+
Args:
|
| 115 |
+
arr1: (N, 2).
|
| 116 |
+
arr2: (M, 2).
|
| 117 |
+
Return:
|
| 118 |
+
a pair of indexes(tuple).
|
| 119 |
+
"""
|
| 120 |
+
dis = ((arr1[:, None, :] - arr2[None, :, :]) ** 2).sum(-1)
|
| 121 |
+
return np.unravel_index(np.argmin(dis, axis=None), dis.shape)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def merge_multi_segment(segments):
|
| 125 |
+
"""Merge multi segments to one list.
|
| 126 |
+
Find the coordinates with min distance between each segment,
|
| 127 |
+
then connect these coordinates with one thin line to merge all
|
| 128 |
+
segments into one.
|
| 129 |
+
|
| 130 |
+
Args:
|
| 131 |
+
segments(List(List)): original
|
| 132 |
+
segmentations in coco's json file.
|
| 133 |
+
like [segmentation1, segmentation2,...],
|
| 134 |
+
each segmentation is a list of coordinates.
|
| 135 |
+
"""
|
| 136 |
+
s = []
|
| 137 |
+
segments = [np.array(i).reshape(-1, 2) for i in segments]
|
| 138 |
+
idx_list = [[] for _ in range(len(segments))]
|
| 139 |
+
|
| 140 |
+
# record the indexes with min distance between each segment
|
| 141 |
+
for i in range(1, len(segments)):
|
| 142 |
+
idx1, idx2 = min_index(segments[i - 1], segments[i])
|
| 143 |
+
idx_list[i - 1].append(idx1)
|
| 144 |
+
idx_list[i].append(idx2)
|
| 145 |
+
|
| 146 |
+
# use two round to connect all the segments
|
| 147 |
+
for k in range(2):
|
| 148 |
+
# forward connection
|
| 149 |
+
if k == 0:
|
| 150 |
+
for i, idx in enumerate(idx_list):
|
| 151 |
+
# middle segments have two indexes
|
| 152 |
+
# reverse the index of middle segments
|
| 153 |
+
if len(idx) == 2 and idx[0] > idx[1]:
|
| 154 |
+
idx = idx[::-1]
|
| 155 |
+
segments[i] = segments[i][::-1, :]
|
| 156 |
+
|
| 157 |
+
segments[i] = np.roll(segments[i], -idx[0], axis=0)
|
| 158 |
+
segments[i] = np.concatenate([segments[i],
|
| 159 |
+
segments[i][:1]])
|
| 160 |
+
# deal with the first segment and the last one
|
| 161 |
+
if i in [0, len(idx_list) - 1]:
|
| 162 |
+
s.append(segments[i])
|
| 163 |
+
else:
|
| 164 |
+
idx = [0, idx[1] - idx[0]]
|
| 165 |
+
s.append(segments[i][idx[0]:idx[1] + 1])
|
| 166 |
+
|
| 167 |
+
else:
|
| 168 |
+
for i in range(len(idx_list) - 1, -1, -1):
|
| 169 |
+
if i not in [0, len(idx_list) - 1]:
|
| 170 |
+
idx = idx_list[i]
|
| 171 |
+
nidx = abs(idx[1] - idx[0])
|
| 172 |
+
s.append(segments[i][nidx:])
|
| 173 |
+
return s
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
if __name__ == '__main__':
|
| 177 |
+
convert_coco_json('coco/annotations',
|
| 178 |
+
use_segments=False,
|
| 179 |
+
cls91to80=True)
|
onnx_inference.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import onnxruntime
|
| 2 |
+
import argparse
|
| 3 |
+
import os
|
| 4 |
+
from utils import *
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def pre_process(img):
|
| 8 |
+
"""
|
| 9 |
+
Preprocessing part of YOLOv3 for scaling and padding image as input to the network.
|
| 10 |
+
Args:
|
| 11 |
+
img (numpy.ndarray): H x W x C, image read with OpenCV
|
| 12 |
+
Returns:
|
| 13 |
+
padded_img (numpy.ndarray): preprocessed image to be fed to the network
|
| 14 |
+
"""
|
| 15 |
+
img = letterbox(img, auto=False)[0]
|
| 16 |
+
# Convert
|
| 17 |
+
img = img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
|
| 18 |
+
img = np.ascontiguousarray(img)
|
| 19 |
+
img = img.astype("float32")
|
| 20 |
+
img = img / 255.0
|
| 21 |
+
img = img[np.newaxis, :]
|
| 22 |
+
return img
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def post_process(x, conf_thres=0.1, iou_thres=0.6, multi_label=True,
|
| 26 |
+
classes=None, agnostic=False):
|
| 27 |
+
"""
|
| 28 |
+
Post-processing part of YOLOv3 for generating final results from outputs of the network.
|
| 29 |
+
Returns:
|
| 30 |
+
pred (torch.tensor): n x 6, dets[:,:4] -> boxes, dets[:,4] -> scores, dets[:,5] -> class indices
|
| 31 |
+
"""
|
| 32 |
+
stride = [32, 16, 8]
|
| 33 |
+
anchors = [[10, 13, 16, 30, 33, 23],
|
| 34 |
+
[30, 61, 62, 45, 59, 119],
|
| 35 |
+
[116, 90, 156, 198, 373, 326]]
|
| 36 |
+
temp = [13, 26, 52]
|
| 37 |
+
res = []
|
| 38 |
+
|
| 39 |
+
def create_grids(ng=(13, 13)):
|
| 40 |
+
nx, ny = ng # x and y grid size
|
| 41 |
+
ng = torch.tensor(ng, dtype=torch.float)
|
| 42 |
+
|
| 43 |
+
# build xy offsets
|
| 44 |
+
yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
|
| 45 |
+
grid = torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
|
| 46 |
+
|
| 47 |
+
return grid
|
| 48 |
+
|
| 49 |
+
for i in range(3):
|
| 50 |
+
out = torch.from_numpy(x[i])
|
| 51 |
+
|
| 52 |
+
bs, _, ny, nx = out.shape # bs, 255, 13, 13
|
| 53 |
+
|
| 54 |
+
anchor = torch.Tensor(anchors[2 - i]).reshape(3, 2)
|
| 55 |
+
anchor_vec = anchor / stride[i]
|
| 56 |
+
anchor_wh = anchor_vec.view(1, 3, 1, 1, 2)
|
| 57 |
+
|
| 58 |
+
grid = create_grids((nx, ny))
|
| 59 |
+
|
| 60 |
+
out = out.view(
|
| 61 |
+
bs, 3, 85, temp[i], temp[i]).permute(
|
| 62 |
+
0, 1, 3, 4, 2).contiguous() # prediction
|
| 63 |
+
|
| 64 |
+
io = out.clone()
|
| 65 |
+
|
| 66 |
+
io[..., :2] = torch.sigmoid(io[..., :2]) + grid
|
| 67 |
+
io[..., 2:4] = torch.exp(io[..., 2:4]) * anchor_wh
|
| 68 |
+
io[..., :4] *= stride[i]
|
| 69 |
+
torch.sigmoid_(io[..., 4:])
|
| 70 |
+
|
| 71 |
+
res.append(io.view(bs, -1, 85))
|
| 72 |
+
|
| 73 |
+
pred = non_max_suppression(torch.cat(res, 1), conf_thres,
|
| 74 |
+
iou_thres, multi_label=multi_label,
|
| 75 |
+
classes=classes, agnostic=agnostic)
|
| 76 |
+
|
| 77 |
+
return pred
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
if __name__ == '__main__':
|
| 81 |
+
parser = argparse.ArgumentParser(
|
| 82 |
+
prog='One image inference of onnx model')
|
| 83 |
+
parser.add_argument(
|
| 84 |
+
'--img',
|
| 85 |
+
type=str,
|
| 86 |
+
help='Path of input image')
|
| 87 |
+
parser.add_argument(
|
| 88 |
+
'--out',
|
| 89 |
+
type=str,
|
| 90 |
+
default='.',
|
| 91 |
+
help='Path of out put image')
|
| 92 |
+
parser.add_argument(
|
| 93 |
+
"--ipu",
|
| 94 |
+
action="store_true",
|
| 95 |
+
help="Use IPU for inference.")
|
| 96 |
+
parser.add_argument(
|
| 97 |
+
"--provider_config",
|
| 98 |
+
type=str,
|
| 99 |
+
default="vaip_config.json",
|
| 100 |
+
help="Path of the config file for seting provider_options.")
|
| 101 |
+
parser.add_argument(
|
| 102 |
+
"--onnx_path",
|
| 103 |
+
type=str,
|
| 104 |
+
default="yolov3-8.onnx",
|
| 105 |
+
help="Path of the onnx model.")
|
| 106 |
+
|
| 107 |
+
opt = parser.parse_args()
|
| 108 |
+
with open('coco.names', 'r') as f:
|
| 109 |
+
names = f.read()
|
| 110 |
+
|
| 111 |
+
if opt.ipu:
|
| 112 |
+
providers = ["VitisAIExecutionProvider"]
|
| 113 |
+
provider_options = [{"config_file": opt.provider_config}]
|
| 114 |
+
else:
|
| 115 |
+
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
| 116 |
+
provider_options = None
|
| 117 |
+
|
| 118 |
+
onnx_path = opt.onnx_path
|
| 119 |
+
onnx_model = onnxruntime.InferenceSession(
|
| 120 |
+
onnx_path, providers=providers, provider_options=provider_options)
|
| 121 |
+
|
| 122 |
+
path = opt.img
|
| 123 |
+
new_path = os.path.join(opt.out, "demo_infer.jpg")
|
| 124 |
+
|
| 125 |
+
conf_thres, iou_thres, classes, agnostic_nms, max_det = 0.25, \
|
| 126 |
+
0.45, None, False, 1000
|
| 127 |
+
|
| 128 |
+
img0 = cv2.imread(path)
|
| 129 |
+
img = pre_process(img0)
|
| 130 |
+
onnx_input = {onnx_model.get_inputs()[0].name: img}
|
| 131 |
+
onnx_output = onnx_model.run(None, onnx_input)
|
| 132 |
+
pred = post_process(onnx_output, conf_thres,
|
| 133 |
+
iou_thres, multi_label=False,
|
| 134 |
+
classes=classes, agnostic=agnostic_nms)
|
| 135 |
+
|
| 136 |
+
colors = [[random.randint(0, 255) for _ in range(3)]
|
| 137 |
+
for _ in range(len(names))]
|
| 138 |
+
det = pred[0]
|
| 139 |
+
im0 = img0.copy()
|
| 140 |
+
|
| 141 |
+
if len(det):
|
| 142 |
+
# Rescale boxes from imgsz to im0 size
|
| 143 |
+
det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round()
|
| 144 |
+
|
| 145 |
+
# Write results
|
| 146 |
+
for *xyxy, conf, cls in reversed(det):
|
| 147 |
+
label = '%s %.2f' % (names[int(cls)], conf)
|
| 148 |
+
plot_one_box(xyxy, im0, label=label, color=colors[int(cls)])
|
| 149 |
+
|
| 150 |
+
# Stream results
|
| 151 |
+
cv2.imwrite(new_path, im0)
|
onnx_test.py
ADDED
|
@@ -0,0 +1,1247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
import glob
|
| 7 |
+
import math
|
| 8 |
+
from PIL import ExifTags, Image
|
| 9 |
+
import shutil
|
| 10 |
+
from torch.utils.data import DataLoader
|
| 11 |
+
from torch.utils.data import Dataset
|
| 12 |
+
from utils import *
|
| 13 |
+
import onnxruntime
|
| 14 |
+
import matplotlib.pyplot as plt
|
| 15 |
+
|
| 16 |
+
for orientation in ExifTags.TAGS.keys():
|
| 17 |
+
if ExifTags.TAGS[orientation] == 'Orientation':
|
| 18 |
+
break
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def create_folder(path='./new_folder'):
|
| 22 |
+
# Create folder
|
| 23 |
+
if os.path.exists(path):
|
| 24 |
+
shutil.rmtree(path) # delete output folder
|
| 25 |
+
os.makedirs(path) # make new output folder
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def exif_size(img):
|
| 29 |
+
# Returns exif-corrected PIL size
|
| 30 |
+
s = img.size # (width, height)
|
| 31 |
+
try:
|
| 32 |
+
rotation = dict(img._getexif().items())[orientation]
|
| 33 |
+
if rotation == 6: # rotation 270
|
| 34 |
+
s = (s[1], s[0])
|
| 35 |
+
elif rotation == 8: # rotation 90
|
| 36 |
+
s = (s[1], s[0])
|
| 37 |
+
except BaseException:
|
| 38 |
+
pass
|
| 39 |
+
|
| 40 |
+
return s
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def ap_per_class(tp, conf, pred_cls, target_cls):
|
| 44 |
+
""" Compute the average precision, given the recall and precision curves.
|
| 45 |
+
Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
|
| 46 |
+
# Arguments
|
| 47 |
+
tp: True positives (nparray, nx1 or nx10).
|
| 48 |
+
conf: Objectness value from 0-1 (nparray).
|
| 49 |
+
pred_cls: Predicted object classes (nparray).
|
| 50 |
+
target_cls: True object classes (nparray).
|
| 51 |
+
# Returns
|
| 52 |
+
The average precision as computed in py-faster-rcnn.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
# Sort by objectness
|
| 56 |
+
i = np.argsort(-conf)
|
| 57 |
+
tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
|
| 58 |
+
|
| 59 |
+
# Find unique classes
|
| 60 |
+
unique_classes = np.unique(target_cls)
|
| 61 |
+
|
| 62 |
+
# Create Precision-Recall curve and compute AP for each class
|
| 63 |
+
pr_score = 0.1
|
| 64 |
+
# score to evaluate P and R
|
| 65 |
+
# https://github.com/ultralytics/yolov3/issues/898
|
| 66 |
+
# number class, number iou thresholds (i.e. 10 for mAP0.5...0.95)
|
| 67 |
+
s = [unique_classes.shape[0], tp.shape[1]]
|
| 68 |
+
ap, p, r = np.zeros(s), np.zeros(s), np.zeros(s)
|
| 69 |
+
for ci, c in enumerate(unique_classes):
|
| 70 |
+
i = pred_cls == c
|
| 71 |
+
n_gt = (target_cls == c).sum() # Number of ground truth objects
|
| 72 |
+
n_p = i.sum() # Number of predicted objects
|
| 73 |
+
|
| 74 |
+
if n_p == 0 or n_gt == 0:
|
| 75 |
+
continue
|
| 76 |
+
else:
|
| 77 |
+
# Accumulate FPs and TPs
|
| 78 |
+
fpc = (1 - tp[i]).cumsum(0)
|
| 79 |
+
tpc = tp[i].cumsum(0)
|
| 80 |
+
|
| 81 |
+
# Recall
|
| 82 |
+
recall = tpc / (n_gt + 1e-16) # recall curve
|
| 83 |
+
# r at pr_score, negative x, xp because xp decreases
|
| 84 |
+
r[ci] = np.interp(-pr_score, -conf[i], recall[:, 0])
|
| 85 |
+
|
| 86 |
+
# Precision
|
| 87 |
+
precision = tpc / (tpc + fpc) # precision curve
|
| 88 |
+
p[ci] = np.interp(-pr_score, -conf[i],
|
| 89 |
+
precision[:, 0]) # p at pr_score
|
| 90 |
+
|
| 91 |
+
# AP from recall-precision curve
|
| 92 |
+
for j in range(tp.shape[1]):
|
| 93 |
+
ap[ci, j] = compute_ap(recall[:, j], precision[:, j])
|
| 94 |
+
|
| 95 |
+
# Plot
|
| 96 |
+
# fig, ax = plt.subplots(1, 1, figsize=(5, 5))
|
| 97 |
+
# ax.plot(recall, precision)
|
| 98 |
+
# ax.set_xlabel('Recall')
|
| 99 |
+
# ax.set_ylabel('Precision')
|
| 100 |
+
# ax.set_xlim(0, 1.01)
|
| 101 |
+
# ax.set_ylim(0, 1.01)
|
| 102 |
+
# fig.tight_layout()
|
| 103 |
+
# fig.savefig('PR_curve.png', dpi=300)
|
| 104 |
+
|
| 105 |
+
# Compute F1 score (harmonic mean of precision and recall)
|
| 106 |
+
f1 = 2 * p * r / (p + r + 1e-16)
|
| 107 |
+
|
| 108 |
+
return p, r, ap, f1, unique_classes.astype('int32')
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def time_synchronized():
|
| 112 |
+
torch.cuda.synchronize() if torch.cuda.is_available() else None
|
| 113 |
+
return time.time()
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def plot_images(
|
| 117 |
+
images,
|
| 118 |
+
targets,
|
| 119 |
+
paths=None,
|
| 120 |
+
fname='images.jpg',
|
| 121 |
+
names=None,
|
| 122 |
+
max_size=640,
|
| 123 |
+
max_subplots=16):
|
| 124 |
+
tl = 3 # line thickness
|
| 125 |
+
tf = max(tl - 1, 1) # font thickness
|
| 126 |
+
if os.path.isfile(fname): # do not overwrite
|
| 127 |
+
return None
|
| 128 |
+
|
| 129 |
+
if isinstance(images, torch.Tensor):
|
| 130 |
+
images = images.cpu().numpy()
|
| 131 |
+
|
| 132 |
+
if isinstance(targets, torch.Tensor):
|
| 133 |
+
targets = targets.cpu().numpy()
|
| 134 |
+
|
| 135 |
+
# un-normalise
|
| 136 |
+
if np.max(images[0]) <= 1:
|
| 137 |
+
images *= 255
|
| 138 |
+
|
| 139 |
+
bs, _, h, w = images.shape # batch size, _, height, width
|
| 140 |
+
bs = min(bs, max_subplots) # limit plot images
|
| 141 |
+
ns = np.ceil(bs ** 0.5) # number of subplots (square)
|
| 142 |
+
|
| 143 |
+
# Check if we should resize
|
| 144 |
+
scale_factor = max_size / max(h, w)
|
| 145 |
+
if scale_factor < 1:
|
| 146 |
+
h = math.ceil(scale_factor * h)
|
| 147 |
+
w = math.ceil(scale_factor * w)
|
| 148 |
+
|
| 149 |
+
# Empty array for output
|
| 150 |
+
mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8)
|
| 151 |
+
|
| 152 |
+
# Fix class - colour map
|
| 153 |
+
prop_cycle = plt.rcParams['axes.prop_cycle']
|
| 154 |
+
|
| 155 |
+
# https://stackoverflow.com/questions/51350872/python-from-color-name-to-rgb
|
| 156 |
+
def hex2rgb(h):
|
| 157 |
+
return tuple(
|
| 158 |
+
int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))
|
| 159 |
+
|
| 160 |
+
color_lut = [hex2rgb(h) for h in prop_cycle.by_key()['color']]
|
| 161 |
+
|
| 162 |
+
for i, img in enumerate(images):
|
| 163 |
+
if i == max_subplots: # if last batch has fewer images than we expect
|
| 164 |
+
break
|
| 165 |
+
|
| 166 |
+
block_x = int(w * (i // ns))
|
| 167 |
+
block_y = int(h * (i % ns))
|
| 168 |
+
|
| 169 |
+
img = img.transpose(1, 2, 0)
|
| 170 |
+
if scale_factor < 1:
|
| 171 |
+
img = cv2.resize(img, (w, h))
|
| 172 |
+
|
| 173 |
+
mosaic[block_y:block_y + h, block_x:block_x + w, :] = img
|
| 174 |
+
if len(targets) > 0:
|
| 175 |
+
image_targets = targets[targets[:, 0] == i]
|
| 176 |
+
boxes = xywh2xyxy(image_targets[:, 2:6]).T
|
| 177 |
+
classes = image_targets[:, 1].astype('int')
|
| 178 |
+
gt = image_targets.shape[1] == 6
|
| 179 |
+
# ground truth if no conf column
|
| 180 |
+
# check for confidence presence (gt vs pred)
|
| 181 |
+
conf = None if gt else image_targets[:, 6]
|
| 182 |
+
|
| 183 |
+
boxes[[0, 2]] *= w
|
| 184 |
+
boxes[[0, 2]] += block_x
|
| 185 |
+
boxes[[1, 3]] *= h
|
| 186 |
+
boxes[[1, 3]] += block_y
|
| 187 |
+
for j, box in enumerate(boxes.T):
|
| 188 |
+
cls = int(classes[j])
|
| 189 |
+
color = color_lut[cls % len(color_lut)]
|
| 190 |
+
cls = names[cls] if names else cls
|
| 191 |
+
if gt or conf[j] > 0.3: # 0.3 conf thresh
|
| 192 |
+
label = '%s' % cls if gt else '%s %.1f' % (cls, conf[j])
|
| 193 |
+
plot_one_box(box, mosaic, label=label,
|
| 194 |
+
color=color, line_thickness=tl)
|
| 195 |
+
|
| 196 |
+
# Draw image filename labels
|
| 197 |
+
if paths is not None:
|
| 198 |
+
label = os.path.basename(paths[i])[:40] # trim to 40 char
|
| 199 |
+
t_size = cv2.getTextSize(
|
| 200 |
+
label, 0, fontScale=tl / 3, thickness=tf)[0]
|
| 201 |
+
cv2.putText(mosaic, label, (block_x +
|
| 202 |
+
5, block_y +
|
| 203 |
+
t_size[1] +
|
| 204 |
+
5), 0, tl /
|
| 205 |
+
3, [220, 220, 220], thickness=tf, lineType=cv2.LINE_AA)
|
| 206 |
+
|
| 207 |
+
# Image border
|
| 208 |
+
cv2.rectangle(mosaic, (block_x, block_y), (block_x + w,
|
| 209 |
+
block_y + h), (255, 255, 255), thickness=3)
|
| 210 |
+
|
| 211 |
+
if fname is not None:
|
| 212 |
+
mosaic = cv2.resize(mosaic,
|
| 213 |
+
(int(ns * w * 0.5),
|
| 214 |
+
int(ns * h * 0.5)),
|
| 215 |
+
interpolation=cv2.INTER_AREA)
|
| 216 |
+
cv2.imwrite(fname, cv2.cvtColor(mosaic, cv2.COLOR_BGR2RGB))
|
| 217 |
+
|
| 218 |
+
return mosaic
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def random_affine(img, targets=(), degrees=10, translate=.1,
|
| 222 |
+
scale=.1, shear=10, border=0):
|
| 223 |
+
# targets = [cls, xyxy]
|
| 224 |
+
|
| 225 |
+
height = img.shape[0] + border * 2
|
| 226 |
+
width = img.shape[1] + border * 2
|
| 227 |
+
|
| 228 |
+
# Rotation and Scale
|
| 229 |
+
R = np.eye(3)
|
| 230 |
+
a = random.uniform(-degrees, degrees)
|
| 231 |
+
# a += random.choice([-180, -90, 0, 90])
|
| 232 |
+
# add 90deg rotations to small rotations
|
| 233 |
+
s = random.uniform(1 - scale, 1 + scale)
|
| 234 |
+
# s = 2 ** random.uniform(-scale, scale)
|
| 235 |
+
R[:2] = cv2.getRotationMatrix2D(angle=a,
|
| 236 |
+
center=(img.shape[1] / 2,
|
| 237 |
+
img.shape[0] / 2),
|
| 238 |
+
scale=s)
|
| 239 |
+
|
| 240 |
+
# Translation
|
| 241 |
+
T = np.eye(3)
|
| 242 |
+
T[0, 2] = (random.uniform(-translate, translate) *
|
| 243 |
+
img.shape[0] + border) # x translation (pixels)
|
| 244 |
+
T[1, 2] = (random.uniform(-translate, translate) *
|
| 245 |
+
img.shape[1] + border) # y translation (pixels)
|
| 246 |
+
|
| 247 |
+
# Shear
|
| 248 |
+
S = np.eye(3)
|
| 249 |
+
S[0, 1] = math.tan(random.uniform(-shear, shear) *
|
| 250 |
+
math.pi / 180) # x shear (deg)
|
| 251 |
+
S[1, 0] = math.tan(random.uniform(-shear, shear) *
|
| 252 |
+
math.pi / 180) # y shear (deg)
|
| 253 |
+
|
| 254 |
+
# Combined rotation matrix
|
| 255 |
+
M = S @ T @ R # ORDER IS IMPORTANT HERE!!
|
| 256 |
+
if (border != 0) or (M != np.eye(3)).any(): # image changed
|
| 257 |
+
img = cv2.warpAffine(img, M[:2], dsize=(width, height),
|
| 258 |
+
flags=cv2.INTER_LINEAR,
|
| 259 |
+
borderValue=(114, 114, 114))
|
| 260 |
+
|
| 261 |
+
# Transform label coordinates
|
| 262 |
+
n = len(targets)
|
| 263 |
+
if n:
|
| 264 |
+
# warp points
|
| 265 |
+
xy = np.ones((n * 4, 3))
|
| 266 |
+
xy[:, :2] = (targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].
|
| 267 |
+
reshape(n * 4, 2))
|
| 268 |
+
# x1y1, x2y2, x1y2, x2y1
|
| 269 |
+
xy = (xy @ M.T)[:, :2].reshape(n, 8)
|
| 270 |
+
|
| 271 |
+
# create new boxes
|
| 272 |
+
x = xy[:, [0, 2, 4, 6]]
|
| 273 |
+
y = xy[:, [1, 3, 5, 7]]
|
| 274 |
+
xy = np.concatenate((x.min(1), y.min(1), x.max(1),
|
| 275 |
+
y.max(1))).reshape(4, n).T
|
| 276 |
+
|
| 277 |
+
# reject warped points outside of image
|
| 278 |
+
xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width)
|
| 279 |
+
xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height)
|
| 280 |
+
w = xy[:, 2] - xy[:, 0]
|
| 281 |
+
h = xy[:, 3] - xy[:, 1]
|
| 282 |
+
area = w * h
|
| 283 |
+
area0 = ((targets[:, 3] - targets[:, 1]) *
|
| 284 |
+
(targets[:, 4] - targets[:, 2]))
|
| 285 |
+
ar = np.maximum(w / (h + 1e-16), h / (w + 1e-16))
|
| 286 |
+
# aspect ratio
|
| 287 |
+
i = (w > 4) & (h > 4) & (area / (area0 * s + 1e-16)
|
| 288 |
+
> 0.2) & (ar < 10)
|
| 289 |
+
|
| 290 |
+
targets = targets[i]
|
| 291 |
+
targets[:, 1:5] = xy[i]
|
| 292 |
+
|
| 293 |
+
return img, targets
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def output_to_target(output, width, height):
|
| 297 |
+
"""
|
| 298 |
+
Convert a YOLO model output to target format
|
| 299 |
+
[batch_id, class_id, x, y, w, h, conf]
|
| 300 |
+
"""
|
| 301 |
+
if isinstance(output, torch.Tensor):
|
| 302 |
+
output = output.cpu().numpy()
|
| 303 |
+
|
| 304 |
+
targets = []
|
| 305 |
+
for i, o in enumerate(output):
|
| 306 |
+
if o is not None:
|
| 307 |
+
for pred in o:
|
| 308 |
+
box = pred[:4]
|
| 309 |
+
w = (box[2] - box[0]) / width
|
| 310 |
+
h = (box[3] - box[1]) / height
|
| 311 |
+
x = box[0] / width + w / 2
|
| 312 |
+
y = box[1] / height + h / 2
|
| 313 |
+
conf = pred[4]
|
| 314 |
+
cls = int(pred[5])
|
| 315 |
+
|
| 316 |
+
targets.append([i, cls, x, y, w, h, conf])
|
| 317 |
+
|
| 318 |
+
return np.array(targets)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def xyxy2xywh(x):
|
| 322 |
+
# Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where
|
| 323 |
+
# xy1=top-left, xy2=bottom-right
|
| 324 |
+
y = torch.zeros_like(x) if isinstance(
|
| 325 |
+
x, torch.Tensor) else np.zeros_like(x)
|
| 326 |
+
y[:, 0] = (x[:, 0] + x[:, 2]) / 2 # x center
|
| 327 |
+
y[:, 1] = (x[:, 1] + x[:, 3]) / 2 # y center
|
| 328 |
+
y[:, 2] = x[:, 2] - x[:, 0] # width
|
| 329 |
+
y[:, 3] = x[:, 3] - x[:, 1] # height
|
| 330 |
+
return y
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)
|
| 334 |
+
# https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
|
| 335 |
+
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20,
|
| 336 |
+
21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
| 337 |
+
41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
|
| 338 |
+
59, 60, 61, 62, 63, 64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79,
|
| 339 |
+
80, 81, 82, 84, 85, 86, 87, 88, 89, 90]
|
| 340 |
+
return x
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def check_file(file):
|
| 344 |
+
# Searches for file if not found locally
|
| 345 |
+
if os.path.isfile(file):
|
| 346 |
+
return file
|
| 347 |
+
else:
|
| 348 |
+
files = glob.glob('./**/' + file, recursive=True) # find file
|
| 349 |
+
assert len(files), 'File Not Found: %s' % file # assert file was found
|
| 350 |
+
return files[0] # return first file if multiple found
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def load_classes(path):
|
| 354 |
+
# Loads *.names file at 'path'
|
| 355 |
+
with open(path, 'r') as f:
|
| 356 |
+
names = f.read().split('\n')
|
| 357 |
+
# filter removes empty strings (such as last line)
|
| 358 |
+
return list(filter(None, names))
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
def load_image(self, index):
|
| 362 |
+
# loads 1 image from dataset, returns img, original hw, resized hw
|
| 363 |
+
img = self.imgs[index]
|
| 364 |
+
if img is None: # not cached
|
| 365 |
+
path = self.img_files[index]
|
| 366 |
+
img = cv2.imread(path) # BGR
|
| 367 |
+
assert img is not None, 'Image Not Found ' + path
|
| 368 |
+
h0, w0 = img.shape[:2] # orig hw
|
| 369 |
+
r = self.img_size / max(h0, w0) # resize image to img_size
|
| 370 |
+
if r != 1:
|
| 371 |
+
# always resize down, only resize up if training with augmentation
|
| 372 |
+
interp = cv2.INTER_AREA if r < 1 and not self.augment \
|
| 373 |
+
else cv2.INTER_LINEAR
|
| 374 |
+
img = cv2.resize(img, (int(w0 * r), int(h0 * r)),
|
| 375 |
+
interpolation=interp)
|
| 376 |
+
return img, (h0, w0), img.shape[:2] # img, hw_original, hw_resized
|
| 377 |
+
else:
|
| 378 |
+
# img, hw_original, hw_resized
|
| 379 |
+
return self.imgs[index], self.img_hw0[index], self.img_hw[index]
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def load_mosaic(self, index):
|
| 383 |
+
# loads images in a mosaic
|
| 384 |
+
|
| 385 |
+
labels4 = []
|
| 386 |
+
s = self.img_size
|
| 387 |
+
xc, yc = [int(random.uniform(s * 0.5, s * 1.5))
|
| 388 |
+
for _ in range(2)] # mosaic center x, y
|
| 389 |
+
indices = [index] + [random.randint(0, len(self.labels) - 1)
|
| 390 |
+
for _ in range(3)] # 3 additional image indices
|
| 391 |
+
for i, index in enumerate(indices):
|
| 392 |
+
# Load image
|
| 393 |
+
img, _, (h, w) = load_image(self, index)
|
| 394 |
+
|
| 395 |
+
# place img in img4
|
| 396 |
+
if i == 0: # top left
|
| 397 |
+
img4 = np.full((s * 2, s * 2, img.shape[2]),
|
| 398 |
+
114, dtype=np.uint8)
|
| 399 |
+
# base image with 4 tiles
|
| 400 |
+
x1a, y1a, x2a, y2a = (max(xc - w, 0),
|
| 401 |
+
max(yc - h, 0), xc, yc)
|
| 402 |
+
# xmin, ymin, xmax, ymax (large image)
|
| 403 |
+
x1b, y1b, x2b, y2b = (w - (x2a - x1a), h -
|
| 404 |
+
(y2a - y1a), w, h)
|
| 405 |
+
# xmin, ymin, xmax, ymax (small image)
|
| 406 |
+
elif i == 1: # top right
|
| 407 |
+
x1a, y1a, x2a, y2a = (xc, max(yc - h, 0),
|
| 408 |
+
min(xc + w, s * 2), yc)
|
| 409 |
+
x1b, y1b, x2b, y2b = (0, h - (y2a - y1a),
|
| 410 |
+
min(w, x2a - x1a), h)
|
| 411 |
+
elif i == 2: # bottom left
|
| 412 |
+
x1a, y1a, x2a, y2a = (max(xc - w, 0), yc,
|
| 413 |
+
xc, min(s * 2, yc + h))
|
| 414 |
+
x1b, y1b, x2b, y2b = (w - (x2a - x1a), 0,
|
| 415 |
+
max(xc, w), min(y2a - y1a, h))
|
| 416 |
+
elif i == 3: # bottom right
|
| 417 |
+
x1a, y1a, x2a, y2a = xc, yc, min(xc + w,
|
| 418 |
+
s * 2), min(s * 2, yc + h)
|
| 419 |
+
x1b, y1b, x2b, y2b = (0, 0,
|
| 420 |
+
min(w, x2a - x1a), min(y2a - y1a, h))
|
| 421 |
+
|
| 422 |
+
img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]
|
| 423 |
+
# img4[ymin:ymax, xmin:xmax]
|
| 424 |
+
padw = x1a - x1b
|
| 425 |
+
padh = y1a - y1b
|
| 426 |
+
|
| 427 |
+
# Labels
|
| 428 |
+
x = self.labels[index]
|
| 429 |
+
labels = x.copy()
|
| 430 |
+
if x.size > 0: # Normalized xywh to pixel xyxy format
|
| 431 |
+
labels[:, 1] = w * (x[:, 1] - x[:, 3] / 2) + padw
|
| 432 |
+
labels[:, 2] = h * (x[:, 2] - x[:, 4] / 2) + padh
|
| 433 |
+
labels[:, 3] = w * (x[:, 1] + x[:, 3] / 2) + padw
|
| 434 |
+
labels[:, 4] = h * (x[:, 2] + x[:, 4] / 2) + padh
|
| 435 |
+
labels4.append(labels)
|
| 436 |
+
|
| 437 |
+
# Concat/clip labels
|
| 438 |
+
if len(labels4):
|
| 439 |
+
labels4 = np.concatenate(labels4, 0)
|
| 440 |
+
# np.clip(labels4[:, 1:] - s / 2, 0, s, out=labels4[:, 1:])
|
| 441 |
+
# use with center crop
|
| 442 |
+
np.clip(labels4[:, 1:], 0, 2 * s, out=labels4[:, 1:])
|
| 443 |
+
# use with random_affine
|
| 444 |
+
|
| 445 |
+
# Augment
|
| 446 |
+
# img4 = img4[s // 2: int(s * 1.5), s // 2:int(s * 1.5)]
|
| 447 |
+
# center crop (WARNING, requires box pruning)
|
| 448 |
+
img4, labels4 = random_affine(img4, labels4,
|
| 449 |
+
degrees=self.hyp['degrees'],
|
| 450 |
+
translate=self.hyp['translate'],
|
| 451 |
+
scale=self.hyp['scale'],
|
| 452 |
+
shear=self.hyp['shear'],
|
| 453 |
+
border=-s // 2) # border to remove
|
| 454 |
+
|
| 455 |
+
return img4, labels4
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
def compute_ap(recall, precision):
|
| 459 |
+
""" Compute the average precision, given the recall and precision curves.
|
| 460 |
+
Source: https://github.com/rbgirshick/py-faster-rcnn.
|
| 461 |
+
# Arguments
|
| 462 |
+
recall: The recall curve (list).
|
| 463 |
+
precision: The precision curve (list).
|
| 464 |
+
# Returns
|
| 465 |
+
The average precision as computed in py-faster-rcnn.
|
| 466 |
+
"""
|
| 467 |
+
|
| 468 |
+
# Append sentinel values to beginning and end
|
| 469 |
+
mrec = np.concatenate(([0.], recall, [min(recall[-1] + 1E-3, 1.)]))
|
| 470 |
+
mpre = np.concatenate(([0.], precision, [0.]))
|
| 471 |
+
|
| 472 |
+
# Compute the precision envelope
|
| 473 |
+
mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))
|
| 474 |
+
|
| 475 |
+
# Integrate area under curve
|
| 476 |
+
method = 'interp' # methods: 'continuous', 'interp'
|
| 477 |
+
if method == 'interp':
|
| 478 |
+
x = np.linspace(0, 1, 101) # 101-point interp (COCO)
|
| 479 |
+
ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate
|
| 480 |
+
else: # 'continuous'
|
| 481 |
+
# points where x axis (recall) changes
|
| 482 |
+
i = np.where(mrec[1:] != mrec[:-1])[0]
|
| 483 |
+
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve
|
| 484 |
+
|
| 485 |
+
return ap
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
def augment_hsv(img, hgain=0.5, sgain=0.5, vgain=0.5):
|
| 489 |
+
r = (np.random.uniform(-1, 1, 3) *
|
| 490 |
+
[hgain, sgain, vgain] + 1) # random gains
|
| 491 |
+
hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
|
| 492 |
+
dtype = img.dtype # uint8
|
| 493 |
+
|
| 494 |
+
x = np.arange(0, 256, dtype=np.int16)
|
| 495 |
+
lut_hue = ((x * r[0]) % 180).astype(dtype)
|
| 496 |
+
lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
|
| 497 |
+
lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
|
| 498 |
+
|
| 499 |
+
img_hsv = cv2.merge((cv2.LUT(hue, lut_hue),
|
| 500 |
+
cv2.LUT(sat, lut_sat),
|
| 501 |
+
cv2.LUT(val, lut_val))).astype(dtype)
|
| 502 |
+
cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img)
|
| 503 |
+
# no return needed
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
class LoadImagesAndLabels(Dataset): # for training/testing
|
| 507 |
+
def __init__(
|
| 508 |
+
self,
|
| 509 |
+
path,
|
| 510 |
+
img_size=416,
|
| 511 |
+
batch_size=16,
|
| 512 |
+
augment=False,
|
| 513 |
+
hyp=None,
|
| 514 |
+
rect=False,
|
| 515 |
+
image_weights=False,
|
| 516 |
+
cache_images=False,
|
| 517 |
+
single_cls=False,
|
| 518 |
+
pad=0.0):
|
| 519 |
+
try:
|
| 520 |
+
path = str(Path(path)) # os-agnostic
|
| 521 |
+
parent = str(Path(path).parent) + os.sep
|
| 522 |
+
if os.path.isfile(path): # file
|
| 523 |
+
with open(path, 'r') as f:
|
| 524 |
+
f = f.read().splitlines()
|
| 525 |
+
# local to global path
|
| 526 |
+
f = [
|
| 527 |
+
x.replace(
|
| 528 |
+
'./',
|
| 529 |
+
parent) if x.startswith('./') else x for x in f]
|
| 530 |
+
elif os.path.isdir(path): # folder
|
| 531 |
+
f = glob.iglob(path + os.sep + '*.*')
|
| 532 |
+
else:
|
| 533 |
+
raise Exception('%s does not exist' % path)
|
| 534 |
+
self.img_files = [x.replace(
|
| 535 |
+
'/', os.sep) for x in f if
|
| 536 |
+
os.path.splitext(x)[-1].lower() in img_formats]
|
| 537 |
+
|
| 538 |
+
except BaseException:
|
| 539 |
+
raise Exception(
|
| 540 |
+
'Error loading data from %s. See %s' %
|
| 541 |
+
(path, help_url))
|
| 542 |
+
|
| 543 |
+
n = len(self.img_files)
|
| 544 |
+
assert n > 0, 'No images found in %s. See %s' % (path, help_url)
|
| 545 |
+
bi = np.floor(np.arange(n) / batch_size).astype(int) # batch index
|
| 546 |
+
nb = bi[-1] + 1 # number of batches
|
| 547 |
+
|
| 548 |
+
self.n = n # number of images
|
| 549 |
+
self.batch = bi # batch index of image
|
| 550 |
+
self.img_size = img_size
|
| 551 |
+
self.augment = augment
|
| 552 |
+
self.hyp = hyp
|
| 553 |
+
self.image_weights = image_weights
|
| 554 |
+
self.rect = False if image_weights else rect
|
| 555 |
+
# load 4 images at a time into a mosaic (only during training)
|
| 556 |
+
self.mosaic = self.augment and not self.rect
|
| 557 |
+
|
| 558 |
+
# Define labels
|
| 559 |
+
self.label_files = [x.replace('images', 'labels').replace(
|
| 560 |
+
os.path.splitext(x)[-1], '.txt') for x in self.img_files]
|
| 561 |
+
|
| 562 |
+
# Read image shapes (wh)
|
| 563 |
+
sp = path.replace('.txt', '') + '.shapes' # shapefile path
|
| 564 |
+
try:
|
| 565 |
+
with open(sp, 'r') as f: # read existing shapefile
|
| 566 |
+
s = [x.split() for x in f.read().splitlines()]
|
| 567 |
+
assert len(s) == n, 'Shapefile out of sync'
|
| 568 |
+
except BaseException:
|
| 569 |
+
s = [exif_size(Image.open(f)) for f in tqdm(
|
| 570 |
+
self.img_files,
|
| 571 |
+
desc='Reading image shapes')]
|
| 572 |
+
np.savetxt(sp, s, fmt='%g') # overwrites existing (if any)
|
| 573 |
+
|
| 574 |
+
self.shapes = np.array(s, dtype=np.float64)
|
| 575 |
+
|
| 576 |
+
# Rectangular Training
|
| 577 |
+
# https://github.com/ultralytics/yolov3/issues/232
|
| 578 |
+
if self.rect:
|
| 579 |
+
# Sort by aspect ratio
|
| 580 |
+
s = self.shapes # wh
|
| 581 |
+
ar = s[:, 1] / s[:, 0] # aspect ratio
|
| 582 |
+
irect = ar.argsort()
|
| 583 |
+
self.img_files = [self.img_files[i] for i in irect]
|
| 584 |
+
self.label_files = [self.label_files[i] for i in irect]
|
| 585 |
+
self.shapes = s[irect] # wh
|
| 586 |
+
ar = ar[irect]
|
| 587 |
+
|
| 588 |
+
# Set training image shapes
|
| 589 |
+
shapes = [[1, 1]] * nb
|
| 590 |
+
for i in range(nb):
|
| 591 |
+
ari = ar[bi == i]
|
| 592 |
+
mini, maxi = ari.min(), ari.max()
|
| 593 |
+
if maxi < 1:
|
| 594 |
+
shapes[i] = [maxi, 1]
|
| 595 |
+
elif mini > 1:
|
| 596 |
+
shapes[i] = [1, 1 / mini]
|
| 597 |
+
|
| 598 |
+
self.batch_shapes = np.ceil(
|
| 599 |
+
np.array(shapes) * img_size / 32. + pad).astype(int) * 32
|
| 600 |
+
|
| 601 |
+
# Cache labels
|
| 602 |
+
self.imgs = [None] * n
|
| 603 |
+
self.labels = [np.zeros((0, 5), dtype=np.float32)] * n
|
| 604 |
+
create_datasubset, extract_bounding_boxes, labels_loaded = \
|
| 605 |
+
False, False, False
|
| 606 |
+
# number missing, found, empty, datasubset, duplicate
|
| 607 |
+
nm, nf, ne, ns, nd = 0, 0, 0, 0, 0
|
| 608 |
+
# saved labels in *.npy file
|
| 609 |
+
np_labels_path = str(Path(self.label_files[0]).parent) + '.npy'
|
| 610 |
+
if os.path.isfile(np_labels_path):
|
| 611 |
+
s = np_labels_path # print string
|
| 612 |
+
|
| 613 |
+
print(np_labels_path)
|
| 614 |
+
|
| 615 |
+
x = np.load(np_labels_path, allow_pickle=True)
|
| 616 |
+
if len(x) == n:
|
| 617 |
+
self.labels = x
|
| 618 |
+
labels_loaded = True
|
| 619 |
+
else:
|
| 620 |
+
s = path.replace('images', 'labels')
|
| 621 |
+
|
| 622 |
+
pbar = tqdm(self.label_files)
|
| 623 |
+
for i, file in enumerate(pbar):
|
| 624 |
+
if labels_loaded:
|
| 625 |
+
l = self.labels[i]
|
| 626 |
+
# np.savetxt(file, l, '%g') # save *.txt from *.npy file
|
| 627 |
+
else:
|
| 628 |
+
try:
|
| 629 |
+
with open(file, 'r') as f:
|
| 630 |
+
l = np.array(
|
| 631 |
+
[x.split() for x in f.read().splitlines()],
|
| 632 |
+
dtype=np.float32)
|
| 633 |
+
except BaseException:
|
| 634 |
+
# print('missing labels for image %s' % self.img_files[i])
|
| 635 |
+
# # file missing
|
| 636 |
+
nm += 1
|
| 637 |
+
continue
|
| 638 |
+
|
| 639 |
+
if l.shape[0]:
|
| 640 |
+
assert l.shape[1] == 5, '> 5 label columns: %s' % file
|
| 641 |
+
assert (l >= 0).all(), 'negative labels: %s' % file
|
| 642 |
+
assert (l[:, 1:] <= 1).all(
|
| 643 |
+
), 'non-normalized or out of bounds coordinate labels: %s' % file
|
| 644 |
+
if np.unique(
|
| 645 |
+
l, axis=0).shape[0] < l.shape[0]: # duplicate rows
|
| 646 |
+
# print('WARNING: duplicate rows in %s' %
|
| 647 |
+
# self.label_files[i]) # duplicate rows
|
| 648 |
+
nd += 1
|
| 649 |
+
if single_cls:
|
| 650 |
+
l[:, 0] = 0 # force dataset into single-class mode
|
| 651 |
+
self.labels[i] = l
|
| 652 |
+
nf += 1 # file found
|
| 653 |
+
|
| 654 |
+
# Create subdataset (a smaller dataset)
|
| 655 |
+
if create_datasubset and ns < 1E4:
|
| 656 |
+
if ns == 0:
|
| 657 |
+
create_folder(path='./datasubset')
|
| 658 |
+
os.makedirs('./datasubset/images')
|
| 659 |
+
exclude_classes = 43
|
| 660 |
+
if exclude_classes not in l[:, 0]:
|
| 661 |
+
ns += 1
|
| 662 |
+
# shutil.copy(src=self.img_files[i],
|
| 663 |
+
# dst='./datasubset/images/') # copy image
|
| 664 |
+
with open('./datasubset/images.txt', 'a') as f:
|
| 665 |
+
f.write(self.img_files[i] + '\n')
|
| 666 |
+
|
| 667 |
+
# Extract object detection boxes for a second stage classifier
|
| 668 |
+
if extract_bounding_boxes:
|
| 669 |
+
p = Path(self.img_files[i])
|
| 670 |
+
img = cv2.imread(str(p))
|
| 671 |
+
h, w = img.shape[:2]
|
| 672 |
+
for j, x in enumerate(l):
|
| 673 |
+
f = '%s%sclassifier%s%g_%g_%s' % (
|
| 674 |
+
p.parent.parent, os.sep, os.sep, x[0], j, p.name)
|
| 675 |
+
if not os.path.exists(Path(f).parent):
|
| 676 |
+
# make new output folder
|
| 677 |
+
os.makedirs(Path(f).parent)
|
| 678 |
+
|
| 679 |
+
b = x[1:] * [w, h, w, h] # box
|
| 680 |
+
b[2:] = b[2:].max() # rectangle to square
|
| 681 |
+
b[2:] = b[2:] * 1.3 + 30 # pad
|
| 682 |
+
b = xywh2xyxy(b.reshape(-1, 4)).ravel().astype(int)
|
| 683 |
+
|
| 684 |
+
# clip boxes outside of image
|
| 685 |
+
b[[0, 2]] = np.clip(b[[0, 2]], 0, w)
|
| 686 |
+
b[[1, 3]] = np.clip(b[[1, 3]], 0, h)
|
| 687 |
+
assert cv2.imwrite(
|
| 688 |
+
f, img[b[1]:b[3], b[0]:b[2]]), \
|
| 689 |
+
'Failure extracting classifier boxes'
|
| 690 |
+
else:
|
| 691 |
+
# print('empty labels for image %s' % self.img_files[i]) #
|
| 692 |
+
# file empty
|
| 693 |
+
ne += 1
|
| 694 |
+
# os.system("rm '%s' '%s'" % (self.img_files[i],
|
| 695 |
+
# self.label_files[i])) # remove
|
| 696 |
+
|
| 697 |
+
pbar.desc = 'Caching labels %s (%g found, %g missing, %g empty,\
|
| 698 |
+
%g duplicate, for %g images)' % (
|
| 699 |
+
s, nf, nm, ne, nd, n)
|
| 700 |
+
assert nf > 0 or n == 20288, 'No labels found in %s. See %s' % (
|
| 701 |
+
os.path.dirname(file) + os.sep, help_url)
|
| 702 |
+
if not labels_loaded and n > 1000:
|
| 703 |
+
print(
|
| 704 |
+
'Saving labels to %s for faster future loading' %
|
| 705 |
+
np_labels_path)
|
| 706 |
+
# np.save(np_labels_path, self.labels) # save for next time
|
| 707 |
+
|
| 708 |
+
# Cache images into memory for faster training (WARNING: large datasets
|
| 709 |
+
# may exceed system RAM)
|
| 710 |
+
if cache_images: # if training
|
| 711 |
+
gb = 0 # Gigabytes of cached images
|
| 712 |
+
pbar = tqdm(range(len(self.img_files)), desc='Caching images')
|
| 713 |
+
self.img_hw0, self.img_hw = [None] * n, [None] * n
|
| 714 |
+
for i in pbar: # max 10k images
|
| 715 |
+
self.imgs[i], self.img_hw0[i], self.img_hw[i] = load_image(
|
| 716 |
+
self, i) # img, hw_original, hw_resized
|
| 717 |
+
gb += self.imgs[i].nbytes
|
| 718 |
+
pbar.desc = 'Caching images (%.1fGB)' % (gb / 1E9)
|
| 719 |
+
|
| 720 |
+
def __len__(self):
|
| 721 |
+
return len(self.img_files)
|
| 722 |
+
|
| 723 |
+
def __getitem__(self, index):
|
| 724 |
+
if self.image_weights:
|
| 725 |
+
index = self.indices[index]
|
| 726 |
+
|
| 727 |
+
hyp = self.hyp
|
| 728 |
+
if self.mosaic:
|
| 729 |
+
# Load mosaic
|
| 730 |
+
img, labels = load_mosaic(self, index)
|
| 731 |
+
shapes = None
|
| 732 |
+
|
| 733 |
+
else:
|
| 734 |
+
# Load image
|
| 735 |
+
img, (h0, w0), (h, w) = load_image(self, index)
|
| 736 |
+
|
| 737 |
+
# Letterbox
|
| 738 |
+
shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size # final letterboxed shape
|
| 739 |
+
img, ratio, pad = letterbox(img, shape, auto=False, scaleup=self.augment)
|
| 740 |
+
shapes = (h0, w0), ((h / h0, w / w0), pad) # for COCO mAP rescaling
|
| 741 |
+
|
| 742 |
+
# Load labels
|
| 743 |
+
labels = []
|
| 744 |
+
x = self.labels[index]
|
| 745 |
+
if x.size > 0:
|
| 746 |
+
# Normalized xywh to pixel xyxy format
|
| 747 |
+
labels = x.copy()
|
| 748 |
+
labels[:, 1] = ratio[0] * w * (x[:, 1] - x[:, 3] / 2) + pad[0] # pad width
|
| 749 |
+
labels[:, 2] = ratio[1] * h * (x[:, 2] - x[:, 4] / 2) + pad[1] # pad height
|
| 750 |
+
labels[:, 3] = ratio[0] * w * (x[:, 1] + x[:, 3] / 2) + pad[0]
|
| 751 |
+
labels[:, 4] = ratio[1] * h * (x[:, 2] + x[:, 4] / 2) + pad[1]
|
| 752 |
+
|
| 753 |
+
if self.augment:
|
| 754 |
+
# Augment imagespace
|
| 755 |
+
if not self.mosaic:
|
| 756 |
+
img, labels = random_affine(img, labels,
|
| 757 |
+
degrees=hyp['degrees'],
|
| 758 |
+
translate=hyp['translate'],
|
| 759 |
+
scale=hyp['scale'],
|
| 760 |
+
shear=hyp['shear'])
|
| 761 |
+
|
| 762 |
+
# Augment colorspace
|
| 763 |
+
augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
|
| 764 |
+
|
| 765 |
+
# Apply cutouts
|
| 766 |
+
# if random.random() < 0.9:
|
| 767 |
+
# labels = cutout(img, labels)
|
| 768 |
+
|
| 769 |
+
nL = len(labels) # number of labels
|
| 770 |
+
if nL:
|
| 771 |
+
# convert xyxy to xywh
|
| 772 |
+
labels[:, 1:5] = xyxy2xywh(labels[:, 1:5])
|
| 773 |
+
|
| 774 |
+
# Normalize coordinates 0 - 1
|
| 775 |
+
labels[:, [2, 4]] /= img.shape[0] # height
|
| 776 |
+
labels[:, [1, 3]] /= img.shape[1] # width
|
| 777 |
+
|
| 778 |
+
if self.augment:
|
| 779 |
+
# random left-right flip
|
| 780 |
+
lr_flip = True
|
| 781 |
+
if lr_flip and random.random() < 0.5:
|
| 782 |
+
img = np.fliplr(img)
|
| 783 |
+
if nL:
|
| 784 |
+
labels[:, 1] = 1 - labels[:, 1]
|
| 785 |
+
|
| 786 |
+
# random up-down flip
|
| 787 |
+
ud_flip = False
|
| 788 |
+
if ud_flip and random.random() < 0.5:
|
| 789 |
+
img = np.flipud(img)
|
| 790 |
+
if nL:
|
| 791 |
+
labels[:, 2] = 1 - labels[:, 2]
|
| 792 |
+
|
| 793 |
+
labels_out = torch.zeros((nL, 6))
|
| 794 |
+
if nL:
|
| 795 |
+
labels_out[:, 1:] = torch.from_numpy(labels)
|
| 796 |
+
|
| 797 |
+
# Convert
|
| 798 |
+
img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
|
| 799 |
+
img = np.ascontiguousarray(img)
|
| 800 |
+
|
| 801 |
+
return torch.from_numpy(img), labels_out, self.img_files[index], shapes
|
| 802 |
+
|
| 803 |
+
@staticmethod
|
| 804 |
+
def collate_fn(batch):
|
| 805 |
+
img, label, path, shapes = zip(*batch) # transposed
|
| 806 |
+
for i, l in enumerate(label):
|
| 807 |
+
l[:, 0] = i # add target image index for build_targets()
|
| 808 |
+
return torch.stack(img, 0), torch.cat(label, 0), path, shapes
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
def parse_data_cfg(path):
|
| 812 |
+
# Parses the data configuration file
|
| 813 |
+
if not os.path.exists(path) and os.path.exists(
|
| 814 |
+
'data' + os.sep + path): # add data/ prefix if omitted
|
| 815 |
+
path = 'data' + os.sep + path
|
| 816 |
+
|
| 817 |
+
with open(path, 'r') as f:
|
| 818 |
+
lines = f.readlines()
|
| 819 |
+
|
| 820 |
+
options = dict()
|
| 821 |
+
for line in lines:
|
| 822 |
+
line = line.strip()
|
| 823 |
+
if line == '' or line.startswith('#'):
|
| 824 |
+
continue
|
| 825 |
+
key, val = line.split('=')
|
| 826 |
+
options[key.strip()] = val.strip()
|
| 827 |
+
|
| 828 |
+
return options
|
| 829 |
+
|
| 830 |
+
|
| 831 |
+
def create_grids(ng=(13, 13), device='cpu'):
|
| 832 |
+
nx, ny = ng # x and y grid size
|
| 833 |
+
ng = torch.tensor(ng, dtype=torch.float)
|
| 834 |
+
|
| 835 |
+
# build xy offsets
|
| 836 |
+
yv, xv = torch.meshgrid(
|
| 837 |
+
[torch.arange(ny, device=device), torch.arange(nx, device=device)])
|
| 838 |
+
grid = torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()
|
| 839 |
+
|
| 840 |
+
return grid
|
| 841 |
+
|
| 842 |
+
|
| 843 |
+
def post_process(x):
|
| 844 |
+
stride = [32, 16, 8]
|
| 845 |
+
anchors = [[10, 13, 16, 30, 33, 23],
|
| 846 |
+
[30, 61, 62, 45, 59, 119],
|
| 847 |
+
[116, 90, 156, 198, 373, 326]]
|
| 848 |
+
temp = [13, 26, 52]
|
| 849 |
+
|
| 850 |
+
res = []
|
| 851 |
+
for i in range(3):
|
| 852 |
+
out = torch.from_numpy(x[i]) if not torch.is_tensor(x[i]) else x[i]
|
| 853 |
+
|
| 854 |
+
bs, _, ny, nx = out.shape # bs, 255, 13, 13
|
| 855 |
+
|
| 856 |
+
anchor = torch.Tensor(anchors[2 - i]).reshape(3, 2)
|
| 857 |
+
anchor_vec = anchor / stride[i]
|
| 858 |
+
anchor_wh = anchor_vec.view(1, 3, 1, 1, 2)
|
| 859 |
+
|
| 860 |
+
grid = create_grids((nx, ny))
|
| 861 |
+
|
| 862 |
+
# p.view(bs, 255, 13, 13) -- > (bs, 3, 13, 13, 85) # (bs, anchors,
|
| 863 |
+
# grid, grid, classes + xywh)
|
| 864 |
+
out = out.view(
|
| 865 |
+
bs, 3, 85, temp[i], temp[i]).permute(
|
| 866 |
+
0, 1, 3, 4, 2).contiguous() # prediction
|
| 867 |
+
|
| 868 |
+
io = out.clone() # inference output
|
| 869 |
+
|
| 870 |
+
io[..., :2] = torch.sigmoid(io[..., :2]) + grid # xy
|
| 871 |
+
io[..., 2:4] = torch.exp(io[..., 2:4]) * anchor_wh # wh yolo method
|
| 872 |
+
io[..., :4] *= stride[i]
|
| 873 |
+
torch.sigmoid_(io[..., 4:])
|
| 874 |
+
|
| 875 |
+
res.append(io.view(bs, -1, 85))
|
| 876 |
+
return torch.cat(res, 1), x
|
| 877 |
+
|
| 878 |
+
|
| 879 |
+
def test(data,
|
| 880 |
+
batch_size=32,
|
| 881 |
+
imgsz=416,
|
| 882 |
+
conf_thres=0.001,
|
| 883 |
+
iou_thres=0.6, # for nms
|
| 884 |
+
save_json=False,
|
| 885 |
+
single_cls=False,
|
| 886 |
+
augment=False,
|
| 887 |
+
model=None,
|
| 888 |
+
dataloader=None,
|
| 889 |
+
multi_label=True,
|
| 890 |
+
names='data/coco.names',
|
| 891 |
+
onnx_runtime=True,
|
| 892 |
+
onnx_weights="yolov3-8",
|
| 893 |
+
ipu=False,
|
| 894 |
+
provider_config='vaip_config.json'):
|
| 895 |
+
"""
|
| 896 |
+
COCO average precision (AP) Evaluation. Iterate inference on the test dataset
|
| 897 |
+
and the results are evaluated by COCO API.
|
| 898 |
+
"""
|
| 899 |
+
|
| 900 |
+
device = torch.device('cpu')
|
| 901 |
+
verbose = False
|
| 902 |
+
if isinstance(onnx_weights, list):
|
| 903 |
+
onnx_weights = onnx_weights[0]
|
| 904 |
+
|
| 905 |
+
if ipu:
|
| 906 |
+
providers = ["VitisAIExecutionProvider"]
|
| 907 |
+
provider_options = [{"config_file": provider_config}]
|
| 908 |
+
else:
|
| 909 |
+
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
| 910 |
+
provider_options = None
|
| 911 |
+
|
| 912 |
+
onnx_model = onnxruntime.InferenceSession(
|
| 913 |
+
onnx_weights,
|
| 914 |
+
providers=providers,
|
| 915 |
+
provider_options=provider_options)
|
| 916 |
+
|
| 917 |
+
# Configure run
|
| 918 |
+
data = parse_data_cfg(data)
|
| 919 |
+
nc = 1 if single_cls else int(data['classes']) # number of classes
|
| 920 |
+
path = data['valid'] # path to test images
|
| 921 |
+
names = load_classes(data['names']) # class names
|
| 922 |
+
iouv = torch.linspace(0.5, 0.95, 10).to(
|
| 923 |
+
device) # iou vector for [email protected]:0.95
|
| 924 |
+
iouv = iouv[0].view(1) # comment for [email protected]:0.95
|
| 925 |
+
niou = iouv.numel()
|
| 926 |
+
|
| 927 |
+
# Dataloader
|
| 928 |
+
if dataloader is None:
|
| 929 |
+
dataset = LoadImagesAndLabels(
|
| 930 |
+
path,
|
| 931 |
+
imgsz,
|
| 932 |
+
batch_size,
|
| 933 |
+
rect=False,
|
| 934 |
+
single_cls=opt.single_cls,
|
| 935 |
+
pad=0.5)
|
| 936 |
+
batch_size = min(batch_size, len(dataset))
|
| 937 |
+
dataloader = DataLoader(dataset,
|
| 938 |
+
batch_size=batch_size,
|
| 939 |
+
num_workers=min([os.cpu_count(),
|
| 940 |
+
batch_size if
|
| 941 |
+
batch_size > 1 else 0,
|
| 942 |
+
8]),
|
| 943 |
+
pin_memory=True,
|
| 944 |
+
collate_fn=dataset.collate_fn)
|
| 945 |
+
|
| 946 |
+
seen = 0
|
| 947 |
+
|
| 948 |
+
coco91class = coco80_to_coco91_class()
|
| 949 |
+
s = ('%20s' + '%10s' * 6) % ('Class', 'Images', 'Targets', 'P', 'R',
|
| 950 |
+
'[email protected]', 'F1')
|
| 951 |
+
p, r, f1, mp, mr, map, mf1, t0, t1 = 0., 0., 0., 0., 0., 0., 0., 0., 0.
|
| 952 |
+
loss = torch.zeros(3, device=device)
|
| 953 |
+
jdict, stats, ap, ap_class = [], [], [], []
|
| 954 |
+
|
| 955 |
+
for batch_i, (imgs, targets, paths, shapes) in enumerate(
|
| 956 |
+
tqdm(dataloader, desc=s)):
|
| 957 |
+
# uint8 to float32, 0 - 255 to 0.0 - 1.0
|
| 958 |
+
imgs = imgs.to(device).float() / 255.0
|
| 959 |
+
targets = targets.to(device)
|
| 960 |
+
nb, _, height, width = imgs.shape
|
| 961 |
+
# batch size, channels, height, width
|
| 962 |
+
whwh = torch.Tensor([width, height, width, height]).to(device)
|
| 963 |
+
|
| 964 |
+
if onnx_runtime:
|
| 965 |
+
outputs = onnx_model.run(
|
| 966 |
+
None, {onnx_model.get_inputs()[0].name: imgs.cpu().numpy()})
|
| 967 |
+
outputs = [torch.tensor(item).to(device) for item in outputs]
|
| 968 |
+
inf_out, train_out = post_process(outputs)
|
| 969 |
+
|
| 970 |
+
else:
|
| 971 |
+
|
| 972 |
+
# Disable gradients
|
| 973 |
+
with torch.no_grad():
|
| 974 |
+
# Run model
|
| 975 |
+
t = time_synchronized()
|
| 976 |
+
|
| 977 |
+
# inference and training outputs
|
| 978 |
+
inf_out, train_out = model(imgs, augment=augment)
|
| 979 |
+
t0 += time_synchronized() - t
|
| 980 |
+
|
| 981 |
+
# Compute loss
|
| 982 |
+
# if is_training: # if model has loss hyperparameters
|
| 983 |
+
# loss += compute_loss(train_out, targets, model)[1][:3] # GIoU,
|
| 984 |
+
# obj, cls
|
| 985 |
+
|
| 986 |
+
# Run NMS
|
| 987 |
+
t = time_synchronized()
|
| 988 |
+
output = non_max_suppression(
|
| 989 |
+
inf_out,
|
| 990 |
+
conf_thres=conf_thres,
|
| 991 |
+
iou_thres=iou_thres,
|
| 992 |
+
multi_label=multi_label)
|
| 993 |
+
t1 += time_synchronized() - t
|
| 994 |
+
|
| 995 |
+
# Statistics per image
|
| 996 |
+
for si, pred in enumerate(output):
|
| 997 |
+
labels = targets[targets[:, 0] == si, 1:]
|
| 998 |
+
nl = len(labels)
|
| 999 |
+
tcls = labels[:, 0].tolist() if nl else [] # target class
|
| 1000 |
+
seen += 1
|
| 1001 |
+
|
| 1002 |
+
if pred is None:
|
| 1003 |
+
if nl:
|
| 1004 |
+
stats.append(
|
| 1005 |
+
(torch.zeros(
|
| 1006 |
+
0,
|
| 1007 |
+
niou,
|
| 1008 |
+
dtype=torch.bool),
|
| 1009 |
+
torch.Tensor(),
|
| 1010 |
+
torch.Tensor(),
|
| 1011 |
+
tcls))
|
| 1012 |
+
continue
|
| 1013 |
+
|
| 1014 |
+
# Append to text file
|
| 1015 |
+
# with open('test.txt', 'a') as file:
|
| 1016 |
+
# [file.write('%11.5g' * 7 % tuple(x) + '\n') for x in pred]
|
| 1017 |
+
|
| 1018 |
+
# Clip boxes to image bounds
|
| 1019 |
+
clip_coords(pred, (height, width))
|
| 1020 |
+
|
| 1021 |
+
# Append to pycocotools JSON dictionary
|
| 1022 |
+
if save_json:
|
| 1023 |
+
image_id = int(Path(paths[si]).stem.split('_')[-1])
|
| 1024 |
+
box = pred[:, :4].clone() # xyxy
|
| 1025 |
+
scale_coords(imgs[si].shape[1:], box, shapes[si]
|
| 1026 |
+
[0], shapes[si][1]) # to original shape
|
| 1027 |
+
box = xyxy2xywh(box) # xywh
|
| 1028 |
+
box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
|
| 1029 |
+
for p, b in zip(pred.tolist(), box.tolist()):
|
| 1030 |
+
jdict.append({'image_id': image_id,
|
| 1031 |
+
'category_id': coco91class[int(p[5])],
|
| 1032 |
+
'bbox': [round(x, 3) for x in b],
|
| 1033 |
+
'score': round(p[4], 5)})
|
| 1034 |
+
|
| 1035 |
+
# Assign all predictions as incorrect
|
| 1036 |
+
correct = torch.zeros(
|
| 1037 |
+
pred.shape[0],
|
| 1038 |
+
niou,
|
| 1039 |
+
dtype=torch.bool,
|
| 1040 |
+
device=device)
|
| 1041 |
+
if nl:
|
| 1042 |
+
detected = [] # target indices
|
| 1043 |
+
tcls_tensor = labels[:, 0]
|
| 1044 |
+
|
| 1045 |
+
# target boxes
|
| 1046 |
+
tbox = xywh2xyxy(labels[:, 1:5]) * whwh
|
| 1047 |
+
|
| 1048 |
+
# Per target class
|
| 1049 |
+
for cls in torch.unique(tcls_tensor):
|
| 1050 |
+
ti = (cls == tcls_tensor).nonzero(
|
| 1051 |
+
).view(-1) # target indices
|
| 1052 |
+
pi = (cls == pred[:, 5]).nonzero(
|
| 1053 |
+
).view(-1) # prediction indices
|
| 1054 |
+
|
| 1055 |
+
# Search for detections
|
| 1056 |
+
if pi.shape[0]:
|
| 1057 |
+
# Prediction to target ious
|
| 1058 |
+
ious, i = box_iou(pred[pi, :4], tbox[ti].cpu()).max(
|
| 1059 |
+
1) # best ious, indices
|
| 1060 |
+
|
| 1061 |
+
# Append detections
|
| 1062 |
+
for j in (ious > iouv[0].cpu()).nonzero():
|
| 1063 |
+
d = ti[i[j]] # detected target
|
| 1064 |
+
if d not in detected:
|
| 1065 |
+
detected.append(d)
|
| 1066 |
+
# iou_thres is 1xn
|
| 1067 |
+
correct[pi[j]] = ious[j] > iouv.cpu()
|
| 1068 |
+
if len(
|
| 1069 |
+
detected) == nl:
|
| 1070 |
+
# all targets already located in image
|
| 1071 |
+
break
|
| 1072 |
+
|
| 1073 |
+
# Append statistics (correct, conf, pcls, tcls)
|
| 1074 |
+
stats.append(
|
| 1075 |
+
(correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls))
|
| 1076 |
+
|
| 1077 |
+
# Plot images
|
| 1078 |
+
if batch_i < 1:
|
| 1079 |
+
f = 'test_batch%g_gt.jpg' % batch_i # filename
|
| 1080 |
+
plot_images(imgs, targets, paths=paths, names=names,
|
| 1081 |
+
fname=f) # ground truth
|
| 1082 |
+
f = 'test_batch%g_pred.jpg' % batch_i
|
| 1083 |
+
plot_images(imgs, output_to_target(output, width, height),
|
| 1084 |
+
paths=paths, names=names, fname=f) # predictions
|
| 1085 |
+
|
| 1086 |
+
# test end
|
| 1087 |
+
|
| 1088 |
+
# Compute statistics
|
| 1089 |
+
stats = [np.concatenate(x, 0) for x in zip(*stats)] # to numpy
|
| 1090 |
+
if len(stats):
|
| 1091 |
+
p, r, ap, f1, ap_class = ap_per_class(*stats)
|
| 1092 |
+
if niou > 1:
|
| 1093 |
+
p, r, ap, f1 = p[:, 0], r[:, 0], ap.mean(
|
| 1094 |
+
1), ap[:, 0] # [P, R, [email protected]:0.95, [email protected]]
|
| 1095 |
+
mp, mr, map, mf1 = p.mean(), r.mean(), ap.mean(), f1.mean()
|
| 1096 |
+
nt = np.bincount(stats[3].astype(np.int64),
|
| 1097 |
+
minlength=nc) # number of targets per class
|
| 1098 |
+
else:
|
| 1099 |
+
nt = torch.zeros(1)
|
| 1100 |
+
|
| 1101 |
+
# Print results
|
| 1102 |
+
pf = '%20s' + '%10.3g' * 6 # print format
|
| 1103 |
+
print(pf % ('all', seen, nt.sum(), mp, mr, map, mf1))
|
| 1104 |
+
|
| 1105 |
+
# Print results per class
|
| 1106 |
+
if verbose and nc > 1 and len(stats):
|
| 1107 |
+
for i, c in enumerate(ap_class):
|
| 1108 |
+
print(pf % (names[c], seen, nt[c], p[i], r[i], ap[i], f1[i]))
|
| 1109 |
+
|
| 1110 |
+
# Print speeds
|
| 1111 |
+
if verbose or save_json:
|
| 1112 |
+
t = tuple(x / seen * 1E3 for x in (t0, t1, t0 + t1)) + \
|
| 1113 |
+
(imgsz, imgsz, batch_size) # tuple
|
| 1114 |
+
print(
|
| 1115 |
+
'Speed: %.1f/%.1f/%.1f ms \
|
| 1116 |
+
inference/NMS/total per %gx%g image at batch-size %g' % t)
|
| 1117 |
+
|
| 1118 |
+
# Save JSON
|
| 1119 |
+
if save_json and map and len(jdict):
|
| 1120 |
+
print('\nCOCO mAP with pycocotools...')
|
| 1121 |
+
imgIds = [int(Path(x).stem.split('_')[-1])
|
| 1122 |
+
for x in dataloader.dataset.img_files]
|
| 1123 |
+
with open('results.json', 'w') as file:
|
| 1124 |
+
json.dump(jdict, file)
|
| 1125 |
+
|
| 1126 |
+
try:
|
| 1127 |
+
from pycocotools.coco import COCO
|
| 1128 |
+
from pycocotools.cocoeval import COCOeval
|
| 1129 |
+
|
| 1130 |
+
# https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
|
| 1131 |
+
# initialize COCO ground truth api
|
| 1132 |
+
cocoGt = COCO(
|
| 1133 |
+
glob.glob('coco/annotations/instances_val*.json')[0])
|
| 1134 |
+
cocoDt = cocoGt.loadRes('results.json') # initialize COCO pred api
|
| 1135 |
+
cocoEval = COCOeval(cocoGt, cocoDt, 'bbox')
|
| 1136 |
+
# [:32] # only evaluate these images
|
| 1137 |
+
cocoEval.params.imgIds = imgIds
|
| 1138 |
+
cocoEval.evaluate()
|
| 1139 |
+
cocoEval.accumulate()
|
| 1140 |
+
cocoEval.summarize()
|
| 1141 |
+
# mf1, map = cocoEval.stats[:2] # update to pycocotools results
|
| 1142 |
+
# ([email protected]:0.95, [email protected])
|
| 1143 |
+
except BaseException:
|
| 1144 |
+
print(
|
| 1145 |
+
'WARNING: pycocotools must be installed with \
|
| 1146 |
+
numpy==1.17 to run correctly. '
|
| 1147 |
+
'See https://github.com/cocodataset/cocoapi/issues/356')
|
| 1148 |
+
|
| 1149 |
+
# Return results
|
| 1150 |
+
maps = np.zeros(nc) + map
|
| 1151 |
+
for i, c in enumerate(ap_class):
|
| 1152 |
+
maps[c] = ap[i]
|
| 1153 |
+
return (mp, mr, map, mf1, *(loss.cpu() / len(dataloader)).tolist()), maps
|
| 1154 |
+
|
| 1155 |
+
|
| 1156 |
+
if __name__ == '__main__':
|
| 1157 |
+
parser = argparse.ArgumentParser(prog='Test onnx model performance on COCO dataset')
|
| 1158 |
+
parser.add_argument(
|
| 1159 |
+
'--data',
|
| 1160 |
+
type=str,
|
| 1161 |
+
default='coco2017.data',
|
| 1162 |
+
help='Path of *.data')
|
| 1163 |
+
parser.add_argument(
|
| 1164 |
+
'--batch-size',
|
| 1165 |
+
type=int,
|
| 1166 |
+
default=1,
|
| 1167 |
+
help='Size of each image batch')
|
| 1168 |
+
parser.add_argument(
|
| 1169 |
+
'--img-size',
|
| 1170 |
+
type=int,
|
| 1171 |
+
default=416,
|
| 1172 |
+
help='Inference size (pixels)')
|
| 1173 |
+
parser.add_argument(
|
| 1174 |
+
'--conf-thres',
|
| 1175 |
+
type=float,
|
| 1176 |
+
default=0.001,
|
| 1177 |
+
help='Object confidence threshold')
|
| 1178 |
+
parser.add_argument(
|
| 1179 |
+
'--iou-thres',
|
| 1180 |
+
type=float,
|
| 1181 |
+
default=0.5,
|
| 1182 |
+
help='IOU threshold for NMS')
|
| 1183 |
+
parser.add_argument(
|
| 1184 |
+
'--save-json',
|
| 1185 |
+
action='store_true',
|
| 1186 |
+
help='Save a COCOapi-compatible JSON results file')
|
| 1187 |
+
parser.add_argument(
|
| 1188 |
+
'--device',
|
| 1189 |
+
default='',
|
| 1190 |
+
help='Device id (i.e. 0 or 0,1) or cpu')
|
| 1191 |
+
parser.add_argument(
|
| 1192 |
+
'--augment',
|
| 1193 |
+
action='store_true',
|
| 1194 |
+
help='Augmented inference')
|
| 1195 |
+
parser.add_argument('--sync_bn', action='store_true')
|
| 1196 |
+
parser.add_argument('--print_model', action='store_true')
|
| 1197 |
+
parser.add_argument('--test_rect', action='store_true')
|
| 1198 |
+
|
| 1199 |
+
parser.add_argument(
|
| 1200 |
+
'--onnx_runtime',
|
| 1201 |
+
action='store_true',
|
| 1202 |
+
help='Use onnx runtime')
|
| 1203 |
+
parser.add_argument(
|
| 1204 |
+
'--onnx_weights',
|
| 1205 |
+
default='yolov3-8.onnx',
|
| 1206 |
+
nargs='+',
|
| 1207 |
+
type=str,
|
| 1208 |
+
help='Path of onnx weights')
|
| 1209 |
+
parser.add_argument(
|
| 1210 |
+
'--single-cls',
|
| 1211 |
+
action='store_true',
|
| 1212 |
+
help='Run as single-class dataset')
|
| 1213 |
+
parser.add_argument(
|
| 1214 |
+
"--ipu",
|
| 1215 |
+
action="store_true",
|
| 1216 |
+
help="Use IPU for inference")
|
| 1217 |
+
parser.add_argument(
|
| 1218 |
+
"--provider_config",
|
| 1219 |
+
type=str,
|
| 1220 |
+
default="vaip_config.json",
|
| 1221 |
+
help="Path of the config file for seting provider_options")
|
| 1222 |
+
|
| 1223 |
+
opt = parser.parse_args()
|
| 1224 |
+
opt.save_json = opt.save_json or any(
|
| 1225 |
+
[x in opt.data for x in ['coco.data',
|
| 1226 |
+
'coco2014.data', 'coco2017.data']])
|
| 1227 |
+
opt.data = check_file(opt.data) # check file
|
| 1228 |
+
print(opt)
|
| 1229 |
+
|
| 1230 |
+
help_url = 'https://github.com/ultralytics/yolov3/wiki/Train-Custom-Data'
|
| 1231 |
+
img_formats = ['.bmp', '.jpg', '.jpeg', '.png', '.tif', '.tiff', '.dng']
|
| 1232 |
+
vid_formats = ['.mov', '.avi', '.mp4', '.mpg', '.mpeg', '.m4v', '.wmv',
|
| 1233 |
+
'.mkv']
|
| 1234 |
+
|
| 1235 |
+
test(opt.data,
|
| 1236 |
+
opt.batch_size,
|
| 1237 |
+
opt.img_size,
|
| 1238 |
+
opt.conf_thres,
|
| 1239 |
+
opt.iou_thres,
|
| 1240 |
+
opt.save_json,
|
| 1241 |
+
opt.single_cls,
|
| 1242 |
+
opt.augment,
|
| 1243 |
+
names='data/coco.names',
|
| 1244 |
+
onnx_weights=opt.onnx_weights,
|
| 1245 |
+
ipu=opt.ipu,
|
| 1246 |
+
provider_config=opt.provider_config
|
| 1247 |
+
)
|
requirements.txt
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pip install -r requirements.txt
|
| 2 |
+
|
| 3 |
+
# base ----------------------------------------
|
| 4 |
+
Cython
|
| 5 |
+
matplotlib>=3.2.2
|
| 6 |
+
numpy>=1.18.5
|
| 7 |
+
opencv-python>=4.1.2
|
| 8 |
+
pillow
|
| 9 |
+
PyYAML>=5.3
|
| 10 |
+
scipy>=1.4.1
|
| 11 |
+
tensorboard>=2.2
|
| 12 |
+
torch==1.12.0
|
| 13 |
+
torchvision>=0.7.0
|
| 14 |
+
tqdm>=4.41.0
|
| 15 |
+
pandas
|
| 16 |
+
#onnxruntime
|
| 17 |
+
|
| 18 |
+
# coco ----------------------------------------
|
| 19 |
+
pycocotools>=2.0
|
| 20 |
+
|
| 21 |
+
# export --------------------------------------
|
| 22 |
+
# packaging # for coremltools
|
| 23 |
+
# coremltools==4.0b3
|
| 24 |
+
# onnx>=1.7.0
|
| 25 |
+
# scikit-learn==0.19.2 # for coreml quantization
|
| 26 |
+
|
| 27 |
+
# extras --------------------------------------
|
| 28 |
+
# thop # FLOPS computation
|
| 29 |
+
# seaborn # plotting
|
utils.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import torch
|
| 4 |
+
import time
|
| 5 |
+
import torchvision
|
| 6 |
+
import random
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def box_iou(box1, box2):
|
| 10 |
+
# https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
|
| 11 |
+
"""
|
| 12 |
+
Return intersection-over-union (Jaccard index) of boxes.
|
| 13 |
+
Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
|
| 14 |
+
Arguments:
|
| 15 |
+
box1 (Tensor[N, 4])
|
| 16 |
+
box2 (Tensor[M, 4])
|
| 17 |
+
Returns:
|
| 18 |
+
iou (Tensor[N, M]): the NxM matrix containing the pairwise
|
| 19 |
+
IoU values for every element in boxes1 and boxes2
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def box_area(box):
|
| 23 |
+
# box = 4xn
|
| 24 |
+
return (box[2] - box[0]) * (box[3] - box[1])
|
| 25 |
+
|
| 26 |
+
area1 = box_area(box1.T)
|
| 27 |
+
area2 = box_area(box2.T)
|
| 28 |
+
|
| 29 |
+
# inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
|
| 30 |
+
inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
|
| 31 |
+
return inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def plot_one_box(x, image, color=None, label=None, line_thickness=None):
|
| 35 |
+
# Plots one bounding box on image img
|
| 36 |
+
tl = line_thickness or round(
|
| 37 |
+
0.002 * (image.shape[0] + image.shape[1]) / 2) + 1 # line/font thickness
|
| 38 |
+
color = color or [random.randint(0, 255) for _ in range(3)]
|
| 39 |
+
c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
|
| 40 |
+
cv2.rectangle(image, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
|
| 41 |
+
if label:
|
| 42 |
+
tf = max(tl - 1, 1) # font thickness
|
| 43 |
+
t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
|
| 44 |
+
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
|
| 45 |
+
cv2.rectangle(image, c1, c2, color, -1, cv2.LINE_AA) # filled
|
| 46 |
+
cv2.putText(image, label, (c1[0], c1[1] - 2), 0, tl / 3,
|
| 47 |
+
[225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def clip_coords(boxes, img_shape):
|
| 51 |
+
# Clip bounding xyxy bounding boxes to image shape (height, width)
|
| 52 |
+
boxes[:, 0].clamp_(0, img_shape[1]) # x1
|
| 53 |
+
boxes[:, 1].clamp_(0, img_shape[0]) # y1
|
| 54 |
+
boxes[:, 2].clamp_(0, img_shape[1]) # x2
|
| 55 |
+
boxes[:, 3].clamp_(0, img_shape[0]) # y2
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
|
| 59 |
+
# Rescale coords (xyxy) from img1_shape to img0_shape
|
| 60 |
+
if ratio_pad is None: # calculate from img0_shape
|
| 61 |
+
gain = max(img1_shape) / max(img0_shape) # gain = old / new
|
| 62 |
+
pad = (img1_shape[1] - img0_shape[1] * gain) / \
|
| 63 |
+
2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
|
| 64 |
+
else:
|
| 65 |
+
gain = ratio_pad[0][0]
|
| 66 |
+
pad = ratio_pad[1]
|
| 67 |
+
|
| 68 |
+
coords[:, [0, 2]] -= pad[0] # x padding
|
| 69 |
+
coords[:, [1, 3]] -= pad[1] # y padding
|
| 70 |
+
coords[:, :4] /= gain
|
| 71 |
+
clip_coords(coords, img0_shape)
|
| 72 |
+
return coords
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def xywh2xyxy(x):
|
| 76 |
+
# Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where
|
| 77 |
+
# xy1=top-left, xy2=bottom-right
|
| 78 |
+
y = torch.zeros_like(x) if isinstance(
|
| 79 |
+
x, torch.Tensor) else np.zeros_like(x)
|
| 80 |
+
y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
|
| 81 |
+
y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
|
| 82 |
+
y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
|
| 83 |
+
y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
|
| 84 |
+
return y
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def letterbox(img, new_shape=(416, 416), color=(114, 114, 114), auto=True,
|
| 88 |
+
scaleFill=False, scaleup=True):
|
| 89 |
+
# Resize image to a 32-pixel-multiple rectangle
|
| 90 |
+
# https://github.com/ultralytics/yolov3/issues/232
|
| 91 |
+
shape = img.shape[:2] # current shape [height, width]
|
| 92 |
+
if isinstance(new_shape, int):
|
| 93 |
+
new_shape = (new_shape, new_shape)
|
| 94 |
+
|
| 95 |
+
# Scale ratio (new / old)
|
| 96 |
+
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
|
| 97 |
+
if not scaleup: # only scale down, do not scale up (for better test mAP)
|
| 98 |
+
r = min(r, 1.0)
|
| 99 |
+
|
| 100 |
+
# Compute padding
|
| 101 |
+
ratio = r, r # width, height ratios
|
| 102 |
+
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
|
| 103 |
+
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - \
|
| 104 |
+
new_unpad[1] # wh padding
|
| 105 |
+
if auto: # minimum rectangle
|
| 106 |
+
dw, dh = np.mod(dw, 32), np.mod(dh, 32) # wh padding
|
| 107 |
+
elif scaleFill: # stretch
|
| 108 |
+
dw, dh = 0.0, 0.0
|
| 109 |
+
new_unpad = new_shape
|
| 110 |
+
ratio = new_shape[0] / shape[1], new_shape[1] / \
|
| 111 |
+
shape[0] # width, height ratios
|
| 112 |
+
|
| 113 |
+
dw /= 2 # divide padding into 2 sides
|
| 114 |
+
dh /= 2
|
| 115 |
+
|
| 116 |
+
if shape[::-1] != new_unpad: # resize
|
| 117 |
+
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
|
| 118 |
+
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
| 119 |
+
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
| 120 |
+
img = cv2.copyMakeBorder(img, top, bottom, left, right,
|
| 121 |
+
cv2.BORDER_CONSTANT, value=color) # add border
|
| 122 |
+
return img, ratio, (dw, dh)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def non_max_suppression(
|
| 126 |
+
prediction,
|
| 127 |
+
conf_thres=0.1,
|
| 128 |
+
iou_thres=0.6,
|
| 129 |
+
multi_label=True,
|
| 130 |
+
classes=None,
|
| 131 |
+
agnostic=False):
|
| 132 |
+
"""
|
| 133 |
+
Performs Non-Maximum Suppression on inference results
|
| 134 |
+
Returns detections with shape:
|
| 135 |
+
nx6 (x1, y1, x2, y2, conf, cls)
|
| 136 |
+
"""
|
| 137 |
+
|
| 138 |
+
# Settings
|
| 139 |
+
merge = True # merge for best mAP
|
| 140 |
+
# (pixels) minimum and maximum box width and height
|
| 141 |
+
min_wh, max_wh = 2, 4096
|
| 142 |
+
time_limit = 10.0 # seconds to quit after
|
| 143 |
+
|
| 144 |
+
t = time.time()
|
| 145 |
+
nc = prediction[0].shape[1] - 5 # number of classes
|
| 146 |
+
multi_label &= nc > 1 # multiple labels per box
|
| 147 |
+
output = [None] * prediction.shape[0]
|
| 148 |
+
for xi, x in enumerate(prediction): # image index, image inference
|
| 149 |
+
# Apply constraints
|
| 150 |
+
x = x[x[:, 4] > conf_thres] # confidence
|
| 151 |
+
x = x[((x[:, 2:4] > min_wh) & (x[:, 2:4] < max_wh)).all(1)]
|
| 152 |
+
|
| 153 |
+
# If none remain process next image
|
| 154 |
+
if not x.shape[0]:
|
| 155 |
+
continue
|
| 156 |
+
|
| 157 |
+
# Compute conf
|
| 158 |
+
x[..., 5:] *= x[..., 4:5] # conf = obj_conf * cls_conf
|
| 159 |
+
|
| 160 |
+
# Box (center x, center y, width, height) to (x1, y1, x2, y2)
|
| 161 |
+
box = xywh2xyxy(x[:, :4])
|
| 162 |
+
|
| 163 |
+
# Detections matrix nx6 (xyxy, conf, cls)
|
| 164 |
+
if multi_label:
|
| 165 |
+
i, j = (x[:, 5:] > conf_thres).nonzero().t()
|
| 166 |
+
x = torch.cat((box[i], x[i, j + 5].unsqueeze(1),
|
| 167 |
+
j.float().unsqueeze(1)), 1)
|
| 168 |
+
else: # best class only
|
| 169 |
+
conf, j = x[:, 5:].max(1)
|
| 170 |
+
x = torch.cat(
|
| 171 |
+
(box, conf.unsqueeze(1), j.float().unsqueeze(1)), 1)[
|
| 172 |
+
conf > conf_thres]
|
| 173 |
+
|
| 174 |
+
# Filter by class
|
| 175 |
+
if classes:
|
| 176 |
+
x = x[(j.view(-1, 1) == torch.tensor(classes,
|
| 177 |
+
device=j.device)).any(1)]
|
| 178 |
+
|
| 179 |
+
# Apply finite constraint
|
| 180 |
+
# if not torch.isfinite(x).all():
|
| 181 |
+
# x = x[torch.isfinite(x).all(1)]
|
| 182 |
+
|
| 183 |
+
# If none remain process next image
|
| 184 |
+
n = x.shape[0] # number of boxes
|
| 185 |
+
if not n:
|
| 186 |
+
continue
|
| 187 |
+
|
| 188 |
+
# Sort by confidence
|
| 189 |
+
# x = x[x[:, 4].argsort(descending=True)]
|
| 190 |
+
|
| 191 |
+
# Batched NMS
|
| 192 |
+
c = x[:, 5] * 0 if agnostic else x[:, 5] # classes
|
| 193 |
+
boxes, scores = x[:, :4].clone() + c.view(-1, 1) * \
|
| 194 |
+
max_wh, x[:, 4] # boxes (offset by class), scores
|
| 195 |
+
i = torchvision.ops.boxes.nms(boxes, scores, iou_thres)
|
| 196 |
+
if merge and (
|
| 197 |
+
1 < n < 3E3): # Merge NMS (boxes merged using weighted mean)
|
| 198 |
+
try: # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
|
| 199 |
+
iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix
|
| 200 |
+
weights = iou * scores[None] # box weights
|
| 201 |
+
x[i, :4] = torch.mm(weights, x[:, :4]).float(
|
| 202 |
+
) / weights.sum(1, keepdim=True) # merged boxes
|
| 203 |
+
# i = i[iou.sum(1) > 1] # require redundancy
|
| 204 |
+
except BaseException:
|
| 205 |
+
# https://github.com/ultralytics/yolov3/issues/1139
|
| 206 |
+
# print(x, i, x.shape, i.shape)
|
| 207 |
+
pass
|
| 208 |
+
|
| 209 |
+
output[xi] = x[i]
|
| 210 |
+
if (time.time() - t) > time_limit:
|
| 211 |
+
break # time limit exceeded
|
| 212 |
+
|
| 213 |
+
return output
|
yolov3-8.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:caf441af4ff1b82258a7e308a89343f0e5ef9440e89383dde019e030a5b698f2
|
| 3 |
+
size 247866093
|
yolov3.cfg
ADDED
|
@@ -0,0 +1,788 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[net]
|
| 2 |
+
# Testing
|
| 3 |
+
#batch=1
|
| 4 |
+
#subdivisions=1
|
| 5 |
+
# Training
|
| 6 |
+
batch=16
|
| 7 |
+
subdivisions=1
|
| 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=255
|
| 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=80
|
| 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=255
|
| 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=80
|
| 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=255
|
| 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=80
|
| 784 |
+
num=9
|
| 785 |
+
jitter=.3
|
| 786 |
+
ignore_thresh = .7
|
| 787 |
+
truth_thresh = 1
|
| 788 |
+
random=1
|