Langzeitprojekt: Unser Feld im Wandel der Jahreszeiten. Das Ziel: ein Zeitraffervideo aus Einzelbildern

Jeden Morgen gehe ich mit meinem Hund Milli aufs Feld und drehe ein paar Runden. Und bin immer wieder überrascht, wie das Feld jeden Tag das Aussehen ändert. Einen großen Einfluss haben natürlich die Jahreszeiten; aber auch von einem Tag auf den anderen, je nach Wetter, erscheint das Feld komplett anders.

So war die Idee geboren: Jeden Tag ein Foto vom Feld machen, und das zu einem Zeitraffer kombinieren.

Da die Einzelbilder sich von Tag zu Tag sehr unterscheiden (Wetter, Kameraposition nicht exakt, Weißabgleich, Sättigung), habe ich mir ein Programm geschrieben, dass das Video etwas glättet.

Verfahren zur Herstellung des Videos

Ausrichten

Zum Ausrichten der Fotos im Lightroom hat es geholfen, auf das Fadenkreuz umzustellen. Die Mitte des Fadenkreuzes setze ich dann auf einen markanten Punkt.

Lightroom Fadenkreuz zum Aurichten des Ausschnittes
Lightroom Fadenkreuz zum Aurichten des Ausschnittes

Exportieren

Die Fotos habe ich in Lightroom in einem Festplatten Veröffentlichungsdienst konfiguriert. Dieser exportiert mir alle geänderten Bilder in einen Arbeitsbereich 1yr/in.

Aufnahmedatum integrieren

Ein Python Script stempelt mir das Aufnahmedatum in die untere Mitte und legt das erzeugte Foto im out/ Ordner ab.

Überblendungen integrieren

Für jeden Frame des Videos berechne ich den Mittelwert der letzen 10 Bilder, wobei die Mittelwerte zusätzlich gewichtet werden: jüngere Fotos dominieren die Bildmitte und ältere den Rand. Die Ereignisse wandern also von Innen nach Außen, was einen Zeittunnel Effekt ergibt.

Ausgangsbild für den 23.5.2021
Ausgangsbild für den 23.5.2021

Realisiert wird dies, indem auf die Bilder Kreise als Masken aufgebracht werden. Das Folgende Bild verdeutlicht dies - hier sind in den Kreisen exakt die Bildinformationen des jeweiligen Tages zu sehen.

Zeittunnel für den 23.5.2021 .. 18.5.2021
Zeittunnel für den 23.5.2021 .. 18.5.2021

Die Grenzen zwischen den Kreisen sind zu scharf, also verwische ich diese mit verschiedenen Techniken:

  • die Kreise überlappen sich
  • die Ränder der Kreise werden mit dem Gauss Filter geblurred
  • In jedem Kreissegment wird zusätzlich noch die gewichtete Summe aller Fotos des Zeittunnels berücksichtigt

Verwischte Kreise
Verwischte Kreise

Durch die Mittelwertberechnung werden die Bilder sehr unscharf. Um einen subjektiven Schärfeeindruck zu erzeugen, booste ich zusätzlich im bildwictigen Bereich (dem Wanderweg) das aktuellste Foto; hierfür hab ich eine Maske hinterlegt.

Maske zur Betonung der Bildmitte und des Pfades
Maske zur Betonung der Bildmitte und des Pfades

Schließlich überblende ich je zwei solcher Bilder in 3 Schritten. Also 3 Frames pro Tag, bei 30 fps gibt das 10 Tage pro Sekunde.

Das Script ist in Python geschrieben. Durch numpy und skimage lassen sich die Bildoperationen extrem schlank notieren.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import imageio as iio
import numpy as np
import os
import sys
import skimage.filters
import skimage.exposure

