Aus Linux-Magazin 03/2017

Amazon Web Services einrichten – Teil 2

© Ivan Smuk, 123RF

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 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.

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).

Abbildung 3: Hochladen der Zip-Datei auf den Lambda-Server über einen Amazon-S3-Bucket.

Abbildung 3: Hochladen der Zip-Datei auf den Lambda-Server über einen Amazon-S3-Bucket.

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.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 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
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben