La cartoonization è una procedura grafica che consente di modificare un’immagine in modo che le sue nuove caratteristiche ricordino quelle di un’illustrazione. I disegnatori adottano tecniche differenti per il proprio lavoro, selezionandole in base al risultato che desiderano ottenere, nello stesso modo anche la cartoonization permette di produrre output differenti a seconda dello stile applicato al file che funge da sorgente.
In questa trattazione verranno presentati alcuni esempi di cartoonization senza l’utilizzo di software per il fotoritocco come per esempio Photoshop, gli strumenti di riferimento saranno invece il linguaggio di programmazione Python, la libreria OpenCV nella sua versione per quest’ultimo, e la seguente fotografia disponibile gratuitamente e liberamente come migliaia di altre su UnSplash:
La libreria OpenCV
OpenCV è una Computer Vision Library rilasciata sotto licenza Open Source che include centinaia di algoritmi per la cosiddetta “visione artificiale”, concetto con il quale vengono ricompresi tutti i processi finalizzati alla riproduzione computerizzata del mondo reale a partire da immagini bidimensionali.
La libreria è disponibile anche come modulo per Python che può essere installato, con tutte le dipendenze necessarie, attraverso il package manager pip utilizzando la seguente istruzione:
pip install opencv-python
Una volta installato, opencv-python può essere utilizzato immediatamente tramite importazione diretta nel sorgente degli script.
Stilizzazione di un’immagine
La stilizzazione è una tecnica con cui si può rappresentare una figura riportando unicamente i suoi tratti e i suoi colori essenziali, motivo per il quale stilizzando la nostra immagine originale tramite la semplice applicazione che verrà proposta di seguito dovremmo ottenere un risultato come il seguente:
Il codice utilizzato prevede l’importazione tramite keyword import dei moduli cv2 di OpenCV e numpy, quest’ultimo viene messo a disposizione dalla libreria NumPy e permette di integrare in Python il supporto per gli array nonché nuove funzionalità appositamente dedicate alle operazioni di matematiche.
# Stilizzazione di un'immagine con Python e OpenCV
import cv2
import numpy as np
# lettura dell'immagine
immagine = cv2.imread("img.jpg")
# stilizzazione dell'immagine
risultato = cv2.stylization(immagine, sigma_s=150, sigma_r=0.25)
# visualizzazione dell'immagine
cv2.imshow('Risultato: stilizzazione', risultato)
# visualizzazione della finestra
cv2.waitKey(0)
# chiusura della finestra
cv2.destroyAllWindows()
L’immagine viene letta tramite il metodo imread() di OpenCV che accetta come argomento il nome del file che la contiene, l’informazione così archiviata nella variabile immagine viene quindi passata al metodo stylization() che ha il compito di restituire un output non fotorealistico dell’immagine processata.
Il risultato ottenuto viene stampato a video all’interno di una finestra grazie al metodo imshow(), che consente anche di definire un titolo per la finestra stessa (“Risultato: stilizzazione” nell’esempio), e al metodo waitKey()con argomento 0, che permette di visualizzare la finestra fino alla pressione di un qualsiasi tasto che ne determinerà la chiusura.
Il metodo destroyAllWindows() chiude infine la finestra nella quale è stata mostrata l’immagine e termina lo script avviato.
Schizzi a matita
La caartonization per ottenere un effetto simile a quello di uno schizzo eseguito a matita può essere generata in vari modi. Nel caso degli esempi proposti di seguito si prevede la produzione di due output differenti, il primo in bianco e nero, come nell’immagine seguente:
Analizzando il codice seguente si nota come l’unica differenza rilevante rispetto allo script per la stilizzazione risiede nella parte relativa alla creazione dell’immagine cartoonizzata:
# Schizzi a matita da un'immagine con Python e OpenCV
# output in bianco e nero
import cv2
import numpy as np
# lettura dell'immagine
immagine = cv2.imread("img.jpg")
# creazione dell'immagine
img_bianco_e_nero, img_colori = cv2.pencilSketch(immagine, sigma_s=60, sigma_r=0.5, shade_factor=0.02)
cv2.imshow('Risultato: immagine in bianco e nero', img_bianco_e_nero)
# visualizzazione della finestra
cv2.waitKey(0)
# chiusura della finestra
cv2.destroyAllWindows()
Nello specifico abbiamo la seguente doppia assegnazione:
img_bianco_e_nero, img_colori = cv2.pencilSketch(immagine, sigma_s=60, sigma_r=0.5, shade_factor=0.02)
in cui vengono valorizzate 2 variabili, img_bianco_e_nero e img_colori, grazie ad una chiamata al metodo pencilSketch() la cui funzione appare sufficientemente chiara già dal nome (pencil sketch significa appunto “schizzo a matita”). Viene però mostrata soltanto l’immagine in bianco e nero tramite il già noto metodo imshow() a cui viene passato come argomento la sola variabile img_bianco_e_nero:
ccv2.imshow('Risultato: immagine in bianco e nero', img_bianco_e_nero)
Per quanto riguarda invece l’output a colori il risultato atteso dovrebbe essere simile a quello proposto di seguito:
Il codice dello script deve rimanere lo stesso in quanto la variabile img_colori è stata valorizzata in precedenza, per ottenere l’output atteso è sufficiente passarla come parametro al metodo imshow() in sostituzione di img_bianco_e_nero:
cv2.imshow('Risultato: immagine a colori', img_colori)
Appiattire l’immagine
Un altro effetto interessante per la cartoonization è quello che consente di appiattire un’immagine evidenziandone i bordi e riducendo le sfumature dei colori. Si tratta di una tecnica utilizzata tra l’altro per le immagini di alcune pellicole cinematografiche in fase post-produzione, dove tale processo prende il nome di interpolazione rotoscopica.
In questo caso dovremmo essere in grado di ottenere un output simile al seguente:
Il codice necessario per ottenere questo effetto si differenzia in alcuni punti da quelli proposti in precedenza, a partire dall’impiego del metodo cvtColor() che permette di convertire un’immagine da uno spazio dei colori (o mappatura dei colori di un oggetto) ad un altro:
# Appiattire un'immagine con Python e OpenCV
import cv2
import numpy as np
# lettura dell'immagine
immagine = cv2.imread("img.jpg")
# Individuazione e colorazione dei bordi dell'immagine
colore = cv2.cvtColor(immagine, cv2.COLOR_BGR2GRAY)
colore = cv2.medianBlur(colore, 7)
# Segmentazione dell’immagine
segmentazione = cv2.adaptiveThreshold(colore, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 10)
# generazione dell'immagine
filtro = cv2.bilateralFilter(immagine, 12, 250, 250)
nuova_immagine = cv2.bitwise_and(filtro, filtro, mask= segmentazione)
# visualizzazione del risultato
cv2.imshow("Risultato: cartoonizzazione", nuova_immagine)
# chiusura della finestra
cv2.waitKey(0)
cv2.destroyAllWindows()
A tale metodo vengono passati come parametri la variabile relativa all’immagine aperta tramite imread() e il flag cv2.COLOR_BGR2GRAY per la conversione da tipo BGR (Red Green Blue) a scala di grigi, in questo modo i bordi vengono spianati ed evidenziati.
Vengono poi utilizzati i metodi cv2.medianBlur(), per spianare l’immagine esaltandone la bidimensionalità, e cv2.adaptiveThreshold() che, come suggerito dal nome, si occupa del thresholding, cioè della segmentazione dell’immagine con cui è possibile produrre un output binario da una rappresentazione a scala di grigi.
Entrano quindi in gioco i metodi cv2.bilateralFilter(), per la riduzione del “rumore” (la variazione casuale del colore) dell’immagine preservandone i bordi, e cv2.bitwise_and(), che consente di ricavare un subset di dati da un’immagine definita a sua volta da un’altra immagine detta maske rappresentata, nel caso specifico del nostro esempio, dal risultato della segmentazione.
Sfocatura di un’immagine
La sfocatura, cioè l’ultimo caso di cartoonization di cui ci occuperemo in questa trattazione, è una procedura che consente di riprodurre graficamente un difetto di messa a fuoco. A suo modo anche l’esempio precedente propone una modalità con cui sfocare un’immagine, ma questa volta l’effetto desiderato è il seguente:
Il risultato così ottenuto è possibile grazie ad alcuni metodi già utilizzati negli script descritti in precedenza, sono però presenti alcune novità tra cui si nota immediatamente cv2.Laplacian() che ha il compito di applicare sull’immagine un filtro Laplaciano permettendo di evidenziare i bordi e di riconoscere i contorni (edge detection).
# Sfocatura di un'immagine con Python e OpenCV
import cv2
import numpy as np
# lettura dell'immagine
immagine = cv2.imread("img.jpg")
# applicazione della scala di grigio
conversione = cv2.cvtColor(immagine, cv2.COLOR_BGR2GRAY)
# sfocatura gaussiana
conversione = cv2.GaussianBlur(conversione, (3, 3), 0)
# rilevazione dei bordi
bordi = cv2.Laplacian(conversione, -1, ksize=5)
bordi = 255 - bordi
# segmentazione dell’immagine
ret, bordi = cv2.threshold(bordi, 150, 255, cv2.THRESH_BINARY)
# sfocatura definitiva
sfocatura = cv2.edgePreservingFilter(immagine, flags=2, sigma_s=50, sigma_r=0.4)
# generazione della matrice di output
nuova_immagine = np.zeros(conversione.shape)
# combinazione tra immagine e bordi
nuova_immagine = cv2.bitwise_and(sfocatura, sfocatura, mask=bordi)
# visualizzazione del risultato
cv2.imshow("Risultato: sfocatura", nuova_immagine)
# chiusura della finestra
cv2.waitKey(0)
cv2.destroyAllWindows()
Da segnalare infine l’utilizzo dei metodi cv2.edgePreservingFilter() e np.zeros(), in quest’ultimo caso un metodo della libreria NumPy, che consentono rispettivamente di ridurre il numero di colori presenti nell’immagine preservando i bordi e di ottenere una nuova immagine dall’output di cv2.Laplacian().
Da notare come cv2.edgePreservingFilter() accetti gli stessi 2 argomenti sigma_s e sigma_r già utilizzati per cv2.pencilSketch(), dei parametri che definiscono nell’ordine il sigma spatial, cioè il livello di smoothing (spianatura) da applicare all’immagine considerando un valore compreso tra 0 e 200, e il sigma range, che è una misura (tra 0 e 1 con possibilità di utilizzare valori decimali) del contrasto tra una componente di un’immagine e le componenti a lei prossime.
In questa trattazione verranno presentati alcuni esempi di cartoonization senza l’utilizzo di software per il fotoritocco come per esempio Photoshop, gli strumenti di riferimento saranno invece il linguaggio di programmazione Python, la libreria OpenCV nella sua versione per quest’ultimo, e la seguente fotografia disponibile gratuitamente e liberamente come migliaia di altre su UnSplash:
La libreria OpenCV
OpenCV è una Computer Vision Library rilasciata sotto licenza Open Source che include centinaia di algoritmi per la cosiddetta “visione artificiale”, concetto con il quale vengono ricompresi tutti i processi finalizzati alla riproduzione computerizzata del mondo reale a partire da immagini bidimensionali.
La libreria è disponibile anche come modulo per Python che può essere installato, con tutte le dipendenze necessarie, attraverso il package manager pip utilizzando la seguente istruzione:
pip install opencv-python
Una volta installato, opencv-python può essere utilizzato immediatamente tramite importazione diretta nel sorgente degli script.
Stilizzazione di un’immagine
La stilizzazione è una tecnica con cui si può rappresentare una figura riportando unicamente i suoi tratti e i suoi colori essenziali, motivo per il quale stilizzando la nostra immagine originale tramite la semplice applicazione che verrà proposta di seguito dovremmo ottenere un risultato come il seguente:
Il codice utilizzato prevede l’importazione tramite keyword import dei moduli cv2 di OpenCV e numpy, quest’ultimo viene messo a disposizione dalla libreria NumPy e permette di integrare in Python il supporto per gli array nonché nuove funzionalità appositamente dedicate alle operazioni di matematiche.
# Stilizzazione di un'immagine con Python e OpenCV
import cv2
import numpy as np
# lettura dell'immagine
immagine = cv2.imread("img.jpg")
# stilizzazione dell'immagine
risultato = cv2.stylization(immagine, sigma_s=150, sigma_r=0.25)
# visualizzazione dell'immagine
cv2.imshow('Risultato: stilizzazione', risultato)
# visualizzazione della finestra
cv2.waitKey(0)
# chiusura della finestra
cv2.destroyAllWindows()
L’immagine viene letta tramite il metodo imread() di OpenCV che accetta come argomento il nome del file che la contiene, l’informazione così archiviata nella variabile immagine viene quindi passata al metodo stylization() che ha il compito di restituire un output non fotorealistico dell’immagine processata.
Il risultato ottenuto viene stampato a video all’interno di una finestra grazie al metodo imshow(), che consente anche di definire un titolo per la finestra stessa (“Risultato: stilizzazione” nell’esempio), e al metodo waitKey()con argomento 0, che permette di visualizzare la finestra fino alla pressione di un qualsiasi tasto che ne determinerà la chiusura.
Il metodo destroyAllWindows() chiude infine la finestra nella quale è stata mostrata l’immagine e termina lo script avviato.
Schizzi a matita
La caartonization per ottenere un effetto simile a quello di uno schizzo eseguito a matita può essere generata in vari modi. Nel caso degli esempi proposti di seguito si prevede la produzione di due output differenti, il primo in bianco e nero, come nell’immagine seguente:
Analizzando il codice seguente si nota come l’unica differenza rilevante rispetto allo script per la stilizzazione risiede nella parte relativa alla creazione dell’immagine cartoonizzata:
# Schizzi a matita da un'immagine con Python e OpenCV
# output in bianco e nero
import cv2
import numpy as np
# lettura dell'immagine
immagine = cv2.imread("img.jpg")
# creazione dell'immagine
img_bianco_e_nero, img_colori = cv2.pencilSketch(immagine, sigma_s=60, sigma_r=0.5, shade_factor=0.02)
cv2.imshow('Risultato: immagine in bianco e nero', img_bianco_e_nero)
# visualizzazione della finestra
cv2.waitKey(0)
# chiusura della finestra
cv2.destroyAllWindows()
Nello specifico abbiamo la seguente doppia assegnazione:
img_bianco_e_nero, img_colori = cv2.pencilSketch(immagine, sigma_s=60, sigma_r=0.5, shade_factor=0.02)
in cui vengono valorizzate 2 variabili, img_bianco_e_nero e img_colori, grazie ad una chiamata al metodo pencilSketch() la cui funzione appare sufficientemente chiara già dal nome (pencil sketch significa appunto “schizzo a matita”). Viene però mostrata soltanto l’immagine in bianco e nero tramite il già noto metodo imshow() a cui viene passato come argomento la sola variabile img_bianco_e_nero:
ccv2.imshow('Risultato: immagine in bianco e nero', img_bianco_e_nero)
Per quanto riguarda invece l’output a colori il risultato atteso dovrebbe essere simile a quello proposto di seguito:
Il codice dello script deve rimanere lo stesso in quanto la variabile img_colori è stata valorizzata in precedenza, per ottenere l’output atteso è sufficiente passarla come parametro al metodo imshow() in sostituzione di img_bianco_e_nero:
cv2.imshow('Risultato: immagine a colori', img_colori)
Appiattire l’immagine
Un altro effetto interessante per la cartoonization è quello che consente di appiattire un’immagine evidenziandone i bordi e riducendo le sfumature dei colori. Si tratta di una tecnica utilizzata tra l’altro per le immagini di alcune pellicole cinematografiche in fase post-produzione, dove tale processo prende il nome di interpolazione rotoscopica.
In questo caso dovremmo essere in grado di ottenere un output simile al seguente:
Il codice necessario per ottenere questo effetto si differenzia in alcuni punti da quelli proposti in precedenza, a partire dall’impiego del metodo cvtColor() che permette di convertire un’immagine da uno spazio dei colori (o mappatura dei colori di un oggetto) ad un altro:
# Appiattire un'immagine con Python e OpenCV
import cv2
import numpy as np
# lettura dell'immagine
immagine = cv2.imread("img.jpg")
# Individuazione e colorazione dei bordi dell'immagine
colore = cv2.cvtColor(immagine, cv2.COLOR_BGR2GRAY)
colore = cv2.medianBlur(colore, 7)
# Segmentazione dell’immagine
segmentazione = cv2.adaptiveThreshold(colore, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 10)
# generazione dell'immagine
filtro = cv2.bilateralFilter(immagine, 12, 250, 250)
nuova_immagine = cv2.bitwise_and(filtro, filtro, mask= segmentazione)
# visualizzazione del risultato
cv2.imshow("Risultato: cartoonizzazione", nuova_immagine)
# chiusura della finestra
cv2.waitKey(0)
cv2.destroyAllWindows()
A tale metodo vengono passati come parametri la variabile relativa all’immagine aperta tramite imread() e il flag cv2.COLOR_BGR2GRAY per la conversione da tipo BGR (Red Green Blue) a scala di grigi, in questo modo i bordi vengono spianati ed evidenziati.
Vengono poi utilizzati i metodi cv2.medianBlur(), per spianare l’immagine esaltandone la bidimensionalità, e cv2.adaptiveThreshold() che, come suggerito dal nome, si occupa del thresholding, cioè della segmentazione dell’immagine con cui è possibile produrre un output binario da una rappresentazione a scala di grigi.
Entrano quindi in gioco i metodi cv2.bilateralFilter(), per la riduzione del “rumore” (la variazione casuale del colore) dell’immagine preservandone i bordi, e cv2.bitwise_and(), che consente di ricavare un subset di dati da un’immagine definita a sua volta da un’altra immagine detta maske rappresentata, nel caso specifico del nostro esempio, dal risultato della segmentazione.
Sfocatura di un’immagine
La sfocatura, cioè l’ultimo caso di cartoonization di cui ci occuperemo in questa trattazione, è una procedura che consente di riprodurre graficamente un difetto di messa a fuoco. A suo modo anche l’esempio precedente propone una modalità con cui sfocare un’immagine, ma questa volta l’effetto desiderato è il seguente:
Il risultato così ottenuto è possibile grazie ad alcuni metodi già utilizzati negli script descritti in precedenza, sono però presenti alcune novità tra cui si nota immediatamente cv2.Laplacian() che ha il compito di applicare sull’immagine un filtro Laplaciano permettendo di evidenziare i bordi e di riconoscere i contorni (edge detection).
# Sfocatura di un'immagine con Python e OpenCV
import cv2
import numpy as np
# lettura dell'immagine
immagine = cv2.imread("img.jpg")
# applicazione della scala di grigio
conversione = cv2.cvtColor(immagine, cv2.COLOR_BGR2GRAY)
# sfocatura gaussiana
conversione = cv2.GaussianBlur(conversione, (3, 3), 0)
# rilevazione dei bordi
bordi = cv2.Laplacian(conversione, -1, ksize=5)
bordi = 255 - bordi
# segmentazione dell’immagine
ret, bordi = cv2.threshold(bordi, 150, 255, cv2.THRESH_BINARY)
# sfocatura definitiva
sfocatura = cv2.edgePreservingFilter(immagine, flags=2, sigma_s=50, sigma_r=0.4)
# generazione della matrice di output
nuova_immagine = np.zeros(conversione.shape)
# combinazione tra immagine e bordi
nuova_immagine = cv2.bitwise_and(sfocatura, sfocatura, mask=bordi)
# visualizzazione del risultato
cv2.imshow("Risultato: sfocatura", nuova_immagine)
# chiusura della finestra
cv2.waitKey(0)
cv2.destroyAllWindows()
Da segnalare infine l’utilizzo dei metodi cv2.edgePreservingFilter() e np.zeros(), in quest’ultimo caso un metodo della libreria NumPy, che consentono rispettivamente di ridurre il numero di colori presenti nell’immagine preservando i bordi e di ottenere una nuova immagine dall’output di cv2.Laplacian().
Da notare come cv2.edgePreservingFilter() accetti gli stessi 2 argomenti sigma_s e sigma_r già utilizzati per cv2.pencilSketch(), dei parametri che definiscono nell’ordine il sigma spatial, cioè il livello di smoothing (spianatura) da applicare all’immagine considerando un valore compreso tra 0 e 200, e il sigma range, che è una misura (tra 0 e 1 con possibilità di utilizzare valori decimali) del contrasto tra una componente di un’immagine e le componenti a lei prossime.