Skip to content

Utilitaires simples

code avec perspective

Le ultralytics fournit une variété d'utilitaires pour soutenir, améliorer et accélérer vos flux de travail. Bien qu'il en existe beaucoup d'autres, ce guide met en évidence certains des plus utiles pour les développeurs, servant de référence pratique pour la programmation avec les outils Ultralytics .



Regarder : Ultralytics Utilitaires | Auto Annotation, Explorer API et Conversion de jeux de données

Données

Étiquetage automatique / Annotations

L'annotation des ensembles de données est un processus qui nécessite beaucoup de ressources et de temps. Si vous disposez d'un modèle dedétection d'objets Ultralytics YOLO entraîné sur une quantité raisonnable de données, vous pouvez l'utiliser avec le logiciel SAM pour annoter automatiquement des données supplémentaires au format de segmentation.

from ultralytics.data.annotator import auto_annotate

auto_annotate(
    data="path/to/new/data",
    det_model="yolo11n.pt",
    sam_model="mobile_sam.pt",
    device="cuda",
    output_dir="path/to/save_labels",
)

Cette fonction ne renvoie aucune valeur. Pour plus de détails :

Visualiser les annotations des ensembles de données

Cette fonction permet de visualiser les annotations YOLO sur une image avant l'entraînement, ce qui permet d'identifier et de corriger les annotations erronées susceptibles d'entraîner des résultats de détection incorrects. Elle dessine des boîtes de délimitation, étiquette les objets avec des noms de classe et ajuste la couleur du texte en fonction de la luminance de l'arrière-plan pour une meilleure lisibilité.

from ultralytics.data.utils import visualize_image_annotations

label_map = {  # Define the label map with all annotated class labels.
    0: "person",
    1: "car",
}

# Visualize
visualize_image_annotations(
    "path/to/image.jpg",  # Input image path.
    "path/to/annotations.txt",  # Annotation file path for the image.
    label_map,
)

Convertir les masques de segmentation au format YOLO

Masques de segmentation au format YOLO

Utilisez cette fonction pour convertir un jeu de données d'images de masques de segmentation au format Ultralytics YOLO d'Ultralytics. Cette fonction prend le répertoire contenant les images de masque au format binaire et les convertit au format de segmentation YOLO .

Les masques convertis seront enregistrés dans le répertoire de sortie spécifié.

from ultralytics.data.converter import convert_segment_masks_to_yolo_seg

# The classes here is the total classes in the dataset.
# for COCO dataset we have 80 classes.
convert_segment_masks_to_yolo_seg(masks_dir="path/to/masks_dir", output_dir="path/to/output_dir", classes=80)

Conversion de COCO au format YOLO

Utilisez-le pour convertir COCO JSON au format YOLO . Pour les jeux de données de détection d'objets (bounding box), définir les deux paramètres suivants use_segments et use_keypoints à False.

from ultralytics.data.converter import convert_coco

convert_coco(
    "../datasets/coco/annotations/",
    use_segments=False,
    use_keypoints=False,
    cls91to80=True,
)

Pour plus d'informations sur le convert_coco fonction, visitez la page de référence.

Obtenir les dimensions du cadre de délimitation

import cv2

from ultralytics import YOLO
from ultralytics.utils.plotting import Annotator

model = YOLO("yolo11n.pt")  # Load pretrain or fine-tune model

# Process the image
source = cv2.imread("path/to/image.jpg")
results = model(source)

# Extract results
annotator = Annotator(source, example=model.names)

for box in results[0].boxes.xyxy.cpu():
    width, height, area = annotator.get_bbox_dimension(box)
    print(f"Bounding Box Width {width.item()}, Height {height.item()}, Area {area.item()}")

Convertir les cadres de délimitation en segments

Avec les x y w h les données de la boîte de délimitation, les convertir en segments à l'aide de la fonction yolo_bbox2segment fonction. Organisez les fichiers d'images et d'annotations comme suit :

data
|__ images
    ├─ 001.jpg
    ├─ 002.jpg
    ├─ ..
    └─ NNN.jpg
|__ labels
    ├─ 001.txt
    ├─ 002.txt
    ├─ ..
    └─ NNN.txt
