La tecnica del Chroma Key, cioè la sostituzione di un determinato colore con un altro (o una diversa sorgente), è riesplosa nell’ultimo decennio con notevole prepotenza, grazie alla sempre più diffusa tecnologia presente nel cinema e nel trattamento di immagini digitali. Se in passato il Chroma Key era utilizzato spesso con scarsi risultati, oggi è una tecnica applicata continuamente, anche in contesti e situazioni non sempre immaginabili o riconoscibili, come dimostra il video qui sotto:
Vi mostreò adesso come applicare in modo abbastanza semplice la tecnica del Chroma Key in ambiente Adobe Flash. Nell’esempio che ho realizzato mi sono avvalso di un oggetto video agganciato alla vostra Camera (o WebCam che sia).
Nota bene: negli esempi di codici qui sotto, e nel filmato Flash dimostrativo, ho usato come colore di chroma il nero! Nella realtà, come forse saprete, vengono usati pannelli o teli di colore blu (da qui il nome di Blue Screen) o verdi (Pantone 354)! Inoltre noterete che i valori
uintdel colore sono tutti nella forma0xAARRGGBB; dove èAAè il canale alpha di trasparenza.
Per elaborare l’immagine ho utilizzato il metodo threshold() dell’oggetto BitmapData. threshold() è un metodo molto potente e al tempo stesso molto semplice da utilizzare. Esso permette di eseguire un controllo pixel per pixel su una Bitmap, verificando che un colore (mascherato) sia uguale, diverso, maggiore o minore di un altro e sostituiendolo con un valore dato. Il suo prototipo è il seguente:
1 2 3 4 5 6 7 8 |
sourceBitmapDatasono i dati Bitmap su cui operare. Tale sorgente può essere diversa o uguale allaBitmapDatasu cui si sta operandosourceRectè un puntatore ad un oggetto di tipoRectangleche definisce l’area su cui eseguire l’operazionedestPointè un puntatore ad un oggetto di tipoPointche definisce le coordinatexedydi destinazione: quasi sempre 0,0operationquesta è una stringa che definisce l’operazione da eseguire sui pixel (vedi sotto per dettagli) e può assumere i seguenti valori:"<", "<=", ">", ">=", "==", "!="thresholdè un unsigned int, un intero senza segno, indicante il colore (o valore soglia) da confrontare con i dati pixel della bitmap, ad esempio0x00FF0000colorè un unsigned int, un intero senza segno, indicante il colore che dovrà essere sostituito se l’operazione di confronto ha successomaskè un unsigned int, un intero senza segno, indicante la maschera da applicare al parametro dithresholde ai pixel della bitmap sorgentecopySourceè un valore booleano che se impostato atruecopierà il valore corrente quando l’operazione di confronto fallisce
Il metodo restituisce un uint che rappresenta il numero di pixel modificati. L’operazione che il metodo svolge può essere schematizzata in questo modo:
- Partendo dalle coordinate definite in
sourceRect - Viene letto il valore del pixel nella
sourceBitmape gli viene applicata la mascheramask, cioè viene eseguita l’operazione di AND logico tra i due valori:(pixelLetto & mask) - Il valore ottenuto viene confrontato con
(threshold & mask), anch’esso mascherato, applicando l’operazione di confronto dioperation - Se il confronto restituisce
true, allora partendo dadestPointviene impostato quel pixel sucolor - Se il confronto restituisce
false, non viene eseguita nessuna operazione, a meno che non abbiamo impostato il parametrocopySourceatrue: in questo caso viene copiato il valore così com’è
Nell’esempio che ho realizzato noterete quattro box con quattro immagini della camera:

Ricordarsi di consentire l’accesso alla camera: nel caso possedete più Camere collegate
selezionate quella giusta con il tasto destro su impostazioni
Il primo box in alto a sinistra, Video Originale, è il video così come viene visualizzato quando usiamo l’oggetto Camera agganciato all’oggetto Video:
Subito sotto troviamo il box Threshold, questo è generato in questo modo:
1 2 3 4 | // Threshold var simpleBitmapData:BitmapData = new BitmapData( vid_video.width, vid_video.height, true); var simpleBitmap:Bitmap = new Bitmap(simpleBitmapData); addChild(simpleBitmap); |
In pratica viene creata prima un’istanza di un oggetto BitmapData delle dimensioni del video. Questa viene agganciata ad un oggetto Bitmap, il quale può essere aggiunto allo stage. A questo punto possiamo applicare il metodo threshold, inserendo la nostra procedura all’interno dell’evento ENTER_FRAME (impostate almeno un valore di 30 fotogrammi al secondo nel vostro progetto):
1 2 3 4 5 6 7 8 9 10 11 12 13 | addEventListener( Event.ENTER_FRAME, function (e:Event):void { simpleBitmap.bitmapData.draw( vid_video ); simpleBitmap.bitmapData.threshold(simpleBitmap.bitmapData, new Rectangle(0, 0, simpleBitmap.bitmapData.width, simpleBitmap.bitmapData.height), new Point(0,0), "==", 0x00000000, 0x00000000, 0x00FFFFFF, true ); } ); |
La riga 3 copia (ad ogni ENTER_FRAME) il contenuto video nella nostra BitmapData, visualizzata nello stage tramite l’oggetto Bitmap simpleBitmap. La riga 4 applica (sempre ad ogni ENTER_FRAME) sulla nostra BitmapData il metodo threshold(), nelle modalità descritte in precedenza. Nell’esempio proposto i parametri che ho usato applicano threshold sull’intera area della Bitmap eseguendo l’operazione di uguaglianza "==". Se il pixel letto è uguale al nero 0x00000000 (o 0xFF000000 in dipendenza della maschera) viene sostituito con il vuoto 0x00000000. Alla fine, dunque, otteniamo un chroma key sul nero.
Uso di un range
Questa tecnica va bene quando il colore da sostituire è preciso. Nel nostro precedente esempio, infatti, un valore di 0x00000001 non viene preso in considerazione. Per superare questo ostacolo bisogna eseguire un’operazione diversa da "==". Inoltre, in un filmato reale, ci sono da considerare ombre e riflessi che, in pratica, spostano il nostro colore di chroma da un livello minimo ad un livello massimo. L’ideale, quindi, sarebbe quello di creare una serie di filtri threshold che verificano sia un livello scuro che un livello chiaro del colore che abbiamo scelto per il chroma.
Per realizzare questo non è sufficiente scrivere:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | addEventListener( Event.ENTER_FRAME, function (e:Event):void { simpleBitmap.bitmapData.draw( vid_video ); simpleBitmap.bitmapData.threshold(simpleBitmap.bitmapData, new Rectangle(0, 0, simpleBitmap.bitmapData.width, simpleBitmap.bitmapData.height), new Point(0,0), "<=", 0x00222222, 0x00000000, 0x00FFFFFF, true ); } ); |
Il risultato, per quanto apprezzabile, non è corretto, per un semplice motivo: utilizzando l’operatore "<=", che concettualmente è corretto, stiamo confrontanto tutti i livelli inferiori o uguali a 0x00222222. Ne segue che il valore 0x00002200 – che è un verde scuro – verrà considerato come chroma, il che non era nelle nostre intenzioni, volendo filtrare solo il nero e qualche gradazione un po’ più chiara ma sempre del “nero”. La tecnica più precisa è quella di sfruttare il parametro mask e analizzare tramite il threshold le tre componenti RGB separatamente. Quello che andremo a realizzare adesso è un filtro inverso, cioè andremo a creare un maschera che conterrà solo il nostro range. Prima di tutto creiamo una funzione in grado di operare un controllo su un canale RGB specifico:
1 2 3 4 5 6 7 8 9 10 11 | function applyThresholdRGB( myBitmapData:BitmapData, channel:String, op:String, threshold:uint ):void { var maskColor:uint = (channel == "red") ? 0x00FF0000 : ( (channel == "green") ? 0x0000FF00 : 0x000000FF ); myBitmapData.threshold(myBitmapData, new Rectangle(0, 0, myBitmapData.width, myBitmapData.height), new Point(0,0), op, threshold, 0x00000000, maskColor, false ); } |
La funzione applyThresholdRGB() permette di eseguire un determinato confronto (vedi parametro op) su un canale RGB specificato nel parametro channel. Creiamo adesso un funzione che genera una maschera:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function createBitmapMask(myBitmapData:BitmapData, colorLow:uint, colorHi:uint):void { //red applyThresholdRGB( myBitmapData, "red", "<", colorLow ); applyThresholdRGB( myBitmapData, "red", ">", colorHi ); //green applyThresholdRGB( myBitmapData, "green", "<", colorLow ); applyThresholdRGB( myBitmapData, "green", ">", colorHi ); //blue applyThresholdRGB( myBitmapData, "blue", "<", colorLow ); applyThresholdRGB( myBitmapData, "blue", ">", colorHi ); } |
Ora che abbiamo la maschera (nell’esempio è il riquadro in alto a destra Bitmap Mask) possiamo sfruttarla per sovrapporla all’immagine originale, eliminando le parti che non servono. Ricapitolando abbiamo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | addEventListener( Event.ENTER_FRAME, function (e:Event):void { // Immagine clone del video simpleBitmap.bitmapData.draw( vid_video ); // Creo un'oggetto Bitmap con i dati BitmapData uguali all'originale var bitmapMask:Bitmap = new Bitmap(simpleBitmap.bitmapData.clone()); // Creo la maschera createBitmapMask(bitmapMask.bitmapData, 0x00000000, 0x00222222); // Applico la maschera all'originale in BlendMode.ERASE simpleBitmap.bitmapData.draw(bitmapMask, null, null, BlendMode.ERASE, null, false); } ); |
Il risultato finale è visibile nel box in basso a destra Bitmap Merge. In questo modo è possibile applicare il Chroma Key non ad un colore specifico ma ad un range.








4
Grazie!
@imagineflash: prego
potresti caricare il file .fla che hai creato??