blog · git · desktop · images · contact & privacy · gopher


pic2sound.sh: Bilder anhören

Man darf dieses Posting getrost als „völlig sinnlose Spielerei“ abheften. Kein Problem. Aber muss ja auch mal sein.

Die Idee ist natürlich auch nicht neu, zum Beispiel Aphex Twin hat das schon gemacht. Es geht darum, beliebige Bilder in Spektralanalysen zu „finden“ oder eben gezielt solche Soundfiles zu erzeugen. Wie schafft man das als armer Mensch? Man schnappt sich zuerst einmal ImageMagick, ein geeignetes Bildformat und SoX. Dann muss man diese Zutaten nur noch passend verwurschteln und schon fällt eine WAV-Datei hinten raus, die man zum Beispiel mit dem wundertollen rtspeccy „anschauen“ kann.

Das Skript dazu:

#!/bin/bash

threshold=64
tmpdir=$(mktemp -d)
pic=$(convert \
    -background white \
    -compress none \
    -depth 8 \
    -resize 256x256 \
    -edge 1 \
    "$1" \
    ppm:-)

read wid hei < <(echo "$pic" | sed -n 2p)

at=0
for (( y = 0; y < hei; y++ ))
do
    args=()
    for (( x = 0; x < wid; x++ ))
    do
        read red
        read green
        read blue
        if (( red > threshold )) ||
            (( green > threshold )) ||
            (( blue > threshold ))
        then
            args+=(sin $((55 * (x + 1))))
        fi
    done
    if (( ${#args[@]} > 0 ))
    then
        sox -nq -c1 "$tmpdir"/$(printf "snd%010d.wav" $at) \
            synth 0.03125 "${args[@]}"
    else
        sox -n -c1 "$tmpdir"/$(printf "snd%010d.wav" $at) \
            trim 0.0 0.03125
    fi
    ((at++))
done < <(echo "$pic" | tail -n +4 | sed 's/ /\n/g' | sed '/^$/d')

sox "$tmpdir"/*wav "$2"
play -q "$2"
rm -R "$tmpdir"

Aufruf ist beispielsweise „pic2sound.sh tux.svg tux.wav“.

Funktionsweise: Nimm das Bild, verkleinere es und mache eine Kantendetektion. Letztere wird gemacht, um den Umriss der Objekte darzustellen – dann ist das Bild besser erkennbar. Dieses bearbeitete Bild wird als PPM ausgegeben und dann Zeile für Zeile vom Skript eingelesen. Liegt der Farbwert eines Pixels über einem gewissen Schwellenwert, dann betrachte den Pixel als „gesetzt“. Ist eine Bildzeile fertig eingelesen, wird sie als kurzes Soundsample gespeichert: Für jeden gesetzten Pixel wird anhand seiner x-Koordinate eine Frequenz bestimmt und mit dieser Frequenz eine kurze Sinusschwingung erzeugt. All diese werden pro Zeile überlagert, sodass dann pro Zeile eine kleine Datei entsteht. Schlussendlich werden die ganzen kleinen Dateien zu einer großen zusammengesetzt.

Wird die Auflösung des Zwischenbildes verdoppelt, muss die Grundfrequenz halbiert werden, ebenso die Dauer einer Zeile. Bei 512x512 bräuchte man also 27.5 Hz statt 55 Hz und 0.015625 Sekunden Länge statt 0.015625 Sekunden. (Im Falle der 27.5 geht das so direkt mit der Bash natürlich nicht mehr, da keine Fließkommaberechnungen möglich sind.) Die Initialwerte wurden von mir experimentell bestimmt. ;) Das Seitenverhältnis des Spektralbilds hängt natürlich von der Fenstergröße des rtspeccy ab.

Die Bilder da oben sehen übrigens so schlecht aus, weil ich einfach ein billiges 5€-Mikrofon vor billige Lautsprecher gehalten habe. Kriegt man bestimmt auch schöner hin, aber ich hatte gerade nichts anderes zur Hand.