[PYTHON] Behandeln Sie transparente Bilder mit OpenCV-Make Sprites Dance-

Einführung

Lassen Sie uns mit OpenCV transparente PNG-Bilder verarbeiten und so etwas wie das Sprite des alten Hobby-Computers machen. Diejenigen, die ernsthaft ein Spiel mit Sprites machen wollen, sollten Pygame verwenden. Ich möchte nur OpenCV studieren.

Grundlegendes RGBA-Element

Hier wird Irasutoyas "Charaktere des Geschwaders, die eine entscheidende Pose einnehmen (Gruppe)" (reduzierte Version) verwendet. Der Hintergrund ist transparent.

sentai.png
sentai.png

Geben Sie zum Erfassen eines RGBA-Bildes * flags = cv2.IMREAD_UNCHANGED * in cv2.imread () an. Eigentlich müssen Sie sich nicht an einen solchen Zauber erinnern, sondern geben nur -1 als zweites Argument an. Wenn das Argument auf 1 gesetzt oder weggelassen wird, wird es als RGB-3-Kanal-Bild erfasst.

python


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

Wenn dieses Bild mit "cv2.imshow ()" angezeigt wird, wird der Hintergrund schwarz. Wenn Sie mit einer Zeichensoftware ein Bild auf eine transparente Leinwand zeichnen, wird der transparente Teil als schwarz behandelt, da kein Farbelement vorhanden ist. Alle RGB-Komponenten sind Null = (0,0,0) = Schwarz.

Nehmen Sie jedes Element von RGBA heraus

Da ich oft schreibe, werden OpenCV-Bilder im numpy.ndarray-Format gespeichert, sodass nicht nur jedes Farbelement zugeschnitten, sondern auch extrahiert werden kann.

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()
Das Originalbild Ein Element
sentai.png imgA.png
B-Element G-Element R-Element
imgB.png imgG.png imgR.png

Jedes Element hat die Form ndim = 2 (Höhe, Breite). Wenn daher jedes Element als Bild angezeigt wird, wird es zu einer Graustufe. Aka Ranger ist zum Beispiel (G, B, R) = (21,11,213), daher ist die Helligkeit von Rot ziemlich hoch, während die Helligkeit von Blau und Grün niedrig ist. Andererseits ist, da der Killer (G, B, R) = (0,171,247) ist, die Helligkeit von Rot höher als die des Akarangers, und das grüne Element ist auch so hoch wie es ist. Außerdem ist der Alpha-Wert 0 für transparent und 255 für undurchsichtig. Es scheint besser, sich daran als Deckkraft als an Transparenz zu erinnern.

Um dann jedes Farbelement in jeder Farbe anzuzeigen, erstellen Sie ein neues RGB-Bild mit anderen Farbkomponenten, die auf 0 gesetzt sind.

Methode 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()

Es gibt auch eine Möglichkeit, die Helligkeit unnötiger Farben im ursprünglichen RGB-Bild auf 0 zu reduzieren.

Methode 2


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

imgB = img3.copy()
imgB[:, :, (1,2)] = 0  # 3ch(BGR)1. von(G)Und zweitens(R)Auf 0
imgG = img3.copy()
imgG[:, :, (0,2)] = 0  # 3ch(BGR)0 ..(B)Und zweitens(R)Auf 0
imgR = img3.copy()
imgR[:, :, (0,1)] = 0  # 3ch(BGR)0 ..(B)Und der erste(G)Auf 0

cv2.imshow("imgB", imgB)
cv2.imshow("imgG", imgG)
cv2.imshow("imgR", imgR)
cv2.waitKey(0)
cv2.destroyAllWindows()
B-Element in blau G-Element in grün R-Element in rot
imgB.png imgG.png imgR.png

Bilder kombinieren

Ab hier ist die Produktion. Das Hintergrundbild ist "Illustration des Universums (Hintergrundmaterial)". Es ist ein JPEG-Bild und hat keinen Alpha-Wert. Das im Hintergrund überlagerte Bild lautet "Astronautenillustration" (reduzierte Version).

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

Vorbereitungen

Erstellen Sie ein RGB-Bild und ein Maskenbild aus einem RGBA-Bild. Da das zuvor als "A-Element" erwähnte ein Kanal ist, kann es nicht mit dem Hintergrundbild kombiniert werden. Erstellen Sie ein Maskenbild des RGB3-Kanals auf die gleiche Weise wie das B-Element blau und das R-Element rot.

python


import cv2

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

img3 = img4[:, :, :3]  #Die ersten drei von RGBA, nämlich RGB
mask1 = img4[:, :, 3]  #Der dritte von RGBA, der von 0 zählt, dh A.
mask3 = cv2.merge((mask1, mask1, mask1)) #3-Kanal-RGB-Bild
Originalbild (RGBA) RGB-Bild Maskenbild
4.png 3.png mask3.png

Schneiden Sie außerdem ein Bild mit der gleichen Größe wie der Vordergrund aus dem Hintergrund aus.

Methode 1 Stellen Sie die transparente Farbe ein

Wenn Sie ein Vordergrundbild mit der Aufschrift "Diese Farbe ist nicht Teil des Hauptbilds, sondern wird nur als Hintergrund verwendet" haben, können Sie mit "numpy.where ()" die Transparenz festlegen. Es ist wie ein Chroma-Key.

python


#
transparence = (0,0,0)
result = np.where(front==transparence, back, front)
back front result
back.jpg 3.png result.jpg

x and 1 = x   x and 0 = 0   x or 1 = 1   x or 0 = x Rücken und Vorderseite müssen dieselbe Form haben Einfach ist einfach, aber stellen Sie beispielsweise in diesem Bild von Irasutoya im Voraus sicher, dass "(0,0,0)" nicht für die schwarzen Haare des Astronauten verwendet wird. Es ist notwendig, es zu tun. Wenn Sie versagen, erhalten Sie so etwas wie Transparentes Gachapin. Methode 2 Maskenverarbeitung Die Antwort wird sofort angezeigt, wenn Sie sich andere Websites ansehen. Versuchen wir jedoch, Fehler beim Studium zu machen. Führen Sie konstante Ausdrücke in logischen Operationen nicht nur für Boolesche Werte von 0 und 1, sondern auch für beliebige Werte aus. Die Helligkeit wird in 8 Bit ausgedrückt. Wenn Sie sie also grob schreiben x (beliebige Farbe) und 255 (weiß) = x (beliebige Farbe) x (beliebige Farbe) und 0 (schwarz) = 0 (schwarz) x (beliebige Farbe) oder 255 (weiß) = 255 (weiß) x (beliebige Farbe) oder 0 (schwarz) = x (beliebige Farbe) Das ist. Die folgende Tabelle wurde von Hand erstellt. Es tut mir leid, wenn Fehler auftreten.

No back Berechnung 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

Nr. 1 und Nr. 4 werden wahrscheinlich in diesem Stadium verwendet. Lassen Sie uns das Vordergrundbild mit ihnen synthetisieren.

No tmp Berechnung front result Auswertung
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 ×

Die richtigen Antworten waren also 1-4 und 4-1. "Vordergrundbild mit schwarzem Hintergrund" und "Maskenbild mit schwarzem Hintergrund und weißem Vordergrund" wurden im Voraus vorbereitet, konnten aber nicht alleine kombiniert werden. In 1-4 war "Vordergrundbild mit weißem Hintergrund" erforderlich, und in 4-1 war "Maskenbild mit weißem Hintergrund und schwarzem Vordergrund" erforderlich. Das wahre Leben kann nicht verlassen werden.

Entsprechung zur Darstellung außerhalb des Bildes

Wenn Sie sich Sprite nennen, müssen Sie es außerhalb des Bereichs des Hintergrundbilds beschreiben können. Ich wollte einen Kommentar schreiben, aber ich habe bereits den vorherigen Artikel geschrieben "Funktion zum Zeichnen japanischer Schriftarten mit OpenCV allgemein machen" 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), daher wird die Erklärung weggelassen.

Methode 3 Verwenden Sie PIL

Obwohl ich OpenCV studiere, kann ich nicht anders, als PIL zu berühren.

Fügen Sie das Bild in das Bild ein. Image.paste (im, box = None, mask = None)

Um ein transparentes Bild mit einem Bild zu kombinieren, geben Sie einfach "Image.paste (im, box, im)" und dieselben Argumente für das erste und dritte an. Es ist PIL und ich möchte sagen, was ich bisher mit OpenCV gemacht habe.

Vergleich der Ausführungsgeschwindigkeit

Bisher wurden drei Methoden gezeigt. Außerdem habe ich im vorherigen Artikel gelernt, wie man nur die erforderlichen Teile PIL, anstatt das gesamte Bild zu verarbeiten. Sehen wir uns also die Ausführungsgeschwindigkeit mit den folgenden vier selbst erstellten Funktionen an.

--putSprite_npwhere Legen Sie die transparente Farbe fest und kombinieren Sie die Bilder mit np.where. Es gibt Unterstützung außerhalb des Hintergrundbildes. --putSprite_mask Setzt das Maskenbild und kombiniert. Es gibt Unterstützung außerhalb des Hintergrundbildes. --putSprite_pil_all Mit PIL synthetisieren. Machen Sie das gesamte Hintergrundbild PIL. Es ist nicht erforderlich, sich mit der Außenseite des Hintergrundbilds zu befassen. --putSprite_pil_roi Mit PIL synthetisieren. Nur der für die Synthese notwendige Teil wird zu PIL verarbeitet. Wenn Sie den ROI einstellen, entspricht er der Außenseite des Hintergrundbilds.

Der auszuführende Inhalt ist wie folgt. Ich schneide den Rahmen des animierten GIF, um die Kapazität zu reduzieren. Die Quelle ist lang, also unten. uchuhikoushi.gif

Ergebnis

Durchschnittswert, wenn jeder 10-mal ausgeführt wird.

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

Abgesehen von der Tatsache, dass das Programm nicht optimiert ist, mein Computer langsam ist und Python in erster Linie langsam ist, fällt die Langsamkeit von PIL auf. Es wurde auch festgestellt, dass selbst wenn PIL verwendet wird, die Geschwindigkeit signifikant verbessert werden kann, wenn nur der minimale ROI verwendet wird. Ich freue mich sehr über die Kommentare, die ich in [dem letzten Artikel] erhalten habe (https://qiita.com/mo256man/items/82da5138eeacc420499d).

Masking und np.where () sind schneller als PIL. np.where () ist schnell genug, aber etwas langsamer als das Maskieren, da es intern Entscheidungen trifft. Es ist notwendig, ein Maskenbild für die Maskenverarbeitung vorzubereiten, aber es ist wahrscheinlich das leichteste in Bezug auf die Verarbeitung, da es nur das Bild überschreibt.

Vorsichtsmaßnahmen

Masking und np.where () können nicht für durchscheinende Bilder verwendet werden.

Da die Maskenverarbeitung 0 oder 1 ist, gilt die Äquivalenzformel, und wenn sie durchscheinend ist, wird sie mit einem Wert berechnet, der weder 0 noch 1 ist. x (Hintergrundfarbe) und a (halbfertige Maske) = tmp (seltsame Farbe) tmp (seltsame Farbe) oder y (Vordergrundfarbe) = z (unerwartete Farbe) Es ist natürlich, dass es wird.

Ich habe verschiedene Dinge ausprobiert und dachte, dass "np.where ()" je nach Gerät durchscheinend sein könnte, aber zumindest konnte ich es nicht.

Vorschau beim nächsten Mal

Als nächstes möchte ich die Transluzenz und die Unterstützung für die Rotation herausfordern.

Quelle

Ich habe etwas über die Funktion eval () gelernt, die einen String als Python-Befehl verwendet, kurz bevor ich diesen Artikel gepostet habe. Referenz: Python - Dynamische Aufruffunktion von Zeichenfolge

python


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

# numpy.Synthetisieren mit wo
#Das RGBA-Bild wird aufgrund der Beziehung zu anderen Funktionen importiert
#Es werden nur RGB-Elemente verwendet, keine Alpha-Werte.
#Transparente Farbe(0,0,0)Es ist eine definitive Entscheidung, aber keine gute Idee.
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

#Maske
#Das Maskenbild wird jedes Mal aus dem RGBA-Bild in der Funktion erstellt.
#Es ist schneller, ein Maskenbild im Voraus zu erstellen.
#Dies ist einfacher zu bedienen. Ich denke.
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

#Ganzes Hintergrundbild zusammengesetzt mit 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)

#Nur der Teil im Hintergrundbild soll mit PIL synthetisiert werden
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))

    ###Beginnen Sie von hier aus, um die Zeit zu messen
    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))
        
        #Dies kann nach Bedarf aktiviert oder deaktiviert werden
        #cv2.imshow(func, img)
        #cv2.waitKey(1)

    elasped_time = time.time() - start_time
    ###Bisher

    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

Behandeln Sie transparente Bilder mit OpenCV-Make Sprites Dance-
Behandle Excel mit Python
Behandle Rabbimq mit Python
Sprites mit OpenCV drehen