La comprensione dell’azione umana è uno dei temi sviluppatosi molto negli ultimi anni in computer vision. Attualmente, è possibile ricostruire completamente in tre dimensioni soggetti umani partendo da sequenze video acquisite con telecamera. Rilevazione e classificazione del movimento sono alcuni dei temi più attuali nelle aziende che trattano sicurezza perimetrale sia civile che militare. Molto spesso lo scopo è quello di rilevare accuratamente non solo l’allarme ma anche il tipo di azione compiuta.
Una delle alternative alle complesse ricostruzioni in tre dimensioni si basa sula teoria sviluppata da James W. Davis e Aaron F. Bobick al MIT (Massachusetts Institute of Technology) [2][3].
L’idea di Davis e Bobick prevede l’utilizzo di un “Temporal Templates”, usato per rilevare il movimento correlando una serie finita di frame collezionati in un dato range di tempo. Si tratta quindi di una analisi fatta su una serie di immagini memorizzate in un buffer circolare la cui lunghezza varia a seconda della quantità di frames al secondo (un’alta velocità richiede un buffer più grande in modo che la serie di frame contengano il movimento).
La teoria del “temporal template” si basa in origine sull’idea di “Motion Energy Images“, già sviluppata in [4].
Figura 1: Motion Energy Images.
Per “Motion Energy Images” (MEI), si intende sempre una sequenza di frames. Se si considera l’esempio in figura 1, la prima serie di frames corrisponde all’azione di “sedersi” su una sedia. Gli altri tre frames sottostanti corrispondono invece all’accumolo del movimento raffigurato come valori binari. In altre parole, la dove i pixel cambiano valore, significa che c’è stato un movimento. L’immagine finale corrisponde sostanzialmente ad un “accumolo” di movimento.
formalizzando, MEI può essere definita come:
dove E(x,y,t) è una sequenza di frames, e per D(x,y,t) si intende l’immagine binaria che indica le regioni di movimento. La variabile ‘t’, il tempo, è molto critica per determinare D. Solitamente nelle applicazioni, si cerca in modo dinamico lungo un più vasto range di tempo. (Movimenti possono essere fatti con velocità diverse e quindi rappresentati tramite MEI in modi diversi a seconda del delta t che si considera).
L’evoluzione che Davis e Bobick hanno apportato a questo modello è una migliore rappresentazione della variabile ‘t’ nel modello, introducendo una funzione Ht definita per ogni pixel coinvolto, che equivale, ad un valore di intensità in funzione all’istante di tempo in cui il movimento è avvenuto in quel punto. Questa rappresentazione, oltre fornirci una informazione relativa al “dove” il movimento è avvenuto, ci dice anche alcune cose sul “come”. La rappresentazione è chiamata “Motion History Images” (MHI).
La funzione Ht che definisce il valore di intensità di ogni pixel soggetto a movimento, tiene perciò conto della “storia” del punto nel tempo.
E’ definita come segue:
Il risultato è una immagine composta da valori scalari dove i pixel di movimento più recente hanno una gradazione più elevata.
Nella figura 2, viene mostrato un esempio, una persona muove le braccia dal basso verso l’alto.
Figura 2: Motion History Images
Successivamente a questo step è ragionevole pensare anche ad un sistema discriminatore di azioni. Sono molti gli approcci che si possono ipotizzare (come [5]). Trasformando l’immagine MHI, si presta per essere trattata da un classificatore addestrato con un set predeterminato.
Fortunatamente, OpenCV implementa al suo interno varie funzioni che permettono la realizzazione in tempi molto brevi di un algoritmo di motion detection basato su questa teoria. Nel seguente esempio ho utilizzato le API di OpenCV per realizzare un sistema di questo tipo. Inoltre, un filtro Haar si occupa di riconoscere le sagome umane.
#include "stdafx.h" #include "opencv.hpp" #include "time.h" using namespace std; using namespace cv; /* * One of the most effective methods for motion detection * is by using 'Motion Templates'. * This method was invented in the MIT Media Lab by Bobick and Davis. * (The representation and recognition of action using temporal templates, MIT media lab, Cambridge) */ // Globals const double MHI_DURATION = 1; const double MAX_TIME_DELTA = 0.5; const double MIN_TIME_DELTA = 0.05; const char* humanCascadeFileName = "F:\\opencv\\data\\haarcascades\\haarcascade_fullbody.xml"; // Parameter specifying how much the image size is reduced at each image scale const float search_scale_factor = 1.1f; // Haar flags const int gflags = CV_HAAR_DO_ROUGH_SEARCH; IplImage **buf; // image circular buffer CvMemStorage *storage = 0; // temporary storage IplImage *mhi = 0; // mhi IplImage *mask = 0; // valid orientation mask IplImage *orient = 0; // orientation IplImage *segmask = 0; // motion segmentation map IplImage *diff; // difference image IplImage *frame = NULL; // final frame IplImage *image; // current frame // lenght of circular buffer, (depending on the cam fps returns different results) const int circularBuffSize = 4; // buffer indices int bufLastIdx = 1, bufOldestIdx; // Prototypes void update_mhi(IplImage* src, IplImage* dst, int threshold); void _init_(CvSize size); void detect(IplImage* img, IplImage* dst, CvHaarClassifierCascade* classifier, CvSize size, CvRect roi, int flags); // Entry point int _tmain(int argc, _TCHAR* argv[]) { // Capture form webcam initialization CvCapture* capture = 0; capture = cvCaptureFromCAM(-1); // Builds windows cvNamedWindow("video", CV_WINDOW_AUTOSIZE); cvNamedWindow("motion", CV_WINDOW_AUTOSIZE); if(capture) { CvHaarClassifierCascade* humanCascade = (CvHaarClassifierCascade*)cvLoad(humanCascadeFileName); for(;;) { // query frame image = cvQueryFrame(capture); if(!image) break; if(!frame) // first iteration only { _init_(cvSize(image->width, image->height)); frame = cvCreateImage ( cvSize(image->width, image->height), image->depth, image->nChannels ); cvZero(frame); frame->origin = image->origin; } // detect motion update_mhi(image, frame, 30); // detect human detect(image, frame, humanCascade, cvSize(80,80), cvRect(0, 0, image->width, image->height), gflags); // Show cvShowImage("video", image); cvShowImage("motion",frame); // Wait for a key to terminate the loop if( waitKey( 10 ) >= 0 ) break; } } } // Detect motion and update images history void update_mhi(IplImage* src, IplImage* dst, int threshold) { // Compute timestamp double timestamp = (double)clock()/CLOCKS_PER_SEC; CvSize size = cvSize(src->width, src->height); // get current frame size // allocates images at the beginning or reallocate them if the frame is changed if((!mhi) || (mhi->width != size.width || mhi->height != size.height)) { cvReleaseImage(&mhi); cvReleaseImage(&orient); cvReleaseImage(&segmask); cvReleaseImage(&mask); mhi = cvCreateImage(size, IPL_DEPTH_32F, 1); cvZero(mhi); orient = cvCreateImage(size, IPL_DEPTH_32F, 1); segmask = cvCreateImage(size, IPL_DEPTH_32F, 1); mask = cvCreateImage(size, IPL_DEPTH_8U, 1); } // Convert image to gray scale cvCvtColor(src, buf[bufLastIdx], CV_BGR2GRAY); /* Get difference between frames * One of the easiest ways of finding object silhouettes is by taking the absolute * difference in the frames of 2 images. * The function which does this is cvAbsDiff. This function takes 2 images from the buffer as input, * and gives the silhouette of cvArray type as the output. */ diff = buf[bufOldestIdx]; cvAbsDiff(buf[bufLastIdx], buf[bufOldestIdx], diff); /* * Threshold it * The function applies fixed-level thresholding to a single-channel array. * The function is typically used to get a bi-level (binary) image out of a grayscale image * ( compare() could be also used for this purpose) or for removing a noise, that is, filtering * out pixels with too small or too large values. There are several types of thresholding supported by the function. */ cvThreshold(diff, diff, threshold, 1, CV_THRESH_BINARY); /* * The function updates the motion history image * Parameters: - silhouette – Silhouette mask that has non-zero pixels where the motion occurs. - mhi – Motion history image that is updated by the function (single-channel, 32-bit floating-point). - timestamp – Current time in milliseconds or other units. - duration – Maximal duration of the motion track in the same units as timestamp . */ cvUpdateMotionHistory(diff, mhi, timestamp, MHI_DURATION); // Convert MHI to blue 8u image cvCvtScale(mhi, mask, 255./MHI_DURATION, (MHI_DURATION - timestamp)*255./MHI_DURATION); cvZero(dst); cvMerge(mask,0,0,0,dst); // Update indices of ring buffer bufLastIdx++; if(bufOldestIdx == bufLastIdx) bufOldestIdx++; if(bufLastIdx > (circularBuffSize-1)) { bufLastIdx = 0; bufOldestIdx++; } if(bufOldestIdx > (circularBuffSize-1)) { bufOldestIdx = 0; } } // Initialize ring buffer and storage void _init_(CvSize size) { // Allocate image buffer buf = (IplImage**)malloc(circularBuffSize*sizeof(buf[0])); for(int i = 0; i < circularBuffSize; i++) { buf[i] = cvCreateImage(cvSize(size.width,size.height),IPL_DEPTH_8U, 1); cvZero(buf[i]); } bufLastIdx = 0; // buffer empty // Storage storage = cvCreateMemStorage(0); } // Detection of feature with Haar classifier void detect(IplImage* img, IplImage* dst, CvHaarClassifierCascade* classifier, CvSize size, CvRect roi, int flags) { CvRect rc; CvSeq *rects; if(!img) return; cvClearMemStorage(storage); // Create a copy IplImage* temp_img = cvCreateImage(cvSize(img->width,img->height), IPL_DEPTH_8U, 1); // Convert in gray scale cvCvtColor(img, temp_img, CV_BGR2GRAY); // Image equalization cvEqualizeHist(temp_img, temp_img); cvResetImageROI(img); if(!((roi.height <= 0) || (roi.width <= 0) || (roi.x <= 0) || (roi.y <= 0))) cvSetImageROI(temp_img, roi); // Human detection with Haar classifier cascade rects = cvHaarDetectObjects(temp_img, classifier, storage, search_scale_factor, 3, flags, size); if(rects->total > 0) { for(int i = 0; i < rects->total; i++) { rc = *(CvRect*)cvGetSeqElem(rects,i); cvRectangle(dst, cvPoint(rc.x,rc.y), cvPoint(rc.x + rc.width,rc.y + rc.height), cvScalar(0,0,200), 2); } } cvReleaseImage(&temp_img); }
Il seguente video mostra l’utilizzo del programma di esempio. La telecamera di acquisizione è stata poggiata ai bordi di una strada per riprendere i passanti.
N.B.
Una telecamera fissa da risultati migliori in quanto non ci sono oscillazioni, che in questo caso creano quell’effetto bordi sul paesaggio di sfondo.
L’esempio è stato compilato con OpenCV 2.4 con supporto Threading Building Blocks di Intel, librerie senza supporto multithreading possono dare risultati differenti.
Bibliografia:
.1 OpenCV: Implementing Motion Detection using Motion Templates
.2 AAron F. Bobick and James W.Davis – Action recognition using temporal templates, MIT media Laboratory
.3 James W. Davis and Aaron F. Bobick – The Representation and Recognition of Action Using Temporal Templates, MIT Media Laboratory
.4 Black, M. and Y. Yacoob. Tracking and Recognizing Rigid and Non-Rigid Facial Motions using Local Parametric Models of Image Motion, Xerox Palo Alto Research Center and Computer Vision Laboratory
.5 Ming-Kuei, Visual Pattern Recognition by Moment Invariants