Deploy 3a26184
Browse files- server/train_plate_chars.py +55 -0
server/train_plate_chars.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Trains the Saudi plate character detector (YOLOv8) and writes
|
| 3 |
+
plate_chars.pt. Used by .github/workflows/train-plate-chars.yml (CPU runner,
|
| 4 |
+
so a time budget keeps it inside GitHub's 6-hour job limit) and runnable by
|
| 5 |
+
hand: python server/train_plate_chars.py <folder with data.yaml> [hours]
|
| 6 |
+
"""
|
| 7 |
+
import glob
|
| 8 |
+
import os
|
| 9 |
+
import shutil
|
| 10 |
+
import sys
|
| 11 |
+
|
| 12 |
+
import yaml
|
| 13 |
+
from ultralytics import YOLO
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def fix_yaml(root):
|
| 17 |
+
yp = glob.glob(os.path.join(root, "**", "data.yaml"), recursive=True)[0]
|
| 18 |
+
base = os.path.dirname(os.path.abspath(yp))
|
| 19 |
+
d = yaml.safe_load(open(yp))
|
| 20 |
+
d["path"] = base
|
| 21 |
+
for k, cands in (("train", ["train"]), ("val", ["valid", "val"]), ("test", ["test"])):
|
| 22 |
+
for c in cands:
|
| 23 |
+
p = os.path.join(base, c, "images")
|
| 24 |
+
if os.path.isdir(p):
|
| 25 |
+
d[k] = p
|
| 26 |
+
break
|
| 27 |
+
else:
|
| 28 |
+
d.pop(k, None)
|
| 29 |
+
d.setdefault("val", d["train"])
|
| 30 |
+
yaml.safe_dump(d, open(yp, "w"), allow_unicode=True)
|
| 31 |
+
print(f"classes ({d['nc']}): {d['names']}", flush=True)
|
| 32 |
+
return yp
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def main():
|
| 36 |
+
root = sys.argv[1] if len(sys.argv) > 1 else "data"
|
| 37 |
+
hours = float(sys.argv[2]) if len(sys.argv) > 2 else 4.5
|
| 38 |
+
yp = fix_yaml(root)
|
| 39 |
+
import torch
|
| 40 |
+
gpu = torch.cuda.is_available()
|
| 41 |
+
model = YOLO("yolov8s.pt" if gpu else "yolov8n.pt")
|
| 42 |
+
model.train(data=yp, imgsz=640 if gpu else 512, epochs=80 if gpu else 40, patience=15,
|
| 43 |
+
batch=16, workers=4, time=hours, # stop (and keep best) within the budget
|
| 44 |
+
fliplr=0.0, # never mirror characters
|
| 45 |
+
degrees=8, perspective=0.0005, hsv_v=0.5, name="plate_chars", exist_ok=True)
|
| 46 |
+
best = sorted(glob.glob("runs/detect/plate_chars*/weights/best.pt"), key=os.path.getmtime)[-1]
|
| 47 |
+
r = YOLO(best).val(data=yp, imgsz=640 if gpu else 512)
|
| 48 |
+
print(f"mAP50 = {r.box.map50:.3f}", flush=True)
|
| 49 |
+
shutil.copy(best, "plate_chars.pt")
|
| 50 |
+
with open("train_summary.txt", "w") as f:
|
| 51 |
+
f.write(f"mAP50={r.box.map50:.3f}\nclasses={YOLO(best).names}\n")
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
main()
|