Skip to content

Mélange alpha et masquage d’images avec Python, OpenCV, NumPy

Effectue le mélange et le masquage alpha avec Python, OpenCV, NumPy.

Il peut être réalisé avec uniquement NumPy sans utiliser OpenCV. Parce que le fonctionnement du tableau de NumPy est plus facile et plus flexible, je le recommande.

Cet article décrit le contenu suivant.

  • Mélange Alpha avec OpenCV :cv2.addWeighted()
  • Masquage avec OpenCV :cv2.bitwise_and()
  • Mélange Alpha avec NumPy
  • Masquage avec NumPy
  • Mélange et masquage des complexes alpha avec NumPy
  • Création d’image de masque par dessin OpenCV

Reportez-vous à l’article suivant sur le mélange et le masquage alpha à l’aide de Pillow (PIL).

L’exemple de code utilise l’image suivante.

avec

fusée

masque de cheval

La version OpenCV de l’exemple de code est 4.0.1. OpenCV3 et 4 ne devraient pas beaucoup changer, mais OpenCV2 peut être différent, alors soyez prudent.

Mélange Alpha avec OpenCV :cv2.addWeighted()

Utilisez cv2.addWeighted() pour effectuer une fusion alpha avec OpenCV.

dst = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])

Il est calculé comme suit en fonction des paramètres.

dst = src1 * alpha + src2 * beta + gamma

Les deux images doivent avoir la même taille, alors redimensionnez-les.

import cv2

src1 = cv2.imread('data/src/lena.jpg')
src2 = cv2.imread('data/src/rocket.jpg')

src2 = cv2.resize(src2, src1.shape[1::-1])

Reportez-vous à l’article suivant pour obtenir la taille de l’image lue en tant que tableau NumPy ndarray.

L’image est alpha mélangée selon le deuxième paramètre alpha et le quatrième paramètre bêta.

Bien que les images soient enregistrées ici sous forme de fichiers, si vous pouvez les afficher dans une autre fenêtre, vous pouvez utiliser cv2.imshow() (par exemple :cv2.imshow('window_name', dst)) . Il en va de même pour l’exemple de code suivant.

dst = cv2.addWeighted(src1, 0.5, src2, 0.5, 0)

cv2.imwrite('data/dst/opencv_add_weighted.jpg', dst)

OpenCV addWeighted()

Le cinquième paramètre gamma est la valeur à ajouter à toutes les valeurs de pixel.

dst = cv2.addWeighted(src1, 0.5, src2, 0.2, 128)

cv2.imwrite('data/dst/opencv_add_weighted_gamma.jpg', dst)

OpenCV addWeighted() avec gamma

Comme vous pouvez le voir dans le résultat ci-dessus, il ne déborde pas même s’il dépasse la valeur maximale (255 pour uint8), mais il est à noter que certains types de données peuvent ne pas être gérés correctement.

Dans ce cas, utilisez la méthode clip() de ndarray. Voir la section sur le mélange alpha avec NumPy ci-dessous.

Masquage avec OpenCV :cv2.bitwise_and()

Utilisez cv2.bitwise_and() pour faire du masquage avec OpenCV.

dst = cv2.bitwise_and(src1, src2[, dst[, mask]])

cv2.bitwise_and() est une fonction qui effectue un traitement ET au niveau du bit comme son nom l’indique. L’ET des valeurs pour chaque pixel des images d’entrée src1 et src2 est la valeur de pixel de l’image de sortie.

Ici, une image en niveaux de gris est utilisée comme image de masque pour 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)

OpenCV bitwise_and()

Lorsque le fichier image est lu, le type de données est uint8 (entier 8 bits non signé : 0-255), le noir indique la valeur de pixel 0 (0b00000000 en binaire), le blanc indique la valeur de pixel 255 (0b11111111 en binaire) ).

Dans le cas de uint8, le résultat de l’opération sur les bits est facile à comprendre, mais dans le cas du nombre à virgule flottante float, il est à noter que l’opération sur les bits est effectuée en notation binaire et que le résultat est inattendu.

Il peut être plus facile de comprendre le traitement du masque avec NumPy décrit plus tard.

En plus de cv2.bitwise_and(), OpenCV inclut également cv2.bitwise_or(), cv2.bitwise_xor() et cv2.bitwise_not() pour effectuer les opérations OR, XOR et NOT.

Mélange Alpha avec NumPy

Étant donné que NumPy peut facilement effectuer des opérations arithmétiques pour chaque pixel du tableau, le mélange alpha peut également être réalisé avec une simple expression.

Ici, les fichiers image sont lus en tant que tableau NumPy ndarray à l’aide de Pillow. Le redimensionnement se fait également par la méthode de Pillow.

Les fichiers image sont lus comme ndarray avec cv2.imread() d’OpenCV, donc peu importe quel OpenCV ou Pillow est utilisé, mais sachez que l’ordre des couleurs est différent.

Étant donné que l’opération de ndarray et de la valeur scalaire est l’opération de la valeur de chaque élément et de la valeur scalaire, le mélange alpha peut être calculé comme suit. Soyez prudent lors de l’enregistrement en tant que fichier image avec Pillow, car le type de données est converti automatiquement.

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

Mélange alpha d'image NumPy

Notez que lors de l’enregistrement en tant que fichier jpg avec la méthode save() de Pillow, vous pouvez spécifier la qualité avec l’argument qualité (il est omis dans l’exemple, il reste donc la valeur par défaut).

