Salta el contingut

7. Entrenament de models

1. Entrenament del model

Entrenament

2. Fonamentació

YOLO (You Only Look Once) és una família de models de deep learning enfocats en la detecció d'objectes en imatges o vídeos. Es caracteritza per la seva velocitat i precisió en temps real, ja que el model processa tota la imatge d'una sola vegada per predir les regions on es troben els objectes i la seva classe.

2.1. Com s'entrena un model YOLO

  • Recopilació de dades: Es reuneixen imatges etiquetades amb les coordenades dels objectes i les seves categories. Les coordenades es representen com a caixes delimitadores (bounding boxes) que envolten els objectes d'interès.
  • Preparació de dades: S'estructuren les etiquetes en un format compatible (per exemple, COCO o YOLO).
  • Configuració de l'entrenament: Es tria l'arquitectura YOLO (p. ex. YOLOv5, YOLOv7, YOLOv8, etc.), la mida del lot (batch size) i els hiperparàmetres (èpoques, taxa d'aprenentatge, etc.).
  • Procés d'entrenament: Amb cada lot d'imatges, la xarxa ajusta els pesos per minimitzar la diferència entre les prediccions i les etiquetes reals.
  • Validació i ajust: S'avalua el rendiment en un conjunt de validació per ajustar hiperparàmetres i evitar sobreajustament.

2.2. COCO

El modelat COCO (sigles de Common Objects in Context) es refereix a un format estàndard utilitzat per estructurar les anotacions en datasets de visió per computador.

Originalment creat per Microsoft per al seu famós dataset, s'ha convertit en un estàndard de la indústria per entrenar models d'Intel·ligència Artificial en tasques com la detecció d'objectes, la segmentació d'instàncies i la detecció de punts clau (keypoints).

A diferència d'altres formats que creen un fitxer de text per cada imatge (com el format YOLO), el format COCO utilitza un únic fitxer JSON gegant que conté tota la informació de tot el dataset (entrenament, validació o test). Aquest fitxer JSON inclou informació sobre les imatges, les categories d'objectes, les anotacions (caixes delimitadores, segmentacions, etc.) i altres metadades.

3. Dataset (Estructura i etiquetes)

L'estructura del dataset es divideix en diverses carpetes i fitxers de configuració:

Text Only
dataset/
├─ data.yaml
├─ images/
│   ├─ train/
│   │   ├─ foto1.jpg
│   │   ├─ foto2.jpg
│   │   └─ ...
│   └─ val/
│       ├─ fotoX.jpg
│       └─ ...
└─ labels/
    ├─ train/
    │   ├─ foto1.txt
    │   ├─ foto2.txt
    │   └─ ...
    └─ val/
        ├─ fotoX.txt
        └─ ...
Text Only
dataset/
├── data.yaml
├── README.dataset.txt
├── README.roboflow.txt
├── test
│   ├── images
│   └── labels
├── train
│   ├── images
│   └── labels
└── valid
    ├── images
    └── labels

Com podem observar:

  • dataset/: Carpeta principal del dataset.
  • data.yaml: Fitxer de configuració que defineix les rutes d'accés a les imatges i les etiquetes, així com les classes d'objectes.
  • images/: Carpeta que conté les imatges d'entrenament i validació.
  • labels/: Carpeta que conté les etiquetes corresponents a les imatges, amb el mateix nom però amb extensió .txt.

Important

  • Per a cada fotoX.jpg a images/train, hi ha un corresponent fotoX.txt a labels/train que descriu totes les caixes dels objectes d'aquesta imatge (una línia per objecte).
  • La separacion primer en imatges i despres en etiquetes dependrà de les preferències de cada persona, però el més important és que les rutes d'accés a les imatges i etiquetes estiguin ben definides al data.yaml per a que el model pugui accedir-hi correctament durant l'entrenament.

3.1. Les etiquetes

Com ja hem vist tenim per un costat les imatges i per a cada imatge tenim un fitxer de text amb el mateix nom però amb extensió .txt que conté les etiquetes. Aquestes etiquetes es representen com a línies de text, on cada línia correspon a un objecte detectat a la imatge i segueix el format següent:

Text Only
<class_id> <x_center> <y_center> <width> <height>

on:

  • class_id: Identificador de la classe (objecte) en format enter (0, 1, 2...). Si només tens una classe (p. ex. "matricula"), serà sempre 0. Si per exemple tens matrícules, far esquerre i far dret, matrícula seria el 0, far esquerre seria l'1 i far dret el 2.
  • x_center: Coordenada X del centre de la caixa, normalitzada (dividida entre l'amplada de la imatge).
  • y_center: Coordenada Y del centre de la caixa, normalitzada (dividida entre l'altura de la imatge).
  • width: Amplada de la caixa, també normalitzada (dividida entre l'amplada de la imatge).
  • height: Altura de la caixa, normalitzada (dividida entre l'altura de la imatge).

Tots els valors estan normalitzats en el rang 0-1 respecte a les dimensions de la imatge.

Exemple d'una BB

Imagina que tens una imatge de 1280 px d'amplada per 720 px d'altura, i la caixa delimitadora que t'interessa va des de (x1=600, y1=200) fins a (x2=750, y2=300).

Valores reales (a la imatge):

  • Ample de la caixa = x2 - x1 = 150 px
  • Alt de la caixa = y2 - y1 = 100 px
  • Centre X = (x1 + x2)/2 = (600 + 750)/2 = 675 px
  • Centre Y = (y1 + y2)/2 = (200 + 300)/2 = 250 px

Valors normalizados (al dataset): - Ample normalitzat = 150 / 1280 ≈ 0.1172 - Alt normalitzat = 100 / 720 ≈ 0.1389 - Centre X normalitzat = 675 / 1280 ≈ 0.5273 - Centre Y normalitzat = 250 / 720 ≈ 0.3472

Si class_id=0, la línea en tu archivo .txt quedaría:

Text Only
0 0.5273 0.3472 0.1172 0.1389

Pensa que, quan YOLO s'entrena (o infereix), cercarà aquests fitxers .txt per saber on es troben els objectes (en el teu cas, les matrícules) i amb quina classe associar-los. Això és fonamental perquè el model aprengui de forma supervisada.

Després, en inferència o predicció, el model generarà les seves pròpies coordenades de sortida, també normalment en un sistema similar, però te les proporcionarà en escala absoluta o normalitzada segons la llibreria que estiguis utilitzant.

3.2. L'arxiu de configuració data.yaml

En el cas de YOLO (especialment en variants com YOLOv5 o YOLOv8), l'arxiu .yaml que es troba a la carpeta principal (o a l'arrel del dataset) serveix per descriure la configuració del conjunt de dades.

En general, aquest arxiu .yaml conté informació com:

  • Rutes dels dades:

    • La ruta o path on es localitzen les imatges d'entrenament (train).
    • La ruta on es localitzen les imatges de validació (val).
    • (Opcionalment) la ruta a les imatges de prova (test).
  • Quantitat i nom de les classes:

    • Un llistat amb els noms de cada classe (per exemple, ['gos', 'gat', 'persona']).
    • El nombre total de classes al dataset.
  • Paràmetres addicionals (opcional):

    • Rutes d'anotacions, si es gestionen en carpetes diferents.
    • Configuracions específiques per a l'entrenament (encara que aquest tipus d'informació a vegades va en un altre arxiu diferent).
    • Identificadors o camps extras que et permeten integrar el dataset en un flux de treball més complex.

Exemple d'un data.yaml:

YAML
train: ../train/images
val: ../valid/images
test: ../test/images

nc: 3
names: ['matricula','faro_izq','faro_der']

roboflow:
  workspace: vc
  project: matriculas-espanolas
  version: 1
  license: CC BY 4.0
  url: https://universe.roboflow.com/vc/matriculas-espanolas/dataset/1

3.3. Augmentació del dataset

Com sabem, un dels principals problemes a l'hora d'entrenar un model de detecció d'objectes és la manca de dades. Per això, una tècnica molt utilitzada és l'augmentació de dades, que consisteix en aplicar transformacions a les imatges existents per crear noves imatges sintètiques que ajudin a millorar la generalització del model.

3.3.1. Augmentación online (on-the-fly)

Aquest tipus d'augmentació es fa durant l'entrenament. La majoria d'implementacions recents de YOLO (p. ex., YOLOv5, YOLOv7, YOLOv8) ja inclouen una sèrie de transformacions aplicades automàticament. Aquesta augmentació es fa al moment d'entrenar, i cada vegada que es carrega un lot (batch) d'imatges, s'apliquen transformacions aleatòries a les imatges i les etiquetes corresponents. Nosaltres al disc tindrem sols el conjunt original d'imatges, però el model veurà versions augmentades d'aquestes imatges durant l'entrenament.

En molts repositoris es crea un fitxer de hiperparàmetres, sovint anomenat hyp.yaml o similar. Dins d'aquest fitxer, pots ajustar distintos paràmetres d'augmentació com:

  • hsv_h, hsv_s, hsv_v: Augments de to, saturació i valor en espai de color HSV.
  • degrees: Rotació.
  • translate: Translació (moviment de la imatge).
  • scale: Escalat.
  • shear: Efecte de cisalladura (shear).
  • flipud: Gir vertical.
  • fliplr: Gir horitzontal.

Per exemple, en YOLOv5 (dins del seu repositori oficial) existeix un fitxer hyp.yaml amb paràmetres predefinits. Pots editar-lo per ajustar la intensitat de cada transformació.

3.3.2. Exemple (simplificat) hyp.yaml

YAML
augments:
    degrees: 0.05
    translate: 0.1
    scale: 0.9
    shear: 0.0
    flipud: 0.0
    fliplr: 0.5
    hsv_h: 0.015
    hsv_s: 0.7
    hsv_v: 0.4

En entrenar el teu model YOLO aplicarà automàticament aquestes transformacions al carregar cada lot (batch) d'imatges.

La crida d'entrenament podria ser similar a aquesta, passant-ho com a paràmetres dins d'un programa Python. Ho veurem més endavant a la secció de codi, però aquí tens un exemple de com es podria configurar l'entrenament amb augmentació:

Python
results = model.train(
    # --- Paràmetres Bàsics ---
    data="data.yaml",
    epochs=50,
    batch=8,
    imgsz=640,
    name="yolov8n_aug_experiment",
    device=0,

    # --- Augmentació Geomètrica ---
    degrees=10.0,      # Rotació de la imatge (+/- 10 graus)
    translate=0.1,     # Translació (desplaçament) horitzontal/vertical (fracció)
    scale=0.5,         # Escalat de la imatge (+/- 50%)
    shear=0.0,         # Cisallament (inclinació)
    perspective=0.0005, # Perspectiva (0.0 - 0.001)
    flipud=0.0,        # Probabilitat de volteig vertical (amunt-avall)
    fliplr=0.5,        # Probabilitat de volteig horitzontal (esquerra-dreta)

    # --- Augmentació de Color/Llum (Espai HSV) ---
    hsv_h=0.015,       # Ajust del to (Hue)
    hsv_s=0.7,         # Ajust de la saturació
    hsv_v=0.4,         # Ajust del valor (brillantor)

    # --- Augmentacions Específiques de YOLO ---
    mosaic=1.0,        # Probabilitat d'usar Mosaic (juntar 4 imatges en una). Molt important!
    mixup=0.0,         # Probabilitat de Mixup (superposar dues imatges amb transparència)
    copy_paste=0.0,    # Útil per a segmentació (copiar objectes i enganxar-los en altres llocs)

    # --- Configuració extra ---
    close_mosaic=10    # Desactiva el mosaic les últimes 10 èpoques per afinar la precisió
)

3.3.3. Augmentació offline o directa

En aquest cas, les transformacions s'apliquen abans de l'entrenament, i es guarden com a noves imatges i etiquetes al disc. Això pot ser útil si vols tenir un control total sobre les imatges augmentades o si vols reutilitzar-les en diferents experiments sense haver de generar-les cada vegada.

Fer augmentació offline implica crear un nou conjunt d'imatges augmentades i les seves corresponents etiquetes. Per exemple, si tens 100 imatges originals i apliques 5 transformacions diferents a cada imatge, podries acabar amb 500 imatges augmentades (a més de les 100 originals).

Farem servir la llibreria albumentations per a fer augmentació offline. Aquesta llibreria és molt potent i flexible, i permet aplicar una gran varietat de transformacions tant geomètriques com de color.

Pots trobar exemples molt interessants a https://docs.ultralytics.com/integrations/albumentations/

El programa que et passes aumentar_dataset_yolo.py és un script que utilitza albumentations per augmentar un dataset de YOLO. Aquest script llegeix les imatges i les etiquetes del dataset original, aplica transformacions d'augmentació a les imatges i actualitza les etiquetes corresponents per reflectir els canvis.

4. Creació del dataset (Lego-StarWars)

Doncs be, ha arribat el pas més complex, que és la creació del dataset. Per a això, necessitem recollir imatges que continguin els objectes que volem detectar (en el nostre cas, figures de Lego de StarWars) i etiquetar-les amb les caixes delimitadores corresponents.

Per a crar-ho anem a fer servir una eina molt còmoda i popular, que és label-studio. Aquesta eina ens permet carregar les imatges, dibuixar les caixes delimitadores al voltant dels objectes d'interès i guardar les etiquetes en el format que necessitem per a entrenar el nostre model YOLO.

L'eina està integrada en una web app, que podem instal·lar en local:

Bash
pip install label-studio

pera arrancar-la, només cal executar:

Bash
label-studio start

i accedir a http://localhost:8080 des del navegador. Un cop dins, podem crear un nou projecte, carregar les imatges i començar a etiquetar.

Si no volem instal·lar res al nostre entor, podem fer ús de docker per a executar label-studio en un contenidor. Per a això, podem utilitzar la següent comanda:

Bash
1
2
3
4
5
docker run 
    -it -p 8080:8080 
    -v $(pwd)/dataset:/label-studio/data 
    --name label-studio 
    heartexlabs/label-studio:latest

Això posa a escoltar el servidor al port 8080 i monta la carpeta dataset del nostre home (on executem docker) del disc dins del contenidor, de manera que les imatges que carreguem a label-studio es guardaran directament a la carpeta dataset del nostre projecte i no les perdrem quan tanquem el contenidor.

Accedirem al servidor de label-studio a http://localhost:8080. Ens hem de registrar, però es un login local, que no es connecta a cap servidor exter.

alt text

Un cop dins, crearem un nou projecte.

alt text

A la següent pestanya carregarem les imatges que volem etiquetar. Podem carregar-les des del disc local o, si estem executant label-studio amb docker, podem carregar-les directament a la carpeta dataset del nostre projecte i després seleccionar-les des de l'eina. Tambe ens permet carregar-les des d'una URL al nuvol, com S3 o Google Drive.

alt text

alt text

La última part de l'assistent és definir per a que és el dataset que estem creant. En el nostre cas, seleccionarem Object Detection with Bounding Boxes.

alt text

Eliminem els labels que venen per defecte i creem un nou label amb el nom de la classe que volem detectar. Els escriurem un en cada línea. Farem clic en add.

Fent clic a les etiquetes podem canviar el color.

Finalment, fem clic a save per a crear el projecte i començar a etiquetar les imatges.

alt text

alt text

Ara ja podem començar a etiquetar les imatges. Per a això, farem doble clic a una imatge i dibuixarem una caixa delimitadora al voltant de cada objecte que volem detectar:

  • Seleccionem la etiqueta
  • Dibuixem el BB de la classe que volem detectar
  • Fem clic a submit per a guardar l'etiqueta

Repetirem el procés per a cada imatge.

alt text alt text

Un cop hem etiquetat tot hem de procedir a generar el dataset en el format que necessitem per a entrenar el nostre model YOLO. Per a això, farem clic a Export i seleccionarem el format YOLO with images. Això generarà un fitxer .zip que conté les imatges i les etiquetes en el format correcte i el arxiu YAML de configuració.

alt text

Ja tenim les nostres dades etiquetades i preparades per a entrenar el nostre model YOLO!

5. Entrenament

Ara ha arribat el moment de entrenar el model. Aquest pas és molt senzill si hem definit el arxiu yaml d'abans, el qual completarem. Anem a veure un exemple al qual descriurem alguna de les parts:

YAML
# Modelo
model_type: "ypolov8s"                # Tipo de modelo a utilizar (puede ser yolov5s, yolov5m, etc.)

# yolo_config.yaml
batch_size: 16
img_size: 1920
epochs: 50
learning_rate: 0.001
momentum: 0.937
weight_decay: 0.0005
pretrained_weights: "yolov8s.pt"   # Modelo preentrenado para continuar el entrenamiento

# Datos
train: "../data/train"            # Directorio donde están las imágenes de entrenamiento
val: "../data/val"         # Directorio donde están las imágenes de validación


# Clases
nc: 7       # number of classes
names:
  0: 'Chewbacca'
  1: 'Leia'
  2: 'Luke'
  3: 'ObiWan'
  4: 'Solo'
  5: 'StormTrooper'
  6: 'Vader'


# add
name: "yolo_sw"
  • model_type: Aquest paràmetre especifica el tipus de model de YOLO que s'utilitzarà. Els models de YOLO es presenten en diverses variants amb diferents mides i capacitats:
  • yolo_vX_[mida]-opt:
    • vx és la versió: 8, 9, 11, etc.
    • mida és una lletra que defineix la mida del model preentrenat. Com més gran sigui, millor serà el resultat, però també augmentarà el cost d'entrenament per la quantitat d'hiperparàmetres. Valors possibles (la inicial): nano, small, medium, large i xtraLarge.
    • opt és per escollir el tipus de detecció que es farà amb el model.

Yolo-Versions

  • batch_size: És el nombre d'imatges que es processen en cada pas d'entrenament abans d'actualitzar els pesos del model. Un valor de 16 significa que el model processarà 16 imatges alhora. Un batch_size més gran pot accelerar l'entrenament, però també requereix més memòria GPU, mentre que un valor petit farà que el model sigui més estable però més lent.
  • img_size: La mida de la imatge a la qual es redimensionaran totes les imatges d'entrada abans de ser alimentades al model. En aquest cas. Una resolució més gran permet que el model capti més detalls, però també requereix més memòria i pot ser més lent.
  • epochs: És el nombre total de vegades que el model passarà per tot el conjunt de dades durant l'entrenament. Si el model no millora després de diverses èpoques, pots aturar l'entrenament per evitar el sobreajust.
  • learning_rate: La taxa d'aprenentatge és la mida del pas que l'optimitzador fa per ajustar els pesos del model en cada iteració. Un valor de 0.001 significa que l'optimitzador ajustarà els pesos amb un pas petit en cada iteració. Un valor més alt pot fer que el model aprengui més ràpid, però pot ser menys estable. Un valor més baix pot fer que el model aprengui de manera més estable, però més lentament.
  • momentum: És un paràmetre que ajuda a accelerar l'entrenament, especialment en les primeres etapes. Un valor de 0.937 és força comú i millora la convergència de l'optimitzador, ajudant que el model no quedi atrapat en mínims locals.
  • weight_decay: És una tècnica de regularització utilitzada per prevenir el sobreajust, penalitzant els pesos grans durant l'entrenament. Un valor de 0.0005 és força baix i és una regularització moderada. Un valor més alt de weight_decay pot reduir el risc de sobreajust, però també pot evitar que el model aprengui massa dels dades.
  • pretrained_weights: Especifica si s'utilitzarà un model preentrenat. Utilitzar pesos preentrenats accelera el procés d'entrenament i millora la precisió del model, especialment si el conjunt de dades és petit. Els models preentrenats s'entrenen amb grans datasets com COCO, cosa que ajuda que el model tingui una bona base per aprendre a detectar objectes.
  • train: Especifica la ruta al directori que conté les imatges d'entrenament. Les imatges d'entrenament s'utilitzen per entrenar el model a reconèixer objectes.
  • val: Especifica la ruta al directori que conté les imatges de validació. Les imatges de validació s'utilitzen per avaluar el model durant l'entrenament, assegurant-se que no estigui sobreajustant les dades d'entrenament.
  • nc: Aquest paràmetre especifica el nombre de classes en el conjunt de dades. En aquest cas, 7 classes. És important que el nombre de classes coincideixi amb el nombre de classes definides als fitxers d'etiquetes.
  • names: Aquest és un diccionari que mapeja un índex de classe (començant des de 0) al seu nom corresponent. Pot ser una llista numerada com l'exemple o algunes versions permeten un array:
  • names: ["Chewbacca", "Leia", "Luke", "ObiWan", "Solo", "StormTrooper", "Vader"]

Al següent exemple podem veure com queda un programa per entrenar el model

Entrenament del model

Python
1
2
3
4
5
6
7
from ultralytics import YOLO
model = YOLO("models/yolov8s.pt")   

results = model.train(
    data="yolo_config.yaml",       # arxiu de configuració vist abans
    device='cpu'                   # '0' per a GPU; 'cpu' per a CPU
)

Com podem veure:

  • Es fa servir el model yolov8s. La primera execució es descarrega desde ultralycs i es guarda en la carpeta model
  • Posteriorment comença l'entrenament i va mostrant per pantalla les iteracions i les èpoques

Ademès

Altres arguments interesants son:

  • save_period=n per a guardar el estat dels hiperparàmetres cada n èpoques i
  • resume=True per a reprendre l'entrenament. L'entrenament pot parar-se en força bruta (CTRL+C, CTRL+Z).

També comentar que els models parcials i finals es guarden dins d'una carpeta que es crea anomenada runs i on es creen train_n on n indica el número d'execucions o llançaments que fem del model.

Dins d'aquest run trobarem al final un model anomenat best.pt amb la millor versió dels hiperparàmetres

Bash
Logging results to runs/detect/train5
Starting training for 100 epochs...

      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
      1/100         0G      2.471      9.334      2.092          3        640: 100%|██████████| 2/2 [01:59<00:00, 59.93s/it] 
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100%|██████████| 1/1 [00:10<00:00, 10.33s/it]
                   all          4         14     0.0158      0.667     0.0967     0.0237


      Epoch    GPU_mem   box_loss   cls_loss   dfl_loss  Instances       Size
      2/100         0G      1.762      5.048      1.539         13        640: 100%|██████████| 2/2 [01:26<00:00, 43.25s/it]
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95): 100%|██████████| 1/1 [00:02<00:00,  2.96s/it]
                   all          4         14      0.373      0.262      0.098     0.0352

Per guardar el millor model, per example de l'execució numero 5 (train5):

Python
1
2
3
import shutil

shutil.copy('runs/detect/train5/weights/best.pt', 'models/best.pt')

Aquest model és el que farem servir a continuació per a les prediccions

Tota la informació la pots trobar a https://docs.ultralytics.com/modes/train/

6. Predicció del model

Finalment ha arribat el moment de probar el model, a veure si funciona de manera adequada.

Els passos a seguir son:

  1. Carregar el model entrenat
  2. Indicar quina imatge volem processar
  3. Fer la predicció
  4. Analitzar els resultats
  5. Mostrar els resultats

Mirem-ho pas a pas:

Python
model_Path='runs/detect/train5/weights/best.pt'
model=YOLO(model_Path)
source_image='data/val/images/ba2b2dbd-captura_20250318-161749.png'

images=[
    'data/val/images/ba2b2dbd-captura_20250318-161749.png',
    'data/val/images/be627c47-captura_20250318-161845.png'
]

results = model.predict(
    source=images,
    conf=0.25,    # Umbral de confianza
    save=False,   # No que dibuje YOLO; lo haremos manualmente con OpenCV
    verbose=False
)

Com podem veure la carrega del model i la predicció no te complicació. Fixar-se que podem fer la predicció per a una o diverses imatges, i el resultat dependrà d'això. La variable result contindrà un llistat amb variables de tipus Results, una per imatge que fem predicció. Tens la informació completa a la seua web en aquest link

Aquesta variable depenent del model que hem entrenat tindrà uns resultats o altres. En el cas que ens segueix, que és la detecció d'objectes, ens interessen els elements:

  • names: diccionari amb les classes de objectes que detecta el nostre model. Coincideix amb les classes que hem entrenat.
  • boxes: un objecte que conté els elements detectats, que son de tipus Boxes
  • len(Results): ens dona quants elements s'han detectat.

Per a cada element de tipus boxes trobarem, per avaluar el resultat:

  • cls: la id de la classe del objecte detectat
  • conf: la confiança en tant per 1 del objecte detectat
  • xyxy: les coordenades dels extrems de la BB, en valors reals
  • xyxyn: igual que l'anterior però normalitzat segons el tamany de la imatge d'entrada
  • xywh: la coordenada del centre de la caixa, ample i llarg
  • xywhn: el mateix, però normalitzat al tamany de la imatge d'entrada

::: note

Text Only
1
Tots aquests valors de retorn són tensors (matrius), per tant per accedir al valor _real_ hem d'agafar el primer element com si fos un array, `cls[0]`, `conf[0]`, etc

Pintem la informació sobre la imatge

Python
import cv2 
import matplotlib.pyplot as plt

orig_img=cv2.imread(source_image)
orig_img=cv2.cvtColor(orig_img, cv2.COLOR_BGR2RGB)

predictions = results[0]
print(len(predictions))
for box in predictions.boxes:
    x1, y1, x2, y2 = box.xyxy[0]

    # Convertir a int para dibujar con OpenCV
    x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)

    # ID de la clase detectada
    cls_id = int(box.cls[0])

    # Confianza de la detección
    conf = float(box.conf[0])

    # Nombre de la clase (basado en model.names)
    class_name = model.names[cls_id] if model.names and cls_id < len(model.names) else f"cls_{cls_id}"

    print(f"Clase: {class_name}, confianza: {conf:.2f}")
    # Dibujo del rectángulo
    color = (0, 255, 0)  # Verde
    thickness = 2
    cv2.rectangle(orig_img, (x1, y1), (x2, y2), color, thickness)

    # Texto (etiqueta + confianza)
    label = f"{class_name} {conf:.2f}"
    # Para dibujar el fondo del texto (opcional, para que sea legible)
    (tw, th), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
    # Caja para el texto encima del bounding box
    cv2.rectangle(orig_img, (x1, y1 - th - baseline), (x1 + tw, y1), color, -1)
    cv2.putText(orig_img, label, (x1, y1 - baseline),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)

plt.figure(figsize=(16, 9))
plt.imshow(orig_img)
plt.show()