Auf Amazons Lambda-Service laufen selbst geschriebene Python-Skripte in Containerumgebungen – demonstriert im Snapshot am Beispiel eines AI-Programms zur Bewegungsanalyse in Überwachungsvideos.
Nach ersten Gehversuchen im Linux-Magazin 02/16 zum Einrichten eines AWS-Accounts, eines S3-Storage mit statischem Webserver sowie der ersten Lambda-Funktion folgt heute das Setup eines API-Servers auf Amazon zum Aufstöbern von interessanten Szenen in Videos einer Überwachungskamera.
Die per Webaufruf vom Browser oder von einem Kommandozeilentool wie »curl« getriggerte Lambda-Funktion holt dabei ein Video vom Netz, jagt es durch einen mittels Open-CV-Library implementierten AI-Algorithmus (Artificial Intelligence), erzeugt ein Bewegungsprofil und gibt die URL eines als Jpeg generierten Kontaktabzugs mit den wichtigsten Videobewegungen zurück (Abbildungen 1 und 2).
Abbildung 1: Das AI-Programm zur Bewegungsanalyse läuft auf einem Amazon-Server hinter einem REST-API.

Abbildung 2: Der auf AWS erzeugte Kontaktabzug zeigt die Sekunden im Überwachungsvideo, in denen sich tatsächlich etwas bewegt hat.
Sandkastenspiele
Im Gegensatz zu Amazons EC2-Instanzen mit ihren vollblütigen (wenngleich nur virtuellen) Linux-Servern bietet der Lambda-Service [2] nur eine containerisierte Umgebung. In ihr laufen Node-JS-, Python- oder Java-Programme in einem Sandkasten, den Amazon nach Belieben zwischen echten Servern herumschubst oder bei Inaktivität ganz wegputzt, um ihn beim nächsten Zugriff wieder hervorzuzaubern. Daten auf der virtuellen Platte des Containers liegen zu lassen und zu hoffen, sie beim nächsten Aufruf vorzufinden, ergäbe also eine instabile Applikation. Stattdessen kommunizieren Lambda-Funktionen mit AWS-Angeboten wie dem S3-Storage oder der Dynamo-Datenbank, um Daten zu sichern, und agieren ansonsten “stateless”.
Was eine Applikation nicht in einem Python-Skript beschreiben kann, darf der Entwickler auch als Zip-Datei in den, so munkelt man, auf Centos basierten Container hochladen (Abbildung 3).
Eine Lambda-Funktion, die wie im Beispiel Artificial-Intelligence-Funktionen aus der Open-CV-Library nutzt, muss die nötigen Binaries oder Libraries vorher in einer dem Lambda-Container ähnlichen Unix-Umgebung kompilieren, verpacken, hochladen und später zur Laufzeit aus dem Python-Skript aufrufen. Dabei kommen vorhandene Python-Bindings zu Shared Libraries zum Einsatz oder das Python-Skript ruft vorkompilierte Binaries als externen Prozess auf.
Rank und schlank
Damit das AI-Programm aus [3] nach der Installation in der Amazon-Cloud nicht zu viel Rechenzeit und nach dem Überschreiten des kostenlosen Kontingents “Free Tier” auch Geld verbrät, sucht der Code in der gegenüber der vorigen Ausgabe verbesserten Version in Listing 1 nicht mehr in jedem Frame, also 50-mal pro Sekunde, nach Bewegungen, sondern hüpft in Zeile 99 in Schritten von einer halben Sekunde durch den Film. Nach einem Frame mit Bewegung springt Zeile 96 gar 2 Sekunden vorwärts. Im Gegensatz zu »vid.read()« dekodiert das in Zeile 50 aufgerufene »vid.grab()« nicht mehr aufwändig, sondern wirft ihn weg, um zum nächsten zu gelangen.
Listing 1
max-movement-lk.cpp
001 #include "opencv2/opencv.hpp"
002 #include <stdio.h>
003
004 using namespace std;
005 using namespace cv;
006
007 const int MAX_FEATURES = 500;
008 const int MAX_MOVEMENT = 100;
009
010 int move_test(Mat& oframe, Mat& frame) {
011 // Select features for optical flow
012 vector<Point2f> ofeatures;
013 goodFeaturesToTrack(oframe,
014 ofeatures, MAX_FEATURES, 0.1, 0.2 );
015
016 // Parameters for LK
017 vector<Point2f> new_features;
018 vector<uchar> status;
019 vector<float> err;
020 TermCriteria criteria(TermCriteria::COUNT
021 | TermCriteria::EPS, 20, 0.03);
022 Size window(10,10);
023 int max_level = 3;
024 int flags = 0;
025 double min_eigT = 0.004;
026
027 // Lucas-Kanade method
028 calcOpticalFlowPyrLK(oframe, frame,
029 ofeatures, new_features, status, err,
030 window, max_level, criteria, flags,
031 min_eigT );
032
033 double max_move = 0;
034 double movement = 0;
035 for(int i=0; i<ofeatures.size(); i++) {
036 Point pointA
037 (ofeatures[i].x, ofeatures[i].y);
038 Point pointB
039 (new_features[i].x, new_features[i].y);
040
041 movement = norm(pointA-pointB);
042 if(movement > max_move)
043 max_move = movement;
044 }
045 return max_move > MAX_MOVEMENT;
046 }
047
048 int frames_skip( VideoCapture vid, int n, int *i ) {
049 for( int c = 0; c < n; c++ ) {
050 if (!vid.grab())
051 break;
052 (*i)++;
053 }
054 }
055
056 int main(int argc, char *argv[]) {
057 int i = 0;
058 Mat frame;
059 Mat cframe;
060 Mat oframe;
061
062 if (argc != 2) {
063 cout << "USAGE: <cmd> <file_in>\n";
064 return -1;
065 }
066
067 VideoCapture vid(argv[1]);
068 if (!vid.isOpened()) {
069 cout << "Video corrupt\n";
070 return -1;
071 }
072
073 int fps = (int)vid.get(CV_CAP_PROP_FPS);
074
075 i++;
076 if(!vid.read(oframe)) return 1;
077
078 cvtColor(oframe, oframe, COLOR_BGR2GRAY);
079
080 while (1) {
081 if (!vid.read(frame))
082 break;
083 i++;
084
085 int movie_second = i / fps;
086
087 cframe = frame.clone();
088 cvtColor(frame,frame,COLOR_BGR2GRAY);
089 if(move_test(oframe, frame)) {
090 cout << movie_second << "\n";
091
092 char filename[80];
093 sprintf( filename, "%04d.jpg", i/fps );
094 imwrite( filename, cframe );
095
096 frames_skip( vid, 2*fps, &i );
097 } else {
098 // fast-forward to next 1/2 sec
099 frames_skip( vid, fps/2, &i );
100 }
101
102 oframe = frame;
103 }
104
105 return 0;
106 }
Und während die erste Version in [3] nur die Sekundenwerte im Video in die Ausgabe schrieb, an denen der Algorithmus Bewegungen erkannte, um nachfolgend über Tausendsassa Mplayer die zugehörigen Frames als Jpeg-Dateien zu extrahieren, schreiben die Zeilen 92 bis 94 erkannte Frames gleich mittels der Open CV beiliegenden Bildverarbeitungsfunktionen »imwrite()« im Format »0001.jpg« auf die virtuelle Festplatte. Ein zweiter Durchlauf sowie die Frickelei zur Installation von Mplayer in den Lambda-Container entfallen somit.
Aus diesen Jpeg-Bildern macht dann ein weiteres Python-Skript, »mk-montage.py«, unter Zuhilfenahme der Imagemagick-Library einen Kontaktabzug, ebenfalls im Jpeg-Format. Diese Datei legt das Lambda-Programm in Amazons S3-Cloud-Speicher ab und schickt dann einen Link darauf an den aufrufenden Client zurück.