from ultralytics.data.converter import yolo_bbox2segment

yolo_bbox2segment(
    im_dir="path/to/images",
    save_dir=None,  # saved to "labels-segment" in images directory
    sam_model="sam_b.pt",
)

Visitez le site yolo_bbox2segment page de référence pour plus d'informations sur cette fonction.

Convertir les segments en boîtes englobantes

Si vous avez un jeu de données qui utilise la méthode format du jeu de données de segmentationvous pouvez facilement les convertir en boîtes de délimitation verticales (ou horizontales) (x y w h ) avec cette fonction.

import numpy as np

from ultralytics.utils.ops import segments2boxes

segments = np.array(
    [
        [805, 392, 797, 400, ..., 808, 714, 808, 392],
        [115, 398, 113, 400, ..., 150, 400, 149, 298],
        [267, 412, 265, 413, ..., 300, 413, 299, 412],
    ]
)

segments2boxes([s.reshape(-1, 2) for s in segments])
# >>> array([[ 741.66, 631.12, 133.31, 479.25],
#           [ 146.81, 649.69, 185.62, 502.88],
#           [ 281.81, 636.19, 118.12, 448.88]],
#           dtype=float32) # xywh bounding boxes

Pour comprendre le fonctionnement de cette fonction, consultez la page de référence.

Utilitaires

Compression d'images

Compression d'un fichier image unique à une taille réduite tout en préservant son rapport hauteur/largeur et sa qualité. Si l'image d'entrée est plus petite que la dimension maximale, elle ne sera pas redimensionnée.

from pathlib import Path

from ultralytics.data.utils import compress_one_image

for f in Path("path/to/dataset").rglob("*.jpg"):
    compress_one_image(f)

Séparation automatique de l'ensemble de données

Diviser automatiquement un ensemble de données en train/val/test et enregistrer les fractionnements résultants dans autosplit_*.txt des fichiers. Cette fonction utilise l'échantillonnage aléatoire, qui est exclu lors de l'utilisation de la fonction fraction argument en faveur de la formation.

from ultralytics.data.utils import autosplit

autosplit(
    path="path/to/images",
    weights=(0.9, 0.1, 0.0),  # (train, validation, test) fractional splits
    annotated_only=False,  # split only images with annotation file when True
)

Voir la page de référence pour plus de détails sur cette fonction.

Segmentation d'un polygone en masque binaire

Convertit un polygone unique (sous forme de liste) en un masque binaire de la taille d'image spécifiée. Le polygone doit être sous la forme de [N, 2]N est le nombre de (x, y) points définissant le contour du polygone.

Avertissement

N doit toujours être égal.

import numpy as np

from ultralytics.data.utils import polygon2mask

imgsz = (1080, 810)
polygon = np.array([805, 392, 797, 400, ..., 808, 714, 808, 392])  # (238, 2)

mask = polygon2mask(
    imgsz,  # tuple
    [polygon],  # input as list
    color=255,  # 8-bit binary
    downsample_ratio=1,
)

Boîtes de délimitation

Instances de la boîte de délimitation (horizontale)

Pour gérer les données relatives à la boîte de délimitation, la fonction Bboxes permet de convertir les formats de coordonnées des boîtes, de mettre à l'échelle les dimensions des boîtes, de calculer les surfaces, d'inclure les décalages, etc.

import numpy as np

from ultralytics.utils.instance import Bboxes

boxes = Bboxes(
    bboxes=np.array(
        [
            [22.878, 231.27, 804.98, 756.83],
            [48.552, 398.56, 245.35, 902.71],
            [669.47, 392.19, 809.72, 877.04],
            [221.52, 405.8, 344.98, 857.54],
            [0, 550.53, 63.01, 873.44],
            [0.0584, 254.46, 32.561, 324.87],
        ]
    ),
    format="xyxy",
)

boxes.areas()
# >>> array([ 4.1104e+05,       99216,       68000,       55772,       20347,      2288.5])

