YOLOv7 : Trainable Bag-of-Freebies

YOLOv7, sorti en juillet 2022, a constitué une avancée majeure dans la détection d'objets en temps réel à sa sortie. Il a atteint 56,8 % d'AP sur GPU V100, établissant de nouveaux benchmarks lors de son introduction. YOLOv7 a surpassé les détecteurs d'objets contemporains tels que YOLOR, YOLOX, Scaled-YOLOv4 et YOLOv5 en termes de vitesse et de précision. Le modèle est entraîné sur le jeu de données MS COCO à partir de zéro, sans utiliser d'autres jeux de données ou poids pré-entraînés. Le code source de YOLOv7 est disponible sur GitHub. Note que des modèles plus récents comme YOLO11 et YOLO26 ont depuis atteint une précision supérieure avec une efficacité améliorée.

Comparaison de YOLOv7 avec les détecteurs d'objets SOTA

Comparaison des détecteurs d'objets SOTA

D'après les résultats du tableau de comparaison YOLO, nous constatons que la méthode proposée offre le meilleur compromis vitesse-précision de manière exhaustive. Si nous comparons YOLOv7-tiny-SiLU avec YOLOv5-N (r6.1), notre méthode est 127 fps plus rapide et 10,7 % plus précise en AP. De plus, YOLOv7 affiche 51,4 % d'AP à une fréquence de 161 fps, alors que PPYOLOE-L, avec la même AP, n'atteint qu'une fréquence de 78 fps. En termes d'utilisation des paramètres, YOLOv7 est 41 % inférieur à PPYOLOE-L.

Si nous comparons YOLOv7-X avec 114 fps en vitesse d'inférence à YOLOv5-L (r6.1) avec 99 fps en vitesse d'inférence, YOLOv7-X peut améliorer l'AP de 3,9 %. Si YOLOv7-X est comparé à YOLOv5-X (r6.1) d'échelle similaire, la vitesse d'inférence de YOLOv7-X est 31 fps plus rapide. De plus, concernant le nombre de paramètres et les calculs, YOLOv7-X réduit de 22 % les paramètres et de 8 % les calculs par rapport à YOLOv5-X (r6.1), mais améliore l'AP de 2,2 % (Source).

Performance
ModèleParamètres
(M)
FLOPs
(G)
Taille
(pixels)
FPSAPtest / val
50-95
APtest
50
APtest
75
APtest
S
APtest
M
APtest
L
YOLOX-S9.026.864010240,5 % / 40,5 %-----
YOLOX-M25.373.86408147,2 % / 46,9 %-----
YOLOX-L54.2155.66406950,1 % / 49,7 %-----
YOLOX-X99.1281.96405851,5 % / 51,1 %-----
PPYOLOE-S7.917.464020843,1 % / 42,7 %60,5 %46,6 %23,2 %46,4 %56,9 %
PPYOLOE-M23.449.964012348,9 % / 48,6 %66,5 %53,0 %28,6 %52,9 %63,8 %
PPYOLOE-L52.2110.16407851,4 % / 50,9 %68,9 %55,6 %31,4 %55,3 %66,1 %
PPYOLOE-X98.4206.66404552,2 % / 51,9 %69,9 %56,5 %33,3 %56,3 %66,4 %
YOLOv5-N (r6.1)1.94.5640159- / 28,0 %-----
YOLOv5-S (r6.1)7.216.5640156- / 37,4 %-----
YOLOv5-M (r6.1)21.249.0640122- / 45,4 %-----
YOLOv5-L (r6.1)46.5109.164099- / 49,0 %-----
YOLOv5-X (r6.1)86.7205.764083- / 50,7 %-----
YOLOR-CSP52.9120.464010651.1% / 50.8%69.6%55.7%31.7%55,3 %64.7%
YOLOR-CSP-X96.9226.86408753.0% / 52.7%71.4%57.9%33.7%57.1%66.8%
YOLOv7-tiny-SiLU6.213.864028638.7% / 38.7%56.7%41.7%18.8%42.4%51.9%
YOLOv736.9104.764016151.4% / 51.2%69.7%55.9%31.8%55.5%65.0%
YOLOv7-X71.3189.964011453.1% / 52.9%71.2%57.8%33.8%57.1%67.4%
YOLOv5-N6 (r6.1)3.218.41280123- / 36.0%-----
YOLOv5-S6 (r6.1)12.667.21280122- / 44.8%-----
YOLOv5-M6 (r6.1)35.7200.0128090- / 51.3%-----
YOLOv5-L6 (r6.1)76.8445.6128063- / 53.7%-----
YOLOv5-X6 (r6.1)140.7839.2128038- / 55.0%-----
YOLOR-P637.2325.612807653.9% / 53.5%71.4%58.9%36.1%57.7%65.6%
YOLOR-W679.8453.212806655.2% / 54.8%72.7%60,5 %37.7%59.1%67.1%
YOLOR-E6115.8683.212804555.8% / 55.7%73.4%61.1%38.4%59.7%67.7%
YOLOR-D6151,7935,612803456,5% / 56,1%74,1%61,9%38,9%60,4%68,7%
YOLOv7-W670,4360,012808454,9% / 54,6%72,6%60,1%37,3%58,7%67.1%
YOLOv7-E697.2515,212805656,0% / 55,9%73,5%61,2%38,0%59,9%68,4%
YOLOv7-D6154,7806,812804456,6% / 56,3%74,0%61,8%38,8%60,1%69,5%
YOLOv7-E6E151,7843,212803656,8% / 56,8%74,4%62,1%39,3%60,5%69,0%