C’est également facile si vous souhaitez ajouter des valeurs à chaque pixel de manière uniforme, comme le paramètre gamma dans cv2.addWeighted() d’OpenCV. Différentes valeurs peuvent être ajoutées à chaque couleur comme suit. Comme mentionné ci-dessus, notez que l’ordre des couleurs diffère selon la façon dont le fichier image est lu.

Utilisez clip() pour découper les valeurs de pixel dans la plage de 0 à 255. Notez que des résultats inattendus se produisent lors de l’enregistrement en tant que fichier image si une valeur dépasse la valeur maximale 255 de uint8.

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

Mélange alpha d'image NumPy avec gamma

Masquage avec NumPy

Le masquage est facile avec les opérations de tableau de NumPy.

Les opérations arithmétiques de tableaux de même forme sont des opérations pour chaque pixel à la même position.

L’image en niveaux de gris lue comme uint8 a 0 pour le noir et 255 pour le blanc. En divisant cela par 255, le noir devient 0,0 et le blanc devient 1,0, et en multipliant cela avec l’image d’origine, il ne reste que la partie blanche 1,0 et le traitement du masque peut être réalisé.

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

Masque d'image NumPy

Dans cet exemple, si dst = src * mask / 255, src * mask est d’abord calculé comme uint8, et la valeur est arrondie puis divisée par 255, ce qui n’est pas le résultat attendu.

C’est OK si dst = src * (mask / 255) ou dst = mask / 255 * src.

Si vous ne voulez pas tenir compte de l’ordre, vous pouvez lancer tous les tableaux pour qu’ils flottent puis fonctionnent. Il peut y avoir moins d’erreurs.

Soyez prudent si l’image du masque est une image en niveaux de gris et un ndarray 2D (sans dimension de couleur). Si la multiplication est effectuée telle quelle, une erreur se produit.

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 dispose d’un mécanisme appelé diffusion qui effectue des opérations en convertissant automatiquement des tableaux de différentes dimensions et formes, le cas échéant. Cependant, selon le message d’erreur, la diffusion n’est pas effectuée de manière appropriée par la combinaison des exemples ci-dessus.

Il diffusera bien si vous ajoutez une dimension supplémentaire à 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 forme du tableau d’origine est décompressée et transmise à reshape().

Une autre façon consiste à utiliser np.newaxis au lieu de reshape().

# mask = mask[:, :, np.newaxis]

Mélange et masquage alpha complexes avec NumPy

Dans l’exemple du mélange alpha ci-dessus, l’image a été composée selon un rapport uniforme sur toute la surface de l’image, mais en utilisant NumPy, il est possible de composer en fonction d’une autre image (tableau).

Utilisez l’image de dégradé suivante. Les images de gradation peuvent être générées à l’aide de NumPy.

image dégradée

Il peut être composé par une opération simple. Une image est générée dans laquelle la valeur alpha (rapport de fusion) change en fonction de la valeur de pixel de l’image de gradation.

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

Dégradé de mélange alpha de l'image NumPy

C’est aussi facile si vous voulez masquer avec une autre image.

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

Masque de dégradé de mélange alpha d'image NumPy

Création d’image de masque par dessin OpenCV

Des images de masque géométrique peuvent être créées à l’aide de la fonction de dessin OpenCV.

Il est possible de générer un ndarray de même forme que l’image à traiter par np.zeros_like() et dans lequel tous les éléments valent 0. Il correspond à une image noire de même taille que l’image d’origine.

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

Vous pouvez également spécifier la taille avec np.zeros().

Ici, dessinez des figures avec la fonction de dessin d’OpenCV. Un rectangle utilise cv2.rectangle(), un cercle utilise cv2.circle() et un polygone utilise cv2.fillConvexPoly(). Les rectangles et cercles seront remplis si épaisseur=-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)

Masque de tirage OpenCV

Lorsque le traitement de lissage (flou) est effectué à l’aide d’une fonction telle que cv2.GaussianBlur(), la frontière devient lisse, de sorte qu’il est possible d’effectuer une synthèse douce par masquage.

Spécifiez la taille du noyau dans les directions x et y en tant que tuple dans le deuxième paramètre de cv2.GaussianBlur(). À mesure que chaque valeur est augmentée, la largeur de flou dans cette direction est augmentée. La valeur doit être impaire. Le troisième paramètre spécifie la valeur de l’écart type gaussien. S’il vaut 0, il est calculé automatiquement. Notez qu’il ne peut pas être omis.

Pour les autres fonctions de lissage, reportez-vous au document officiel ci-dessous.

mask_blur = cv2.GaussianBlur(mask, (51, 51), 0)

cv2.imwrite('data/dst/opencv_draw_mask_blur.jpg', mask_blur)

Masque de dessin OpenCV flou

dst = src * (mask_blur / 255)

cv2.imwrite('data/dst/opencv_draw_mask_blur_result.jpg', dst)

Flou de mélange d'image NumPy

Notez que si la partie dst = src * (mask_blur / 255) est dst = src * mask_blur / 255, le résultat ne sera pas celui attendu. Voir la section Masquage avec NumPy.

De plus, si le ndarray utilisé comme masque est un tableau à deux dimensions (pas de dimension de couleur), il ne peut pas être calculé sans ajouter une dimension supplémentaire. Voir aussi la section Masquage avec NumPy.