Magazine Salute e Benessere

W. Davis e F. Bobick motion detection – OpenCV 2.4

Creato il 13 aprile 2013 da Matteo Tosato @MatteoTosato87

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.

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:

mei

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:

decay operator

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

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



Potrebbero interessarti anche :

Ritornare alla prima pagina di Logo Paperblog

Possono interessarti anche questi articoli :