Zeitraffer - ein Foto pro Tag
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.

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.

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.

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

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.

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.
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.
# 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.
Kommentare
Lade Kommentare …