Chris Leo commited on
scorevision: push artifact
Browse files- training/export_onnx.py +65 -0
training/export_onnx.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Export a trained YOLOv11 .pt to ONNX with NMS baked in -> output shape [1, max_det, 6].
|
| 3 |
+
|
| 4 |
+
The default chute miner.py decode path expects [1, N, 6] (NMS-baked). The king's own
|
| 5 |
+
weights.onnx is raw-YOLO [1, 10, 8400] without NMS — our exported model does NOT match
|
| 6 |
+
that, but our miner.py handles both formats. NMS-baked is preferred because:
|
| 7 |
+
- chute startup is faster (less graph work),
|
| 8 |
+
- the decode path is simpler (no fallback to raw YOLO numpy NMS),
|
| 9 |
+
- p95 latency is more stable (no Python-NMS spike on busy frames).
|
| 10 |
+
|
| 11 |
+
Usage:
|
| 12 |
+
python export_onnx.py --weights runs/detect/crime_b/weights/best.pt --imgsz 1280 \
|
| 13 |
+
--out ../weights.onnx
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import argparse
|
| 19 |
+
import shutil
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
from ultralytics import YOLO
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def main() -> int:
|
| 26 |
+
ap = argparse.ArgumentParser()
|
| 27 |
+
ap.add_argument("--weights", required=True)
|
| 28 |
+
ap.add_argument("--imgsz", type=int, default=1280)
|
| 29 |
+
ap.add_argument("--out", default=None,
|
| 30 |
+
help="destination path for weights.onnx (defaults next to source .pt)")
|
| 31 |
+
ap.add_argument("--max-det", type=int, default=300)
|
| 32 |
+
ap.add_argument("--opset", type=int, default=17)
|
| 33 |
+
ap.add_argument("--half", action="store_true",
|
| 34 |
+
help="export FP16 (smaller, faster on CUDA, marginal accuracy delta)")
|
| 35 |
+
args = ap.parse_args()
|
| 36 |
+
|
| 37 |
+
weights = Path(args.weights).resolve()
|
| 38 |
+
model = YOLO(str(weights))
|
| 39 |
+
|
| 40 |
+
onnx_path = model.export(
|
| 41 |
+
format="onnx",
|
| 42 |
+
imgsz=args.imgsz,
|
| 43 |
+
nms=True,
|
| 44 |
+
max_det=args.max_det,
|
| 45 |
+
opset=args.opset,
|
| 46 |
+
dynamic=False,
|
| 47 |
+
half=args.half,
|
| 48 |
+
simplify=True,
|
| 49 |
+
agnostic_nms=False, # class-aware NMS (must match miner.py expectations)
|
| 50 |
+
device="cpu",
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
src = Path(onnx_path)
|
| 54 |
+
if args.out:
|
| 55 |
+
dst = Path(args.out).resolve()
|
| 56 |
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
| 57 |
+
shutil.move(str(src), str(dst))
|
| 58 |
+
print(f"[export] {src} -> {dst}")
|
| 59 |
+
else:
|
| 60 |
+
print(f"[export] {src}")
|
| 61 |
+
return 0
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
if __name__ == "__main__":
|
| 65 |
+
raise SystemExit(main())
|