OpenCV / Python – Primi passi

closeQuesto articolo è stato pubblicato 1 anno 4 mesi 28 giorni giorni fa quindi alcuni contenuti o informazioni presenti in esso potrebbero non essere più validi. Questo sito non è responsabile per eventuali errori causati da questo problema.

 

L’utilizzo di OpenCV rende veramente semplice l’elaborazione di immagini e video ed è ancora più semplice scoprire come python si presti bene a questo utilizzo. Prima di entrare nei dettagli voglio fare un piccolo riepilogo di quello che stiamo per utilizzare:

PRO

  • Semplicità d’uso;
  • Velocità di scrittura del codice;
  • Sorgenti efficienti;
  • Utilizzo nativo di strutture dati efficienti (numpy);
  • Si può fare di tutto!

CONTRO

  • Poca efficienza in operazioni computazionalmente onerose (risolvibile);

Come vediamo potremmo dire che l’unica pecca sia quella dell’efficienza computazionale di python che non è proprio il massimo, ma solitamente operazioni di questo tipo vengono richieste veramente pochissime volte dato che la maggior parte delle operazioni di image processing ha una funzione opencv apposita, ad ogni modo è possibile migliorare l’efficienza affiancando a python librerie per il calcolo scientifico (scipy) o librerie C-Friends come Cython. Chiudiamo qui questa piccola introduzione senza entrare troppo nei dettagli non essendo argomento di trattazione.

Strutture Dati

OpenCV comprende una moltitudine di strutture dati come ad esempio:

  • Mat
  • Point
  • InputArray
  • OutputArray
  • CvPoint
  • CvRect
  • CvSize
  • CvSeq
  • CvSet
  • ecc…

Fortunatamente in python tutto questo si riduce a pochissime strutture dati quali numpy.ndarray, liste e tuple!

Iniziamo

L’operazione principale è certamente l’importazione della libreria questa viene effettuata attraverso la seguente riga:

import cv2

Da tener presente che esiste anche una libreria chiamata cv, ma che non ci interessa (se non in casi speciali come per l’utilizzo di qualche costante) perchè si riferisce alla molto vecchia versione di opencv 2.1 ormai deprecata, mentre noi stiamo utilizzando il nuovo binding python.

Questo è tutto quello che ci interessa, non abbiamo bisogno di settare nulla perchè è già stato tutto fatto in fase di installazione.

Lavorare con le immagini – cenni

Facciamo una rapidissima introduzione a quelle che sono le operazioni di base nell’elaborazione di un immagine. Quando parliamo di immagini in un elaboratore elettronico ci stiamo riferendo ad una matrice bidimensionale in cui le coordinate di lunghezza e altezza identificano i pixel. Le operazioni vengono effettuate in modo puntuale, ovvero ogni operazione viene effettuata su un singolo elemento e non su tutta la matrice. Solitamente il filtraggio viene effettuato tramite una maschera/kernel che scorre sull’immagine e viene centrata nel pixel che vogliamo elaborare (più propiamente viene effettuata un’operazione di convoluzione [Wikipedia]). Il kernel solitamente ha una forma quadrata con dimensione dispari in modo da possedere un centro di simmetria che viene posto sul pixel in elaborazione. Spese queste poche parole per descrivere un immenso mondo quale quello dell’image processing (di cui ci occuperemo in altri articoli) passiamo a vedere come possiamo effettuare qualche operazione di base con OpenCV

OpenCV – Operazioni di base

Lettura immagine

Tutto quello che dobbiamo fare per iniziare è caricare un’immagine in un array, e capire come questo array viene costruito:

>>> image = cv2.imread('img.png')
>>> type(image)
<type 'numpy.ndarray'>
>>> image
array([[[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        ...,
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]]], dtype=uint8)
>>> image.shape
(187, 200, 3)

Notiamo che l’array viene costruito come un numpy.ndarray e visualizzandolo non otteniamo tutti i valori, ma solo una loro porzione, è interessante osservare che alla fine ci viene restituito il tipo di immagine che abbiamo inserito, in questo caso uint8 corrisponde ad un’immagine con valori interi senza segno ad 8 bit (256 livelli di intensità possibili nel range [0-255]). Il resto delle informazioni per capire la natura della nostra immagine ci è data da shape che ci restituisce come valori:

  • Il numero di righe (187)
  • Il numero di colonne (200)
  • Il numero di canali (3), il numero di informazioni utilizzate per caratterizzare il colore di un pixel, in questo caso stiamo caricando un’immagine RGB (o meglio BGR) e ogni canale corrisponde ad un determinato valore nello spazio colore nominato poco fa.

