Aus Linux-Magazin 12/2016

Überwachungsvideos zeitsparend bearbeiten

© alicephoto, 123RF

Statt auf langweilige Überwachungsvideos zu starren, auf denen zu 90 Prozent nichts passiert, setzt Perlmeister Michael Schilli lieber die Bilderkennungssoftware Open CV ein, die automatisch den Handlungsablauf an den interessantesten Stellen extrahiert.

Eine extrem ungleiche Einkommensverteilung, gepaart mit einer gewissen Wurstigkeit der Gesetzeshüter in meiner Wahlheimat San Francisco, hat mittlerweile dazu geführt, dass kein Tag vergeht, ohne dass Hunderte von Autos, Garagen und Häuser aufgebrochen werden und dort gelagerte Sachen Füße bekommen. Statt mich darüber aufzuregen, lagere ich inzwischen nichts Wertvolles mehr an einfach zugänglichen Stellen und installiere klammheimlich Security-Kameras, um mich dann später an den Video-Aufnahmen von Dieben bei der Arbeit zu erfreuen.

Sogar ohne Stromkabel

Freilich ist die Installation einer Security-Kamera kein leichtes Unterfangen, denn Kabel müssen verlegt und Verbindungen zum Monitor hergestellt werden. Auch wenn die Kamera an sich drahtlos mit der Zentrale kommuniziert, benötigt sie dennoch eine Stromversorgung, und die ist an den heißesten Brennpunkten wie in der Tiefgarage oder im Treppenhaus nicht leicht zu bekommen.

Aber seit einigen Jahren bietet die Firma Arlo batteriebetriebene, Kinderfaust-große Kameras [2] an, die der Hobbyspion einfach mit einem Magneten aufhängt (Abbildung 1). Aufgenommene Videos senden die kleinen Wunderwerke drahtlos zu einem bis zu etwa 30 Meter entfernten Hub, der sie wiederum übers Internet auf einen Server spielt, von dem allerlei Smartphone-Apps sowie eine Webseite die Daten auf Wunsch auf den Bildschirm des Users übertragen.

Abbildung 1: Ohne Stromversorgung findet die kleine Arlo-Kamera auch im kleinsten Kabuff Platz.

Abbildung 1: Ohne Stromversorgung findet die kleine Arlo-Kamera auch im kleinsten Kabuff Platz.

Schonbetrieb

Um die vier Lithium-Batterien in der Kamera so weit zu schonen, dass sie etwa einen Monat halten, darf die Kamera etwa ein halbes Dutzend Mal am Tag anspringen, wenn sie Bewegungen in ihrer Nähe feststellt, um dann jeweils ein einminütiges Video zu übertragen. Diese Filmchen lädt der User von Arlos Webseite (Abbildung 2) herunter und kann zum Beispiel dabei zusehen, wie Diebe gerade sein neues Rennrad aus der Garage schleppen. Meist ist nur am Anfang eines Überwachungsvideos eine Bewegung zu sehen, der Rest der einminütigen Aufnahmen zeigt üblicherweise gähnende Leere (Abbildung 3).

Abbildung 2: Von der Kamera aufgenommene Videos stehen zum Download.

Abbildung 2: Von der Kamera aufgenommene Videos stehen zum Download.

Abbildung 3: Die meisten Frames zeigen nur die geschlossene Tür.

Abbildung 3: Die meisten Frames zeigen nur die geschlossene Tür.

Vorwärts zur Action

“Cut to the Chase” sagt der Amerikaner, wenn jemand nicht gleich zur Sache kommt. Das Bonmot bezieht sich wohl auf Aktionfilme, bei denen der Zuschauer keinen langen Spannungsbogen-Aufbau wünscht, sondern am liebsten gleich zum Autoverfolgungsrennen am Höhepunkt der Hollywood-Produktion vorspulen möchte.

In diesem Sinne wäre es schön, wenn eine Software die Videos nach Frames durchforstet, in denen sich tatsächlich ein Subjekt durchs Bild bewegt, sodass der Zuseher weiß, ob sich das Ansehen des Videos überhaupt lohnt und zu welchen Stellen er sofort vorspulen sollte.

Zappelfilmchen

