LosReturn commited on
Commit
67c84d4
·
verified ·
1 Parent(s): 4dc20af

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ onnx/detr.axmodel filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, AXERA
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.md CHANGED
@@ -1,3 +1,28 @@
1
- ---
2
- license: bsd-3-clause
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # RF-DETR.axera
2
+ RF-DETR DEMO on AXERA NPU
3
+
4
+ ### 1. 工程下载
5
+ ```
6
+ gh repo clone AXERA-TECH/Deformable-Detr.axera
7
+ ```
8
+
9
+ ### 2. 模型导出
10
+ ```
11
+ pip3 install -r tools/requirements.txt
12
+ python3 export_onnx.py --variant small --out-dir onnx
13
+ ```
14
+ ### 3. Pulsar2
15
+ ```
16
+ onnxsim rf-detr-small.onnx sim.onnx
17
+ pulsar2 build --config onnx/config.json
18
+ ```
19
+ ### 4. 板端运行
20
+ ```
21
+ python src/infer.py --model detr.axmodel --img ./assets/test.jpg --thresh 0.5
22
+ ```
23
+ ### 5. 结果展示
24
+ #### AXMODEL
25
+ ![axmodel_result](./asserts/ax_result.jpg)
26
+
27
+ #### ONNX
28
+ ![onnx_result](./asserts/onnx_result.jpg)
asserts/ax_result.jpg ADDED
asserts/imagenet-calib.tar ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e99da619eedfd33ca8e07d2629e61e92bc5da9e81823ae84a8e1c28a6fe56781
3
+ size 16281600
asserts/onnx_result.jpg ADDED
asserts/test.jpg ADDED
onnx/.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.onnx filter=lfs diff=lfs merge=lfs -text
onnx/config.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "input": "./onnx/sim.onnx",
3
+ "output_dir": "./out",
4
+ "output_name": "detr.axmodel",
5
+ "model_type": "ONNX",
6
+ "target_hardware": "AX650",
7
+ "npu_mode": "NPU3",
8
+ "quant": {
9
+ "input_configs": [
10
+ {
11
+ "tensor_name": "DEFAULT",
12
+ "calibration_dataset": "./asserts/imagenet-calib.tar",
13
+ "calibration_size": 128,
14
+ "calibration_mean": [123.675, 116.28, 103.53],
15
+ "calibration_std": [58.395, 57.12, 57.375],
16
+ "calibration_format": "Image"
17
+ }
18
+ ],
19
+ "calibration_method": "MinMax",
20
+ "precision_analysis": true,
21
+ "precision_analysis_method": "EndToEnd",
22
+ "precision_analysis_mode": "NPUBackend",
23
+ "conv_bias_data_type": "FP32",
24
+ "layer_configs": [
25
+ {
26
+ "start_tensor_names": ["DEFAULT"],
27
+ "end_tensor_names": ["DEFAULT"],
28
+ "data_type": "U16"
29
+ }
30
+ ]
31
+ },
32
+ "input_processors": [
33
+ {
34
+ "tensor_name": "DEFAULT",
35
+ "tensor_format": "RGB",
36
+ "tensor_layout": "NCHW",
37
+ "src_dtype": "U8",
38
+ "src_layout": "NHWC",
39
+ "mean": [123.675, 116.28, 103.53],
40
+ "std": [58.395, 57.12, 57.375]
41
+ }
42
+ ],
43
+ "compiler": {
44
+ "enable_slice_mode": true
45
+ }
46
+ }
onnx/detr.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a60c1d0711edddc754a7ea250993839e3e101cd4b8daf71535bedc5560c1acc1
3
+ size 33966166
onnx/rf-detr-small.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:793b4d89e3aff9ee4a95c43af54cce78162a8d3aa2c75333ea188f12e323c1e2
3
+ size 121913199
onnx/sim.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ac48eca7f3dddd9c1d436999c65a1ffbbcb0e63758dbd89cdd95fc71133d0c06
3
+ size 118451286
src/infer.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import colorsys
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+ from PIL import Image, ImageDraw, ImageFont
9
+
10
+ try:
11
+ import axengine as ort
12
+
13
+ BACKEND = "axengine"
14
+ print("Running on AXera NPU (axengine)...")
15
+ except ImportError:
16
+ import onnxruntime as ort
17
+
18
+ BACKEND = "onnxruntime"
19
+ print("Running on CPU/GPU (onnxruntime)...")
20
+
21
+
22
+ MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32)
23
+ STD = np.array([0.229, 0.224, 0.225], dtype=np.float32)
24
+
25
+ CLASSES = [
26
+ "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic_light",
27
+ "fire_hydrant", "stop_sign", "parking_meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow",
28
+ "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee",
29
+ "skis", "snowboard", "sports_ball", "kite", "baseball_bat", "baseball_glove", "skateboard", "surfboard",
30
+ "tennis_racket", "bottle", "wine_glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
31
+ "sandwich", "orange", "broccoli", "carrot", "hot_dog", "pizza", "donut", "cake", "chair", "couch",
32
+ "potted_plant", "bed", "dining_table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard",
33
+ "cell_phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase",
34
+ "scissors", "teddy_bear", "hair_drier", "toothbrush",
35
+ ]
36
+ COCO_IDS = [
37
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21,
38
+ 22, 23, 24, 25, 27, 28, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
39
+ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
40
+ 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90,
41
+ ]
42
+ CLASS_NAME_BY_ID = {cid: name for cid, name in zip(COCO_IDS, CLASSES)}
43
+
44
+
45
+ def sigmoid(x: np.ndarray) -> np.ndarray:
46
+ return 1.0 / (1.0 + np.exp(-np.clip(x, -88.0, 88.0)))
47
+
48
+
49
+ def get_numpy_dtype(input_meta: object) -> np.dtype:
50
+ if hasattr(input_meta, "dtype"):
51
+ return np.dtype(input_meta.dtype)
52
+
53
+ ort_type = getattr(input_meta, "type", "")
54
+ mapping = {
55
+ "tensor(float)": np.float32,
56
+ "tensor(float16)": np.float16,
57
+ "tensor(uint8)": np.uint8,
58
+ "tensor(int8)": np.int8,
59
+ "tensor(int32)": np.int32,
60
+ "tensor(int64)": np.int64,
61
+ }
62
+ if ort_type not in mapping:
63
+ raise ValueError(f"Unsupported input type: {ort_type}")
64
+ return np.dtype(mapping[ort_type])
65
+
66
+
67
+ def preprocess(
68
+ image_path: str,
69
+ input_h: int,
70
+ input_w: int,
71
+ layout: str,
72
+ dtype: np.dtype,
73
+ letterbox: bool,
74
+ ) -> tuple[np.ndarray, Image.Image, dict[str, float]]:
75
+ raw_image = Image.open(image_path).convert("RGB")
76
+ orig_w, orig_h = raw_image.size
77
+ if letterbox:
78
+ scale_x = min(input_w / orig_w, input_h / orig_h)
79
+ scale_y = scale_x
80
+ resized_w = max(1, int(round(orig_w * scale_x)))
81
+ resized_h = max(1, int(round(orig_h * scale_y)))
82
+ pad_x = (input_w - resized_w) // 2
83
+ pad_y = (input_h - resized_h) // 2
84
+
85
+ resized = raw_image.resize((resized_w, resized_h), Image.Resampling.BILINEAR)
86
+ canvas = Image.new("RGB", (input_w, input_h), (0, 0, 0))
87
+ canvas.paste(resized, (pad_x, pad_y))
88
+ image = np.array(canvas)
89
+ else:
90
+ resized = raw_image.resize((input_w, input_h), Image.Resampling.BILINEAR)
91
+ image = np.array(resized)
92
+ pad_x = 0.0
93
+ pad_y = 0.0
94
+ scale_x = input_w / orig_w
95
+ scale_y = input_h / orig_h
96
+
97
+ if BACKEND == "axengine":
98
+ if layout == "NHWC":
99
+ tensor = image[None, ...].astype(dtype, copy=False)
100
+ else:
101
+ tensor = image.transpose(2, 0, 1)[None, ...].astype(dtype, copy=False)
102
+ else:
103
+ if layout == "NHWC":
104
+ if dtype != np.uint8:
105
+ raise ValueError(f"NHWC input only supports uint8 in this simple script, got {dtype}")
106
+ tensor = image[None, ...].astype(np.uint8)
107
+ else:
108
+ tensor = image.astype(np.float32) / 255.0
109
+ tensor = (tensor - MEAN) / STD
110
+ tensor = tensor.transpose(2, 0, 1)[None, ...].astype(dtype)
111
+
112
+ meta = {
113
+ "orig_w": float(orig_w),
114
+ "orig_h": float(orig_h),
115
+ "scale_x": float(scale_x),
116
+ "scale_y": float(scale_y),
117
+ "pad_x": float(pad_x),
118
+ "pad_y": float(pad_y),
119
+ "input_w": float(input_w),
120
+ "input_h": float(input_h),
121
+ }
122
+ return tensor, raw_image, meta
123
+
124
+
125
+ def decode(outputs: list[np.ndarray], meta: dict[str, float], thresh: float) -> list[tuple[np.ndarray, float, int, int]]:
126
+ dets = outputs[0][0]
127
+ labels = outputs[1][0]
128
+
129
+ if dets.shape[-1] != 4:
130
+ dets, labels = labels, dets
131
+
132
+ boxes = dets
133
+ logits = labels[:, :-1]
134
+ probs = sigmoid(logits)
135
+ input_w = meta["input_w"]
136
+ input_h = meta["input_h"]
137
+ orig_w = meta["orig_w"]
138
+ orig_h = meta["orig_h"]
139
+ scale_x = meta["scale_x"]
140
+ scale_y = meta["scale_y"]
141
+ pad_x = meta["pad_x"]
142
+ pad_y = meta["pad_y"]
143
+
144
+ flat = probs.reshape(-1)
145
+ topk = min(300, flat.size)
146
+ top_idx = np.argpartition(-flat, topk - 1)[:topk]
147
+ top_idx = top_idx[np.argsort(-flat[top_idx])]
148
+
149
+ num_classes = probs.shape[1]
150
+ results = []
151
+ for rank, idx in enumerate(top_idx.tolist()):
152
+ query_id = idx // num_classes
153
+ label_id = idx % num_classes
154
+ score = float(flat[idx])
155
+ if score < thresh:
156
+ continue
157
+
158
+ cx, cy, bw, bh = boxes[query_id]
159
+ x1 = ((cx - bw / 2.0) * input_w - pad_x) / scale_x
160
+ y1 = ((cy - bh / 2.0) * input_h - pad_y) / scale_y
161
+ x2 = ((cx + bw / 2.0) * input_w - pad_x) / scale_x
162
+ y2 = ((cy + bh / 2.0) * input_h - pad_y) / scale_y
163
+
164
+ x1 = max(0.0, min(orig_w, x1))
165
+ y1 = max(0.0, min(orig_h, y1))
166
+ x2 = max(0.0, min(orig_w, x2))
167
+ y2 = max(0.0, min(orig_h, y2))
168
+
169
+ results.append((np.array([x1, y1, x2, y2]), score, label_id, query_id))
170
+ return results
171
+
172
+
173
+ def color_for_label(label_id: int) -> tuple[int, int, int]:
174
+ hue = (label_id * 0.61803398875) % 1.0
175
+ r, g, b = colorsys.hsv_to_rgb(hue, 0.75, 1.0)
176
+ return int(r * 255), int(g * 255), int(b * 255)
177
+
178
+
179
+ def draw(raw_img: Image.Image, results: list[tuple[np.ndarray, float, int, int]], output_path: str) -> None:
180
+ draw_obj = ImageDraw.Draw(raw_img)
181
+ try:
182
+ font = ImageFont.truetype("DejaVuSans.ttf", 18)
183
+ except OSError:
184
+ font = ImageFont.load_default()
185
+
186
+ for box, score, label_id, query_id in results:
187
+ x1, y1, x2, y2 = box.tolist()
188
+ name = CLASS_NAME_BY_ID.get(label_id, f"obj_{label_id}")
189
+ text = f"{name} {score:.2f}"
190
+ color = color_for_label(label_id)
191
+
192
+ draw_obj.rectangle([x1, y1, x2, y2], outline=color, width=3)
193
+ draw_obj.rectangle([x1, max(0, y1 - 22), x1 + 140, y1], fill=color)
194
+ draw_obj.text((x1 + 2, max(0, y1 - 20)), text, fill="black", font=font)
195
+ print(f"query={query_id:3d} class={name:<15} score={score:.4f} box=({x1:.1f}, {y1:.1f}, {x2:.1f}, {y2:.1f})")
196
+
197
+ Path(output_path).parent.mkdir(parents=True, exist_ok=True)
198
+ raw_img.save(output_path)
199
+
200
+
201
+ def main() -> None:
202
+ parser = argparse.ArgumentParser()
203
+ parser.add_argument("--model", type=str, required=True)
204
+ parser.add_argument("--img", type=str, required=True)
205
+ parser.add_argument("--output", type=str, default="result.jpg")
206
+ parser.add_argument("--thresh", type=float, default=0.3)
207
+ parser.add_argument("--letterbox", action="store_true", help="use letterbox resize instead of direct resize")
208
+ args = parser.parse_args()
209
+
210
+ session = ort.InferenceSession(args.model)
211
+ input_meta = session.get_inputs()[0]
212
+ shape = [int(x) for x in input_meta.shape]
213
+ dtype = get_numpy_dtype(input_meta)
214
+
215
+ if shape[1] in (1, 3, 4):
216
+ layout = "NCHW"
217
+ input_h, input_w = shape[2], shape[3]
218
+ else:
219
+ layout = "NHWC"
220
+ input_h, input_w = shape[1], shape[2]
221
+
222
+ print(f"input_name={input_meta.name} shape={shape} dtype={dtype} layout={layout}")
223
+
224
+ img_tensor, raw_img, meta = preprocess(args.img, input_h, input_w, layout, dtype, args.letterbox)
225
+ outputs = session.run(None, {input_meta.name: img_tensor})
226
+
227
+ results = decode(outputs, meta, args.thresh)
228
+ print(f"Detected {len(results)} objects.")
229
+
230
+ draw(raw_img, results, args.output)
231
+ print(f"Result saved to {args.output}")
232
+
233
+
234
+ if __name__ == "__main__":
235
+ main()
tools/export_onnx.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ # Reference: https://github.com/infracv/rf-detr-cpp/blob/develop/trt-files/scripts/export_onnx.py
4
+ """Export an RF-DETR PyTorch checkpoint to ONNX, plus a meta-sidecar JSON.
5
+
6
+ The sidecar is consumed by `rfdetr/core/engine_meta.hpp`. It captures the
7
+ variant identity, input H/W, query count, normalization stats, and color
8
+ order — i.e. everything the C++ runtime needs that is NOT recoverable from the
9
+ engine's tensor shapes alone.
10
+
11
+ Example:
12
+ python export_onnx.py --variant small --out-dir onnx
13
+ python export_onnx.py --variant seg-large --weights /tmp/seg-large.pth
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import shutil
22
+ import sys
23
+ from pathlib import Path
24
+ from typing import Any, Dict
25
+
26
+ VARIANT_TABLE: Dict[str, Dict[str, Any]] = {
27
+ "nano": {"resolution": 384, "num_queries": 300, "patch": 16, "has_masks": False,
28
+ "ctors": ("RFDETRNano",)},
29
+ "small": {"resolution": 512, "num_queries": 300, "patch": 16, "has_masks": False,
30
+ "ctors": ("RFDETRSmall",)},
31
+ "medium": {"resolution": 576, "num_queries": 300, "patch": 16, "has_masks": False,
32
+ "ctors": ("RFDETRMedium",)},
33
+ "base": {"resolution": 560, "num_queries": 300, "patch": 14, "has_masks": False,
34
+ "ctors": ("RFDETRBase",)},
35
+ "large": {"resolution": 704, "num_queries": 300, "patch": 16, "has_masks": False,
36
+ "ctors": ("RFDETRLarge",)},
37
+ "seg-nano": {"resolution": 312, "num_queries": 100, "patch": 12, "has_masks": True,
38
+ "ctors": ("RFDETRSegNano",)},
39
+ "seg-small": {"resolution": 384, "num_queries": 100, "patch": 12, "has_masks": True,
40
+ "ctors": ("RFDETRSegSmall",)},
41
+ "seg-medium": {"resolution": 432, "num_queries": 200, "patch": 12, "has_masks": True,
42
+ "ctors": ("RFDETRSegMedium",)},
43
+ "seg-large": {"resolution": 504, "num_queries": 200, "patch": 12, "has_masks": True,
44
+ "ctors": ("RFDETRSegLarge",)},
45
+ "seg-xlarge": {"resolution": 624, "num_queries": 300, "patch": 12, "has_masks": True,
46
+ "ctors": ("RFDETRSegXLarge",)},
47
+ "seg-2xlarge": {"resolution": 768, "num_queries": 300, "patch": 12, "has_masks": True,
48
+ "ctors": ("RFDETRSeg2XLarge",)},
49
+ "seg-preview": {"resolution": 432, "num_queries": 200, "patch": 12, "has_masks": True,
50
+ "ctors": ("RFDETRSegPreview",)},
51
+ }
52
+
53
+
54
+ def resolve_ctor(rfdetr_module, ctor_names):
55
+ for name in ctor_names:
56
+ if hasattr(rfdetr_module, name):
57
+ return getattr(rfdetr_module, name)
58
+ raise AttributeError(
59
+ f"none of {ctor_names} are exposed by `rfdetr` — upstream may have renamed the variant. "
60
+ f"Edit VARIANT_TABLE in this script to match."
61
+ )
62
+
63
+
64
+ def main():
65
+ ap = argparse.ArgumentParser(description=__doc__,
66
+ formatter_class=argparse.RawDescriptionHelpFormatter)
67
+ ap.add_argument("--variant", required=True, choices=sorted(VARIANT_TABLE.keys()),
68
+ help="RF-DETR variant to export")
69
+ ap.add_argument("--weights", default=None,
70
+ help="path to .pth checkpoint (default: upstream pretrained)")
71
+ ap.add_argument("--out-dir", default="onnx",
72
+ help="output directory for .onnx and .json sidecar")
73
+ ap.add_argument("--name", default=None,
74
+ help="output basename (default: rf-detr-<variant>)")
75
+ ap.add_argument("--opset", type=int, default=17)
76
+ ap.add_argument("--dynamic-batch", action="store_true",
77
+ help="export with batch dimension as a dynamic axis")
78
+ args = ap.parse_args()
79
+
80
+ spec = VARIANT_TABLE[args.variant]
81
+
82
+ try:
83
+ import rfdetr # type: ignore
84
+ except ImportError:
85
+ sys.exit("rfdetr Python package not installed (pip install rfdetr)")
86
+
87
+ ctor = resolve_ctor(rfdetr, spec["ctors"])
88
+ model_kwargs = {"pretrain_weights": args.weights} if args.weights else {}
89
+ model = ctor(**model_kwargs)
90
+
91
+ out_dir = Path(args.out_dir)
92
+ out_dir.mkdir(parents=True, exist_ok=True)
93
+ basename = args.name or f"rf-detr-{args.variant}"
94
+
95
+ # rfdetr's `.export(format="onnx", output_dir=...)` typically writes
96
+ # `inference_model.onnx` (and possibly a sim'd variant) into output_dir.
97
+ # We invoke it, then locate the produced .onnx and rename to our convention.
98
+ print(f"[export_onnx] exporting variant={args.variant} to {out_dir}/")
99
+ model.export(
100
+ format="onnx",
101
+ output_dir=str(out_dir),
102
+ opset_version=args.opset,
103
+ dynamic_batch=args.dynamic_batch,
104
+ )
105
+
106
+ # Find the most recently produced .onnx in out_dir; rename to <basename>.onnx.
107
+ candidates = sorted(out_dir.glob("*.onnx"), key=lambda p: p.stat().st_mtime, reverse=True)
108
+ if not candidates:
109
+ sys.exit(f"export reported success but no .onnx file appeared in {out_dir}")
110
+ src = candidates[0]
111
+ dst = out_dir / f"{basename}.onnx"
112
+ if src.resolve() != dst.resolve():
113
+ shutil.move(str(src), str(dst))
114
+ print(f"[export_onnx] renamed {src.name} -> {dst.name}")
115
+
116
+ print(f"[export_onnx] wrote {dst}")
117
+
118
+
119
+
120
+ if __name__ == "__main__":
121
+ main()
tools/requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ rfdetr>=1.7.0
2
+ onnx>=1.15
3
+ onnxsim>=0.4.36