Système de segmentation des défauts d'œufs basé sur YOLOv8


Dans un contexte mondial où la sécurité alimentaire est une préoccupation croissante, le contrôle qualité et la détection des défauts des œufs, un aliment de base essentiel, revêtent une importance capitale. Les œufs peuvent développer divers défauts tels que des salissures ou des fissures lors de leur production, transport ou stockage. Ces imperfections affectent non seulement leur apparence mais peuvent aussi présenter des risques pour la santé des consommateurs. Par conséquent, le développement d'un système efficace et précis pour la détection des défauts des œufs est crucial pour identifier et éliminer les produits non conformes en amont de la chaîne de production.

Ces dernières années, les avancées en vision par ordinateur, et plus particulièrement les techniques d'apprentissage profond pour la détection et la segmentation d'objets, ont offert de nouvelles perspectives pour le contrôle qualité des produits agricoles. La famille de modèles YOLO (You Only Look Once), reconnue pour son efficacité en détection temps réel, a été largement adoptée. YOLOv8, en tant que dernière itération, intègre des architectures réseau et des algorithmes d'optimisation plus sophistiqués, permettant d'améliorer la précision tout en maintenant une vitesse de traitement élevée. Ainsi, une recherche axée sur un système de segmentation des défauts d'œufs basé sur une version améliorée de YOLOv8 présente une valeur théorique et pratique significative.

Le jeu de données utilisé dans cette étude, nommé "eggs2", comprend 3000 images couvrant six catégories de défauts d'œufs : œufs bruns sales, œufs bruns intacts, œufs bruns fissurés, œufs blancs intacts, œufs blancs fissurés et œufs blancs sales. Ce jeu de données riche fournit une base solide pour l'entraînement et la validation du modèle, lui permettant d'apprendre les caractéristiques distinctes des divers défauts. En réalisant une segmentation d'instance sur ces images, le système peut identifier précisément les zones défectueuses à la surface des œufs, fournissant ainsi des informations essentielles pour le contrôle qualité.

En pratique, un système de segmentation des défauts d'œufs basé sur YOLOv8 amélioré peut automatiser le contrôle qualité, réduire les coûts et les erreurs humaines, et accroître l'efficacité de la production. Il fournirait aux entreprises du secteur des œufs des outils de gestion de la qualité basés sur la science, leur permettant de conserver un avantage concurrentiel. De plus, les résultats de cette recherche pourraient servir de référence pour la détection de défauts dans d'autres produits agricoles, contribuant ainsi à l'avancement de l'intelligence artificielle dans l'agriculture.

Démonstration Visuelle du Système

Note : Les captures d'écran ou vidéos de démonstration peuvent dater d'une version antérieure du système. La version actuelle a été mise à jour avec les améliorations suivantes :

  • Support des modèles de "détection d'objets" et de "segmentation d'instance" de YOLOv8, avec chargement adaptatif des poids (.pt).
  • Trois modes de reconnaissance : image, vidéo et flux de caméra en temps réel.
  • Exportation automatique des résultats de reconnaissance pour les trois modes, évitant les problèmes de consommation mémoire lors de l'exportation manuelle. Les résultats sont sauvegardés dans le répertoire tempDir.
  • Possibilité de personnaliser le titre et l'image de fond de l'interface Web. Des tutoriels seront fournis à cet effet.

Le projet fournit le jeu de données et les tutoriels d'entraînement. Les poids pré-entraînés (best.pt) ne sont pas inclus directement ; vous devrez entraîner le modèle selon les instructions pour obtenir les résultats de démonstration visuelle et l'interface Web.

Informations sur le Jeu de Données

Détails du Jeu de Données (Nombre et Noms des Classes)


nc: 6
names: ['brow-egg-dirty', 'brown-egg', 'brown-egg-crack', 'white-egg', 'white-egg-crack', 'white-egg-dirty']
 

Description du Jeu de Données

Le jeu de données "eggs2" est spécifiquement conçu pour entraîner un système de segmentation des défauts d'œufs, en particulier pour une application améliorée du modèle YOLOv8-seg. Il couvre six catégories principales afin de fournir des données annotées riches pour les tâches de vision par ordinateur, améliorant ainsi la précision et la robustesse du modèle dans des scénarios réels.

Les six catégories sont : brow-egg-dirty (œuf brun sale), brown-egg (œuf brun), brown-egg-crack (œuf brun fissuré), white-egg (œuf blanc), white-egg-crack (œuf blanc fissuré), et white-egg-dirty (œuf blanc sale). Ces classes reflètent les types d'œufs courants et les défauts possibles, assurant une diversité d'échantillons pour l'entraînement du modèle.

Des annotations précises ont été réalisées pour chaque image afin de garantir l'identification correcte des caractéristiques des défauts. La classification des différents défauts (salissure, fissures) permet au modèle de distinguer les œufs normaux des œufs défectueux et de s'adapter aux diverses conditions environnementales. Par exemple, le modèle apprendra à distinguer les subtilités entre un œuf brun sale et un œuf blanc sale.

La diversité des échantillons est assurée par une grande quantité d'images collectées sous différentes conditions d'éclairage, angles de prise de vue et arrière-plans. Cela améliore la capacité du modèle à maintenir une haute précision de reconnaissance et de segmentation face à des situations complexes du monde réel.

La conception du jeu de données prend en compte les besoins applicatifs, notamment dans les domaines de la sécurité alimentaire et du contrôle qualité, où la rapidité et la précision sont essentielles. L'utilisation du modèle YOLOv8-seg entraîné sur ce jeu de données permet une surveillance en temps réel des défauts, aidant les producteurs à identifier les problèmes rapidement, à réduire les pertes et à améliorer la qualité des produits.

Des techniques d'augmentation de données (rotations, mises à l'échelle, rotations) seront appliquées pendant l'entraînement pour améliorer la capacité de généralisation du modèle et sa robustesse face à des données inconnues.

Principes de l'Algorithme de Segmentation d'Instance YOLOv8-seg

L'algorithme YOLOv8-seg représente une avancée significative dans la famille YOLO, combinant la détection d'objets et la segmentation sémantique. Il traite la détection et la segmentation comme une tâche unifiée, utilisant un seul réseau neuronal pour prédire les boîtes englobantes et segmenter les objets au niveau des pixels.

Architecture du Réseau

Le réseau de YOLOv8-seg est composé d'un réseau principal (backbone), d'un réseau d'amélioration de caractéristiques (feature enhancement network) et d'une tête de détection (detection head).

  • Réseau Principal : Utilise la structure CSP (Cross Stage Partial) pour réduire la charge de calcul tout en maintenant la précision de l'extraction de caractéristiques.
  • Réseau d'Amélioration de Caractéristiques : Intègre les concepts de PAN-FPN (Path Aggregatino Network - Feature Pyramid Network) pour fusionner des caractéristiques multiscales, améliorant ainsi la capacité du modèle à percevoir des objets de différentes tailles.
  • Tête de Détection : Emploie une tête découplée (Decoupled Head) qui sépare les tâches de classification et de régression, optimisant la précision de la localisation et de la prédiction de classe.

Méthode Anchor-Free

YOLOv8-seg adopte une approche de détection sans ancres (Anchor-free). Au lieu de s'appuyer sur des boîtes prédéfinies, il prédit directement la position et la taille des objets. Cela offre une plus grande flexibilité et rapidité, car le modèle se concentre directement sur les points proches de la localisation de l'objet.

Fonctions de Perte

L'entraînement intègre de nouvelles stratégies de fonction de perte, combinant la perte de focalisation (Focal Loss) et la perte d'intersection sur union (IoU Loss) pour une détection et une segmentation plus précises. La perte de focalisation ajuste dynamiquement les poids des échantillons pour se concentrer sur les objets difficiles à détecter, améliorant la robustesse, tandis que la perte IoU affine la précision de la localisation.

Prétraitement des Données

Des techniques telles que l'augmentation de données Mosaic, le redimensionnement adaptatif des images et le remplissage en niveaux de gris sont utilisées pour améliorer l'efficacité de l'entraînement et la capacité d'adaptation du modèle à différents environnements et conditions d'éclairage.

Fonctionnalités du Système (Exemple de Détection)

Les exemples de détection suivants illustrent les capacités du système. La nature exacte du contenu dépendra du jeu de données spécifique utilisé.

Le framework Ultralytics est une bibliothèque efficace de vision par ordinateur axée sur la détection d'objets, la segmentation et le suivi. Sa conception modulaire distribue les différentes fonctionnalités dans plusieurs fichiers pour faciliter la gestion et l'extension.