Présentation

La détection d'objets en temps réel est un composant essentiel de nombreux systèmes de computer vision, incluant le object tracking multi-objets, la conduite autonome, la robotics et l'medical image analysis. Ces dernières années, le développement de la détection d'objets en temps réel s'est concentré sur la conception d'architectures efficaces et l'amélioration de la vitesse d'inférence sur divers CPU, GPU et unités de traitement neuronal (NPU). YOLOv7 prend en charge à la fois les appareils mobiles GPU et GPU, de la périphérie au cloud.

Contrairement aux détecteurs d'objets en temps réel traditionnels qui se concentrent sur l'optimisation de l'architecture, YOLOv7 introduit un accent sur l'optimisation du processus d'entraînement. Cela inclut des modules et des méthodes d'optimisation conçus pour améliorer la précision de la détection d'objets sans augmenter le coût d'inférence, un concept connu sous le nom de "trainable bag-of-freebies".

Fonctionnalités clés

YOLOv7 introduit plusieurs fonctionnalités clés :

  1. Model Re-parameterization : YOLOv7 propose un modèle re-paramétré planifié, qui est une stratégie applicable aux couches dans différents réseaux avec le concept de chemin de propagation de gradient.

  2. Dynamic Label Assignment : L'entraînement du modèle avec plusieurs couches de sortie présente un nouveau problème : "Comment assigner des cibles dynamiques pour les sorties de différentes branches ?" Pour résoudre ce problème, YOLOv7 introduit une nouvelle méthode d'assignation d'étiquettes appelée assignation d'étiquettes guidée par lead de grossier à fin.

  3. Extended and Compound Scaling : YOLOv7 propose des méthodes d'"extension" et de "mise à l'échelle composée" pour le détecteur d'objets en temps réel, capables d'utiliser efficacement les paramètres et le calcul.

  4. Efficiency : La méthode proposée par YOLOv7 peut réduire efficacement environ 40 % des paramètres et 50 % du calcul des détecteurs d'objets en temps réel de pointe, tout en offrant une vitesse d'inférence plus rapide et une précision de détection plus élevée.

Exemples d'utilisation

Ultralytics ne publie pas de poids pré-entraînés yolov7.pt ni de fichiers YAML ultralytics/cfg/models/v7/, et l'entraînement et l'inférence PyTorch natifs pour YOLOv7 ne sont pas pris en charge par le package Python Ultralytics. Cependant, tu peux intégrer un checkpoint YOLOv7 entraîné dans le dépôt YOLOv7 upstream vers Ultralytics en l'exportant vers ONNX ou TensorRT, comme illustré ci-dessous.

Exportation ONNX

Pour utiliser le modèle ONNX de YOLOv7 avec Ultralytics :

  1. (Optionnel) Installe Ultralytics et exporte un modèle ONNX pour que les dépendances requises soient installées automatiquement :

    pip install ultralytics
    yolo export model=yolo26n.pt format=onnx
  2. Exporte le modèle YOLOv7 souhaité en utilisant l'exportateur dans le dépôt YOLOv7 :

    git clone https://github.com/WongKinYiu/yolov7
    cd yolov7
    python export.py --weights yolov7-tiny.pt --grid --end2end --simplify --topk-all 100 --iou-thres 0.65 --conf-thres 0.35 --img-size 640 640 --max-wh 640
  3. Modifie le graphe du modèle ONNX pour qu'il soit compatible avec Ultralytics en utilisant le script suivant :

    import numpy as np
    import onnx
    from onnx import helper, numpy_helper
    
    # Load the ONNX model
    model_path = "yolov7/yolov7-tiny.onnx"  # Replace with your model path
    model = onnx.load(model_path)
    graph = model.graph
    
    # Fix input shape to batch size 1
    input_shape = graph.input[0].type.tensor_type.shape
    input_shape.dim[0].dim_value = 1
    
    # Define the output of the original model
    original_output_name = graph.output[0].name
    
    # Create slicing nodes
    sliced_output_name = f"{original_output_name}_sliced"
    
    # Define initializers for slicing (remove the first value)
    start = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_start")
    end = numpy_helper.from_array(np.array([7], dtype=np.int64), name="slice_end")
    axes = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_axes")
    steps = numpy_helper.from_array(np.array([1], dtype=np.int64), name="slice_steps")
    
    graph.initializer.extend([start, end, axes, steps])
    
    slice_node = helper.make_node(
        "Slice",
        inputs=[original_output_name, "slice_start", "slice_end", "slice_axes", "slice_steps"],
        outputs=[sliced_output_name],
        name="SliceNode",
    )
    graph.node.append(slice_node)
    
    # Define segment slicing
    seg1_start = numpy_helper.from_array(np.array([0], dtype=np.int64), name="seg1_start")
    seg1_end = numpy_helper.from_array(np.array([4], dtype=np.int64), name="seg1_end")
    seg2_start = numpy_helper.from_array(np.array([4], dtype=np.int64), name="seg2_start")
    seg2_end = numpy_helper.from_array(np.array([5], dtype=np.int64), name="seg2_end")
    seg3_start = numpy_helper.from_array(np.array([5], dtype=np.int64), name="seg3_start")
    seg3_end = numpy_helper.from_array(np.array([6], dtype=np.int64), name="seg3_end")
    
    graph.initializer.extend([seg1_start, seg1_end, seg2_start, seg2_end, seg3_start, seg3_end])
    
    # Create intermediate tensors for segments
    segment_1_name = f"{sliced_output_name}_segment1"
    segment_2_name = f"{sliced_output_name}_segment2"
    segment_3_name = f"{sliced_output_name}_segment3"
    
    # Add segment slicing nodes
    graph.node.extend(
        [
            helper.make_node(
                "Slice",
                inputs=[sliced_output_name, "seg1_start", "seg1_end", "slice_axes", "slice_steps"],
                outputs=[segment_1_name],
                name="SliceSegment1",
            ),
            helper.make_node(
                "Slice",
                inputs=[sliced_output_name, "seg2_start", "seg2_end", "slice_axes", "slice_steps"],
                outputs=[segment_2_name],
                name="SliceSegment2",
            ),
            helper.make_node(
                "Slice",
                inputs=[sliced_output_name, "seg3_start", "seg3_end", "slice_axes", "slice_steps"],
                outputs=[segment_3_name],
                name="SliceSegment3",
            ),
        ]
    )
    
    # Concatenate the segments
    concat_output_name = f"{sliced_output_name}_concat"
    concat_node = helper.make_node(
        "Concat",
        inputs=[segment_1_name, segment_3_name, segment_2_name],
        outputs=[concat_output_name],
        axis=1,
        name="ConcatSwapped",
    )
    graph.node.append(concat_node)
    
    # Reshape to [1, -1, 6]
    reshape_shape = numpy_helper.from_array(np.array([1, -1, 6], dtype=np.int64), name="reshape_shape")
    graph.initializer.append(reshape_shape)
    
    final_output_name = f"{concat_output_name}_batched"
    reshape_node = helper.make_node(
        "Reshape",
        inputs=[concat_output_name, "reshape_shape"],
        outputs=[final_output_name],
        name="AddBatchDimension",
    )
    graph.node.append(reshape_node)
    
    # Get the shape of the reshaped tensor
    shape_node_name = f"{final_output_name}_shape"
    shape_node = helper.make_node(
        "Shape",
        inputs=[final_output_name],
        outputs=[shape_node_name],
        name="GetShapeDim",
    )
    graph.node.append(shape_node)
    
    # Extract the second dimension
    dim_1_index = numpy_helper.from_array(np.array([1], dtype=np.int64), name="dim_1_index")
    graph.initializer.append(dim_1_index)
    
    second_dim_name = f"{final_output_name}_dim1"
    gather_node = helper.make_node(
        "Gather",
        inputs=[shape_node_name, "dim_1_index"],
        outputs=[second_dim_name],
        name="GatherSecondDim",
    )
    graph.node.append(gather_node)
    
    # Subtract from 100 to determine how many values to pad
    target_size = numpy_helper.from_array(np.array([100], dtype=np.int64), name="target_size")
    graph.initializer.append(target_size)
    
    pad_size_name = f"{second_dim_name}_padsize"
    sub_node = helper.make_node(
        "Sub",
        inputs=["target_size", second_dim_name],
        outputs=[pad_size_name],
        name="CalculatePadSize",
    )
    graph.node.append(sub_node)
    
    # Build the [2, 3] pad array:
    # 1st row -> [0, 0, 0] (no padding at the start of any dim)
    # 2nd row -> [0, pad_size, 0] (pad only at the end of the second dim)
    pad_starts = numpy_helper.from_array(np.array([0, 0, 0], dtype=np.int64), name="pad_starts")
    graph.initializer.append(pad_starts)
    
    zero_scalar = numpy_helper.from_array(np.array([0], dtype=np.int64), name="zero_scalar")
    graph.initializer.append(zero_scalar)
    
    pad_ends_name = "pad_ends"
    concat_pad_ends_node = helper.make_node(
        "Concat",
        inputs=["zero_scalar", pad_size_name, "zero_scalar"],
        outputs=[pad_ends_name],
        axis=0,
        name="ConcatPadEnds",
    )
    graph.node.append(concat_pad_ends_node)
    
    pad_values_name = "pad_values"
    concat_pad_node = helper.make_node(
        "Concat",
        inputs=["pad_starts", pad_ends_name],
        outputs=[pad_values_name],
        axis=0,
        name="ConcatPadStartsEnds",
    )
    graph.node.append(concat_pad_node)
    
    # Create Pad operator to pad with zeros
    pad_output_name = f"{final_output_name}_padded"
    pad_constant_value = numpy_helper.from_array(
        np.array([0.0], dtype=np.float32),
        name="pad_constant_value",
    )
    graph.initializer.append(pad_constant_value)
    
    pad_node = helper.make_node(
        "Pad",
        inputs=[final_output_name, pad_values_name, "pad_constant_value"],
        outputs=[pad_output_name],
        mode="constant",
        name="PadToFixedSize",
    )
    graph.node.append(pad_node)
    
    # Update the graph's final output to [1, 100, 6]
    new_output_type = onnx.helper.make_tensor_type_proto(
        elem_type=graph.output[0].type.tensor_type.elem_type, shape=[1, 100, 6]
    )
    new_output = onnx.helper.make_value_info(name=pad_output_name, type_proto=new_output_type)
    
    # Replace the old output with the new one
    graph.output.pop()
    graph.output.extend([new_output])
    
    # Save the modified model
    onnx.save(model, "yolov7-ultralytics.onnx")
  4. Tu peux ensuite charger le modèle ONNX modifié et exécuter l'inférence avec lui dans Ultralytics normalement :

    from ultralytics import ASSETS, YOLO
    
    model = YOLO("yolov7-ultralytics.onnx", task="detect")
    
    results = model(ASSETS / "bus.jpg")

Exportation TensorRT

  1. Suis les étapes 1-2 dans la section Exportation ONNX.

  2. Installe le package Python TensorRT :

    pip install tensorrt
  3. Exécute le script suivant pour convertir le modèle ONNX modifié en moteur TensorRT :

    from ultralytics.utils.export import export_engine
    
    export_engine("yolov7-ultralytics.onnx", half=True)
  4. Charge et exécute le modèle dans Ultralytics :

    from ultralytics import ASSETS, YOLO
    
    model = YOLO("yolov7-ultralytics.engine", task="detect")
    
    results = model(ASSETS / "bus.jpg")

Citations et remerciements

Nous tenons à remercier les auteurs de YOLOv7 pour leurs contributions significatives dans le domaine de la détection d'objets en temps réel :

Citation
@article{wang2022yolov7,
  title={YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors},
  author={Wang, Chien-Yao and Bochkovskiy, Alexey and Liao, Hong-Yuan Mark},
  journal={arXiv preprint arXiv:2207.02696},
  year={2022}
}

L'article original sur YOLOv7 est disponible sur arXiv. Les auteurs ont rendu leur travail publiquement disponible, et la base de code est accessible sur GitHub. Nous apprécions leurs efforts pour faire avancer le domaine et rendre leur travail accessible à la communauté plus large.

FAQ

Qu'est-ce que YOLOv7 et pourquoi est-il considéré comme une avancée dans la object detection en temps réel ?

YOLOv7, sorti en juillet 2022, était un modèle de détection d'objets en temps réel significatif qui a atteint une excellente vitesse et précision au moment de sa sortie. Il a surpassé les modèles contemporains tels que YOLOX, YOLOv5 et PPYOLOE à la fois en termes d'utilisation des paramètres et de vitesse d'inférence. Les caractéristiques distinctives de YOLOv7 incluent sa re-paramétrisation du modèle et son assignation dynamique d'étiquettes, qui optimisent ses performances sans augmenter les coûts d'inférence. Pour plus de détails techniques sur son architecture et ses métriques de comparaison avec d'autres détecteurs d'objets de pointe, réfère-toi à l'article YOLOv7.

Comment YOLOv7 améliore-t-il les modèles YOLO précédents comme YOLOv4 et YOLOv5 ?

YOLOv7 introduit plusieurs innovations, incluant la re-paramétrisation du modèle et l'assignation dynamique d'étiquettes, qui améliorent le processus d'entraînement et augmentent la précision de l'inférence. Comparé à YOLOv5, YOLOv7 booste significativement la vitesse et la précision. Par exemple, YOLOv7-X améliore la précision de 2,2 % et réduit les paramètres de 22 % par rapport à YOLOv5-X. Des comparaisons détaillées se trouvent dans le tableau de performance comparaison de YOLOv7 avec les détecteurs d'objets SOTA.

Puis-je utiliser YOLOv7 avec les outils et plateformes Ultralytics ?

À ce jour, Ultralytics prend uniquement en charge l'inférence YOLOv7 ONNX et TensorRT. Pour exécuter la version exportée ONNX et TensorRT de YOLOv7 avec Ultralytics, consulte la section Exemples d'utilisation.

Comment puis-je entraîner un modèle YOLOv7 personnalisé en utilisant mon jeu de données ?

Pour installer et entraîner un modèle YOLOv7 personnalisé, suis ces étapes :

  1. Clone le dépôt YOLOv7 :

    git clone https://github.com/WongKinYiu/yolov7
  2. Navigue vers le répertoire cloné et installe les dépendances :

    cd yolov7
    pip install -r requirements.txt
  3. Prépare ton jeu de données et configure les paramètres du modèle conformément aux instructions d'utilisation fournies dans le dépôt. Pour plus d'orientation, visite le dépôt GitHub de YOLOv7 pour les dernières informations et mises à jour.

  4. Après l'entraînement, tu peux exporter le modèle vers ONNX ou TensorRT pour une utilisation dans Ultralytics, comme montré dans Exemples d'utilisation.

Quelles sont les fonctionnalités clés et les optimisations introduites dans YOLOv7 ?

YOLOv7 offre plusieurs fonctionnalités clés qui révolutionnent la détection d'objets en temps réel :

  • Model Re-parameterization : Améliore les performances du modèle en optimisant les chemins de propagation de gradient.
  • Dynamic Label Assignment : Utilise une méthode guidée par lead de grossier à fin pour assigner des cibles dynamiques pour les sorties à travers différentes branches, améliorant la précision.
  • Extended and Compound Scaling : Utilise efficacement les paramètres et le calcul pour mettre à l'échelle le modèle pour diverses applications en temps réel.
  • Efficiency : Réduit le nombre de paramètres de 40 % et le calcul de 50 % par rapport à d'autres modèles de pointe tout en atteignant des vitesses d'inférence plus rapides.

Pour plus de détails sur ces fonctionnalités, consulte la section Vue d'ensemble de YOLOv7.

Commentaires