Aus Linux-Magazin 05/2007

OCR im Eigenbau mit Perl-Modulen

© photocase.com

In manchen Firmen laufen die Mitarbeiter mit Secur-ID-Tokens der Firma RSA Security herum. Das kleine Authentisierungsgerät berechnet und zeigt jede Minute eine Ziffernkombination, die beim Einloggen temporär gültig ist. Eine mit Perl gestrickte Zeichenerkennung schaut dem Schlüsselgenerator beim Zocken zu.

Neulich berichtete mein Arbeitskollege Fergus, dass sein Secur-ID-Token gerade »000000« angezeigt hätte, und postete prompt ein Foto auf Flickr [2]. Das besagte Token in Form eines Secur-ID-Schlüsselanhängers gibt alle 60 Sekunden eine neue sechsstellige Zahl aus (Abbildung 1). Wenn alle Ziffernfolgen mit der gleichen Wahrscheinlichkeit auftreten, ist die Chance, mit einem Blick »000000« zu sehen, eins zu einer Million. Das kommt fast einem Lottotreffer gleich, nur ohne Geld.

Abbildung 1: Der Schlüsselanhänger von RSA Security zeigt alle 60 Sekunden eine neue sechsstellige Zahl an.

Abbildung 1: Der Schlüsselanhänger von RSA Security zeigt alle 60 Sekunden eine neue sechsstellige Zahl an.

Totpatentiert

Das Ereignis weckte meine Neugier: Was zeigt eigentlich mein Keyfob (so heißen Schlüsselanhänger auf Amerikanisch) an, wenn ich gerade nicht hinsehe? Mit einer Webcam oder einem Scanner lässt sich die Anzeige als Bild digitalisieren. Und aus den Pixeln ermittle ich dann per Optical Character Recognition (OCR) die tatsächlichen Ziffernwerte. Da Hersteller den OCR-Bereich aber leider totpatentiert haben, gibt es dafür kaum funktionsfähige freie Software.

Vorher noch ein Warnhinweis: Der heutige Snapshot dient nur zu Forschungszwecken. Niemand sollte die Keyfob-Daten elektronisch übermitteln. Die so genannte Fobcam [3] eines Computernutzers, der die aktuelle Ziffernfolge seines Secur-ID-Token aus Bequemlichkeit per Webcam im Internet anzeigt, ist zwar immer wieder für einen Lacher gut, aber nicht zur Nachahmung empfohlen.

Um ein Bild des Token aufzunehmen, bieten sich eine Webcam (Abbildung 2) oder ein Scanner an. Ein früherer Snapshot [4] zeigte schon, wie unter Linux mittels Video::Capture::V4l eine Webcam anzusteuern ist. Mittlerweile ist das CPAN-Modul Video::Capture::V4l::Imager ebenfalls verfügbar, das die Sache weiter vereinfacht.

Abbildung 2: Der Versuchsaufbau zur Fobcam: Eine Lampe sorgt für gleichmäßig verteiltes Licht und die Webcam zeigt senkrecht auf den Secur-ID-Token.

Abbildung 2: Der Versuchsaufbau zur Fobcam: Eine Lampe sorgt für gleichmäßig verteiltes Licht und die Webcam zeigt senkrecht auf den Secur-ID-Token.

Ins rechte Licht gerückt

Listing 1 zeigt, wie das Perl-Modul die Kamera anspricht und zunächst die gewünschte Helligkeit mit der Methode »brightness()« einstellt. Der beste Wert hängt vom Kameratyp und von den herrschenden Lichtverhältnissen ab. Nach einigem Experimentieren findet sich meist ein akzeptabler Wert.

Listing 1:
»fobcam«

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: $!";

Das Modul bietet auch die Methode »calibrate()« an, die so lange 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, dessen Bilddaten sich entweder weiterbearbeiten oder in einem gängigen Format wie Jpeg oder PNG zur weiteren Verwendung auf der Festplatte abspeichern lassen.

OCR für Arme

Für die Zeichenerkennung ist zunächst die Lage des Keyfob-Displays im Bild sowie die Position jeder einzelnen Ziffer zu ermitteln. Steht das Rechteck fest, in dem die LCD-Segmente liegen, definiert das Erkennungsskript über jedem Segment einen Fühler, der die Helligkeitswerte der jeweiligen Pixel misst.

Liefert der virtuelle Messfühler einen hohen Wert, hat er den Hintergrund vermessen und das zugehörige Segment ist inaktiv. Kommt allerdings ein niedriger RGB-Wert zurück, dann weist das Bild genau dort eine dunkle Stelle auf – und die ist bei ordnungsgemäßer Belichtung auf ein aktives LCD-Segment zurückzuführen. Abbildung 3 zeigt eine einzelne LCD-Ziffer. Ihre Segmente tragen hier willkürliche Nummern. Bei der Ziffer Acht, sind alle Segmente aktiv, während etwa bei der Ziffer Eins nur die Segmente 2 und 3 aufleuchten.

Abbildung 3: Die Nummerierung der Segmente des LCD-Displays vereinfacht die Zeichenerkennung.

Abbildung 3: Die Nummerierung der Segmente des LCD-Displays vereinfacht die Zeichenerkennung.

Soweit jedenfalls die Theorie. In der Praxis stellt sich jedoch heraus, dass beispielsweise der Token manchmal etwas schief im Bild liegt, die Billig-Webcam im Nahbereich erschreckend schlechte Bilder liefert oder dass die Ausleuchtung der Szene mit einer Schreibtischlampe alles andere als homogen ist. Es bedarf also einiger Tricks.

Um die genaue Lage der Einzelsegmente berechnen zu können, muss sich das Erkennungsskript zunächst im Bild orientieren. Das geschieht mit Hilfe zweier Referenzpunkte, deren Pixelkoordinaten (»x1_ref«, »y1_ref«) und (»x2_ref«, »y2_ref«) dem Skript bekannt sind. Bei dem verwendeten RSA-Token kommen dafür die beiden äußeren oberen Eckpunkte der blauen Fläche (Abbildung 1) zum Einsatz.

Wer suchet, der findet

Zu Experimentierzwecken ist unter den Listings zu diesem Artikel [1] noch ein Modul »Blue.pm« erhältlich, das die beiden Referenzpunkte durch ein ebenso kompliziertes wie ausgefuchstes Verfahren ermittelt. Wer die Punkte lieber mit Hilfe von Gimp feststellen will, lädt eine Testaufnahme und fährt mit der Maus die beiden Referenzpunkte an. Gimp zeigt die Koordinaten daraufhin 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 außerdem beeinflusst die eingestellte Auflösung beziehungsweise Bildvergrößerung der Aufnahme auch für den gleichen Token die Werte für die Entfernungen von den Referenzpunkten zu den Segmenten.

Maßgeschneidert für jeden Token

Aus diesem Grund kodiert das Skript die Maße nicht fest, sondern verwaltet sie in der Konfigurationsdatei »fobs.yml« (Listing 2). Den horizontalen Abstand zwischen dem ersten Referenzpunkt eines zu Testzwecken kerzengerade im Bild liegenden Token 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 7).

Listing 2:
»fobs.yml«

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

Die Breite einer LCD-Ziffer definiert »digit_width« und »digit_height« gibt deren Höhe an. Die Variable »digit_dist« speichert den horizontalen 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ße in »fobs.yml« sind in Pixeln angegeben, aber unabhängig von einer bestimmen Bildauflösung. Denn bevor das Skript die Maße verwendet, berechnet es den Abstand der beiden Referenzpunkte im realen Bild, vergleicht ihn mit dem willkürlichen in Listing 2 und passt alle Werte entsprechend an.

Das Skript in Listing 3 nimmt eine Bilddatei und die vier Koordinaten der zwei Referenzpunkte des Token entgegen. Aufgerufen wird es als »reco fob.jpg 160 193 425 218 372394«. Das Skript gibt dann die erkannte sechsstellige Anzeige aus. Es bedient sich dabei des im nächsten Abschnitt besprochenen Moduls »LCDOCR.pm« (Listing 4), dessen Methode »reco()« eine Referenz auf einen Array zurückliefert, der die erkannten Ziffern beinhaltet.

Listing 3:
»reco«

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 y2n" 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";

Listing 4:
»LCDOCR.pm«

001 package LCDOCR;
002 use strict;
003 use Imager;
004 use Log::Log4perl qw(:easy);
005 use YAML qw(LoadFile);
006
007 ###########################################
008 sub new {
009 ###########################################
010  my($class, %options) = @_;
011
012  my $refd = LoadFile("/etc/fobs.yml")->
013       {$options{name}};
014  my $self = {
015    name      => "RSA1",
016    threshold   => 0.85,
017    debug     => 0,
018    digits     => $refd->{digits},
019    %options,
020  };
021   # Adapt coordinates to real image
022  my $stretch = ref_dist($self) /
023         ref_dist($refd);
024  for (qw(x_off y_off digit_width
025      digit_height digit_dist)) {
026    $self->{$_} = $refd->{$_} * $stretch;
027  }
028
029  $self->{angle} = atan2 (
030   $self->{y2_ref}-$self->{y1_ref},
031   $self->{x2_ref}-$self->{x1_ref});
032
033  bless $self, $class;
034 }
035
036 ###########################################
037 sub ref_dist {
038 ###########################################
039  my($h) = @_;
040  return sqrt(
041   ($h->{x2_ref} - $h->{x1_ref})**2 +
042   ($h->{y2_ref} - $h->{y1_ref})**2)
043 }
044
045 ###########################################
046 sub reco {
047 ###########################################
048  my($self) = @_;
049
050  my @digits;
051  my %seg_orient = qw(
052      1 h 2 v 3 v 4 h 5 v 6 v 7 h);
053
054  for (1..$self->{digits}) {
055   my $coords  = $self->seg_coords($_);
056   my $segstring = "";
057
058   my $bkground = (
059    xybrightness($self->{image},
060          @{$coords->{8}}) +
061    xybrightness($self->{image},
062          @{$coords->{9}})
063   ) / 2;
064
065   for my $c (1..7) {
066    my($x, $y) = @{$coords->{$c}};
067
068    if(pixel_dark($self->{image}, $x,
069           $y, $bkground,
070           $self->{debug}, $c,
071           $seg_orient{$c},
072           $self->{threshold})) {
073     $segstring .= "$c";
074    }
075
076    if($self->{debug}) {
077     my $red = Imager::Color->new(
078                 255, 0, 0);
079     $self->{image}->circle(
080      color=>$red, r=>1, x=>$x, y=>$y);
081    }
082   }
083
084   my $digit = seg2digit($segstring);
085   push @digits,
086      defined $digit ? $digit : "X";
087   }
088
089   return @digits;
090 }
091
092 ###########################################
093 sub seg_coords {
094 ###########################################
095  my($self, $digit) = @_;
096
097  my $x = $self->{x_off} +
098     ($digit-1) * $self->{digit_dist};
099  my $y = $self->{y_off};
100  my $w = $self->{digit_width};
101  my $h = $self->{digit_height};
102  my $r = sub { [ $self->rotate(@_) ] };
103
104  return {
105   1 => $r->($x,    $y),
106   2 => $r->($x + $w/2, $y + $h/4),
107   3 => $r->($x + $w/2, $y + 3*$h/4),
108   4 => $r->($x,    $y + $h),
109   5 => $r->($x - $w/2, $y + 3*$h/4),
110   6 => $r->($x - $w/2, $y + $h/4),
111   7 => $r->($x,    $y + $h/2),
112   # ref points
113   8 => $r->($x,    $y + $h/4),
114   9 => $r->($x,    $y + 3*$h/4),
115  };
116 }
117
118 ###########################################
119 sub seg2digit {
120 ###########################################
121  my %h = (
122   "23"   => 1, "12457"  => 2,
123   "12347" => 3, "2367"  => 4,
124   "13467" => 5, "134567" => 6,
125   "123"  => 7, "1234567" => 8,
126   "123467" => 9, "123456" => 0,
127   );
128  return $h{$_[0]};
129 }
130
131 ###########################################
132 sub rotate {
133 ###########################################
134   my($self, $xd, $yd) = @_;
135
136   my $r = sqrt($xd*$xd + $yd*$yd);
137
138   my $phi = atan2($yd,$xd);
139   $phi += $self->{angle};
140
141   my $xd_rot = $r * cos($phi);
142   my $yd_rot = $r * sin($phi);
143   my $x_abs = $self->{x1_ref} + $xd_rot;
144   my $y_abs = $self->{y1_ref} + $yd_rot;
145
146   return($x_abs, $y_abs);
147 }
148
149 use Inline C => <<'EOT' => WITH => 'Imager';
150
151 int pixel_dark(Imager im, int x, int y,
152        int threshold, int debug,
153        int seg, char *direction,
154        float percent) {
155  i_color val;
156  int br, i, j, dark=0, min=-1;
157  int imin=0, imax=1, jmin=0, jmax=1;
158  float rel;
159
160  if(direction == 'h') {
161    jmin = -1; jmax = 2;
162  } else {
163    imin = -1; imax = 2;
164  }
165
166  for(i=imin; i<imax; i++) {
167    for(j=jmin; j<jmax; j++) {
168      i_gpix(im, x+i, y+j, &val);
169      br = brightness(&val);
170      if(min == -1 || min > br)
171        min = br;
172    }
173  }
174
175  rel = 1.0*min/threshold;
176  if(rel < percent)
177   dark = 1;
178
179  if(debug) {
180   printf("TH[%d]: %d (%d %.1f%%: %d)n",
181   seg, min, threshold, rel*100.0, dark);
182  }
183  return dark;
184 }
185
186 int brightness(i_color *val) {
187   return((val->channel[0] +
188       val->channel[1] +
189       val->channel[2])/3);
190 }
191
192 int xybrightness(Imager im, int x, int y) {
193   i_color val;
194   i_gpix(im, x, y, &val);
195   return brightness(&val);
196 }
197
198 EOT
199
200 1;

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, eignet es sich zur Feinjustage der Konfigurationsparameter. Mit der Option »-d« aufgerufen startet das Skript nach dem Erkennungslauf jetzt zusätzlich den schnellen Image-Viewer »xv« und übergibt ihm das Bild mit den eingeblendeten Informationen.

On the Fly kompiliert

Das Modul »LCDOCR.pm« (in Listing 4) implementiert den OCR-Vorgang. Es steigt dafür in die C-Welt des Imager-Moduls hinab. Dies könnte, wie in [4] beschrieben, mit eine XS-Datei erfolgen, doch mit dem Modul Inline::C vom CPAN lässt sich C-Code auch direkt in das Perl-Skript einbinden. Er wird dann beim ersten Aufruf für den User unsichtbar übersetzt. Die Objekt- und Shared-Library-Dateien gelangen ins Unterverzeichnis »_Inline«. Der nächste Aufruf überspringt den Kompilierschritt, das Skript startet dann mit voller Geschwindigkeit. Ändert sich der C-Code im Skript, merkt Inline::C dies und leitet eine Neukompilierung ein.

Um den Drehwinkel des Token relativ zum Bildrand aus den Referenzpunkten zu berechnen, verwendet der Konstruktor »new()« ab Zeile 8 nur einfache trigonometrische Funktionen. Die Koordinatenabstände bilden die Katheten eines rechtwinkligen Dreiecks (Abbildung 4), also ergibt sich der Drehwinkel aus dem Arcus Tangens des Quotienten aus Gegenkathete zu Ankathete. Perl hat von Haus aus keine Atan-Funktion, aber dafür »atan2()«, die beide Kathetenlängen separat entgegennimmt.

Dreh dich im Kreis, Marie

Da der Token verdreht im Bild liegen darf, spannt die Zeichenerkennung ihr Erkennungsnetz horizontal auf und dreht es anschließend mit »rotate()« in den zu diesem Zeitpunkt bereits bekannten Winkel des Token. Drehungen in einem kartesischen Koordinatensystem sind leider etwas umständlich zu berechnen. Sie gelingen aber leichter, wenn man die kartesischen zunächst in Polarkoordinaten »r« und »phi« umwandelt (Abbildung 5).

Der Radius »r« errechnet sich aus dem Satz des Pythagoras und der Drehwinkel »phi« aus dem Arcus Tangens des Quotienten aus y- und x-Wert. Zu diesem Winkel addiert Zeile 138 dann den bekannten Drehwinkel des Token, bevor die darauf folgenden Zeilen die Koordinaten mit einfacher Trigonometrie (unter Verwendung von Sinus, Gegenkathete, Hypotenuse) wieder in kartesische Werte zurückrechnen. Fertig ist die Rotation der OCR-Maske um den Drehpunkt »[x1_ref, y1_ref]«.

Die Funktion »seg2digit()« ermittelt aus einem String mit sortierten Ordnungsnummern aktivierter Segmente die dargestellte Ziffer. Die Sortierung vereinfacht den Zugriff, denn wenn das Skript findet, dass die Segmente 2 und 3 eines Elements schwarz sind, liefert der Aufruf von »seg2digit« mit »23« als Parameter einfach per Hash-Lookup »1« zurück – und das entspricht genau der dargestellten Ziffer.

Stellen die Segmente keine Zahl dar, gibt »seg2digit()« den Wert »undef« zurück und das Hauptprogramm setzt stattdessen »X« ein. So lässt sich feststellen, dass noch etwas bei der Justage oder den Lichtverhältnissen im Argen liegt.

Doppelt hält besser

Falls das Display nicht gleichmäßig ausgeleuchtet ist, erscheint der graue Hintergrund unterschiedlich hell. Es ist also 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 jenen Stellen, an denen sich Segmente befinden, sondern auch in den segmentfreien Zonen in der Mitte des oberen und des unteren Vierecks der Ziffer Acht (Abbildung 3). Aus den beiden Messwerten – sie erhalten die Nummern 8 und 9 – berechnet das Skript den Mittelwert der Hintergrundhelligkeit eines Segments.

Abbildung 4: Der Neigungswinkel des Token errechnet sich aus den Koordinaten der Referenzpunkte.

Abbildung 4: Der Neigungswinkel des Token errechnet sich aus den Koordinaten der Referenzpunkte.

Abbildung 5: Umwandlung von kartesischen in Polarkoordinaten und zurück.

Abbildung 5: Umwandlung von kartesischen in Polarkoordinaten und zurück.

Abbildung 6: Das Skript erkennt die Ziffer Null, der Parameter »threshold« zu Unterscheidung von aktiven und inaktiven Segmenten beträgt 85 Prozent.

Abbildung 6: Das Skript erkennt die Ziffer Null, der Parameter »threshold« zu Unterscheidung von aktiven und inaktiven Segmenten beträgt 85 Prozent.

Erkennungsschwelle

Der Parameter »threshold« gibt an, um wie viel dunkler als der Hintergrund ein Messwert sein muss, damit das Verfahren ein aktives Segment erkennt. Angenommen »threshold« sei 0,85 und der Hintergrund habe die mittlere Helligkeit 180, dann gelten Messwerte größer oder gleich 153 als Hintergrund, also als inaktives LCD-Segment.

Abbildung 6 beschreibt die Ziffer Null bei einem Threshold-Wert von 0,85. Aktive Segmente schwanken zwischen 40,5 Prozent und 72,5 Prozent des Hintergrund-Mittelwerts von 131. Das inaktive Segment 7 weist hingegen den Helligkeitswert 123 auf, mit 93,9 Prozent knapp über dem festgesetzten Schwellenwert von 85 Prozent.

Um auch bei leicht verschobenen Koordinaten zuverlässig festzustellen, welche Segmente der Anzeige schwarz gefärbt sind, misst die im Inline-C-Bereich definierte Funktion »pixel_dark()« nicht nur das einzelne Pixel, sondern berücksichtigt auch umliegende. Am Ende gibt sie aber nur den dunkelsten Messwert weiter. Damit sie nicht aus Versehen Teile des Nachbarsegments misst, entnimmt die Funktion ihre Messwerte immer orthogonal zum Segment. Das heißt, bei waagerechten Segmenten betrachtet sie die oberen und unteren Nachbarpixel, bei senkrechten Segmenten die links und rechts liegenden.

Der Hash »%segdir« gibt hierzu zu jeder Segmentnummer die Lage der zu berücksichtigenden Nachbarn an. Die Funktion »brightness()« misst jeweils die Helligkeit eines Pixelwerts und zählt dazu die Rot-, Grün- und Blauanteile eines Messpunkts zusammen. »xybrightness()« berechnet die Helligkeit an einer Koordinate »[x,y]«.

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. Der Aufrufer erhält dann eine Referenz auf einen Hash zurück, der als Schlüssel die Segment-Ordnungsnummern 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 8 auch gleich in roter Farbe mit ins Bild hinein. Dies erfolgt natürlich nach der Messung, denn sonst würden ja alle Pixel plötzlich rot erscheinen. Mit diesen Zusatzinformationen lässt sich eine Feinkalibrierung vornehmen, falls die Justage noch nicht ganz stimmt.

Abbildung 7: Die beiden Referenzpunkte (»x1_ref«, »y1_ref«) und (»x2_ref«, »y2_ref«) an den oberen Eckpunkten der blauen Fläche legen das Koordinatensystem fest, aus dem später die Pixelwerte einzelner Ziffern stammen.

Abbildung 7: Die beiden Referenzpunkte (»x1_ref«, »y1_ref«) und (»x2_ref«, »y2_ref«) an den oberen Eckpunkten der blauen Fläche legen das Koordinatensystem fest, aus dem später die Pixelwerte einzelner Ziffern stammen.

Abbildung 8: Die beiden grünen Messpunkte am Rand der blauen Fläche helfen dabei, die Lage des Keyfob zu ermitteln, die roten Messpunkte in den Ziffern des Displays greifen die dargestellten Ziffern ab.

Abbildung 8: Die beiden grünen Messpunkte am Rand der blauen Fläche helfen dabei, die Lage des Keyfob zu ermitteln, die roten Messpunkte in den Ziffern des Displays greifen die dargestellten Ziffern ab.

Installation

Die Installation der Software für diesen Versuch ist denkbar einfach. Es genügt bereits, die erforderlichen CPAN-Module »Video::Capture::V4l::Imager« und »YAML« herunterzuladen. Erfolgt dies innerhalb einer CPAN-Shell, dann holt diese automatisch alle weitere Module nach, die noch gebraucht werden, aber möglicherweise auf dem lokalen System noch nicht vorhanden sind. Das Modul »LCDOCR.pm« ist im Anschluss irgendwo dort zu platzieren, wo das Skript »reco« es finden kann. Ein geeigneter Platz wäre beispielsweise das Verzeichnis »/usr/lib/perl5/site_perl« der Perl-Installation.

Dann startet »fobcam« (Listing 1) die erste Aufnahme. Gimp ermittelt anschließend die Referenzpunkte und erweitert die Datei »/etc/fobs.yml« mit den Daten des verwendeten Displays. Daraufhin startet das Skript »reco« und übernimmt sowohl den Namen der gesicherten Bilddatei als auch die Referenzkoordinaten. Nach anfänglichem Gefummel erkennt das OCR-System die angezeigten Zahlenkolonnen bald zuverlässig.

Das Lauern auf ungewöhnliche Ziffernkombinationen auf dem Display kann damit beginnen. Wer das Skript zu diesem Zweck auch über Nacht laufen lassen will, sollte aber auf jeden Fall daran denken, die Schreibtischlampe angeschaltet zu lassen. (jcb)

Infos

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

[2] Foto der 000000-Anzeige: [http://www.flickr.com/photos/ferg2k/381185553/]

[3] Fobcam: [http://fob.webhop.net/]

[4] M. Schilli, “Angeln in der Bilderflut”: [https://www.linux-magazin.de/heft_abo/ausgaben/2006/06/angeln_in_der_bilderflut]

Der Autor


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.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben