SecurID-Tokens helfen bei der Zugangskontrolle, denn sie zeigen Ziffernfolgen an, die jeweils nur eine Minute lang zum Einloggen auf einem Zielsystem gültig sind. Eine mit Perl gestrickte Zeichenerkennung legt offen, was der Token den ganzen Tag so treibt.
Neulich berichtete mein Arbeitskollege Fergus, dass sein SecurID-Token gerade ``000000'' angezeigt hätte und postete prompt ein Foto auf Flickr [2]. SecurID-Anhänger von der Firma RSA geben alle 60 Sekunden eine unterschiedliche 6-stellige Zahl aus. Wenn alle Ziffernfolgen mit der gleichen Wahrscheinlichkeit auftreten, ist die Chance, zu einem zufälligen Zeitpunkt auf der Anzeige ``000000'' zu sehen, eins zu einer Million. Das kommt fast einem Lottotreffer gleich!
Abbildung 1: Der 'Keyfob' von RSA Security zeigt alle 60 Sekunden eine neue sechsstellige Zahl an. |
Dieser Zufall weckte meine Neugier: Was zeigt eigentlich mein ``Keyfob'' (amerikanisch für Schlüsselanhänger) an, wenn ich gerade nicht hinsehe? Mit einer Webcam oder einem Scanner lässt sich die Anzeige leicht als Bild digitalisieren. Und aus den Pixeln ermittelt man dann per Optical Character Recognition (OCR) die tatsächlichen Ziffernwerte. Da der Bereich aber leider totpatentiert wurde, gibt es dafür kaum funktionsfähige freie Software.
Abbildung 2: Der Versuchsaufbau zur Fobcam: Eine Lampe sorgt für gleichmässig verteiltes Licht und die Webcam zeigt senkrecht auf den SecurID-Token. |
Im vorliegen Fall ist das Problem jedoch einfacher zu lösen, denn der vom Token dargestellte Zeichensatz ist auf die Ziffern 0-9 beschränkt. Sie sind grob aus nur sieben LCD-Segmenten zusammengesetzt, und die Erkennung ist tatsächlich relativ leicht mit einem Perlskript und den richtigen CPAN-Modulen zu bewerkstelligen.
Aber vorher noch ein Warnhinweis: Der heutige Snapshot dient nur zu Forschungszwecken. Auf keinen Fall sollten die Keyfob-Daten auf elektronischem Wege übermittelt werden. Die so genannte ``Fobcam'' [3] eines Computernutzers, der die aktuelle Ziffernfolge seines SecurID-Token aus Bequemlichkeit mittels Webcam auf seiner Website anzeigte, ist zwar immer wieder für einen Lacher gut, wird aber nicht zur Nachahmung empfohlen.
Abbildung 3: Das OCR-System hat die dargestellte Zahl korrekt erkannt. An den beiden grünen Referenzpunkten hängt die OCR-Maske, an den roten Stellen sind Sensoren platziert, die die dargestellte Zahl abtasten. |
Um ein Bild des Tokens aufzunehmen, bietet sich eine Webcam oder ein Scanner an. In einem frühreren Snapshot [4] wurde schon mal gezeigt, wie man unter Linux mittels Video::Capture::V4l eine Webcam ansteuert, mittlerweile habe ich das Modul Video::Capture::V4l::Imager aufs CPAN gestellt, das die Sache noch weiter vereinfacht.
01 #!/usr/bin/perl -w 02 use strict; 03 use Video::Capture::V4l::Imager; 04 use Log::Log4perl qw(:easy); 05 06 Log::Log4perl->easy_init($DEBUG); 07 08 my $v = Video::Capture::V4l::Imager->new( 09 width => 640, 10 height => 480, 11 ); 12 13 $v->brightness($ARGV[0] || 27_900); 14 my $img = $v->capture(); 15 16 $img->write(file => 'fob.jpg') 17 or die "Can't write: $!";
Listing fobcam
zeigt, wie die Kamera angesteuert und zunächst
die gewünschte Helligkeit mit der Methode brightness()
eingestellt
wird. Der beste Wert hängt vom Kamertyp und von den aktuell
herrschenden Lichtverhältnissen ab. Nach einigem Experimentieren
findet sich meistens ein akzeptabler Wert. Das CPAN-Modul bietet auch
die Methode calibrate()
an, die solange mit verschiedenen Einstellungen
für brightness()
herumprobiert, bis das aufgenommene Bild eine
vorgegebene mittlere Helligkeit zeigt.
Das Capture-Objekt $v
liefert im Erfolgsfall als Ergebnis
ein Objekt vom Typ Imager
zurück, an dem man entweder
Bildmanipulationen vornehmen oder die Daten in einem gängigen
Format wie JPEG oder PNG auf der Festplatte abspeichern kann.
Für den OCR-Vorgang wird zunächst die Lage des Keyfob-Displays im Bild ermittelt und die Position jeder einzelnen Ziffer berechnet. Steht das Rechteck fest, in dem die aus sieben LCD-Segmenten zusammengesetzte Zahl liegt, definiert das Erkennungsskript über jedem Segment einen Fühler, der die dort vorhandenen Pixelwerte misst.
Liefert der Fühler einen hellen Pixelwert, ist das dort vermutete Segment inaktiv. Kommt allerdings ein niedriger RGB-Wert zurück, weist das Bild dort eine dunkle Stelle auf und die ist bei ordnungsgemäßer Belichtung auf ein aktives LCD-Segment zurückzuführen.
Abbildung 4: Die Segmente des LCD-Displays werden zur einfachen Zeichenerkennung durchnumeriert. |
Abbildung 4 zeigt eine einzelne LCD-Ziffer, mit denen der Keyfob die Zahlen von 0-9 anzeigt. Der Übersicht halber wurden ihre Segmente willkürlich durchnumeriert. Zeigt das Display die Zahl 8 an, sind alle Segmente von 1-7 aktiv, während bei der Zahl 1 nur die Segmente 2 und 3 aufleuchten.
Soweit die Theorie. In der Praxis stellt sich heraus, dass der Token manchmal schief im Bild liegt, die Billig-Webcam im Nahbereich erschreckend schlechte Bildqualität liefert und die Ausleuchtung der Szene mit einer Schreibtischlampe alles andere als homogen ist. Es müssen also einige Tricks ran.
Um die Lage der Einzelsegmente berechnen zu können, muss sich das Erkennungsskript zunächst im Bild orientieren. Dies geschieht mittels zweier Referenzpunkte, deren Pixelkoordinaten (x1_ref, y1_ref) und (x2_ref, y2_ref) dem Skript bekannt sind. Für den verwendeten RSA-Token wurden hierfür die beiden äußeren oberen Eckpunkte der blauen Fläche (Abb. 1) ausgewählt.
Zu Experimentierzwecken ist unter den Listings zu diesem Artikel [1]
noch ein Modul Blue.pm
erhältlich, das die beiden Referenzpunkte
durch ein ausgefuchstes Verfahren ermittelt, dessen Funktion allerdings
den Rahmen dieses Artikels sprengen würde. Man kann sie allerdings
genausogut mit Gimp ermitteln, in dem man eine Testaufnahme lädt und
mit der Maus an die beiden Referenzpunkte heranfährt. Gimp zeigt die
jeweiligen X/Y-Koordinaten dann in der linken unteren Ecke an.
Mit den beiden Referenzpunkten lässt sich der Token im Bild eindeutig lokalisieren. Jeder Keyfob hat jedoch andere Abmessungen, und auch für den gleichen Token beeinflusst die eingestellte Auflösung bzw. Bildvergrößerung die Werte für die Entfernungen von den Referenzpunkten zu den Segmenten.
Aus diesem Grund werden die Maße nicht fest in das Skript
einkodiert sondern in der Konfigurationsdatei fobs.yml
verwaltet.
01 # Key Fob Characteristics 02 RSA1: 03 x1_ref: 176 04 y1_ref: 232 05 x2_ref: 422 06 y2_ref: 155 07 x_off: 99 08 y_off: 9 09 digit_width: 12 10 digit_height: 27.5 11 digit_dist: 23 12 digits: 6
Abbildung 5: Die zwei Referenzpunkte (x1_ref, y1_ref) und (x2_ref, y2_ref) an den oberen Eckpunkten der blauen Fläche legen das Koordinatensystem fest, in denen später die Pixelwerte einzelner Ziffern abgegriffen werden. |
Den horizontalen Abstand zwischen dem ersten Referenzpunkt eines
zu Testzwecken
kerzengerade im Bild liegenden Tokens und dem Mittelpunkt der
ersten Ziffer der Anzeige legt x_off
fest. y_off
ist hingegen
der vertikale Abstand zwischen der obersten Segmentreihe und
einer gedachten horizontalen Linie, die die beiden Referenzpunkte
verbindet (Abbildung 5).
Die Breite einer LCD-Ziffer wird mit digit_width
definiert
und digit_height
gibt deren Höhe an. digit_dist
ist der
horizontale Abstand vom Anfang einer Ziffer zum Anfang der
nächsten. digits
schließlich gibt die Anzahl der auf dem
Display dargestellten Zeichen an.
Die Maßangaben in fobs.yml
werden in Pixeln gemessen, sind aber
unabhängig von einer bestimmen Bildauflösung. Denn bevor das
Skript die Maße verwendet, berechnet es den Abstand der
beiden Referenzpunkte im wirklichen Bild, vergleicht ihn mit
dem willkürlich in fobs.yml
gewählten und passt alle Werte
entsprechend an.
Das Skript in Listing reco
nimmt eine Bilddatei entgegen
und die vier Koordinaten der zwei Referenzpunkte des Tokens
im Bild:
$ reco fob.jpg 160 193 425 218 372394
Es gibt die erkannte sechsstellige Anzeige aus. Es
bedient sich dabei des weiter unten besprochenen Moduls
LCDOCR.pm
, dessen Methode reco()
eine Referenz auf einen
Array zurückliefert, der die erkannten Ziffern beinhaltet.
Da das Skript im Verbose-Modus (-v
) zusätzlich die Referenzpunkte,
die Fühler und die gefundenen Ziffern ins Bild einblendet und
in der Datei out1.jpg
abspeichert (wie in Abbildung 3 dargestellt),
lässt es sich zur Feinjustierung der Konfigurationsparameter
nutzen. Mit der Option -d
aufgerufen startet es nach
dem Erkennungslauf zusätzlich den schnellen Image-Viewer xv
mit
dem durch die eingeblendeten Informationen angereicherten Bild.
01 #!/usr/bin/perl -w 02 use strict; 03 use Log::Log4perl qw(:easy); 04 use LCDOCR; 05 use Getopt::Std; 06 07 getopts("vd", \my %opts); 08 Log::Log4perl->easy_init($opts{v} ? 09 $DEBUG : $ERROR); 10 my($file, $x1, $y1, $x2, $y2) = @ARGV; 11 die "usage: $0 file x1 y1 x2 y2\n" unless 12 defined $y2; 13 14 my $i = Imager->new(); 15 $i->read(file => $file, type => "jpeg") or 16 die "Can't read $file"; 17 18 my $gr = Imager::Color->new(0, 255,0); 19 $i->circle(color=>$gr,r=>1,x=>$x1, y=>$y1); 20 $i->circle(color=>$gr,r=>1,x=>$x2, y=>$y2); 21 22 my $ocr = LCDOCR->new( 23 name => 'RSA1', 24 x1_ref => $x1, y1_ref => $y1, 25 x2_ref => $x2, y2_ref => $y2, 26 image => $i, 27 debug => ($opts{v}||0)); 28 29 my $digits = $ocr->reco(); 30 31 if($opts{v}) { 32 my $font = Imager::Font->new(file => 33 "/usr/X11R6/lib/X11/fonts/TTF/Vera.ttf"); 34 35 $i->string(x => 50, y => 50, 36 string => "Reco: @$digits", 37 font => $font, color => "white", 38 size => 30); 39 $i->write(file => "out1.jpg", 40 type => "jpeg"); 41 system("xv out1.jpg") if $opts{d}; 42 } 43 print join('', @$digits), "\n";
Das Modul LCDOCR.pm
implementiert den OCR-Vorgang auf
das LCD-Display. Es
steigt zum Herumorgeln auf den Bilddaten in die C-Welt
des Imager-Moduls hinunter. Dies könnte man wie in [4] mit einer
.xs-Datei bewerkstelligen, doch mit dem Modul Inline::C vom
CPAN lässt sich C-Code auch direkt in das Perlskript einbinden.
Dieser wird dann beim ersten Aufruf für den User unsichtbar übersetzt und die Objekt- und Shared-Library-Dateien im Unterverzeichnis _Inline aufgehoben. Beim nächsten Aufruf wird der Compilierschritt übersprungen und das Skript startet mit voller Geschwindigkeit. Ändert sich der C-Code im Skript, merkt Inline::C das und leitet wieder eine Neukompilation ein.
001 use strict; 002 use Imager; 003 use Log::Log4perl qw(:easy); 004 use YAML qw(LoadFile); 005 006 ########################################### 007 sub new { 008 ########################################### 009 my($class, %options) = @_; 010 011 my $refd = LoadFile("/etc/fobs.yml")-> 012 {$options{name}}; 013 my $self = { 014 name => "RSA1", 015 threshold => 0.85, 016 debug => 0, 017 digits => $refd->{digits}, 018 %options, 019 }; 020 # Adapt coordinates to real image 021 my $stretch = ref_dist($self) / 022 ref_dist($refd); 023 for (qw(x_off y_off digit_width 024 digit_height digit_dist)) { 025 $self->{$_} = $refd->{$_} * $stretch; 026 } 027 028 $self->{angle} = atan2 ( 029 $self->{y2_ref}-$self->{y1_ref}, 030 $self->{x2_ref}-$self->{x1_ref}); 031 032 bless $self, $class; 033 } 034 035 ########################################### 036 sub ref_dist { 037 ########################################### 038 my($h) = @_; 039 return sqrt( 040 ($h->{x2_ref} - $h->{x1_ref})**2 + 041 ($h->{y2_ref} - $h->{y1_ref})**2) 042 } 043 044 ########################################### 045 sub reco { 046 ########################################### 047 my($self) = @_; 048 049 my @digits; 050 my %seg_orient = qw( 051 1 h 2 v 3 v 4 h 5 v 6 v 7 h); 052 053 for (1..$self->{digits}) { 054 my $coords = $self->seg_coords($_); 055 my $segstring = ""; 056 057 my $bkground = ( 058 xybrightness($self->{image}, 059 @{$coords->{8}}) + 060 xybrightness($self->{image}, 061 @{$coords->{9}}) 062 ) / 2; 063 064 for my $c (1..7) { 065 my($x, $y) = @{$coords->{$c}}; 066 067 if(pixel_dark($self->{image}, $x, 068 $y, $bkground, 069 $self->{debug}, $c, 070 $seg_orient{$c}, 071 $self->{threshold})) { 072 $segstring .= "$c"; 073 } 074 075 if($self->{debug}) { 076 my $red = Imager::Color->new( 077 255, 0, 0); 078 $self->{image}->circle( 079 color=>$red, r=>1, x=>$x, y=>$y); 080 } 081 } 082 083 my $digit = seg2digit($segstring); 084 push @digits, 085 defined $digit ? $digit : "X"; 086 } 087 088 return \@digits; 089 } 090 091 ########################################### 092 sub seg_coords { 093 ########################################### 094 my($self, $digit) = @_; 095 096 my $x = $self->{x_off} + 097 ($digit-1) * $self->{digit_dist}; 098 my $y = $self->{y_off}; 099 my $w = $self->{digit_width}; 100 my $h = $self->{digit_height}; 101 my $r = sub { [ $self->rotate(@_) ] }; 102 103 return { 104 1 => $r->($x, $y), 105 2 => $r->($x + $w/2, $y + $h/4), 106 3 => $r->($x + $w/2, $y + 3*$h/4), 107 4 => $r->($x, $y + $h), 108 5 => $r->($x - $w/2, $y + 3*$h/4), 109 6 => $r->($x - $w/2, $y + $h/4), 110 7 => $r->($x, $y + $h/2), 111 # ref points 112 8 => $r->($x, $y + $h/4), 113 9 => $r->($x, $y + 3*$h/4), 114 }; 115 } 116 117 ########################################### 118 sub seg2digit { 119 ########################################### 120 my %h = ( 121 "23" => 1, "12457" => 2, 122 "12347" => 3, "2367" => 4, 123 "13467" => 5, "134567" => 6, 124 "123" => 7, "1234567" => 8, 125 "123467" => 9, "123456" => 0, 126 ); 127 return $h{$_[0]}; 128 } 129 130 ########################################### 131 sub rotate { 132 ########################################### 133 my($self, $xd, $yd) = @_; 134 135 my $r = sqrt($xd*$xd + $yd*$yd); 136 137 my $phi = atan2($yd,$xd); 138 $phi += $self->{angle}; 139 140 my $xd_rot = $r * cos($phi); 141 my $yd_rot = $r * sin($phi); 142 my $x_abs = $self->{x1_ref} + $xd_rot; 143 my $y_abs = $self->{y1_ref} + $yd_rot; 144 145 return($x_abs, $y_abs); 146 } 147 148 use Inline C => <<'EOT' => WITH => 'Imager'; 149 150 int pixel_dark(Imager im, int x, int y, 151 int threshold, int debug, 152 int seg, char *direction, 153 float percent) { 154 i_color val; 155 int br, i, j, dark=0, min=-1; 156 int imin=0, imax=1, jmin=0, jmax=1; 157 float rel; 158 159 if(direction == 'h') { 160 jmin = -1; jmax = 2; 161 } else { 162 imin = -1; imax = 2; 163 } 164 165 for(i=imin; i<imax; i++) { 166 for(j=jmin; j<jmax; j++) { 167 i_gpix(im, x+i, y+j, &val); 168 br = brightness(&val); 169 if(min == -1 || min > br) 170 min = br; 171 } 172 } 173 174 rel = 1.0*min/threshold; 175 if(rel < percent) 176 dark = 1; 177 178 if(debug) { 179 printf("TH[%d]: %d (%d %.1f%%: %d)\n", 180 seg, min, threshold, rel*100.0, dark); 181 } 182 return dark; 183 } 184 185 int brightness(i_color *val) { 186 return((val->channel[0] + 187 val->channel[1] + 188 val->channel[2])/3); 189 } 190 191 int xybrightness(Imager im, int x, int y) { 192 i_color val; 193 i_gpix(im, x, y, &val); 194 return brightness(&val); 195 } 196 197 EOT 198 199 1;
Um den Drehwinkel des Tokens relativ zum Bildrand
aus den Referenzpunkten zu berechnen, verwendet
der Konstruktor new
einfache trigonometrische Funktionen.
Die Koordinatenabstände bilden die Katheten eines rechtwinkligen Dreiecks, also ergibt sich der Drehwinkel aus dem Arcus Tangens des Quotienen von Gegenkathete zu Ankathete. Perl hat von Haus aus keine atan-Funktion, aber dafür atan2(), die die zwei Kathetenlängen separat entgegennimmt.
Abbildung 6: Der Neigungswinkel des Tokens berechnet sich aus den Koordinaten der Referenzpunkte. |
Da der Token verdreht im Bild liegen darf, spannt die Zeichenerkennung ihr Erkennungsnetz horizontal auf und dreht es mit C<rotate()> in den zu diesem Zeitpunkt bereits bekannten Winkel des Tokens. Drehungen in einem karthesischen X/Y-Koordinatensystem sind etwas umständlich zu berechnen, deshalb werden die karthesischen Koordinaten zunächst in Polarkoordinaten C<r> und C<phi> umgewandelt (Abbildung 6) Der Radius C<r> errechnet sich aus dem Satz des Pytagoras und der Drehwinkel C<phi> aus dem Arcus Tangens des Quotienten aus Y- und X-Wert.
Zu diesem Winkel addiert Zeile 138 dann den bekannten Drehwinkel des Tokens, bevor die darauffolgenden Zeilen die Koordinaten mit einfacher Trigonometrie (Sinus, Gegenkathete, Hypotenuse!) wieder in karthesische X-/Y-Werte zurückrechnen. Fertig ist die Rotation der OCR-Maske um den Drehpunkt [x1_ref, y1_ref].
Abbildung 7: Umwandlung von karthesischen in Polarkoordinaten und zurück. |
Die Funktion seg2digit()
ermittelt aus einem String mit sortierten
Ordnungsnummern aktivierter Segmente die dargestellte Ziffer.
Die Sortierung vereinfacht den Zugriff, denn findet das Skript,
dass die Segmente 2 und 3 eines Elementes schwarz sind, liefert der
Aufruf von seg2digit
mit ``23'' einfach per Hash-Lookup ``1'' zurück,
was genau der dargestellten Ziffer entspricht.
Ist das von den Segmenten dargestellte keine Zahl, gibt seg2digit
den Wert undef
zurück und das Hauptprogramm setzt statt dessen
``X'' ein. So lässt sich feststellen, dass noch etwas mit der Justierung
oder den Lichtverhältnissen im Argen liegt.
Abbildung 8: Die zwei grünen Messpunkte am Rand helfen, die Lage des Keyfobs zu ermitteln, die roten Messpunkte im Display greifen die dargestellten Ziffern ab. |
Falls das Display nicht gleichmäßig ausgeleuchtet ist, ist der graue Hintergrund unterschiedlich hell und es ist nicht einfach, einen zuverlässigen Schwellenwert zu bestimmen, der die Pixelwerte eines aktivierten von denen eines inaktiven Segments unterscheidet.
Deshalb misst die Methode reco
nicht nur die Pixelhelligkeit an den
Stellen, an denen sich Segmente befinden, sondern auch in den segmentfreien
Zonen
in den oberen und unteren Bäuchen der viereckigen Achten.
Diesen Messwerten werden die Nummern 8 und 9 zugewiesen
Sie werden gesondert behandelt und ihr Mittelwert als
Hintergrundhelligkeit eines Segments bewertet.
Der Parameter threshold
gibt an, um wieviel dunkler als der Hintergrund
ein Messwert sein muss als, damit das Verfahren
ein aktives Segment erkennt. Ist threshold
zum Beispiel 0.85
und der Hintergrund hat gemäß den zwei Bauchwerten
die mittlere Helligkeit 180, werden Messwerte größer
gleich 153 als Hintergrund, also als inaktive LCD-Segmente interpretiert.
Abbildung 8 zeigt, wie die Ziffer ``0'' mit einem threshold
-Wert von
0.85 erkannt wird. Aktive Segmente schwanken zwischen 40.5% und 72.5%
des vorher ermittelten Bauchmittelwerts von 131. Das inaktive Segment
7 weist hingegen den Helligkeitswert 123 auf, mit 93.9% knapp über
dem festgestetzten Schwellwert von 85%.
Abbildung 9: Die Ziffer '0' wird erkannt, der Parameter 'threshold' zur Unterscheidung von aktiven und inaktiven Segmenten ist auf 85% gesetzt. |
Um auch bei leicht verschobenen Koordinaten zuverlässig festzustellen,
welche Segmente der Anzeige schwarz eingefärbt sind, misst die
im Inline-C-Bereich definierte Funktion pixel_dark
nicht nur den
aktuellen Pixel, sondern auch umliegende und nimmt nur den dunkelsten
als Messwert. Damit so nicht aus Versehen Teile des Nachbarsegments
gemessen werden, misst die Funktion immer orthogonal zum Segment:
Bei waagrechten Segmenten werden die oberen und unteren Pixel
zur Messung hinzugezogen, bei senkrechten Segmenten die links
und rechts liegenden. Der Hash %segdir
gibt hierzu
zu jeder Segmentnummer
dessen Lage (h=horizontal v=vertikal) an.
Die Funktion brightness()
misst jeweils die Helligkeit
eines Pixelwertes und zählt dazu die Rot-, Grün- und Blauanteile eines
Messpunkts zusammen. xybrightness()
berechnet die Helligkeit
an einer Koordinate [x,y].
Abbildung 10: Eine lustige Kombination wurde gefunden. |
Die Funktion seg_coords($x, $y)
liefert die X/Y-Koordinaten aller Segmente
einer Ziffer, deren oberstes Segment auf den Koordinaten $x
und
$y
liegt. Zurück kommt eine
Referenz auf einen Hash, der als Schlüssel die Segmentordnungsnummern
und als Werte anonyme Arrays mit X/Y-Koordinaten führt.
Ist die Debug-Option debug
aktiviert, zeichnet die Funktion reco()
die Segmentkoordinaten gemäß Abbildung 6
gleich in Rot ins Bild hinein. Dies erfolgt natürlich
nach der Messung, denn sonst wären ja alle Pixel plötzlich rot.
Mit diesen Zusatzinformationen lassen sich
Feinkalibrierungen vornehmen, falls die Justierung noch nicht
ganz stimmt und nachgebessert werden muss.
Zur Installation werden die erforderlichen CPAN-Module
Video::Capture::V4l::Imager
und YAML
heruntergeladen. Innerhalb
einer CPAN-Shell schleifen sie automatisch alle weiteren
benötigten Module
mit. Das Modul LCDOCR.pm
muss irgendwo hin, wo das Skript reco
es findet (z.B. /usr/lib/perl5/site_perl) und dann kann mit
fobcam
die erste Aufnahme gestartet werden. Mit Gimp werden
dann die Referenzpunkte ermittelt und die Datei /etc/fobs.yml
mit den
Daten des verwendeten Displays erweitert. Anschließend wird
reco
mit dem Namen der gesicherten Bilddatei und den Referenzkoordinaten
aufgerufen. Nach etwas Gefummle erkennt das OCR-System die angezeigten
Zahlenkolonnen zuverlässig und die Auswertung kann
beginnen. Wer das Skript über Nacht laufen lässt, sollte bedenken,
die Schreibtischlampe angeschaltet zu lassen :).
Michael Schilliarbeitet 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. |