boxes.convert("xywh")
print(boxes.bboxes)
# >>> array(
#     [[ 413.93, 494.05,  782.1, 525.56],
#      [ 146.95, 650.63,  196.8, 504.15],
#      [  739.6, 634.62, 140.25, 484.85],
#      [ 283.25, 631.67, 123.46, 451.74],
#      [ 31.505, 711.99,  63.01, 322.91],
#      [  16.31, 289.67, 32.503,  70.41]]
# )

Voir le Bboxes section de référence pour plus d'attributs et de méthodes.

Conseil

La plupart des fonctions suivantes (et d'autres encore) sont accessibles à l'aide de la fonction Bboxes classemais si vous préférez travailler directement avec les fonctions, reportez-vous aux sous-sections suivantes pour savoir comment les importer indépendamment.

Boîtes de mise à l'échelle

Lors de la mise à l'échelle d'une image vers le haut ou vers le bas, vous pouvez mettre à l'échelle de manière appropriée les coordonnées de la boîte englobante correspondante en utilisant la fonction ultralytics.utils.ops.scale_boxes.

import cv2 as cv
import numpy as np

from ultralytics.utils.ops import scale_boxes

image = cv.imread("ultralytics/assets/bus.jpg")
h, w, c = image.shape
resized = cv.resize(image, None, (), fx=1.2, fy=1.2)
new_h, new_w, _ = resized.shape

xyxy_boxes = np.array(
    [
        [22.878, 231.27, 804.98, 756.83],
        [48.552, 398.56, 245.35, 902.71],
        [669.47, 392.19, 809.72, 877.04],
        [221.52, 405.8, 344.98, 857.54],
        [0, 550.53, 63.01, 873.44],
        [0.0584, 254.46, 32.561, 324.87],
    ]
)

new_boxes = scale_boxes(
    img1_shape=(h, w),  # original image dimensions
    boxes=xyxy_boxes,  # boxes from original image
    img0_shape=(new_h, new_w),  # resized image dimensions (scale to)
    ratio_pad=None,
    padding=False,
    xywh=False,
)

print(new_boxes)
# >>> array(
#     [[  27.454,  277.52,  965.98,   908.2],
#     [   58.262,  478.27,  294.42,  1083.3],
#     [   803.36,  470.63,  971.66,  1052.4],
#     [   265.82,  486.96,  413.98,    1029],
#     [        0,  660.64,  75.612,  1048.1],
#     [   0.0701,  305.35,  39.073,  389.84]]
# )

Conversions de formats de boîtes englobantes

XYXY → XYWH

Convertit les coordonnées de la boîte englobante du format (x1, y1, x2, y2) au format (x, y, largeur, hauteur), où (x1, y1) est le coin supérieur gauche et (x2, y2) est le coin inférieur droit.

import numpy as np

from ultralytics.utils.ops import xyxy2xywh

xyxy_boxes = np.array(
    [
        [22.878, 231.27, 804.98, 756.83],
        [48.552, 398.56, 245.35, 902.71],
        [669.47, 392.19, 809.72, 877.04],
        [221.52, 405.8, 344.98, 857.54],
        [0, 550.53, 63.01, 873.44],
        [0.0584, 254.46, 32.561, 324.87],
    ]
)
xywh = xyxy2xywh(xyxy_boxes)

print(xywh)
# >>> array(
#     [[ 413.93,  494.05,   782.1, 525.56],
#     [  146.95,  650.63,   196.8, 504.15],
#     [   739.6,  634.62,  140.25, 484.85],
#     [  283.25,  631.67,  123.46, 451.74],
#     [  31.505,  711.99,   63.01, 322.91],
#     [   16.31,  289.67,  32.503,  70.41]]
# )

Toutes les conversions de la boîte englobante

from ultralytics.utils.ops import (
    ltwh2xywh,
    ltwh2xyxy,
    xywh2ltwh,  # xywh → top-left corner, w, h
    xywh2xyxy,
    xywhn2xyxy,  # normalized → pixel
    xyxy2ltwh,  # xyxy → top-left corner, w, h
    xyxy2xywhn,  # pixel → normalized
)

for func in (ltwh2xywh, ltwh2xyxy, xywh2ltwh, xywh2xyxy, xywhn2xyxy, xyxy2ltwh, xyxy2xywhn):
    print(help(func))  # print function docstrings

Voir la docstring pour chaque fonction ou visiter la page ultralytics.utils.ops page de référence pour en savoir plus.

Traçage

Dessiner des annotations

Ultralytics comprend un Annotator pour annoter différents types de données. Il est préférable de l'utiliser avec détection d'objets boîtes de délimitation, poser des points cléset boîtes de délimitation orientées.

Ultralytics Annotation par balayage

Exemples Python utilisant Ultralytics YOLO 🚀

import cv2

from ultralytics import YOLO
from ultralytics.solutions.solutions import SolutionAnnotator
from ultralytics.utils.plotting import colors

# User defined video path and model file
cap = cv2.VideoCapture("path/to/video.mp4")
model = YOLO(model="yolo11s-seg.pt")  # Model file i.e. yolo11s.pt or yolo11m-seg.pt

if not cap.isOpened():
    print("Error: Could not open video.")
    exit()

# Initialize the video writer object.
w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
video_writer = cv2.VideoWriter("ultralytics.avi", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))