def build_filter(maskname, maxlen):

    mask = iio.imread(maskname).astype(float) / 255.0 # values 0..1 expected
    circles = []
    for i in range(0,maxlen):
        # The timetunnel mask files center_0.jpg ... center_8.jpg must be present in the workdir
        image = iio.imread(f'center_{i}.jpg').astype(float)
        image = skimage.filters.gaussian(image, sigma=(30,30), multichannel=True)
        image = image*(float(maxlen-i)/255.0)
        circles.append(image)
        # write blurred just for debugging / demonstration
        #iio.imwrite(f'center_blurred_{i}.jpg',convert(image, 0, 255, np.uint8))

    return mask, circles

def convert(img, target_type_min, target_type_max, target_type):
    imin = img.min()
    imax = img.max()

    a = (target_type_max - target_type_min) / (imax - imin)
    b = target_type_max - a * imax
    new_img = (a * img + b).astype(target_type)
    return new_img


def main():
    MAXLEN = 9
    MAXSTEPS = 3
    outdir = 'frames'

    # initialize filters and history
    mask_off, circles = build_filter(sys.argv[1], MAXLEN)
    mask_on = 1-mask_off # values in range 0..1
    mask_off = mask_off + 1
    history = np.zeros((MAXLEN,)+mask_off.shape)

    for idx, path in enumerate(sys.argv[2:]):
        today = iio.imread(path).astype(float)


        # drop oldest image, insert newest at pos 0
        # summed_images : [ today, today-1 (yesterday), today -2, today -3 , ...]
        history = np.roll(history,1,0)
        history[0]=today
        yesterday = history[1]

        # et filename without path and ext, and ext separately
        name, ext = os.path.splitext(os.path.normpath(path).split(os.sep)[-1])

        print(f'{name}{ext}')

        if idx >= MAXLEN:
            # at this point, history is filled.
            # let's build frames

            # the interpolated image should contain a weighted average of the history images
            # instead of a scalar weight, we use masks containing weighted blurred circles
            # this would give us a subtle timetunnel effect

            interpolated = np.zeros(today.shape)
            for i in range(0,MAXLEN):
                interpolated += history[i] * circles[i]

            # circle_0 values range from 0..9, circle_8  from 0..1.
            # The mean is approx MAXLEN**2/2.0.
            # we must normalize the values to 0..1
            interpolated /= (MAXLEN**2/2.0)

            for step in range(0,MAXSTEPS):
                # for each day we build MAXSTEPS frames
                # we add to the interpolated image a masked fading between the images of yesterday and today
                # This gives a nice emphasis on the area specified ba mask
                frame = mask_off*interpolated + mask_on*( (step/MAXSTEPS ) * today + ((MAXSTEPS-step)/MAXSTEPS)*yesterday)
                iio.imwrite(f'{outdir}/{name}_{step}{ext}',convert(frame, 0, 255, np.uint8))

Video erzeugen

Das Video erzeuge ich mit ffmpeg in mehreren Schritten. Zunächst wird das Video aus den Frames erzeugt, dann mit einem Soundtrack hinterlegt und schließlich am Anfang und am Ende gefaded.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# create video
rm frames.mp4 || true
ffmpeg -f image2 -r 30 -pattern_type glob -i 'frames/*.jpg' -vcodec libx264 -acodec aac -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2"  frames.mp4

# add audio
rm audio.mp4 || true
ffmpeg -i frames.mp4 -i slide.mp3 -map 0 -map 1:a -c:v copy -shortest audio.mp4

# fade
dur=$(ffprobe -loglevel error -show_entries format=duration -of default=nk=1:nw=1 "audio.mp4")
voff=$(bc -l <<< "$dur"-3)
aoff=$(bc -l <<< "$dur"-7)
rm video.mp4 || true
ffmpeg  -i audio.mp4 -vf "fade=t=in:st=0:d=3, fade=t=out:st=$voff:d=3" -af "afade=t=out:st=$aoff:d=7" video.mp4

 

Die Vorgängerversion

Das folgende Video hab ich im Mai 2021 aus den unbearbeiteten Bildern gemacht und jeweils weiche Übergänge integriert.

 

Weitere Beiträge zum Thema