Skip to content

Elaborazione di immagini con Python, NumPy

Python

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.

lena

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')
Colore diviso per l'elaborazione di immagini NumPy

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')

Python NumPy inverso

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')

Python NumPy diminuisce il colore

Binarizzazione

È anche possibile assegnare al bianco e nero in base alla soglia.

Vedere i seguenti articoli per i dettagli.

Binarizzazione Python NumPy OpenCV

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')
NumPy elaborazione delle immagini gamma divisa

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')

ritaglio insensibile dell'immagine 1

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')

ritaglio insensibile dell'immagine 2

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')

ritaglio insensibile dell'immagine 3

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')

immagine numpy divisa 0

numpy immagine divisa 1

È 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')

incollare l'immagine insensibile

dst_copy = dst.copy()
dst_copy[64:192, 64:192] = src

Image.fromarray(dst_copy).save('data/dst/lena_numpy_paste_all.jpg')

numpy image incolla tutto

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.

Gradazione della fusione alfa dell'immagine NumPy

Sfocatura della fusione dell'immagine NumPy

Ruota e capovolgi

Ci sono anche funzioni che ruotano l’array e lo capovolgono in alto, in basso, a sinistra ea destra.

Immagine originale:

lena

Immagine ruotata:

immagine numpy rot90

Immagine capovolta:

immagine di Nupmy Flipud