yolov3/LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
yolov3/README.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # YOLOv3 ONNX Conversion
2
+
3
+ ## Prerequisites
4
+
5
+ ### 1. Model files (cfg + weights)
6
+
7
+ The Darknet `.cfg` file is already provided in [opencv_extra/testdata/dnn](https://github.com/opencv/opencv_extra/tree/master/testdata/dnn) (`yolov3.cfg`).
8
+
9
+ Download the `.weights` file using the OpenCV test data download script:
10
+
11
+ ```bash
12
+ git clone https://github.com/opencv/opencv_extra.git
13
+ cd opencv_extra/testdata/dnn
14
+ python download_models.py YOLOv3
15
+ ```
16
+
17
+ ### 2. Python environment for `pytorch-YOLOv4`
18
+
19
+ The conversion uses [`pytorch-YOLOv4`](https://github.com/Tianxiaomo/pytorch-YOLOv4). Create a Python environment with the required dependencies:
20
+
21
+ Supported Python versions: **3.8 – 3.10**.
22
+
23
+ ```bash
24
+ conda create -n <env_name> python=<3.8-3.10> -y
25
+ conda activate <env_name>
26
+ pip install "torch<2.4" "torchvision<0.19" "numpy<2" onnx onnxruntime "onnxscript==0.1.0"
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Conversion of YOLOv3 to ONNX
32
+
33
+ ### Why it requires a patch
34
+
35
+ The original YOLOv3 `.cfg` does not contain the `scale_x_y` field (introduced in YOLOv4). The `pytorch-YOLOv4` converter requires this field unconditionally, causing a `KeyError` when converting YOLOv3. The fix is to default `scale_x_y` to `1.0` when the field is absent.
36
+
37
+ ### The Fix (modified script provided)
38
+
39
+ A patched version of **`darknet2pytorch.py`** is provided in this repository. It adds a default value for `scale_x_y` when the field is absent:
40
+
41
+ ```python
42
+ # changed line in tool/darknet2pytorch.py
43
+ yolo_layer.scale_x_y = float(block.get('scale_x_y', 1.0))
44
+ ```
45
+
46
+ ### Conversion Steps
47
+
48
+ ```bash
49
+ git clone https://github.com/Tianxiaomo/pytorch-YOLOv4.git
50
+ cd pytorch-YOLOv4
51
+
52
+ # [!] Replace tool/darknet2pytorch.py with the patched version from this repository
53
+ # before running the conversion.
54
+
55
+ # Convert YOLOv3 (dynamic batch, batch_size=0)
56
+ python -c "from tool.darknet2onnx import transform_to_onnx; transform_to_onnx('yolov3.cfg', 'yolov3.weights', 0)"
57
+ ```
58
+
59
+ The output file will be named `yolov4_-1_3_416_416_dynamic.onnx` (the script uses `yolov4` as the default prefix regardless of input model).
60
+
61
+ ---
62
+
63
+ ## Usage
64
+
65
+ A demo script is provided to run inference using OpenCV DNN:
66
+
67
+ ```bash
68
+ python demo.py --model yolov3.onnx \
69
+ --image example_outputs/input.jpg \
70
+ --output example_outputs/yolov3_output.jpg
71
+ ```
72
+
73
+ The demo prints the detected COCO classes, confidence scores, and bounding boxes, and saves an annotated output image.
74
+
75
+ ---
76
+
77
+ ## License
78
+
79
+ See [LICENSE](./LICENSE) — This conversion tool is based on [pytorch-YOLOv4](https://github.com/Tianxiaomo/pytorch-YOLOv4) (Apache-2.0). Original YOLOv3 model weights and configuration are released by Joseph Redmon ([pjreddie/darknet](https://github.com/pjreddie/darknet)).
yolov3/darknet2pytorch.py ADDED
@@ -0,0 +1,536 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ import torch.nn.functional as F
3
+ import numpy as np
4
+ from tool.region_loss import RegionLoss
5
+ from tool.yolo_layer import YoloLayer
6
+ from tool.config import *
7
+ from tool.torch_utils import *
8
+
9
+
10
+ class Mish(torch.nn.Module):
11
+ def __init__(self):
12
+ super().__init__()
13
+
14
+ def forward(self, x):
15
+ x = x * (torch.tanh(torch.nn.functional.softplus(x)))
16
+ return x
17
+
18
+
19
+ class MaxPoolDark(nn.Module):
20
+ def __init__(self, size=2, stride=1):
21
+ super(MaxPoolDark, self).__init__()
22
+ self.size = size
23
+ self.stride = stride
24
+
25
+ def forward(self, x):
26
+ '''
27
+ darknet output_size = (input_size + p - k) / s +1
28
+ p : padding = k - 1
29
+ k : size
30
+ s : stride
31
+ torch output_size = (input_size + 2*p -k) / s +1
32
+ p : padding = k//2
33
+ '''
34
+ p = self.size // 2
35
+ if ((x.shape[2] - 1) // self.stride) != ((x.shape[2] + 2 * p - self.size) // self.stride):
36
+ padding1 = (self.size - 1) // 2
37
+ padding2 = padding1 + 1
38
+ else:
39
+ padding1 = (self.size - 1) // 2
40
+ padding2 = padding1
41
+ if ((x.shape[3] - 1) // self.stride) != ((x.shape[3] + 2 * p - self.size) // self.stride):
42
+ padding3 = (self.size - 1) // 2
43
+ padding4 = padding3 + 1
44
+ else:
45
+ padding3 = (self.size - 1) // 2
46
+ padding4 = padding3
47
+ x = F.max_pool2d(F.pad(x, (padding3, padding4, padding1, padding2), mode='replicate'),
48
+ self.size, stride=self.stride)
49
+ return x
50
+
51
+
52
+ class Upsample_expand(nn.Module):
53
+ def __init__(self, stride=2):
54
+ super(Upsample_expand, self).__init__()
55
+ self.stride = stride
56
+
57
+ def forward(self, x):
58
+ assert (x.data.dim() == 4)
59
+
60
+ x = x.view(x.size(0), x.size(1), x.size(2), 1, x.size(3), 1).\
61
+ expand(x.size(0), x.size(1), x.size(2), self.stride, x.size(3), self.stride).contiguous().\
62
+ view(x.size(0), x.size(1), x.size(2) * self.stride, x.size(3) * self.stride)
63
+
64
+ return x
65
+
66
+
67
+ class Upsample_interpolate(nn.Module):
68
+ def __init__(self, stride):
69
+ super(Upsample_interpolate, self).__init__()
70
+ self.stride = stride
71
+
72
+ def forward(self, x):
73
+ assert (x.data.dim() == 4)
74
+
75
+ out = F.interpolate(x, size=(x.size(2) * self.stride, x.size(3) * self.stride), mode='nearest')
76
+ return out
77
+
78
+
79
+ class Reorg(nn.Module):
80
+ def __init__(self, stride=2):
81
+ super(Reorg, self).__init__()
82
+ self.stride = stride
83
+
84
+ def forward(self, x):
85
+ stride = self.stride
86
+ assert (x.data.dim() == 4)
87
+ B = x.data.size(0)
88
+ C = x.data.size(1)
89
+ H = x.data.size(2)
90
+ W = x.data.size(3)
91
+ assert (H % stride == 0)
92
+ assert (W % stride == 0)
93
+ ws = stride
94
+ hs = stride
95
+ x = x.view(B, C, H / hs, hs, W / ws, ws).transpose(3, 4).contiguous()
96
+ x = x.view(B, C, H / hs * W / ws, hs * ws).transpose(2, 3).contiguous()
97
+ x = x.view(B, C, hs * ws, H / hs, W / ws).transpose(1, 2).contiguous()
98
+ x = x.view(B, hs * ws * C, H / hs, W / ws)
99
+ return x
100
+
101
+
102
+ class GlobalAvgPool2d(nn.Module):
103
+ def __init__(self):
104
+ super(GlobalAvgPool2d, self).__init__()
105
+
106
+ def forward(self, x):
107
+ N = x.data.size(0)
108
+ C = x.data.size(1)
109
+ H = x.data.size(2)
110
+ W = x.data.size(3)
111
+ x = F.avg_pool2d(x, (H, W))
112
+ x = x.view(N, C)
113
+ return x
114
+
115
+
116
+ # for route, shortcut and sam
117
+ class EmptyModule(nn.Module):
118
+ def __init__(self):
119
+ super(EmptyModule, self).__init__()
120
+
121
+ def forward(self, x):
122
+ return x
123
+
124
+
125
+ # support route shortcut and reorg
126
+ class Darknet(nn.Module):
127
+ def __init__(self, cfgfile, inference=False):
128
+ super(Darknet, self).__init__()
129
+ self.inference = inference
130
+ self.training = not self.inference
131
+
132
+ self.blocks = parse_cfg(cfgfile)
133
+ self.width = int(self.blocks[0]['width'])
134
+ self.height = int(self.blocks[0]['height'])
135
+
136
+ self.models = self.create_network(self.blocks) # merge conv, bn,leaky
137
+ self.loss = self.models[len(self.models) - 1]
138
+
139
+ if self.blocks[(len(self.blocks) - 1)]['type'] == 'region':
140
+ self.anchors = self.loss.anchors
141
+ self.num_anchors = self.loss.num_anchors
142
+ self.anchor_step = self.loss.anchor_step
143
+ self.num_classes = self.loss.num_classes
144
+
145
+ self.header = torch.IntTensor([0, 0, 0, 0])
146
+ self.seen = 0
147
+
148
+ def forward(self, x):
149
+ ind = -2
150
+ self.loss = None
151
+ outputs = dict()
152
+ out_boxes = []
153
+ for block in self.blocks:
154
+ ind = ind + 1
155
+ # if ind > 0:
156
+ # return x
157
+
158
+ if block['type'] == 'net':
159
+ continue
160
+ elif block['type'] in ['convolutional', 'maxpool', 'reorg', 'upsample', 'avgpool', 'softmax', 'connected']:
161
+ x = self.models[ind](x)
162
+ outputs[ind] = x
163
+ elif block['type'] == 'route':
164
+ layers = block['layers'].split(',')
165
+ layers = [int(i) if int(i) > 0 else int(i) + ind for i in layers]
166
+ if len(layers) == 1:
167
+ if 'groups' not in block.keys() or int(block['groups']) == 1:
168
+ x = outputs[layers[0]]
169
+ outputs[ind] = x
170
+ else:
171
+ groups = int(block['groups'])
172
+ group_id = int(block['group_id'])
173
+ _, b, _, _ = outputs[layers[0]].shape
174
+ x = outputs[layers[0]][:, b // groups * group_id:b // groups * (group_id + 1)]
175
+ outputs[ind] = x
176
+ elif len(layers) == 2:
177
+ x1 = outputs[layers[0]]
178
+ x2 = outputs[layers[1]]
179
+ x = torch.cat((x1, x2), 1)
180
+ outputs[ind] = x
181
+ elif len(layers) == 4:
182
+ x1 = outputs[layers[0]]
183
+ x2 = outputs[layers[1]]
184
+ x3 = outputs[layers[2]]
185
+ x4 = outputs[layers[3]]
186
+ x = torch.cat((x1, x2, x3, x4), 1)
187
+ outputs[ind] = x
188
+ else:
189
+ print("rounte number > 2 ,is {}".format(len(layers)))
190
+
191
+ elif block['type'] == 'shortcut':
192
+ from_layer = int(block['from'])
193
+ activation = block['activation']
194
+ from_layer = from_layer if from_layer > 0 else from_layer + ind
195
+ x1 = outputs[from_layer]
196
+ x2 = outputs[ind - 1]
197
+ x = x1 + x2
198
+ if activation == 'leaky':
199
+ x = F.leaky_relu(x, 0.1, inplace=True)
200
+ elif activation == 'relu':
201
+ x = F.relu(x, inplace=True)
202
+ outputs[ind] = x
203
+ elif block['type'] == 'sam':
204
+ from_layer = int(block['from'])
205
+ from_layer = from_layer if from_layer > 0 else from_layer + ind
206
+ x1 = outputs[from_layer]
207
+ x2 = outputs[ind - 1]
208
+ x = x1 * x2
209
+ outputs[ind] = x
210
+ elif block['type'] == 'region':
211
+ continue
212
+ if self.loss:
213
+ self.loss = self.loss + self.models[ind](x)
214
+ else:
215
+ self.loss = self.models[ind](x)
216
+ outputs[ind] = None
217
+ elif block['type'] == 'yolo':
218
+ # if self.training:
219
+ # pass
220
+ # else:
221
+ # boxes = self.models[ind](x)
222
+ # out_boxes.append(boxes)
223
+ boxes = self.models[ind](x)
224
+ out_boxes.append(boxes)
225
+ elif block['type'] == 'cost':
226
+ continue
227
+ else:
228
+ print('unknown type %s' % (block['type']))
229
+
230
+ if self.training:
231
+ return out_boxes
232
+ else:
233
+ return get_region_boxes(out_boxes)
234
+
235
+ def print_network(self):
236
+ print_cfg(self.blocks)
237
+
238
+ def create_network(self, blocks):
239
+ models = nn.ModuleList()
240
+
241
+ prev_filters = 3
242
+ out_filters = []
243
+ prev_stride = 1
244
+ out_strides = []
245
+ conv_id = 0
246
+ for block in blocks:
247
+ if block['type'] == 'net':
248
+ prev_filters = int(block['channels'])
249
+ continue
250
+ elif block['type'] == 'convolutional':
251
+ conv_id = conv_id + 1
252
+ batch_normalize = int(block['batch_normalize'])
253
+ filters = int(block['filters'])
254
+ kernel_size = int(block['size'])
255
+ stride = int(block['stride'])
256
+ is_pad = int(block['pad'])
257
+ pad = (kernel_size - 1) // 2 if is_pad else 0
258
+ activation = block['activation']
259
+ model = nn.Sequential()
260
+ if batch_normalize:
261
+ model.add_module('conv{0}'.format(conv_id),
262
+ nn.Conv2d(prev_filters, filters, kernel_size, stride, pad, bias=False))
263
+ model.add_module('bn{0}'.format(conv_id), nn.BatchNorm2d(filters))
264
+ # model.add_module('bn{0}'.format(conv_id), BN2d(filters))
265
+ else:
266
+ model.add_module('conv{0}'.format(conv_id),
267
+ nn.Conv2d(prev_filters, filters, kernel_size, stride, pad))
268
+ if activation == 'leaky':
269
+ model.add_module('leaky{0}'.format(conv_id), nn.LeakyReLU(0.1, inplace=True))
270
+ elif activation == 'relu':
271
+ model.add_module('relu{0}'.format(conv_id), nn.ReLU(inplace=True))
272
+ elif activation == 'mish':
273
+ model.add_module('mish{0}'.format(conv_id), Mish())
274
+ elif activation == 'linear':
275
+ model.add_module('linear{0}'.format(conv_id), nn.Identity())
276
+ elif activation == 'logistic':
277
+ model.add_module('sigmoid{0}'.format(conv_id), nn.Sigmoid())
278
+ else:
279
+ print("No convolutional activation named {}".format(activation))
280
+
281
+ prev_filters = filters
282
+ out_filters.append(prev_filters)
283
+ prev_stride = stride * prev_stride
284
+ out_strides.append(prev_stride)
285
+ models.append(model)
286
+ elif block['type'] == 'maxpool':
287
+ pool_size = int(block['size'])
288
+ stride = int(block['stride'])
289
+ if stride == 1 and pool_size % 2:
290
+ # You can use Maxpooldark instead, here is convenient to convert onnx.
291
+ # Example: [maxpool] size=3 stride=1
292
+ model = nn.MaxPool2d(kernel_size=pool_size, stride=stride, padding=pool_size // 2)
293
+ elif stride == pool_size:
294
+ # You can use Maxpooldark instead, here is convenient to convert onnx.
295
+ # Example: [maxpool] size=2 stride=2
296
+ model = nn.MaxPool2d(kernel_size=pool_size, stride=stride, padding=0)
297
+ else:
298
+ model = MaxPoolDark(pool_size, stride)
299
+ out_filters.append(prev_filters)
300
+ prev_stride = stride * prev_stride
301
+ out_strides.append(prev_stride)
302
+ models.append(model)
303
+ elif block['type'] == 'avgpool':
304
+ model = GlobalAvgPool2d()
305
+ out_filters.append(prev_filters)
306
+ models.append(model)
307
+ elif block['type'] == 'softmax':
308
+ model = nn.Softmax()
309
+ out_strides.append(prev_stride)
310
+ out_filters.append(prev_filters)
311
+ models.append(model)
312
+ elif block['type'] == 'cost':
313
+ if block['_type'] == 'sse':
314
+ model = nn.MSELoss(reduction='mean')
315
+ elif block['_type'] == 'L1':
316
+ model = nn.L1Loss(reduction='mean')
317
+ elif block['_type'] == 'smooth':
318
+ model = nn.SmoothL1Loss(reduction='mean')
319
+ out_filters.append(1)
320
+ out_strides.append(prev_stride)
321
+ models.append(model)
322
+ elif block['type'] == 'reorg':
323
+ stride = int(block['stride'])
324
+ prev_filters = stride * stride * prev_filters
325
+ out_filters.append(prev_filters)
326
+ prev_stride = prev_stride * stride
327
+ out_strides.append(prev_stride)
328
+ models.append(Reorg(stride))
329
+ elif block['type'] == 'upsample':
330
+ stride = int(block['stride'])
331
+ out_filters.append(prev_filters)
332
+ prev_stride = prev_stride // stride
333
+ out_strides.append(prev_stride)
334
+
335
+ models.append(Upsample_expand(stride))
336
+ # models.append(Upsample_interpolate(stride))
337
+
338
+ elif block['type'] == 'route':
339
+ layers = block['layers'].split(',')
340
+ ind = len(models)
341
+ layers = [int(i) if int(i) > 0 else int(i) + ind for i in layers]
342
+ if len(layers) == 1:
343
+ if 'groups' not in block.keys() or int(block['groups']) == 1:
344
+ prev_filters = out_filters[layers[0]]
345
+ prev_stride = out_strides[layers[0]]
346
+ else:
347
+ prev_filters = out_filters[layers[0]] // int(block['groups'])
348
+ prev_stride = out_strides[layers[0]] // int(block['groups'])
349
+ elif len(layers) == 2:
350
+ assert (layers[0] == ind - 1 or layers[1] == ind - 1)
351
+ prev_filters = out_filters[layers[0]] + out_filters[layers[1]]
352
+ prev_stride = out_strides[layers[0]]
353
+ elif len(layers) == 4:
354
+ assert (layers[0] == ind - 1)
355
+ prev_filters = out_filters[layers[0]] + out_filters[layers[1]] + out_filters[layers[2]] + \
356
+ out_filters[layers[3]]
357
+ prev_stride = out_strides[layers[0]]
358
+ else:
359
+ print("route error!!!")
360
+
361
+ out_filters.append(prev_filters)
362
+ out_strides.append(prev_stride)
363
+ models.append(EmptyModule())
364
+ elif block['type'] == 'shortcut':
365
+ ind = len(models)
366
+ prev_filters = out_filters[ind - 1]
367
+ out_filters.append(prev_filters)
368
+ prev_stride = out_strides[ind - 1]
369
+ out_strides.append(prev_stride)
370
+ models.append(EmptyModule())
371
+ elif block['type'] == 'sam':
372
+ ind = len(models)
373
+ prev_filters = out_filters[ind - 1]
374
+ out_filters.append(prev_filters)
375
+ prev_stride = out_strides[ind - 1]
376
+ out_strides.append(prev_stride)
377
+ models.append(EmptyModule())
378
+ elif block['type'] == 'connected':
379
+ filters = int(block['output'])
380
+ if block['activation'] == 'linear':
381
+ model = nn.Linear(prev_filters, filters)
382
+ elif block['activation'] == 'leaky':
383
+ model = nn.Sequential(
384
+ nn.Linear(prev_filters, filters),
385
+ nn.LeakyReLU(0.1, inplace=True))
386
+ elif block['activation'] == 'relu':
387
+ model = nn.Sequential(
388
+ nn.Linear(prev_filters, filters),
389
+ nn.ReLU(inplace=True))
390
+ prev_filters = filters
391
+ out_filters.append(prev_filters)
392
+ out_strides.append(prev_stride)
393
+ models.append(model)
394
+ elif block['type'] == 'region':
395
+ loss = RegionLoss()
396
+ anchors = block['anchors'].split(',')
397
+ loss.anchors = [float(i) for i in anchors]
398
+ loss.num_classes = int(block['classes'])
399
+ loss.num_anchors = int(block['num'])
400
+ loss.anchor_step = len(loss.anchors) // loss.num_anchors
401
+ loss.object_scale = float(block['object_scale'])
402
+ loss.noobject_scale = float(block['noobject_scale'])
403
+ loss.class_scale = float(block['class_scale'])
404
+ loss.coord_scale = float(block['coord_scale'])
405
+ out_filters.append(prev_filters)
406
+ out_strides.append(prev_stride)
407
+ models.append(loss)
408
+ elif block['type'] == 'yolo':
409
+ yolo_layer = YoloLayer()
410
+ anchors = block['anchors'].split(',')
411
+ anchor_mask = block['mask'].split(',')
412
+ yolo_layer.anchor_mask = [int(i) for i in anchor_mask]
413
+ yolo_layer.anchors = [float(i) for i in anchors]
414
+ yolo_layer.num_classes = int(block['classes'])
415
+ self.num_classes = yolo_layer.num_classes
416
+ yolo_layer.num_anchors = int(block['num'])
417
+ yolo_layer.anchor_step = len(yolo_layer.anchors) // yolo_layer.num_anchors
418
+ yolo_layer.stride = prev_stride
419
+ yolo_layer.scale_x_y = float(block.get('scale_x_y', 1.0))
420
+ # yolo_layer.object_scale = float(block['object_scale'])
421
+ # yolo_layer.noobject_scale = float(block['noobject_scale'])
422
+ # yolo_layer.class_scale = float(block['class_scale'])
423
+ # yolo_layer.coord_scale = float(block['coord_scale'])
424
+ out_filters.append(prev_filters)
425
+ out_strides.append(prev_stride)
426
+ models.append(yolo_layer)
427
+ else:
428
+ print('unknown type %s' % (block['type']))
429
+
430
+ return models
431
+
432
+ def load_weights(self, weightfile):
433
+ fp = open(weightfile, 'rb')
434
+ header = np.fromfile(fp, count=5, dtype=np.int32)
435
+ self.header = torch.from_numpy(header)
436
+ self.seen = self.header[3]
437
+ buf = np.fromfile(fp, dtype=np.float32)
438
+ fp.close()
439
+
440
+ start = 0
441
+ ind = -2
442
+ for block in self.blocks:
443
+ if start >= buf.size:
444
+ break
445
+ ind = ind + 1
446
+ if block['type'] == 'net':
447
+ continue
448
+ elif block['type'] == 'convolutional':
449
+ model = self.models[ind]
450
+ batch_normalize = int(block['batch_normalize'])
451
+ if batch_normalize:
452
+ start = load_conv_bn(buf, start, model[0], model[1])
453
+ else:
454
+ start = load_conv(buf, start, model[0])
455
+ elif block['type'] == 'connected':
456
+ model = self.models[ind]
457
+ if block['activation'] != 'linear':
458
+ start = load_fc(buf, start, model[0])
459
+ else:
460
+ start = load_fc(buf, start, model)
461
+ elif block['type'] == 'maxpool':
462
+ pass
463
+ elif block['type'] == 'reorg':
464
+ pass
465
+ elif block['type'] == 'upsample':
466
+ pass
467
+ elif block['type'] == 'route':
468
+ pass
469
+ elif block['type'] == 'shortcut':
470
+ pass
471
+ elif block['type'] == 'sam':
472
+ pass
473
+ elif block['type'] == 'region':
474
+ pass
475
+ elif block['type'] == 'yolo':
476
+ pass
477
+ elif block['type'] == 'avgpool':
478
+ pass
479
+ elif block['type'] == 'softmax':
480
+ pass
481
+ elif block['type'] == 'cost':
482
+ pass
483
+ else:
484
+ print('unknown type %s' % (block['type']))
485
+
486
+ # def save_weights(self, outfile, cutoff=0):
487
+ # if cutoff <= 0:
488
+ # cutoff = len(self.blocks) - 1
489
+ #
490
+ # fp = open(outfile, 'wb')
491
+ # self.header[3] = self.seen
492
+ # header = self.header
493
+ # header.numpy().tofile(fp)
494
+ #
495
+ # ind = -1
496
+ # for blockId in range(1, cutoff + 1):
497
+ # ind = ind + 1
498
+ # block = self.blocks[blockId]
499
+ # if block['type'] == 'convolutional':
500
+ # model = self.models[ind]
501
+ # batch_normalize = int(block['batch_normalize'])
502
+ # if batch_normalize:
503
+ # save_conv_bn(fp, model[0], model[1])
504
+ # else:
505
+ # save_conv(fp, model[0])
506
+ # elif block['type'] == 'connected':
507
+ # model = self.models[ind]
508
+ # if block['activation'] != 'linear':
509
+ # save_fc(fc, model)
510
+ # else:
511
+ # save_fc(fc, model[0])
512
+ # elif block['type'] == 'maxpool':
513
+ # pass
514
+ # elif block['type'] == 'reorg':
515
+ # pass
516
+ # elif block['type'] == 'upsample':
517
+ # pass
518
+ # elif block['type'] == 'route':
519
+ # pass
520
+ # elif block['type'] == 'shortcut':
521
+ # pass
522
+ # elif block['type'] == 'sam':
523
+ # pass
524
+ # elif block['type'] == 'region':
525
+ # pass
526
+ # elif block['type'] == 'yolo':
527
+ # pass
528
+ # elif block['type'] == 'avgpool':
529
+ # pass
530
+ # elif block['type'] == 'softmax':
531
+ # pass
532
+ # elif block['type'] == 'cost':
533
+ # pass
534
+ # else:
535
+ # print('unknown type %s' % (block['type']))
536
+ # fp.close()
yolov3/demo.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Demo script for YOLOv3 ONNX model using OpenCV DNN.
3
+
4
+ Output format:
5
+ - boxes: [batch, N, 1, 4] -> [x1, y1, x2, y2] normalized to [0, 1]
6
+ - confs: [batch, N, num_classes]
7
+
8
+ Default input size: 416x416
9
+
10
+ Usage:
11
+ python demo.py --image example_outputs/input.jpg --output result.jpg
12
+ """
13
+
14
+ import argparse
15
+ import cv2
16
+ import numpy as np
17
+
18
+
19
+ COCO_CLASSES = [
20
+ "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck",
21
+ "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench",
22
+ "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra",
23
+ "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
24
+ "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove",
25
+ "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup",
26
+ "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange",
27
+ "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch",
28
+ "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse",
29
+ "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink",
30
+ "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier",
31
+ "toothbrush",
32
+ ]
33
+
34
+
35
+ def postprocess(outputs, conf_threshold, nms_threshold):
36
+ """Parse [boxes, confs] outputs and run NMS."""
37
+ boxes_raw = outputs[0].reshape(-1, 4)
38
+ confs_raw = outputs[1].reshape(boxes_raw.shape[0], -1)
39
+
40
+ class_ids = []
41
+ confidences = []
42
+ boxes_xywh = []
43
+
44
+ for j in range(boxes_raw.shape[0]):
45
+ cls_id = int(np.argmax(confs_raw[j]))
46
+ score = float(confs_raw[j][cls_id])
47
+ if score >= conf_threshold:
48
+ x1, y1, x2, y2 = boxes_raw[j]
49
+ class_ids.append(cls_id)
50
+ confidences.append(score)
51
+ boxes_xywh.append([float(x1), float(y1), float(x2 - x1), float(y2 - y1)])
52
+
53
+ if not boxes_xywh:
54
+ return []
55
+
56
+ indices = cv2.dnn.NMSBoxes(boxes_xywh, confidences, conf_threshold, nms_threshold)
57
+ if len(indices) == 0:
58
+ return []
59
+ indices = np.array(indices).flatten()
60
+
61
+ detections = []
62
+ for i in indices:
63
+ x, y, w, h = boxes_xywh[i]
64
+ detections.append((class_ids[i], confidences[i], [x, y, x + w, y + h]))
65
+ return detections
66
+
67
+
68
+ def draw_detections(image, detections, output_path):
69
+ """Draw bounding boxes and labels on the image."""
70
+ out = image.copy()
71
+ h, w = out.shape[:2]
72
+ for cls_id, score, (x1, y1, x2, y2) in detections:
73
+ px1, py1 = int(x1 * w), int(y1 * h)
74
+ px2, py2 = int(x2 * w), int(y2 * h)
75
+ label = f"{COCO_CLASSES[cls_id]} {score:.2f}"
76
+ cv2.rectangle(out, (px1, py1), (px2, py2), (0, 0, 255), 2)
77
+ (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
78
+ cv2.rectangle(out, (px1, py1 - th - 6), (px1 + tw + 4, py1), (0, 0, 255), -1)
79
+ cv2.putText(out, label, (px1 + 2, py1 - 4), cv2.FONT_HERSHEY_SIMPLEX,
80
+ 0.6, (255, 255, 255), 2, cv2.LINE_AA)
81
+ cv2.imwrite(output_path, out)
82
+ print(f"Saved annotated image: {output_path}")
83
+
84
+
85
+ def main():
86
+ parser = argparse.ArgumentParser(description="YOLOv3 ONNX demo (OpenCV DNN)")
87
+ parser.add_argument("--model", default="yolov3.onnx", help="Path to ONNX model")
88
+ parser.add_argument("--image", default="example_outputs/input.jpg", help="Path to input image")
89
+ parser.add_argument("--output", default="output.jpg", help="Path for annotated output")
90
+ parser.add_argument("--input-size", type=int, default=416,
91
+ help="Model input size (W=H). Default: 416")
92
+ parser.add_argument("--conf", type=float, default=0.4, help="Confidence threshold")
93
+ parser.add_argument("--nms", type=float, default=0.5, help="NMS IoU threshold")
94
+ args = parser.parse_args()
95
+
96
+ print(f"Using input size: {args.input_size}x{args.input_size}")
97
+
98
+ img = cv2.imread(args.image)
99
+ if img is None:
100
+ raise FileNotFoundError(f"Cannot read image: {args.image}")
101
+ print(f"Input image: {args.image} ({img.shape[1]}x{img.shape[0]})")
102
+
103
+ blob = cv2.dnn.blobFromImage(img, 1.0 / 255.0,
104
+ (args.input_size, args.input_size),
105
+ swapRB=True, crop=False)
106
+ print(f"Blob shape: {blob.shape}")
107
+
108
+ net = cv2.dnn.readNetFromONNX(args.model)
109
+ net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
110
+ net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
111
+
112
+ net.setInput(blob)
113
+ out_names = net.getUnconnectedOutLayersNames()
114
+ print(f"Output layer names: {out_names}")
115
+ outputs = net.forward(out_names)
116
+ for name, o in zip(out_names, outputs):
117
+ print(f"Output '{name}' shape: {o.shape}")
118
+
119
+ detections = postprocess(outputs, args.conf, args.nms)
120
+ print(f"\nDetections (conf >= {args.conf}, after NMS): {len(detections)}")
121
+ for cls_id, score, (x1, y1, x2, y2) in detections:
122
+ print(f" {COCO_CLASSES[cls_id]:15s} score={score:.4f} "
123
+ f"bbox=[{x1:.4f}, {y1:.4f}, {x2:.4f}, {y2:.4f}]")
124
+
125
+ draw_detections(img, detections, args.output)
126
+
127
+
128
+ if __name__ == "__main__":
129
+ main()
yolov3/example_outputs/input.jpg ADDED

Git LFS Details

  • SHA256: 5a9522051c3cec2bbd2f6323fccba32e8fbf3ddcc2b3e2fd46b04c720bc6f866
  • Pointer size: 131 Bytes
  • Size of remote file: 164 kB
yolov3/example_outputs/yolov3_output.jpg ADDED

Git LFS Details

  • SHA256: d7eef868800e786ca7f6cfbeca2bef3be37fda343ca661658b1d97e8fbc6f246
  • Pointer size: 131 Bytes
  • Size of remote file: 188 kB
yolov3/yolov3.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8f97018524d0752062051732c97da311712df2fca2bbb21d931142017d1b8d53
3
+ size 247918553