Jules Musquin commited on
Commit
ef8cc33
·
1 Parent(s): 5bffa8b

[ADD] adding a IoU.py, a script to calculate the IoU metric between 2 yolo models.

Browse files
Files changed (3) hide show
  1. .gitignore +2 -1
  2. IoU_yolo.py +110 -0
  3. inference.py +43 -22
.gitignore CHANGED
@@ -31,4 +31,5 @@ lib64/
31
  generated_html/
32
  annotations/
33
  dataset/
34
- test/
 
 
31
  generated_html/
32
  annotations/
33
  dataset/
34
+ test/
35
+ yolov5/
IoU_yolo.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+
4
+ GT_DIR = "test_images/wit396_pdf545/labels"
5
+ PRED_DIR = ""
6
+
7
+ def yolo_to_corners(box):
8
+ """Convertit (x_center, y_center, w, h) en (x1, y1, x2, y2)."""
9
+ x_c, y_c, w, h = box
10
+ x1 = x_c - w / 2
11
+ y1 = y_c - h / 2
12
+ x2 = x_c + w / 2
13
+ y2 = y_c + h / 2
14
+ return np.array([x1, y1, x2, y2])
15
+
16
+
17
+ def get_iou(box_a, box_b):
18
+ """Calcule l'IoU entre deux boxes au format YOLO (x_center, y_center, w, h)."""
19
+ a = yolo_to_corners(box_a)
20
+ b = yolo_to_corners(box_b)
21
+
22
+ ix1 = np.maximum(a[0], b[0])
23
+ iy1 = np.maximum(a[1], b[1])
24
+ ix2 = np.minimum(a[2], b[2])
25
+ iy2 = np.minimum(a[3], b[3])
26
+
27
+ i_width = np.maximum(ix2 - ix1, 0.0)
28
+ i_height = np.maximum(iy2 - iy1, 0.0)
29
+ area_of_intersection = i_width * i_height
30
+
31
+ area_a = max(a[2] - a[0], 0.0) * max(a[3] - a[1], 0.0)
32
+ area_b = max(b[2] - b[0], 0.0) * max(b[3] - b[1], 0.0)
33
+
34
+ area_of_union = area_a + area_b - area_of_intersection
35
+ if area_of_union <= 0:
36
+ return 0.0
37
+
38
+ return float(area_of_intersection / area_of_union)
39
+
40
+
41
+ def parse_yolo_file(filepath):
42
+ """Lit un .txt YOLO et retourne une liste de {class_id, box}."""
43
+ boxes = []
44
+ if not os.path.exists(filepath):
45
+ return boxes
46
+
47
+ with open(filepath, "r") as f:
48
+ for line in f:
49
+ parts = line.strip().split()
50
+ if not parts:
51
+ continue
52
+ class_id = int(float(parts[0]))
53
+ xc, yc, w, h = (float(p) for p in parts[1:5])
54
+ boxes.append({"class_id": class_id, "box": [xc, yc, w, h]})
55
+ return boxes
56
+
57
+
58
+ def match_boxes(gt_boxes, pred_boxes):
59
+ """Associe GT et prédictions par classe, en priorisant le meilleur IoU."""
60
+ matches = []
61
+ unmatched_gt = list(gt_boxes)
62
+ unmatched_pred = list(pred_boxes)
63
+
64
+ classes = set(b["class_id"] for b in gt_boxes) | set(b["class_id"] for b in pred_boxes)
65
+
66
+ for cls in classes:
67
+ gt_cls = [b for b in unmatched_gt if b["class_id"] == cls]
68
+ pred_cls = [b for b in unmatched_pred if b["class_id"] == cls]
69
+
70
+ pairs = []
71
+ for gt_b in gt_cls:
72
+ for pred_b in pred_cls:
73
+ iou = get_iou(gt_b["box"], pred_b["box"])
74
+ pairs.append((iou, gt_b, pred_b))
75
+
76
+ pairs.sort(key=lambda x: x[0], reverse=True)
77
+ used_gt, used_pred = set(), set()
78
+ for iou, gt_b, pred_b in pairs:
79
+ if id(gt_b) in used_gt or id(pred_b) in used_pred:
80
+ continue
81
+ matches.append((gt_b, pred_b, iou))
82
+ used_gt.add(id(gt_b))
83
+ used_pred.add(id(pred_b))
84
+ unmatched_gt.remove(gt_b)
85
+ unmatched_pred.remove(pred_b)
86
+
87
+ return matches, unmatched_gt, unmatched_pred
88
+
89
+ gt_files = sorted(f for f in os.listdir(GT_DIR) if f.endswith(".txt"))
90
+
91
+ all_ious = []
92
+
93
+ for filename in gt_files:
94
+ gt_path = os.path.join(GT_DIR, filename)
95
+ pred_path = os.path.join(PRED_DIR, filename)
96
+
97
+ gt_boxes = parse_yolo_file(gt_path)
98
+ pred_boxes = parse_yolo_file(pred_path)
99
+
100
+ matches, unmatched_gt, unmatched_pred = match_boxes(gt_boxes, pred_boxes)
101
+ ious = [m[2] for m in matches]
102
+ all_ious.extend(ious)
103
+
104
+ mean_iou = np.mean(ious) if ious else 0.0
105
+ print(f"{filename}: {len(matches)} paires, IoU moyen = {mean_iou:.4f}, "
106
+ f"GT manqués = {len(unmatched_gt)}, faux positifs = {len(unmatched_pred)}")
107
+
108
+ print(f"Fichiers traités : {len(gt_files)}")
109
+ print(f"Paires appariées : {len(all_ious)}")
110
+ print(f"IoU moyen global : {np.mean(all_ious) if all_ious else 0.0:.4f}")
inference.py CHANGED
@@ -1,14 +1,14 @@
1
  import argparse
