Statt langweilige Überwachungsvideos manuell durchzusehen, auf denen zu 90% nichts passiert, setzt Perlmeister Michael Schilli lieber die Bilderkennungssoftware OpenCV 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 aufbrochen werden und dort gelagerte Sachen Füße bekommen. Statt mich darüber aufzuregen, lagere ich nichts Wertvolles mehr an einfach zugänglichen Stellen, und installiere heimlich Security-Kameras, um mich an Video-Aufnahmen von Dieben bei der Arbeit zu erfreuen.
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 oft an den heißesten Brennpunkten wie in der Tiefgarage oder im Treppenhaus nicht leicht zu bekommen. Vor einigen Jahren kam die Firma Arlo deshalb mit batteriebetriebenen kinderfaustgroßen Kameras heraus ([2]), die der Hobbyspion einfach mittels eines Magneten aufhängt. Aufgenommene Videos senden die kleinen Wunderwerke vollkommen 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.
Um die vier Litium-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 darf der User dann von Arlos Webseite herunterladen und zum Beispiel golf-clappend dabei zusehen, wie Diebe gerade sein neues Rennrad aus der Garage schleppen. Meist ist allerdings nur am Anfang eines Überwachungsvideos eine Bewegung zu sehen, der Rest der einminütigen Aufnahmen zeigt üblicherweise gähnende Leere.
Abbildung 1: Ohne Stromversorgung findet die kleine Arlo-Kamera auch im kleinsten Kabuff Platz. |
"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 Spannungsbogenaufbau 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 durchforstete, in denen sich tatsächlich ein Objekt durchs Bild bewegt, sodass der Zuseher vorab weiß, ob sich das Ansehen des Videos überhaupt lohnt und zu welchen Stellen schleunigst vorspulen sollte.
Abbildung 2: Von der Kamera aufgenommene Videos stehen zum Download bereit. |
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. Heraus kommt in diesem
Fall ein etwa fünfmal so schnell wie normal laufendes Video, das etwas
an die Serie "Als die Bilder laufen lernten" oder alte handgekurbelte Werke von
Charlie-Chaplin erinnert. Aber das Verfahren erfordert einen
menschlichen Gutachter, während mir ein automatischer Vorgang vorschwebte,
der zu einem automatisch eingeholten Video als Metadaten wie auf einem
Kontaktabzug eines Fotografen die wichtigsten Sekunden mitsamt
illustrierender Thumbnails auflistet.
Abbildung 3: Die meisten Frames im Überwachungsvideo zeigen nur die geschlossene Tür. |
Zum Thema Mustererkennung in der Bildverarbeitung hat sich in den letzten Jahrzehnten viel getan und das Paket OpenCV bietet sogar einige hochwissenschaftliche Routinen als Open-Source-Software an. Um festzustellen, ob sich gerade ein Objekt durchs Video bewegt, muss ein Programm die Einzelbilder im Videostrom auslesen und feststellen, welche Bildpunkte sich von den Koordinaten X,Y zu den Koordinaten X+deltaX, Y+deltaY verschoben haben. Findet sich eine größere zusammenhängende Fläche, für das offensichtlich der Fall ist, fand zwischen den beiden Frames eine Bewegung statt.
Eines der in OpenCV gebündelten Verfahren heißt "Lucas-Kanade" ([4]) und 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 zu extrahieren versucht. Letzterer konzentriert sich auf Bildpunkte in Flächen mit erkennbarer Struktur oder an Kanten dargestellter Objekte.
Zur Lucas-Kanade-Analyse stellt das Paket OpenCV (auf Ubuntu heißt
es libopencv-dev
) die Funktion calcOpticalFlowPyrLK()
mit sage und schreibe 11 Parametern bereit.
Das C++-Programm in Listing 1 schnappt sich ein Video und
erkennt Bewegungen von Objekten zwischen verschiednen 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 anschließendes make
erzeugen nach einigen
Zwischenschritten schließlich das Binary max-movement-lk
, das eine
Video-Datei erwartet und die Sekundenwerte im Video ausspuckt, an denen
Bewegungen stattfinden.
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 }
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} )
Hierzu liest die Hauptfunktion main()
den Dateinamen des Videos von
der Kommandozeile und öffnet in Zeile 57 eine VideoCapture aus dem
OpenCV-Paket. Die Framerate, also die Anzahl der Bilder pro Sekunde,
liest Zeile 63 aus der Video-Datei und speichert sie in der Variablen
fps
ab. Da der LK-Algorithmus am besten mit Grauton-Bildern funktioniert,
entziehen die Zeilen 68 und 75 dem gerade analysierten Frame die Farbe.
Eine while-Schleife iteriert über alle Frames und die Funktion move_test()
in Zeile 76 prüft, ob sich zwischen dem zuletzt gelesenen Frame oframe
und dem aktuellen frame
eine Bewegung nachweisen lässt. Ist dies
der Fall, teilt Zeile 77 den Zählerwert durch den Frames-per-Second-Wert
des Videos und erhält somit den Zeitwert in Sekunden im Video, an dem die
Bewegung stattfand.
Der aus [3] entliehene Algorithmus in der Funktion move_test()
ab Zeile
9 ruft zunächst die Funktion goodFeaturesToTrack(()
aus dem OpenCV-Paket
auf, um im alten Frame oframe
interessante Punkte aufzustöbern, deren
Maximalzahl die Konstante MAX_FEATURES
auf 500 setzt. Zeile 27 ruft dann
calcOpticalFlowPyrLK(oframe()
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 500, gibt Zeile 44 in move_test()
den Wert 1 zurück, deutet also an, dass offenbar eine Bewegung
stattgefunden hat.
Abbildung 4: Der Motion-Filter zeigt nur die Videosekunden, in denen tatsächlich etwas passiert. |
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 2, diese Rohdaten aufzumoppen, Thumbnails an den entsprechenden Zeitpunkten zu generieren und das Ganze zu einer Übersicht wie in Abbildung 4 zusammenzufassen.
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 }
Für die Thumbnails nutzt es das gute alte Allround-Tool mplayer
, spult
damit mittels 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 danach 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 Hilfe des CPAN-Moduls DateTime::Format::Duration
ins Format SS::MM:ss
um. Aus dem Frame an Sekunde 64 wird so die Datei
00:01:04.jpg
.
Das Ubuntu-Paket perlmagick
bringt das CPAN-Modul Image::Magick
aufs System, mit dem es ganz einfach ist, aus mehreren Bilddateien
sogenannte Montage
-Konstrukte, also Kontaktabzüge im Format
von Abbildung 4 zu erstellen. Der 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.
Verfahren zum Erkennen von beweglichen 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, dazu Nein sagen?
Listings zu diesem Artikel: http://www.linux-magazin.de/pub/listings/magazin/2016/12/perl-snapshot
"Arlo Security System", https://www.amazon.com/dp/B00P7EVST6
Oscar Deniz Suarez, "OpenCV Essentials", 2014
"Lucas–Kanade method", https://en.wikipedia.org/wiki/Lucas%E2%80%93Kanade_method
"Ex-Googler Sebastian Thrun says the going rate for self-driving talent is $10 million per person", http://www.recode.net/2016/9/17/12943214/sebastian-thrun-self-driving-talent-pool