Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

common.py 51 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
  1. # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
  2. """Common modules."""
  3. import ast
  4. import contextlib
  5. import json
  6. import math
  7. import platform
  8. import warnings
  9. import zipfile
  10. from collections import OrderedDict, namedtuple
  11. from copy import copy
  12. from pathlib import Path
  13. from urllib.parse import urlparse
  14. import cv2
  15. import numpy as np
  16. import pandas as pd
  17. import requests
  18. import torch
  19. import torch.nn as nn
  20. from PIL import Image
  21. from torch.cuda import amp
  22. # Import 'ultralytics' package or install if missing
  23. try:
  24. import ultralytics
  25. assert hasattr(ultralytics, "__version__") # verify package is not directory
  26. except (ImportError, AssertionError):
  27. import os
  28. os.system("pip install -U ultralytics")
  29. import ultralytics
  30. from ultralytics.utils.plotting import Annotator, colors, save_one_box
  31. from utils import TryExcept
  32. from utils.dataloaders import exif_transpose, letterbox
  33. from utils.general import (
  34. LOGGER,
  35. ROOT,
  36. Profile,
  37. check_requirements,
  38. check_suffix,
  39. check_version,
  40. colorstr,
  41. increment_path,
  42. is_jupyter,
  43. make_divisible,
  44. non_max_suppression,
  45. scale_boxes,
  46. xywh2xyxy,
  47. xyxy2xywh,
  48. yaml_load,
  49. )
  50. from utils.torch_utils import copy_attr, smart_inference_mode
  51. def autopad(k, p=None, d=1):
  52. """
  53. Pads kernel to 'same' output shape, adjusting for optional dilation; returns padding size.
  54. `k`: kernel, `p`: padding, `d`: dilation.
  55. """
  56. if d > 1:
  57. k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size
  58. if p is None:
  59. p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
  60. return p
  61. class Conv(nn.Module):
  62. """Applies a convolution, batch normalization, and activation function to an input tensor in a neural network."""
  63. default_act = nn.SiLU() # default activation
  64. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
  65. """Initializes a standard convolution layer with optional batch normalization and activation."""
  66. super().__init__()
  67. self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
  68. self.bn = nn.BatchNorm2d(c2)
  69. self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
  70. def forward(self, x):
  71. """Applies a convolution followed by batch normalization and an activation function to the input tensor `x`."""
  72. return self.act(self.bn(self.conv(x)))
  73. def forward_fuse(self, x):
  74. """Applies a fused convolution and activation function to the input tensor `x`."""
  75. return self.act(self.conv(x))
  76. class DWConv(Conv):
  77. """Implements a depth-wise convolution layer with optional activation for efficient spatial filtering."""
  78. def __init__(self, c1, c2, k=1, s=1, d=1, act=True):
  79. """Initializes a depth-wise convolution layer with optional activation; args: input channels (c1), output
  80. channels (c2), kernel size (k), stride (s), dilation (d), and activation flag (act).
  81. """
  82. super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), d=d, act=act)
  83. class DWConvTranspose2d(nn.ConvTranspose2d):
  84. """A depth-wise transpose convolutional layer for upsampling in neural networks, particularly in YOLOv5 models."""
  85. def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0):
  86. """Initializes a depth-wise transpose convolutional layer for YOLOv5; args: input channels (c1), output channels
  87. (c2), kernel size (k), stride (s), input padding (p1), output padding (p2).
  88. """
  89. super().__init__(c1, c2, k, s, p1, p2, groups=math.gcd(c1, c2))
  90. class TransformerLayer(nn.Module):
  91. """Transformer layer with multihead attention and linear layers, optimized by removing LayerNorm."""
  92. def __init__(self, c, num_heads):
  93. """
  94. Initializes a transformer layer, sans LayerNorm for performance, with multihead attention and linear layers.
  95. See as described in https://arxiv.org/abs/2010.11929.
  96. """
  97. super().__init__()
  98. self.q = nn.Linear(c, c, bias=False)
  99. self.k = nn.Linear(c, c, bias=False)
  100. self.v = nn.Linear(c, c, bias=False)
  101. self.ma = nn.MultiheadAttention(embed_dim=c, num_heads=num_heads)
  102. self.fc1 = nn.Linear(c, c, bias=False)
  103. self.fc2 = nn.Linear(c, c, bias=False)
  104. def forward(self, x):
  105. """Performs forward pass using MultiheadAttention and two linear transformations with residual connections."""
  106. x = self.ma(self.q(x), self.k(x), self.v(x))[0] + x
  107. x = self.fc2(self.fc1(x)) + x
  108. return x
  109. class TransformerBlock(nn.Module):
  110. """A Transformer block for vision tasks with convolution, position embeddings, and Transformer layers."""
  111. def __init__(self, c1, c2, num_heads, num_layers):
  112. """Initializes a Transformer block for vision tasks, adapting dimensions if necessary and stacking specified
  113. layers.
  114. """
  115. super().__init__()
  116. self.conv = None
  117. if c1 != c2:
  118. self.conv = Conv(c1, c2)
  119. self.linear = nn.Linear(c2, c2) # learnable position embedding
  120. self.tr = nn.Sequential(*(TransformerLayer(c2, num_heads) for _ in range(num_layers)))
  121. self.c2 = c2
  122. def forward(self, x):
  123. """Processes input through an optional convolution, followed by Transformer layers and position embeddings for
  124. object detection.
  125. """
  126. if self.conv is not None:
  127. x = self.conv(x)
  128. b, _, w, h = x.shape
  129. p = x.flatten(2).permute(2, 0, 1)
  130. return self.tr(p + self.linear(p)).permute(1, 2, 0).reshape(b, self.c2, w, h)
  131. class Bottleneck(nn.Module):
  132. """A bottleneck layer with optional shortcut and group convolution for efficient feature extraction."""
  133. def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):
  134. """Initializes a standard bottleneck layer with optional shortcut and group convolution, supporting channel
  135. expansion.
  136. """
  137. super().__init__()
  138. c_ = int(c2 * e) # hidden channels
  139. self.cv1 = Conv(c1, c_, 1, 1)
  140. self.cv2 = Conv(c_, c2, 3, 1, g=g)
  141. self.add = shortcut and c1 == c2
  142. def forward(self, x):
  143. """Processes input through two convolutions, optionally adds shortcut if channel dimensions match; input is a
  144. tensor.
  145. """
  146. return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
  147. class BottleneckCSP(nn.Module):
  148. """CSP bottleneck layer for feature extraction with cross-stage partial connections and optional shortcuts."""
  149. def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
  150. """Initializes CSP bottleneck with optional shortcuts; args: ch_in, ch_out, number of repeats, shortcut bool,
  151. groups, expansion.
  152. """
  153. super().__init__()
  154. c_ = int(c2 * e) # hidden channels
  155. self.cv1 = Conv(c1, c_, 1, 1)
  156. self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
  157. self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
  158. self.cv4 = Conv(2 * c_, c2, 1, 1)
  159. self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3)
  160. self.act = nn.SiLU()
  161. self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
  162. def forward(self, x):
  163. """Performs forward pass by applying layers, activation, and concatenation on input x, returning feature-
  164. enhanced output.
  165. """
  166. y1 = self.cv3(self.m(self.cv1(x)))
  167. y2 = self.cv2(x)
  168. return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1))))
  169. class CrossConv(nn.Module):
  170. """Implements a cross convolution layer with downsampling, expansion, and optional shortcut."""
  171. def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False):
  172. """
  173. Initializes CrossConv with downsampling, expanding, and optionally shortcutting; `c1` input, `c2` output
  174. channels.
  175. Inputs are ch_in, ch_out, kernel, stride, groups, expansion, shortcut.
  176. """
  177. super().__init__()
  178. c_ = int(c2 * e) # hidden channels
  179. self.cv1 = Conv(c1, c_, (1, k), (1, s))
  180. self.cv2 = Conv(c_, c2, (k, 1), (s, 1), g=g)
  181. self.add = shortcut and c1 == c2
  182. def forward(self, x):
  183. """Performs feature sampling, expanding, and applies shortcut if channels match; expects `x` input tensor."""
  184. return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
  185. class C3(nn.Module):
  186. """Implements a CSP Bottleneck module with three convolutions for enhanced feature extraction in neural networks."""
  187. def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
  188. """Initializes C3 module with options for channel count, bottleneck repetition, shortcut usage, group
  189. convolutions, and expansion.
  190. """
  191. super().__init__()
  192. c_ = int(c2 * e) # hidden channels
  193. self.cv1 = Conv(c1, c_, 1, 1)
  194. self.cv2 = Conv(c1, c_, 1, 1)
  195. self.cv3 = Conv(2 * c_, c2, 1) # optional act=FReLU(c2)
  196. self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
  197. def forward(self, x):
  198. """Performs forward propagation using concatenated outputs from two convolutions and a Bottleneck sequence."""
  199. return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
  200. class C3x(C3):
  201. """Extends the C3 module with cross-convolutions for enhanced feature extraction in neural networks."""
  202. def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
  203. """Initializes C3x module with cross-convolutions, extending C3 with customizable channel dimensions, groups,
  204. and expansion.
  205. """
  206. super().__init__(c1, c2, n, shortcut, g, e)
  207. c_ = int(c2 * e)
  208. self.m = nn.Sequential(*(CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)))
  209. class C3TR(C3):
  210. """C3 module with TransformerBlock for enhanced feature extraction in object detection models."""
  211. def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
  212. """Initializes C3 module with TransformerBlock for enhanced feature extraction, accepts channel sizes, shortcut
  213. config, group, and expansion.
  214. """
  215. super().__init__(c1, c2, n, shortcut, g, e)
  216. c_ = int(c2 * e)
  217. self.m = TransformerBlock(c_, c_, 4, n)
  218. class C3SPP(C3):
  219. """Extends the C3 module with an SPP layer for enhanced spatial feature extraction and customizable channels."""
  220. def __init__(self, c1, c2, k=(5, 9, 13), n=1, shortcut=True, g=1, e=0.5):
  221. """Initializes a C3 module with SPP layer for advanced spatial feature extraction, given channel sizes, kernel
  222. sizes, shortcut, group, and expansion ratio.
  223. """
  224. super().__init__(c1, c2, n, shortcut, g, e)
  225. c_ = int(c2 * e)
  226. self.m = SPP(c_, c_, k)
  227. class C3Ghost(C3):
  228. """Implements a C3 module with Ghost Bottlenecks for efficient feature extraction in YOLOv5."""
  229. def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
  230. """Initializes YOLOv5's C3 module with Ghost Bottlenecks for efficient feature extraction."""
  231. super().__init__(c1, c2, n, shortcut, g, e)
  232. c_ = int(c2 * e) # hidden channels
  233. self.m = nn.Sequential(*(GhostBottleneck(c_, c_) for _ in range(n)))
  234. class SPP(nn.Module):
  235. """Implements Spatial Pyramid Pooling (SPP) for feature extraction, ref: https://arxiv.org/abs/1406.4729."""
  236. def __init__(self, c1, c2, k=(5, 9, 13)):
  237. """Initializes SPP layer with Spatial Pyramid Pooling, ref: https://arxiv.org/abs/1406.4729, args: c1 (input channels), c2 (output channels), k (kernel sizes)."""
  238. super().__init__()
  239. c_ = c1 // 2 # hidden channels
  240. self.cv1 = Conv(c1, c_, 1, 1)
  241. self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
  242. self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
  243. def forward(self, x):
  244. """Applies convolution and max pooling layers to the input tensor `x`, concatenates results, and returns output
  245. tensor.
  246. """
  247. x = self.cv1(x)
  248. with warnings.catch_warnings():
  249. warnings.simplefilter("ignore") # suppress torch 1.9.0 max_pool2d() warning
  250. return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
  251. class SPPF(nn.Module):
  252. """Implements a fast Spatial Pyramid Pooling (SPPF) layer for efficient feature extraction in YOLOv5 models."""
  253. def __init__(self, c1, c2, k=5):
  254. """
  255. Initializes YOLOv5 SPPF layer with given channels and kernel size for YOLOv5 model, combining convolution and
  256. max pooling.
  257. Equivalent to SPP(k=(5, 9, 13)).
  258. """
  259. super().__init__()
  260. c_ = c1 // 2 # hidden channels
  261. self.cv1 = Conv(c1, c_, 1, 1)
  262. self.cv2 = Conv(c_ * 4, c2, 1, 1)
  263. self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
  264. def forward(self, x):
  265. """Processes input through a series of convolutions and max pooling operations for feature extraction."""
  266. x = self.cv1(x)
  267. with warnings.catch_warnings():
  268. warnings.simplefilter("ignore") # suppress torch 1.9.0 max_pool2d() warning
  269. y1 = self.m(x)
  270. y2 = self.m(y1)
  271. return self.cv2(torch.cat((x, y1, y2, self.m(y2)), 1))
  272. class Focus(nn.Module):
  273. """Focuses spatial information into channel space using slicing and convolution for efficient feature extraction."""
  274. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
  275. """Initializes Focus module to concentrate width-height info into channel space with configurable convolution
  276. parameters.
  277. """
  278. super().__init__()
  279. self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act)
  280. # self.contract = Contract(gain=2)
  281. def forward(self, x):
  282. """Processes input through Focus mechanism, reshaping (b,c,w,h) to (b,4c,w/2,h/2) then applies convolution."""
  283. return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1))
  284. # return self.conv(self.contract(x))
  285. class GhostConv(nn.Module):
  286. """Implements Ghost Convolution for efficient feature extraction, see https://github.com/huawei-noah/ghostnet."""
  287. def __init__(self, c1, c2, k=1, s=1, g=1, act=True):
  288. """Initializes GhostConv with in/out channels, kernel size, stride, groups, and activation; halves out channels
  289. for efficiency.
  290. """
  291. super().__init__()
  292. c_ = c2 // 2 # hidden channels
  293. self.cv1 = Conv(c1, c_, k, s, None, g, act=act)
  294. self.cv2 = Conv(c_, c_, 5, 1, None, c_, act=act)
  295. def forward(self, x):
  296. """Performs forward pass, concatenating outputs of two convolutions on input `x`: shape (B,C,H,W)."""
  297. y = self.cv1(x)
  298. return torch.cat((y, self.cv2(y)), 1)
  299. class GhostBottleneck(nn.Module):
  300. """Efficient bottleneck layer using Ghost Convolutions, see https://github.com/huawei-noah/ghostnet."""
  301. def __init__(self, c1, c2, k=3, s=1):
  302. """Initializes GhostBottleneck with ch_in `c1`, ch_out `c2`, kernel size `k`, stride `s`; see https://github.com/huawei-noah/ghostnet."""
  303. super().__init__()
  304. c_ = c2 // 2
  305. self.conv = nn.Sequential(
  306. GhostConv(c1, c_, 1, 1), # pw
  307. DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # dw
  308. GhostConv(c_, c2, 1, 1, act=False),
  309. ) # pw-linear
  310. self.shortcut = (
  311. nn.Sequential(DWConv(c1, c1, k, s, act=False), Conv(c1, c2, 1, 1, act=False)) if s == 2 else nn.Identity()
  312. )
  313. def forward(self, x):
  314. """Processes input through conv and shortcut layers, returning their summed output."""
  315. return self.conv(x) + self.shortcut(x)
  316. class Contract(nn.Module):
  317. """Contracts spatial dimensions into channel dimensions for efficient processing in neural networks."""
  318. def __init__(self, gain=2):
  319. """Initializes a layer to contract spatial dimensions (width-height) into channels, e.g., input shape
  320. (1,64,80,80) to (1,256,40,40).
  321. """
  322. super().__init__()
  323. self.gain = gain
  324. def forward(self, x):
  325. """Processes input tensor to expand channel dimensions by contracting spatial dimensions, yielding output shape
  326. `(b, c*s*s, h//s, w//s)`.
  327. """
  328. b, c, h, w = x.size() # assert (h / s == 0) and (W / s == 0), 'Indivisible gain'
  329. s = self.gain
  330. x = x.view(b, c, h // s, s, w // s, s) # x(1,64,40,2,40,2)
  331. x = x.permute(0, 3, 5, 1, 2, 4).contiguous() # x(1,2,2,64,40,40)
  332. return x.view(b, c * s * s, h // s, w // s) # x(1,256,40,40)
  333. class Expand(nn.Module):
  334. """Expands spatial dimensions by redistributing channels, e.g., from (1,64,80,80) to (1,16,160,160)."""
  335. def __init__(self, gain=2):
  336. """
  337. Initializes the Expand module to increase spatial dimensions by redistributing channels, with an optional gain
  338. factor.
  339. Example: x(1,64,80,80) to x(1,16,160,160).
  340. """
  341. super().__init__()
  342. self.gain = gain
  343. def forward(self, x):
  344. """Processes input tensor x to expand spatial dimensions by redistributing channels, requiring C / gain^2 ==
  345. 0.
  346. """
  347. b, c, h, w = x.size() # assert C / s ** 2 == 0, 'Indivisible gain'
  348. s = self.gain
  349. x = x.view(b, s, s, c // s**2, h, w) # x(1,2,2,16,80,80)
  350. x = x.permute(0, 3, 4, 1, 5, 2).contiguous() # x(1,16,80,2,80,2)
  351. return x.view(b, c // s**2, h * s, w * s) # x(1,16,160,160)
  352. class Concat(nn.Module):
  353. """Concatenates tensors along a specified dimension for efficient tensor manipulation in neural networks."""
  354. def __init__(self, dimension=1):
  355. """Initializes a Concat module to concatenate tensors along a specified dimension."""
  356. super().__init__()
  357. self.d = dimension
  358. def forward(self, x):
  359. """Concatenates a list of tensors along a specified dimension; `x` is a list of tensors, `dimension` is an
  360. int.
  361. """
  362. return torch.cat(x, self.d)
  363. class DetectMultiBackend(nn.Module):
  364. """YOLOv5 MultiBackend class for inference on various backends including PyTorch, ONNX, TensorRT, and more."""
  365. def __init__(self, weights="yolov5s.pt", device=torch.device("cpu"), dnn=False, data=None, fp16=False, fuse=True):
  366. """Initializes DetectMultiBackend with support for various inference backends, including PyTorch and ONNX."""
  367. # PyTorch: weights = *.pt
  368. # TorchScript: *.torchscript
  369. # ONNX Runtime: *.onnx
  370. # ONNX OpenCV DNN: *.onnx --dnn
  371. # OpenVINO: *_openvino_model
  372. # CoreML: *.mlpackage
  373. # TensorRT: *.engine
  374. # TensorFlow SavedModel: *_saved_model
  375. # TensorFlow GraphDef: *.pb
  376. # TensorFlow Lite: *.tflite
  377. # TensorFlow Edge TPU: *_edgetpu.tflite
  378. # PaddlePaddle: *_paddle_model
  379. from models.experimental import attempt_download, attempt_load # scoped to avoid circular import
  380. super().__init__()
  381. w = str(weights[0] if isinstance(weights, list) else weights)
  382. pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle, triton = self._model_type(w)
  383. fp16 &= pt or jit or onnx or engine or triton # FP16
  384. nhwc = coreml or saved_model or pb or tflite or edgetpu # BHWC formats (vs torch BCWH)
  385. stride = 32 # default stride
  386. cuda = torch.cuda.is_available() and device.type != "cpu" # use CUDA
  387. if not (pt or triton):
  388. w = attempt_download(w) # download if not local
  389. if pt: # PyTorch
  390. model = attempt_load(weights if isinstance(weights, list) else w, device=device, inplace=True, fuse=fuse)
  391. stride = max(int(model.stride.max()), 32) # model stride
  392. names = model.module.names if hasattr(model, "module") else model.names # get class names
  393. model.half() if fp16 else model.float()
  394. self.model = model # explicitly assign for to(), cpu(), cuda(), half()
  395. elif jit: # TorchScript
  396. LOGGER.info(f"Loading {w} for TorchScript inference...")
  397. extra_files = {"config.txt": ""} # model metadata
  398. model = torch.jit.load(w, _extra_files=extra_files, map_location=device)
  399. model.half() if fp16 else model.float()
  400. if extra_files["config.txt"]: # load metadata dict
  401. d = json.loads(
  402. extra_files["config.txt"],
  403. object_hook=lambda d: {int(k) if k.isdigit() else k: v for k, v in d.items()},
  404. )
  405. stride, names = int(d["stride"]), d["names"]
  406. elif dnn: # ONNX OpenCV DNN
  407. LOGGER.info(f"Loading {w} for ONNX OpenCV DNN inference...")
  408. check_requirements("opencv-python>=4.5.4")
  409. net = cv2.dnn.readNetFromONNX(w)
  410. elif onnx: # ONNX Runtime
  411. LOGGER.info(f"Loading {w} for ONNX Runtime inference...")
  412. check_requirements(("onnx", "onnxruntime-gpu" if cuda else "onnxruntime"))
  413. import onnxruntime
  414. providers = ["CUDAExecutionProvider", "CPUExecutionProvider"] if cuda else ["CPUExecutionProvider"]
  415. session = onnxruntime.InferenceSession(w, providers=providers)
  416. output_names = [x.name for x in session.get_outputs()]
  417. meta = session.get_modelmeta().custom_metadata_map # metadata
  418. if "stride" in meta:
  419. stride, names = int(meta["stride"]), eval(meta["names"])
  420. elif xml: # OpenVINO
  421. LOGGER.info(f"Loading {w} for OpenVINO inference...")
  422. check_requirements("openvino>=2023.0") # requires openvino-dev: https://pypi.org/project/openvino-dev/
  423. from openvino.runtime import Core, Layout, get_batch
  424. core = Core()
  425. if not Path(w).is_file(): # if not *.xml
  426. w = next(Path(w).glob("*.xml")) # get *.xml file from *_openvino_model dir
  427. ov_model = core.read_model(model=w, weights=Path(w).with_suffix(".bin"))
  428. if ov_model.get_parameters()[0].get_layout().empty:
  429. ov_model.get_parameters()[0].set_layout(Layout("NCHW"))
  430. batch_dim = get_batch(ov_model)
  431. if batch_dim.is_static:
  432. batch_size = batch_dim.get_length()
  433. ov_compiled_model = core.compile_model(ov_model, device_name="AUTO") # AUTO selects best available device
  434. stride, names = self._load_metadata(Path(w).with_suffix(".yaml")) # load metadata
  435. elif engine: # TensorRT
  436. LOGGER.info(f"Loading {w} for TensorRT inference...")
  437. import tensorrt as trt # https://developer.nvidia.com/nvidia-tensorrt-download
  438. check_version(trt.__version__, "7.0.0", hard=True) # require tensorrt>=7.0.0
  439. if device.type == "cpu":
  440. device = torch.device("cuda:0")
  441. Binding = namedtuple("Binding", ("name", "dtype", "shape", "data", "ptr"))
  442. logger = trt.Logger(trt.Logger.INFO)
  443. with open(w, "rb") as f, trt.Runtime(logger) as runtime:
  444. model = runtime.deserialize_cuda_engine(f.read())
  445. context = model.create_execution_context()
  446. bindings = OrderedDict()
  447. output_names = []
  448. fp16 = False # default updated below
  449. dynamic = False
  450. is_trt10 = not hasattr(model, "num_bindings")
  451. num = range(model.num_io_tensors) if is_trt10 else range(model.num_bindings)
  452. for i in num:
  453. if is_trt10:
  454. name = model.get_tensor_name(i)
  455. dtype = trt.nptype(model.get_tensor_dtype(name))
  456. is_input = model.get_tensor_mode(name) == trt.TensorIOMode.INPUT
  457. if is_input:
  458. if -1 in tuple(model.get_tensor_shape(name)): # dynamic
  459. dynamic = True
  460. context.set_input_shape(name, tuple(model.get_profile_shape(name, 0)[2]))
  461. if dtype == np.float16:
  462. fp16 = True
  463. else: # output
  464. output_names.append(name)
  465. shape = tuple(context.get_tensor_shape(name))
  466. else:
  467. name = model.get_binding_name(i)
  468. dtype = trt.nptype(model.get_binding_dtype(i))
  469. if model.binding_is_input(i):
  470. if -1 in tuple(model.get_binding_shape(i)): # dynamic
  471. dynamic = True
  472. context.set_binding_shape(i, tuple(model.get_profile_shape(0, i)[2]))
  473. if dtype == np.float16:
  474. fp16 = True
  475. else: # output
  476. output_names.append(name)
  477. shape = tuple(context.get_binding_shape(i))
  478. im = torch.from_numpy(np.empty(shape, dtype=dtype)).to(device)
  479. bindings[name] = Binding(name, dtype, shape, im, int(im.data_ptr()))
  480. binding_addrs = OrderedDict((n, d.ptr) for n, d in bindings.items())
  481. batch_size = bindings["images"].shape[0] # if dynamic, this is instead max batch size
  482. elif coreml: # CoreML
  483. LOGGER.info(f"Loading {w} for CoreML inference...")
  484. import coremltools as ct
  485. model = ct.models.MLModel(w)
  486. elif saved_model: # TF SavedModel
  487. LOGGER.info(f"Loading {w} for TensorFlow SavedModel inference...")
  488. import tensorflow as tf
  489. keras = False # assume TF1 saved_model
  490. model = tf.keras.models.load_model(w) if keras else tf.saved_model.load(w)
  491. elif pb: # GraphDef https://www.tensorflow.org/guide/migrate#a_graphpb_or_graphpbtxt
  492. LOGGER.info(f"Loading {w} for TensorFlow GraphDef inference...")
  493. import tensorflow as tf
  494. def wrap_frozen_graph(gd, inputs, outputs):
  495. """Wraps a TensorFlow GraphDef for inference, returning a pruned function."""
  496. x = tf.compat.v1.wrap_function(lambda: tf.compat.v1.import_graph_def(gd, name=""), []) # wrapped
  497. ge = x.graph.as_graph_element
  498. return x.prune(tf.nest.map_structure(ge, inputs), tf.nest.map_structure(ge, outputs))
  499. def gd_outputs(gd):
  500. """Generates a sorted list of graph outputs excluding NoOp nodes and inputs, formatted as '<name>:0'."""
  501. name_list, input_list = [], []
  502. for node in gd.node: # tensorflow.core.framework.node_def_pb2.NodeDef
  503. name_list.append(node.name)
  504. input_list.extend(node.input)
  505. return sorted(f"{x}:0" for x in list(set(name_list) - set(input_list)) if not x.startswith("NoOp"))
  506. gd = tf.Graph().as_graph_def() # TF GraphDef
  507. with open(w, "rb") as f:
  508. gd.ParseFromString(f.read())
  509. frozen_func = wrap_frozen_graph(gd, inputs="x:0", outputs=gd_outputs(gd))
  510. elif tflite or edgetpu: # https://www.tensorflow.org/lite/guide/python#install_tensorflow_lite_for_python
  511. try: # https://coral.ai/docs/edgetpu/tflite-python/#update-existing-tf-lite-code-for-the-edge-tpu
  512. from tflite_runtime.interpreter import Interpreter, load_delegate
  513. except ImportError:
  514. import tensorflow as tf
  515. Interpreter, load_delegate = (
  516. tf.lite.Interpreter,
  517. tf.lite.experimental.load_delegate,
  518. )
  519. if edgetpu: # TF Edge TPU https://coral.ai/software/#edgetpu-runtime
  520. LOGGER.info(f"Loading {w} for TensorFlow Lite Edge TPU inference...")
  521. delegate = {"Linux": "libedgetpu.so.1", "Darwin": "libedgetpu.1.dylib", "Windows": "edgetpu.dll"}[
  522. platform.system()
  523. ]
  524. interpreter = Interpreter(model_path=w, experimental_delegates=[load_delegate(delegate)])
  525. else: # TFLite
  526. LOGGER.info(f"Loading {w} for TensorFlow Lite inference...")
  527. interpreter = Interpreter(model_path=w) # load TFLite model
  528. interpreter.allocate_tensors() # allocate
  529. input_details = interpreter.get_input_details() # inputs
  530. output_details = interpreter.get_output_details() # outputs
  531. # load metadata
  532. with contextlib.suppress(zipfile.BadZipFile):
  533. with zipfile.ZipFile(w, "r") as model:
  534. meta_file = model.namelist()[0]
  535. meta = ast.literal_eval(model.read(meta_file).decode("utf-8"))
  536. stride, names = int(meta["stride"]), meta["names"]
  537. elif tfjs: # TF.js
  538. raise NotImplementedError("ERROR: YOLOv5 TF.js inference is not supported")
  539. # PaddlePaddle
  540. elif paddle:
  541. LOGGER.info(f"Loading {w} for PaddlePaddle inference...")
  542. check_requirements("paddlepaddle-gpu" if cuda else "paddlepaddle>=3.0.0")
  543. import paddle.inference as pdi
  544. w = Path(w)
  545. if w.is_dir():
  546. model_file = next(w.rglob("*.json"), None)
  547. params_file = next(w.rglob("*.pdiparams"), None)
  548. elif w.suffix == ".pdiparams":
  549. model_file = w.with_name("model.json")
  550. params_file = w
  551. else:
  552. raise ValueError(f"Invalid model path {w}. Provide model directory or a .pdiparams file.")
  553. if not (model_file and params_file and model_file.is_file() and params_file.is_file()):
  554. raise FileNotFoundError(f"Model files not found in {w}. Both .json and .pdiparams files are required.")
  555. config = pdi.Config(str(model_file), str(params_file))
  556. if cuda:
  557. config.enable_use_gpu(memory_pool_init_size_mb=2048, device_id=0)
  558. predictor = pdi.create_predictor(config)
  559. input_handle = predictor.get_input_handle(predictor.get_input_names()[0])
  560. output_names = predictor.get_output_names()
  561. elif triton: # NVIDIA Triton Inference Server
  562. LOGGER.info(f"Using {w} as Triton Inference Server...")
  563. check_requirements("tritonclient[all]")
  564. from utils.triton import TritonRemoteModel
  565. model = TritonRemoteModel(url=w)
  566. nhwc = model.runtime.startswith("tensorflow")
  567. else:
  568. raise NotImplementedError(f"ERROR: {w} is not a supported format")
  569. # class names
  570. if "names" not in locals():
  571. names = yaml_load(data)["names"] if data else {i: f"class{i}" for i in range(999)}
  572. if names[0] == "n01440764" and len(names) == 1000: # ImageNet
  573. names = yaml_load(ROOT / "data/ImageNet.yaml")["names"] # human-readable names
  574. self.__dict__.update(locals()) # assign all variables to self
  575. def forward(self, im, augment=False, visualize=False):
  576. """Performs YOLOv5 inference on input images with options for augmentation and visualization."""
  577. b, ch, h, w = im.shape # batch, channel, height, width
  578. if self.fp16 and im.dtype != torch.float16:
  579. im = im.half() # to FP16
  580. if self.nhwc:
  581. im = im.permute(0, 2, 3, 1) # torch BCHW to numpy BHWC shape(1,320,192,3)
  582. if self.pt: # PyTorch
  583. y = self.model(im, augment=augment, visualize=visualize) if augment or visualize else self.model(im)
  584. elif self.jit: # TorchScript
  585. y = self.model(im)
  586. elif self.dnn: # ONNX OpenCV DNN
  587. im = im.cpu().numpy() # torch to numpy
  588. self.net.setInput(im)
  589. y = self.net.forward()
  590. elif self.onnx: # ONNX Runtime
  591. im = im.cpu().numpy() # torch to numpy
  592. y = self.session.run(self.output_names, {self.session.get_inputs()[0].name: im})
  593. elif self.xml: # OpenVINO
  594. im = im.cpu().numpy() # FP32
  595. y = list(self.ov_compiled_model(im).values())
  596. elif self.engine: # TensorRT
  597. if self.dynamic and im.shape != self.bindings["images"].shape:
  598. i = self.model.get_binding_index("images")
  599. self.context.set_binding_shape(i, im.shape) # reshape if dynamic
  600. self.bindings["images"] = self.bindings["images"]._replace(shape=im.shape)
  601. for name in self.output_names:
  602. i = self.model.get_binding_index(name)
  603. self.bindings[name].data.resize_(tuple(self.context.get_binding_shape(i)))
  604. s = self.bindings["images"].shape
  605. assert im.shape == s, f"input size {im.shape} {'>' if self.dynamic else 'not equal to'} max model size {s}"
  606. self.binding_addrs["images"] = int(im.data_ptr())
  607. self.context.execute_v2(list(self.binding_addrs.values()))
  608. y = [self.bindings[x].data for x in sorted(self.output_names)]
  609. elif self.coreml: # CoreML
  610. im = im.cpu().numpy()
  611. im = Image.fromarray((im[0] * 255).astype("uint8"))
  612. # im = im.resize((192, 320), Image.BILINEAR)
  613. y = self.model.predict({"image": im}) # coordinates are xywh normalized
  614. if "confidence" in y:
  615. box = xywh2xyxy(y["coordinates"] * [[w, h, w, h]]) # xyxy pixels
  616. conf, cls = y["confidence"].max(1), y["confidence"].argmax(1).astype(np.float)
  617. y = np.concatenate((box, conf.reshape(-1, 1), cls.reshape(-1, 1)), 1)
  618. else:
  619. y = list(reversed(y.values())) # reversed for segmentation models (pred, proto)
  620. elif self.paddle: # PaddlePaddle
  621. im = im.cpu().numpy().astype(np.float32)
  622. self.input_handle.copy_from_cpu(im)
  623. self.predictor.run()
  624. y = [self.predictor.get_output_handle(x).copy_to_cpu() for x in self.output_names]
  625. elif self.triton: # NVIDIA Triton Inference Server
  626. y = self.model(im)
  627. else: # TensorFlow (SavedModel, GraphDef, Lite, Edge TPU)
  628. im = im.cpu().numpy()
  629. if self.saved_model: # SavedModel
  630. y = self.model(im, training=False) if self.keras else self.model(im)
  631. elif self.pb: # GraphDef
  632. y = self.frozen_func(x=self.tf.constant(im))
  633. else: # Lite or Edge TPU
  634. input = self.input_details[0]
  635. int8 = input["dtype"] == np.uint8 # is TFLite quantized uint8 model
  636. if int8:
  637. scale, zero_point = input["quantization"]
  638. im = (im / scale + zero_point).astype(np.uint8) # de-scale
  639. self.interpreter.set_tensor(input["index"], im)
  640. self.interpreter.invoke()
  641. y = []
  642. for output in self.output_details:
  643. x = self.interpreter.get_tensor(output["index"])
  644. if int8:
  645. scale, zero_point = output["quantization"]
  646. x = (x.astype(np.float32) - zero_point) * scale # re-scale
  647. y.append(x)
  648. if len(y) == 2 and len(y[1].shape) != 4:
  649. y = list(reversed(y))
  650. y = [x if isinstance(x, np.ndarray) else x.numpy() for x in y]
  651. y[0][..., :4] *= [w, h, w, h] # xywh normalized to pixels
  652. if isinstance(y, (list, tuple)):
  653. return self.from_numpy(y[0]) if len(y) == 1 else [self.from_numpy(x) for x in y]
  654. else:
  655. return self.from_numpy(y)
  656. def from_numpy(self, x):
  657. """Converts a NumPy array to a torch tensor, maintaining device compatibility."""
  658. return torch.from_numpy(x).to(self.device) if isinstance(x, np.ndarray) else x
  659. def warmup(self, imgsz=(1, 3, 640, 640)):
  660. """Performs a single inference warmup to initialize model weights, accepting an `imgsz` tuple for image size."""
  661. warmup_types = self.pt, self.jit, self.onnx, self.engine, self.saved_model, self.pb, self.triton
  662. if any(warmup_types) and (self.device.type != "cpu" or self.triton):
  663. im = torch.empty(*imgsz, dtype=torch.half if self.fp16 else torch.float, device=self.device) # input
  664. for _ in range(2 if self.jit else 1): #
  665. self.forward(im) # warmup
  666. @staticmethod
  667. def _model_type(p="path/to/model.pt"):
  668. """
  669. Determines model type from file path or URL, supporting various export formats.
  670. Example: path='path/to/model.onnx' -> type=onnx
  671. """
  672. # types = [pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle]
  673. from export import export_formats
  674. from utils.downloads import is_url
  675. sf = list(export_formats().Suffix) # export suffixes
  676. if not is_url(p, check=False):
  677. check_suffix(p, sf) # checks
  678. url = urlparse(p) # if url may be Triton inference server
  679. types = [s in Path(p).name for s in sf]
  680. types[8] &= not types[9] # tflite &= not edgetpu
  681. triton = not any(types) and all([any(s in url.scheme for s in ["http", "grpc"]), url.netloc])
  682. return types + [triton]
  683. @staticmethod
  684. def _load_metadata(f=Path("path/to/meta.yaml")):
  685. """Loads metadata from a YAML file, returning strides and names if the file exists, otherwise `None`."""
  686. if f.exists():
  687. d = yaml_load(f)
  688. return d["stride"], d["names"] # assign stride, names
  689. return None, None
  690. class AutoShape(nn.Module):
  691. """AutoShape class for robust YOLOv5 inference with preprocessing, NMS, and support for various input formats."""
  692. conf = 0.25 # NMS confidence threshold
  693. iou = 0.45 # NMS IoU threshold
  694. agnostic = False # NMS class-agnostic
  695. multi_label = False # NMS multiple labels per box
  696. classes = None # (optional list) filter by class, i.e. = [0, 15, 16] for COCO persons, cats and dogs
  697. max_det = 1000 # maximum number of detections per image
  698. amp = False # Automatic Mixed Precision (AMP) inference
  699. def __init__(self, model, verbose=True):
  700. """Initializes YOLOv5 model for inference, setting up attributes and preparing model for evaluation."""
  701. super().__init__()
  702. if verbose:
  703. LOGGER.info("Adding AutoShape... ")
  704. copy_attr(self, model, include=("yaml", "nc", "hyp", "names", "stride", "abc"), exclude=()) # copy attributes
  705. self.dmb = isinstance(model, DetectMultiBackend) # DetectMultiBackend() instance
  706. self.pt = not self.dmb or model.pt # PyTorch model
  707. self.model = model.eval()
  708. if self.pt:
  709. m = self.model.model.model[-1] if self.dmb else self.model.model[-1] # Detect()
  710. m.inplace = False # Detect.inplace=False for safe multithread inference
  711. m.export = True # do not output loss values
  712. def _apply(self, fn):
  713. """
  714. Applies to(), cpu(), cuda(), half() etc.
  715. to model tensors excluding parameters or registered buffers.
  716. """
  717. self = super()._apply(fn)
  718. if self.pt:
  719. m = self.model.model.model[-1] if self.dmb else self.model.model[-1] # Detect()
  720. m.stride = fn(m.stride)
  721. m.grid = list(map(fn, m.grid))
  722. if isinstance(m.anchor_grid, list):
  723. m.anchor_grid = list(map(fn, m.anchor_grid))
  724. return self
  725. @smart_inference_mode()
  726. def forward(self, ims, size=640, augment=False, profile=False):
  727. """
  728. Performs inference on inputs with optional augment & profiling.
  729. Supports various formats including file, URI, OpenCV, PIL, numpy, torch.
  730. """
  731. # For size(height=640, width=1280), RGB images example inputs are:
  732. # file: ims = 'data/images/zidane.jpg' # str or PosixPath
  733. # URI: = 'https://ultralytics.com/images/zidane.jpg'
  734. # OpenCV: = cv2.imread('image.jpg')[:,:,::-1] # HWC BGR to RGB x(640,1280,3)
  735. # PIL: = Image.open('image.jpg') or ImageGrab.grab() # HWC x(640,1280,3)
  736. # numpy: = np.zeros((640,1280,3)) # HWC
  737. # torch: = torch.zeros(16,3,320,640) # BCHW (scaled to size=640, 0-1 values)
  738. # multiple: = [Image.open('image1.jpg'), Image.open('image2.jpg'), ...] # list of images
  739. dt = (Profile(), Profile(), Profile())
  740. with dt[0]:
  741. if isinstance(size, int): # expand
  742. size = (size, size)
  743. p = next(self.model.parameters()) if self.pt else torch.empty(1, device=self.model.device) # param
  744. autocast = self.amp and (p.device.type != "cpu") # Automatic Mixed Precision (AMP) inference
  745. if isinstance(ims, torch.Tensor): # torch
  746. with amp.autocast(autocast):
  747. return self.model(ims.to(p.device).type_as(p), augment=augment) # inference
  748. # Pre-process
  749. n, ims = (len(ims), list(ims)) if isinstance(ims, (list, tuple)) else (1, [ims]) # number, list of images
  750. shape0, shape1, files = [], [], [] # image and inference shapes, filenames
  751. for i, im in enumerate(ims):
  752. f = f"image{i}" # filename
  753. if isinstance(im, (str, Path)): # filename or uri
  754. im, f = Image.open(requests.get(im, stream=True).raw if str(im).startswith("http") else im), im
  755. im = np.asarray(exif_transpose(im))
  756. elif isinstance(im, Image.Image): # PIL Image
  757. im, f = np.asarray(exif_transpose(im)), getattr(im, "filename", f) or f
  758. files.append(Path(f).with_suffix(".jpg").name)
  759. if im.shape[0] < 5: # image in CHW
  760. im = im.transpose((1, 2, 0)) # reverse dataloader .transpose(2, 0, 1)
  761. im = im[..., :3] if im.ndim == 3 else cv2.cvtColor(im, cv2.COLOR_GRAY2BGR) # enforce 3ch input
  762. s = im.shape[:2] # HWC
  763. shape0.append(s) # image shape
  764. g = max(size) / max(s) # gain
  765. shape1.append([int(y * g) for y in s])
  766. ims[i] = im if im.data.contiguous else np.ascontiguousarray(im) # update
  767. shape1 = [make_divisible(x, self.stride) for x in np.array(shape1).max(0)] # inf shape
  768. x = [letterbox(im, shape1, auto=False)[0] for im in ims] # pad
  769. x = np.ascontiguousarray(np.array(x).transpose((0, 3, 1, 2))) # stack and BHWC to BCHW
  770. x = torch.from_numpy(x).to(p.device).type_as(p) / 255 # uint8 to fp16/32
  771. with amp.autocast(autocast):
  772. # Inference
  773. with dt[1]:
  774. y = self.model(x, augment=augment) # forward
  775. # Post-process
  776. with dt[2]:
  777. y = non_max_suppression(
  778. y if self.dmb else y[0],
  779. self.conf,
  780. self.iou,
  781. self.classes,
  782. self.agnostic,
  783. self.multi_label,
  784. max_det=self.max_det,
  785. ) # NMS
  786. for i in range(n):
  787. scale_boxes(shape1, y[i][:, :4], shape0[i])
  788. return Detections(ims, y, files, dt, self.names, x.shape)
  789. class Detections:
  790. """Manages YOLOv5 detection results with methods for visualization, saving, cropping, and exporting detections."""
  791. def __init__(self, ims, pred, files, times=(0, 0, 0), names=None, shape=None):
  792. """Initializes the YOLOv5 Detections class with image info, predictions, filenames, timing and normalization."""
  793. super().__init__()
  794. d = pred[0].device # device
  795. gn = [torch.tensor([*(im.shape[i] for i in [1, 0, 1, 0]), 1, 1], device=d) for im in ims] # normalizations
  796. self.ims = ims # list of images as numpy arrays
  797. self.pred = pred # list of tensors pred[0] = (xyxy, conf, cls)
  798. self.names = names # class names
  799. self.files = files # image filenames
  800. self.times = times # profiling times
  801. self.xyxy = pred # xyxy pixels
  802. self.xywh = [xyxy2xywh(x) for x in pred] # xywh pixels
  803. self.xyxyn = [x / g for x, g in zip(self.xyxy, gn)] # xyxy normalized
  804. self.xywhn = [x / g for x, g in zip(self.xywh, gn)] # xywh normalized
  805. self.n = len(self.pred) # number of images (batch size)
  806. self.t = tuple(x.t / self.n * 1e3 for x in times) # timestamps (ms)
  807. self.s = tuple(shape) # inference BCHW shape
  808. def _run(self, pprint=False, show=False, save=False, crop=False, render=False, labels=True, save_dir=Path("")):
  809. """Executes model predictions, displaying and/or saving outputs with optional crops and labels."""
  810. s, crops = "", []
  811. for i, (im, pred) in enumerate(zip(self.ims, self.pred)):
  812. s += f"\nimage {i + 1}/{len(self.pred)}: {im.shape[0]}x{im.shape[1]} " # string
  813. if pred.shape[0]:
  814. for c in pred[:, -1].unique():
  815. n = (pred[:, -1] == c).sum() # detections per class
  816. s += f"{n} {self.names[int(c)]}{'s' * (n > 1)}, " # add to string
  817. s = s.rstrip(", ")
  818. if show or save or render or crop:
  819. annotator = Annotator(im, example=str(self.names))
  820. for *box, conf, cls in reversed(pred): # xyxy, confidence, class
  821. label = f"{self.names[int(cls)]} {conf:.2f}"
  822. if crop:
  823. file = save_dir / "crops" / self.names[int(cls)] / self.files[i] if save else None
  824. crops.append(
  825. {
  826. "box": box,
  827. "conf": conf,
  828. "cls": cls,
  829. "label": label,
  830. "im": save_one_box(box, im, file=file, save=save),
  831. }
  832. )
  833. else: # all others
  834. annotator.box_label(box, label if labels else "", color=colors(cls))
  835. im = annotator.im
  836. else:
  837. s += "(no detections)"
  838. im = Image.fromarray(im.astype(np.uint8)) if isinstance(im, np.ndarray) else im # from np
  839. if show:
  840. if is_jupyter():
  841. from IPython.display import display
  842. display(im)
  843. else:
  844. im.show(self.files[i])
  845. if save:
  846. f = self.files[i]
  847. im.save(save_dir / f) # save
  848. if i == self.n - 1:
  849. LOGGER.info(f"Saved {self.n} image{'s' * (self.n > 1)} to {colorstr('bold', save_dir)}")
  850. if render:
  851. self.ims[i] = np.asarray(im)
  852. if pprint:
  853. s = s.lstrip("\n")
  854. return f"{s}\nSpeed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {self.s}" % self.t
  855. if crop:
  856. if save:
  857. LOGGER.info(f"Saved results to {save_dir}\n")
  858. return crops
  859. @TryExcept("Showing images is not supported in this environment")
  860. def show(self, labels=True):
  861. """
  862. Displays detection results with optional labels.
  863. Usage: show(labels=True)
  864. """
  865. self._run(show=True, labels=labels) # show results
  866. def save(self, labels=True, save_dir="runs/detect/exp", exist_ok=False):
  867. """
  868. Saves detection results with optional labels to a specified directory.
  869. Usage: save(labels=True, save_dir='runs/detect/exp', exist_ok=False)
  870. """
  871. save_dir = increment_path(save_dir, exist_ok, mkdir=True) # increment save_dir
  872. self._run(save=True, labels=labels, save_dir=save_dir) # save results
  873. def crop(self, save=True, save_dir="runs/detect/exp", exist_ok=False):
  874. """
  875. Crops detection results, optionally saves them to a directory.
  876. Args: save (bool), save_dir (str), exist_ok (bool).
  877. """
  878. save_dir = increment_path(save_dir, exist_ok, mkdir=True) if save else None
  879. return self._run(crop=True, save=save, save_dir=save_dir) # crop results
  880. def render(self, labels=True):
  881. """Renders detection results with optional labels on images; args: labels (bool) indicating label inclusion."""
  882. self._run(render=True, labels=labels) # render results
  883. return self.ims
  884. def pandas(self):
  885. """
  886. Returns detections as pandas DataFrames for various box formats (xyxy, xyxyn, xywh, xywhn).
  887. Example: print(results.pandas().xyxy[0]).
  888. """
  889. new = copy(self) # return copy
  890. ca = "xmin", "ymin", "xmax", "ymax", "confidence", "class", "name" # xyxy columns
  891. cb = "xcenter", "ycenter", "width", "height", "confidence", "class", "name" # xywh columns
  892. for k, c in zip(["xyxy", "xyxyn", "xywh", "xywhn"], [ca, ca, cb, cb]):
  893. a = [[x[:5] + [int(x[5]), self.names[int(x[5])]] for x in x.tolist()] for x in getattr(self, k)] # update
  894. setattr(new, k, [pd.DataFrame(x, columns=c) for x in a])
  895. return new
  896. def tolist(self):
  897. """
  898. Converts a Detections object into a list of individual detection results for iteration.
  899. Example: for result in results.tolist():
  900. """
  901. r = range(self.n) # iterable
  902. return [
  903. Detections(
  904. [self.ims[i]],
  905. [self.pred[i]],
  906. [self.files[i]],
  907. self.times,
  908. self.names,
  909. self.s,
  910. )
  911. for i in r
  912. ]
  913. def print(self):
  914. """Logs the string representation of the current object's state via the LOGGER."""
  915. LOGGER.info(self.__str__())
  916. def __len__(self):
  917. """Returns the number of results stored, overrides the default len(results)."""
  918. return self.n
  919. def __str__(self):
  920. """Returns a string representation of the model's results, suitable for printing, overrides default
  921. print(results).
  922. """
  923. return self._run(pprint=True) # print results
  924. def __repr__(self):
  925. """Returns a string representation of the YOLOv5 object, including its class and formatted results."""
  926. return f"YOLOv5 {self.__class__} instance\n" + self.__str__()
  927. class Proto(nn.Module):
  928. """YOLOv5 mask Proto module for segmentation models, performing convolutions and upsampling on input tensors."""
  929. def __init__(self, c1, c_=256, c2=32):
  930. """Initializes YOLOv5 Proto module for segmentation with input, proto, and mask channels configuration."""
  931. super().__init__()
  932. self.cv1 = Conv(c1, c_, k=3)
  933. self.upsample = nn.Upsample(scale_factor=2, mode="nearest")
  934. self.cv2 = Conv(c_, c_, k=3)
  935. self.cv3 = Conv(c_, c2)
  936. def forward(self, x):
  937. """Performs a forward pass using convolutional layers and upsampling on input tensor `x`."""
  938. return self.cv3(self.cv2(self.upsample(self.cv1(x))))
  939. class Classify(nn.Module):
  940. """YOLOv5 classification head with convolution, pooling, and dropout layers for channel transformation."""
  941. def __init__(
  942. self, c1, c2, k=1, s=1, p=None, g=1, dropout_p=0.0
  943. ): # ch_in, ch_out, kernel, stride, padding, groups, dropout probability
  944. """Initializes YOLOv5 classification head with convolution, pooling, and dropout layers for input to output
  945. channel transformation.
  946. """
  947. super().__init__()
  948. c_ = 1280 # efficientnet_b0 size
  949. self.conv = Conv(c1, c_, k, s, autopad(k, p), g)
  950. self.pool = nn.AdaptiveAvgPool2d(1) # to x(b,c_,1,1)
  951. self.drop = nn.Dropout(p=dropout_p, inplace=True)
  952. self.linear = nn.Linear(c_, c2) # to x(b,c2)
  953. def forward(self, x):
  954. """Processes input through conv, pool, drop, and linear layers; supports list concatenation input."""
  955. if isinstance(x, list):
  956. x = torch.cat(x, 1)
  957. return self.linear(self.drop(self.pool(self.conv(x)).flatten(1)))
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...