|
import argparse |
|
import cv2 |
|
import numpy as np |
|
import torch |
|
from pathlib import Path |
|
import time |
|
import traceback |
|
|
|
from ultralytics import YOLO |
|
|
|
|
|
|
|
from tvcalib.infer.module import TvCalibInferModule |
|
|
|
from visualizer import ( |
|
create_minimap_view, |
|
create_minimap_with_offset_skeletons, |
|
DYNAMIC_SCALE_MIN_MODULATION, |
|
DYNAMIC_SCALE_MAX_MODULATION |
|
) |
|
|
|
from pose_estimator import get_player_data |
|
|
|
|
|
IMAGE_SHAPE = (720, 1280) |
|
SEGMENTATION_MODEL_PATH = Path("models/segmentation/train_59.pt") |
|
|
|
YOLO_MODEL_PATH = Path("models/detection/yolo_football.pt") |
|
|
|
BALL_CLASS_INDEX = 2 |
|
|
|
def preprocess_image_tvcalib(image_bgr): |
|
"""Prétraite l'image BGR pour TvCalib et retourne le tenseur et l'image RGB redimensionnée.""" |
|
if image_bgr is None: |
|
raise ValueError("Impossible de charger l'image") |
|
|
|
|
|
h, w = image_bgr.shape[:2] |
|
if h != IMAGE_SHAPE[0] or w != IMAGE_SHAPE[1]: |
|
print(f"Redimensionnement de l'image vers {IMAGE_SHAPE[1]}x{IMAGE_SHAPE[0]}") |
|
image_bgr_resized = cv2.resize(image_bgr, (IMAGE_SHAPE[1], IMAGE_SHAPE[0]), interpolation=cv2.INTER_LINEAR) |
|
else: |
|
image_bgr_resized = image_bgr |
|
|
|
|
|
image_rgb_resized = cv2.cvtColor(image_bgr_resized, cv2.COLOR_BGR2RGB) |
|
|
|
|
|
image_tensor = torch.from_numpy(image_rgb_resized).float() |
|
image_tensor = image_tensor.permute(2, 0, 1) |
|
mean = torch.tensor([0.485, 0.456, 0.406]).view(-1, 1, 1) |
|
std = torch.tensor([0.229, 0.224, 0.225]).view(-1, 1, 1) |
|
image_tensor = (image_tensor / 255.0 - mean) / std |
|
|
|
|
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') |
|
image_tensor = image_tensor.to(device) |
|
|
|
|
|
return image_tensor, image_bgr_resized, image_rgb_resized |
|
|
|
def main(): |
|
parser = argparse.ArgumentParser(description="Exécute la méthode TvCalib sur une seule image.") |
|
parser.add_argument("image_path", type=str, help="Chemin vers l'image à traiter.") |
|
parser.add_argument("--output_homography", type=str, default=None, help="Chemin optionnel pour sauvegarder la matrice d'homographie (.npy).") |
|
parser.add_argument("--optim_steps", type=int, default=500, help="Nombre d'étapes d'optimisation pour la calibration (l'arrêt anticipé est désactivé).") |
|
parser.add_argument("--target_avg_scale", type=float, default=1, |
|
help="Facteur d'échelle MOYEN CIBLE pour dessiner les squelettes sur la minimap (défaut: 0.35). Le script ajuste l'échelle de base pour tenter d'atteindre cette moyenne.") |
|
|
|
args = parser.parse_args() |
|
|
|
if not Path(args.image_path).exists(): |
|
print(f"Erreur : Fichier image introuvable : {args.image_path}") |
|
return |
|
|
|
if not SEGMENTATION_MODEL_PATH.exists(): |
|
print(f"Erreur : Modèle de segmentation introuvable : {SEGMENTATION_MODEL_PATH}") |
|
print("Assurez-vous d'avoir copié train_59.pt dans le dossier models/segmentation/") |
|
return |
|
|
|
|
|
if not YOLO_MODEL_PATH.exists(): |
|
print(f"Erreur : Modèle YOLO introuvable : {YOLO_MODEL_PATH}") |
|
print(f"Assurez-vous d'avoir téléchargé {YOLO_MODEL_PATH.name} et de l'avoir placé dans {YOLO_MODEL_PATH.parent}/") |
|
return |
|
|
|
print("Initialisation de TvCalibInferModule...") |
|
try: |
|
model = TvCalibInferModule( |
|
segmentation_checkpoint=SEGMENTATION_MODEL_PATH, |
|
image_shape=IMAGE_SHAPE, |
|
optim_steps=args.optim_steps, |
|
lens_dist=False |
|
) |
|
print(f"✓ Modèle chargé sur {next(model.model_calib.parameters()).device}") |
|
except Exception as e: |
|
print(f"Erreur lors de l'initialisation du modèle : {e}") |
|
return |
|
|
|
print(f"Traitement de l'image : {args.image_path}") |
|
try: |
|
|
|
image_path_obj = Path(args.image_path) |
|
absolute_path = image_path_obj.resolve() |
|
print(f"Tentative de lecture de l'image via cv2.imread depuis : {absolute_path}") |
|
if not absolute_path.is_file(): |
|
print(f"ERREUR : Le chemin absolu {absolute_path} ne pointe pas vers un fichier existant juste avant imread !") |
|
return |
|
|
|
|
|
image_bgr_orig = cv2.imread(args.image_path) |
|
if image_bgr_orig is None: |
|
raise FileNotFoundError(f"Impossible de lire le fichier image: {args.image_path} (vérifié comme existant juste avant, problème avec imread)") |
|
|
|
|
|
start_preprocess = time.time() |
|
image_tensor, image_bgr_resized, image_rgb_resized = preprocess_image_tvcalib(image_bgr_orig) |
|
print(f"Temps de prétraitement TvCalib : {time.time() - start_preprocess:.3f}s") |
|
|
|
|
|
print("\nChargement du modèle YOLO et détection du ballon...") |
|
start_yolo = time.time() |
|
ball_ref_point_img = None |
|
try: |
|
yolo_model = YOLO(YOLO_MODEL_PATH) |
|
|
|
results = yolo_model.predict(image_bgr_resized, classes=[BALL_CLASS_INDEX], verbose=False) |
|
|
|
if results and len(results[0].boxes) > 0: |
|
|
|
best_ball_box = results[0].boxes[results[0].boxes.conf.argmax()] |
|
x1, y1, x2, y2 = map(int, best_ball_box.xyxy[0].tolist()) |
|
conf = best_ball_box.conf[0].item() |
|
|
|
|
|
ball_ref_point_img = np.array([(x1 + x2) / 2, y2], dtype=np.float32) |
|
print(f" ✓ Ballon trouvé (conf: {conf:.2f}) à la bbox [{x1},{y1},{x2},{y2}]. Point réf: {ball_ref_point_img}") |
|
else: |
|
print(" Aucun ballon détecté.") |
|
|
|
except Exception as e_yolo: |
|
print(f" Erreur pendant la détection YOLO : {e_yolo}") |
|
print(f"Temps de détection YOLO : {time.time() - start_yolo:.3f}s") |
|
|
|
|
|
print("Exécution de la segmentation...") |
|
start_segment = time.time() |
|
with torch.no_grad(): |
|
keypoints = model._segment(image_tensor) |
|
print(f"Temps de segmentation : {time.time() - start_segment:.3f}s") |
|
|
|
|
|
print("Exécution de la calibration (optimisation)...") |
|
start_calibrate = time.time() |
|
homography = model._calibrate(keypoints) |
|
print(f"Temps de calibration : {time.time() - start_calibrate:.3f}s") |
|
|
|
if homography is not None: |
|
print("\n--- Homographie Calculée ---") |
|
if isinstance(homography, torch.Tensor): |
|
homography_np = homography.detach().cpu().numpy() |
|
else: |
|
homography_np = homography |
|
print(homography_np) |
|
|
|
if args.output_homography: |
|
try: |
|
np.save(args.output_homography, homography_np) |
|
print(f"\nHomographie sauvegardée dans : {args.output_homography}") |
|
except Exception as e: |
|
print(f"Erreur lors de la sauvegarde de l'homographie : {e}") |
|
|
|
|
|
print("\nExtraction des données joueurs (pose+couleur)...") |
|
start_pose = time.time() |
|
player_list = get_player_data(image_bgr_resized) |
|
print(f"Temps d'extraction données joueurs : {time.time() - start_pose:.3f}s ({len(player_list)} joueurs trouvés)") |
|
|
|
|
|
print("\nCalcul de l'échelle de base pour atteindre la cible...") |
|
target_average_scale = args.target_avg_scale |
|
|
|
|
|
|
|
avg_modulation_expected = DYNAMIC_SCALE_MIN_MODULATION + \ |
|
(DYNAMIC_SCALE_MAX_MODULATION - DYNAMIC_SCALE_MIN_MODULATION) * (1.0 - 0.5) |
|
|
|
estimated_base_scale = target_average_scale |
|
if avg_modulation_expected != 0: |
|
estimated_base_scale = target_average_scale / avg_modulation_expected |
|
else: |
|
print("Avertissement : Modulation moyenne attendue nulle, impossible d'estimer l'échelle de base.") |
|
|
|
print(f" Modulation dynamique moyenne attendue (pour Y=0.5) : {avg_modulation_expected:.3f}") |
|
print(f" Échelle de base interne estimée pour cible {target_average_scale:.3f} : {estimated_base_scale:.3f}") |
|
|
|
|
|
print("\nGénération des minimaps (Originale et Squelettes Décalés)...") |
|
|
|
|
|
minimap_original = create_minimap_view(image_rgb_resized, homography_np) |
|
|
|
|
|
|
|
minimap_offset_skeletons, actual_avg_scale = create_minimap_with_offset_skeletons( |
|
player_list, |
|
homography_np, |
|
base_skeleton_scale=estimated_base_scale, |
|
ball_ref_point_img=ball_ref_point_img |
|
) |
|
|
|
|
|
if actual_avg_scale is not None: |
|
print(f"\nÉchelle moyenne CIBLE demandée (--target_avg_scale) : {target_average_scale:.3f}") |
|
print(f"Échelle moyenne FINALE RÉELLEMENT appliquée (basée sur joueurs réels) : {actual_avg_scale:.3f}") |
|
|
|
|
|
print("\nAffichage des résultats. Appuyez sur une touche pour quitter.") |
|
|
|
|
|
if minimap_original is not None: |
|
cv2.imshow("Minimap avec Projection Originale", minimap_original) |
|
else: |
|
print("N'a pas pu générer la minimap originale.") |
|
|
|
|
|
if minimap_offset_skeletons is not None: |
|
cv2.imshow("Minimap avec Squelettes Decales", minimap_offset_skeletons) |
|
else: |
|
print("N'a pas pu générer la minimap squelettes décalés.") |
|
|
|
cv2.waitKey(0) |
|
|
|
else: |
|
print("\nAucune homographie n'a pu être calculée.") |
|
|
|
except Exception as e: |
|
print(f"Erreur lors du traitement de l'image : {e}") |
|
traceback.print_exc() |
|
finally: |
|
print("Fermeture des fenêtres OpenCV.") |
|
cv2.destroyAllWindows() |
|
|
|
if __name__ == "__main__": |
|
main() |