L'attuale tendenza delle interfacce uomo-macchina, specialmente nel settore videoludico, è quella di ridurre al minimo la presenza di controller e periferiche, puntando sull'interazione tramite i movimenti dell'utente. Ne sono un esempio la Wii di Nintendo con il suo Wiimote (poi ulteriormente migliorato con il MotionPlus), o il Project Natal di Microsoft che si pone l'obiettivo di eliminare completamente il controller (ecco un video di esempio per chi ancora non conoscesse questo progetto)
L'idea non è delle più recenti, già con Playstation 2 infatti era disponibile l'EyeToy e addirittura ai tempi della console Sega Dreamcast era disponibile il DreamEye mentre per il GameBoy c'era la GameBoy Camera, e parliamo del 1998! Se l'idea non è così giovane, la sua implementazione oggi si fa più a portata di mano, grazie ad una tecnologia più evoluta, pensiamo alle definizione delle telecamere (la GameBoy Camera era monocromatica) e alla potenza dei processori, che permette tempi di elaborazione e risposta agli input molto più rapidi.
Anche con Flash e ActionScript è possibile creare un'interazione di questo tipo tra utente e applicazione, eliminando i controller e utilizzando solo i movimenti del corpo, eventuali oggetti e la voce, tutto ciò sfruttando la webcam.
Anche altri sistemi, dalla GameBoy Camera a Project Natal, si basano essenzialmente sull'uso di una telecamera, e anche se il livello che possiamo raggiungere con Flash non è elevato, possiamo comunque realizzare diverse applicazioni che sfruttino questo sistema di controllo decisamente accattivante anche se limitato a poche azioni.
Flash già da diverse versioni permette l'interazione con la webcam, ma due sono le principali migliorie che hanno permesso di sperimentare sistemi di rilevazione dei movimenti: le classi Bitmap (vedremo meglio in seguito perchè), e Actionscript 3. Sebbene infatti già con ActionScript 2 siano stati fatti esperimenti con la rilevazione del movimento via webcam, l'incremento di prestazioni garantito da ActionScript 3 consente applicazioni più avanzate.
Prima di affrontare il lato "pratico" via codice, vediamo di capire meglio come sia possibile, partendo dall'input della webcam, ricavare i movimenti effettuati dall'utente.
Come funziona il motion tracking
La rilevazione del movimento, procedimento detto motion tracking, si basa sull'operazione più logica che ci si possa aspettare: analizza i fotogrammi ricevuti dalla webcam, li confronta tra loro e calcola di conseguenza quale movimento è stato fatto.
Più precisamente il confronto avviene sempre tra due fotogrammi, quello attuale e quello precedente, l'immagine viene letta pixel per pixel e per ogni pixel vengono ricavati luminosità e colore, quindi in base alle differenze calcola l'immagine risultante. Vedremo meglio a breve questi passaggi, possiamo però già capire il motivo per cui, in ambito Flash, Actionscript 3 sia un passo avanti, infatti nell'analizzare delle immagini pixel per pixel è stato stimato che quest'ultima versione del linguaggio sia circa 10 volte più veloce della precedente.
Un altro aspetto che possiamo facilmente intuire, dato che il motion tracking lavora sui pixel, è l'importanza di avere una webcam con una buona definizione, in caso infatti di webcam con una definizione bassa o con una messa a fuoco non correttamente impostata la definizione sarà inevitalmente meno precisa poichè i pixel non saranno ben definiti. Ormai la maggior parte delle webcam, anche a basso costo, garantisce una definizione soddisfacente per le sperimentazioni con il Flash Player, a patto di impostarne correttamente la messa a fuoco e se possibile evitare l'eventuale zoom.
È poi importante, soprattutto per il tracking in Flash, un ambiente privo di eccessive variazioni di luce, poichè un cambio di luminosità eccessivo viene rilevato come un movimento. Ovviamente è altresì fondamentale avere alle spalle uno sfondo statico: Flash non è in grado di rilevare la profondità e la posizione dell'utente e quindi ogni variazione che avviene nella porzione inquadrata dalla webcam viene considerata nell'immagine risultante.
I progetti di Motion Tracking in Flash
Nelle community Flash, almeno per il momento, non emergono veri e propri progetti "ufficiali" o gruppi dedicati al motion tracking, a differenza di altri ambiti applicativi come il 3D, ad esempio. In ogni caso diversi sviluppatori hanno rilasciato il codice sorgente dei loro esperimenti.
L'assenza di un progetto centrale, cui poter apportare migliorie, è forse penalizzante ma comprensibile: infatti le operazioni da svolgere per il tracking sono per tutti le medesime così come i comandi utilizzati. A fare la differenza tra progetto e progetto sono le azioni successive all'elaborazione del motion tracking.
Per capire meglio questo discorso ci possiamo servire di alcuni esempi di implementazione, tra i più interessanti che abbiamo trovato in rete.
Progetto | Versione di AS |
---|---|
Soulwire | AS2 e AS3 |
Spikything | AS3 |
Guy Watson | AS2 |
Shockwave India | porting AS3 dell'esempio di Guy Watson |
Laser Pirate | AS3 |
Notiamo in particolare sull'esempio di SpkyThing la suddivisione delle immagini:
Abbiamo l'immagine live ricavata dalla webcam, la previous image
che è il fotogramma precedente a quello attuale, la difference image
è invece il risultato del confronto tra le due mentre la threshold image
è quella ottenuta da un filtro che mostra i pixel che hanno subito una variazione superiore ad una certa soglia.
Nel corso del rilevamento può sembrare che ci siano delle imprecisioni: pur restando fermi davanti alla WebCam, qualche pixel nelle immagini di differenza e threshold risulta, ma questo è assolutamente normale.
Usare una classe pre-esistente o creare il proprio tracker?
Abbiamo appena visto che gli esempi non differiscono particolarmente tra loro, anche se cambia la rappresentazione finale del tracking, rimane da scegliere se affidarsi ad una delle classi già disponibili oppure se crearne una da zero.
In questo articolo adottiamo una soluzione "ibrida": non creeremo un nostro motion tracker ma cercheremo di analizzarne uno pre-esistente e di sfruttarlo per creare una semplice interfaccia che l'utente potrà usare tramite webcam.
Tra le classi che si possono scaricare, la più apprezzata al momento sembra quella di Soulwire, grazie anche al fatto di essere disponibile sia per Actionscript 3 che per Actionscript 2. Optiamo anche noi per questa classe, e iniziamo a costruire un'interfaccia guidata via WebCam!
Nella prossima parte andremo ad analizzare questa interessante classe e la utilizzeremo dal punto di vista pratico; viste le performance migliori e la maggior modernità, opteremo per la versione Actionscipt 3.
Scaricare e installare le classi
Per prima cosa scarichiamo il progetto Soulwire . Al suo interno troveremo una cartella MotionTracker
.
All'interno dell'archivio troviamo diverse cartelle, il contenuto è un progetto Flex ma possiamo estrarre i vari file per utilizzarli con Flash.
Nella cartella bin
troviamo i file MotionTrackerDemo
(fla e swf) che come si può intuire sono una dimostrazione dell'uso del motion tracker, possiamo aprirlo e notare in particolare come variando il parametro della luminosità cambi la precisione del tracker, infatti come già abbiamo detto uno degli aspetti principali su cui si basano i motion tracker è la diversa luminosità tra i pixel delle due immagini.
Nella cartella src
troviamo la classe MotionTrackerDemo.as
(relativa ai due file appena citati) e la cartella uk
: questa è quella che ci interessa per il nostro esperimento in Flash, infatti in fondo alla serie di sottocartelle che rappresentano il namespace, troviamo la classe MotionTracker.as
.
La cartella src_lib
contiene una cartella com
e un file swc
. Mentre il file swc serve per MotionTrackerDemo
, la cartella com
è richiesta dalla classe MotionTracker
, che sfrutta la classe ColorMatrix
creata da Grant Skinner per alcune operazioni sulle immagini ricavate dalla webcam.
Copiamo allora la cartella uk
e la cartella com
nella directory del nostro progetto. Estraiamo inoltre MotionTrackerDemo.as
dalla cartella src
, in quanto lo utilizzeremo come "guida".
La classe MotionTrackerDemo
Non essendo disponibile, almeno al momento, una guida o una documentazione per la classe, utilizzeremo come "guida" il file MotionTrackerDemo, che contiene il codice necessario a importare e eseguire i comandi della classe MotionTracker.
Apriamo allora il file MotionTrackerDemo.as. Ovviamente per prima cosa viene importata la classe MotionTracker, insieme ad altre classi incluse in Flash, tuttavia non tutte saranno necessarie nel nostro esempio.
import uk.co.soulwire.cv.MotionTracker; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Shape; import flash.display.Sprite; import flash.events.Event; import flash.filters.ColorMatrixFilter; import flash.media.Camera; import flash.media.Video;
Dopo la dichiarazione della classe vengono dichiarate alcune variabili, anche in questo caso non seguiremo alla lettera questo file, l'unico punto sicuramente in comune sarà la dichiarazione di una variabile di tipo MotionTracker.
private var _motionTracker : MotionTracker; private var _target : Shape; private var _bounds : Shape; private var _output : Bitmap; private var _source : Bitmap; private var _video : BitmapData; private var _matrix : ColorMatrix;
A questo punto, leggiamo l'event handler onAddedToStage
, che semplicemente a sua volta richiama altri tre metodi. Di questi ultimi, ignoriamo configureUI
che si occupa di creare gli slider per cambiare i parametri (possiamo vederli nell'esempio MotionTrackerDemo.swf
) così come applyFilters
e ci concentriamo sulla funzione di initTracking
.
initTracking
Questo metodo istanzia gli elementi necessari al tracking: come vedremo il codice da utilizzare è piuttosto semplice, seguiamone il flusso attraverso i commenti, tradotti e ampliati rispetto al sorgente originale.
// imposta larghezza e altezza del // flusso della webcam var camW : int = 420; var camH : int = 320; // carica un oggetto camera, che ricava // l'immagine dalla webcam var cam : Camera = Camera.getCamera(); // indica alla camera le dimensioni del video e il // framerate, impostato uguale a quello dello stage cam.setMode(camW, camH, stage.frameRate); // Crea un nuovo oggetto Video largo e alto come il // flusso della webcam, quindi aggancia il flusso // della cam all'oggetto Video var vid : Video = new Video(camW, camH); vid.attachCamera(cam); // Crea l'oggetto MotionTracker, che agisce sull'oggetto // vid (che contiene il flusso proveniente dalla webcam) _motionTracker = new MotionTracker(vid); // Impostata su true la proprietà flipInput del MotionTracker, // che fa sì che l'immagine visualizzata nell'SWF // appaia riflessa rispetto all'originale _motionTracker.flipInput = true;
Le azioni viste fin qui impostano gli elementi principali per il tracking (gli oggetti Camera, Video e il MotionTracker stesso).
Tralasciamo per il momento le righe di codice fino alla 125, queste infatti creano degli elementi che servono per la demo, dove l'utente può vedere da un lato l'immagine attualmente analizzata dal tracker e di fianco la corrispondente immagine elaborata, con tanto di clip che si muove in base al movimento. Queste azioni cambiano ovviamente a seconda di ciò che vogliamo accada durante il tracking, il codice fondamentale dopo quello appena visto è:
addEventListener(Event.ENTER_FRAME, onEnterFrameHandler);
Tramite questa azione diciamo a Flash di passare il controllo al metodo onEnterFrameHandler
in maniera continua. Che cosa fa questa funzione? Esaminiamola istruzione per istruzione:
private function onEnterFrameHandler(event : Event) : void { // Aggiorna il MotionTracker _motionTracker.track(); // Sposta la clip _target in base alle coordinate del motion tracker _target.x += ((_motionTracker.x + _bounds.x) - _target.x) / 10; _target.y += ((_motionTracker.y + _bounds.y) - _target.y) / 10; // aggiorna l'immagine _video.draw(_motionTracker.input); // Se non c'è abbastanza movimento il codice viene fermato if ( !_motionTracker.hasMovement ) return; // Se invece c'è abbastanza movimento viene disegnata l'area attiva del motion tracker _bounds.graphics.clear(); _bounds.graphics.lineStyle(0, 0xFFFFFF); _bounds.graphics.drawRect(_motionTracker.motionArea.x, _motionTracker.motionArea.y, _motionTracker.motionArea.width, _motionTracker.motionArea.height); }
Le righe che sono di nostro interesse sono in realtà solo le prime 3, ovvero l'aggiornamento del tracker e l'utilizzo delle sue coordinate x
e y
. Gli altri riferimenti agli oggetti che abbiamo tralasciato poco fa non sono fondamentali per il nostro scopo.
Possiamo quindi intuire che una volta eseguito l'aggiornamento, tramite il metodo track()
, si può accedere a due proprietà x
e y
dell'oggetto Motion Tracker, che indicano le coordinate del punto centrale dell'area dove è stato rilevato il movimento.
Ricapitolando, abbiamo essenzialmente bisogno di tre oggetti: un oggetto Camera
, uno Video
(a cui sarà agganciato il flusso della webcam) e un MotionTracker
(cui passeremo come parametro l'oggetto video), infine agganciamo all'evento ENTER_FRAME
l'aggiornamento del tracker e ricavare le coordinate relative al movimento per ogni frame.
La prima applicazione con il Motion Tracking
Applichiamo quanto visto finora ad un semplice esempio. Creiamo un nuovo file Actionscript 3 e inseriamo su uno stage da 640x480
pixel, tre MovieClip: due quadrati, con nomi istanza b1
e b2
, e un cerchio con nome istanza _target
. Disponiamo i due quadrati ai lati opposti dello stage, posizioniamo invece a piacere la clip _target
.
Prendendo quindi spunto dal codice visto in precedenza, creiamo la nostra classe, che salveremo come demoTracker.as
, con il seguente codice:
package { import uk.co.soulwire.cv.MotionTracker; import flash.display.MovieClip import flash.events.Event import flash.media.Camera import flash.media.Video public class demoTracker extends MovieClip { private var _motionTracker : MotionTracker; public function demoTracker():void { addEventListener(Event.ADDED_TO_STAGE, avvia); } private function avvia(evt:Event):void { var camW : int = 640; var camH : int = 480; var cam : Camera = Camera.getCamera(); cam.setMode(camW, camH, stage.frameRate); var vid : Video = new Video(camW, camH); vid.attachCamera(cam); _motionTracker = new MotionTracker(vid); _motionTracker.flipInput = false; // Aggiunge il video allo stage addChild(vid); // porta le clip b1, b2 e _target in // primo piano rispetto al video della webcam addChild(b1); addChild(b2); addChild(_target); addEventListener(Event.ENTER_FRAME,aggiorna); } private function aggiorna(evt:Event):void { _motionTracker.track(); _target.x = (_motionTracker.x); _target.y = (_motionTracker.y ); // controlla se il movimento è nell'area // dove si trova b1, nel qual caso ne // imposta l'alpha ad 1 if(_motionTracker.x > b1.x && _motionTracker.x < b1.x + b1.width && _motionTracker.y > b1.y && _motionTracker.y < b1.y + b1.height) { b1.alpha = 1; } else { // altrimenti ne impostiamo l'alpha a 0.3 b1.alpha = .3; } // controlla se il movimento è nell'area // dove si trova b2, nel qual caso ne // imposta l'alpha ad 1 if(_motionTracker.x > b2.x && _motionTracker.x < b2.x + b2.width && _motionTracker.y > b2.y && _motionTracker.y < b2.y + b2.height) { b2.alpha = 1; } else { // altrimenti ne impostiamo l'alpha a 0.3 b2.alpha = .3 } } } }
Abbiamo aggiunto il flusso video vid
alla scena prima di portare in primo piano i tre elementi che abbiamo inserito. Ovviamente il clip _target
non è obbligatorio, ma avere un riferimento visivo che indica in quale punto preciso è stato rilevato il movimento è molto utile.
Non ci resta che associare al nostro FLA la Document Class demoTracker
ed esportare il filmato: muovendoci sopra gli spazi occupati dai due quadrati potremo vederne variare la trasparenza.