In questo articolo andremo ad utilizzare la libreria OpenCV in Python per realizzare un progetto che chiameremo "Tela virtuale". Tale applicazione consente di disegnare, virtualmente, sullo schermo del nostro computer utilizzando soltanto una webcam ed un pennarello. In realtà, invece del pennarello, può essere utilizzato qualsiasi oggetto di colore uniforme e che si distingua dallo sfondo. Questo perché l'applicazione sfrutta la tecnica del rilevamento del contorno basata su una maschera dell'oggetto utilizzato. Grazie ai dati in tempo reale della webcam, questa applicazione è in grado di tracciare un oggetto specifico, consentendo all'utente di disegnare sullo schermo nella posizione tracciata.
Introduzione
OpenCV (Open Source Computer Vision Library) è una libreria software open source per la visione artificiale e l'apprendimento automatico. OpenCV è stata progettata per essere multipiattaforma. La libreria è stata scritta in C rendendola portabile su quasi tutti i sistemi commerciali. Per la maggior parte, i nuovi algoritmi OpenCV sono ora sviluppati in C ++. Sono stati sviluppati anche wrapper per linguaggi come Python e Java per incoraggiarne l'adozione da parte di un pubblico sempre più ampio. OpenCV funziona sia su sistemi operativi desktop (Windows, Linux, MacOS, FreeBSD, OpenBSD) che su dispositivi mobili (Android, Maemo, iOS). In questo nostro progetto useremo il linguaggio Python e quindi la libreria OpenCV corrispondente. La libreria presenta più di 2500 algoritmi ottimizzati, sia per la visione artificiale che per l'apprendimento automatico. Questi algoritmi possono essere utilizzati per rilevare e riconoscere volti, identificare oggetti, classificare azioni umane nei video, tracciare i movimenti della telecamera, tracciare oggetti in movimento, estrarre modelli 3D di oggetti, unire immagini insieme per produrre un'immagine ad alta risoluzione di un'intera scena, trovare immagini simili in un database di immagini, rimuovere gli occhi rossi dalle immagini scattate con il flash, seguire i movimenti degli occhi, riconoscere lo scenario e stabilire marcatori per sovrapporlo alla realtà aumentata, etc.
Il progetto
Sebbene si tratti di un progetto non particolarmente complicato, prima di iniziare con la descrizione, vogliamo consigliare a chi fosse a digiuno degli argomenti che affronteremo nell'articolo alcune letture di supporto. In particolare:
- Per chi non avesse un'installazione Python o non sapesse come importare la libreria OpenCV, può seguire le istruzioni presenti in "Machine Learning con Python: introduzione al progetto".
- Per chi volesse saperne di più sulla Computer Vision, può leggere gli articoli di Cristiano Scavongelli, come "Computer Vision 1.01 – Filtraggi" e "Computer Vision 1.02 – Canny Edge Detection".
- Per quanto riguarda gli spazi di colore consigliamo "Tutti i colori dell’astrofotografia digitale".
Dopo questa premessa possiamo tuffarci nella descrizione del progetto. Le fasi principali per la realizzazione dell'applicazione sono le seguenti:
- Fase 1 - Trovare l'intervallo di valori nello spazio di colore HSV relativo all'oggetto usato come marker (il pennarello nel nostro caso).
- Fase 2 - Creare una maschera del marker utilizzando la gamma di valori nello spazio di colore HSV trovati nella Fase 1.
- Fase 3 - Utilizzare il rilevamento del contorno per tracciare la posizione del marker. In poche parole, ottenere le coordinate x, y del pennarello all'interno della "tela virtuale".
- Fase 4 - Disegnare un punto colorato alle coordinate x, y della posizione trovata.
Oltre ad implementare queste quattro fasi, nel nostro codice abbiamo inserito alcune funzionalità aggiuntive come la possibilità per l'utente di scegliere la forma, la dimensione ed il colore del pennello virtuale, grazie ad una finestra ospitante dei cursori di selezione.
Passiamo ora alla descrizione dell'implementazione delle quattro fasi principali.
Fase 1
Il tracciamento video è il processo di localizzazione di un oggetto in movimento nel tempo utilizzando una telecamera. Ha una varietà di usi, alcuni dei quali sono: interazione uomo-computer, sicurezza e sorveglianza, comunicazione e compressione video, realtà aumentata, controllo del traffico, imaging medico e montaggio video. Nel nostro progetto dobbiamo tracciare la posizione di un marker (pennarello). Per farlo cercheremo di filtrare il marker in uno spazio di colore appropriato in modo da separarlo da tutto ciò che si trova nella scena. Occorre cioè trovare un intervallo ristretto di valori in cui è compreso il colore del nostro marker.
Dal momento che stiamo cercando di rilevare il colore, convertiremo la nostra immagine dal formato RGB (o BGR in OpenCV) al formato HSV (tonalità, saturazione, valore) poiché è molto più facile manipolare i colori in tale modello. Lo script presente al seguente LINK ti consentirà di utilizzare i vari cursori presenti nella finestra "Track" per regolare i canali min e max relativi a tonalità (H), saturazione (S) e valore (V) dell'immagine. Si tratta di giocare con i cursori nella finestra affinché solo il marker (pennarello nel nostro caso) sia visibile, lasciando il resto dello schermo nero. In Figura 1 abbiamo riportato l'esempio del pennarello. Il marker scelto è un pennarello con il tappo verde e il corpo di altri colori. Quello che ci interessa è mettere in evidenza il tappo verde, poiché all'interno della scena quel colore non è presente e quindi risulta più facile da tracciare. I valori trovati ci serviranno nella Fase 2 per la creazione della maschera. I valori possono essere pensati come due array: uno contenente i valori dei cursori con _min [H_min, S_min, V_min], e l'altro contenente i valori dei cursori con _max [H_max, S_max, V_max].
Fase 2
Una volta trovati i valori che evidenziano il nostro marker, oscurando il resto della scena, possono essere utilizzati per creare la maschera. Per mezzo della funzione cv.inRange(img, bordoinf, bordosup) possiamo controllare se gli elementi di un'immagine (img) si trovano compresi tra gli elementi di due array (bordoinf, bordosup). In parole semplici, quello che faremo è convertire img nello spazio di colore HSV. Quindi filtreremo l'immagine per estrapolare solo i pixel il cui valore HSV rientra tra i valori espressi da bordoinf e bordosup. La funzione fornirà in uscita un'immagine delle stesse dimensioni di img ma con valore 1 nelle posizioni dei pixel estrapolati e 0 altrimenti. Nel nostro caso img è dato dai fotogrammi catturati dalla webcam e convertiti in formato HSV, e gli array di riferimento sono i valori trovati nella Fase 1, ovvero [H_min, S_min, V_min] e [H_max, S_max, V_max].
Fase 3
L'intera applicazione si basa fondamentalmente sul rilevamento dei contorni. Una volta ottenuta la maschera binaria del nostro marker, possiamo utilizzare il rilevamento del contorno per trovare la posizione del marker nella scena. Tracciato il marker, possiamo localizzare il punto effettivo da cui partirà la pittura sulla tela virtuale. La Figura 2 dimostra il tracciamento del marker tramite il rettangolo verde intorno al tappo del pennarello, e la valutazione della punta del pennello virtuale con il punto blu.
Queste operazioni sono eseguite dalla funzione getContorno() che verrà descritta più nel dettaglio proseguendo nell'articolo.
Fase 4
Una volta che il marker è stato tracciato si può cominciare a dipingere, grazie alla funzione drawOC(). Anche questa funzione verrà analizzata più nel dettaglio durante la descrizione del codice. Per rendere l'applicazione più divertente, sono state inserite delle funzioni che permettono di selezionare il colore, la forma e la dimensione del pennello virtuale attraverso una barra selezionatrice (Figura 3).
Il codice
Passiamo quindi all'analisi del codice in Python per implementare l'applicazione. Analizzeremo il codice suddividendolo in piccole porzioni.
import cv2 as cv import numpy as np
Come al solito, per prima cosa importiamo le librerie che utilizzeremo nello script. In particolare cv2 è il nome della libreria OpenCV per Python mentre numpy è la libreria Python che ci permette di manipolare array e matrici.
L=960 AR=4/3 H=int(L/AR) cap = cv.VideoCapture(0) cap.set(3,L) #id=3 larghezza cap.set(4,H) #id=4 altezza cap.set(10,150) #id=10 luminosità myPs = []
Nella seconda porzione di codice impostiamo alcuni parametri di configurazione come la larghezza (L), il rapporto d'aspetto (AR) e l'altezza (H) della tela virtuale su cui vogliamo dipingere. Adesso dobbiamo catturare lo streaming live con la webcam. OpenCV fornisce un'interfaccia molto semplice per farlo. Per acquisire un video, è necessario creare un oggetto VideoCapture(). Il suo argomento può essere l'indice del dispositivo o il nome di un file video. L'indice di un dispositivo è solo il numero per specificare quale telecamera selezionare. Passando semplicemente 0 o -1, richiediamo la webcam integrata. Per selezionare altre telecamere basta passare il valore 1, 2 e così via. Una volta creata un'istanza che abbiamo denominato cap, possiamo utilizzare il metodo set() per impostare alcuni parametri come larghezza, altezza e luminosità. Ogni parametro viene impostato, indicando il relativo identificativo (id) e il valore. Passiamo quindi a definire alcune funzioni che verranno poi chiamate nel ciclo principale.
###Colore dello spot### def on_trackbar(): B = cv.getTrackbarPos("Blu", "Palette") V = cv.getTrackbarPos("Verde", "Palette") R = cv.getTrackbarPos("Rosso", "Palette") spotColore =[B, V, R] return spotColore
Questa funzione serve per ottenere il colore scelto dall'utente attraverso la barra selezionatrice. Ogni cursore rappresenta uno dei tre colori primari, combinando insieme diverse quantità di rosso, verde e blu è possibile ottenere una grande varietà di colori.
###Dimensione dello spot### def on_trackbar2(): spotSize = cv.getTrackbarPos("Dimens.", "Palette") return spotSize ###Forma dello spot### def on_trackbar3(): spotShape= cv.getTrackbarPos("Forma", "Palette") return spotShape
Queste due funzioni servono per ottenere la dimensione della punta (spot) del pennello virtuale e la forma, scelte dall'utente attraverso la barra selezionatrice. Esse ritornano un valore che verrà poi passato alla funzione findColor().
###Impostazioni della Barra### cv.namedWindow("Palette") cv.createTrackbar("Blu", "Palette" , 0, 255, on_trackbar) cv.createTrackbar("Verde", "Palette" , 255, 255, on_trackbar) cv.createTrackbar("Rosso", "Palette" , 0, 255, on_trackbar) cv.createTrackbar("Dimens", "Palette" , 5, 20, on_trackbar) cv.createTrackbar("Forma", "Palette" , 0, 1, on_trackbar)
In questo pezzo di codice avvengono le impostazioni della barra selezionatrice. Si definisce il nome della finestra namedWindow("Palette") che ospiterà i vari cursori. Vengono quindi aggiunti i selezionatori createTrackbar() relativi ai tre colori primari, alla dimensione del pennello e alla sua forma. [...]
ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 2250 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici che potrai leggere in formato PDF per un anno. ABBONATI ORA, è semplice e sicuro.
Salve, intanto complimenti e grazie per l articolo davvero interessante. Vorrei capire una cosa, ma le finestre con cursori di regolazione nominate Track e Palette sono generate dal codice stesso allegato .py o sono strumenti ausiliari del raspbian ?
scusate me ne sono accorto adesso che vengono create le finestre da codice. Vorrei allora sapere se nella finestra PALETTE è possibile inserire e come una anteprima del colore RGB risultante dal mix delle 3 tonalità ?
Grazie.
Per avere un’anteprima del colore risultante segua le istruzioni seguenti.
All’interno del ##Main## subito dopo l’istruzione “while” inserire le seguenti linee di codice:
img = np.zeros((300,512,3), np.uint8)
img[:] = [on_trackbar()]
cv.imshow(“Palette”, img)
All’interno della Palette dovrebbe apparire un rettangolo colorato del colore risultante dal mix.
Grazie mille funziona tutto ma dovrebbe correggere nel codice le righe seguenti:
– cv.createTrackbar(“Dimens”, “Palette” , 5, 20, on_trackbar)
– cv.createTrackbar(“Forma”, “Palette” , 0, 1, on_trackbar)
poichè si riferiscono a funzioni diverse ossia a on_trackbar2 e on_trackbar3.
Direi che la prontezza dello script nel riconoscimento è istantanea e il disegno da parte dell’utente con un po di pratica diventa molto accurata e divertente.
Ultima domanda, esiste qualche modo per fare il porting di uno script simile su piattaforme embedded tipo ESP32? O meglio, il TinyML permette cose del genere? Che io sappia è molto limitato,,,
Grazie per la correzione.
Per quanto riguarda la sua domanda sul porting: al momento non mi risulta possibile un’applicazione simile utilizzando il tinyml, invece è fattibile l’utilizzo dell’ESP32-CAM come videocamera al posto della videocamera del computer.
Comunque sul microcontrollore verrebbe eseguita solo la parte di interfacciamento con il computer, mentre l’applicazione Python continuerebbe ad essere eseguita su PC.
Grazie, cercando sul web ho trovato qualcosa come il progetto OpenMV, http://www.openmv.io , per il quale hanno messo in commercio una scheda con cam ottimizzata per la CV, magari potete prendere spunto per un futuro articolo.