Bildverarbeitung#

Die nachfolgenden Aufgaben behandeln das Einlesen und Bearbeiten von Bilddateien. Verwenden das Modul Matplotlib zum Einlesen und Darstellen der Dateien und Numpy zur Datenverarbeitung. JPEG Bilder wie sie im Folgenden verwendet werden, können mittels Python als dreidimensionales Array eingelesen werden. Oftmals empfiehlt es sich die Bearbeitung getrennt für die drei Farbkanäle \((R, G, B)\) vorzunehmen. Sämtliche Bilder können zur lokalen Bearbeitung der Aufgaben hier heruntergeladen heruntergeladen werden: Bilder.zip. Die Bilder befinden sich ebenfalls auf dem JupyterHub zur Vorlesung und können wie folgt adressiert werden:

image_dir = '/srv/images/'
image = image_dir + 'imagename.jpg'
bild_array = plt.imread(image)

Weißabgleich#

Der Weißabgleich dient der Anpassung der Farbtemperatur eines Fotos an die Lichtbedingungen des Aufnahmeortes. Dies kann erreicht werden, indem die Intensitäten der drei Farbkanäle des Fotos anhand eines Referenzpixels skaliert werden. Der Referenzpixel wird dabei aus einem Bereich des Bildes ausgewählt, der wahrheitsgemäß als weiß oder grau angenommen wird. Für die Übung benötigen Sie das Bild “Weissabgleich.jpg”

Aufgabe#

Nachfolgendes Foto weist durch einen falschen Weißabgleich einen erheblichen Blaustich auf. Wählen Sie einen Referenzpixel innerhalb des Bildes und korrigieren Sie die Intensitäten der drei Farbkanäle auf ein einheitliches Niveau. Skalieren Sie sämtliche Pixel des Bildes anhand der zuvor bestimmten Korrekturfaktoren.

Orientieren Sie sich dabei an den folgenden Bearbeitungsschritten:

  1. Lesen Sie das Bild mit Matplotlib als dreidimensionales Numpy.array ein.

  2. Extrahieren Sie die Werte für die drei Farbkanäle für einen einzigen Referenzpixel innerhalb des Bildes, welcher in der Realität idealerweise einen Weiß- oder Grauton aufweist, für den also gilt \(R = G = B\).

  3. Bestimmen Sie die Skalierungsfaktoren, die erforderlich sind um ein Farbgleichgewicht für diesen Pixel zu erreichen. Normieren Sie diese auf den grünen Farbkanal (Kanal 1).

  4. Skalieren Sie die Farbwerte sämtlicher Pixel des Bildes mit den Skalierungsfaktoren.

  5. Stellen Sie das modifizierte Numpy.array erneut mit Matplotlib dar.

Hinweis

Beachten Sie, dass die Funktion matplotlib.imshow lediglich die Ausgabe von RGB Bildern mit Integerwerten, also Ganzzahlen erlaubt. Floatwerte (Fließkommazahlen) müssen vorab entsprechend gerundet bzw. konvertiert werden.

../../../../_images/weissabgleich.jpg

Fig. 3.2 Foto mit falschem Weißabgleich und starkem Blaustich#

Hinweis

Die Koordinaten beim Auslesen von Pixeldaten mit Numpy entsprechen den Indizes des Numpy.array und sind demnach gegenüber der Ausgabe mit Matplotlib transponiert.

Lösungsvorschlag#

Als Referenzpixel wird ein Punkt mit den Pixel-Koordinaten x = 3900, y = 1300 auf der Mühle gewählt, von dem angenommen wird, dass er in Wirklichkeit annähernd perfekt weiß ist.

Hide code cell content
import os
import matplotlib.pyplot as plt
import numpy as np

# Laden der Bilddateie als Numpy.array
image = plt.imread('bilder/Weissabgleich.jpg')

# Auslesen der Farbwerte eines Referenzpixels
x = 3900
y = 1300
ref_pixel = image[y,x]

# Berechnung der Skalierungsfaktoren und Normierung anhand des grünen Farbkanals
rgb_scale = ref_pixel/ref_pixel[1]

# Weißabgleich des gesamten Bildes anhand der Skallierungsfaktoren
image_wb = (image/rgb_scale).astype('uint8')

# Darstellen des korrigierten Bildes
fig = plt.figure(figsize=(10,10))
plt.scatter(x,y, color='red', label="Referenzpixel")
plt.imshow(image_wb)
plt.legend(loc='best')
plt.show()
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[1], line 6
      3 import numpy as np
      5 # Laden der Bilddateie als Numpy.array
----> 6 image = plt.imread('bilder/Weissabgleich.jpg')
      8 # Auslesen der Farbwerte eines Referenzpixels
      9 x = 3900

File /opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/matplotlib/pyplot.py:2404, in imread(fname, format)
   2400 @_copy_docstring_and_deprecators(matplotlib.image.imread)
   2401 def imread(
   2402         fname: str | pathlib.Path | BinaryIO, format: str | None = None
   2403 ) -> np.ndarray:
-> 2404     return matplotlib.image.imread(fname, format)

File /opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/matplotlib/image.py:1525, in imread(fname, format)
   1518 if isinstance(fname, str) and len(parse.urlparse(fname).scheme) > 1:
   1519     # Pillow doesn't handle URLs directly.
   1520     raise ValueError(
   1521         "Please open the URL for reading and pass the "
   1522         "result to Pillow, e.g. with "
   1523         "``np.array(PIL.Image.open(urllib.request.urlopen(url)))``."
   1524         )
-> 1525 with img_open(fname) as image:
   1526     return (_pil_png_to_float_array(image)
   1527             if isinstance(image, PIL.PngImagePlugin.PngImageFile) else
   1528             pil_to_array(image))

File /opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/site-packages/PIL/Image.py:3277, in open(fp, mode, formats)
   3274     filename = os.path.realpath(os.fspath(fp))
   3276 if filename:
-> 3277     fp = builtins.open(filename, "rb")
   3278     exclusive_fp = True
   3280 try:

FileNotFoundError: [Errno 2] No such file or directory: '/home/runner/work/LectureComputerScience/LectureComputerScience/book/content/Exercises/03_datenanalyse/zusatzaufgaben/bilder/Weissabgleich.jpg'

Image Stacking#

Der Begriff “Image Stacking” bezeichnet allgemein das Überlagern von Bildern um unterschiedliche Effekte und Wirkungen zu erzielen. Neben dem Erzeugen von künstlerischen Effekten können auf diese Weise auch bewegte Objekte aus einer Bildserie des selben Bildausschnittes entfernt werden.

Für diese Aufgabe benötigen Sie die Bilder “Image_stackig_1.jpg” - “Image_stacking_5.jpg”

../../../../_images/Stacking_bildserie.jpg

Fig. 3.3 Bildserie des selben Bildauschnittes mit “bewegtem” Objekt#

Aufgabenteil 1#

Überlagern Sie alle Bilder der genannten Bildserie, indem Sie den Durchschnitt der Farbwerte eines jeden Pixels bilden. Gehen Sie dazu wie folgt vor:

  1. Lesen Sie das Bild mit Matplotlib als dreidimensionales Numpy.array ein und separieren Sie die Farbkanäle als zweidimensionale Arrays

  2. Bilden Sie die Durschnittswerte der monochromatischen Bildarrays aus den Bildserien unter Anwendung der Numpy.mean Funktion. Tip: Verwenden Sie eine Schleife zum Einlesen der Daten und speichern Sie die Arrays in einer Liste zwischen.

  3. Fügen Sie die Arrays mit den Durchschnittswerten wieder zu einem dreidimensionalen Array zusammen und plotten Sie das Ergebnis.

Lösungsvorschlag#

Hide code cell content
# Anlegen von Listen zum Speichern der `Numpy.array`s aus den einzelnen Bildern
r_layers = []
g_layers = []
b_layers = []

# Einlesen der Bilder und Separieren der Farbkanäle
for i in range(1,6)[0:5]:
    image = plt.imread(f'bilder/Image_stacking_{i}.jpg')
    r_layers.append(image[:,:,0])
    g_layers.append(image[:,:,1])
    b_layers.append(image[:,:,2])
    
# Berechung der durchschnittlichen Farbwerte
r_mean = np.mean(r_layers, axis=0)
g_mean = np.mean(g_layers, axis=0)
b_mean = np.mean(b_layers, axis=0)

# Zusammenfassen der eizelnen Farbkanäle 
rgb_mean = np.dstack((r_mean, g_mean, b_mean)).astype('uint8')

# Darstellen der überlagerten Bildserie
fig = plt.figure(figsize=(10,10))
plt.imshow(rgb_mean)
plt.xlabel("Pixel X / -")
plt.ylabel("Pixel Y / -")
plt.show()
../../../../_images/941048418cf473896d54f13c3a2b1c5207ff4f3ee4a55f00de24cc4992d4d7c6.png

Aufgabenteil 2#

Überlagern Sie erneut die einzelnen Bilder und wenden Sie einen Hochpassfilter (z.B. Median) auf die einzelnen Farbkanäle an um stark abweichende Farbwerte aus dem Bild zu eliminieren. Führen Sie hierzu die Arbeitsschritte aus Aufgabenteil 1 aus. Verwenden Sie statt der Funktion zur Berechnung des Durchschnitts z.B. Numpy.median. Überlegen Sie vorab, welcher Effekt hierdurch in Bezug auf das überlagerte Bild erzielt wird.

Lösungsvorschlag#

Hide code cell content
# Berechung des Medians der Farbwerte
r_med = np.median(r_layers, axis=0)
g_med = np.median(g_layers, axis=0)
b_med = np.median(b_layers, axis=0)

# Zusammenfassen der eizelnen Farbkanäle 
rgb_med = np.dstack((r_med, g_med, b_med)).astype('uint8')

# Darstellen der überlagerten Bildserie
fig = plt.figure(figsize=(10,10))
plt.imshow(rgb_med)
plt.xlabel("Pixel X / -")
plt.ylabel("Pixel Y / -")
plt.show()
../../../../_images/e70b3be21eecf3754cd8ded7c64d78c70c1598a6148129acbfab7bb429054632.png

Freistellen von Objekten#

Das Freistellen von Objekten kann über verschiedene Methoden mit unterschiedlicher Komplexizität erfolgen. Eine einfache, wenn auch äußerst fehleranfällige Methode ist das Maskieren anhand eines Schwellenwertes einer oder mehrerer Farbkanäle.

Aufgabe#

Für die Übung benötigen Sie die Bilder “Freistellen_objekt.jpg” und “Freistellen_hintergrund.jpg”. Maskieren Sie die Bereiche um die Kerze herum anhand von oberen oder unteren Schwellenwerten eines oder mehrerer Farbkanäle. Platzieren Sie die Kerze anschließend innerhalb des zweiten Bildes.

Orientieren Sie sich an folgenden Arbeitsschritten:

  1. Definieren Sie einen unteren Schwellenwert zwischen 0 und 255 für den roten Farbkanal. Legen Sie im erste Schritt eine beliebigen Wert fest und iterieren Sie diesen, bis das gewünschte Ergebnis erreicht ist.

  2. Bilden Sie das Bild ‘Freistellen_objekt.jpg’ als dreidimensionales Numpy.array ab und separieren Sie die einzelnen Farbkanäle als zweidimensionale Arrays.

  3. Erstellen Sie eine Maske, welche nur die Pixelkoordinaten berücksichtigt, in denen der festgelegte Schwellenwert überschritten wird. Nutze Sie hierfür z.B. die Funktion Numpy.where.

  4. Weisen Sie den maskierten Pixel einen fixen Farbwert zu um einen einfarbigen Hintergrund zu erhalten

  5. Weisen Sie den maskierten Pixeln die entsprechenden Pixel mit den selben Koordinaten aus dem Bild “Freistellen_hintergrund.jpg” zu.

../../../../_images/Freistellen_bildserie.jpg

Fig. 3.4 Objekt und Hintergrund#

Lösungsvorschlag#

Hide code cell content
# Laden der Bilddateien als Numpy.array
image_object = plt.imread('bilder/freistellen_objekt.jpg')
image_background = plt.imread('bilder/freistellen_hintergrund.jpg')

# Definition des Schwellenwertes und Erstellen einer Maske anhand des roten Farbkanals
th_red = 120
red_channel = image_object[:,:,0]
mask = np.where(red_channel > th_red, 1, 0)

# Maskieren des Bildes anhand des roten Farbkanals; Maskierten Bereichen wird der Wert 255 zugewiesen. Somit ergibt sich in Summe ein weißer Hintergrund
masked_img_red = np.where(mask==1, image_object[:,:,0],255)
masked_img_green = np.where(mask==1, image_object[:,:,1],255)
masked_img_blue = np.where(mask==1, image_object[:,:,2],255)
masked_img_rgb = np.dstack((masked_img_red, masked_img_green, masked_img_blue)).astype('uint8')

# Maskieren des Bildes anhand des roten Farbkanals; Maskierten Bereichen werden die Pixel des Hintergrundbildes zugewiesen.
new_img_red = np.where(mask==1, image_object[:,:,0],image_background[:,:,0])
new_img_green = np.where(mask==1, image_object[:,:,1],image_background[:,:,1])
new_img_blue = np.where(mask==1, image_object[:,:,2],image_background[:,:,2])
new_img_rgb = np.dstack((new_img_red, new_img_green, new_img_blue)).astype('uint8')

# Darstellen der überlagerten Bilder
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(15,7.5), sharey=True)
ax1.set_xlabel("Pixel X / -")
ax2.set_xlabel("Pixel X / -")
ax1.set_ylabel("Pixel Y / -")
ax1.set_title("Maskiertes Bild mit weißem Hintergrund")
ax2.set_title("Maskiertes Bild mit Hintergrund aus anderem Bild")

ax1.imshow(masked_img_rgb)
ax2.imshow(new_img_rgb)
plt.show()
../../../../_images/26d0c16a737ebb5dbc2fff94962b12369fefb2fee9d030d004c2b8427a071f49.png

Gammakorrektur#

In der digitalen Bildverarbeitung dient die Gammakorrektur zur Überführung linearer physikalischer Zusammenhänge in die nichtlineare menschliche Wahrnehmung. Die Gammafunktion ist eine Potenzfunktion, die z.B. im Rahmen der Farbraumumwandlung in den sRGB Standard die Rohdaten eines Bildes, wie die Pixelhelligkeit \(I_{in}\) in eine entsprechende Ausgabegröße \(I_{out}\) umwandelt. Die Gammafunktion kann wie folgt beschrieben werden:

\[I_{out} = {I_{in}}^\gamma\]

Dabei gilt: \(0 \leq I_{in} \leq 1\) und \(0 \leq I_{out} \leq 1\)

Aufgabe#

Führen Sie Gammakorrektur für die Faktoren \(\gamma = 2.2\) und \(\gamma = 0.5\) durch. Gehen Sie dazu wie folgt vor:

  1. Erstellen Sie zuerst zwei “Lookup-Tabellen” (LUT) in der Sie alle möglichen Pixelwerte \(I_{in}\) in einem linearen Wertebereich von 8bit mit den durch die Gamma-Funktion korrigierten Werten \(I_{out}\) verknüpfen. Erstellen die für beide \(\gamma\)-Werte unabhängige LUT.

  2. Plotten Sie jeweils die Abhängigkeit zwischen \(I_{in}\) und \(I_{out}\).

  3. Führen Sie anschließend die Gammakorrektur für sämtliche Pixel der drei Farbkanäle des Bildes durch. Verwenden Sie hierzu die Möglichkeit der Indizierung eines Numpy.array nach dem Schema

    I_out = lut[I_in]
    

Verwenden Sie das nachfolgende Bild “Gammakorrektur.jpg” für diese Aufgabe:

../../../../_images/Gammakorrektur.jpg

Fig. 3.5 Bild mit falscher Gammakorrektur.#

Lösungsvorschlag#

Hide code cell content
# Definition der Gammawerte
gamma_1 = 0.5
gamma_2 = 2.2

# Definition der Farbtiefe
color_depth = range(2**8)

# Zusammenhang zwischen I_in und I_out über die Gammafunktion
i_out = lambda i_in, gamma : 255 * (i_in/255)**gamma

# Berechung der Lookup Tabellen (LUT)
gamma_1_lut  = np.array([i_out(i_in, gamma_1) for i_in in color_depth], dtype=np.uint8)
gamma_2_lut  = np.array([i_out(i_in, gamma_2) for i_in in color_depth], dtype= np.uint8)

# Plot der LUTs
fig = plt.figure(figsize=(10,10))
plt.plot(color_depth, color_depth, color='gray', label="$\gamma = 1.0$")
plt.plot(color_depth, gamma_1_lut, color='red', label="$\gamma = 0.5$")
plt.plot(color_depth, gamma_2_lut, color = 'blue', label="$\gamma = 2.2$")
plt.xlabel("$I_{in}~/~-$")
plt.ylabel("$I_{out}~/~-$")
plt.grid(linestyle='--', alpha=0.5)
plt.legend(loc='best')
plt.show()
../../../../_images/d4b46b4fb4a0a83681c877c170a87e838b327451818565b04d620945e55ecc0b.png
Hide code cell content
# Gammakorrektur mit y = 0.5
image = plt.imread('bilder/Gammakorrektur.jpg')
image_gamma1 = gamma_1_lut[image]
fig = plt.figure(figsize=(10,10))
plt.imshow(image_gamma1)
plt.title("$\gamma = 0.6$")
plt.xlabel("Pixel X / -")
plt.ylabel("Pixel Y / -")
plt.show()
../../../../_images/9a6e6dae8fa0de33f51af47ecb66c26aff8cd869f8393a492b99e7b928aaf5b1.png
Hide code cell content
# Gammakorrektur mit y = 2.2
image_gamma2 = gamma_2_lut[image]
fig = plt.figure(figsize=(10,10))
plt.imshow(image_gamma2)
plt.title("$\gamma = 2.2$")
plt.xlabel("Pixel X / -")
plt.ylabel("Pixel Y / -")
plt.show()
../../../../_images/15a20ab048716a7d90597429666d83e6f33cc3e26aa2847f245b9798343f828b.png