
Leggendo l’immagine come un array NumPy ndarray, è possibile eseguire varie elaborazioni di immagini utilizzando le funzioni NumPy.
Operando ndarray, puoi ottenere e impostare (modificare) i valori dei pixel, ritagliare immagini, concatenare immagini, ecc. Coloro che hanno familiarità con NumPy possono eseguire varie elaborazioni di immagini senza utilizzare librerie come OpenCV.
Anche quando si utilizza OpenCV, OpenCV per Python tratta i dati dell’immagine come ndarray, quindi è utile sapere come utilizzare NumPy (ndarray). Oltre a OpenCV, molte librerie come scikit-image trattano le immagini come ndarray.
In questo articolo vengono descritti i seguenti contenuti.
Leggi e scrivi immagini:
- Come leggere il file immagine come array NumPy ndarray
- Come salvare l’array NumPy ndarray come file immagine
Esempi di elaborazione delle immagini con NumPy (ndarray):
- Ottieni e imposta (modifica) i valori dei pixel
- Generazione di immagini monocolore e concatenazione
- Inversione negativa-positiva (inversione del valore dei pixel)
- Riduzione del colore
- Binarizzazione
- Correzione gamma
- Rifilatura con fetta
- Dividi con fetta o funzione
- Incolla con la fetta
- Miscelazione e mascheratura alfa
- Ruota e capovolgi
I codici di esempio in questo articolo utilizzano Pillow per leggere e salvare file di immagine. Se desideri utilizzare OpenCV, consulta il seguente articolo.
Vedi anche il seguente articolo su Pillow. Semplici operazioni come la lettura, il salvataggio, il ridimensionamento e la rotazione delle immagini possono essere eseguite da Pillow da solo.
Come leggere un file immagine come ndarray
Prendi l’immagine seguente come esempio.
Il passaggio dei dati dell’immagine letti da PIL.Image.open() a np.array() restituisce ndarray 3D la cui forma è (riga (altezza), colonna (larghezza), colore (canale)).
from PIL import Image
import numpy as np
im = np.array(Image.open('data/src/lena.jpg'))
print(type(im))
#
print(im.dtype)
# uint8
print(im.shape)
# (225, 400, 3)
L’ordine dei colori (canali) è RGB (rosso, verde, blu). Nota che è diverso dal caso della lettura con cv2.imread() di OpenCV.
Se converti l’immagine in scala di grigi con convert(‘L’) e poi la passi a np.array(), restituisce ndarray 2D la cui forma è (riga (altezza), colonna (larghezza)).
im_gray = np.array(Image.open('data/src/lena.jpg').convert('L'))
print(im_gray.shape)
# (225, 400)
Puoi anche ottenere ndarray da PIL.Image con np.asarray(). np.array() restituisce un ndarray riscrivibile, mentre np.asarray() restituisce un ndarray non riscrivibile.
Per np.array(), puoi modificare il valore dell’elemento (pixel).
print(im.flags.writeable)
# True
print(im[0, 0, 0])
# 109
im[0, 0, 0] = 0
print(im[0, 0, 0])
# 0
Per np.asarray(), non è possibile modificare il valore perché la riscrittura è vietata. È possibile creare un nuovo ndarray basato sul ndarray letto.
im_as = np.asarray(Image.open('data/src/lena.jpg'))
print(type(im_as))
#
print(im_as.flags.writeable)
# False
# im_as[0, 0, 0] = 0
# ValueError: assignment destination is read-only
Il tipo di dati dtype del ndarray letto è uint8 (intero senza segno a 8 bit).
Se vuoi elaborarlo come un numero a virgola mobile float, puoi convertirlo con astype() o specificare il tipo di dati nel secondo argomento di np.array() e np.asarray().
im_f = im.astype(np.float64)
print(im_f.dtype)
# float64
im_f = np.array(Image.open('data/src/lena.jpg'), np.float64)
print(im_f.dtype)
# float64
Per ulteriori informazioni sul tipo di dati dtype in NumPy, vedere l’articolo seguente.
Come salvare l’array NumPy ndarray come file immagine
Il passaggio di ndarray a Image.fromarray() restituisce PIL.Image. Può essere salvato come file immagine con il metodo save(). Il formato del file salvato è determinato automaticamente dall’estensione del percorso passato nell’argomento di save().
pil_img = Image.fromarray(im)
print(pil_img.mode)
# RGB
pil_img.save('data/temp/lena_save_pillow.jpg')
Un’immagine in scala di grigi (array 2D) può anche essere passata a Image.fromarray(). la modalità diventa automaticamente ‘L’ (scala di grigi). Può essere salvato con save().
pil_img_gray = Image.fromarray(im_gray)
print(pil_img_gray.mode)
# L
pil_img_gray.save('data/temp/lena_save_pillow_gray.jpg')
Se vuoi solo salvarlo, puoi scriverlo in una riga.
Image.fromarray(im).save('data/temp/lena_save_pillow.jpg')
Image.fromarray(im_gray).save('data/temp/lena_save_pillow_gray.jpg')
Se il tipo di dati dtype di ndarray è float, ecc., si verificherà un errore, quindi è necessario convertire in uint8.
# pil_img = Image.fromarray(im_f)
# TypeError: Cannot handle this data type
pil_img = Image.fromarray(im_f.astype(np.uint8))
pil_img.save('data/temp/lena_save_pillow.jpg')
Si noti che se il valore del pixel è rappresentato da 0,0 a 1,0, è necessario moltiplicare per 255 e convertire in uint8 e salvare.
Con save(), i parametri in base al formato possono essere passati come argomenti. Per i dettagli, vedere Formato file immagine .
Ad esempio, nel caso di JPG, puoi passare la qualità dell’immagine all’argomento qualità. Va da 1 (il più basso) a 95 (il più alto) e il valore predefinito è 75.
Ottieni e imposta (modifica) i valori dei pixel
Puoi ottenere il valore di un pixel specificando le coordinate nell’indice [riga, colonne] di ndarray. Nota che l’ordine è y, x in coordinate xy. L’origine è in alto a sinistra.
from PIL import Image
import numpy as np
im = np.array(Image.open('data/src/lena.jpg'))
print(im.shape)
# (225, 400, 3)
print(im[100, 150])
# [111 81 109]
print(type(im[100, 150]))
#
L’esempio sopra mostra il valore a (y, x) = (100, 150), cioè la centesima riga e la 150a colonna di pixel. Come accennato in precedenza, i colori del ndarray ottenuti utilizzando Pillow sono in ordine RGB, quindi il risultato è (R, G, B) = (111, 81, 109).
Puoi anche utilizzare la decompressione per assegnarli a variabili separate.
R, G, B = im[100, 150]
print(R)
# 111
print(G)
# 81
print(B)
# 109
È anche possibile ottenere il valore specificando il colore.
print(im[100, 150, 0])
# 111
print(im[100, 150, 1])
# 81
print(im[100, 150, 2])
# 109
Puoi anche cambiare a un nuovo valore. Puoi cambiare RGB tutto in una volta o cambiarlo con un solo colore.
im[100, 150] = (0, 50, 100)
print(im[100, 150])
# [ 0 50 100]
im[100, 150, 0] = 150
print(im[100, 150])
# [150 50 100]
Generazione di immagini monocolore e concatenazione
Genera immagini a un colore impostando altri valori di colore su 0 e concatenandoli orizzontalmente con np.concatenate(). Puoi anche concatenare le immagini usando np.hstack() o np.c_[]
from PIL import Image
import numpy as np
im = np.array(Image.open('data/src/lena_square.png'))
im_R = im.copy()
im_R[:, :, (1, 2)] = 0
im_G = im.copy()
im_G[:, :, (0, 2)] = 0
im_B = im.copy()
im_B[:, :, (0, 1)] = 0
im_RGB = np.concatenate((im_R, im_G, im_B), axis=1)
# im_RGB = np.hstack((im_R, im_G, im_B))
# im_RGB = np.c_['1', im_R, im_G, im_B]
pil_img = Image.fromarray(im_RGB)
pil_img.save('data/dst/lena_numpy_split_color.jpg')