Am einfachsten wäre ein Schnelldurchlauf des Videos, zum Beispiel mit Hilfe des Parameters »fps« (Frames per Second) im »mplayer« :

mplayer -framedrop -fps 150 video.mp4

Der Parameter »-framedrop« wirft in diesem Modus Frames einfach weg, falls die CPU mit dem Dekodieren nicht mehr nachkommt. Daraus entsteht in diesem Fall ein etwa fünfmal so schnell wie normal laufendes Video, das etwas an die Serie “Als die Bilder laufen lernten” oder an alte handgekurbelte Werke von Charlie Chaplin erinnert.

Aber das Verfahren erfordert einen menschlichen Gutachter, während mir ein automatischer Vorgang vorschwebt, der zu einem automatisch eingeholten Video als Metadaten wie auf dem Kontaktabzug eines Fotografen die wichtigsten Sekunden mitsamt illustrierender Thumbnails auflistet.

Motion Detection

Zum Thema Mustererkennung in der Bildverarbeitung hat sich in den letzten Jahren viel getan und das Paket Open CV [3] bietet sogar einige hochwissenschaftliche Routinen als Open-Source-Software an. Um festzustellen, ob sich gerade jemand oder etwas durchs Video bewegt, muss ein Programm die Einzelbilder im Videostrom auslesen und feststellen, welche Bildpunkte sich von den Koordinaten x und y zu den Koordinaten x+ ?x und y+ ?y verschoben haben. Findet sich eine größere zusammenhängende Fläche, für die das zutrifft, fand zwischen beiden Frames eine Bewegung statt.

Eines der in Open CV gebündelten Verfahren heißt Lucas-Kanade [4], es versucht Optical Flow in einem Video zu finden, also Bereiche um zentrale Punkte, die sich gemeinschaftlich von einem Frame zum nächsten verschieben. Dazu benötigt es zunächst eine Reihe interessanter Bereiche, deren Beobachtung Erfolg verspricht und die ein weiterer Algorithmus aus einem Bild extrahieren kann. Letzterer konzentriert sich auf Bildpunkte in Flächen mit erkennbarer Struktur oder an Kanten dargestellter Objekte.

Zur Lucas-Kanade-Analyse stellt das Paket Open CV (auf Ubuntu heißt es »libopencv-dev« ) die Funktion »calcOpticalFlowPyrLK()« mit sage und schreibe elf Parametern bereit.

Klimmzüge mit Cmake

Das C++-Programm in Listing 1 schnappt sich ein Video und erkennt Bewegungen von Objekten zwischen mehreren Frames. Es in ein ausführbares Programm umzuwandeln, erfordert einige Klimmzüge mit Include-Dateien und Link-Libraries, was am einfachsten mit »cmake« und dem Meta-Makefile in Listing 2 geht. Ein »cmake .« (der Punkt steht für das aktuelle Verzeichnis, in dem die Datei »CMakeLists.txt« residiert) und das anschließende »make« erzeugen nach einigen Zwischenschritten das Binary »max-movement-lk« , das eine Videodatei erwartet und die Sekundenwerte ausspuckt, an denen Bewegungen stattfinden.

Listing 1

max-movement-lk.cpp

01 #include "opencv2/opencv.hpp"
02
03 using namespace std;
04 using namespace cv;
05
06 const int MAX_FEATURES = 500;
07 const int MAX_MOVEMENT = 100;
08
09 int move_test(Mat& oframe, Mat& frame) {
10     // Select features for optical flow
11   vector<Point2f> ofeatures;
12   goodFeaturesToTrack(oframe,
13     ofeatures, MAX_FEATURES, 0.1, 0.2 );
14
15     // Parameters for LK
16   vector<Point2f> new_features;
17   vector<uchar> status;
18   vector<float> err;
19   TermCriteria criteria(TermCriteria::COUNT
20       | TermCriteria::EPS, 20, 0.03);
21   Size window(10,10);
22   int max_level   = 3;
23   int flags       = 0;
24   double min_eigT = 0.004;
25
26     // Lucas-Kanade method
27   calcOpticalFlowPyrLK(oframe, frame,
28     ofeatures, new_features, status, err,
29     window, max_level, criteria, flags,
30     min_eigT );
31
32   double max_move = 0;
33   double movement = 0;
34   for(int i=0; i<ofeatures.size(); i++) {
35     Point pointA
36       (ofeatures[i].x, ofeatures[i].y);
37     Point pointB
38       (new_features[i].x, new_features[i].y);
39
40     movement = norm(pointA-pointB);
41     if(movement > max_move)
42         max_move = movement;
43   }
44   return max_move > MAX_MOVEMENT;
45 }
46
47 int main(int argc, char *argv[]) {
48   int i = 0;
49   Mat frame;
50   Mat oframe;
51
52   if (argc != 2) {
53     cout << "USAGE: <cmd> <file_in>\n";
54     return -1;
55   }
56
57   VideoCapture vid(argv[1]);
58   if (!vid.isOpened()) {
59     cout << "Video corrupt\n";
60     return -1;
61   }
62
63   int fps = (int)vid.get(CV_CAP_PROP_FPS);
64
65   i++;
66   if(!vid.read(oframe)) return 1;
67
68   cvtColor(oframe, oframe, COLOR_BGR2GRAY);
69
70   while (1) {
71     if (!vid.read(frame))
72       break;
73     i++;
74
75     cvtColor(frame,frame,COLOR_BGR2GRAY);
76     if(move_test(oframe, frame))
77       cout << i/fps << "\n";
78     oframe = frame;
79   }
80
81   return 0;
82 }

Hierzu liest die Hauptfunktion »main()« den Dateinamen des Videos von der Kommandozeile und öffnet in Zeile 57 eine Videocapture aus dem Open-CV-Paket. Die Framerate liest Zeile 63 aus der Videodatei und speichert sie in der Variablen »fps« . Da der LK-Algorithmus am besten mit Grauton-Bildern funktioniert, entziehen die Zeilen 68 und 75 dem analysierten Frame die Farbe.

Listing 2

CMakeLists.txt

1 cmake_minimum_required(VERSION 2.8)
2 project( max-movement-lk )
3 find_package( OpenCV REQUIRED )
4 add_executable( max-movement-lk max-movement-lk.cpp )
5 target_link_libraries( max-movement-lk ${OpenCV_LIBS} )

Eine While-Schleife iteriert über alle Frames, und die Funktion »move_test()« in Zeile 76 prüft, ob zwischen dem zuletzt gelesenen Frame »oframe« und dem aktuellen »frame« eine Bewegung stattfand. Wenn ja, teilt Zeile 77 den Zählerwert durch den FPS-Wert des Videos und erhält so den Zeitwert in Sekunden im Video, an dem die Bewegung passierte.

Mitmuss-Algorithmus

Der aus [3] entliehene Algorithmus in der Funktion »move_test()« ab Zeile 9 ruft die Funktion »goodFeaturesToTrack(()« aus dem Open-CV-Paket auf, um mit ihr in dem alten Frame »oframe« interessante Punkte aufzustöbern, deren Maximalzahl die Konstante »MAX_FEATURES« auf 500 setzt. Zeile 27 ruft dann »calcOpticalFlowPyrLK()« auf, und zurück kommt in »new_features« eine Reihe von Bereichen, die sich gegenüber »ofeatures« im letzten Frame offenbar verschoben haben.

Die For-Schleife ab Zeile 34 iteriert über sie und sucht den Bereich, der den weitesten Weg zurückgelegt hat. Überschreitet einer davon den Wert 100, gibt Zeile 44 in »move_test()« den Wert 1 zurück und deutet damit an, dass hier offenbar eine Bewegung stattgefunden haben muss.

Aufpeppen und anzeigen

Listing 1 gibt also zu einem Video reihenweise Integerwerte aus, die die Sekundenwerte im Video angeben, an denen sich von einem Frame zum nächsten etwas im Bild bewegt hat. Es ist nun an Listing 3, diese Rohdaten aufpeppen, Thumbnails an den entsprechenden Zeitpunkten zu generieren und das Ganze zu einer Übersicht wie in Abbildung 4 zusammenzufassen.

Listing 3

motion-meta

01 #!/usr/local/bin/perl -w
02 use strict;
03 use File::Temp qw( tempdir );
04 use File::Copy qw( move );
05 use DateTime::Duration;
06 use DateTime::Format::Duration;
07 use Image::Magick;
08
09 my %seen = ();
10
11 my $video = shift @ARGV;
12 if( !defined $video ) {
13   die "usage: $0 video";
14 }
15
16 my $tmpdir = tempdir( CLEANUP => 1 );
17 my $magick = Image::Magick->new;
18
19 while( <> ) {
20   chomp;
21   my $second = $_;
22   next if $seen{ $second }++;
23
24   system "mplayer", "-ss", $second, $video,
25     "-vo", "jpeg:outdir=$tmpdir",
26     "-ao", "null", "-frames", 1;
27
28   my( $frame ) = glob "$tmpdir/0*";
29   my $newname = sprintf
30     "$tmpdir/frame-%s.jpg",
31       secs_format( $second );
32   move $frame, $newname;
33   $magick->Read( $newname );
34 }
35
36 my $montage = $magick->Montage(
37     label  => "%f",
38     shadow => "True",
39     tile   => "5",
40 );
41
42 $montage->Write( "motion-meta.jpg" ) and
43   die "write failed";
44
45 sub secs_format {
46   my( $secs ) = @_;
47
48   my $fmt =
49     DateTime::Format::Duration->new(
50       pattern => "%T" );
51
52   return $fmt->format_duration(
53     DateTime::Duration->new(
54       seconds => $secs )
55   );
56 }
Abbildung 4: Der Motion-Filter zeigt nur die Videosekunden, in denen tatsächlich etwas passiert.

Abbildung 4: Der Motion-Filter zeigt nur die Videosekunden, in denen tatsächlich etwas passiert.

Für die Thumbnails nutzt es das gute alte Allround-Tool »mplayer« , spult mit der Option »-ss« bis zur angegebenen Videosekunde vor und legt in einem temporären Verzeichnis »$tmpdir« den dort liegenden Frame ab. Die Option »-frames 1« legt fest, dass dann Schicht im Schacht ist, keine weiteren Frames mehr ausgelesen werden und »mplayer« sich beendet. Die Funktion »move()« aus dem CPAN-Modul File::Copy benennt die Datei im temporären Verzeichnis in eine im aktuellen um und rechnet den Sekundenwert mit dem CPAN-Modul DateTime::Format::Duration ins Format »SS::MM:ss« um. Aus dem Frame bei Sekunde 64 wird so die Datei »00:01:04.jpg« .

Das Ubuntu-Paket »perlmagick« bringt das CPAN-Modul »Image::Magick« ins System, mit dem es dann ganz einfach ist, aus mehreren Bilddateien so genannte Montage-Konstrukte, also Kontaktabzüge im Format von Abbildung 4 zu erstellen. Der folgende Aufruf

$ max-movement-lk test.mp4 | ./motion-meta test.mp4

hängt die beiden Teile der Pipeline aneinander.

Der erste Teil analysiert die Frames im Video und gibt die Sekundenwerte aus, an denen Bewegungen stattfanden. Der zweite schnappt die Sekundenwerte auf, dedupliziert sie, sucht die zugehörigen Thumbnails im Video heraus und montiert sie zu einem Kontaktabzug zusammen, wobei die Dateinamen so gewählt sind, dass sich der Zeitpunkt im Video in Minuten und Sekunden ablesen lässt.

10 Millionen pro Spezialist

Verfahren zum Erkennen von bewegten Objekten in Videostreams kommen nicht nur bei Überwachungsvideos zum Einsatz. Auch selbstfahrende Autos analysieren aufgenommene Bilddaten mit ähnlichen Verfahren, um gefährdete Fußgänger von feststehenden Straßenschildern zu unterscheiden. Diese Techniken zu erlernen könnte sich im beruflichen Werdegang auszahlen: Laut Ex-Googler Sebastian Thrun überbieten sich die Firmen zurzeit in diesem Bereich gegenseitig und zahlen etwa 10 Millionen Dollar pro Fachkraft [5]. Wer kann es sich leisten und dazu Nein sagen?

Online PLUS

Im Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/Ausgaben/2016/12/plus

Der Autor

Michael Schilli arbeitet als Software Engineer in der San Francisco Bay Area in Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mailto:mschilli@perlmeister.com beantwortet er gerne Fragen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
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
Nach oben