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.
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 |
---|
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.
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 | |
---|---|---|
Élément B | Élément G | Élément R |
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 |
---|---|---|
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 |
---|
uchuhikoushi.png |
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 |
---|---|---|
Découpez également une image de la même taille que le premier plan de l'arrière-plan.
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 |
---|---|---|
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.
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 | OR | → | |||
2 | AND | → | |||
3 | OR | → | |||
4 | AND | → |
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 | OR | → | × | |||
1-2 | AND | → | × | |||
1-3 | OR | → | × | |||
1-4 | AND | → | ○ | |||
4-1 | OR | → | ○ | |||
4-2 | AND | → | × | |||
4-3 | OR | → | × | |||
4-4 | AND | → | × |
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é.
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ême si j'étudie OpenCV, je ne peux pas m'empêcher de toucher PIL.
(x, y)
. Heureusement, cela peut être en dehors de la plage de l'image d'origine. La valeur par défaut est "Aucun", qui est en haut à gauche. Il existe également une méthode à spécifier avec un taple à 4 éléments, mais qui est omise.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.
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.
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.
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.
Ensuite, je voudrais contester la translucidité et le soutien à la rotation.
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)