Gimp (Linux-Magazin, Juli 2008)

Statt digitale Bilder mit den immer gleichen Schritten im Gimp zu bearbeiten, bieten Perlskripts die Möglichkeit, den Ablauf zu automatisieren.

Bevor ich Bilder von meiner digitalen Kamera aufs Netz stelle, laufen seit Jahren immer die gleichen Schritte im Gimp ab: Erst wird die Bilddatei auf 1000 Pixel Breite verkleinert, denn die von meiner Nikon D70 gelieferte Auflösung ist eigentlich schon fast zuviel für's Web und das Hochladen dauert nur unnötig lange. Zweitens versucht fast jeder ambitionierte Fotograf, den Kontrast zu verbessern und eventuelle Farbstiche zu zu korrigieren. Und drittens verbessert die Funktion ``Sharpen'' im Gimp die Bildschärfe, üblicherweise stelle ich den Wert 20 ein.

Abbildung 1: Das Originalbild hat einen leichten Farbstich und ist vom Kontrast her verbesserungswürdig.

Abbildung 2: Das mit picfix korrigierte Bild zeigt natürlichere Farben und einen besseren Kontrast.

Dabei bietet der Gimp, wie schon einmal vor fünf Jahren im Snapshot vorgestellt, eine komfortable Skript-Schnittstelle, um diese immer wiederkehrenden Aktionen zu automatisieren und sogar ohne GUI von der Kommandzeile aus ablaufen zu lassen. Allerdings haben die Gimp-Entwickler seit dem die ganze API durcheinander gewirbelt und nichts funktioniert mehr wie früher. Aber zum Glück dokumentiert der Gimp unter dem Menüpunkt Xtns->Procedure Browser sämtliche Funktionen vollständig und pinibel genau. Ein dickes Bravo an das Gimp-Team!

Die Standardschnittstelle ist in der verrückten Professorsprache Scheme geschrieben, aber Gott sei Dank wird auch ein Perl-Modul angeboten. Zur Installation unter Ubuntu waren ein paar Tricks notwendig, im Abschnitt ``Trickreiche Installation'' am Ende dazu mehr. Listing picfix wird dann einfach von der Kommandozeile aus mit

    picfix file.jpg

aufgerufen, worauf es intern den Gimp aufruft (allerdings ohne dass sich dieser auf der Oberfläche zeigt) und mit register() die Funktion picfix() registriert. Die Gimp-Schnittstelle besteht seltsamerweise darauf, dass auch ein Skript, das nur auf der Kommandozeile läuft, unter einem genau vorgegebenen Menüpunkt nutzloserweise einen Eintrag einstellt, also tut picfix ihr den Gefallen. In Zeile 43 ruft das Skript dann die Funktion main() auf, die in den Gimp verzweigt und erst nach getaner Arbeit wieder von dort zurückkehrt. Das vorangestellte exit() stellt dann sicher, dass sich das Skript mit dem Returncode von main() ebenfalls verabschiedet.

Abbildung 1 zeigt das Originalbild, eine Aufnahme von meinem Balkon in San Francisco. Das Abendlicht kam von hinten und leuchtete die Stadt schön aus. Trotzdem waren die Farben im Original nicht ganz richtig. Nachdem Aufruf von picfix kam als Ergebnis das Bild in Abbildung 2 zum Vorschein. Der Kontrast ist deutlich besser und auch die Farben entsprechen ziemlich genau der Realität.

In der ab Zeile 46 definierten Funktion picfix(), die der Gimp als Callback aufruft, lädt die Funktion gimp_file_load() das digitale Bild unter dem vorher festgelegten Dateinamen von der Festplatte. Es wird dann mit image_scale() auf 1000 Pixel Breite skaliert. Die Funktion gimp_levels_stretch() simuliert dann den ``Auto''-Knopf in Gimps ``Levels''-Dialog und maximiert damit den Kontrast, indem es die tatsächlich verwendeten Pixelwerte über die ganze verfügbare Bandbreite ausdehnt. Die Methode plug_in_sharpen() mit dem Parameter 20 verbessert anschließend die Bildschärfe und gimp_file_save() speichert die Datei am Ende unter dem Pfad file-1.jpg im ursprünglich verwendeten Format ab. Es ist also egal, ob das Bild als JPG oder PNG vorliegt, die load/save-Methoden geben intern die Kontrolle an die auf das jeweilige Bildformat spezialisierten Gimp-Routinen ab.

Das Laden der Module mit use Gimp qw(:auto) und use Gimp::Fu importiert alle Gimp-Funktionen praktischerweise in den Namensraum des Skripts. Auch Gimp-Konstanten wie RUN_NONINTERACTIVE schleust das :auto-Tag zur Freude des Programmierers mit ein.

Listing 1: picfix

    001 #!/usr/bin/perl
    002 use warnings;
    003 use strict;
    004 
    005 use Gimp qw(:auto);
    006 use Gimp::Fu;
    007 use ColorCast;
    008 use Getopt::Std;
    009 use Log::Log4perl qw(:easy);
    010 
    011 my $viewer = "eog";
    012 
    013 Log::Log4perl->easy_init($DEBUG);
    014 
    015 getopts("xl:c:a:s:u:", \my %opts);
    016 
    017 $opts{a} ||= "green"; # color adjust
    018 $opts{s} ||= "1000";  # size
    019 $opts{l} ||= 1;       # autolevel
    020 
    021 DEBUG "Starting up";
    022 
    023 my $menu = 
    024     "<Toolbox>/Xtns/Perl-Fu/Picfix";
    025 
    026 my $file = $ARGV[0];
    027 die "No file" 
    028       unless defined $file;
    029 
    030 register(
    031   "perl_fu_picfix",    # Name
    032   "Fix Colors and More", # Explain
    033   "",                  # Help 
    034   "",                  # Author
    035   "",                  # Copyright
    036   "",                  # Date
    037   $menu,               # Menu
    038   "*",                 # Images accepted
    039   [ undef ],           # No parameters
    040   \&picfix             # Function
    041 );
    042 
    043 exit main();
    044 
    045 ###########################################
    046 sub picfix { 
    047 ###########################################
    048 
    049   my $img = gimp_file_load(
    050       RUN_NONINTERACTIVE, $file, $file);
    051 
    052   die "Can't load $file" unless $img;
    053 
    054   my $layer = image_get_active_layer($img);
    055 
    056   scale_image_down($img, $opts{s});
    057 
    058   $layer = $img->get_active_layer();
    059   if($opts{l}) {
    060     DEBUG "Autolevel [$file]";
    061     gimp_levels_stretch($layer);
    062   }
    063 
    064   if($opts{c}) {
    065     my $colorcast = ColorCast->new(
    066       yml_file => $opts{c},
    067       drawable => $layer,
    068     );
    069     $colorcast->load();
    070     $colorcast->adjust_to($opts{a});
    071   }
    072     
    073   if($opts{u}) {
    074       my $unsharp = {
    075           people  => [1, 1.5, 10],
    076           urban   => [3, 0.65, 2],
    077           general => [1, 0.85, 4],
    078       };
    079       if($opts{u} =~ /([\d.]+):([\d.]+):([\d.]+)/) {
    080           $unsharp->{ $opts{u} } = [$1, $2, $3];
    081       } elsif(! exists $unsharp->{$opts{u}}) {
    082           LOGDIE "Unknown unsharp value (use ", 
    083                  join(', ', keys %$unsharp), ")";
    084       }
    085 
    086       DEBUG sprintf "Unsharp mask radius=%.2f amount=%.2f threshold=%.2f",
    087                     @{ $unsharp->{$opts{u} } };
    088       $img->plug_in_unsharp_mask($layer, @{ $unsharp->{$opts{u}} });
    089   } else {
    090       DEBUG "Sharpening $file";
    091       $img->plug_in_sharpen($layer, 20);
    092   }
    093 
    094   $file =~ s/\./-1./g;
    095   $file =~ s/\.nef$/.png/g;
    096 
    097   DEBUG "Saving $file";
    098   gimp_file_save(
    099     RUN_NONINTERACTIVE,
    100     $img,
    101     $layer,
    102     $file,
    103     $file);
    104 
    105   system("$viewer $file") if $opts{x};
    106   return $img;
    107 }
    108 
    109 ###########################################
    110 sub scale_image_down { 
    111 ###########################################
    112   my($img, $size) = @_;
    113 
    114   my $w = $img->image_width();
    115   my $h = $img->image_height();
    116 
    117   if($w >= $h) {
    118       if($w > $size) {
    119           $h = int($h * $size/$w);
    120           $w = $size;
    121       } else {
    122           return 1;
    123       }
    124   } else {
    125       if($h > $size) {
    126           $w = int($w * $size/$h);
    127           $h = $size;
    128       } else {
    129           return 1;
    130       }
    131   }
    132 
    133   DEBUG "Resizing to $w x $h";
    134   $img->image_scale($w, $h);
    135 }

Optionen

Die Bildskalierung ist auf 1000 Pixel voreingestellt, aber mit der Kommandozeilenoption -s (size) sind beliebige Werte verfügbar. Der Aufruf picfix -s 500 file.jpg reduziert die maximale Bildbreite zum Beispiel auf 500 Pixel. Die maximale Größe bezieht sich bei querformatigen Bildern auf die Breite, bei hochformatigen aber auf die Höhe. Die Funktion scale_image_down() ab Zeile 92 enthält die dazu notwendige if/else-Logik.

Wer nach getaner Arbeit das Bild gleich ansehen möchte, kann mit der Option -x (für X-Windows) das Bild gleich in den in Zeile 11 eingestellten Viewer (Eye of Gnome, eog) ansehen. Soll die nicht immer zufriedenstellende Ergebnisse liefernde Autolevel-Funktion unterbleiben, stellt dies die Option -l 0 ein.

Farbstich

Ist ein Bild zu grün, rot oder blau, stimmt etwas mit der Farbenbalance nicht. Ein weißes Objekt in der fotografierten Szene sollte auch im Foto knallweiß und ohne Farbeinschlag zu sehen sein. Analoges gilt für graue oder pechschwarze Objekte. Wurde die White-Balance der Kamera aber nicht eingestellt, wie das die Profis machen, sind öfter mal unnatürliche Farbanteile zu sehen. Solche Fehler lassen sich nach einem in [2] beschriebenen Verfahren auch noch hinterher am digitalen Bild korrigieren. Die Möglichkeiten sind zwar bei dem von vielen preiswerten Kameras verwendeten JPEG-Format beschränkt, aber etwas lässt sich üblicherweise doch noch rausholen.

Abbildung 3: Ein Satz weißer, grauer und schwarzer Kärtchen erleichtert das Finden der Kontrollpunkte.

Zunächst stellt sich das Problem, dass sich nicht in jedem Bild Elemente bar jedes Farbtupfers befinden. Es gibt aber im Fotofachhandel genormte Plastikkarten zu kaufen (scherzhafterweise auch ``die teuersten Plastikkarten der Welt'' genannt), die man einfach ins Bild hält, eine Testaufnahme macht und die Messwerte dann für alle folgenden Aufnahmen der gleichen Szene verwendet. Ändern sich allerdings die Lichtverhältnisse und kommt nur die Sonne hinter einer Wolke hervor, muss eine neue Kontrollaufnahme mit den Karten gemacht werden.

In Abbildung 3 wurde mit Gimps ``Color Picker'' der Pixelwert einer Stelle auf der grauen Karte gemessen und das Ergebnis war Rot:122, Grün:127 und Blau:123. Wäre die Aufnahme perfekt, hätten alle drei Farbkanäle denselben Wert gespeichert. Ähnliches gilt für die weiße und die schwarze Karte, dort wurden Werte von 227/235/228 und 16/10/17 gemessen.

Die Datei colorcast.yml (Abbildung 4) zeigt die mit Gimp eingefangenen Messwerte im YAML-Format. Diese einmal dort abgelegten Werte nimmt das Skript picfix auf der Kommandozeile mit

    picfix -c colorcast.yml file.jpg

entgegen und korrigiert damit beliebig viele Bilder der selben Szene. Um das Bild nun zu korrigieren, gilt es, alle im Bild verwendeten Farben so zu transformieren, dass die die Farbanteile der farblosen Bildelemente verschwinden. Man definiert hierzu einen Übergangsfunktion, deren Graph sich an den bekannten Messpunkten entlangschlängelt und für alle übrigen Werte Spline-ähnliche Interpolationen bereithält.

Abbildung 4: Die Datei colorcast.yml speichert die gemessenen Farbwerte für die weiße, die graue und die schwarze Karte.

Im Gimp lässt sich dies im ``Curves''-Dialog aus dem Menü ``Tools->Color Tools'' bewerkstelligen. In der Auswahlbox oben im Dialogfenster stellt man hierzu die zu korrigierende Farbe ein (Red/Green/Blue) und verbeult dann die anfangs gerade Kurve, bis sie ermittelten Kontrollwerte berührt.

Abbildung 5: Im "Curves"-Dialog lässt sich ein Farbstich korrigieren. Hier wird die Kurve des roten Kanals mit drei Kontrollpunkten angepasst.

Das Verfahren ist recht simpel: Wenn zum Beispiel die graue Karte die Werte Rot:122, Grün:127 und Blau:123 ergibt, werden sowohl der rote als auch der blaue Kanal auf den grünen Wert eingestellt, sodass sich Rot:127, Grün:127 und Blau:127 und damit Farblosigkeit ergibt. Hierzu öffnet man den roten Kanal im ``Curves''-Dialog und zieht die sich geschmeidig anpassende Kurve am Punkt 122/122 (auf der Geraden) zum Punkt 122/127 (Beule). Gimp zeigt während des Verschiebens links oben die aktuellen Koordinaten an. Im blauen Kanal wird entsprechende der Punkt 123/123 auf den Punkt 123/127 gezogen. So entstehen im roten und blauen Kanal leicht verbeulte Kurven wie in Abbildung 5 gezeigt.

Wurde das Verfahren auch für die schwarze und die weiße Karte wiederholt, befinden sich sowohl im roten als auch im blauen Kanal jeweils drei Kontrollpunkte, die die Kurve verbiegen. Das Ganze lässt sich natürlich auch skripten. Listing ColorCast.pm zeigt, dass der Konstruktor zwei Werte erwartet, yml_file (die YAML-Datei mit den Messwerten) und drawable, den Gimp-Layer, auf dem die Transformation durchgeführt wird.

Listing 2: ColorCast.pm

    01 ###########################################
    02 package ColorCast;
    03 # Mike Schilli, 2008 (m@perlmeister.com)
    04 ###########################################
    05 use strict;
    06 use warnings;
    07 
    08 use YAML qw(LoadFile DumpFile);
    09 use Gimp qw(:auto);
    10 use Log::Log4perl qw(:easy);
    11 
    12 my %channels = (
    13    red   => HISTOGRAM_RED,
    14    blue  => HISTOGRAM_BLUE,
    15    green => HISTOGRAM_GREEN,
    16 );
    17 
    18 ###########################################
    19 sub new {
    20 ###########################################
    21     my($class, %options) = @_;
    22 
    23     my $self = {
    24         yml_file => undef,
    25         drawable => undef,
    26         ctrls    => undef,
    27         %options,
    28     };
    29 
    30     bless $self, $class;
    31 }
    32 
    33 ###########################################
    34 sub save {
    35 ###########################################
    36     my($self) = @_;
    37 
    38     DumpFile $self->{yml_file}, 
    39              $self->{ctrls};
    40 }
    41 
    42 ###########################################
    43 sub load {
    44 ###########################################
    45     my($self) = @_;
    46 
    47     $self->{ctrls} =
    48       LoadFile $self->{yml_file};
    49 }
    50 
    51 ###########################################
    52 sub adjust_to {
    53 ###########################################
    54   my($self, $ref_channel) = @_;
    55 
    56   DEBUG "Adjusting to $ref_channel";
    57 
    58   for my $channel (keys %channels) {
    59 
    60     next if $ref_channel eq $channel;
    61 
    62     my $ctrls = $self->{ctrls};
    63 
    64     my @points = (0, 0, 255, 255);
    65 
    66     for my $ctrl (keys %$ctrls) {
    67       push @points, 
    68          $ctrls->{$ctrl}->{$channel},
    69          $ctrls->{$ctrl}->{$ref_channel};
    70     }
    71 
    72     gimp_curves_spline(
    73       $self->{drawable}, 
    74       $channels{ $channel },
    75       \@points);
    76   }
    77 }
    78 
    79 1;

Die Methode load() liest dann die YAML-Werte ein, die anschließend in einem Hash von Hashes liegen. Ab Zeile 52 erwartet dann adjust_to() einen Kanal (z.B. ``green''), worauf es die Werte der restlichen Kanäle (``red'', ``blue'') an die grünen Werte anpasst. Dies geschieht mit der Gimp-Funktion gimp_curves_spline(), die als Parameter den aktiven Layer des zu modifizierenden Bildes, den zu modifizierenden Kanal und eine Reihe von Kontrollpunkten erwartet. Zusätzlich zu den Messpunkten kommen in adjust_to() immer noch (0,0) und (255,255) hinzu, damit die Kurve wie in Abbildung 5 gezeigt links unten anfängt und rechts oben aufhört.

Ohne spezielle Angaben führt das Skript picfix keine Farbkorrektur durch. Mit der Option -c gibt man eine eventuell vorher erstellte YAML-Datei an und die Option -a (adjust) nimmt einen Kanalnamen an, auf den die beiden anderen Kanäle ausgerichtet werden. Voreingestellt ist ``green''.

Trickreich installieren

Eigentlich sollte ein einfaches

    sudo apt-get install libgimp-perl

genügen, um die ganze Perl-Gimp-Enchilada installieren, aber leider flippt der aktuelle Ubuntu-Release (auch der Perlmeister ist mittlerweile Ubuntu verfallen) dabei aus. Synaptic treibt's noch bunter und will gar den Gimp und sogar den Ubuntu-Desktop entfernen, falls man libgimp-perl zu installieren begehrt. Grund für dieses Tohuwabohu scheint ein kaputtes Gimp-Paket zu sein, das zwar einige Dateien der libgimp-perl-Distribution enthält, aber nicht die notwendigen Perlmodule. Und natürlich definiert es auch noch einen ``Conflict'' und ein ``Replaces'' auf die aktuelle Version von libgimp-perl, damit der Package-Manager mit den überlappenden Dateien nicht durcheinander kommt.

Man kann allerdings mit den im Kasten ``Ubuntu ausgetrickst'' gezeigten Kommandos die libgimp-perl-Sourcen herunterladen, neu kompilieren, eine Version mit einer um 1 höheren Nummer erzeugen (aus ...dfsg-2 wird ...dfsg-3) und installieren. Ganz sauber ist die Lösung nicht, der nächste Gimp-Update wird wahrscheinlcih dazwischenfunken.

    ----------------------------------------------------
    Kasten: "Ubuntu ausgetrickst"
    sudo apt-get install devscripts
    sudo apt-get source libgimp-perl
    sudo apt-get build-dep libgimp-perl
    cd libgimp-perl-2.0.dfsg+2.2pre1.dfsg
    sudo dch --newversion=2.0.dfsg+2.2pre1.dfsg-3 -- Version Bump
    sudo dpkg-buildpackage -uc -us
    cd ..
    sudo dpkg --install --force-overwrite libgimp-perl_2.0.dfsg+2.2pre1.dfsg-3_i386.deb
    ----------------------------------------------------

Das Modul ColorCast.pm sollte irgendwo installiert werden, wo picfix es findet, alternativ kann man picfix im Code mit usr lib "verzeichnis"; auf das Installationsverzeichnis von ColorCast.pm hinweisen.

Abbildung 6: Behält man den voreingestellten Loglevel bei, loggt das Skript detailliert mit, was es gerade treibt.

Die zusätzlich verwendeten Perl-Module Log::Log4perl und YAML liegen auf dem CPAN und lassen sich einfach entweder mit einer CPAN-Shell oder, komfortabler in Ubuntu, mit apt-get install libyaml-perl bzw. liblog-log4perl-perl installieren. Zeile 13 stellt mit dem Logging-Level $DEBUG detailliertes Logging ein, wen diese Geschwätzigkeit mit der Zeit nervt, setzt es auf $ERROR hoch.

Manche mögen's roh

Digitale SLR-Kameras wie die Nikon D70 speichern aufgenommene Bilder auf Verlangen auch zusätzlich im Rohformat ab. Allerdings verbraucht dies massiv Speicherplatz und erfordert die Installation des Ubuntu-Pakets gimp-ufraw, damit Gimp die rohen Daten erfasst. Allerdings kann Gimp die Daten im rohen .nef-Format zwar lesen, aber nicht mehr zurückschreiben. Deswegen wandelt picfix die Dateiendung kurz vor dem Schreibbefehl in .png um, worauf Gimp das Ergebnis im png-Format abspeichert.

Infos

[1]
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2008/07/Perl

[2]
``Grokking the Gimp'', Carey Bunks, New Riders, 2000

[3]
``Foto-Labor'', Michael Schilli, Linux Magazin 09/2003 http://www.linux-magazin.de/Artikel/ausgabe/2003/09/perl/perl.html

[4]
Tutorial zu Gimps Perlschnittstelle: http://imagic.weizmann.ac.il/~dov/gimp/perl-tut-2.0/

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.