Bildverarbeitung mit Allegro Common Lisp und der VIGRA = VIGRACL
Aufbau der Schnittstelle
Da es mit Allegro Common Lisp oder auch mit Ansi Commmon Lisp recht leicht möglich ist, sogenannten "Foreign-Code", also Code der nicht mit Lisp selbst geschrieben wurde zu laden, wird diese Schnittstelle (das Foreign-Funktion-Interface: FFI) im Rahmen der VIGRACL ausgenutzt. Diese ermöglicht es, C-konforme Shared Libraries (unter Windows: DLLs, unter MacOSX: dylibs) dynamisch zur Laufzeit an Lisp zu binden, so dass die enthaltenen Funktionen benutzt werden können.
Da die VIGRA allerings nur für Import- bzw. Export von Bildern eine Shared Library anbietet, musste als erste Zwischenstufe ein C-Wrapper für die VIGRA-Funktionen geschrieben werden (VIGRA_C), die innerhalb der Allegro Common Lisp Umgebung zur Verfügung stehen sollen. An diese dynamische C-Biblitohek werden dann die entsprechenden Common Lisp Funktionen gebunden, die den Aufruf vor dem Benutzer verschatten.
Verwendete Datenstrukturen
Eine weitere zentrale Frage ist die Wahl der Repräsentation eines Bilders innerhalb der Lisp-Umgebung. Dabei fiel die Wahl auf eine Liste, deren Elemente vom Common-Lisp Datentyp Array (und zwar in seiner zweidimensionalen Ausprägung) sind, wobei die erste Dimension von [0...Breite-1] und die zweite Dimension von [0...Höhe-1] reicht. Jedes Array enthält Werte vom Typ 'float' und steht für ein spezifisches Band. Die Bänder sind wie folgt angeornet: '(R G B) für Farbbilder und '(G) für Grauwertbilder
Die folgende Tabelle zeigt noch einmal übersichtlich alle Funktionsnamen, die diese Schnittstelle Common Lisp über die VIGRA_C bereitstellt. Dabei ist zu beachten, dass die '-band'-Funktionen jeweils auf ein Band eines Bildes angewendet werden können, wohin gegen die anderen Funktionen direkt auf obiger Bildrepräsentation arbeiten.
vigracl.impex |
vigracl.imgproc |
vigracl.filters |
vigracl.segmentation |
vigracl.morphology |
loadimage saveimage |
resizeimage-band resizeimage rotateimage-band rotateimage affinewarpimage-band affinewarpimage reflectimage-band reflectimage fouriertransform-band fouriertransform |
convolveimage-band convolveimage separableconvolveimage-band separableconvolveimage gsmooth-band gsmooth gaussiangradient-band gaussiangradient ggradient-band ggradient laplacianofgaussian-band laplacianofgaussian gsharpening-band gsharpening sharpening-band sharpening nonlineardiffusion-band nonlineardiffusion distancetransform-band distancetransform |
labelimage-band labelimage watersheds-band watersheds cannyedgeimage-band cannyedgeimage differenceofexponentialedgeimage-band differenceofexponentialedgeimage regionimagetocrackedgeimage-band regionimagetocrackedgeimage |
erodeimage-band erodeimage dilateimage-band dilateimage openingimage-band = (dilateimage-band (erodeimage-band band)) openingimage = (dilateimage (erodeimage img)) closingimage-band = (erodeimage-band (dilateimage-band band)) closingimage = (erodeimage (dilateimage img)) |
Außerdem gibt es noch in CommonLisp geschriebene Hilfsfunktionen, die den Umgang mit Bildern erleichern in der funktionalen Programmierung erleichern sollen:
vigracl.helpers |
:array-map array-map! array-reduce array-for-each-index image-map image-map! image-reduce image-for-each-index-array |
Anwendungsbeispiele
Einbindung der VIGRACL in Allegro Common Lisp (dafür muss dem Common Lisp der Pfad bekannt sein, in dem die VIGRACL liegt):
CL-USER> (load "vigracl.cl")
Anschließend kann man zum Beispiel ein beliebiges Bild laden (hier: "blox.gif"):
CL-USER> (setq img (vigracl.impex:loadimage "blox.gif"))
Seit Version 0.3.0 kann man auch direkt in das VIGRACL-Paket wechseln und hier drin ohne Paket-Präfix der Funktionen weiterarbeiten:
Ab hier wird davon ausgegangen, dass es sich um Versionen >= 0.3.0 handelt. Andernfalls müssten die Paketnamen wieder als Präfix vor die Funktionen geschrieben werden
CL-USER> (in-package :vigracl) #<The VIGRACL package> VIGRACL> (setq img (loadimage "blox.gif"))
Das Bild mit einem Gaussfilter mit sigma=3 glätten und das geglättete Bild in img2 ablegen:
VIGRACL> (setq img2 (gsmooth img 3))
Das geglättete Bild in den diskretisierten Frequenzraum überführen:
VIGRACL> (setq img3 (fouriertransform img2))
Die Wasserscheiden-Transformation auf das Gradientenbetragsbild (Gauss'scher Gradientenfilter, sigma=1) des vegrößerten Ausgangsbildes (vergr. auf 500x500 Pixel) anwenden:
VIGRACL> (setq img4 (watersheds (ggradient (resizeimage img 500 500 4) 1.0)))
Eigene Bildverarbeitungsfunktionen implementieren:
Ein Schwellenwertbild bilden (Pixelwerte >50 : 1, 0 sonst) und dessen zusammenhängende Regionen durchnumerieren:
VIGRACL> (defun threshold-band (arr t) (let* ((width (array-dimension arr 0)) (height (array-dimension arr 1)) (arr2 (make-array (array-dimensions arr) :element-type (array-element-type arr) :initial-element 0.0)) (foo (do* ((j 1 (+ j 1))) ((= j (- (array-dimension arr 1) 1))) (do* ((i 1 (+ i 1))) ((= i (- (array-dimension arr 0) 1))) (if (> (aref arr i j) t) (setf (aref arr2 i j) 1.0) (setf (aref arr2 i j) 0.0)))))) arr2)) VIGRACL> (compile 'threshold) VIGRACL> (setq img5 (labelimage (mapcar #'(lambda (arr) (threshold-band arr 50)) img)))
ACHTUNG: Bei eigenen Funktionen nicht das (compile 'function_name) vergessen! Dies führt zu einer deutlichen Steigerung der Ausführungsgeschwindigkeit
Mit den neuen Array-Hilfsfunktionen (seit Version 0.2.0) lässt sich das obige Beispiel auch wie folgt schreiben:
VIGRACL> (defun threshold (arr t) (image-map #'(lamba (x) (if (> x t) 1.0 0.0)) arr)) VIGRACL> (compile 'threshold)
An diesem Beispiel kann man sehr gut die funktionale Abstraktionsebene sehen, die sich durch die neuen array-Hilfsfunktionen ergibt. Sie macht die Programme lesbarer und eleganter.
Download & Installation
Das aktuelle Paket (Version 0.5.0) kann hier heruntergeladen werden. Es beinhaltet im Ordner vigracl die VIGRACL Anbindung und im Unterordner vigra_c die nötigen VIGRA_C Dateien (siehe oben).Dieses Paket beinhaltet nicht die VIGRA! Diese muss bereits auf dem System installiert sein.
Veränderungen gegenüber Version 0.4.0:
- Einführung von Farbbildern
- Definition der VIGRACL mithilfe Allegros defsystem
- Interface-änderung: Bilder werden nicht mehr als array repräsentiert, sonden als Listen von arrays
- Neue Funktionen: Die alten Funktionen, die auf Arrays arbeiten besitzen nun die Endung '-band'. Die neuen gleichnamigen Funktionen arbeiten nun auf der neuen Bildrepräsentation (s.o)
- Neue Funktionen: convolveimage[-band] und separableconvolveimage[-band] zur Faltung von Bildern
- Neue Funktion: gaussiangradient[-band] Ermittelt die partiellen Ableitungen in x- und y-Richtung, anstatt (vgl. mit ggradient[-band]) nur die Stärke zu bestimmen.
- Die Beispieldateien wurden auf Farbbilder angepasst.
Die alte Version (Version 0.4.0) kann hier heruntergeladen werden. Es beinhaltet im Ordner vigracl die VIGRACL Anbindung und im Unterordner vigra_c die nötigen VIGRA_C Dateien (siehe oben).Dieses Paket beinhaltet nicht die VIGRA! Diese muss bereits auf dem System installiert sein.
Veränderungen gegenüber Version 0.3.0:
- Weitere Array-Hilfsfunktionen wurden eingeführt: array-map!, array-reduce und array-for-each-index.
- Interface-änderung: array-map und array-map! verwenden jetzt die Signatur (func arrays) statt (arr func) und lassen auch nicht unäre Funktionen auf Listen von Arrays zu.
- Interface-änderung: array-map und array-map! arbeiten nun auf Listen. Daher wurde array-combine abgeschafft!
- Interface-änderung: array-copy wurde in copy-array umbenannt.
- Interface-änderung: Die Fourier Transformation beinhaltet nicht mehr die Skalierung des Bildes. Die neue Signatur ist einfach (arr) statt (arr new_width new_height)
- Neue Funktion: affinewarpimage mit Signatur (arr affinematrix resize_mode), zur affinen Transformation eines Bildes. AffineMatrix muss hierbei ein 3x3 Array vom Typ 'double-float sein.
- Neu: Morphologische Funktionen erodeimage(arr), dilateimage(arr), openingimage(arr) und closingimage(arr) wurden eingeführt
- Neue Funktion: regionimagetocrackedgeimage(arr) wurde eingeführt
- Die Beispieldateien ("examples.cl" und "fft_example.cl") wurden auf die neuen Funktionen bzw. Funktionssignaturen umgestellt.
Die alte Version (Version 0.3.0) kann hier weiterhin heruntergeladen werden.
Veränderungen gegenüber Version 0.2.0:
- Einheitliche Paketumgebung wurde eingeführt.
- Die Beispieldateien ("examples.cl" und "fft_example.cl") wurden auf das neue Paketsystem umgestellt.
Die alte Version (Version 0.2.0) kann hier weiterhin heruntergeladen werden.
Veränderungen gegenüber Version 0.1.0:
- Fehler in der Fast Fourier-Transformation wurde behoben.
- array-Hilfsfunktionen wurden eingeführt:
array-copy (Kopieren von Arrays)
array-map (Anwenden von Funktionen auf alle Elemente im Array)
array-combine (Kombination zweier Arrays mittels einer binären Funktion) - Weitere Beispieldatei wurde hinzugefügt : Umgang mit den array-Funktionen sowie mit der FFT ("fft_example.cl")
- Allgemeine Beispieldatei wurde umbenannt von "example.cl" in "examples.cl"
Die alte Version (Version 0.1.0) kann hier weiterhin heruntergeladen werden, wovon ich allerdings wegen der behobenen Fehler dringed abrate
Installationshinweise für MacOS X:
Erstellen der dynamischen Bibliothek (vigra_c.dylib)
Dazu muss das Makefile im Unterordner vigra_c ausgeführt werden. Nach erfolgreichem Aufruf sollte sich eine Datei namens "vigra_c.dylib" im VIGRACL-Verzeichnis befinden.
Installationshinweise für Linux:
Erstellen der dynamischen Bibliothek (vigra_c.so)
Das Erstellen einer Linux-Bibliothek wird zur Zeit noch nicht direkt unterstützt, es sollte jedoch kein Problem sein die Anpassungen zu tun, die dafür nötig sind:
- Das Makefile im Ordner "vigracl/vigra_c" so anpassen, dass ein C-kompatibles shared object file im Ordner vigracl erzeugt wird.
- Die vigracl.cl Datei musss nun nur noch so angepasst werden, dass sie auch nach dieser Datei sucht, falls das Betriebssystem ein Linux ist. Dies müsste in Zeile 2-4 ergänzt werden.
Installationshinweise für Windows:
Erstellen der dynamischen Bibliothek (vigra_c.dll)
Dazu befindet sich im Unterordner "vigra_c\win32-msvc2005" ein Visual Studio Projekt. Hier müssen eventuell noch die Pfade, die zur VIGRA führen, angepasst werden. Danach kann man mit "Projekt erstellen" die DLL erzeugen. Nach erfolgreichem Aufruf sollte sich eine Datei namens "vigra_c.dll" im Release-Verzeichnis befinden. Diese muss nun noch ins "vigracl" Verzeichnis kopiert werden
Weitere Meilensteine:
- Einbindung weiterer VIGRA-Funktionen