Eine Webcam bietet die billigste Möglichkeit, die eigene Burg oder die Schwiegermutter zu überwachen. Die Skripte dieses Perl-Snapshots kalibrieren automatisch die Belichtung, wofür sie als Turbo C-Code einspannen, und fischen dann die interessanten Bilder aus dem Datenstrom der Kamera.
Die meisten Webcams bringen Windows-Software mit, die unter Linux nicht verwendbar ist. Neuere Linux-Distributionen haben allerdings oft Video4Linux an Bord, das problemarm eine eingestöpselte USB-Kamera ansteuert. Die in diesem Snapshot verwendete Kamera Creative NX Ultra kann neben Standbildern auch Videos liefern und kostet etwa 70 Euro. Für die Benutzung als einfache Webcam ist sie eigentlich zu schade, aber sie lag in einer Schublade des Perlmeister-Labors herum und war somit verfügbar. Sie benötigt keine externe Stromversorgung und das Hot-Plugging erkennt sie, sobald der USB-Stecker im Rechner steckt. Ihre Videodaten erscheinen typischerweise unter »/dev/video0«. Das Perl-Modul Linux::Capture::V4l vom CPAN hält sich am Device-Eintrag fest, greift die Framedaten ab und erlaubt es, Aufnahmeparameter wie die Empfindlichkeit laufend zu verändern.
|
Listing 1: |
|---|
01 #!/usr/bin/perl 02 03 use strict; 04 use warnings; 05 use Camcap; 06 07 my $cam = Camcap->new(width => 640, 08 height => 480); 09 $cam->cam_bright(42_000); 10 my $img = $cam->capture(); 11 $img->write(file => 'buero.jpg') 12 or die "Can't write: $!"; |

Abbildung 1: Diese Kamera aus der Schublade des Perl-Meisters, eine NX Ultra von Creative, musste für die Versuche herhalten.
Abstrakter Bilderdieb
Listing 1 zeigt eine einfache Anwendung, die erst die Empfindlichkeit der Kamera auf 40000 einstellt, ein Bild aus dem Videostrom abzwackt und es anschließend als Jpeg-Foto auf der Festplatte ablegt (Abbildung 2). Das von »single« verwendete Modul »Camcap« (Listing 2) abstrahiert den Zugriff auf den Videostrom. Der Konstruktor ab Zeile 12 definiert einige Default-Parameter wie die Bildbreite und -höhe und die minimale und maximale Helligkeitseinstellung (»br_min«, »br_max«). Anschließend hängt sich der Code über das CPAN-Modul Video::Capture::V4l an das Videodevice »/dev/video0« an. Lauscht schon ein anderer Interessent daran, schlägt die Verbindung fehl.
Die ab Zeile 34 definierte Methode »cam_bright()« stellt die Empfindlichkeit der Kamera ein. Sie nimmt einen Wert zwischen 0 und 65535 entgegen, holt mit der Methode »picture()« die Datenstruktur Picture der Kamera, setzt in ihr mit »brightness()« den übergebenen Empfindlichkeitswert und übergibt sie anschließend mit der Methode »set()« an die Video4Linux-Schicht.
Die Methode »capture()« ab Zeile 94 nimmt optional eine Empfindlichkeitsvorgabe entgegen und macht sich dann daran, den nächsten Frame aus dem Videostrom abzugreifen. Der erste Frame erhält die Nummer 0. Ein anschließender Aufruf der Methode »sync()« mit der Framenummer stellt sicher, dass die Bilddaten auch gut im Skalar » ankommen.
|
Listing 2: |
|---|
001 ###########################################
002 package Camcap;
003 ###########################################
004 use strict;
005 use warnings;
006 use Video::Capture::V4l;
007 use Imager;
008 use Imager::Misc;
009 use Log::Log4perl qw(:easy);
010
011 ###########################################
012 sub new {
013 ###########################################
014 my($class, @options) = @_;
015
016 my $self = {
017 width => 320,
018 height => 240,
019 avg_opt => 128,
020 avg_acc => 20,
021 br_min => 0,
022 br_max => 65535,
023 @options,
024 };
025
026 $self->{video} =
027 Video::Capture::V4l->new() or
028 LOGDIE "Open video failed: $!";
029
030 bless $self, $class;
031 }
032
033 ###########################################
034 sub cam_bright {
035 ###########################################
036 my($self, $brightness) = @_;
037
038 my $pic = $self->{video}->picture();
039 $pic->brightness($brightness);
040 $pic->set();
041 }
042
043 ###########################################
044 sub img_avg {
045 ###########################################
046 my($img) = @_;
047
048 my $br = Imager::Misc::brightness($img);
049 DEBUG "Brightness: $br";
050 return $br;
051 }
052
053 ###########################################
054 sub calibrate {
055 ###########################################
056 my($self) = @_;
057
058 DEBUG "Calibrating";
059
060 return if
061 img_avg($self->capture($self->{br_min}))
062 > $self->{avg_opt};
063
064 return if
065 img_avg($self->capture($self->{br_max}))
066 < $self->{avg_opt};
067
068 # Binary search
069 my($low, $high) = ($self->{br_min},
070 $self->{br_max});
071
072 for(my $max = 5;
073 $low <= $high && $max;
074 $max--) {
075 my $try = int( ($low + $high) / 2);
076
077 my $i = $self->capture($try);
078 my $br = img_avg($i);
079
080 DEBUG "br=$try got avg=$br";
081 return if abs($br-$self->{avg_opt}) <=
082 $self->{avg_acc};
083
084 if($br < $self->{avg_opt}) {
085 $low = $try + 1;
086 } else {
087 $high = $try - 1;
088 }
089 }
090 # Nothing found, use last setting
091 }
092
093 ###########################################
094 sub capture {
095 ###########################################
096 my($self, $br) = @_;
097
098 $self->cam_bright($br) if defined $br;
099
100 my $frame;
101 for my $frameno (0, 1) {
102 $frame = $self->{video}->capture(
103 $frameno, $self->{width},
104 $self->{height});
105
106 $self->{video}->sync($frameno) or
107 LOGDIE "Unable to sync";
108 }
109
110 my $i = Imager->new();
111 $frame = reverse $frame;
112 $i->read(
113 type => "pnm",
114 data => "P6n$self->{width} " .
115 "$self->{height}n255n" .
116 $frame
117 );
118 $i->flip(dir => "hv");
119 return $i;
120 }
121
122 1;
|
Vom Kopf auf die Füße
Einige Tests zeigen, dass manchmal der erste vorbeisausende Frame nicht akzeptabel ist, da eine kurz zuvor eingestellte Kameraempfindlichkeit noch nicht gegriffen hat. Deswegen holt »capture« grundsätzlich zwei Frames ab und wirft den ersten weg. Sobald die Methode »sync()« zurückgekehrt ist, liegen in der Variablen » die rohen Bilddaten im BGR-Format. Drei aufeinander folgende Bytes beschreiben jedes Pixel mit den Blau-, Grün- und Rotwerten (jeweils 0 bis 255). Um daraus ein Format zu generieren, das typische Bildverarbeitungsprogramme verstehen, kehrt das Kommando »reverse« den Bytestring zunächst um. Dies hat zur Folge, dass die Daten nun im etwas gängigeren RGB-Format vorliegen.
Wird das Ganze, wie in Zeile 114 geschehen, noch von einem P6-Header für das PPM-Format eingeleitet und die Breite und Höhe des Bildes angegeben, kann die Methode »read()« des CPAN-Moduls Imager daraus ein Bild zaubern. Allerdings hat sich durch die Umkehrung mit »reverse« auch die Reihenfolge der Pixel umgedreht, sodass das Bild nun auf dem Kopf steht. Das macht ein Aufruf der Methode »flip()« sofort rückgängig, die mit dem Parameter »dir => “hv”« eine vertikale 180-Grad-Spiegelung vornimmt. Die Methode »capture()« liefert ein Objekt vom Typ »Imager« zurück, das die aufrufende Funktion dann weiterverarbeiten kann.
Belichtungsmesser
Um die Kamera auf das Umgebungslicht einzustellen, untersucht ein Skript ein Testbild und korrigiert die Empfindlichkeit der Kamera entsprechend der Bildhelligkeit. Aber noch bleibt im Dunkeln, ob das Bild richtig belichtet ist? Die Abbildungen 3 und 4 zeigen die Verteilungen aller RGB-Werte der Pixel zweier unterschiedlicher Bilder. Das Histogramm in Abbildung 3 stammt von einem stark unterbelichteten Bild, das zwar einige RGB-Werte im unteren Bereich aufweist, dann aber abreißt und keine helleren Töne enthält. Abbildung 4 zeigt das Histogramm eines normal belichteten Bildes. Fast alle Werte zwischen 0 und 255 sind gleichmäßig vertreten.

Abbildung 3: Im Histogramm eines schwach ausgeleuchteten, unterbelichteten Fotos fehlen die höheren Helligkeitswerte.

Abbildung 4: Das Histogramm eines normal ausgeleuchteten Bildes zeigt eine gleichmäßige Verteilung der Helligkeitswerte.
Um nun die Helligkeit eines aufgenommenen Testbilds zu bestimmen, kommt ein primitiver Algorithmus zum Einsatz: Er addiert alle RGB-Werte und teilt die Summe durch drei und die Anzahl der Pixel. Wenn dann etwa die Hälfte von 256 herauskommt, ist das Bild einigermaßen ausgewogen.
Allerdings ist Perl nicht gerade für solche Sprints konzipiert. Ein Bild mit 320 mal 240 Pixeln, von denen jedes einen Wert im Rot-, Blau- und Grün-Kanal hat, umfasst 230400 Datenpunkte. Sie alle abklappern dauert seine Zeit, und wenn nicht jeder Zugriff blitzschnell erfolgt, gestaltet sich die Berechnung äußerst schleppend.
Turbo in C
Das Imager-Modul lässt sich aber glücklicherweise leicht durch C-Code erweitern. Der rast Maschinen-näher durch die Datenstrukturen und gibt die ermittelten Ergebnisse elegant ans Perl-Skript zurück. Hierzu legt
h2xs -Axn Imager::Misc
in der entpackten Distribution des Imager-Moduls vom CPAN (einige Header werden gebraucht) einfach ein neues Unterverzeichnis »Imager-Misc« an.
In der darunter entstandenen Datei »Makefile.PL« ist die Zeile »INC => -I.« in »INC => -I..« umzuändern, damit die nachfolgenden Kommandos »perl Makefile.PL« und »make« auch die Include-Dateien der Imager-Distribution finden. Außerdem hat »h2xs« eine Datei »Misc.xs« für den C-Code erzeugt, dessen neue Funktionen Listing 3 zeigt.
|
Listing 3: |
|---|
01 #ifdef __cplusplus
02 extern "C" {
03 #endif
04 #include "EXTERN.h"
05 #include "perl.h"
06 #include "XSUB.h"
07 #include "ppport.h"
08 #ifdef __cplusplus
09 }
10 #endif
11
12 #include "imext.h"
13 #include "imperl.h"
14
15 DEFINE_IMAGER_CALLBACKS;
16
17 /* ===================================== */
18 int
19 brightness(i_img *im) {
20 int x, y;
21 i_color val;
22 double sum;
23 int br;
24 int avg;
25
26 for(x = 0; x < im->xsize; x++) {
27 for(y = 0; y < im->ysize; y++) {
28 i_gpix(im, x, y, &val);
29 br = (val.channel[0] + val.channel[1]
30 + val.channel[2]) / 3;
31 sum += br;
32 }
33 }
34
35 avg = sum / ((int) (im->xsize) *
36 (int) (im->ysize));
37 return avg;
38 }
39
40 /* ===================================== */
41 int
42 changed(i_img *im1, i_img *im2, int diff) {
43 int x, y, z, chan;
44 i_color val1, val2;
45 int diffcount = 0;
46
47 for(x = 0; x < im1->xsize; x++) {
48 for(y = 0; y < im1->ysize; y++) {
49
50 i_gpix(im1, x, y, &val1);
51 i_gpix(im2, x, y, &val2);
52
53 for(z = 0; z < 3; z++) {
54 if(abs(val1.channel[z] -
55 val2.channel[z]) > diff)
56 diffcount++;
57 }
58 }
59 }
60
61 return diffcount;
62 }
63
64 /* ===================================== */
65 MODULE=Imager::Misc PACKAGE=Imager::Misc
66
67 PROTOTYPES: ENABLE
68
69 int
70 brightness(im)
71 Imager::ImgRaw im
72
73 int
74 changed(im1, im2, diff)
75 Imager::ImgRaw im1
76 Imager::ImgRaw im2
77 int diff
78
79 BOOT:
80 PERL_INITIALIZE_IMAGER_CALLBACKS;
|
Es zeigt ab Zeile 18 den C-Code der Funktion »brightness()« und ab Zeile 65 das Perl-XS-Voodoo, das ihn in ein Perl-Skript integriert (XS oder Xsubs: Perl-Interface, das C-Code einbindet, Abkürzung für Extended Subroutine). Die Breite des hereingereichten Bildes in Pixeln liefert »im->xsize«, die Höhe »im->ysize«. Zwei For-Schleifen laufen über alle Pixel und das Makro »i_gpix« ruft eine Funktion auf, die die Farbwerte an der Bildposition (x,y) in der Struktur »val« ablegt. Anschließend kann zum Beispiel »val.channel[0]« den Rotwert des Pixels hervorholen.
Die bekannte Folge »perl Makefile.PL; make; make install« übersetzt und installiert das Modul Imager::Misc. Bindet ein Perl-Skript das Modul mit »use Imager::Misc« ein, steht ihm danach die Funktion »Imager::Misc::brightness« zur Verfügung, die ihrerseits ein Imager-Bild entgegennimmt, untersucht und als Maß für dessen Helligkeit einen Integerwert zurückliefert.
Sonne lacht, Blende acht
Der einfache Algorithmus berechnet für das zu dunkle Bild in Abbildung 3 den Wert 7, während sich für das normal belichtete Foto gemäß dem Histogramm in Abbildung 4 der Wert 125 für »brightness()« ergibt.
Um die Kamera auf die herrschenden Lichtverhältnisse einzustellen, macht die Methode »calibrate()« aus Listing 2 eine Testaufnahme, ermittelt deren »brightness«-Wert und vergleicht ihn mit dem Idealwert 128. Liegt er darunter, stellt »calibrate()« mit »cam_bright()« eine höhere Kameraempfindlichkeit ein. Liegt er über dem Idealwert, ist das Bild also überbelichtet, startet »cam_bright()« die nächste Testaufnahme mit einem reduzierten Wert.
Am Anfang macht »calibrate()« zwei Aufnahmen mit maximaler beziehungsweise minimaler Kameraempfindlichkeit. Stellt sich heraus, dass selbst bei maximaler Empfindlichkeit das Bild zu dunkel (oder bei minimaler Empfindlichkeit zu hell) ist, hilft alles nichts, die Funktion behält dann den gerade eingestellten Wert bei.
Lässt sich hingegen etwas ausrichten, läuft ab Zeile 68 in Listing 2 eine Binärsuche für die optimale Empfindlichkeit zwischen 0 und 65535 an. Maximal fünf Durchgänge liefern jeweils in der Mitte des Intervalls einen Messwert. Ist das Bild zu dunkel, fährt der Algorithmus in der oberen Hälfte des Intervalls mit der Suche fort. Ist es zu hell, kommt hingegen die untere Hälfte dran. Am Ende der Suche sollte die Kamera ein Bild produzieren, das einen Helligkeitswert von 128 bei +/-20 (Unschärfeparameter »avg_acc«) aufzuweisen hat.
Differenzen
Läuft die Webcam ununterbrochen, produziert sie ein Unmenge Bilder. Für Überwachungsaufgaben sollte der Rechner aber nur jene speichern, die eine signifikante Änderung gegenüber der letzten Aufnahme zeigen. Hierzu definiert »Misc.xs« aus Listing 3 eine Funktion »changed()«, sie liefert die Anzahl der RGB-Werte zurück, die in zwei Bildern unterschiedlich sind. Außer zwei Pointern auf »i_img«-Strukturen (»Imager::ImgRaw«-Objekte auf der Perl-Ebene) nimmt »Misc.xs« mit dem Parameter »diff« eine Mindestdifferenz für Kanalwerte entgegen.
Ist der Rotwert eines Pixels des ersten Bildes zum Beispiel 15 und der Rotwert des gleichen Pixels im zweiten Bild 30, erhöht sich der Zähler »diffcount« um eins, falls der »diff«-Parameter mit einem Wert von 15 oder mehr belegt war. Das soll jene statistischen Effekte kompensieren, die aufgrund natürlicher Schwankungen der Lichtverhältnisse und durch das Rauschen von CCD-Chips unweigerlich auftreten.
Das Skript »tracker« aus Listing 4 läuft in einer Endlosschleife, schießt Aufnahme um Aufnahme, speichert neue Bilder aber nur, falls »Imager::Misc::changed()« eine signifikante Änderung signalisiert. Sonst überschreibt es einfach das letzte Bild im Cache, um graduellen Änderungen auf der Spur zu bleiben. Die gesicherten Bilder landen in einem Cache der Art »Cache::FileCache« und werden automatisch nach 48 Stunden gelöscht. Das Skript speichert die Bilder unter einem Datumsschlüssel (etwa »2006-03-28-11:21:22«).
|
Listing 4: |
|---|
01 #!/usr/bin/perl
02 ###########################################
03 use strict;
04 use warnings;
05 use Camcap;
06 use Imager::Misc;
07 use Log::Log4perl qw(:easy);
08 use Cache::FileCache;
09 use Time::Piece;
10 use List::Util qw(maxstr);
11
12 my $c = Cache::FileCache->new({
13 namespace => "tracker",
14 auto_purge_interval => 3600
15 default_expires_in => 3600 * 24 });
15
16 Log::Log4perl->easy_init($DEBUG);
17
18 my $cam = Camcap->new();
19
20 while(1) {
21 my $lkey = maxstr grep /d/,
22 $c->get_keys();
23
24 if(! $c->get("calibrated")) {
25 $cam->calibrate();
26 $c->set("calibrated", 1, 300);
27 my $img = $cam->capture();
28 saveimg($img, $c, $lkey);
29 next;
30 }
31
32 my $img = $cam->capture();
33
34 if($lkey) {
35 my $limg = Imager->new();
36 $limg->read(type => "jpeg",
37 data => $c->get($lkey));
38 my $dpix = Imager::Misc::changed($limg,
39 $img, 80);
40 DEBUG "$dpix pixels changed";
41 if($dpix > 2000) {
42 saveimg($img, $c);
43 next;
44 } else {
45 # minor change,
46 # refresh reference
47 saveimg($img, $c, $lkey);
48 }
49 } else {
50 # save first img
51 saveimg($img, $c);
52 }
53
54 sleep(1);
55 }
56
57 ###########################################
58 sub saveimg {
59 ###########################################
60 my($img, $cache, $date) = @_;
61
62 if(! $date) {
63 $date = localtime()->
64 strftime("%Y/%m/%d-%H:%M:%S");
65 }
66
67 DEBUG "Saving image $date";
68 $img->write(type => "jpeg",
69 data => my $val) or die;
70 $cache->set($date, $val);
71 }
|
Um das letzte Bild aus dem Cache zu holen, ruft »tracker« einfach die Cache-Funktion »get_keys()« auf, die alle bekannten Schlüssel zurückliefert. Die Funktion »maxstr()« aus dem Modul List::Util sucht sich daraus das jüngste Datum aus. Das zugehörige Bild liefert dann die Cache-Funktion »get()« mit dem ermittelten Schlüssel als Argument.
Die Kamera soll sich alle fünf Minuten neu kalibrieren. Dafür sorgt ein Eintrag im Cache unter dem Schlüssel »calibrated«, den die Software automatisch alle 300 Sekunden löscht. Findet »tracker« den Eintrag nicht, leitet es eine neue Kalibrierung ein und setzt ihn neu.
Bildergalerie
Listing 5 holt die Aufnahmen aus dem Cache, macht daraus Jpeg-Bilder und legt sie in einem temporären Verzeichnis ab. Danach ruft es das Programm »montage« aus dem Imagemagick-Fundus auf, das den Linux-Distributionen üblicherweise beiliegt und Zusammenstellungen erzeugt, die ähnlich wie Contact-Prints aussehen. Der anschließend aufgerufene Viewer »xv« holt die Thumbnails auf den Bildschirm.
|
Listing 5: |
|---|
01 #!/usr/bin/perl -w
02 use strict;
03 use Imager;
04 use Cache::FileCache;
05 use Time::Piece;
06 use List::Util qw(maxstr);
07 use Sysadm::Install qw(rmf mkd cd);
08 use File::Temp qw(tempdir);
09
10 my $dir = tempdir(CLEANUP => 1);
11
12 my $c = Cache::SharedMemoryCache->new({
13 namespace => "tracker",
14 });
15
16 for my $date (sort $c->get_keys()) {
17
18 next unless $date =~ /d/;
19 my $val = $c->get($date);
20 my $img = Imager->new();
21 $img->read(type => "jpeg",
22 data => $val);
23 $date =~ s#/#-#g;
24 $img->write(file => "$dir/$date.jpg") or
25 die "Can't write $!";
26 }
27
28 cd $dir;
29 my $str = "";
30 for (<*.jpg>) {
31 (my $date = $_) =~ s/.jpg//g;
32 $str .= "-label $date $_ ";
33 }
34 `montage -tile 6x6 $str sequence.jpg`;
35 `xv $_` for <sequence*>;
|
Abbildung 5 zeigt den kontinuierlichen Strom von Aufnahmen, den »tracker« für speichernswert hielt. Sie zeigen die surreale Welt der Perlmeister-Studios mit der Besetzungscouch im Vordergrund. Um 0:09 Uhr geht das Licht im Arbeitszimmer aus und ab 1:17 Uhr tut sich dann auch in der restlichen sichtbaren Wohnung nichts mehr. Bei nur graduellen Veränderungen zwischen 1:17 und 6:45 überschrieb die Software die Aufnahme mit dem Zeitstempel 01:17:03 immer wieder, bis um 06:45:37 eine im Dunkeln tappende Person eine signifikante Anzahl von Pixeln änderte und »tracker« die Bewegung registrierte. Um 07:07:56 zieht jemand den Vorhang auf und Tageslicht fällt herein. (jcb)
|
Der Autor |
|---|
|
|
|
Infos |
|---|
|
[1] Listings zu diesem Artikel:[ftp://www.linux-magazin.de/pub/listings/magazin/2006/06/Perl] [2] Definition der Bildhelligkeit: [http://en.wikipedia.org/wiki/Brightness] [3] Marc Lehmann, “Capturing Video in Real Time”: The Perl Journal, 2005/02 [4] Website des Perl-Moduls Imager:[http://imager.perl.org] |








