File size: 5,801 Bytes
81b4246 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | """
CarDentIQ β Model Evaluation
Developer: Saksham Pathak (github.com/parthmax2)
Runs YOLO validation on a chosen split and reports per-class metrics,
overall mAP, and an optional inference speed benchmark.
Usage (run from project root):
python scripts/evaluate.py # test split, best.pt
python scripts/evaluate.py --model runs/phase2_full/weights/best.pt
python scripts/evaluate.py --split val
python scripts/evaluate.py --save-images # save annotated predictions
python scripts/evaluate.py --speed # add latency benchmark
python scripts/evaluate.py --conf 0.25 --iou 0.5 # custom NMS thresholds
"""
import argparse
import time
from pathlib import Path
from ultralytics import YOLO
# ββ project root βββββββββββββββββββββββββββββββββββββββββββββββββ
ROOT = Path(__file__).resolve().parent.parent
DATA_YAML = str(ROOT / "configs" / "data.yaml")
DEFAULT_MODEL = str(ROOT / "best.pt")
RUNS_DIR = str(ROOT / "runs")
CLASS_NAMES = [
"no_damage", "lost_parts", "torn", "dent",
"paint_scratch", "hole", "broken_glass", "broken_lamp",
]
# βββββββββββββββββββββββββββββββββββββββββββββ
def run_validation(
model_path: str,
split: str,
conf: float,
iou: float,
save_images: bool,
):
if not Path(model_path).exists():
raise FileNotFoundError(f"Weights not found: {model_path}")
if not Path(DATA_YAML).exists():
raise FileNotFoundError(f"Dataset config not found: {DATA_YAML}")
print(f"\n{'='*60}")
print(f" Model : {model_path}")
print(f" Split : {split}")
print(f" Conf : {conf} IoU: {iou}")
print(f"{'='*60}")
model = YOLO(model_path)
metrics = model.val(
data = DATA_YAML,
split = split,
conf = conf,
iou = iou,
save = save_images,
save_txt = False,
save_json = False,
plots = True,
project = RUNS_DIR,
name = f"eval_{split}",
exist_ok = True,
verbose = True,
)
_print_results(metrics)
return metrics
# βββββββββββββββββββββββββββββββββββββββββββββ
def _print_results(metrics) -> None:
box = metrics.box
print(f"\n{'='*60}")
print(" OVERALL METRICS")
print(f"{'='*60}")
print(f" mAP @ 0.5 : {box.map50:.4f}")
print(f" mAP @ 0.5:0.95 : {box.map:.4f}")
print(f" Precision (mean) : {box.mp:.4f}")
print(f" Recall (mean) : {box.mr:.4f}")
print(f"\n{'='*60}")
print(" PER-CLASS METRICS")
print(f"{'='*60}")
header = f" {'Class':<20} {'AP@0.5':>8} {'mAP':>8} {'P':>8} {'R':>8}"
print(header)
print(" " + "-" * (len(header) - 2))
for i, name in enumerate(CLASS_NAMES):
ap50 = box.ap50[i] if i < len(box.ap50) else float("nan")
mAP = box.maps[i] if i < len(box.maps) else float("nan")
p = box.p[i] if i < len(box.p) else float("nan")
r = box.r[i] if i < len(box.r) else float("nan")
print(f" {name:<20} {ap50:>8.4f} {mAP:>8.4f} {p:>8.4f} {r:>8.4f}")
print(f"{'='*60}")
print(f" Confusion matrix and plots saved to: {RUNS_DIR}/eval_*/\n")
# βββββββββββββββββββββββββββββββββββββββββββββ
def speed_benchmark(model_path: str, n: int = 100) -> None:
import torch
from PIL import Image
model = YOLO(model_path)
dummy = Image.new("RGB", (640, 640), color=(128, 128, 128))
for _ in range(10): # warmup
model(dummy, verbose=False)
t0 = time.perf_counter()
for _ in range(n):
model(dummy, verbose=False)
elapsed = time.perf_counter() - t0
device = "CUDA" if torch.cuda.is_available() else "CPU"
print(f"\n[speed] {n} Γ 640Γ640 inference on {device}")
print(f" Avg latency : {elapsed / n * 1000:.1f} ms / image")
print(f" Throughput : {n / elapsed:.1f} FPS")
# βββββββββββββββββββββββββββββββββββββββββββββ
def main() -> None:
parser = argparse.ArgumentParser(
description="Evaluate a car-damage YOLO model",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("--model", default=DEFAULT_MODEL,
help="Path to model weights (.pt)")
parser.add_argument("--split", default="test",
choices=["train", "val", "test"],
help="Dataset split to evaluate on")
parser.add_argument("--conf", type=float, default=0.001,
help="Confidence threshold (use 0.001 for full mAP curve)")
parser.add_argument("--iou", type=float, default=0.6,
help="IoU threshold for NMS")
parser.add_argument("--save-images", action="store_true",
help="Save annotated prediction images")
parser.add_argument("--speed", action="store_true",
help="Run inference speed benchmark after evaluation")
args = parser.parse_args()
run_validation(
model_path = args.model,
split = args.split,
conf = args.conf,
iou = args.iou,
save_images = args.save_images,
)
if args.speed:
speed_benchmark(args.model)
if __name__ == "__main__":
main()
|