Data Science Retrospettiva Test delle prime versioni di YOLO
Review of YOLO's early versions' testing in Data Science.
Viaggiamo indietro di 8 anni nel tempo

Il mondo della data science è in costante cambiamento. Spesso, non riusciamo a vedere questi cambiamenti solo perché avvengono lentamente, ma dopo un po’ di tempo, è facile guardarsi indietro e vedere che il panorama è diventato drasticamente diverso. Strumenti e librerie, che erano al top del progresso solo 10 anni fa, possono essere completamente dimenticati oggi.
YOLO (You Only Look Once) è una popolare libreria di rilevamento di oggetti. La sua prima versione è stata rilasciata molto tempo fa, nel 2015. YOLO funzionava velocemente, forniva buoni risultati, e i modelli pre-addestrati erano pubblicamente disponibili. Il modello è diventato rapidamente popolare, e il progetto è ancora attivamente migliorato oggi. Questo ci dà l’opportunità di vedere come gli strumenti e le librerie di data science sono evoluti nel corso degli anni. In questo articolo, testerò diverse versioni di YOLO, dalla prima versione V1 fino all’ultima V8.
Per ulteriori test, userò un’immagine dal tutorial di YOLO di OpenCV:

I lettori che vorrebbero riprodurre i risultati da soli possono aprire quel link e scaricare l’immagine originale.
- Come creare bei grafici di distribuzione dell’età con Seaborn e Matplotlib (inclusa l’animazione)
- Il MIT-Pillar AI Collective annuncia i primi beneficiari di finanziamenti seminativi.
- Sconfiggere i video Deepfake
Cominciamo.
YOLO V1..V3
Il primo paper, “You Only Look Once: Unified, Real-Time Object Detection”, su YOLO è stato rilasciato nel 2015. E sorprendentemente, YOLO v1 è ancora disponibile per il download. Come ha scritto Mr. Redmon, uno degli autori del paper originale, sta mantenendo questa versione “per scopi storici”, il che è veramente bello. Ma possiamo eseguirlo ancora oggi? Il modello è distribuito sotto forma di due file. Il file di configurazione ” yolo.cfg
” contiene i dettagli sul modello di rete neurale:
[net]batch=1height=448width=448channels=3momentum=0.9decay=0.0005...[convolutional]batch_normalize=1filters=64size=7stride=2pad=1activation=leaky
E il secondo file ” yolov1.weights
“, come suggerisce il nome, contiene i pesi del modello pre-addestrato.
Questo tipo di formato non proviene da PyTorch o Keras. Si è scoperto che il modello è stato creato con Darknet, un framework di rete neurale open-source scritto in C. Questo progetto è ancora disponibile su GitHub, ma sembra abbandonato. Al momento della scrittura di questo articolo, ci sono 164 richieste di pull e 1794 problemi aperti; gli ultimi commit risalgono al 2018, e successivamente solo il README.md è stato modificato (beh, probabilmente è così che la morte del progetto appare nel mondo digitale moderno).
Il progetto Darknet originale è abbandonato; questa è una brutta notizia. La buona notizia è che il metodo readNetFromDarknet è ancora disponibile in OpenCV, ed è presente anche nelle ultime versioni di OpenCV. Quindi, possiamo facilmente provare a caricare il modello originale YOLO v1 utilizzando l’ambiente Python moderno:
import cv2model = cv2.dnn.readNetFromDarknet("yolo.cfg", "yolov1.weights")
Purtroppo, non ha funzionato; ho ricevuto solo un errore:
darknet_io.cpp:902: error: (-212:Parsing error) Unknown layer type: local in function 'ReadDarknetFromCfgStream'
Si è scoperto che “yolo.cfg” ha un layer chiamato “local”, che non è supportato da OpenCV, e non so se c’è un modo per aggirarlo. In ogni caso, la configurazione di YOLO v2 non ha più questo layer, e questo modello può essere caricato con successo in OpenCV:
import cv2model = cv2.dnn.readNetFromDarknet("yolov2.cfg", "yolov2.weights")
Utilizzare il modello non è così facile come potremmo pensare. Prima di tutto, dobbiamo trovare gli output del modello:
ln = model.getLayerNames()output_layers = [ln[i - 1] per i in model.getUnconnectedOutLayers()]
Poi dobbiamo caricare l’immagine e convertirla in formato binario, che il modello può comprendere:
img = cv2.imread('horse.jpg')H, W = img.shape[:2]blob = cv2.dnn.blobFromImage(img, 1/255.0, (608, 608), swapRB=True, crop=False)
Infine, possiamo eseguire la propagazione in avanti. Un metodo “forward” eseguirà i calcoli e restituirà gli output richiesti:
model.setInput(blob)outputs = model.forward(output_layers)
Eseguire la propagazione in avanti è semplice, ma l’analisi degli output può essere un po’ complicata. Il modello produce vettori di feature a 85 dimensioni come output, dove le prime 4 cifre rappresentano i rettangoli degli oggetti, la quinta cifra è una probabilità di presenza di un oggetto e le ultime 80 cifre contengono le informazioni di probabilità per le 80 categorie su cui il modello è stato addestrato. Con queste informazioni, possiamo disegnare le etichette sull’immagine originale:
soglia = 0,5scatole, confidenze, id_classi = [], [], []# Ottenere tutte le scatole e le etichettefor output in outputs: for detection in output: punteggi = detection[5:] id_classe = np.argmax(punteggi) confidenza = punteggi[id_classe] if confidenza > soglia: centro_x, centro_y = int(detection[0] * W), int(detection[1] * H) larghezza, altezza = int(detection[2] * W), int(detection[3] * H) sinistra = centro_x - larghezza//2 alto = centro_y - altezza//2 scatole.append([sinistra, alto, larghezza, altezza]) id_classi.append(id_classe) confidenze.append(float(confidenza))# Combina le scatole insieme utilizzando la soppressione non massimaindici = cv2.dnn.NMSBoxes(scatole, confidenze, 0.5, 0.4)# Tutte le classi COCOclassi = "persona;bicicletta;auto;moto;aereo;bus;treno;camion;barca;semaforo;idrante;cartello stop;parcometro;panca;uccello;" \ "gatto;cane;cavallo;pecora;mucca;elefante;orso;zebra;giraffa;zaino;ombrello;borsa;cravatta;valigia;frisbee;sci;snowboard;palla sportiva;aquilone;" \ "mazza da baseball;guanto da baseball;skateboard;surf;racchetta da tennis;bottiglia;bicchiere di vino;tazza;forchetta;coltello;cucchiaio;ciotola;banana;mela;panino;" \ "arancia;broccoli;carota;hot dog;pizza;ciambella;torta;sedia;divano;pianta in vaso;letto;tavolo da pranzo;water;monitor tv;laptop;topo;telecomando;tastiera;" \ "cellulare;microonde;forno;tostapane;lavandino;frigorifero;libro;orologio;vaso;forbici;orsetto di peluche;asciugacapelli;spazzolino da denti".split(";")# Disegna i rettangoli sull'immaginecolori = np.random.randint(0, 255, size=(len(classi), 3), dtype='uint8')for i in indici.flatten(): x, y, w, h = scatole[i] colore = [int(c) for c in colori[id_classi[i]]] cv2.rectangle(img, (x, y), (x + w, y + h), colore, 2) testo = f"{classi[id_classi[i]]}: {confidenze[i]:.2f}" cv2.putText(img, testo, (x + 2, y - 6), cv2.FONT_HERSHEY_COMPLEX, 0.5, colore, 1)# Mostracv2.imshow('window', img)cv2.waitKey(0)cv2.destroyAllWindows()
Qui uso np.argmax per trovare l’ID della classe con la massima probabilità. Il modello YOLO è stato addestrato utilizzando il dataset COCO (Common Objects in Context, Creative Commons Attribution 4.0 License) e, per motivi di semplicità, ho inserito tutti i 80 nomi delle etichette direttamente nel codice. Ho anche utilizzato il metodo OpenCV NMSBoxes per combinare i rettangoli incorporati insieme.
Il risultato finale appare così:

Siamo riusciti ad eseguire con successo un modello rilasciato nel 2016 in un ambiente moderno!
La versione successiva, YOLO v3, è stata rilasciata due anni dopo, nel 2018, e possiamo eseguirla usando lo stesso codice (i file di configurazione e pesi sono disponibili online). Come gli autori hanno scritto nella relazione , il nuovo modello è più preciso e possiamo verificarlo facilmente:

Effettivamente, un modello V3 è stato in grado di trovare più oggetti sulla stessa immagine. I lettori interessati ai dettagli tecnici possono leggere questo articolo TDS scritto nel 2018.
YOLO V5..V7
Come possiamo vedere, il modello caricato con il metodo readNetFromDarknet funziona, ma il codice richiesto è abbastanza “a basso livello” e ingombrante. Gli sviluppatori di OpenCV hanno deciso di semplificare la vita e nel 2019 è stata aggiunta alla versione 4.1.2 una nuova classe DetectionModel. Possiamo caricare il modello YOLO in questo modo; la logica generale rimane la stessa, ma la quantità di codice richiesto è molto più piccola. Il modello restituisce direttamente ID di classe, valori di confidenza e rettangoli in una sola chiamata di metodo:
import cv2model = cv2.dnn_DetectionModel("yolov7.cfg", "yolov7.weights")model.setInputParams(size=(640, 640), scale=1/255, mean=(127.5, 127.5, 127.5), swapRB=True)class_ids, confidences, boxes = model.detect(img, confThreshold=0.5)# Combina i rettangoli insieme usando la soppressione non massima.indices = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4)# Tutte le classi COCOclasses = "persona;bicicletta;auto;moto;aereo;bus;treno;camion;nave;semaforo;idrante;stop;parcometro;panca;uccello;" \ "gatto;cane;cavallo;pecora;mucca;elefante;orso;zebra;giraffa;zaino;ombrello;borsa;cravatta;valigia;frisbee;sci;snowboard;palla sportiva;aquilone;" \ "mazza da baseball;guanto da baseball;skateboard;surf;racchetta da tennis;bottiglia;calice da vino;tazza;forchetta;coltello;cucchiaio;ciotola;banana;mela;panino;" \ "arancia;broccoli;carota;cane caldo;pizza;ciambella;torta;sedia;divano;pianta in vaso;letto;tavolo da pranzo;water;monitor tv;laptop;topo;telecomando;tastiera;" \ "cellulare;forno a microonde;forno;tostapane;lavandino;frigorifero;libro;orologio;vaso;forbici;orsetto di peluche;asciugacapelli;spazzolino da denti".split(";")# Disegna i rettangoli sull'immaginecolors = np.random.randint(0, 255, size=(len(classes), 3), dtype='uint8')for i in indices.flatten(): x, y, w, h = boxes[i] color = [int(c) for c in colors[class_ids[i]]] cv2.rectangle(img, (x, y), (x + w, y + h), color, 2) text = f"{classes[class_ids[i]]}: {confidences[i]:.2f}" cv2.putText(img, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)# Mostra l'immaginecv2.imshow('window', img)cv2.waitKey(0)cv2.destroyAllWindows()
Come possiamo vedere, non è più necessario tutto il codice a basso livello necessario per estrarre i rettangoli e i valori di confidenza dall’output del modello.
Il risultato dell’esecuzione di YOLO v7 è, in generale, lo stesso, ma il rettangolo intorno al cavallo appare più preciso:

YOLO V8
La versione 8 è stata rilasciata nel 2023, quindi al momento della stesura di questo testo non la si può considerare “retro”. Ma solo per confrontare i risultati, vediamo il codice necessario per eseguire YOLO oggi:
from ultralytics import YOLOimport supervision as svmodel = YOLO('yolov8m.pt')results = model.predict(source=img, save=False, save_txt=False, verbose=False)detections = sv.Detections.from_yolov8(results[0])# Creare una lista di etichettelabels = []for ind, class_id in enumerate(detections.class_id): labels.append(f"{model.model.names[class_id]}: {detections.confidence[ind]:.2f}")# Disegnare i rettangoli sull'immaginebox_annotator = sv.BoxAnnotator(thickness=2, text_thickness=1, text_scale=0.4)box_annotator.annotate(scene=img, detections=detections, labels=labels)# Mostrarecv2.imshow('finestra', img)cv2.waitKey(0)cv2.destroyAllWindows()
Come possiamo vedere, il codice è diventato ancora più compatto. Non dobbiamo preoccuparci dei nomi delle etichette del dataset (il modello fornisce una proprietà “names”) o di come disegnare i rettangoli e le etichette sull’immagine (c’è una classe BoxAnnotator speciale per questo). Non dobbiamo nemmeno più scaricare i pesi del modello; la libreria lo farà automaticamente per noi. Rispetto al 2016, il programma del 2023 è passato da circa 50 a circa 5 righe di codice! È ovviamente un miglioramento piacevole, e i moderni sviluppatori non hanno più bisogno di conoscere la propagazione in avanti o il formato del livello di output. Il modello funziona semplicemente come una scatola nera con un po’ di “magia” all’interno. È buono o cattivo? Non lo so 🙂
Per quanto riguarda il risultato stesso, è più o meno simile:

Il modello funziona bene, e almeno sul mio computer, la velocità di calcolo è migliorata rispetto a v7, forse grazie all’utilizzo migliore della GPU.
Conclusioni
In questo articolo, siamo stati in grado di testare quasi tutti i modelli YOLO, realizzati dal 2016 al 2023. A prima vista, tentare di eseguire un modello rilasciato quasi 10 anni fa può sembrare una perdita di tempo. Ma per me, ho imparato molto facendo questi test:
- È stato interessante vedere come gli strumenti e le librerie di data science popolari sono evoluti nel corso degli anni. La tendenza a passare dal codice a basso livello a metodi ad alto livello, che fanno tutto e persino scaricano il modello preaddestrato prima dell’esecuzione (almeno per ora, senza chiedere ancora una chiave di abbonamento, ma chissà cosa succederà tra 10 anni?), sembra chiara. È buono o cattivo? Questa è una domanda interessante e aperta.
- Era importante sapere che OpenCV è “nativamente” in grado di eseguire modelli di deep learning. Ciò consente di utilizzare modelli di reti neurali non solo in grandi framework come PyTorch o Keras, ma anche in applicazioni in puro Python o persino C++. Non ogni applicazione viene eseguita in un cloud con risorse virtualmente illimitate. Il mercato IoT è in crescita, e questo è particolarmente importante per l’esecuzione di reti neurali su dispositivi a basso consumo energetico come robot, telecamere di sorveglianza o smart doorbell.
Nel prossimo articolo, lo testerò in modo più dettagliato e mostrerò come YOLO v8 funziona su una scheda a bassa potenza come un Raspberry Pi, e saremo in grado di testare sia le versioni Python che C++. Restate sintonizzati.
Se avete apprezzato questa storia, sentitevi liberi di iscrivervi a Nisoo, e riceverete notifiche quando i miei nuovi articoli saranno pubblicati, nonché l’accesso completo a migliaia di storie di altri autori.