Tableau des Fonctionnalités des Fichiers

Chemin du Fichier Description de la Fonctionnalité
ultralytics/models/rtdetr/train.py Définit le processus d'entraînement du modèle RT-DETR, incluant l'initialisation du modèle, la construction du jeu de données, l'obtention du validateur et le prétraitement par lots.
ultralytics/nn/backbone/fasternet.py Implémente la construction du modèle FasterNet, comprenant divers composants et modules de base, supportant des tâches efficaces de traitement d'images.
ultralytics/trackers/track.py Intègre la fonctionnalité de suivi d'objets, fournissant un mécanisme pour initialiser et mettre à jour les traceurs pendant le processus de détection d'objets.
ultralytics/nn/extra_modules/ops_dcnv3/modules/__init__.py Agit comme point d'entrée pour le module DCNv3, intégrant et exposant des fonctionnalités spécifiques pour un accès facile par d'autres parties du code.
ultralytics/utils/loss.py Définit plusieurs fonctions de perte pour supporter les tâches de détection d'objets, de segmentation et de détection de points clés, aidant le modèle à optimiser ses performances pendant l'entraînement.

Note : Certains extraits de code dans la section "Explication du Code Source du Projet" peuvent avoir été optimisés ou mis à jour. Ils sont fournis à titre de référence et d'apprentissage. Les codes sources complets pour l'entraînement, l'interface Web et les 50+ innovations sont disponibles via les liens fournis dans la section "Acquisition Complète des Codes Sources".

Code de Démonstration de Segmentation (Image, Vidéo, Caméra) sans Interface Web

Cette section aborde l'utilisation du modèle de segmentation sans l'interface Web. Le code du projet a été optimisé pour faciliter l'intégration des fonctionnalités de segmentation dans vos propres projets. Les fonctionnalités clés incluent la segmentation d'images, de vidéos et de flux de caméra, l'extraction de contours de régions d'intérêt (ROI), la classification de classes, le calcul de périmètres, de surfaces, de circularités et l'extraction de couleurs. Ces fonctions offrent une excellente base pour un développement secondaire.

Extrait de Code Principal Annoté


import random
import cv2
import numpy as np
from PIL import ImageFont, ImageDraw, Image
from hashlib import md5
# Supposons que ces modules soient disponibles dans l'environnement du projet
# from model import Web_Detector
# from chinese_name_list import Label_list

# Fonction fictive pour simuler la disponibilité des modules
class Web_Detector:
   def __init__(self):
       print("Web_Detector initialisé (simulation).")
       self.model = None
       self.label_list = ["dirty", "intact", "crack"] # Simulation

   def load_model(self, weights_path):
       print(f"Chargement du modèle depuis {weights_path} (simulation).")
       # Ici, on simulerait le chargement d'un modèle YOLOv8-seg
       self.model = "simulated_yolov8_seg_model"

   def preprocess(self, image):
       print("Prétraitement de l'image (simulation).")
       # Retourne une image prétraitée simulée
       return image

   def predict(self, preprocessed_image):
       print("Prédiction (simulation).")
       # Simule une sortie de détection : liste de résultats pour chaque image/frame
       # Chaque résultat est une liste de détections, où chaque détection contient:
       # [x1, y1, x2, y2, confidence, class_id, mask_coordinates]
       # Les coordonnées du masque sont une liste de points.
       if isinstance(preprocessed_image, list): # Si c'est une image ou une séquence
           simulated_detections = []
           for img in preprocessed_image:
               # Simuler quelques détections pour un exemple
               sim_det = [
                   [50, 50, 150, 150, 0.9, 0, [[50, 50, 55, 55, 60, 50]]], # Détection 1: bbox, conf, class_id, mask
                   [200, 200, 300, 300, 0.8, 1, [[200, 200, 205, 205, 210, 200]]]  # Détection 2
               ]
               simulated_detections.append(sim_det)
           return simulated_detections
       else: # Si c'est une seule image
            sim_det = [
               [50, 50, 150, 150, 0.9, 0, [[50, 50, 55, 55, 60, 50]]],
               [200, 200, 300, 300, 0.8, 1, [[200, 200, 205, 205, 210, 200]]]
           ]
            return [sim_det]


   def postprocess(self, predictions):
       print("Post-traitement des prédictions (simulation).")
       # Simule le traitement des prédictions pour obtenir des infos structurées
       processed_results = []
       for detection_batch in predictions:
           batch_info = []
           for det in detection_batch:
               x1, y1, x2, y2, conf, cls_id, mask_coords = det
               class_name = self.label_list[cls_id] if cls_id < len(self.label_list) else f"class_{cls_id}"
               
               # Convertir les coordonnées du masque en format attendu par draw_detections
               mask_np = np.array(mask_coords).reshape(-1, 1, 2) # Assurez-vous que c'est un tableau Nx1x2

               batch_info.append({
                   'class_name': class_name,
                   'bbox': [x1, y1, x2, y2],
                   'score': conf,
                   'class_id': cls_id,
                   'mask': [mask_np] # Doit être une liste de contours
               })
           processed_results.append(batch_info)
       return processed_results


# Fonction fictive pour générer une couleur basée sur un nom
def generate_color_based_on_name(name):
   # Utilise une empreinte MD5 pour générer une couleur de manière déterministe
   hash_val = int(md5(name.encode()).hexdigest(), 16)
   r = (hash_val >> 16) & 0xFF
   g = (hash_val >> 8) & 0xFF
   b = hash_val & 0xFF
   return (r, g, b)

# Calculer l'aire d'un polygone
def calculate_polygon_area(points):
   # points doit être un tableau numpy de forme (N, 1, 2) ou (N, 2)
   if points.ndim == 3 and points.shape[1] == 1:
       points = np.squeeze(points, axis=1)
   if points.shape[0] < 3:
       return 0.0
   return cv2.contourArea(points.astype(np.float32))

# Dessiner du texte chinois sur une image OpenCV
def draw_with_chinese(image, text, position, font_size=20, color=(255, 0, 0)):
   image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
   draw = ImageDraw.Draw(image_pil)
   try:
       # Utilise une police courante comme SimSun (Simsun)
       font = ImageFont.truetype("simsun.ttc", font_size, encoding="unic")
   except IOError:
       print("Erreur: Police simsun.ttc non trouvée. Utilisation de la police par défaut.")
       font = ImageFont.load_default()
   draw.text(position, text, font=font, fill=color)
   return cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)

# Ajuster les paramètres en fonction de la taille de l'image
def adjust_parameter(image_size, base_size=1000):
   max_size = max(image_size)
   return max_size / base_size

# Dessiner les détections sur l'image
def draw_detections(image, info, alpha=0.2):
   name, bbox, conf, cls_id, mask_list = info['class_name'], info['bbox'], info['score'], info['class_id'], info['mask']
   
   image_shape = image.shape[:2]
   adjust_param = adjust_parameter(image_shape)
   spacing = int(20 * adjust_param)

   aim_frame_area = 0.0
   
   if not mask_list or not mask_list[0]: # Si pas de masque ou masque vide
       x1, y1, x2, y2 = bbox
       aim_frame_area = (x2 - x1) * (y2 - y1)
       cv2.rectangle(image, (int(x1), int(y1)), (int(x2), int(y2)), color=(0, 0, 255), thickness=max(1, int(3 * adjust_param)))
       image = draw_with_chinese(image, f"{name} ({conf:.2f})", (int(x1), int(y1) - int(30 * adjust_param)), font_size=max(10, int(35 * adjust_param)))
   else:
       # Assurez-vous que mask_list contient des tableaux numpy valides
       all_mask_points = np.vstack([m.reshape(-1, 2) for m in mask_list if m is not None])
       
       if all_mask_points.size > 0:
           aim_frame_area = calculate_polygon_area(all_mask_points)
           
           # Utiliser la couleur générée pour le masque
           mask_color = generate_color_based_on_name(name)
           
           # Créer une image overlay pour le masque
           overlay = image.copy()
           # Dessiner le polygone du masque sur l'overlay
           for mask_contour in mask_list:
               if mask_contour is not None and mask_contour.shape[0] >= 3:
                    cv2.fillPoly(overlay, [mask_contour.astype(np.int32)], mask_color)
                    cv2.drawContours(overlay, [mask_contour.astype(np.int32)], -1, (0, 0, 255), thickness=max(1, int(8 * adjust_param)))
           
           # Fusionner l'overlay avec l'image originale
           image = cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0)
           
           # Redessiner les contours principaux pour une meilleure visibilité
           for mask_contour in mask_list:
                if mask_contour is not None and mask_contour.shape[0] >= 3:
                   cv2.drawContours(image, [mask_contour.astype(np.int32)], -1, (0, 0, 255), thickness=max(1, int(3 * adjust_param)))

           # Calculer et afficher les métriques si le masque est valide
           area = cv2.contourArea(all_mask_points.astype(np.int32))
           perimeter = cv2.arcLength(all_mask_points.astype(np.int32), True)
           
           # Calcul de la circularité: 4 * pi * Area / Perimeter^2
           circularity = 0.0
           if perimeter > 0:
               circularity = 4 * np.pi * area / (perimeter ** 2)
           
           # Calcul de la couleur moyenne dans le masque
           mask_instance = np.zeros(image.shape[:2], dtype=np.uint8)
           for mask_contour in mask_list:
               if mask_contour is not None and mask_contour.shape[0] >= 3:
                   cv2.drawContours(mask_instance, [mask_contour.astype(np.int32)], -1, 255, -1)
           
           if np.count_nonzero(mask_instance) > 0:
               mean_color_bgr = cv2.mean(image, mask=mask_instance)[:3]
               color_str = f"B{mean_color_bgr[0]:.0f} G{mean_color_bgr[1]:.0f} R{mean_color_bgr[2]:.0f}"
           else:
               color_str = "N/A"

           # Position pour le texte du nom de la classe et les métriques
           x_min, y_min = np.min(all_mask_points, axis=0).astype(int)
           
           # Dessiner le nom de la classe et le score
           image = draw_with_chinese(image, f"{name} ({conf:.2f})", (int(x_min), int(y_min) - int(30 * adjust_param)), font_size=max(10, int(35 * adjust_param)))
           
           # Dessiner les métriques sous le nom de la classe
           y_offset = int(y_min) - int(50 * adjust_param) # Position verticale initiale pour les métriques
           metrics = [
               ("Area", f"{area:.2f}"),
               ("Perimeter", f"{perimeter:.2f}"),
               ("Circularity", f"{circularity:.2f}"),
               ("Color", color_str)
           ]
           
           for idx, (metric_name, metric_value) in enumerate(metrics):
               metric_text = f"{metric_name}: {metric_value}"
               image = draw_with_chinese(image, metric_text, (int(x_min), y_offset - idx * spacing), font_size=max(8, int(20 * adjust_param)))
       else:
            # Fallback si le masque est vide ou invalide, dessiner juste la boîte englobante
           x1, y1, x2, y2 = bbox
           aim_frame_area = (x2 - x1) * (y2 - y1)
           cv2.rectangle(image, (int(x1), int(y1)), (int(x2), int(y2)), color=(0, 0, 255), thickness=max(1, int(3 * adjust_param)))
           image = draw_with_chinese(image, f"{name} ({conf:.2f})", (int(x1), int(y1) - int(30 * adjust_param)), font_size=max(10, int(35 * adjust_param)))

   return image, aim_frame_area

# Traiter une seule image ou une liste d'images
def process_images(model, images):
   if isinstance(images, np.ndarray): # Cas d'une seule image
       images = [images]
       
   preprocessed_images = model.preprocess(images)
   predictions = model.predict(preprocessed_images)
   
   results_list = []
   if predictions:
       # Assurer que predictions est une liste de listes de détections
       if not isinstance(predictions[0], list):
           predictions = [predictions] # Si une seule liste de détections est retournée

       processed_detections = model.postprocess(predictions)

       for i, img in enumerate(images):
           processed_image = img.copy()
           if i < len(processed_detections): # S'assurer qu'il y a des détections pour cette image
               for info in processed_detections[i]:
                   processed_image, _ = draw_detections(processed_image, info)
           results_list.append(processed_image)
           
   return results_list[0] if len(results_list) == 1 else results_list

