[PYTHON] Gérez les images transparentes avec OpenCV - Faites danser les sprites

introduction

Traitons les images png transparentes avec OpenCV et faisons quelque chose comme le sprite de l'ancien ordinateur de loisir. Ceux qui veulent vraiment créer un jeu en utilisant des sprites devraient utiliser pygame. Je veux juste étudier OpenCV.

Élément RGBA de base

Ici, les "[Personnages de l'escadron qui prennent une pose décisive (groupe)] d'Irasutoya (https://www.irasutoya.com/2016/01/blog-post_293.html)" (version réduite) sont utilisés. Le fond est transparent.

sentai.png
sentai.png

Pour importer une image RGBA, spécifiez * flags = cv2.IMREAD_UNCHANGED * dans cv2.imread (). En fait, vous n'avez pas à vous souvenir d'un tel sort, spécifiez simplement -1 comme deuxième argument. Si l'argument est défini sur 1 ou omis, il sera capturé en tant qu'image RVB à 3 canaux.

python


import cv2
filename = "sentai.png "
img4 = cv2.imread(filename, -1)
img3 = cv2.imread(filename)
print (img4.shape)  #résultat:(248, 300, 4)
print (img3.shape)  #résultat:(248, 300, 3)

Lorsque cette image est affichée avec cv2.imshow (), l'arrière-plan devient noir. Lorsque vous dessinez une image sur un canevas transparent avec un logiciel de dessin, la partie transparente est traitée comme noire car il n'y a pas d'élément de couleur. Tous les composants RVB sont zéro = (0,0,0) = noir.

Retirez chaque élément de RGBA

Comme j'écris plusieurs fois, les images OpenCV sont stockées au format numpy.ndarray, donc non seulement le rognage mais aussi l'extraction de chaque élément de couleur peuvent être effectués par découpage.

python


import cv2
filename = "sentai.png "
img4 = cv2.imread(filename, -1)
b = img4[:, :, 0]
g = img4[:, :, 1]
r = img4[:, :, 2]
a = img4[:, :, 3]

cv2.imshow("img4", img4)
cv2.imshow("b", b)
cv2.imshow("g", g)
cv2.imshow("r", r)
cv2.imshow("a", a)
cv2.waitKey(0)
cv2.destroyAllWindows()
L'image originale Un élément
sentai.png imgA.png
Élément B Élément G Élément R
imgB.png imgG.png imgR.png

Chaque élément a la forme ndim = 2, c'est-à-dire (hauteur, largeur). Par conséquent, lorsque chaque élément est affiché sous forme d'image, il devient une échelle de gris. Aka Ranger est, par exemple, (G, B, R) = (21,11,213), donc la luminosité du rouge est assez élevée, tandis que la luminosité du bleu et du vert est faible. D'autre part, puisque le tueur est (G, B, R) = (0,171,247), la luminosité du rouge est plus élevée que celle de l'akaranger, et l'élément vert est également élevé tel quel. En outre, la valeur alpha est 0 pour transparent et 255 pour opaque. Il semble préférable de s'en souvenir comme de l'opacité plutôt que de la transparence.

Ensuite, comment afficher chaque élément de couleur dans chaque couleur consiste à créer une nouvelle image RVB avec d'autres composants de couleur définis sur 0.

Méthode 1


import cv2
import numpy as np
filename = "sentai.png "
img3 = cv2.imread(filename)

b = img3[:, :, 0]
g = img3[:, :, 1]
r = img3[:, :, 2]
z = np.full(img3.shape[:2], 0, np.uint8)
imgB = cv2.merge((b,z,z))
imgG = cv2.merge((z,g,z))
imgR = cv2.merge((z,z,r))

cv2.imshow("imgB", imgB)
cv2.imshow("imgG", imgG)
cv2.imshow("imgR", imgR)
cv2.waitKey(0)
cv2.destroyAllWindows()

Il existe également un moyen de réduire la luminosité des couleurs inutiles à 0 dans l'image RVB d'origine.

Méthode 2


import cv2
import numpy as np
filename = "sentai.png "
img3 = cv2.imread(filename)

imgB = img3.copy()
imgB[:, :, (1,2)] = 0  # 3ch(BGR)1er de(G)Et deuxieme(R)À 0
imgG = img3.copy()
imgG[:, :, (0,2)] = 0  # 3ch(BGR)0ème(B)Et deuxieme(R)À 0
imgR = img3.copy()
imgR[:, :, (0,1)] = 0  # 3ch(BGR)0ème(B)Et le premier(G)À 0

cv2.imshow("imgB", imgB)
cv2.imshow("imgG", imgG)
cv2.imshow("imgR", imgR)
cv2.waitKey(0)
cv2.destroyAllWindows()
Élément B en bleu Élément G en vert Élément R en rouge
imgB.png imgG.png imgR.png

Combiner des images

De là, c'est la production. L'image d'arrière-plan est "Illustration de l'univers (matériel de base)". C'est une image jpeg et n'a pas de valeur alpha. L'image superposée sur l'arrière-plan est "Illustration de l'astronaute" (version réduite).

space.jpg
space.jpg
uchuhikoushi.png
4.png

Préparation préalable

Créez une image RVB et une image de masque à partir d'une image RVBA. Étant donné que celui mentionné précédemment comme "Un élément" est un canal, il ne peut pas être combiné avec l'image d'arrière-plan. Créez une image de masque du canal RGB3 de la même manière que pour rendre l'élément B bleu et l'élément R rouge.

python


import cv2

filename = "uchuhikoushi.png "
img4 = cv2.imread(filename, -1)

img3 = img4[:, :, :3]  #Les trois premiers de RGBA, à savoir RVB
mask1 = img4[:, :, 3]  #Le troisième de RGBA, à partir de 0, c'est-à-dire A
mask3 = cv2.merge((mask1, mask1, mask1)) #Image RVB 3 canaux
Image originale (RGBA) Image RVB Image de masque
4.png 3.png mask3.png

Découpez également une image de la même taille que le premier plan de l'arrière-plan.

Méthode 1 Définissez la couleur transparente

Si vous avez une image de premier plan qui dit "Cette couleur ne fait pas partie de l'image principale, elle est juste utilisée comme arrière-plan", vous pouvez utiliser numpy.where () pour définir la transparence. C'est comme une clé chroma. Si vous n'écrivez que les éléments minimaux, ce sera comme ça.

python


#le dos et le devant doivent avoir la même forme
transparence = (0,0,0)
result = np.where(front==transparence, back, front)
back front result
back.jpg 3.png result.jpg

C'est facile, mais par exemple, dans cette image d'Irasutoya, il faut confirmer à l'avance que «(0,0,0)» n'est pas utilisé pour les cheveux noirs de l'astronaute. Si vous échouez, vous vous retrouverez avec quelque chose comme Transparent Gachapin.

Méthode 2 Traitement du masque

Si vous regardez d'autres sites, la réponse apparaîtra immédiatement, mais essayons de faire des erreurs d'étude. Expression constante en opération logique   x and 1 = x   x and 0 = 0   x or 1 = 1   x or 0 = x Est effectué pour des valeurs arbitraires, pas seulement pour des valeurs booléennes de 0 et 1. La luminosité est exprimée en 8 bits, donc si vous l'écrivez grossièrement x (n'importe quelle couleur) et 255 (blanc) = x (n'importe quelle couleur) x (couleur arbitraire) et 0 (noir) = 0 (noir) x (couleur arbitraire) ou 255 (blanc) = 255 (blanc) x (couleur arbitraire) ou 0 (noir) = x (couleur arbitraire) C'est. Le tableau ci-dessous a été fait à la main, donc je suis désolé s'il y a des erreurs.

No back Calcul mask tmp
1 back.jpg OR mask3.png result1.jpg
2 back.jpg AND mask3.png result2.jpg
3 back.jpg OR mask3_inv.png result3.jpg
4 back.jpg AND mask3_inv.png result4.jpg

Les numéros 1 et 4 seront probablement utilisés à ce stade. Synthétisons l'image de premier plan avec eux.

No tmp Calcul front result Évaluation
1-1 result1.jpg OR 3.png result1.jpg ×
1-2 result1.jpg AND 3.png 3.png ×
1-3 result1.jpg OR front3_white.png result5.jpg ×
1-4 result1.jpg AND front3_white.png result.jpg
4-1 result4.jpg OR 3.png result.jpg
4-2 result4.jpg AND 3.png result6.jpg ×
4-3 result4.jpg OR front3_white.png front3_white.png ×
4-4 result4.jpg AND front3_white.png result4.jpg ×

Les bonnes réponses étaient donc 1-4 et 4-1. "Image de premier plan avec fond noir" et "Image de masque avec fond noir et premier plan blanc" ont été préparées à l'avance, mais elles n'ont pas pu être combinées par elles-mêmes. En 1-4, "image de premier plan avec fond blanc" était requise, et en 4-1 "image de masque avec fond blanc et premier plan noir" était requise. La vraie vie est quelque chose qui ne peut pas être laissé.

Correspondance avec la représentation en dehors de l'image

Si vous vous appelez un sprite, vous devez être capable de le décrire en dehors de la plage de l'image d'arrière-plan. J'allais écrire un commentaire, mais déjà dans l'article précédent ["Faire la fonction de dessin de polices japonaises avec OpenCV général"](https://qiita.com/mo256man/items/b6e17b5a66d1ea13b5e3#% E7% 94% BB% E5% 83% 8F% E5% A4% 96% E3% 81% B8% E3% 81% AE% E6% 8F% 8F% E5% 86% 99% E3% 81% B8% E3% 81% AE% E5% AF% BE% E5% BF% 9C), donc l'explication est omise.

Méthode 3 Utiliser PIL

Même si j'étudie OpenCV, je ne peux pas m'empêcher de toucher PIL.

Collez l'image sur l'image Image.paste (im, box = None, mask = None)

Pour combiner une image transparente avec une image, spécifiez simplement ʻImage.paste (im, box, im) `et les mêmes arguments pour le premier et le troisième. C'est PIL, et cela me donne envie de dire ce que j'ai fait avec OpenCV jusqu'à présent.

Comparaison de la vitesse d'exécution

Jusqu'à présent, trois méthodes ont été présentées. De plus, dans l'article précédent, j'ai appris à PIL uniquement les parties nécessaires au lieu de PIL traitant l'image entière. Voyons donc la vitesse d'exécution avec les quatre fonctions auto-créées suivantes.

--putSprite_npwhere Définit la couleur transparente et combine les images avec np.where. Il y a un support en dehors de l'image d'arrière-plan. --putSprite_mask Définit l'image du masque et combine. Il y a un support en dehors de l'image d'arrière-plan. --putSprite_pil_all Synthétiser avec PIL. Rendre l'image d'arrière-plan entière PIL. Il n'est pas nécessaire de traiter l'extérieur de l'image d'arrière-plan. --putSprite_pil_roi Synthétiser avec PIL. Seule la partie nécessaire à la synthèse est transformée en PIL. Lors du réglage du ROI, il correspond à l'extérieur de l'image d'arrière-plan.

Le contenu à exécuter est comme ça. Je coupe le cadre du GIF animé pour réduire la capacité. La source est longue, donc en bas. uchuhikoushi.gif

résultat

Valeur moyenne lorsque chacun est exécuté 10 fois.

putSprite_npwhere : 0.265903830528259 sec
putSprite_mask    : 0.213901996612548 sec
putSprite_pil_all : 0.973412466049193 sec
putSprite_pil_roi : 0.344804096221923 sec

Outre le fait que le programme n'est pas optimisé, ma machine est lente, et Python est lent en premier lieu, la lenteur de PIL se démarque. Il a également été constaté que même si PIL est utilisé, la vitesse peut être considérablement améliorée si seul le retour sur investissement minimum est utilisé. J'apprécie vraiment les commentaires que j'ai reçus dans le dernier article.

Le masquage et «np.where ()» sont plus rapides que PIL. np.where () est assez rapide, mais légèrement plus lent que le masquage car il prend des décisions en interne. Il est nécessaire de préparer une image de masque pour le traitement de masque, mais c'est probablement la plus légère en termes de traitement car elle n'écrase que l'image.

Remarques

Le masquage et «np.where ()» ne peuvent pas être utilisés pour les images translucides.

Étant donné que le traitement du masque est 0 ou 1, la formule d'équivalence est valable et, si elle est translucide, c'est-à-dire qu'elle est calculée avec une valeur qui n'est ni 0 ni 1. x (couleur d'arrière-plan) et a (masque semi-fini) = tmp (couleur étrange) tmp (couleur étrange) ou y (couleur de premier plan) = z (couleur inattendue) Il est naturel que cela devienne.

J'ai essayé différentes choses, pensant que «np.where ()» pouvait être translucide selon l'appareil, mais au moins je ne pouvais pas.

Aperçu de la prochaine fois

Ensuite, je voudrais contester la translucidité et le soutien à la rotation.

La source

Je connaissais la fonction «eval ()» qui utilise une chaîne comme commande Python juste avant de publier cet article. Référence: Python - Fonction d'appel dynamique à partir de la chaîne

python


import cv2
import numpy as np
from PIL import Image
import time
import math

# numpy.Synthétiser avec où
#L'image RGBA est importée en raison de la relation avec d'autres fonctions
#Seuls les éléments RVB sont utilisés, pas les valeurs alpha.
#Couleur transparente(0,0,0)C'est une décision définitive, mais ce n'est pas une bonne idée.
def putSprite_npwhere(back, front4, pos):
    x, y = pos
    fh, fw = front4.shape[:2]
    bh, bw = back.shape[:2]
    x1, y1 = max(x, 0), max(y, 0)
    x2, y2 = min(x+fw, bw), min(y+fh, bh)
    if not ((-fw < x < bw) and (-fh < y < bh)) :
        return back
    front3 = front4[:, :, :3]
    front_roi = front3[y1-y:y2-y, x1-x:x2-x]
    roi = back[y1:y2, x1:x2]
    tmp = np.where(front_roi==(0,0,0), roi, front_roi)
    back[y1:y2, x1:x2] = tmp
    return back

#Masque
#L'image de masque est créée à chaque fois à partir de l'image RGBA dans la fonction.
#Il est plus rapide de créer une image de masque à l'avance,
#C'est plus facile à utiliser. Je pense.
def putSprite_mask(back, front4, pos):
    x, y = pos
    fh, fw = front4.shape[:2]
    bh, bw = back.shape[:2]
    x1, y1 = max(x, 0), max(y, 0)
    x2, y2 = min(x+fw, bw), min(y+fh, bh)
    if not ((-fw < x < bw) and (-fh < y < bh)) :
        return back
    front3 = front4[:, :, :3]
    mask1 = front4[:, :, 3]
    mask3 = 255 - cv2.merge((mask1, mask1, mask1))
    mask_roi = mask3[y1-y:y2-y, x1-x:x2-x]
    front_roi = front3[y1-y:y2-y, x1-x:x2-x]
    roi = back[y1:y2, x1:x2]
    tmp = cv2.bitwise_and(roi, mask_roi)
    tmp = cv2.bitwise_or(tmp, front_roi)
    back[y1:y2, x1:x2] = tmp
    return back

#Image d'arrière-plan entière composée avec PIL
def putSprite_pil_all(back, front4, pos):
    back_pil = Image.fromarray(back)
    front_pil = Image.fromarray(front4)
    back_pil.paste(front_pil, pos, front_pil)
    return np.array(back_pil, dtype = np.uint8)

#Seule la partie de l'image d'arrière-plan à synthétiser avec PIL
def putSprite_pil_roi(back, front4, pos):
    x, y = pos
    fh, fw = front4.shape[:2]
    bh, bw = back.shape[:2]
    x1, y1 = max(x, 0), max(y, 0)
    x2, y2 = min(x+fw, bw), min(y+fh, bh)
    if not ((-fw < x < bw) and (-fh < y < bh)) :
        return back
    back_roi_pil = Image.fromarray(back[y1:y2, x1:x2])
    front_pil = Image.fromarray(front4[y1-y:y2-y, x1-x:x2-x])
    back_roi_pil.paste(front_pil, (0,0), front_pil)
    back_roi = np.array(back_roi_pil, dtype = np.uint8)
    back[y1:y2, x1:x2] = back_roi
    return back

def main(func):
    filename_back = "space.jpg "
    filename_front = "uchuhikoushi.png "
    img_back = cv2.imread(filename_back)
    img_front = cv2.imread(filename_front, -1)
    bh, bw = img_back.shape[:2]
    xc, yc = bw*0.5, bh*0.5
    rx, ry = bw*0.3, bh*1.2
    cv2.putText(img_back, func, (20,bh-20), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255))

    ###Commencez à partir d'ici pour mesurer le temps
    start_time = time.time()

    for angle in range(-180, 180):
        back = img_back.copy()
        x = int(xc + rx * math.cos(math.radians(angle)))
        y = int(yc + ry * math.sin(math.radians(angle)))
        img = eval(func)(back, img_front, (x,y))
        
        #Cela peut être activé ou désactivé selon les besoins
        #cv2.imshow(func, img)
        #cv2.waitKey(1)

    elasped_time = time.time() - start_time
    ###Jusque là

    print (f"{func} : {elasped_time} sec")    
    cv2.destroyAllWindows()
                
if __name__ == "__main__":
    funcs = ["putSprite_npwhere",
             "putSprite_mask",
             "putSprite_pil_all" ,
             "putSprite_pil_roi" ]
    for func in funcs:
        for i in range(10):
            main(func)

Recommended Posts

Gérez les images transparentes avec OpenCV - Faites danser les sprites
Gérez Excel avec python
Manipuler rabbimq avec python
Faire pivoter les sprites avec OpenCV