La funzione imread carica un’immagine utilizzando uno spazio colore BGR (Blu – Verde – Rosso) invece del convenzionale RGB (Rosso – Verde – Blu) assegnando un canale per ognuno dei tre colori principali con i valori ad 8 bit, ogni piano colore (B) (G) (R)  può avere massimo di 256 variazioni di intensità.

 Colore <-> Monocromatico

Una delle operazioni che più vengono utilizzate è quella di trasformare l’immagine a ‘colori’ in una a sole tonalità di grigio:

>>> image_grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
>>> image_grayscale.shape
(187, 200)

Verifichiamo subito che la shape ottenuta è leggermente divesa, manca l’ultimo termine, questo perchè un’immagine monocromatica è caratterizzata da un solo canale.

N.B. se provassimo a caricare immagini che ci appaiono monocromatiche con la imread questa sarà letta sempre come immagine BGR, quindi dobbiamo effettuare la conversione in ogni caso.

Separazione/Unione dei canali

Lavorare con immagini a colori significa operare sui canali che la compongono in modo separato, per far questo dobbiamo effettuare la separazione dei canali:

blue, green, red = cv2.split(image)

La funzione split ci permette di effettuare molto semplicemente questa operazione, in questo modo possiamo trattare i tre piani di colore separatamente, alla fine delle operazioni è d’obbligo unire questi tre piani:

image_merged = cv2.merge([blue, green, red])

Visualizzazione immagine

La visualizzazione è molto semplice basta dare un nome alla finestra che stiamo visualizzando:

cv2.imshow('immagine', image)
cv2.waitKey(0)

La seconda istruzione è fondamentale perchè imshow visualizza la finestra per un millisecondo, invece aggiungendo il waitKey() chiediamo alla finestra di rimanere visualizzata per il numero di millisecondi passati alla funzione come paramentro, nel caso ci fosse 0 allora aspetta la pressione di un tasto (in ogni caso questa funzione restituisce il tasto premuto dall’utente durante la visualizzazione)

Chiusura immagini

Solitamente alla terminazione del programma tutte le immagini visualizzate vengono automaticamente chiuse, ma se per qualche strano motivo o semplicemente per operazioni di “pulizia” vogliamo chiudere le finestre a mano possiamo invocare le seguenti function:

// Chiusura di una finestra
cv2.destroyWindow('immagine')
// Chiusura di tutte le finestre
cv2.destroyAllWindows()

Disegnare sulle immagini

Se abbiamo necessità di disegnare qualche forma geometrica sulle immagini possiamo affidarci alle comode funzioni di OpenCV:

>>> // Cerchio
>>> image_with_circle = image.copy()
>>> cv2.circle(img=image_with_circle, center=(image_with_circle.shape[1] / 2, image_with_circle.shape[0] / 2), radius=15, color=(255, 125, 255), thickness=5)
>>> image_with_line = image.copy()
>>> cv2.line(img=image_with_line, pt1=(0, 0), pt2=(199, 186), color=(125, 255, 255), thickness=5)
>>> image_with_rectangle = image.copy()
>>> cv2.rectangle(img=image_with_rectangle, pt1=(0, 0), pt2=(199, 186), color=(255, 255, 125), thickness=5)
>>> cv2.imshow('circle', image_with_circle)
>>> cv2.imshow('line', image_with_line)
>>> cv2.imshow('rectangle', image_with_rectangle)
>>> cv2.waitKey(0)
>>> cv2.destroyAllWindows()

Come potete notare quando ci si riferisce ad un punto bisogna inserire prima il valore sull’asse delle x e poi quello delle y (prima shape[1] e poi shape[0]) ecco il risultato:

Schermata 2013-05-30 alle 00.26.52Lettura/Visualizzazione video

I video non sono altro che immagini in movimento dunque per la loro elaborazione non facciamo altro che prendere ogni frame (immagine che compone il video) ed elaborarlo semplicemente come se fosse una immagine, abbiamo due possibilità:

  • Elaborare un video in acquisizione;
  • Elaborare un video salvato su disco;
>>> // Video in acquisizione
>>> video_acquisition = cv2.VideoCapture(0)
>>> // Video da file
>>> video_file = cv2.VideoCapture('video.mp4')
>>> ''' In alternativa possiamo fare
>>> video = VideoCapture()
>>> video.open('video.mp4') '''
>>> while video.grab():
>>>     _, frame = video_file.retrieve()
>>> // Elaborazione video ...