masks = None  # Initialize variable to store masks data
f = 0  # Initialize frame count variable for enabling mouse event.
line_x = w  # Store width of line.
dragging = False  # Initialize bool variable for line dragging.
classes = model.names  # Store model classes names for plotting.
window_name = "Ultralytics Sweep Annotator"


def drag_line(event, x, y, flags, param):  # Mouse callback for dragging line.
    global line_x, dragging
    if event == cv2.EVENT_LBUTTONDOWN or (flags & cv2.EVENT_FLAG_LBUTTON):
        line_x = max(0, min(x, w))
        dragging = True


while cap.isOpened():  # Loop over the video capture object.
    ret, im0 = cap.read()
    if not ret:
        break
    f = f + 1  # Increment frame count.
    count = 0  # Re-initialize count variable on every frame for precise counts.
    annotator = SolutionAnnotator(im0)
    results = model.track(im0, persist=True)  # Track objects using track method.
    if f == 1:
        cv2.namedWindow(window_name)
        cv2.setMouseCallback(window_name, drag_line)

    if results[0].boxes.id is not None:
        if results[0].masks is not None:
            masks = results[0].masks.xy
        track_ids = results[0].boxes.id.int().cpu().tolist()
        clss = results[0].boxes.cls.cpu().tolist()
        boxes = results[0].boxes.xyxy.cpu()

        for mask, box, cls, t_id in zip(masks or [None] * len(boxes), boxes, clss, track_ids):
            color = colors(t_id, True)  # Assign different color to each tracked object.
            if mask is not None and mask.size > 0:
                # If you want to overlay the masks
                # mask[:, 0] = np.clip(mask[:, 0], line_x, w)
                # mask_img = cv2.fillPoly(im0.copy(), [mask.astype(int)], color)
                # cv2.addWeighted(mask_img, 0.5, im0, 0.5, 0, im0)

                if box[0] > line_x:
                    count += 1
                    annotator.seg_bbox(mask=mask, mask_color=color, label=str(classes[cls]))
            else:
                if box[0] > line_x:
                    count += 1
                    annotator.box_label(box=box, color=color, label=str(classes[cls]))

    annotator.sweep_annotator(line_x=line_x, line_y=h, label=f"COUNT:{count}")  # Display the sweep
    cv2.imshow(window_name, im0)
    video_writer.write(im0)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

cap.release()  # Release the video capture.
video_writer.release()  # Release the video writer.
cv2.destroyAllWindows()  # Destroy all opened windows.

Cadres horizontaux

import cv2 as cv
import numpy as np

from ultralytics.utils.plotting import Annotator, colors

names = {
    0: "person",
    5: "bus",
    11: "stop sign",
}

image = cv.imread("ultralytics/assets/bus.jpg")
ann = Annotator(
    image,
    line_width=None,  # default auto-size
    font_size=None,  # default auto-size
    font="Arial.ttf",  # must be ImageFont compatible
    pil=False,  # use PIL, otherwise uses OpenCV
)

