170 lines
5.0 KiB
Python
170 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
TOKEN_FR = {
|
|
"healthy": "saine",
|
|
"leaf": "feuille",
|
|
"plant": "plante",
|
|
"disease": "maladie",
|
|
"blight": "mildiou",
|
|
"rust": "rouille",
|
|
"spot": "tache",
|
|
"scab": "tavelure",
|
|
"mold": "moisissure",
|
|
"mildew": "oidium",
|
|
"powdery": "poudreux",
|
|
"bacterial": "bacterien",
|
|
"viral": "viral",
|
|
"virus": "virus",
|
|
"yellow": "jaune",
|
|
"mosaic": "mosaique",
|
|
"late": "tardif",
|
|
"early": "precoce",
|
|
"septoria": "septoriose",
|
|
"target": "cible",
|
|
"black": "noire",
|
|
"rot": "pourriture",
|
|
}
|
|
|
|
|
|
def translate_label_to_fr(label: str) -> str:
|
|
tokens = (
|
|
label.replace("-", " ")
|
|
.replace("_", " ")
|
|
.replace(",", " ")
|
|
.replace("(", " ")
|
|
.replace(")", " ")
|
|
.split()
|
|
)
|
|
if not tokens:
|
|
return label
|
|
translated = [TOKEN_FR.get(tok.lower(), tok.lower()) for tok in tokens]
|
|
return " ".join(translated)
|
|
|
|
|
|
def resolve_model(model_arg: str) -> tuple[str, str]:
|
|
model_path = Path(model_arg)
|
|
if model_path.exists():
|
|
return str(model_path), model_arg
|
|
|
|
lower = model_arg.lower()
|
|
if "/" in model_arg and not lower.endswith((".pt", ".onnx", ".engine", ".torchscript")):
|
|
try:
|
|
from huggingface_hub import hf_hub_download
|
|
except Exception as exc:
|
|
raise SystemExit(
|
|
"Le modèle semble être un repo Hugging Face. Installe:\n"
|
|
"pip install huggingface_hub\n"
|
|
f"Détail: {exc}"
|
|
)
|
|
|
|
try:
|
|
downloaded = hf_hub_download(repo_id=model_arg, filename="best.pt")
|
|
return downloaded, model_arg
|
|
except Exception as exc:
|
|
raise SystemExit(
|
|
f"Impossible de télécharger best.pt depuis le repo Hugging Face '{model_arg}'.\n"
|
|
f"Détail: {exc}"
|
|
)
|
|
|
|
return model_arg, model_arg
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(
|
|
description="Test YOLO feuille/plante avec sortie JSON et image annotée."
|
|
)
|
|
parser.add_argument("--image", required=True, help="Chemin image à analyser")
|
|
parser.add_argument(
|
|
"--model",
|
|
default="foduucom/plant-leaf-detection-and-classification",
|
|
help="Chemin vers .pt/.onnx ou repo Hugging Face (best.pt)",
|
|
)
|
|
parser.add_argument("--conf", type=float, default=0.25, help="Seuil confiance")
|
|
parser.add_argument("--iou", type=float, default=0.45, help="Seuil IoU NMS")
|
|
parser.add_argument("--max-det", type=int, default=1000, help="Nombre max de détections")
|
|
parser.add_argument(
|
|
"--json-out",
|
|
default="test_yolo/output/detections.json",
|
|
help="Fichier JSON de sortie",
|
|
)
|
|
parser.add_argument(
|
|
"--image-out",
|
|
default="test_yolo/output/annotated.jpg",
|
|
help="Image annotée de sortie",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
image_path = Path(args.image)
|
|
if not image_path.exists():
|
|
raise SystemExit(f"Image introuvable: {image_path}")
|
|
|
|
try:
|
|
import cv2
|
|
from ultralytics import YOLO
|
|
except Exception as exc:
|
|
raise SystemExit(
|
|
"Dépendances manquantes. Installe dans un venv:\n"
|
|
"pip install ultralytics opencv-python matplotlib huggingface_hub\n"
|
|
f"Détail: {exc}"
|
|
)
|
|
|
|
model_source, model_display = resolve_model(args.model)
|
|
model = YOLO(model_source)
|
|
model.overrides["conf"] = args.conf
|
|
model.overrides["iou"] = args.iou
|
|
model.overrides["max_det"] = args.max_det
|
|
|
|
results = model.predict(str(image_path))
|
|
result = results[0]
|
|
|
|
boxes = result.boxes
|
|
names = getattr(result, "names", {})
|
|
detections: list[dict] = []
|
|
for i, box in enumerate(boxes):
|
|
cls_id = int(box.cls.item())
|
|
conf = float(box.conf.item())
|
|
xyxy = [float(v) for v in box.xyxy[0].tolist()]
|
|
detections.append(
|
|
{
|
|
"index": i,
|
|
"class_id": cls_id,
|
|
"class_name": names.get(cls_id, str(cls_id)),
|
|
"class_name_fr": translate_label_to_fr(names.get(cls_id, str(cls_id))),
|
|
"confidence": conf,
|
|
"bbox_xyxy": xyxy,
|
|
}
|
|
)
|
|
|
|
payload = {
|
|
"image": str(image_path),
|
|
"model": model_display,
|
|
"model_source": model_source,
|
|
"params": {"conf": args.conf, "iou": args.iou, "max_det": args.max_det},
|
|
"count": len(detections),
|
|
"detections": detections,
|
|
}
|
|
|
|
json_out = Path(args.json_out)
|
|
json_out.parent.mkdir(parents=True, exist_ok=True)
|
|
json_out.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
annotated = result.plot()
|
|
image_out = Path(args.image_out)
|
|
image_out.parent.mkdir(parents=True, exist_ok=True)
|
|
cv2.imwrite(str(image_out), annotated)
|
|
|
|
print(f"Détections: {len(detections)}")
|
|
print(f"JSON: {json_out}")
|
|
print(f"Image annotée: {image_out}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|