# Fonction principale pour exécuter les démonstrations
if __name__ == "__main__":
   # label_list = Label_list # Utiliser la liste de labels réelle
   # Supposons que Label_list soit une liste de chaînes de caractères pour les noms de classes
   # Par exemple : label_list = ['brow-egg-dirty', 'brown-egg', 'brown-egg-crack', 'white-egg', 'white-egg-crack', 'white-egg-dirty']

   model = Web_Detector()
   # Supposons que le fichier de poids existe et soit accessible
   weights_file = "./weights/yolov8s-seg.pt" # Chemin simulé
   try:
       model.load_model(weights_file)
   except FileNotFoundError:
       print(f"Attention: Fichier de poids '{weights_file}' non trouvé. Utilisation du modèle simulé.")

   print("\n--- Démonstration Caméra ---")
   # Simulation de la capture vidéo
   cap_sim = cv2.VideoCapture(0) # Utilise l'ID 0 pour la webcam
   frame_count_sim = 0
   max_frames_sim = 5 # Limiter pour la simulation

   while cap_sim.isOpened() and frame_count_sim < max_frames_sim:
       ret, frame = cap_sim.read()
       if not ret:
           print("Impossible de lire la frame depuis la caméra.")
           break
       
       # Traiter la frame
       processed_frame = process_images(model, frame)
       
       cv2.imshow('Real-time Camera Feed (Simulated)', processed_frame)
       
       if cv2.waitKey(1) & 0xFF == ord('q'):
           break
       frame_count_sim += 1
   cap_sim.release()
   cv2.destroyAllWindows()

   print("\n--- Démonstration Image ---")
   # Simulation du traitement d'une image
   image_path = './icon/OIP.jpg' # Chemin simulé
   try:
       image = cv2.imread(image_path)
       if image is not None:
           print(f"Traitement de l'image: {image_path}")
           processed_image = process_images(model, image)
           cv2.imshow('Image Detection (Simulated)', processed_image)
           cv2.waitKey(0) # Attendre une touche pour fermer
       else:
           print(f"Erreur: Impossible de charger l'image depuis {image_path}")
   except Exception as e:
       print(f"Erreur lors du traitement de l'image: {e}")
   cv2.destroyAllWindows()

   print("\n--- Démonstration Vidéo ---")
   # Simulation du traitement d'une vidéo
   video_path = 'path/to/your/video.mp4' # Chemin simulé vers un fichier vidéo
   cap_video_sim = cv2.VideoCapture(video_path)
   
   if not cap_video_sim.isOpened():
       print(f"Erreur: Impossible d'ouvrir la vidéo depuis {video_path}. Simulation des résultats.")
       # Si la vidéo n'est pas disponible, on peut simuler le traitement d'une image fixe
       try:
           # Utiliser une image comme substitut si la vidéo n'est pas disponible
           fallback_image = cv2.imread('./icon/OIP.jpg') # Utiliser une image de fallback
           if fallback_image is not None:
               print("Traitement d'une image de fallback pour simuler la vidéo.")
               processed_fallback = process_images(model, fallback_image)
               cv2.imshow('Video Detection Simulation (Fallback Image)', processed_fallback)
               cv2.waitKey(0)
           else:
               print("Erreur: Image de fallback non trouvée.")
       except Exception as e:
           print(f"Erreur lors de la simulation vidéo avec image fallback: {e}")
   else:
       frame_count_video_sim = 0
       max_frames_video_sim = 50 # Limiter pour la simulation

       while cap_video_sim.isOpened() and frame_count_video_sim < max_frames_video_sim:
           ret, frame = cap_video_sim.read()
           if not ret:
               print("Fin de la vidéo.")
               break
           
           processed_frame = process_images(model, frame)
           cv2.imshow('Video Detection (Simulated)', processed_frame)
           
           if cv2.waitKey(1) & 0xFF == ord('q'):
               break
           frame_count_video_sim += 1
       cap_video_sim.release()
   
   cv2.destroyAllWindows()
   print("\nDémonstrations terminées.")

 

Acquisition Complète des Codes Sources, Jeu de Données, et Interface Web

Pour obtenir l'ensemble complet des codes sources pour l'entraînement, l'interface Web et les plus de 50 innovations, ainsi que le jeu de données, veuillez vous référer aux liens suivants :

Note : En raison des restrictions de droits d'auteur, ce blog ne fournit que des liens vers les dépôts originaux, où les liens de téléchargement sont disponibles.

Étiquettes: YOLOv8 Segmentation d'instance détection de défauts vision par ordinateur apprentissage profond

Publié le 21 juillet à 14h56