xyxy_boxes = np.array(
    [
        [5, 22.878, 231.27, 804.98, 756.83],  # class-idx x1 y1 x2 y2
        [0, 48.552, 398.56, 245.35, 902.71],
        [0, 669.47, 392.19, 809.72, 877.04],
        [0, 221.52, 405.8, 344.98, 857.54],
        [0, 0, 550.53, 63.01, 873.44],
        [11, 0.0584, 254.46, 32.561, 324.87],
    ]
)

for nb, box in enumerate(xyxy_boxes):
    c_idx, *box = box
    label = f"{str(nb).zfill(2)}:{names.get(int(c_idx))}"
    ann.box_label(box, label, color=colors(c_idx, bgr=True))

image_with_bboxes = ann.result()

Les noms peuvent être utilisés à partir de model.names quand travailler avec les résultats de la détection.

Boîtes de délimitation orientées (OBB)

import cv2 as cv
import numpy as np

from ultralytics.utils.plotting import Annotator, colors

obb_names = {10: "small vehicle"}
obb_image = cv.imread("datasets/dota8/images/train/P1142__1024__0___824.jpg")
obb_boxes = np.array(
    [
        [0, 635, 560, 919, 719, 1087, 420, 803, 261],  # class-idx x1 y1 x2 y2 x3 y2 x4 y4
        [0, 331, 19, 493, 260, 776, 70, 613, -171],
        [9, 869, 161, 886, 147, 851, 101, 833, 115],
    ]
)
ann = Annotator(
    obb_image,
    line_width=None,  # default auto-size
    font_size=None,  # default auto-size
    font="Arial.ttf",  # must be ImageFont compatible
    pil=False,  # use PIL, otherwise uses OpenCV
)
for obb in obb_boxes:
    c_idx, *obb = obb
    obb = np.array(obb).reshape(-1, 4, 2).squeeze()
    label = f"{obb_names.get(int(c_idx))}"
    ann.box_label(
        obb,
        label,
        color=colors(c_idx, True),
        rotated=True,
    )

image_with_obb = ann.result()

Boîtes de délimitation Annotation de cercle Étiquette de cercle



Regarder : Guide détaillé des annotations de texte et de cercle avec Python Live Demos | Ultralytics Annotations 🚀

import cv2

from ultralytics import YOLO
from ultralytics.solutions.solutions import SolutionAnnotator
from ultralytics.utils.plotting import colors

model = YOLO("yolo11s.pt")
names = model.names
cap = cv2.VideoCapture("path/to/video.mp4")

w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
writer = cv2.VideoWriter("Ultralytics circle annotation.avi", cv2.VideoWriter_fourcc(*"MJPG"), fps, (w, h))

while True:
    ret, im0 = cap.read()
    if not ret:
        break

    annotator = SolutionAnnotator(im0)
    results = model.predict(im0)
    boxes = results[0].boxes.xyxy.cpu()
    clss = results[0].boxes.cls.cpu().tolist()

    for box, cls in zip(boxes, clss):
        annotator.circle_label(box, label=names[int(cls)], color=colors(cls, True))

    writer.write(im0)
    cv2.imshow("Ultralytics circle annotation", im0)

    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

writer.release()
cap.release()
cv2.destroyAllWindows()

Cadres de délimitation Annotation de texte Étiquette de texte

import cv2

from ultralytics import YOLO
from ultralytics.solutions.solutions import SolutionAnnotator
from ultralytics.utils.plotting import colors

model = YOLO("yolo11s.pt")
names = model.names
cap = cv2.VideoCapture("path/to/video.mp4")

w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
writer = cv2.VideoWriter("Ultralytics text annotation.avi", cv2.VideoWriter_fourcc(*"MJPG"), fps, (w, h))

while True:
    ret, im0 = cap.read()
    if not ret:
        break

    annotator = SolutionAnnotator(im0)
    results = model.predict(im0)
    boxes = results[0].boxes.xyxy.cpu()
    clss = results[0].boxes.cls.cpu().tolist()

    for box, cls in zip(boxes, clss):
        annotator.text_label(box, label=names[int(cls)], color=colors(cls, True))

    writer.write(im0)
    cv2.imshow("Ultralytics text annotation", im0)

    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

writer.release()
cap.release()
cv2.destroyAllWindows()

Voir le Annotator Page de référence pour plus d'informations.

Divers

Profilage du code