La prima istruzione utilizza un video ripreso dal primo dispositivo di acquisizione, in teoria si dovrebbe inserire il numero del device che sta acquisendo il video, ma se c’è un solo device basta inserire 0. La seconda istruzione invece legge un video da un file, i frame sono scansionati con il metodo grab() e recuperati con il metodo retrieve().

Se invece vogliamo visualizzare un video l’operazione è la stessa delle immagini, solo che dobbiamo utilizzare il waitKey per far scorrere il video a seconda dei suoi fps (frame per second):

cv2.imshow('video', frame)
cv2.waitKey(33)

In questo caso abbiamo supposto che il video girasse a 30fps, per calcolare semplicemente a quanto impostare questo timer basta dividere un secondo espresso in millisecondi (1000)  per fps e prendendo la parte intera (waitKey accetta solo interi):

  • 1000/fps => 1000/30 = 33
  • paolo lo bello

    Come posso installare le opencV su Mac OSX Maverics e su Linux Debian?

    • http://www.emanueldinardo.com/ Emanuel

      Ciao su Mac puoi semplificare le cose usando homebrew:

      Se non hai homebrew installato puoi farlo semplicemente nel terminale con questa riga:

      ruby -e “$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)”
      (Se non si vede il comando completo lo trovi infondo alla seguente pagina: http://brew.sh/)

      Poi i seguenti comandi sempre nel terminale:

      brew tap homebrew/science
      brew install opencv

      Su debian devo controllare se c’e` un metodo altrettanto veloce perche` solitamente effettuo l’installazione manuale.

      • paolo lo bello

        Se ho tempo creo un programma con interfaccia grafica e lo metto in rete perchè è uno scepmpio su un mac! ;)

        • http://www.emanueldinardo.com/ Emanuel

          Su è normale utilizzare il terminale su Mac ;)

        • http://www.emanueldinardo.com/ Emanuel

          Su è normale utilizzare il terminale su Mac ;)

        • http://www.emanueldinardo.com/ Emanuel

          Su è normale utilizzare il terminale su Mac ;)

  • paolo lo bello

    una domanda ma per creare il video tu hai scritto
    cv2.imshow(‘video’, video)
    cv2.waitKey(33)

    Ma il primo parametro ed il secondo parametro che servono?
    Quale è dei due il file e quale è la directory?

    • http://www.emanueldinardo.com/ Emanuel

      Il file viene aperto in cv2.VideoCapture(filename).
      cv2.imshow() serve a visualizzare il frame che hai estratto con video.retrieve, il primo parametro è il nome della finestra mentre il secondo è il frame, effettivamente non me ne sono accorto nella guida il secondo parametro dovrebbe essere frame come scritto precedentemente, correggo subito.
      cv2.waitKey(milliseconds) è il tempo che il frame deve essere visualizzato, se non lo metti non verrà mai visualizzato, in questo caso è 33 perchè ho supposto che il video avesse 30 frame al secondo.

      Ad ogni modo queste funzioni non salvano un video, ma lo visualizzano. per salvare il video devi utilizzare cv2.VideoWriter, ma non lo ho mai utilizzato, ti rimando alla documentazione:

      http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html?highlight=videowriter#VideoWriter

    • http://www.emanueldinardo.com/ Emanuel

      Il file viene aperto in cv2.VideoCapture(filename).
      cv2.imshow() serve a visualizzare il frame che hai estratto con video.retrieve, il primo parametro è il nome della finestra mentre il secondo è il frame, effettivamente non me ne sono accorto nella guida il secondo parametro dovrebbe essere frame come scritto precedentemente, correggo subito.
      cv2.waitKey(milliseconds) è il tempo che il frame deve essere visualizzato, se non lo metti non verrà mai visualizzato, in questo caso è 33 perchè ho supposto che il video avesse 30 frame al secondo.

      Ad ogni modo queste funzioni non salvano un video, ma lo visualizzano. per salvare il video devi utilizzare cv2.VideoWriter, ma non lo ho mai utilizzato, ti rimando alla documentazione:

      http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html?highlight=videowriter#VideoWriter

      • paolo lo bello

        Domanda perchè su linux non mi fa sentire l’audio del video?
        Sto usando debian su una raspberry arm.

        • http://www.emanueldinardo.com/ Emanuel

          opencv è una libreria per l’elaborazione video, penso che escluda l’audio, per farlo funzionare devi usare anche altre librerie