""" evaluate.py Egitilmis modeli test seti uzerinde degerlendir + hata analizi yap. Ornek: python evaluate.py \ --weights runs/arac-hasar/yolo26m-seg_ep150/weights/best.pt \ --data cardd.yaml --split test --save_failures Cikti: runs/eval_/ results.json # Tum metrikler per_class.csv # Sinif bazinda detay confusion_matrix.png failures/ # Yanlis tahminlerin gorsellesmesi (ilk N) """ import argparse import json from datetime import datetime from pathlib import Path import cv2 import numpy as np from tqdm import tqdm from ultralytics import YOLO CARDD_CLASSES = ["dent", "scratch", "crack", "glass_shatter", "lamp_broken", "tire_flat"] def visualize_prediction(img_path, pred_result, gt_path, save_path): """Bir goruntude tahmin + GT'yi yan yana cizer.""" img = cv2.imread(str(img_path)) if img is None: return h, w = img.shape[:2] # GT okuma gt_img = img.copy() if gt_path and gt_path.exists(): with open(gt_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) < 7: continue cls_id = int(parts[0]) coords = [float(x) for x in parts[1:]] pts = np.array([[coords[i] * w, coords[i + 1] * h] for i in range(0, len(coords), 2)], dtype=np.int32) color = (0, 255, 0) # yesil = GT cv2.polylines(gt_img, [pts], True, color, 2) if len(pts) > 0: cv2.putText(gt_img, CARDD_CLASSES[cls_id], tuple(pts[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) # Tahmin pred_img = img.copy() if pred_result.masks is not None: for i, mask_xy in enumerate(pred_result.masks.xy): cls_id = int(pred_result.boxes.cls[i].item()) conf = float(pred_result.boxes.conf[i].item()) pts = mask_xy.astype(np.int32) color = (0, 0, 255) # kirmizi = tahmin cv2.polylines(pred_img, [pts], True, color, 2) if len(pts) > 0: label = f"{CARDD_CLASSES[cls_id]} {conf:.2f}" cv2.putText(pred_img, label, tuple(pts[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) # Yan yana birlestir combined = np.hstack([gt_img, pred_img]) cv2.putText(combined, "GT (yesil)", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.putText(combined, "Tahmin (kirmizi)", (w + 10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imwrite(str(save_path), combined) def main(): parser = argparse.ArgumentParser() parser.add_argument("--weights", type=str, required=True) parser.add_argument("--data", type=str, default="cardd.yaml") parser.add_argument("--split", type=str, default="test", choices=["train", "val", "test"]) parser.add_argument("--imgsz", type=int, default=640) parser.add_argument("--batch", type=int, default=16) parser.add_argument("--conf", type=float, default=0.25) parser.add_argument("--iou", type=float, default=0.5) parser.add_argument("--save_failures", action="store_true", help="Hatali tahminleri PNG olarak kaydet") parser.add_argument("--max_failures", type=int, default=100) args = parser.parse_args() # Cikti klasoru timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") out_dir = Path(f"runs/eval_{timestamp}") out_dir.mkdir(parents=True, exist_ok=True) print(f"Cikti klasoru: {out_dir}") # Modeli yukle model = YOLO(args.weights) # Ultralytics built-in val - en saglikli metrikler print(f"\n=== Validation ({args.split}) ===") metrics = model.val( data=args.data, split=args.split, imgsz=args.imgsz, batch=args.batch, conf=args.conf, iou=args.iou, plots=True, save_json=True, project=str(out_dir), name="val_run", ) # Genel metrikler print("\n=== Genel Metrikler ===") box_map = metrics.box.map box_map50 = metrics.box.map50 box_map75 = metrics.box.map75 mask_map = metrics.seg.map mask_map50 = metrics.seg.map50 print(f"Box mAP50: {box_map50:.4f}") print(f"Box mAP50-95: {box_map:.4f}") print(f"Box mAP75: {box_map75:.4f}") print(f"Mask mAP50: {mask_map50:.4f}") print(f"Mask mAP50-95: {mask_map:.4f}") # Per-class metrikler print("\n=== Sinif Bazinda mAP50 ===") per_class = {} for i, name in enumerate(CARDD_CLASSES): if i < len(metrics.box.maps): class_map50 = metrics.box.ap50[i] if i < len(metrics.box.ap50) else 0.0 class_map = metrics.box.maps[i] if i < len(metrics.box.maps) else 0.0 per_class[name] = { "map50": float(class_map50), "map50_95": float(class_map), } print(f" {name:18s} mAP50={class_map50:.4f} mAP50-95={class_map:.4f}") # JSON olarak kaydet results = { "weights": args.weights, "split": args.split, "overall": { "box_map50": float(box_map50), "box_map50_95": float(box_map), "mask_map50": float(mask_map50), "mask_map50_95": float(mask_map), }, "per_class": per_class, } with open(out_dir / "results.json", "w") as f: json.dump(results, f, indent=2) print(f"\nResults: {out_dir / 'results.json'}") # Failure case'leri kaydet if args.save_failures: print(f"\n=== Failure Analizi (max {args.max_failures}) ===") failures_dir = out_dir / "failures" failures_dir.mkdir(exist_ok=True) # Test goruntulerinin yolunu cikar import yaml as yaml_mod with open(args.data, "r") as f: data_cfg = yaml_mod.safe_load(f) data_root = Path(data_cfg["path"]) img_dir = data_root / data_cfg[args.split] lbl_dir = data_root / "labels" / args.split img_paths = sorted(list(img_dir.glob("*.jpg")) + list(img_dir.glob("*.png"))) failures_saved = 0 for img_path in tqdm(img_paths, desc="Failure tarama"): if failures_saved >= args.max_failures: break pred = model.predict(str(img_path), conf=args.conf, verbose=False, imgsz=args.imgsz)[0] gt_path = lbl_dir / (img_path.stem + ".txt") # Basit failure tanimi: tahmin sayisi != GT sayisi VEYA bos vs dolu gt_count = 0 if gt_path.exists(): with open(gt_path, "r") as f: gt_count = len([l for l in f if l.strip()]) pred_count = len(pred.boxes) if pred.boxes is not None else 0 if gt_count != pred_count: save_path = failures_dir / f"{img_path.stem}_gt{gt_count}_pred{pred_count}.jpg" visualize_prediction(img_path, pred, gt_path, save_path) failures_saved += 1 print(f"Kaydedilen failure: {failures_saved} -> {failures_dir}") print(f"\n=== Hata Analizi Onerileri ===") if per_class: worst = min(per_class.items(), key=lambda kv: kv[1]["map50"]) print(f"En zayif sinif: {worst[0]} (mAP50={worst[1]['map50']:.3f})") if worst[1]["map50"] < 0.3: print(f" -> Bu sinifin etiketlerini elden gozden gecir, gercek anlamda az veya hatali olabilir.") print(f" -> Class weights ayarla veya focal loss dene.") if mask_map50 < 0.45: print("Mask mAP50 dusuk -> imgsz artir (640->1024) veya mask loss weight'i artir") if box_map50 < 0.55: print("Box mAP50 baseline'in altinda -> daha cok epoch, daha buyuk model dene") if __name__ == "__main__": main()