Cet article détaille l'utilisation de YOLOv8-Seg pour la segmentation sémantique des défauts dans les canalisations d'assainissement, permettant une identification au niveau du pixel des anomalies telles que les fissures, la corrosion et les décalages. Il couvre la préparation des données, la formation du modèle, l'inférence et l'exportation pour des applications industrielles comme l'inspection automatisée et l'évaluation de l'état des canalisations.
Informations sur le jeu de données
Le jeu de données comprend 4055 images annotées au niveau du pixel pour 7 types de défauts, conformément à la norme chinoise CJJ181-2012. Les annotations sont au format VOC XML et compatibles avec la conversion au format YOLO-Seg.
Tableau des classes de défauts
| ID | Nom Français | Code | Description |
|---|---|---|---|
| 0 | Fissure | PL | Présence de fissures ou de ruptures dans la canalisation. |
| 1 | Corrosion | (Non spécifié) | Dégradation de la paroi interne de la canalisation due à la corrosion. |
| 2 | Décalage | (Non spécifié) | Désalignement ou décalage entre deux sections de canalisation. |
| 3 | Infiltration | (Non spécifié) | Filtration d'eau ou d'autres substances à travers la canalisation. |
| 4 | Déformation | BX | Modification de la forme de la canalisation. |
| 5 | Intrusion Racinaire | SG | Pénétration de racines d'arbres dans la canalisation. |
| 6 | Obstruction | ZW | Présence de débris ou d'obstacles bloquant le flux dans la canalisation. |
Configuration de l'environnement
-
Vérification des pilotes NVIDIA : Exécutez
nvidia-smipour confirmer la détection du GPU et la version de CUDA (>= 11.8 recommandé). -
Installation d'Anaconda et création d'un environnement : ```bash
conda create -n pipe_env python=3.9 conda activate pipe_env
-
Installation des dépendances : ```bash
Installation de PyTorch (exemple avec CUDA 11.8)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
Installation de YOLOv8-Seg
pip install ultralytics opencv-python numpy matplotlib lxml scikit-image pillow tensorboard
Vérification de la disponibilité du GPU
python -c "import torch; print(torch.cuda.is_available())"
Préparation des données (Conversion VOC vers YOLO-Seg)
Le format requis par YOLOv8-Seg est le suivant :
dataset/
├── images/
│ ├── train/
│ └── val/
├── labels/
│ ├── train/
│ └── val/
└── data.yaml
Chaque fichier .txt dans le dossier labels doit contenir les points du polygone de segmentation normalisés :
<class_id> <x1> <y1> <x2> <y2> ... <xn> <yn>
Script de conversion VOC vers YOLO-Seg
Ce script convertit les annotations au format XML (VOC) contenant des polygones en fichiers .txt au format YOLO-Seg.
# convert_voc_to_yolo_seg.py
import os
from xml.etree.ElementTree import parse
from pathlib import Path
import numpy as np
# Mappage des classes (ajuster selon vos noms)
class_map = {
'crack': 0,
'corrosion': 1,
'dislocation': 2,
'leakage': 3,
'deformation': 4,
'root_intrusion': 5,
'obstruction': 6
}
def normalize_polygon(polygon_points, img_width, img_height):
"""Convertit les points du polygone en coordonnées normalisées."""
normalized_coords = []
for point in polygon_points:
x_norm = float(point.find('x').text) / img_width
y_norm = float(point.find('y').text) / img_height
normalized_coords.extend([x_norm, y_norm])
return normalized_coords
def process_voc_annotation(xml_filepath, output_labels_dir):
tree = parse(xml_filepath)
root = tree.getroot()
img_dims = root.find('size')
img_w = int(img_dims.find('width').text)
img_h = int(img_dims.find('height').text)
output_txt_path = os.path.join(output_labels_dir, Path(xml_filepath).stem + '.txt')
with open(output_txt_path, 'w') as outfile:
for obj in root.findall('object'):
class_name = obj.find('name').text.strip().lower()
if class_name not in class_map:
continue
class_id = class_map[class_name]
segment_element = obj.find('polygon') # Assurez-vous que vos XML ont une balise 'polygon'
if segment_element is None:
print(f"Avertissement : Pas de balise 'polygon' trouvée pour {xml_filepath}")
continue
points = segment_element.findall('pt')
if len(points) < 3:
continue
yolo_segment_format = normalize_polygon(points, img_w, img_h)
outfile.write(f"{class_id} {' '.join(f'{coord:.6f}' for coord in yolo_segment_format)}\n")
# Configuration des chemins
voc_annotations_path = 'path/to/your/voc/annotations'
yolo_labels_output_path = 'dataset/labels/train' # Ajustez le chemin de sortie
os.makedirs(yolo_labels_output_path, exist_ok=True)
# Boucle sur tous les fichiers XML
for xml_file in Path(voc_annotations_path).glob('*.xml'):
process_voc_annotation(xml_file, yolo_labels_output_path)
print("✅ Conversion VOC vers YOLO-Seg terminée.")
Note : Si vos annotations sont des boîtes englobantes (<bndbox>), vous devrez d'abord les convertir en polygones (par exemple, en utilisant les quatre coins de la boîte).
Division des données (Train/Validation)
Un script Python pour séparer les images et leurs annotations correspondantes en ensembles d'entraînement et de validation (80/20).
# split_data.py
import random
from pathlib import Path
import shutil
source_images_dir = 'dataset/images_source' # Votre dossier d'images brutes
source_labels_dir = 'dataset/labels' # Votre dossier d'annotations .txt générées
output_images_train = 'dataset/images/train'
output_images_val = 'dataset/images/val'
output_labels_train = 'dataset/labels/train'
output_labels_val = 'dataset/labels/val'
# Création des répertoires de sortie
for dir_path in [output_images_train, output_images_val, output_labels_train, output_labels_val]:
os.makedirs(dir_path, exist_ok=True)
# Obtention de la liste des fichiers image
image_files = list(Path(source_images_dir).glob('*.jpg')) # Adaptez l'extension si nécessaire
random.shuffle(image_files)
# Calcul du point de séparation
split_index = int(0.8 * len(image_files))
train_image_files = image_files[:split_index]
val_image_files = image_files[split_index:]
def distribute_files(image_list, img_dest_dir, lbl_dest_dir):
for img_path in image_list:
shutil.copy(img_path, img_dest_dir)
label_path = Path(source_labels_dir) / (img_path.stem + '.txt')
if label_path.exists():
shutil.copy(label_path, lbl_dest_dir)
distribute_files(train_image_files, output_images_train, output_labels_train)
distribute_files(val_image_files, output_images_val, output_labels_val)
print("✅ Répartition des données : 80% entraînement, 20% validation.")
Fichier de configuration data.yaml
Ce fichier décrit la structure du jeu de données pour l'entraînement YOLO.
train: ./dataset/images/train
val: ./dataset/images/val
# Nombre de classes
nc: 7
# Noms des classes
names:
- crack
- corrosion
- dislocation
- leakage
- deformation
- root_intrusion
- obstruction
Entraînement du modèle YOLOv8-Seg
Utilisez le script Python suivant pour entraîner un modèle YOLOv8-Seg. Il est recommandé d'utiliser un modèle pré-entraîné comme yolov8l-seg.pt.
from ultralytics import YOLO
# Charger un modèle pré-entraîné pour la segmentation
model = YOLO('yolov8l-seg.pt') # Ou yolov8m-seg.pt
# Lancer l'entraînement
results = model.train(
data='dataset/data.yaml', # Chemin vers votre fichier data.yaml
epochs=300, # Nombre d'époques (la segmentation nécessite plus d'époques)
batch=16, # Taille du batch (ajustez selon votre VRAM)
imgsz=640, # Taille de l'image d'entrée
optimizer='AdamW',
lr0=0.001,
weight_decay=0.0005,
# Augmentation des données (cruciale pour la segmentation)
augment=True,
hsv_h=0.015, hsv_s=0.7, hsv_v=0.4,
degrees=10.0, translate=0.2, scale=0.5,
flipud=0.0, fliplr=0.5,
mosaic=1.0, mixup=0.1,
# Paramètres spécifiques à la segmentation
overlap_mask=True, # Gère le chevauchement des masques
mask_ratio=4, # Ratio de sous-échantillonnage pour les masques
# Planification du taux d'apprentissage
cos_lr=True,
# Sauvegarde des résultats
project='runs/segmentation_training',
name='pipeline_defect_detection',
save=True,
save_period=25,
exist_ok=False,
# Mise en cache des données pour accélérer l'entraînement
cache=True
)
print("Entraînement terminé. Les meilleurs poids sont sauvegardés dans runs/segmentation_training/pipeline_defect_detection/weights/best.pt")
Inférence et prédiction
1. Inférence sur une seule image
from ultralytics import YOLO
import cv2
# Charger le modèle entraîné
trained_model = YOLO('runs/segmentation_training/pipeline_defect_detection/weights/best.pt')
# Effectuer l'inférence sur une image de test
test_image_path = 'path/to/your/test_image.jpg'
results = trained_model(test_image_path, conf=0.3) # Seuil de confiance
# Afficher les résultats annotés
for result in results:
annotated_frame = result.plot()
cv2.imshow('Résultat de Segmentation', annotated_frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. Prédiction par lots
# Prédire sur l'ensemble de validation
batch_results = trained_model.predict(
source='dataset/images/val',
save=True, # Sauvegarde les images avec les prédictions
project='runs/prediction_output',
name='validation_segmentation',
conf=0.3,
imgsz=640
)
3. Extraction des masques et boîtes englobantes
# Itérer sur les résultats pour accéder aux détails des détections
for res in batch_results: # Ou 'results' pour une seule image
boxes = res.boxes.xyxy.cpu().numpy() # Coordonnées des boîtes englobantes
class_ids = res.boxes.cls.cpu().numpy() # IDs des classes prédites
masks_polygons = res.masks.xy # Liste des polygones de masques
for i, mask_points in enumerate(masks_polygons):
print(f"Défaut ID {int(class_ids[i])}: Masque avec {len(mask_points)} points.")
# Vous pouvez maintenant traiter chaque masque individuellement
# Par exemple, calculer l'aire du défaut à partir des points du masque.
Évaluation du modèle
Évaluez les performances de votre modèle sur l'ensemble de validation.
metrics = trained_model.val(
data='dataset/data.yaml',
split='val', # Utiliser l'ensemble de validation
batch=16,
imgsz=640,
save_json=True # Sauvegarde les métriques au format JSON
)
print(f"MAP@0.5 (masque) : {metrics.seg.map50:.4f}")
print(f"MAP@0.5:0.95 (masque) : {metrics.seg.map:.4f}")
print(f"Précision (masque) : {metrics.seg.p:.4f}")
print(f"Rappel (masque) : {metrics.seg.r:.4f}")
Exportation du modèle
Exportez votre modèle entraîné pour le déploiement dans différents environnements.
# Exporter en ONNX (compatible avec de nombreuses plateformes)
trained_model.export(format='onnx', dynamic=True, opset=13, imgsz=640)
# Exporter en TensorRT (pour une performance maximale sur GPU NVIDIA)
trained_model.export(format='engine', half=True, dynamic=True)