Inversione negativa-positiva (invertire il valore dei pixel)
È anche facile calcolare e manipolare i valori dei pixel.
Un’immagine invertita negativa-positiva può essere generata sottraendo il valore dei pixel dal valore massimo (255 per uint8).
import numpy as np
from PIL import Image
im = np.array(Image.open('data/src/lena_square.png').resize((256, 256)))
im_i = 255 - im
Image.fromarray(im_i).save('data/dst/lena_numpy_inverse.jpg')
Poiché la dimensione originale è troppo grande, viene ridimensionata con resize() per comodità. Lo stesso vale per i seguenti esempi.
Riduzione del colore
Taglia il resto della divisione usando // e moltiplica ancora, i valori dei pixel diventano discreti e il numero di colori può essere ridotto.
import numpy as np
from PIL import Image
im = np.array(Image.open('data/src/lena_square.png').resize((256, 256)))
im_32 = im // 32 * 32
im_128 = im // 128 * 128
im_dec = np.concatenate((im, im_32, im_128), axis=1)
Image.fromarray(im_dec).save('data/dst/lena_numpy_dec_color.png')
Binarizzazione
È anche possibile assegnare al bianco e nero in base alla soglia.
Vedere i seguenti articoli per i dettagli.
Correzione gamma
Puoi fare tutto ciò che vuoi con i valori dei pixel, come moltiplicazione, divisione, esponenziazione, ecc.
Non è necessario utilizzare il ciclo for perché l’intera immagine può essere calcolata così com’è.
from PIL import Image
import numpy as np
im = np.array(Image.open('data/src/lena_square.png'))
im_1_22 = 255.0 * (im / 255.0)**(1 / 2.2)
im_22 = 255.0 * (im / 255.0)**2.2
im_gamma = np.concatenate((im_1_22, im, im_22), axis=1)
pil_img = Image.fromarray(np.uint8(im_gamma))
pil_img.save('data/dst/lena_numpy_gamma.jpg')

Come risultato del calcolo, il tipo di dati dtype di numpy.ndarray viene convertito nel numero a virgola mobile float. Nota che devi convertirlo in uint8 quando lo salvi.
Rifilatura con fetta
Specificando un’area con fetta, puoi tagliarla a un rettangolo.
from PIL import Image
import numpy as np
im = np.array(Image.open('data/src/lena_square.png'))
print(im.shape)
# (512, 512, 3)
im_trim1 = im[128:384, 128:384]
print(im_trim1.shape)
# (256, 256, 3)
Image.fromarray(im_trim1).save('data/dst/lena_numpy_trim.jpg')
Vedere il seguente articolo per ulteriori informazioni sullo slicing per numpy.ndarray.
Può essere utile definire una funzione che specifichi le coordinate in alto a sinistra e la larghezza e l’altezza dell’area da ritagliare.
def trim(array, x, y, width, height):
return array[y:y + height, x:x+width]
im_trim2 = trim(im, 128, 192, 256, 128)
print(im_trim2.shape)
# (128, 256, 3)
Image.fromarray(im_trim2).save('data/dst/lena_numpy_trim2.jpg')
Se specifichi al di fuori della dimensione dell’immagine, verrà ignorata.
im_trim3 = trim(im, 128, 192, 512, 128)
print(im_trim3.shape)
# (128, 384, 3)
Image.fromarray(im_trim3).save('data/dst/lena_numpy_trim3.jpg')
Dividi con fetta o funzione
Puoi anche dividere l’immagine affettandola.
from PIL import Image
import numpy as np
im = np.array(Image.open('data/src/lena_square.png').resize((256, 256)))
print(im.shape)
# (256, 256, 3)
im_0 = im[:, :100]
im_1 = im[:, 100:]
print(im_0.shape)
# (256, 100, 3)
print(im_1.shape)
# (256, 156, 3)
Image.fromarray(im_0).save('data/dst/lena_numpy_split_0.jpg')
Image.fromarray(im_1).save('data/dst/lena_numpy_split_1.jpg')
È anche possibile dividere l’immagine con la funzione NumPy.
np.hsplit() divide ndarray orizzontalmente. Se viene specificato un valore intero per il secondo argomento, ndarray viene diviso equamente.
im_0, im_1 = np.hsplit(im, 2)
print(im_0.shape)
# (256, 128, 3)
print(im_1.shape)
# (256, 128, 3)
Se viene specificato un elenco come secondo argomento, ndarray viene diviso nella posizione di quel valore.
im_0, im_1, im_2 = np.hsplit(im, [100, 150])
print(im_0.shape)
# (256, 100, 3)
print(im_1.shape)
# (256, 50, 3)
print(im_2.shape)
# (256, 106, 3)
np.vsplit() divide ndarray verticalmente. L’utilizzo di np.vsplit() è lo stesso di np.hsplit().
Quando viene specificato un valore intero come secondo argomento con np.hsplit() o np.vsplit(), viene generato un errore se non può essere suddiviso equamente. np.array_split() regola la dimensione in modo appropriato e la divide.
# im_0, im_1, im_2 = np.hsplit(im, 3)
# ValueError: array split does not result in an equal division
im_0, im_1, im_2 = np.array_split(im, 3, axis=1)
print(im_0.shape)
# (256, 86, 3)
print(im_1.shape)
# (256, 85, 3)
print(im_2.shape)
# (256, 85, 3)
Incolla con la fetta
Utilizzando le sezioni, un rettangolo di matrice può essere sostituito con un altro rettangolo di matrice.
Usando questo, una parte dell’immagine o l’intera immagine può essere incollata su un’altra immagine.
import numpy as np
from PIL import Image
src = np.array(Image.open('data/src/lena_square.png').resize((128, 128)))
dst = np.array(Image.open('data/src/lena_square.png').resize((256, 256))) // 4
dst_copy = dst.copy()
dst_copy[64:128, 128:192] = src[32:96, 32:96]
Image.fromarray(dst_copy).save('data/dst/lena_numpy_paste.jpg')
dst_copy = dst.copy()
dst_copy[64:192, 64:192] = src
Image.fromarray(dst_copy).save('data/dst/lena_numpy_paste_all.jpg')
Si noti che si verificherà un errore se la dimensione dell’area specificata sul lato sinistro differisce dalla dimensione dell’area specificata sul lato destro.
Miscelazione e mascheratura alfa
Mediante l’operazione per ciascun elemento (= pixel) dell’array, due immagini possono essere miscelate alfa o composte in base a un’immagine maschera. Vedere i seguenti articoli per i dettagli.
Ruota e capovolgi
Ci sono anche funzioni che ruotano l’array e lo capovolgono in alto, in basso, a sinistra ea destra.
Immagine originale:
Immagine ruotata:
Immagine capovolta: