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.