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. |