2
  import glob
3
  import os
4
- import matplotlib.pyplot as plt
5
-
6
  from ultralytics import YOLO
7
 
8
- # uv run predict.py path_to_model.pt path_to_images ./test
 
 
9
  def parse_args() -> argparse.Namespace:
10
  parser = argparse.ArgumentParser(
11
- description="Prédiction YOLO sur un dossier d'images avec sauvegarde des résultats annotés."
12
  )
13
 
14
  parser.add_argument(
@@ -24,41 +24,62 @@ def parse_args() -> argparse.Namespace:
24
  parser.add_argument(
25
  "output_path",
26
  type=str,
27
- help="Dossier de sortie pour les images annotées",
28
  )
29
 
30
  return parser.parse_args()
31
 
32
 
33
- def predict(model_path:str, images_path:str, output_path:str):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  model = YOLO(model_path)
35
  images = glob.glob(os.path.join(images_path, "*.jpg"))
36
  selection = images[:25]
37
- print(selection)
 
38
  results = model(selection)
39
- name_model = model_path.split('/')
40
 
41
- output_dir = output_path
42
- model_dir = name_model[0]
 
 
 
 
 
 
43
 
44
- if not os.path.isdir(output_dir):
45
- os.mkdir(output_dir)
46
 
47
- for i, result in enumerate(results):
48
- boxes = result.boxes
49
- im_array = result.plot() # retourne un array numpy BGR avec les boxes dessinées
50
 
51
- plt.figure(figsize=(10, 10))
52
- plt.imshow(im_array[..., ::-1]) # conversion BGR -> RGB pour matplotlib
53
- plt.axis("off")
54
- plt.show()
 
 
55
 
56
- result.save(filename=os.path.join(output_dir, model_dir, f"result_{i}.jpg")) # sauvegarde avec nom unique
57
 
58
  def main() -> None:
59
  args = parse_args()
60
  predict(args.model_path, args.images_path, args.output_path)
61
 
 
62
  if __name__ == "__main__":
63
- main()
64
-
 
1
  import argparse
2
  import glob
3
  import os
 
 
4
  from ultralytics import YOLO
5
 
6
+ # uv run predict.py path_to_model.pt path_to_images ./test
7
+
8
+
9
  def parse_args() -> argparse.Namespace:
10
  parser = argparse.ArgumentParser(
11
+ description="Prédiction YOLO sur un dossier d'images avec sauvegarde des résultats annotés et des labels .txt"
12
  )
13
 
14
  parser.add_argument(
 
24
  parser.add_argument(
25
  "output_path",
26
  type=str,
27
+ help="Dossier de sortie pour les images annotées et les labels",
28
  )
29
 
30
  return parser.parse_args()
31
 
32
 
33
+ def save_yolo_txt(result, txt_path):
34
+ """
35
+ Sauvegarde les prédictions d'un résultat Ultralytics au format YOLO :
36
+ <class_id> <x_center> <y_center> <width> <height> <confidence>
37
+ (coordonnées normalisées entre 0 et 1)
38
+ """
39
+ boxes = result.boxes
40
+ with open(txt_path, "w") as f:
41
+ for box in boxes:
42
+ class_id = int(box.cls[0])
43
+ conf = float(box.conf[0])
44
+ x_center, y_center, width, height = box.xywhn[0].tolist()
45
+ f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f} {conf:.6f}\n")
46
+
47
+
48
+ def predict(model_path: str, images_path: str, output_path: str):
49
  model = YOLO(model_path)
50
  images = glob.glob(os.path.join(images_path, "*.jpg"))
51
  selection = images[:25]
52
+ print(f"{len(selection)} images sélectionnées pour la prédiction")
53
+
54
  results = model(selection)
 
55
 
56
+ # Nom du modèle (sans extension) pour organiser les résultats par modèle testé
57
+ model_name = model_path.split('/')
58
+ model_name = model_name[0]
59
+
60
+ images_dir = os.path.join(output_path, model_name, "images")
61
+ labels_dir = os.path.join(output_path, model_name, "labels")
62
+ os.makedirs(images_dir, exist_ok=True)
63
+ os.makedirs(labels_dir, exist_ok=True)
64
 
65
+ for image_path, result in zip(selection, results):
66
+ image_name = os.path.splitext(os.path.basename(image_path))[0]
67
 
68
+ # Sauvegarde de l'image annotée
69
+ result.save(filename=os.path.join(images_dir, f"{image_name}.jpg"))
 
70
 
71
+ # Sauvegarde des prédictions au format YOLO .txt
72
+ save_yolo_txt(result, os.path.join(labels_dir, f"{image_name}.txt"))
73
+
74
+ print(f"Modèle testé : {model_name}")
75
+ print(f"Images annotées sauvegardées dans : {images_dir}")
76
+ print(f"Labels YOLO sauvegardés dans : {labels_dir}")
77
 
 
78
 
79
  def main() -> None:
80
  args = parse_args()
81
  predict(args.model_path, args.images_path, args.output_path)
82
 
83
+
84
  if __name__ == "__main__":
85
+ main()