====== Pixel Art & Bildbearbeitung mit Python ====== **Hauptziele:** 1. Wichtigste Funktionalitäten der **Numpy-Library** anwenden können (v.a. Numpy-Arrays). 1. Mit **Jupyter-Notebook** arbeiten können. 1. Einfache **Bildbearbeitungsfunktionen** verstehen und programmieren. **Links:** * [[talit:python_advanced#numpy|Numpy Tutorial]] * [[talit:python_advanced#jupyter_notebook|Jupyter-Notebook Tutorial]] ===== Theorie ===== ==== Pixel ==== Ein Bild besteht aus lauter Pixel. Jedes Pixel wird durch vier Werte festgelegt: $$[R,G,B,A]$$ Also: Rot, Grün, Blau und Alpha. Jeder dieser Werte liegt im Bereich $0$ bis und mit $255$. Alpha gibt die **Transparenz** des Pixels an. Ein Pixel, welches Rot in voller Stärke leuchten soll, wird also durch $[255,0,0,255]$ beschrieben. === Transparenz === Nicht alle Bildformate unterstützen Transparenz, das populäre [[wpde>JPEG]]-Format (`*.jpg`) hat beispielsweise keinen Alpha-Kanal. Im folgenden wird der Alpha-Kanal darum teilweise weggelassen, dann sind die Pixel nicht transparent, ein Bildpunkt besteht nur aus drei statt vier Werten. === Beispiel === Betrachte das folgende Bild: {{ :talit:6_colors.png?200 |}} Es besteht aus $2 \times 3$, also insgesamt $6$ Pixel. In Python wird dieses Bild durch das folgende **Numpy-Array** beschrieben: img = np.array( [[[255,0,0,255], # red [0,255,0,255], # green [0,0,255,255]], # blue [[255,0,255,255], # pink [255,255,255,255], # white [0,0,0,255]]] # black ) Dieses Arrays ist also ein 3D-Array mit Shape (`img.shape`) $(2, 3, 4)$. ==== Bilder einlesen, anzeigen und speichern ==== Der folgende Code liest das Bild `input_image.png` (im gleichen Ordner wie Python-File) ein und speichert es unter dem Namen `output_image.png`. Achtung: Das Bild `output_image.png` wird bei jedem Ausführen überschrieben. import numpy as np from skimage import io # to read image import matplotlib.pyplot as plt # to show (and save) image import copy img_org = io.imread('input_image.png') # read image, is stored as numpy-array in variable img # make copy of image before you manipulate it img = copy.deepcopy(img_org) # here can manipulate img pass # show image plt.axis('off') # Turn off axis ticks and labels plt.tight_layout(pad=0) # Remove padding around the image plt.imshow(img) # For color images plt.show() # save image plt.savefig(`output_image.png`) ==== Bild mit Code zeichnen ==== Möchtest du einen Code schreiben, der dir ein Bild zeichnet? Dann ist es eine gute Idee, mit einem mit lauter Nullen gefüllten Numpy-Array mit der richtigen Dimension anzufangen: img = np.zeros((32,64,4),dtype=int) * Die erste Zahl gibt die Anzahl Pixel in vertikale Richtung ($y$) an. * Die zweite Zahl gibt die Anzahl Pixel in horizontale Richtung ($x$) an. * Die dritte Zahl gibt an, dass für jedes Pixel $4$ Werte (RGB und Alpha) benötigt werden. * `dtype=int` stellt sicher, dass im Array nur ganze Zahlen (integers) und keine Nachkommazahlen stehen. Ohne dies kann man die Transparenz (Alpha) nicht einstellen. ==== Bildbearbeitung ==== **Bild Original:** {{ :talit:grumpy_cat_small.png?300 |}} **Flipped** {{ :talit:grumpy_cat_flipped.png?300 |}} **Bildausschnitte I:** {{ :talit:grumpy_cat_section_1.png?150 |}} {{ :talit:grumpy_cat_section_2.png?150 |}} {{ :talit:grumpy_cat_section_3.png?150 |}} {{ :talit:grumpy_cat_section_4.png?150 |}} **Bildausschnitte II:** {{ :talit:grumpy_cat_sections.png?400 |}} **Verpixeln:** {{ :talit:grumpy_cat_pixellate.png?400 |}} **Grayscale:** {{ :talit:grumpy_cat_grayscale_1.png?300 |}} {{ :talit:grumpy_cat_grayscale_2.png?300 |}} **Black n' Write:** {{ :talit:grumpy_cat_black_n_white.png?300 |}} **Sepia:** {{ :talit:grumpy_cat_sepia.png?300 |}} **Invertieren:** {{ :talit:grumpy_cat_invert.png?300 |}} **Heller / Dunkler:** {{ :talit:grumpy_cat_bright.png?300 |}} {{ :talit:grumpy_cat_dark.png?300 |}} **Kontrast erhöht:** {{ :talit:grumpy_cat_contrast_high.png?300 |}} **Colors Andy Warhol-Style:** Farbkanal isolieren {{ :talit:grumpy_cat_warhol.png?600 |}} Beispiel oben links (rot): Für alle Pixel werden R und G channels auf $0$ gesetzt. ===== Aufgaben ===== ==== Aufgaben A: Pixel Art ==== === Aufgabe 0 === == Installieren == * Installiere die Jupyter Extension von Microsoft in VSCode * Installiere die benötigten Python Module (pip Befehl muss ev. angepasst werden): pip install numpy pip install scikit-image pip install matplotlib Falls dies nicht funktioniert, probiere (passe e.v. Python Version an!!!) py -3.11 -m pip install numpy py -3.11 -m pip install scikit-image py -3.11 -m pip install matplotlib == Git-Repo == 1. Erstelle auf GitHub ein **neues Repo** mit passendem Namen, z.B. "image\_processing\_python". 1. Gib das Repo den **LPs frei** (`anschae` / `tkilla77`). 1. Füge **alle Files**, die du im Verlaufe dieses Themas erstellen/bearbeiten wirst *unaufgefordert* dem Repo hinzu. 1. Commite und Pushe immer dann, wenn du Fortschritte gemacht hast. === Aufgabe 1: Numpy-Arrays === 1. Lade das Jupyter-Notebook {{ :talit:numpy_tutorial.ipynb.zip |}} herunter, entzippe es und füge es deinem Repo hinzu. Jupyter-Notebooks haben die Extension `.ipynb`. 1. Arbeite es dann durch. Die notwendige Theorie dazu findest du im [[talit:python_advanced#numpy|Numpy-Tutorial hier.]] Falls du etwas nicht verstehst, kannst du es dir z.B. von ChatGPT erklären lassen. === Aufgabe 2: Fun with Flags === 1. Studiere die [[image_processing_python#theorie|Theorie bis und mit "Bilder einlesen, anzeigen und speichern"]]. 1. Erstelle ein Jupyter-Notebook `pixel_art.ipynb` im Repo und arbeite darin. 1. Nehme das Array aus dem [[https://sca.ksr.ch/doku.php?id=talit:image_processing_python#pixel|Beispiel zu "Pixel"]], also `img = ...` und zeige das Bild mit matplotlib an. 1. Verändere es nun so, dass die italienische Flagge angezeigt wird (zu Ehren der Pizza und *nicht* Berlusconi): {{ :talit:italia.png?200 |}} 1. Wähle nun eine schöne (und einfache) [[wpde>Liste_der_Nationalflaggen|Flagge]] und zeichne sie selbst mithilfe eines Numpy-Arrays. === Aufgabe 3: Pixel Art === In der letzten Aufgabe hast du gesehen, dass es ziemlich mühsam ist, mit einem Numpy-Array ein Bild zu designen. Je grösser das Bild sein soll, desto schlimmer wird es. Wir wollen deshalb eine Funktion schreiben, die es uns erlaubt, auf einfache Art und weise ein Pixel Art Bild zu zeichnen. == Teil I == Schreibe deshalb eine Funktion `create_pixel_img_from_str(img_str)`, die eine Liste mit Strings, die das Bild beschreiben, entgegennimmt. Sie generiert dann das entsprechende Numpy-Array und gibt es zurück. Zum Beispiel soll der Code ... img_str = [ " rrrrr ", " rrrrrrrrr ", " kkkssks ", " ksksssksss ", " kskksssksss ", " kksssskkkk ", " sssssss ", " bbrbbb ", " bbbrbbrbbb ", " bbbbrbbrbbbb ", " ssbbrrrrbbss ", " sssryrryrsss ", " ssrrrrrrrrss ", " rrr rrr ", " kkk kkk ", " kkkk kkkk ", ] img = create_pixel_img_from_str(img_str) plt.axis('off') plt.tight_layout(pad=0) plt.imshow(img) plt.show() ... das folgende Bild erzeugen: {{ :talit:supermario_pixel.png?200 |}} Die Buchstaben stehen für die verschiedenen Farben, z.B. 'r' für Rot, 'y' für Gelb (Yellow), 's' für Haut (Skin). Ein Leerschlag steht für transparent ($[0,0,0,0]$). Es lohnt sich aber, den Code so zu schreiben, dass er eine Vielzahl an Farben beinhaltet. Du kannst gerne meine Farben übernehmen, diese abändern und weitere hinzu fügen. ++++Farben| TRANSPARENT = [0,0,0,0] BLACK = [0,0,0,255] WHITE = [255,255,255,255] RED = [255,0,0,255] GREEN = [0,255,0,255] BLUE = [0,0,255,255] YELLOW = [255,255,0,255] PINK = [255,0,255,255] CYAN = [0,255,255,255] SKIN = [240,184,160,255] ++++ ++++Tipps| * Erstelle ein 3D-Numpy-Array mit der richtigen Form. Die Werte sind egal, da sie nachher angepasst werden. * Gehe mit einer ersten Schleife alle Strings von `img_str` durch. * Gehe in einer darin verschachtelten Schleife alle Zeichen der einzelnen Strings durch. * Je nachdem, um welches Zeichen es sich handelt, wird ein anderes Pixel (also Liste mit $4$ Werten, RGB Alpha) an den richtigen Ort ins Array geschrieben. ++++ == Teil II == Mache damit tolle Pixel Art! === Aufgabe 4: GIF-Animation (Optional) === Erstelle mehrere Pixel Images wie in der Aufgabe vorher und animiere diese. Speichere sie dann als GIF. Der folgende Code dient als Inspiration: ++++Animation| import imageio import numpy as np from IPython.display import Image # Create a list of NumPy arrays representing frames of the GIF frames = [np.random.randint(0, 256, (100, 100, 4), dtype=np.uint8) for _ in range(10)] # Save the frames as a looping GIF file imageio.mimsave('noise.gif', frames, format='GIF', duration=100, loop=0) # Display the looping GIF in the Jupyter Notebook Image(filename='noise.gif') ++++ ==== Aufgaben B: Bildbearbeitung ==== === Aufgabe B1 === * Gruppenarbeit (2-3 Personen) * ohne Computer, Papier * wird ein Bildbearbeitungseffekt zugeteilt * überlegt euch, wie man dies umsetzen kann Einfache Effekte: * Farbkanal isolieren (Andy Warhol) * Invertieren * Flippen / Spiegeln * Bildausschnitt * Verpixeln * (Black n' White) === Aufgabe B2 === Programmiere mind. zwei dieser Effekte. Mache es selbst und ohne direkte Hilfe (z.B. von ChatGPT). Hole dir Hilfe nur für *allgemeine* Fragen. Prompt in ChatGPT darf nicht sein "Wie invertiere ich ein Bild in Python", sondern sollte sein "Wie kann ich ein Numpy Array erstellen?" === Aufgabe B3 (optional) === Überlege dir andere oder eigene Effekte, probiere aus, sei kreativ und erstelle damit neue Versionen eine Bilds (z.B. der Grumpy Cat). === Aufgabe B4: Eigenes Bildbearbeitungsprogramm (optional) === Programmiere ein eigenes Bildbearbeitungsprogramm mit einer graphischen Oberfläche, welches folgendes kann: * Open-Button: Kann Bild laden * dieses wird angezeigt * einige Effekte, die man anwählen und einstellen kann, z.B. mit: * Checkboxes * Sliders * bearbeitetes Bild wird angezeigt, in real time updated * Save-Button: bearbeitetes Bild speichern Tipps: * Verwende dazu eine Python-Library für das Erstellen von GUI, wie z.B. **PyQT**. * Verwende z.B. ChatGPT um das Grundgerüst des GUI zu erstellen. ===== Lösungen ===== ++++Aufgabe 1| # A1 li = [[3,2,1,5,2,4,2],[7,5,6,4,1,3,4],[0,3,4,6,8,8,5]] arr = np.array(li) print(arr) # A2 zeros = np.zeros((5,7),dtype=int) print(zeros) # A3 rand_arr = np.random.randint(0,10,size=(5,7)) print(rand_arr) # A4 arr = np.array([[-1, 7, 0, -7, -3, 0, 3], [-9, -2, 5, -5, -4, -7, 3], [ 3, -7, 10, -8, 2, 0, 4], [ 9, -9, 4, 8, 3, -4, 0], [-2, -8, 3, -3, -5, -1, 5]]) for y in range(arr.shape[0]): for x in range(arr.shape[1]): if arr[y,x] < 0: arr[y,x] = 0 print(a) ++++