Vérifier la durée d'exécution/de traitement du code à l'aide de with ou en tant que décorateur.

from ultralytics.utils.ops import Profile

with Profile(device="cuda:0") as dt:
    pass  # operation to measure

print(dt)
# >>> "Elapsed time is 9.5367431640625e-07 s"

Ultralytics Formats pris en charge

Vous avez besoin d'utiliser par programme les formats d'image ou de vidéo supportés par Ultralytics? Utilisez ces constantes si nécessaire :

from ultralytics.data.utils import IMG_FORMATS, VID_FORMATS

print(IMG_FORMATS)
# {'tiff', 'pfm', 'bmp', 'mpo', 'dng', 'jpeg', 'png', 'webp', 'tif', 'jpg'}

print(VID_FORMATS)
# {'avi', 'mpg', 'wmv', 'mpeg', 'm4v', 'mov', 'mp4', 'asf', 'mkv', 'ts', 'gif', 'webm'}

Rendre divisible

Calculer le nombre entier le plus proche de x qui est divisible par y.

from ultralytics.utils.ops import make_divisible

make_divisible(7, 3)
# >>> 9
make_divisible(7, 2)
# >>> 8

FAQ

Quels sont les utilitaires inclus dans le package Ultralytics pour améliorer les flux de travail d'apprentissage automatique ?

Le package Ultralytics comprend des utilitaires conçus pour rationaliser et optimiser les flux de travail d'apprentissage automatique. Les principaux utilitaires comprennent l'auto-annotation pour l'étiquetage des ensembles de données, la conversion du format COCO au format YOLO avec convert_coco, la compression des images et l'auto-scission des ensembles de données. Ces outils réduisent le travail manuel, garantissent la cohérence et améliorent l'efficacité du traitement des données.

Comment puis-je utiliser Ultralytics pour étiqueter automatiquement mon ensemble de données ?

Si vous disposez d'un modèle de détection d'objets pré-entraîné Ultralytics YOLO , vous pouvez l'utiliser avec le modèle SAM pour auto-annoter votre jeu de données au format segmentation. Voici un exemple :

from ultralytics.data.annotator import auto_annotate

auto_annotate(
    data="path/to/new/data",
    det_model="yolo11n.pt",
    sam_model="mobile_sam.pt",
    device="cuda",
    output_dir="path/to/save_labels",
)

Pour plus de détails, consultez la section de référence auto_annotate.

Comment convertir les annotations du jeu de données COCO au format YOLO dans Ultralytics?

Pour convertir les annotations COCO JSON au format YOLO pour la détection d'objets, vous pouvez utiliser la fonction convert_coco utilitaire. Voici un exemple de code :

from ultralytics.data.converter import convert_coco

convert_coco(
    "../datasets/coco/annotations/",
    use_segments=False,
    use_keypoints=False,
    cls91to80=True,
)

Pour plus d'informations, consultez la page de référence convert_coco.

Quel est l'objectif de l'explorateur de données YOLO dans le paquet Ultralytics ?

Le YOLO Explorateur est un outil puissant introduit dans le 8.1.0 pour améliorer la compréhension des ensembles de données. Il vous permet d'utiliser des requêtes textuelles pour trouver des instances d'objets dans votre ensemble de données, ce qui facilite l'analyse et la gestion de vos données. Cet outil fournit des informations précieuses sur la composition et la distribution des ensembles de données, ce qui permet d'améliorer la formation et les performances des modèles.

Comment convertir les boîtes de délimitation en segments dans Ultralytics?

Pour convertir des données de boîtes de délimitation existantes (dans x y w h ) à des segments, vous pouvez utiliser le format yolo_bbox2segment fonction. Veillez à ce que vos fichiers soient organisés avec des répertoires distincts pour les images et les étiquettes.

from ultralytics.data.converter import yolo_bbox2segment

yolo_bbox2segment(
    im_dir="path/to/images",
    save_dir=None,  # saved to "labels-segment" in the images directory
    sam_model="sam_b.pt",
)

Pour plus d'informations, consultez la page de référenceyolo_bbox2segment.

📅C réé il y a 1 an ✏️ Mis à jour il y a 5 jours

Commentaires