
Esegue la fusione e il mascheramento alfa con Python, OpenCV, NumPy.
Può essere realizzato solo con NumPy senza utilizzare OpenCV. Poiché il funzionamento dell’array di NumPy è più semplice e flessibile, lo consiglio.
In questo articolo vengono descritti i seguenti contenuti.
- Fusione alfa con OpenCV:
cv2.addWeighted()
- Mascheratura con OpenCV:
cv2.bitwise_and()
- Miscelazione alfa con NumPy
- Mascheratura con NumPy
- Miscelazione e mascheratura alfa complessa con NumPy
- Creazione dell’immagine della maschera tramite il disegno di OpenCV
Fare riferimento al seguente articolo sulla miscelazione alfa e il mascheramento utilizzando Pillow (PIL).
Il codice di esempio usa l’immagine seguente.
La versione OpenCV del codice di esempio è 4.0.1. OpenCV3 e 4 non dovrebbero cambiare molto, ma OpenCV2 potrebbe essere diverso, quindi fai attenzione.
Fusione alfa con OpenCV:cv2.addWeighted()
Usa cv2.addWeighted() per eseguire la fusione alfa con OpenCV.
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
Viene calcolato come segue in base ai parametri.
dst = src1 * alpha + src2 * beta + gamma
Le due immagini devono avere le stesse dimensioni, quindi ridimensionale.
import cv2
src1 = cv2.imread('data/src/lena.jpg')
src2 = cv2.imread('data/src/rocket.jpg')
src2 = cv2.resize(src2, src1.shape[1::-1])
Fare riferimento al seguente articolo per ottenere la dimensione dell’immagine letta come array NumPy ndarray.
L’immagine viene miscelata alfa in base al secondo parametro alfa e al quarto parametro beta.
Sebbene le immagini siano salvate come file qui, se vuoi visualizzarle in un’altra finestra, puoi usare cv2.imshow() (es:cv2.imshow('window_name', dst)). The same is true for the following sample code.
dst = cv2.addWeighted(src1, 0.5, src2, 0.5, 0)
cv2.imwrite('data/dst/opencv_add_weighted.jpg', dst)
Il quinto parametro gamma è il valore da sommare a tutti i valori dei pixel.
dst = cv2.addWeighted(src1, 0.5, src2, 0.2, 128)
cv2.imwrite('data/dst/opencv_add_weighted_gamma.jpg', dst)
Come puoi vedere dal risultato precedente, non va in overflow anche se supera il valore massimo (255 per uint8), ma si noti che alcuni tipi di dati potrebbero non essere gestiti correttamente.
In tal caso, utilizzare il metodo clip() di ndarray. Vedi la sezione sulla fusione alfa con NumPy di seguito.
Mascheratura con OpenCV:cv2.bitwise_and()
Usa cv2.bitwise_and() per fare il mascheramento con OpenCV.
dst = cv2.bitwise_and(src1, src2[, dst[, mask]])
cv2.bitwise_and() è una funzione che esegue l’elaborazione AND bit per bit come suggerisce il nome. L’AND dei valori per ciascun pixel delle immagini di input src1 e src2 è il valore di pixel dell’immagine di output.
Qui, un’immagine in scala di grigi viene utilizzata come immagine maschera per src2.
src2 = cv2.imread('data/src/horse_r.png')
src2 = cv2.resize(src2, src1.shape[1::-1])
print(src2.shape)
# (225, 400, 3)
print(src2.dtype)
# uint8
dst = cv2.bitwise_and(src1, src2)
cv2.imwrite('data/dst/opencv_bitwise_and.jpg', dst)
Quando il file immagine viene letto, il tipo di dati è uint8 (intero a 8 bit senza segno: 0-255), il nero indica il valore del pixel 0 (0b00000000 in binario), il bianco indica il valore del pixel 255 (0b11111111 in binario) ).
Nel caso di uint8, il risultato dell’operazione sui bit è facile da capire, ma nel caso del numero in virgola mobile float, si nota che l’operazione sui bit viene eseguita in notazione binaria e il risultato è inaspettato.
Potrebbe essere più facile comprendere l’elaborazione della maschera con NumPy descritto più avanti.
Oltre a cv2.bitwise_and(), OpenCV include anche cv2.bitwise_or(), cv2.bitwise_xor() e cv2.bitwise_not() per eseguire operazioni OR, XOR e NOT.
Miscelazione alfa con NumPy
Poiché NumPy può eseguire facilmente operazioni aritmetiche per ogni pixel dell’array, la fusione alfa può essere realizzata anche con una semplice espressione.
Qui, i file di immagine vengono letti come array NumPy ndarray utilizzando Pillow. Il ridimensionamento viene eseguito anche con il metodo di Pillow.
I file di immagine vengono letti come ndarray con cv2.imread() di OpenCV, quindi non importa quale OpenCV o Pillow venga utilizzato, ma tieni presente che l’ordine dei colori è diverso.
Poiché l’operazione di ndarray e valore scalare è l’operazione del valore di ciascun elemento e del valore scalare, la alfa può essere calcolata come segue. Fai attenzione quando salvi come file immagine con Pillow perché il tipo di dati viene trasmesso automaticamente.
import numpy as np
from PIL import Image
src1 = np.array(Image.open('data/src/lena.jpg'))
src2 = np.array(Image.open('data/src/rocket.jpg').resize(src1.shape[1::-1], Image.BILINEAR))
print(src1.dtype)
# uint8
dst = src1 * 0.5 + src2 * 0.5
print(dst.dtype)
# float64
Image.fromarray(dst.astype(np.uint8)).save('data/dst/numpy_image_alpha_blend.jpg')
Nota che quando salvi come file jpg con il metodo save() di Pillow, puoi specificare la qualità con l’argomento quality (è omesso nell’esempio, quindi rimane l’impostazione predefinita).
È anche facile se vuoi aggiungere valori a ciascun pixel in modo uniforme, come il parametro in cv2.addWeighted() di OpenCV. È possibile aggiungere valori diversi a ciascun colore come segue. Come accennato in precedenza, si noti che l’ordine dei colori varia a seconda di come viene letto il file immagine.
Usa clip() per ritagliare i valori dei pixel nell’intervallo da 0 a 255. Si noti che si verificano risultati imprevisti durante il salvataggio come file di immagine se un valore che supera il valore massimo 255 di int8.
dst = src1 * 0.5 + src2 * 0.2 + (96, 128, 160)
print(dst.max())
# 311.1
dst = dst.clip(0, 255)
print(dst.max())
# 255.0
Image.fromarray(dst.astype(np.uint8)).save('data/dst/numpy_image_alpha_blend_gamma.jpg')
Mascheratura con NumPy
Il mascheramento è facile con le operazioni sugli array di NumPy.
Le operazioni aritmetiche di array della stessa forma sono operazioni per ciascun pixel nella stessa posizione.
L’immagine in scala di grigi letta come uint8 ha 0 per il nero e 255 per il bianco. Dividendo questo per 255, il nero diventa 0,0 e il bianco diventa 1,0 e moltiplicandolo per l’immagine originale, rimane solo la parte bianca 1,0 e si può realizzare l’elaborazione della maschera.
import numpy as np
from PIL import Image
src = np.array(Image.open('data/src/lena.jpg'))
mask = np.array(Image.open('data/src/horse_r.png').resize(src.shape[1::-1], Image.BILINEAR))
print(mask.dtype, mask.min(), mask.max())
# uint8 0 255
mask = mask / 255
print(mask.dtype, mask.min(), mask.max())
# float64 0.0 1.0
dst = src * mask
Image.fromarray(dst.astype(np.uint8)).save('data/dst/numpy_image_mask.jpg')
In questo esempio, se dst = src * mask / 255,src * mask viene prima calcolato come uint8 e il valore viene arrotondato e quindi diviso per 255, che non è il risultato previsto.
Va bene se dst = src * (mask / 255) o dst = mask / 255 * src.
Se non si desidera considerare l’ordine, è possibile eseguire il cast di tutti gli array in modo che siano fluttuanti e quindi operare. Potrebbe esserci meno errori.
Fare attenzione se l’immagine della maschera è un’immagine in scala di grigi e un ndarray 2D (nessuna dimensione del colore). Se la moltiplicazione viene eseguita così com’è, si verifica un errore.
mask = np.array(Image.open('data/src/horse_r.png').convert('L').resize(src.shape[1::-1], Image.BILINEAR))
print(mask.shape)
# (225, 400)
mask = mask / 255
# dst = src * mask
# ValueError: operands could not be broadcast together with shapes (225,400,3) (225,400)
NumPy ha un meccanismo chiamato broadcast che esegue operazioni convertendo automaticamente array di dimensioni e forme diverse a seconda dei casi. Tuttavia, secondo il messaggio di errore, la trasmissione non viene eseguita in modo appropriato dalla combinazione degli esempi precedenti.
Trasmetterà bene se aggiungi un’altra dimensione a un ndarray 2D.
mask = mask.reshape(*mask.shape, 1)
print(mask.shape)
# (225, 400, 1)
dst = src * mask
Image.fromarray(dst.astype(np.uint8)).save('data/dst/numpy_image_mask_l.jpg')
la forma dell’array originale viene decompressa e passata a reshape().
Un altro modo è usare np.newaxis invece di reshape().
# mask = mask[:, :, np.newaxis]
Miscelazione e mascheratura alfa complessa con NumPy
Nell’esempio della fusione alfa sopra, l’immagine è stata composta con un rapporto uniforme sull’intera superficie dell’immagine, ma usando NumPy, è possibile comporre in base un’altra immagine (array).
Usa la seguente immagine di gradazione. Le immagini di gradazione possono essere generate usando NumPy.
Può essere composto con una semplice operazione. Viene generata un’immagine in cui il valore alfa (rapporto di fusione) cambia in base al valore in pixel dell’immagine di gradazione.
import numpy as np
from PIL import Image
src1 = np.array(Image.open('data/src/lena.jpg'))
src2 = np.array(Image.open('data/src/rocket.jpg').resize(src1.shape[1::-1], Image.BILINEAR))
mask1 = np.array(Image.open('data/src/gradation_h.jpg').resize(src1.shape[1::-1], Image.BILINEAR))
mask1 = mask1 / 255
dst = src1 * mask1 + src2 * (1 - mask1)
Image.fromarray(dst.astype(np.uint8)).save('data/dst/numpy_image_ab_grad.jpg')
È anche facile se vuoi mascherare con un’altra immagine.
mask2 = np.array(Image.open('data/src/horse_r.png').resize(src1.shape[1::-1], Image.BILINEAR))
mask2 = mask2 / 255
dst = (src1 * mask1 + src2 * (1 - mask1)) * mask2
Image.fromarray(dst.astype(np.uint8)).save('data/dst/numpy_image_ab_mask_grad.jpg')
Creazione dell’immagine della maschera tramite il disegno di OpenCV
È possibile creare immagini di maschere geometriche utilizzando la funzione di disegno di OpenCV.
È possibile generare un ndarray della stessa forma dell’immagine da elaborare da np.zeros_like() e in cui tutti gli elementi sono 0. Corrisponde a un’immagine nera della stessa dimensione dell’immagine originale.
import cv2
import numpy as np
src = cv2.imread('data/src/lena.jpg')
mask = np.zeros_like(src)
print(mask.shape)
# (225, 400, 3)
print(mask.dtype)
# uint8
Puoi anche specificare la dimensione con np.zeros().
Qui, disegna figura con la funzione di disegno di OpenCV. Un rettangolo usa cv2.rectangle(), un cerchio usa cv2.circle() e un poligono usa cv2.fillConvexPoly(). Rettangoli e cerchi verranno riempiti se spessore=-1.
cv2.rectangle(mask, (50, 50), (100, 200), (255, 255, 255), thickness=-1)
cv2.circle(mask, (200, 100), 50, (255, 255, 255), thickness=-1)
cv2.fillConvexPoly(mask, np.array([[330, 50], [300, 200], [360, 150]]), (255, 255, 255))
cv2.imwrite('data/dst/opencv_draw_mask.jpg', mask)
Quando l’elaborazione di smussamento (sfocatura) viene eseguita utilizzando una funzione come cv2.GaussianBlur(), il confine diventa smussato, in modo che sia possibile eseguire una sintesi uniforme mediante mascheramento.
Specificare la dimensione del kernel nella direzione xey come una tupla nel secondo parametro di cv2.GaussianBlur(). All’aumentare di ciascun valore, aumenta la larghezza della sfocatura in quella direzione. Il valore deve essere dispari. Il terzo parametro specifico il valore della deviazione standard gaussiana. Se è 0, viene calcolato automaticamente. Si noti che non può essere omesso.
Per altre funzioni di smoothing, fare riferimento al documento ufficiale di seguito.
mask_blur = cv2.GaussianBlur(mask, (51, 51), 0)
cv2.imwrite('data/dst/opencv_draw_mask_blur.jpg', mask_blur)
dst = src * (mask_blur / 255)
cv2.imwrite('data/dst/opencv_draw_mask_blur_result.jpg', dst)
Si noti che se la parte dst = src * (mask_blur / 255) è dst = src * mask_blur / 255, il risultato non sarà quello previsto. Vedere la sezione Mascheratura con NumPy.
Inoltre, se il ndarray utilizzatore come maschera è un array bidimensionale (nessuna dimensione del colore), non può essere calcolato senza aggiungere un’altra dimensione. Vedi anche Mascherare con la sezione NumPy.