In questa penultima parte del nostro viaggio per la realizzazione di una applicazione di realtà aumentata browser-based ci occupiamo di utilizzare NyARToolKit con GWT, una libreria che consente di costuire applicazioni di realtà aumentata, del tipo che consentono di sovrapporre immagini virtuali alle riprese del mondo reale.
NyARToolKit
NyARToolKit è il porting in Java della celebre libreria ARToolKit che è rilasciata sotto licenza GPL3 e scaricabile da [0]; la descrizione dall’autore della libreria la trovate sul suo blog [1].
Visto che NyARToolKit è scritto interamente in Java (e GWT è un compilatore da Java a JavaScript) l’idea è di capire come, e se, sia possibile compilare direttamente il toolkit in JavaScript e quindi usarlo nel browser.
La compilazione e l’utilizzo di ARToolKit è in teoria banale, ma ci sono alcuni dettagli tecnici da sistemare: implementare alcune classi della JRE che ARToolKit utilizza ma non sono emulati da GWT; capire come rendere i byte-array utilizzati internamente dal toolkit compatibili con l’oggetto ImageData che possiamo ottenere dal nostro canvas.
Per gli impazienti c’è il progetto pronto su Google Code da scaricare [2].
Importare il toolkit
Iniziamo scaricando NyARToolKit-4.1.1.zip [3] e creiamo, con Eclipse, un progetto GWT (è comodo per avere la struttura). Cancelliamo dal progetto appena creato tutte le classi inutili (GreetingService*, FieldVerifier) e poi ripuliamo web.xml e l’EnrtyPoint, fino ad avere un progetto GWT senza errori e pronto all’utilizzo.
A questo punto possiamo iniziare a importare il toolkit. Una volta aperto il folder compresso, dobbiamo importare nel progetto Eclipse le directory src (che io per comodità ho rinominato in src.nyartoolkit) e src.markersystem.
Per poter essere “viste” dal compilatore GWT dobbiamo creare un module file. La cosa più semplice risulta essere di crearne uno chiamato NyARToolKit.gwt.xml in jp.nyatla.nyartoolkit, con questo contenuto:
che istruisce sostanzialmente il compilatore a visitare tutte le directory che compongono NyARToolKit.
Passi ulteriori
Fatto questo possiamo aggiungere la riga
nel file gwt.xml del nostro progetto e, in teoria, siamo pronti per scrivere il nostro programma.
Possiamo, ad esempio, inserire nel metodo onModuleLoad del nostro EntryPoint:
NyARSensor i_sensor = new NyARSensor(new NyARIntSize(320,240));
che ha (anzi, avrebbe, se funzionasse) l’effetto di creare un oggetto di tipo sensor che risulterà utile per il nostro programma; ma se proviamo a eseguire il codice appena scritto (anche in development mode) purtroppo otteniamo un errore:
[ERROR] [test] - No source code is available for type java.io.InputStream; did you forget to inherit a required module?
La realtà è che NyARToolKit non era pensato per essere compilato con GWT e utilizza molte classi che non si trovano nella JRE emulata [4]. Fortunatamente, leggendo il toolkit si vede facilmente che la maggior parte delle classi non disponibili nella JRE emulata sono utilizzate dal toolkit per operazioni che noi non vogliamo (e neanche possiamo) utilizzare nel browser: per arrivare a compilare la libreria dobbiamo solo dare al compilatore delle implementazioni no-op di queste classi, in modo che formalmente la compilazione possa avvenire, e prestare attenzione a non utilizzarle nel nostro codice.
Creare implementazioni “fasulle”…
Utilizzando il tag super-source nel file gwt.xml del nostro progetto [5] che serve proprio per permettere di estendere la JRE emulata, dobbiamo creare implementazioni “fasulle” di:
- java.io.FileInputStream
- java.io.InputStream
- java.io.InputStreamReader
- java.io.StreamTokenizer
- java.nio.ByteBuffer
- java.nio.ByteOrder
Ad esempio, la nostra InputStreamReader è semplicemente:
package java.io; import com.google.gwt.user.client.Window; public class InputStreamReader { public InputStreamReader(InputStream is) { Window.alert("Error, InputStreamReader created"); } }
Insomma, una classe totalmente “farlocca” che, visto che nei nostri esperimenti vogliamo essere sicuri di non utilizzarla mai, ci avvisa con un alert nel caso che il nostro codice la utilizzi.
…e implementazioni mancanti
Poco più complicata è anche l’implementazione dell’unica classe non implementata dalla JRE emulata ma effettivamente utile per i nostri scopi: java.lang.reflect.Array.
Infatti java.lang.reflect non è supportata dalla JRE di GWT, e la sua implementazione completa sarebbe probabilmente estremamente complessa (se fattibile); ma implementare gli Array nei casi particolari utilizzati da NtARToolkit è invece molto semplice:
package java.lang.reflect; import jp.nyatla.nyartoolkit.core.labeling.rlelabeling.NyARRleLabelFragmentInfo; import jp.nyatla.nyartoolkit.markersystem.INyARSingleCameraSystemObserver; import jp.nyatla.nyartoolkit.markersystem.utils.SquareStack; import com.google.gwt.user.client.Window; public class Array { public static Object newInstance(Class c, int n) { if(NyARRleLabelFragmentInfo.class.equals(c)) return new NyARRleLabelFragmentInfo[n]; else if(SquareStack.Item.class.equals(c)) return new SquareStack.Item[n]; else if(INyARSingleCameraSystemObserver.class.equals(c)) { return new INyARSingleCameraSystemObserver[n]; } else Window.alert("Creating array of size " + n + " of " + c.toString()); return null; } }
L’esempio eseguibile
Forti di queste nuove classi, adesso il nostro esempio è eseguibile (e compilabile), anche se non produce al momento nessun output. Per poter utilizzare il toolkit possiamo ora scrivere nel nostro entrypoint:
NyARMarkerSystemConfig config = new NyARMarkerSystemConfig(320,240); NyARMarkerSystem nyar=new NyARMarkerSystem(config); NyARSensor i_sensor = new NyARSensor(new NyARIntSize(320,240));
creando così un markersysytem (l’oggetto che servirà per fare la detection dei marker) e un sensor (l’oggetto che dovrà essere inizializzato con le immagini da processare).
Il marker
Il passo successivo è quello di creare un marker; il metodo addARMarker di NyARMarkerSystem sarebbe la scelta più semplice, ma purtroppo utilizza InputStream e quindi con il nostro attuale setup di super-source non possiamo avvalercene. Dobbiamo quindi reimplementare addARMarker in modo che legga il marker da una stringa invece che da un input stream: la stinga ce la procureremo utilizzando un ClientBundle.
Il loading del Marker sarà quindi:
int i_patt_resolution = 16; int i_patt_edge_percentage = 25; double i_marker_size =2; NyARCode arCode= new NyARCode(i_patt_resolution,i_patt_resolution); loadFromARToolKitFormString(Data.INSTANCE.patt_hiro().getText(),arCode); marker_id = nyar.addARMarker(arCode, i_patt_edge_percentage, i_marker_size);
I numeretti “magici” i_patt_resolution e i_patt_edge_percentage sono presi da SimpleLite.java, uno degli esempi del toolkit in cui viene caricato il medesimo marker che stiamo usando in questo esempio.
L’implementazione di loadFromARToolKitFormString non ha nulla di interessante ed è sostanzialmente una trasformazione di NyARCode.loadFromARToolKitFormFile(…) fatta allo scopo di sostituire l’InputStream con String; la trovate nel codice allegato e anche nel progetto su Google Code, mentre la TextResource patt.hiro la possiamo reperire direttamente nello ZIP del toolkit ed è resa disponibile all’applicazione grazie al ClientBundle [6] Data:
public interface Data extends ClientBundle { public static final Data INSTANCE = GWT.create(Data.class); @Source("patt.hiro") public TextResource patt_hiro(); @Source("320x240ABGR.png") public ImageResource captured(); }
L’uso del ClientBundle ci risparmia anche una richiesta asincrona facendo sì che il compilatore inserisca il contenuto del file patt.hiro come stringa nel JavaScript generato [7].
Nel ClientBundle Data abbiamo incluso anche l’ImageResource captured che ha come sorgente una delle immagini d’esempio che si trovano nel toolkit (320x240ABGR.png che rappresenta la fotografia di un marker) e che utilizzeremo come input per il nostro programma di esempio.
Popolare il sensor con le immagini
Una volta creato e inserito il marker nel markersystem, non ci resta che popolare il sensor con le immagini nelle quali vogliamo cercare il marker.
In HTML5 la sorgente ideale delle immagini da utilizzare come input per NyARToolKit è certamente il canvas sul quale, come abbiamo visto in una precedente parte, è possibile anche copiare un video. Il canvas permette di accedere all’array di pixels attraverso il metodo getImageData:
ImageData capt = ctx.getImageData(0, 0, 320, 240);
che restituisce un oggetto di tipo ImageData che è sostanzialmente un array di pixels.
Purtroppo “sostanzialmente un array” non ci basta in quanto i_sensor accetta di essere popolato da un INyARRGBRaster che è implementato da NyARRgbRaster ma che per essere inizializzato (wrap) accetta solo un array di bytes che, sfortunatamente, è un tipo diverso da ImageData.
Per aggirare questo problema la strategia più semplice è certamente quella di copiare imagedata in un array di bytes come descritto in [8]. Questo approccio è certamente semplice ma anche time-consuming in quanto si deve iterare su tutti i pixel dell’immagine (e anche pochi millisecondi potrebbero pesare quando useremo il codice sul video proveniente dalla webcam); quindi dobbiamo procedere con un approccio meno diretto, “wrappando” il nostro ImageData in un oggetto che implementi direttamente INyARRgbRaster. Si tratta di una operazione meno complicata di quanto possa sembrare a una prima lettura in quanto, benchè ImageData non sia propriamente un array, è pur sempre una collezione di pixel e quindi la nostra implementazione di ImageDataRaster si è potuta ampiamente ispirare a NyARRgbRaster con solo poche modifiche sintattiche.
Individuazione del marker
Con anche ImageDatRaster a disposizione, il codice per fare la detection del marker è adesso estremamente semplice:
ImageDataRaster input = new ImageDataRaster(capt); i_sensor.update(input); nyar.update(i_sensor); if(nyar.isExistMarker(marker_id)) { //marker trovato } else { //marker non trovato }
Codice che, come avviene nell’esempio allegato, andrà eseguito dopo aver copiato sul canvas l’immagine:
ImageElement imgElement = ImageElement.as(img.getElement()); ctx.drawImage(imgElement, 0, 0, 320, 240);
A sua volta la copia dell’immagine sul canvas potrà essere fatta solo dopo il caricamento dell’immagine stessa, ad esempio in un LoadHandler associato all’elemento Image.
L’esempio allegato
Se tutto ha funzionato, dovreste poter eseguire l’esempio (si chiama TrivialSample sul progetto) ed ottenere qualcosa di simile alla figura seguente.
Figura 1 – Il marker dell’esempio.
Se cambiate immagine, mettendone una che non contiene il marker, la scritta detected dovrebbe scomparire: NyARToolKit funziona interamente clientside.
Estendere il programma da questo punto non è troppo complicato, ad esempio inserendo un video al posto dell’immagine, come nell’esempio che trovate in [9].
Figura 2 – Il marker viene individuato. In [9] è possibile vedere un video dimostrativo.
Conclusioni
Se ci avete seguiti fin qui, vi sarete resi conto che far riconoscere marker 2D è attività che richiede una adeguata programmazione, ma che non rappresenta nulla di trascendentale.
Inserire oggetti 3D ed utilizzare come input lo stream proveniente dalla webcam è invece appena un po’ più complicato e richiede di “incollare” i vari progetti che abbiamo presentato nelle precedenti parti. Sarà questo l’argomento della prossima e ultima puntata.
Riferimenti
[0] NyARToolKit
http://sourceforge.jp/projects/nyartoolkit/releases/?package_id=7512
[1] NyARToolKit for Java
http://nyatla.jp/nyartoolkit/wp/?page_id=315
[2] Il codice adattato per GWT
https://code.google.com/p/gwt-nyartoolkit/
[3] Download di NyARToolkit for Java.EN
http://es.sourceforge.jp/projects/nyartoolkit/downloads/57614/NyARToolKit-4.1.1.zip/
[4] Emulazione JRE
https://developers.google.com/web-toolkit/doc/latest/RefJreEmulation
[5] Organizzazione dei progetti GWT
https://developers.google.com/web-toolkit/doc/latest/DevGuideOrganizingProjects
[6] Il ClientBundle e le sue funzioni
https://developers.google.com/web-toolkit/doc/latest/DevGuideClientBundle
[7] Text Resource
https://developers.google.com/web-toolkit/doc/latest/DevGuideClientBundle#TextResource
[8] Copiare imagedata in un array di bytes
http://jooink.blogspot.it/2012/10/gwt-augmented-reality-howto-step-1.html
[9] Il video dimostrativo della marker detection
http://www.jooink.com/experiments/GWT_NyARToolKit_Sample