glenn-jocher Piotr Skalski SkalskiP Peretz Cohen tudoulei chocosaj BuildTools developer0hye Sam_S Samridha Shrestha edificewang commited on
Commit
f3c3d2c
·
unverified ·
1 Parent(s): 3f03acb

Merge `develop` branch into `master` (#3518)

Browse files

* update ci-testing.yml (#3322)

* update ci-testing.yml

* update greetings.yml

* bring back os matrix

* update ci-testing.yml (#3322)

* update ci-testing.yml

* update greetings.yml

* bring back os matrix

* Enable direct `--weights URL` definition (#3373)

* Enable direct `--weights URL` definition

@KalenMike this PR will enable direct --weights URL definition. Example use case:
```
python train.py --weights https://storage.googleapis.com/bucket/dir/model.pt
```

* cleanup

* bug fixes

* weights = attempt_download(weights)

* Update experimental.py

* Update hubconf.py

* return bug fix

* comment mirror

* min_bytes

* Update tutorial.ipynb (#3368)

add Open in Kaggle badge

* `cv2.imread(img, -1)` for IMREAD_UNCHANGED (#3379)

* Update datasets.py

* comment

Co-authored-by: Glenn Jocher <glenn.jocher@ultralytics.com>

* COCO evolution fix (#3388)

* COCO evolution fix

* cleanup

* update print

* print fix

* Create `is_pip()` function (#3391)

Returns `True` if file is part of pip package. Useful for contextual behavior modification.

```python
def is_pip():
# Is file in a pip package?
return 'site-packages' in Path(__file__).absolute().parts
```

* Revert "`cv2.imread(img, -1)` for IMREAD_UNCHANGED (#3379)" (#3395)

This reverts commit 21a9607e00f1365b21d8c4bd81bdbf5fc0efea24.

* Update FLOPs description (#3422)

* Update README.md

* Changing FLOPS to FLOPs.

Co-authored-by: BuildTools <unconfigured@null.spigotmc.org>

* Parse URL authentication (#3424)

* Parse URL authentication

* urllib.parse.unquote()

* improved error handling

* improved error handling

* remove %3F

* update check_file()

* Add FLOPs title to table (#3453)

* Suppress jit trace warning + graph once (#3454)

* Suppress jit trace warning + graph once

Suppress harmless jit trace warning on TensorBoard add_graph call. Also fix multiple add_graph() calls bug, now only on batch 0.

* Update train.py

* Update MixUp augmentation `alpha=beta=32.0` (#3455)

Per VOC empirical results https://github.com/ultralytics/yolov5/issues/3380#issuecomment-853001307 by

@developer0hye


* Add `timeout()` class (#3460)

* Add `timeout()` class

* rearrange order

* Faster HSV augmentation (#3462)

remove datatype conversion process that can be skipped

* Add `check_git_status()` 5 second timeout (#3464)

* Add check_git_status() 5 second timeout

This should prevent the SSH Git bug that we were discussing @KalenMike

* cleanup

* replace timeout with check_output built-in timeout

* Improved `check_requirements()` offline-handling (#3466)

Improve robustness of `check_requirements()` function to offline environments (do not attempt pip installs when offline).

* Add `output_names` argument for ONNX export with dynamic axes (#3456)

* Add output names & dynamic axes for onnx export

Add output_names and dynamic_axes names for all outputs in torch.onnx.export. The first four outputs of the model will have names output0, output1, output2, output3

* use first output only + cleanup

Co-authored-by: Samridha Shrestha <samridha.shrestha@g42.ai>
Co-authored-by: Glenn Jocher <glenn.jocher@ultralytics.com>

* Revert FP16 `test.py` and `detect.py` inference to FP32 default (#3423)

* fixed inference bug ,while use half precision

* replace --use-half with --half

* replace space and PEP8 in detect.py

* PEP8 detect.py

* update --half help comment

* Update test.py

* revert space

Co-authored-by: Glenn Jocher <glenn.jocher@ultralytics.com>

* Add additional links/resources to stale.yml message (#3467)

* Update stale.yml

* cleanup

* Update stale.yml

* reformat

* Update stale.yml HUB URL (#3468)

* Stale `github.actor` bug fix (#3483)

* Explicit `model.eval()` call `if opt.train=False` (#3475)

* call model.eval() when opt.train is False

call model.eval() when opt.train is False

* single-line if statement

* cleanup

Co-authored-by: Glenn Jocher <glenn.jocher@ultralytics.com>

* check_requirements() exclude `opencv-python` (#3495)

Fix for 3rd party or contrib versions of installed OpenCV as in https://github.com/ultralytics/yolov5/issues/3494.

* Earlier `assert` for cpu and half option (#3508)

* early assert for cpu and half option

early assert for cpu and half option

* Modified comment

Modified comment

* Update tutorial.ipynb (#3510)

* Reduce test.py results spacing (#3511)

* Update README.md (#3512)

* Update README.md

Minor modifications

* 850 width

* Update greetings.yml

revert greeting change as PRs will now merge to master.

Co-authored-by: Piotr Skalski <SkalskiP@users.noreply.github.com>
Co-authored-by: SkalskiP <piotr.skalski92@gmail.com>
Co-authored-by: Peretz Cohen <pizzaz93@users.noreply.github.com>
Co-authored-by: tudoulei <34886368+tudoulei@users.noreply.github.com>
Co-authored-by: chocosaj <chocosaj@users.noreply.github.com>
Co-authored-by: BuildTools <unconfigured@null.spigotmc.org>
Co-authored-by: Yonghye Kwon <developer.0hye@gmail.com>
Co-authored-by: Sam_S <SamSamhuns@users.noreply.github.com>
Co-authored-by: Samridha Shrestha <samridha.shrestha@g42.ai>
Co-authored-by: edificewang <609552430@qq.com>

.github/workflows/ci-testing.yml CHANGED
@@ -2,12 +2,10 @@ name: CI CPU testing
2
 
3
  on: # https://help.github.com/en/actions/reference/events-that-trigger-workflows
4
  push:
5
- branches: [ master ]
6
  pull_request:
7
  # The branches below must be a subset of the branches above
8
- branches: [ master ]
9
- schedule:
10
- - cron: '0 0 * * *' # Runs at 00:00 UTC every day
11
 
12
  jobs:
13
  cpu-tests:
 
2
 
3
  on: # https://help.github.com/en/actions/reference/events-that-trigger-workflows
4
  push:
5
+ branches: [ master, develop ]
6
  pull_request:
7
  # The branches below must be a subset of the branches above
8
+ branches: [ master, develop ]
 
 
9
 
10
  jobs:
11
  cpu-tests:
.github/workflows/stale.yml CHANGED
@@ -10,8 +10,26 @@ jobs:
10
  - uses: actions/stale@v3
11
  with:
12
  repo-token: ${{ secrets.GITHUB_TOKEN }}
13
- stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
14
- stale-pr-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  days-before-stale: 30
16
  days-before-close: 5
17
  exempt-issue-labels: 'documentation,tutorial'
 
10
  - uses: actions/stale@v3
11
  with:
12
  repo-token: ${{ secrets.GITHUB_TOKEN }}
13
+ stale-issue-message: |
14
+ 👋 Hello, this issue has been automatically marked as stale because it has not had recent activity. Please note it will be closed if no further activity occurs.
15
+
16
+ Access additional [YOLOv5](https://ultralytics.com/yolov5) 🚀 resources:
17
+ - **Wiki** – https://github.com/ultralytics/yolov5/wiki
18
+ - **Tutorials** – https://github.com/ultralytics/yolov5#tutorials
19
+ - **Docs** – https://docs.ultralytics.com
20
+
21
+ Access additional [Ultralytics](https://ultralytics.com) ⚡ resources:
22
+ - **Ultralytics HUB** – https://ultralytics.com/pricing
23
+ - **Vision API** – https://ultralytics.com/yolov5
24
+ - **About Us** – https://ultralytics.com/about
25
+ - **Join Our Team** – https://ultralytics.com/work
26
+ - **Contact Us** – https://ultralytics.com/contact
27
+
28
+ Feel free to inform us of any other **issues** you discover or **feature requests** that come to mind in the future. Pull Requests (PRs) are also always welcomed!
29
+
30
+ Thank you for your contributions to YOLOv5 🚀 and Vision AI ⭐!
31
+
32
+ stale-pr-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions YOLOv5 🚀 and Vision AI ⭐.'
33
  days-before-stale: 30
34
  days-before-close: 5
35
  exempt-issue-labels: 'documentation,tutorial'
README.md CHANGED
@@ -1,5 +1,5 @@
1
  <a align="left" href="https://apps.apple.com/app/id1452689527" target="_blank">
2
- <img width="800" src="https://user-images.githubusercontent.com/26833433/98699617-a1595a00-2377-11eb-8145-fc674eb9b1a7.jpg"></a>
3
  &nbsp
4
 
5
  <a href="https://github.com/ultralytics/yolov5/actions"><img src="https://github.com/ultralytics/yolov5/workflows/CI%20CPU%20testing/badge.svg" alt="CI CPU testing"></a>
@@ -30,19 +30,19 @@ This repository represents Ultralytics open-source research into future object d
30
 
31
  [assets]: https://github.com/ultralytics/yolov5/releases
32
 
33
- Model |size<br><sup>(pixels) |mAP<sup>val<br>0.5:0.95 |mAP<sup>test<br>0.5:0.95 |mAP<sup>val<br>0.5 |Speed<br><sup>V100 (ms) | |params<br><sup>(M) |FLOPS<br><sup>640 (B)
34
- --- |--- |--- |--- |--- |--- |---|--- |---
35
- [YOLOv5s][assets] |640 |36.7 |36.7 |55.4 |**2.0** | |7.3 |17.0
36
- [YOLOv5m][assets] |640 |44.5 |44.5 |63.1 |2.7 | |21.4 |51.3
37
- [YOLOv5l][assets] |640 |48.2 |48.2 |66.9 |3.8 | |47.0 |115.4
38
- [YOLOv5x][assets] |640 |**50.4** |**50.4** |**68.8** |6.1 | |87.7 |218.8
39
  | | | | | | || |
40
- [YOLOv5s6][assets] |1280 |43.3 |43.3 |61.9 |**4.3** | |12.7 |17.4
41
- [YOLOv5m6][assets] |1280 |50.5 |50.5 |68.7 |8.4 | |35.9 |52.4
42
- [YOLOv5l6][assets] |1280 |53.4 |53.4 |71.1 |12.3 | |77.2 |117.7
43
- [YOLOv5x6][assets] |1280 |**54.4** |**54.4** |**72.0** |22.4 | |141.8 |222.9
44
  | | | | | | || |
45
- [YOLOv5x6][assets] TTA |1280 |**55.0** |**55.0** |**72.0** |70.8 | |- |-
46
 
47
  <details>
48
  <summary>Table Notes (click to expand)</summary>
@@ -112,7 +112,7 @@ Namespace(agnostic_nms=False, augment=False, classes=None, conf_thres=0.25, devi
112
  YOLOv5 v4.0-96-g83dc1b4 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)
113
 
114
  Fusing layers...
115
- Model Summary: 224 layers, 7266973 parameters, 0 gradients, 17.0 GFLOPS
116
  image 1/2 /content/yolov5/data/images/bus.jpg: 640x480 4 persons, 1 bus, Done. (0.010s)
117
  image 2/2 /content/yolov5/data/images/zidane.jpg: 384x640 2 persons, 1 tie, Done. (0.011s)
118
  Results saved to runs/detect/exp2
 
1
  <a align="left" href="https://apps.apple.com/app/id1452689527" target="_blank">
2
+ <img width="850" src="https://user-images.githubusercontent.com/26833433/121094150-72607500-c7ee-11eb-9f39-1d9e4ce89a9e.jpg"></a>
3
  &nbsp
4
 
5
  <a href="https://github.com/ultralytics/yolov5/actions"><img src="https://github.com/ultralytics/yolov5/workflows/CI%20CPU%20testing/badge.svg" alt="CI CPU testing"></a>
 
30
 
31
  [assets]: https://github.com/ultralytics/yolov5/releases
32
 
33
+ |Model |size<br><sup>(pixels) |mAP<sup>val<br>0.5:0.95 |mAP<sup>test<br>0.5:0.95 |mAP<sup>val<br>0.5 |Speed<br><sup>V100 (ms) | |params<br><sup>(M) |FLOPs<br><sup>640 (B)
34
+ |--- |--- |--- |--- |--- |--- |---|--- |---
35
+ |[YOLOv5s][assets] |640 |36.7 |36.7 |55.4 |**2.0** | |7.3 |17.0
36
+ |[YOLOv5m][assets] |640 |44.5 |44.5 |63.1 |2.7 | |21.4 |51.3
37
+ |[YOLOv5l][assets] |640 |48.2 |48.2 |66.9 |3.8 | |47.0 |115.4
38
+ |[YOLOv5x][assets] |640 |**50.4** |**50.4** |**68.8** |6.1 | |87.7 |218.8
39
  | | | | | | || |
40
+ |[YOLOv5s6][assets] |1280 |43.3 |43.3 |61.9 |**4.3** | |12.7 |17.4
41
+ |[YOLOv5m6][assets] |1280 |50.5 |50.5 |68.7 |8.4 | |35.9 |52.4
42
+ |[YOLOv5l6][assets] |1280 |53.4 |53.4 |71.1 |12.3 | |77.2 |117.7
43
+ |[YOLOv5x6][assets] |1280 |**54.4** |**54.4** |**72.0** |22.4 | |141.8 |222.9
44
  | | | | | | || |
45
+ |[YOLOv5x6][assets] TTA |1280 |**55.0** |**55.0** |**72.0** |70.8 | |- |-
46
 
47
  <details>
48
  <summary>Table Notes (click to expand)</summary>
 
112
  YOLOv5 v4.0-96-g83dc1b4 torch 1.7.0+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)
113
 
114
  Fusing layers...
115
+ Model Summary: 224 layers, 7266973 parameters, 0 gradients, 17.0 GFLOPs
116
  image 1/2 /content/yolov5/data/images/bus.jpg: 640x480 4 persons, 1 bus, Done. (0.010s)
117
  image 2/2 /content/yolov5/data/images/zidane.jpg: 384x640 2 persons, 1 tie, Done. (0.011s)
118
  Results saved to runs/detect/exp2
detect.py CHANGED
@@ -28,7 +28,7 @@ def detect(opt):
28
  # Initialize
29
  set_logging()
30
  device = select_device(opt.device)
31
- half = device.type != 'cpu' # half precision only supported on CUDA
32
 
33
  # Load model
34
  model = attempt_load(weights, map_location=device) # load FP32 model
@@ -172,6 +172,7 @@ if __name__ == '__main__':
172
  parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
173
  parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
174
  parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
 
175
  opt = parser.parse_args()
176
  print(opt)
177
  check_requirements(exclude=('tensorboard', 'pycocotools', 'thop'))
 
28
  # Initialize
29
  set_logging()
30
  device = select_device(opt.device)
31
+ half = opt.half and device.type != 'cpu' # half precision only supported on CUDA
32
 
33
  # Load model
34
  model = attempt_load(weights, map_location=device) # load FP32 model
 
172
  parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
173
  parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
174
  parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
175
+ parser.add_argument('--half', type=bool, default=False, help='use FP16 half-precision inference')
176
  opt = parser.parse_args()
177
  print(opt)
178
  check_requirements(exclude=('tensorboard', 'pycocotools', 'thop'))
hubconf.py CHANGED
@@ -42,8 +42,7 @@ def _create(name, pretrained=True, channels=3, classes=80, autoshape=True, verbo
42
  cfg = list((Path(__file__).parent / 'models').rglob(f'{name}.yaml'))[0] # model.yaml path
43
  model = Model(cfg, channels, classes) # create model
44
  if pretrained:
45
- attempt_download(fname) # download if not found locally
46
- ckpt = torch.load(fname, map_location=torch.device('cpu')) # load
47
  msd = model.state_dict() # model state_dict
48
  csd = ckpt['model'].float().state_dict() # checkpoint state_dict as FP32
49
  csd = {k: v for k, v in csd.items() if msd[k].shape == v.shape} # filter
 
42
  cfg = list((Path(__file__).parent / 'models').rglob(f'{name}.yaml'))[0] # model.yaml path
43
  model = Model(cfg, channels, classes) # create model
44
  if pretrained:
45
+ ckpt = torch.load(attempt_download(fname), map_location=torch.device('cpu')) # load
 
46
  msd = model.state_dict() # model state_dict
47
  csd = ckpt['model'].float().state_dict() # checkpoint state_dict as FP32
48
  csd = {k: v for k, v in csd.items() if msd[k].shape == v.shape} # filter
models/experimental.py CHANGED
@@ -116,8 +116,7 @@ def attempt_load(weights, map_location=None, inplace=True):
116
  # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
117
  model = Ensemble()
118
  for w in weights if isinstance(weights, list) else [weights]:
119
- attempt_download(w)
120
- ckpt = torch.load(w, map_location=map_location) # load
121
  model.append(ckpt['ema' if ckpt.get('ema') else 'model'].float().fuse().eval()) # FP32 model
122
 
123
  # Compatibility updates
 
116
  # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
117
  model = Ensemble()
118
  for w in weights if isinstance(weights, list) else [weights]:
119
+ ckpt = torch.load(attempt_download(w), map_location=map_location) # load
 
120
  model.append(ckpt['ema' if ckpt.get('ema') else 'model'].float().fuse().eval()) # FP32 model
121
 
122
  # Compatibility updates
models/export.py CHANGED
@@ -44,22 +44,19 @@ if __name__ == '__main__':
44
 
45
  # Load PyTorch model
46
  device = select_device(opt.device)
 
47
  model = attempt_load(opt.weights, map_location=device) # load FP32 model
48
  labels = model.names
49
 
50
- # Checks
51
  gs = int(max(model.stride)) # grid size (max stride)
52
  opt.img_size = [check_img_size(x, gs) for x in opt.img_size] # verify img_size are gs-multiples
53
- assert not (opt.device.lower() == 'cpu' and opt.half), '--half only compatible with GPU export, i.e. use --device 0'
54
-
55
- # Input
56
  img = torch.zeros(opt.batch_size, 3, *opt.img_size).to(device) # image size(1,3,320,192) iDetection
57
 
58
  # Update model
59
  if opt.half:
60
  img, model = img.half(), model.half() # to FP16
61
- if opt.train:
62
- model.train() # training mode (no grid construction in Detect layer)
63
  for k, m in model.named_modules():
64
  m._non_persistent_buffers_set = set() # pytorch 1.6.0 compatibility
65
  if isinstance(m, models.common.Conv): # assign export-friendly activations
@@ -96,11 +93,14 @@ if __name__ == '__main__':
96
 
97
  print(f'{prefix} starting export with onnx {onnx.__version__}...')
98
  f = opt.weights.replace('.pt', '.onnx') # filename
99
- torch.onnx.export(model, img, f, verbose=False, opset_version=opt.opset_version, input_names=['images'],
100
  training=torch.onnx.TrainingMode.TRAINING if opt.train else torch.onnx.TrainingMode.EVAL,
101
  do_constant_folding=not opt.train,
102
- dynamic_axes={'images': {0: 'batch', 2: 'height', 3: 'width'}, # size(1,3,640,640)
103
- 'output': {0: 'batch', 2: 'y', 3: 'x'}} if opt.dynamic else None)
 
 
 
104
 
105
  # Checks
106
  model_onnx = onnx.load(f) # load onnx model
 
44
 
45
  # Load PyTorch model
46
  device = select_device(opt.device)
47
+ assert not (opt.device.lower() == 'cpu' and opt.half), '--half only compatible with GPU export, i.e. use --device 0'
48
  model = attempt_load(opt.weights, map_location=device) # load FP32 model
49
  labels = model.names
50
 
51
+ # Input
52
  gs = int(max(model.stride)) # grid size (max stride)
53
  opt.img_size = [check_img_size(x, gs) for x in opt.img_size] # verify img_size are gs-multiples
 
 
 
54
  img = torch.zeros(opt.batch_size, 3, *opt.img_size).to(device) # image size(1,3,320,192) iDetection
55
 
56
  # Update model
57
  if opt.half:
58
  img, model = img.half(), model.half() # to FP16
59
+ model.train() if opt.train else model.eval() # training mode = no Detect() layer grid construction
 
60
  for k, m in model.named_modules():
61
  m._non_persistent_buffers_set = set() # pytorch 1.6.0 compatibility
62
  if isinstance(m, models.common.Conv): # assign export-friendly activations
 
93
 
94
  print(f'{prefix} starting export with onnx {onnx.__version__}...')
95
  f = opt.weights.replace('.pt', '.onnx') # filename
96
+ torch.onnx.export(model, img, f, verbose=False, opset_version=opt.opset_version,
97
  training=torch.onnx.TrainingMode.TRAINING if opt.train else torch.onnx.TrainingMode.EVAL,
98
  do_constant_folding=not opt.train,
99
+ input_names=['images'],
100
+ output_names=['output'],
101
+ dynamic_axes={'images': {0: 'batch', 2: 'height', 3: 'width'}, # shape(1,3,640,640)
102
+ 'output': {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
103
+ } if opt.dynamic else None)
104
 
105
  # Checks
106
  model_onnx = onnx.load(f) # load onnx model
models/yolo.py CHANGED
@@ -21,7 +21,7 @@ from utils.torch_utils import time_synchronized, fuse_conv_and_bn, model_info, s
21
  select_device, copy_attr
22
 
23
  try:
24
- import thop # for FLOPS computation
25
  except ImportError:
26
  thop = None
27
 
@@ -140,13 +140,13 @@ class Model(nn.Module):
140
  x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers
141
 
142
  if profile:
143
- o = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 if thop else 0 # FLOPS
144
  t = time_synchronized()
145
  for _ in range(10):
146
  _ = m(x)
147
  dt.append((time_synchronized() - t) * 100)
148
  if m == self.model[0]:
149
- logger.info(f"{'time (ms)':>10s} {'GFLOPS':>10s} {'params':>10s} {'module'}")
150
  logger.info(f'{dt[-1]:10.2f} {o:10.2f} {m.np:10.0f} {m.type}')
151
 
152
  x = m(x) # run
 
21
  select_device, copy_attr
22
 
23
  try:
24
+ import thop # for FLOPs computation
25
  except ImportError:
26
  thop = None
27
 
 
140
  x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers
141
 
142
  if profile:
143
+ o = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 if thop else 0 # FLOPs
144
  t = time_synchronized()
145
  for _ in range(10):
146
  _ = m(x)
147
  dt.append((time_synchronized() - t) * 100)
148
  if m == self.model[0]:
149
+ logger.info(f"{'time (ms)':>10s} {'GFLOPs':>10s} {'params':>10s} {'module'}")
150
  logger.info(f'{dt[-1]:10.2f} {o:10.2f} {m.np:10.0f} {m.type}')
151
 
152
  x = m(x) # run
requirements.txt CHANGED
@@ -27,4 +27,4 @@ pandas
27
  # extras --------------------------------------
28
  # Cython # for pycocotools https://github.com/cocodataset/cocoapi/issues/172
29
  pycocotools>=2.0 # COCO mAP
30
- thop # FLOPS computation
 
27
  # extras --------------------------------------
28
  # Cython # for pycocotools https://github.com/cocodataset/cocoapi/issues/172
29
  pycocotools>=2.0 # COCO mAP
30
+ thop # FLOPs computation
test.py CHANGED
@@ -95,7 +95,7 @@ def test(data,
95
  confusion_matrix = ConfusionMatrix(nc=nc)
96
  names = {k: v for k, v in enumerate(model.names if hasattr(model, 'names') else model.module.names)}
97
  coco91class = coco80_to_coco91_class()
98
- s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Labels', 'P', 'R', 'mAP@.5', 'mAP@.5:.95')
99
  p, r, f1, mp, mr, map50, map, t0, t1 = 0., 0., 0., 0., 0., 0., 0., 0., 0.
100
  loss = torch.zeros(3, device=device)
101
  jdict, stats, ap, ap_class, wandb_images = [], [], [], [], []
@@ -228,7 +228,7 @@ def test(data,
228
  nt = torch.zeros(1)
229
 
230
  # Print results
231
- pf = '%20s' + '%12i' * 2 + '%12.3g' * 4 # print format
232
  print(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
233
 
234
  # Print results per class
@@ -306,6 +306,7 @@ if __name__ == '__main__':
306
  parser.add_argument('--project', default='runs/test', help='save to project/name')
307
  parser.add_argument('--name', default='exp', help='save to project/name')
308
  parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
 
309
  opt = parser.parse_args()
310
  opt.save_json |= opt.data.endswith('coco.yaml')
311
  opt.data = check_file(opt.data) # check file
@@ -326,6 +327,7 @@ if __name__ == '__main__':
326
  save_txt=opt.save_txt | opt.save_hybrid,
327
  save_hybrid=opt.save_hybrid,
328
  save_conf=opt.save_conf,
 
329
  opt=opt
330
  )
331
 
 
95
  confusion_matrix = ConfusionMatrix(nc=nc)
96
  names = {k: v for k, v in enumerate(model.names if hasattr(model, 'names') else model.module.names)}
97
  coco91class = coco80_to_coco91_class()
98
+ s = ('%20s' + '%11s' * 6) % ('Class', 'Images', 'Labels', 'P', 'R', 'mAP@.5', 'mAP@.5:.95')
99
  p, r, f1, mp, mr, map50, map, t0, t1 = 0., 0., 0., 0., 0., 0., 0., 0., 0.
100
  loss = torch.zeros(3, device=device)
101
  jdict, stats, ap, ap_class, wandb_images = [], [], [], [], []
 
228
  nt = torch.zeros(1)
229
 
230
  # Print results
231
+ pf = '%20s' + '%11i' * 2 + '%11.3g' * 4 # print format
232
  print(pf % ('all', seen, nt.sum(), mp, mr, map50, map))
233
 
234
  # Print results per class
 
306
  parser.add_argument('--project', default='runs/test', help='save to project/name')
307
  parser.add_argument('--name', default='exp', help='save to project/name')
308
  parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
309
+ parser.add_argument('--half', type=bool, default=False, help='use FP16 half-precision inference')
310
  opt = parser.parse_args()
311
  opt.save_json |= opt.data.endswith('coco.yaml')
312
  opt.data = check_file(opt.data) # check file
 
327
  save_txt=opt.save_txt | opt.save_hybrid,
328
  save_hybrid=opt.save_hybrid,
329
  save_conf=opt.save_conf,
330
+ half_precision=opt.half,
331
  opt=opt
332
  )
333
 
train.py CHANGED
@@ -4,6 +4,7 @@ import math
4
  import os
5
  import random
6
  import time
 
7
  from copy import deepcopy
8
  from pathlib import Path
9
  from threading import Thread
@@ -62,7 +63,6 @@ def train(hyp, opt, device, tb_writer=None):
62
  init_seeds(2 + rank)
63
  with open(opt.data) as f:
64
  data_dict = yaml.safe_load(f) # data dict
65
- is_coco = opt.data.endswith('coco.yaml')
66
 
67
  # Logging- Doing this before checking the dataset. Might update data_dict
68
  loggers = {'wandb': None} # loggers dict
@@ -78,12 +78,13 @@ def train(hyp, opt, device, tb_writer=None):
78
  nc = 1 if opt.single_cls else int(data_dict['nc']) # number of classes
79
  names = ['item'] if opt.single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names
80
  assert len(names) == nc, '%g names found for nc=%g dataset in %s' % (len(names), nc, opt.data) # check
 
81
 
82
  # Model
83
  pretrained = weights.endswith('.pt')
84
  if pretrained:
85
  with torch_distributed_zero_first(rank):
86
- attempt_download(weights) # download if not found locally
87
  ckpt = torch.load(weights, map_location=device) # load checkpoint
88
  model = Model(opt.cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
89
  exclude = ['anchor'] if (opt.cfg or hyp.get('anchors')) and not opt.resume else [] # exclude keys
@@ -323,18 +324,19 @@ def train(hyp, opt, device, tb_writer=None):
323
  mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
324
  mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB)
325
  s = ('%10s' * 2 + '%10.4g' * 6) % (
326
- '%g/%g' % (epoch, epochs - 1), mem, *mloss, targets.shape[0], imgs.shape[-1])
327
  pbar.set_description(s)
328
 
329
  # Plot
330
  if plots and ni < 3:
331
  f = save_dir / f'train_batch{ni}.jpg' # filename
332
  Thread(target=plot_images, args=(imgs, targets, paths, f), daemon=True).start()
333
- if tb_writer:
334
- tb_writer.add_graph(torch.jit.trace(de_parallel(model), imgs, strict=False), []) # model graph
335
- # tb_writer.add_image(f, result, dataformats='HWC', global_step=epoch)
 
336
  elif plots and ni == 10 and wandb_logger.wandb:
337
- wandb_logger.log({"Mosaics": [wandb_logger.wandb.Image(str(x), caption=x.name) for x in
338
  save_dir.glob('train*.jpg') if x.exists()]})
339
 
340
  # end batch ------------------------------------------------------------------------------------------------
@@ -358,6 +360,7 @@ def train(hyp, opt, device, tb_writer=None):
358
  single_cls=opt.single_cls,
359
  dataloader=testloader,
360
  save_dir=save_dir,
 
361
  verbose=nc < 50 and final_epoch,
362
  plots=plots and final_epoch,
363
  wandb_logger=wandb_logger,
@@ -409,41 +412,38 @@ def train(hyp, opt, device, tb_writer=None):
409
  # end epoch ----------------------------------------------------------------------------------------------------
410
  # end training
411
  if rank in [-1, 0]:
412
- # Plots
413
  if plots:
414
  plot_results(save_dir=save_dir) # save as results.png
415
  if wandb_logger.wandb:
416
  files = ['results.png', 'confusion_matrix.png', *[f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R')]]
417
  wandb_logger.log({"Results": [wandb_logger.wandb.Image(str(save_dir / f), caption=f) for f in files
418
  if (save_dir / f).exists()]})
419
- # Test best.pt
420
- logger.info('%g epochs completed in %.3f hours.\n' % (epoch - start_epoch + 1, (time.time() - t0) / 3600))
421
- if opt.data.endswith('coco.yaml') and nc == 80: # if COCO
422
- for m in [last, best] if best.exists() else [last]: # speed, mAP tests
423
- results, _, _ = test.test(opt.data,
424
- batch_size=batch_size * 2,
425
- imgsz=imgsz_test,
426
- conf_thres=0.001,
427
- iou_thres=0.7,
428
- model=attempt_load(m, device).half(),
429
- single_cls=opt.single_cls,
430
- dataloader=testloader,
431
- save_dir=save_dir,
432
- save_json=True,
433
- plots=False,
434
- is_coco=is_coco)
435
-
436
- # Strip optimizers
437
- final = best if best.exists() else last # final model
438
- for f in last, best:
439
- if f.exists():
440
- strip_optimizer(f) # strip optimizers
441
- if opt.bucket:
442
- os.system(f'gsutil cp {final} gs://{opt.bucket}/weights') # upload
443
- if wandb_logger.wandb and not opt.evolve: # Log the stripped model
444
- wandb_logger.wandb.log_artifact(str(final), type='model',
445
- name='run_' + wandb_logger.wandb_run.id + '_model',
446
- aliases=['latest', 'best', 'stripped'])
447
  wandb_logger.finish_run()
448
  else:
449
  dist.destroy_process_group()
 
4
  import os
5
  import random
6
  import time
7
+ import warnings
8
  from copy import deepcopy
9
  from pathlib import Path
10
  from threading import Thread
 
63
  init_seeds(2 + rank)
64
  with open(opt.data) as f:
65
  data_dict = yaml.safe_load(f) # data dict
 
66
 
67
  # Logging- Doing this before checking the dataset. Might update data_dict
68
  loggers = {'wandb': None} # loggers dict
 
78
  nc = 1 if opt.single_cls else int(data_dict['nc']) # number of classes
79
  names = ['item'] if opt.single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names
80
  assert len(names) == nc, '%g names found for nc=%g dataset in %s' % (len(names), nc, opt.data) # check
81
+ is_coco = opt.data.endswith('coco.yaml') and nc == 80 # COCO dataset
82
 
83
  # Model
84
  pretrained = weights.endswith('.pt')
85
  if pretrained:
86
  with torch_distributed_zero_first(rank):
87
+ weights = attempt_download(weights) # download if not found locally
88
  ckpt = torch.load(weights, map_location=device) # load checkpoint
89
  model = Model(opt.cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
90
  exclude = ['anchor'] if (opt.cfg or hyp.get('anchors')) and not opt.resume else [] # exclude keys
 
324
  mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
325
  mem = '%.3gG' % (torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0) # (GB)
326
  s = ('%10s' * 2 + '%10.4g' * 6) % (
327
+ f'{epoch}/{epochs - 1}', mem, *mloss, targets.shape[0], imgs.shape[-1])
328
  pbar.set_description(s)
329
 
330
  # Plot
331
  if plots and ni < 3:
332
  f = save_dir / f'train_batch{ni}.jpg' # filename
333
  Thread(target=plot_images, args=(imgs, targets, paths, f), daemon=True).start()
334
+ if tb_writer and ni == 0:
335
+ with warnings.catch_warnings():
336
+ warnings.simplefilter('ignore') # suppress jit trace warning
337
+ tb_writer.add_graph(torch.jit.trace(de_parallel(model), imgs, strict=False), []) # graph
338
  elif plots and ni == 10 and wandb_logger.wandb:
339
+ wandb_logger.log({'Mosaics': [wandb_logger.wandb.Image(str(x), caption=x.name) for x in
340
  save_dir.glob('train*.jpg') if x.exists()]})
341
 
342
  # end batch ------------------------------------------------------------------------------------------------
 
360
  single_cls=opt.single_cls,
361
  dataloader=testloader,
362
  save_dir=save_dir,
363
+ save_json=is_coco and final_epoch,
364
  verbose=nc < 50 and final_epoch,
365
  plots=plots and final_epoch,
366
  wandb_logger=wandb_logger,
 
412
  # end epoch ----------------------------------------------------------------------------------------------------
413
  # end training
414
  if rank in [-1, 0]:
415
+ logger.info(f'{epoch - start_epoch + 1} epochs completed in {(time.time() - t0) / 3600:.3f} hours.\n')
416
  if plots:
417
  plot_results(save_dir=save_dir) # save as results.png
418
  if wandb_logger.wandb:
419
  files = ['results.png', 'confusion_matrix.png', *[f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R')]]
420
  wandb_logger.log({"Results": [wandb_logger.wandb.Image(str(save_dir / f), caption=f) for f in files
421
  if (save_dir / f).exists()]})
422
+
423
+ if not opt.evolve:
424
+ if is_coco: # COCO dataset
425
+ for m in [last, best] if best.exists() else [last]: # speed, mAP tests
426
+ results, _, _ = test.test(opt.data,
427
+ batch_size=batch_size * 2,
428
+ imgsz=imgsz_test,
429
+ conf_thres=0.001,
430
+ iou_thres=0.7,
431
+ model=attempt_load(m, device).half(),
432
+ single_cls=opt.single_cls,
433
+ dataloader=testloader,
434
+ save_dir=save_dir,
435
+ save_json=True,
436
+ plots=False,
437
+ is_coco=is_coco)
438
+
439
+ # Strip optimizers
440
+ for f in last, best:
441
+ if f.exists():
442
+ strip_optimizer(f) # strip optimizers
443
+ if wandb_logger.wandb: # Log the stripped model
444
+ wandb_logger.wandb.log_artifact(str(best if best.exists() else last), type='model',
445
+ name='run_' + wandb_logger.wandb_run.id + '_model',
446
+ aliases=['latest', 'best', 'stripped'])
 
 
 
447
  wandb_logger.finish_run()
448
  else:
449
  dist.destroy_process_group()
tutorial.ipynb CHANGED
@@ -517,7 +517,8 @@
517
  "colab_type": "text"
518
  },
519
  "source": [
520
- "<a href=\"https://colab.research.google.com/github/ultralytics/yolov5/blob/master/tutorial.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
 
521
  ]
522
  },
523
  {
@@ -529,7 +530,7 @@
529
  "<img src=\"https://user-images.githubusercontent.com/26833433/98702494-b71c4e80-237a-11eb-87ed-17fcd6b3f066.jpg\">\n",
530
  "\n",
531
  "This is the **official YOLOv5 🚀 notebook** authored by **Ultralytics**, and is freely available for redistribution under the [GPL-3.0 license](https://choosealicense.com/licenses/gpl-3.0/). \n",
532
- "For more information please visit https://github.com/ultralytics/yolov5 and https://www.ultralytics.com. Thank you!"
533
  ]
534
  },
535
  {
@@ -610,7 +611,7 @@
610
  "YOLOv5 🚀 v5.0-1-g0f395b3 torch 1.8.1+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n",
611
  "\n",
612
  "Fusing layers... \n",
613
- "Model Summary: 224 layers, 7266973 parameters, 0 gradients, 17.0 GFLOPS\n",
614
  "image 1/2 /content/yolov5/data/images/bus.jpg: 640x480 4 persons, 1 bus, Done. (0.008s)\n",
615
  "image 2/2 /content/yolov5/data/images/zidane.jpg: 384x640 2 persons, 2 ties, Done. (0.008s)\n",
616
  "Results saved to runs/detect/exp\n",
@@ -733,7 +734,7 @@
733
  "100% 168M/168M [00:05<00:00, 32.3MB/s]\n",
734
  "\n",
735
  "Fusing layers... \n",
736
- "Model Summary: 476 layers, 87730285 parameters, 0 gradients, 218.8 GFLOPS\n",
737
  "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco/val2017' images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100% 5000/5000 [00:01<00:00, 3102.29it/s]\n",
738
  "\u001b[34m\u001b[1mval: \u001b[0mNew cache created: ../coco/val2017.cache\n",
739
  " Class Images Labels P R mAP@.5 mAP@.5:.95: 100% 157/157 [01:23<00:00, 1.87it/s]\n",
@@ -963,7 +964,7 @@
963
  " 22 [-1, 10] 1 0 models.common.Concat [1] \n",
964
  " 23 -1 1 1182720 models.common.C3 [512, 512, 1, False] \n",
965
  " 24 [17, 20, 23] 1 229245 models.yolo.Detect [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512]]\n",
966
- "Model Summary: 283 layers, 7276605 parameters, 7276605 gradients, 17.1 GFLOPS\n",
967
  "\n",
968
  "Transferred 362/362 items from yolov5s.pt\n",
969
  "Scaled weight_decay = 0.0005\n",
@@ -1260,4 +1261,4 @@
1260
  "outputs": []
1261
  }
1262
  ]
1263
- }
 
517
  "colab_type": "text"
518
  },
519
  "source": [
520
+ "<a href=\"https://colab.research.google.com/github/ultralytics/yolov5/blob/master/tutorial.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>",
521
+ "<a href=\"https://kaggle.com/kernels/welcome?src=https://github.com/ultralytics/yolov5/blob/master/tutorial.ipynb\" target=\"_parent\"><img alt=\"Kaggle\" title=\"Open in Kaggle\" src=\"https://kaggle.com/static/images/open-in-kaggle.svg\"></a>"
522
  ]
523
  },
524
  {
 
530
  "<img src=\"https://user-images.githubusercontent.com/26833433/98702494-b71c4e80-237a-11eb-87ed-17fcd6b3f066.jpg\">\n",
531
  "\n",
532
  "This is the **official YOLOv5 🚀 notebook** authored by **Ultralytics**, and is freely available for redistribution under the [GPL-3.0 license](https://choosealicense.com/licenses/gpl-3.0/). \n",
533
+ "For more information please visit https://github.com/ultralytics/yolov5 and https://ultralytics.com. Thank you!"
534
  ]
535
  },
536
  {
 
611
  "YOLOv5 🚀 v5.0-1-g0f395b3 torch 1.8.1+cu101 CUDA:0 (Tesla V100-SXM2-16GB, 16160.5MB)\n",
612
  "\n",
613
  "Fusing layers... \n",
614
+ "Model Summary: 224 layers, 7266973 parameters, 0 gradients, 17.0 GFLOPs\n",
615
  "image 1/2 /content/yolov5/data/images/bus.jpg: 640x480 4 persons, 1 bus, Done. (0.008s)\n",
616
  "image 2/2 /content/yolov5/data/images/zidane.jpg: 384x640 2 persons, 2 ties, Done. (0.008s)\n",
617
  "Results saved to runs/detect/exp\n",
 
734
  "100% 168M/168M [00:05<00:00, 32.3MB/s]\n",
735
  "\n",
736
  "Fusing layers... \n",
737
+ "Model Summary: 476 layers, 87730285 parameters, 0 gradients, 218.8 GFLOPs\n",
738
  "\u001b[34m\u001b[1mval: \u001b[0mScanning '../coco/val2017' images and labels... 4952 found, 48 missing, 0 empty, 0 corrupted: 100% 5000/5000 [00:01<00:00, 3102.29it/s]\n",
739
  "\u001b[34m\u001b[1mval: \u001b[0mNew cache created: ../coco/val2017.cache\n",
740
  " Class Images Labels P R mAP@.5 mAP@.5:.95: 100% 157/157 [01:23<00:00, 1.87it/s]\n",
 
964
  " 22 [-1, 10] 1 0 models.common.Concat [1] \n",
965
  " 23 -1 1 1182720 models.common.C3 [512, 512, 1, False] \n",
966
  " 24 [17, 20, 23] 1 229245 models.yolo.Detect [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512]]\n",
967
+ "Model Summary: 283 layers, 7276605 parameters, 7276605 gradients, 17.1 GFLOPs\n",
968
  "\n",
969
  "Transferred 362/362 items from yolov5s.pt\n",
970
  "Scaled weight_decay = 0.0005\n",
 
1261
  "outputs": []
1262
  }
1263
  ]
1264
+ }
utils/datasets.py CHANGED
@@ -535,7 +535,7 @@ class LoadImagesAndLabels(Dataset): # for training/testing
535
  # MixUp https://arxiv.org/pdf/1710.09412.pdf
536
  if random.random() < hyp['mixup']:
537
  img2, labels2 = load_mosaic(self, random.randint(0, self.n - 1))
538
- r = np.random.beta(8.0, 8.0) # mixup ratio, alpha=beta=8.0
539
  img = (img * r + img2 * (1 - r)).astype(np.uint8)
540
  labels = np.concatenate((labels, labels2), 0)
541
 
@@ -655,12 +655,12 @@ def augment_hsv(img, hgain=0.5, sgain=0.5, vgain=0.5):
655
  hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
656
  dtype = img.dtype # uint8
657
 
658
- x = np.arange(0, 256, dtype=np.int16)
659
  lut_hue = ((x * r[0]) % 180).astype(dtype)
660
  lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
661
  lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
662
 
663
- img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))).astype(dtype)
664
  cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img) # no return needed
665
 
666
 
 
535
  # MixUp https://arxiv.org/pdf/1710.09412.pdf
536
  if random.random() < hyp['mixup']:
537
  img2, labels2 = load_mosaic(self, random.randint(0, self.n - 1))
538
+ r = np.random.beta(32.0, 32.0) # mixup ratio, alpha=beta=32.0
539
  img = (img * r + img2 * (1 - r)).astype(np.uint8)
540
  labels = np.concatenate((labels, labels2), 0)
541
 
 
655
  hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
656
  dtype = img.dtype # uint8
657
 
658
+ x = np.arange(0, 256, dtype=r.dtype)
659
  lut_hue = ((x * r[0]) % 180).astype(dtype)
660
  lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
661
  lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
662
 
663
+ img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))
664
  cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img) # no return needed
665
 
666
 
utils/general.py CHANGED
@@ -1,5 +1,6 @@
1
  # YOLOv5 general utils
2
 
 
3
  import glob
4
  import logging
5
  import math
@@ -7,11 +8,13 @@ import os
7
  import platform
8
  import random
9
  import re
10
- import subprocess
11
  import time
 
12
  from itertools import repeat
13
  from multiprocessing.pool import ThreadPool
14
  from pathlib import Path
 
15
 
16
  import cv2
17
  import numpy as np
@@ -33,6 +36,26 @@ cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with Py
33
  os.environ['NUMEXPR_MAX_THREADS'] = str(min(os.cpu_count(), 8)) # NumExpr max threads
34
 
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  def set_logging(rank=-1, verbose=True):
37
  logging.basicConfig(
38
  format="%(message)s",
@@ -53,12 +76,12 @@ def get_latest_run(search_dir='.'):
53
 
54
 
55
  def is_docker():
56
- # Is environment a Docker container
57
  return Path('/workspace').exists() # or Path('/.dockerenv').exists()
58
 
59
 
60
  def is_colab():
61
- # Is environment a Google Colab instance
62
  try:
63
  import google.colab
64
  return True
@@ -66,6 +89,11 @@ def is_colab():
66
  return False
67
 
68
 
 
 
 
 
 
69
  def emojis(str=''):
70
  # Return platform-dependent emoji-safe version of string
71
  return str.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else str
@@ -80,13 +108,13 @@ def check_online():
80
  # Check internet connectivity
81
  import socket
82
  try:
83
- socket.create_connection(("1.1.1.1", 443), 5) # check host accesability
84
  return True
85
  except OSError:
86
  return False
87
 
88
 
89
- def check_git_status():
90
  # Recommend 'git pull' if code is out of date
91
  print(colorstr('github: '), end='')
92
  try:
@@ -95,9 +123,9 @@ def check_git_status():
95
  assert check_online(), 'skipping check (offline)'
96
 
97
  cmd = 'git fetch && git config --get remote.origin.url'
98
- url = subprocess.check_output(cmd, shell=True).decode().strip().rstrip('.git') # github repo url
99
- branch = subprocess.check_output('git rev-parse --abbrev-ref HEAD', shell=True).decode().strip() # checked out
100
- n = int(subprocess.check_output(f'git rev-list {branch}..origin/master --count', shell=True)) # commits behind
101
  if n > 0:
102
  s = f"⚠️ WARNING: code is out of date by {n} commit{'s' * (n > 1)}. " \
103
  f"Use 'git pull' to update or 'git clone {url}' to download latest."
@@ -105,7 +133,7 @@ def check_git_status():
105
  s = f'up to date with {url} ✅'
106
  print(emojis(s)) # emoji-safe
107
  except Exception as e:
108
- print(e)
109
 
110
 
111
  def check_python(minimum='3.7.0', required=True):
@@ -135,10 +163,11 @@ def check_requirements(requirements='requirements.txt', exclude=()):
135
  try:
136
  pkg.require(r)
137
  except Exception as e: # DistributionNotFound or VersionConflict if requirements not met
138
- n += 1
139
  print(f"{prefix} {r} not found and is required by YOLOv5, attempting auto-update...")
140
  try:
141
- print(subprocess.check_output(f"pip install '{r}'", shell=True).decode())
 
 
142
  except Exception as e:
143
  print(f'{prefix} {e}')
144
 
@@ -178,7 +207,8 @@ def check_file(file):
178
  if Path(file).is_file() or file == '': # exists
179
  return file
180
  elif file.startswith(('http://', 'https://')): # download
181
- url, file = file, Path(file).name
 
182
  print(f'Downloading {url} to {file}...')
183
  torch.hub.download_url_to_file(url, file)
184
  assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}' # check
 
1
  # YOLOv5 general utils
2
 
3
+ import contextlib
4
  import glob
5
  import logging
6
  import math
 
8
  import platform
9
  import random
10
  import re
11
+ import signal
12
  import time
13
+ import urllib
14
  from itertools import repeat
15
  from multiprocessing.pool import ThreadPool
16
  from pathlib import Path
17
+ from subprocess import check_output
18
 
19
  import cv2
20
  import numpy as np
 
36
  os.environ['NUMEXPR_MAX_THREADS'] = str(min(os.cpu_count(), 8)) # NumExpr max threads
37
 
38
 
39
+ class timeout(contextlib.ContextDecorator):
40
+ # Usage: @timeout(seconds) decorator or 'with timeout(seconds):' context manager
41
+ def __init__(self, seconds, *, timeout_msg='', suppress_timeout_errors=True):
42
+ self.seconds = int(seconds)
43
+ self.timeout_message = timeout_msg
44
+ self.suppress = bool(suppress_timeout_errors)
45
+
46
+ def _timeout_handler(self, signum, frame):
47
+ raise TimeoutError(self.timeout_message)
48
+
49
+ def __enter__(self):
50
+ signal.signal(signal.SIGALRM, self._timeout_handler) # Set handler for SIGALRM
51
+ signal.alarm(self.seconds) # start countdown for SIGALRM to be raised
52
+
53
+ def __exit__(self, exc_type, exc_val, exc_tb):
54
+ signal.alarm(0) # Cancel SIGALRM if it's scheduled
55
+ if self.suppress and exc_type is TimeoutError: # Suppress TimeoutError
56
+ return True
57
+
58
+
59
  def set_logging(rank=-1, verbose=True):
60
  logging.basicConfig(
61
  format="%(message)s",
 
76
 
77
 
78
  def is_docker():
79
+ # Is environment a Docker container?
80
  return Path('/workspace').exists() # or Path('/.dockerenv').exists()
81
 
82
 
83
  def is_colab():
84
+ # Is environment a Google Colab instance?
85
  try:
86
  import google.colab
87
  return True
 
89
  return False
90
 
91
 
92
+ def is_pip():
93
+ # Is file in a pip package?
94
+ return 'site-packages' in Path(__file__).absolute().parts
95
+
96
+
97
  def emojis(str=''):
98
  # Return platform-dependent emoji-safe version of string
99
  return str.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else str
 
108
  # Check internet connectivity
109
  import socket
110
  try:
111
+ socket.create_connection(("1.1.1.1", 443), 5) # check host accessibility
112
  return True
113
  except OSError:
114
  return False
115
 
116
 
117
+ def check_git_status(err_msg=', for updates see https://github.com/ultralytics/yolov5'):
118
  # Recommend 'git pull' if code is out of date
119
  print(colorstr('github: '), end='')
120
  try:
 
123
  assert check_online(), 'skipping check (offline)'
124
 
125
  cmd = 'git fetch && git config --get remote.origin.url'
126
+ url = check_output(cmd, shell=True, timeout=5).decode().strip().rstrip('.git') # git fetch
127
+ branch = check_output('git rev-parse --abbrev-ref HEAD', shell=True).decode().strip() # checked out
128
+ n = int(check_output(f'git rev-list {branch}..origin/master --count', shell=True)) # commits behind
129
  if n > 0:
130
  s = f"⚠️ WARNING: code is out of date by {n} commit{'s' * (n > 1)}. " \
131
  f"Use 'git pull' to update or 'git clone {url}' to download latest."
 
133
  s = f'up to date with {url} ✅'
134
  print(emojis(s)) # emoji-safe
135
  except Exception as e:
136
+ print(f'{e}{err_msg}')
137
 
138
 
139
  def check_python(minimum='3.7.0', required=True):
 
163
  try:
164
  pkg.require(r)
165
  except Exception as e: # DistributionNotFound or VersionConflict if requirements not met
 
166
  print(f"{prefix} {r} not found and is required by YOLOv5, attempting auto-update...")
167
  try:
168
+ assert check_online(), f"'pip install {r}' skipped (offline)"
169
+ print(check_output(f"pip install '{r}'", shell=True).decode())
170
+ n += 1
171
  except Exception as e:
172
  print(f'{prefix} {e}')
173
 
 
207
  if Path(file).is_file() or file == '': # exists
208
  return file
209
  elif file.startswith(('http://', 'https://')): # download
210
+ url, file = file, Path(urllib.parse.unquote(str(file))).name # url, file (decode '%2F' to '/' etc.)
211
+ file = file.split('?')[0] # parse authentication https://url.com/file.txt?auth...
212
  print(f'Downloading {url} to {file}...')
213
  torch.hub.download_url_to_file(url, file)
214
  assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}' # check
utils/google_utils.py CHANGED
@@ -4,6 +4,7 @@ import os
4
  import platform
5
  import subprocess
6
  import time
 
7
  from pathlib import Path
8
 
9
  import requests
@@ -16,11 +17,39 @@ def gsutil_getsize(url=''):
16
  return eval(s.split(' ')[0]) if len(s) else 0 # bytes
17
 
18
 
19
- def attempt_download(file, repo='ultralytics/yolov5'):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  # Attempt file download if does not exist
21
  file = Path(str(file).strip().replace("'", ''))
22
 
23
  if not file.exists():
 
 
 
 
 
 
 
 
 
24
  file.parent.mkdir(parents=True, exist_ok=True) # make parent dir (if required)
25
  try:
26
  response = requests.get(f'https://api.github.com/repos/{repo}/releases/latest').json() # github api
@@ -34,27 +63,14 @@ def attempt_download(file, repo='ultralytics/yolov5'):
34
  except:
35
  tag = 'v5.0' # current release
36
 
37
- name = file.name
38
  if name in assets:
39
- msg = f'{file} missing, try downloading from https://github.com/{repo}/releases/'
40
- redundant = False # second download option
41
- try: # GitHub
42
- url = f'https://github.com/{repo}/releases/download/{tag}/{name}'
43
- print(f'Downloading {url} to {file}...')
44
- torch.hub.download_url_to_file(url, file)
45
- assert file.exists() and file.stat().st_size > 1E6 # check
46
- except Exception as e: # GCP
47
- print(f'Download error: {e}')
48
- assert redundant, 'No secondary mirror'
49
- url = f'https://storage.googleapis.com/{repo}/ckpt/{name}'
50
- print(f'Downloading {url} to {file}...')
51
- os.system(f"curl -L '{url}' -o '{file}' --retry 3 -C -") # curl download, retry and resume on fail
52
- finally:
53
- if not file.exists() or file.stat().st_size < 1E6: # check
54
- file.unlink(missing_ok=True) # remove partial downloads
55
- print(f'ERROR: Download failure: {msg}')
56
- print('')
57
- return
58
 
59
 
60
  def gdrive_download(id='16TiPfZj7htmTyhntwcZyEEAejOUxuT6m', file='tmp.zip'):
 
4
  import platform
5
  import subprocess
6
  import time
7
+ import urllib
8
  from pathlib import Path
9
 
10
  import requests
 
17
  return eval(s.split(' ')[0]) if len(s) else 0 # bytes
18
 
19
 
20
+ def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''):
21
+ # Attempts to download file from url or url2, checks and removes incomplete downloads < min_bytes
22
+ file = Path(file)
23
+ assert_msg = f"Downloaded file '{file}' does not exist or size is < min_bytes={min_bytes}"
24
+ try: # url1
25
+ print(f'Downloading {url} to {file}...')
26
+ torch.hub.download_url_to_file(url, str(file))
27
+ assert file.exists() and file.stat().st_size > min_bytes, assert_msg # check
28
+ except Exception as e: # url2
29
+ file.unlink(missing_ok=True) # remove partial downloads
30
+ print(f'ERROR: {e}\nRe-attempting {url2 or url} to {file}...')
31
+ os.system(f"curl -L '{url2 or url}' -o '{file}' --retry 3 -C -") # curl download, retry and resume on fail
32
+ finally:
33
+ if not file.exists() or file.stat().st_size < min_bytes: # check
34
+ file.unlink(missing_ok=True) # remove partial downloads
35
+ print(f"ERROR: {assert_msg}\n{error_msg}")
36
+ print('')
37
+
38
+
39
+ def attempt_download(file, repo='ultralytics/yolov5'): # from utils.google_utils import *; attempt_download()
40
  # Attempt file download if does not exist
41
  file = Path(str(file).strip().replace("'", ''))
42
 
43
  if not file.exists():
44
+ # URL specified
45
+ name = Path(urllib.parse.unquote(str(file))).name # decode '%2F' to '/' etc.
46
+ if str(file).startswith(('http:/', 'https:/')): # download
47
+ url = str(file).replace(':/', '://') # Pathlib turns :// -> :/
48
+ name = name.split('?')[0] # parse authentication https://url.com/file.txt?auth...
49
+ safe_download(file=name, url=url, min_bytes=1E5)
50
+ return name
51
+
52
+ # GitHub assets
53
  file.parent.mkdir(parents=True, exist_ok=True) # make parent dir (if required)
54
  try:
55
  response = requests.get(f'https://api.github.com/repos/{repo}/releases/latest').json() # github api
 
63
  except:
64
  tag = 'v5.0' # current release
65
 
 
66
  if name in assets:
67
+ safe_download(file,
68
+ url=f'https://github.com/{repo}/releases/download/{tag}/{name}',
69
+ # url2=f'https://storage.googleapis.com/{repo}/ckpt/{name}', # backup url (optional)
70
+ min_bytes=1E5,
71
+ error_msg=f'{file} missing, try downloading from https://github.com/{repo}/releases/')
72
+
73
+ return str(file)
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
 
76
  def gdrive_download(id='16TiPfZj7htmTyhntwcZyEEAejOUxuT6m', file='tmp.zip'):
utils/torch_utils.py CHANGED
@@ -18,7 +18,7 @@ import torch.nn.functional as F
18
  import torchvision
19
 
20
  try:
21
- import thop # for FLOPS computation
22
  except ImportError:
23
  thop = None
24
  logger = logging.getLogger(__name__)
@@ -105,13 +105,13 @@ def profile(x, ops, n=100, device=None):
105
  x = x.to(device)
106
  x.requires_grad = True
107
  print(torch.__version__, device.type, torch.cuda.get_device_properties(0) if device.type == 'cuda' else '')
108
- print(f"\n{'Params':>12s}{'GFLOPS':>12s}{'forward (ms)':>16s}{'backward (ms)':>16s}{'input':>24s}{'output':>24s}")
109
  for m in ops if isinstance(ops, list) else [ops]:
110
  m = m.to(device) if hasattr(m, 'to') else m # device
111
  m = m.half() if hasattr(m, 'half') and isinstance(x, torch.Tensor) and x.dtype is torch.float16 else m # type
112
  dtf, dtb, t = 0., 0., [0., 0., 0.] # dt forward, backward
113
  try:
114
- flops = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 # GFLOPS
115
  except:
116
  flops = 0
117
 
@@ -219,13 +219,13 @@ def model_info(model, verbose=False, img_size=640):
219
  print('%5g %40s %9s %12g %20s %10.3g %10.3g' %
220
  (i, name, p.requires_grad, p.numel(), list(p.shape), p.mean(), p.std()))
221
 
222
- try: # FLOPS
223
  from thop import profile
224
  stride = max(int(model.stride.max()), 32) if hasattr(model, 'stride') else 32
225
  img = torch.zeros((1, model.yaml.get('ch', 3), stride, stride), device=next(model.parameters()).device) # input
226
- flops = profile(deepcopy(model), inputs=(img,), verbose=False)[0] / 1E9 * 2 # stride GFLOPS
227
  img_size = img_size if isinstance(img_size, list) else [img_size, img_size] # expand if int/float
228
- fs = ', %.1f GFLOPS' % (flops * img_size[0] / stride * img_size[1] / stride) # 640x640 GFLOPS
229
  except (ImportError, Exception):
230
  fs = ''
231
 
 
18
  import torchvision
19
 
20
  try:
21
+ import thop # for FLOPs computation
22
  except ImportError:
23
  thop = None
24
  logger = logging.getLogger(__name__)
 
105
  x = x.to(device)
106
  x.requires_grad = True
107
  print(torch.__version__, device.type, torch.cuda.get_device_properties(0) if device.type == 'cuda' else '')
108
+ print(f"\n{'Params':>12s}{'GFLOPs':>12s}{'forward (ms)':>16s}{'backward (ms)':>16s}{'input':>24s}{'output':>24s}")
109
  for m in ops if isinstance(ops, list) else [ops]:
110
  m = m.to(device) if hasattr(m, 'to') else m # device
111
  m = m.half() if hasattr(m, 'half') and isinstance(x, torch.Tensor) and x.dtype is torch.float16 else m # type
112
  dtf, dtb, t = 0., 0., [0., 0., 0.] # dt forward, backward
113
  try:
114
+ flops = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 # GFLOPs
115
  except:
116
  flops = 0
117
 
 
219
  print('%5g %40s %9s %12g %20s %10.3g %10.3g' %
220
  (i, name, p.requires_grad, p.numel(), list(p.shape), p.mean(), p.std()))
221
 
222
+ try: # FLOPs
223
  from thop import profile
224
  stride = max(int(model.stride.max()), 32) if hasattr(model, 'stride') else 32
225
  img = torch.zeros((1, model.yaml.get('ch', 3), stride, stride), device=next(model.parameters()).device) # input
226
+ flops = profile(deepcopy(model), inputs=(img,), verbose=False)[0] / 1E9 * 2 # stride GFLOPs
227
  img_size = img_size if isinstance(img_size, list) else [img_size, img_size] # expand if int/float
228
+ fs = ', %.1f GFLOPs' % (flops * img_size[0] / stride * img_size[1] / stride) # 640x640 GFLOPs
229
  except (ImportError, Exception):
230
  fs = ''
231