連載
» 2011年11月16日 03時26分 公開

NyARToolKit for Androidよりも簡単なAndARとはモバイルARアプリ開発“超”入門(3)(2/3 ページ)

[金岡徹,TIS株式会社]

AndARのソースコードを確認

 どのような仕組みで動いているか実際にソースコードを見ながら確認していきましょう。「src」以下には以下の5つのパッケージがあります。

図4 AndARのサンプルディレクトリ構造 図4 AndARのサンプルディレクトリ構造
  • edu.dhbw.andar
    ARを実現するためのコアとなるクラスを格納
  • edu.dhbw.andar.exceptions
    例外(Exception)に関するクラスを格納
  • edu.dhbw.andar.interfaces
    インターフェイスを格納
  • edu.dhbw.andar.pub
    カスタマイズ用のクラスを格納
  • edu.dhbw.andar.util
    ユーティリティクラスを格納

 アプリを起動したとき、最初に呼ばれるのが「edu.dhbw.andar.pub.CustomActivity.onCreate()」です。このクラスはAndARActivityを継承しています。

    @Override
    public void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
        CustomRenderer renderer = new CustomRenderer();//optional, may be set to null
        super.setNonARRenderer(renderer);//or might be omited
        try {
 
            artoolkit = super.getArtoolkit();
            someObject = new CustomObject("test", "patt.hiro", 80.0, new double[]{0,0});
            artoolkit.registerARObject(someObject);
 
        } catch (AndARException ex){
            //handle the exception, that means: show the user what happened
            System.out.println("");
        }
        
    }

 「super.onCreate(savedInstanceState)」で「edu.dhbw.andar.AndARActivity.onCreate()」を呼び、さまざまな初期化処理を行います。その後、「super.setNonARRenderer(renderer)」でレンダラを設定し、「artoolkit.registerARObject(someObject)」で「どのマーカーに対してどのような3Dモデルを描画するのか」という情報を登録しています。

 AndARは「AndARActivity.onCreate()」を基点としてARを実現するためのコアな処理を次々と実行していきます。そのため、ここからは「AndARActivity.onCreate()」がどのような処理をしているのか詳しく見ていきます。

AndARActivity.onCreate()の処理

    @Override
    public void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
        Thread.currentThread().setUncaughtExceptionHandler(this);
        res = getResources();
        
        artoolkit = new ARToolkit(res, getFilesDir());
        setFullscreen();
        disableScreenTurnOff();
        //orientation is set via the manifest
        try {
                IO.transferFilesToPrivateFS(getFilesDir(),res);
        } catch (IOException e) {
                e.printStackTrace();
                throw new AndARRuntimeException(e.getMessage());
        }
        FrameLayout frame = new FrameLayout(this);
        previewSurface = new Preview(this);
                
        glSurfaceView = new GLSurfaceView(this);
        renderer = new AndARRenderer(res, artoolkit, this);
        cameraHandler = new CameraPreviewHandler(glSurfaceView, renderer, res, artoolkit, camStatus);
        glSurfaceView.setRenderer(renderer);
        glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        glSurfaceView.getHolder().addCallback(this);
 
        frame.addView(glSurfaceView);
        frame.addView(previewSurface);
 
        setContentView(frame);
        if(Config.DEBUG)
            Debug.startMethodTracing("AndAR");
    }

 処理の流れは、以下のようになっています。

  1. ARToolkitクラス(ネイティブコードのARToolkitとの橋渡しを担っているクラス)のartoolkitを生成
  2. SurfaceViewの生成とSurfaceHolderの設定
  3. GLSurfaceViewの生成とAndARRendererクラスのインスタンスのセット(AndARRenderer.onDrawFrame()を定期的に呼ぶ)
  4. PreviewCallbackの設定(cameraHandler.onPreviewFrame()を定期的に呼ぶ)

 つまり、AndARActivityはカメラプレビューとレンダリングの設定を行っていることになります。これにより、「CameraPreviewHandler.onPreviewFrame()」「AndARRenderer.onDrawFrame()」を定期的に呼ぶようになります。

 基本的には、この2つのメソッドが協調して動作することにより、カメラから取得したプレビュー画像上に3Dモデルを表示できます。

 具体的には、以下の流れで処理を実行します。

  1. CameraPreviewHandler.onPreviewFrame()でカメラから取得したプレビューデータの中にマーカーが存在するかをチェック
  2. もしマーカーが存在すれば、AndARRenderer.onDrawFrame()でマーカー上に3Dモデルをレンダリング

 ここからは、「CameraPreviewHandler.onPreviewFrame()」「AndARRenderer.onDrawFrame()」について、それぞれ詳しく見ていきましょう。

edu.dhbw.andar.CameraPreviewHandler.onPreviewFrame()の処理

 まず、CameraPreviewHandler.onPreviewFrame()のソースコードを確認します。

    @Override
    public synchronized void onPreviewFrame(byte[] data, Camera camera) {
        //prevent null pointer exceptions
        if (data == null)
            return;
        if(cam==null)
            cam = camera;
        //camera.setPreviewCallback(null);
        convWorker.nextFrame(data);
        markerInfo.detectMarkers(data);
    }

 ここでは、カメラプレビューを取得し、取得したデータを利用して、それぞれ以下の処理を行っています。

    convWorker.nextFrame(data);
    markerInfo.detectMarkers(data);

 この2つの処理ではThreadを継承した「convWorker」「detectMarkerWorker」というスレッドが動作しています。この2つの処理についてそれぞれ詳しく説明していきます。

 まず、「convWorker.nextFrame(data)」について説明します。convWorkerはThreadクラスを継承した「ConversionWorker」というクラスのインスタンスで、カメラプレビューから取得した「YCbCr 4:2:0 SP」という形式のデータをRGB形式に変換する処理を行っています。

 このとき、カメラプレビューは定期的に取得され続けるので、1フレームずつ取り出して変換処理に渡す必要があるのですが、すべてのフレームを変換するのはパフォーマンスの面で効率が良くありません。そのため、変換処理が完了するまで新規のフレームを破棄し、受け付けないようにしています。

 その制御を行っているのがconvWorker.nextFrame(data)です。このようにconvWorkerではカメラから取得したプレビューのデータを次々とRGB形式に変換していきます。変換したデータは保持され、レンダリング処理時に利用されます。

 次に、「markerInfo.detectMarkers(data)」について説明します。markerInfoはARToolkitクラスのインスタンスなので、ARToolkitクラスの「detectMarkers(data)」が呼ばれます。その中で、「detectMarkerWorker.nextFrame(image)」が呼ばれます。

 detectMarkerWorkerは、Threadクラスを継承した「DetectMarkerWorker」というクラスのインスタンスです。detectMarkerWorkerはカメラプレビューのデータからマーカーの検出と変換行列の計算を行っています。具体的には以下のメソッドで行います。

    artoolkit_detectmarkers(curFrame, transMatMonitor);

 このネイティブメソッドの戻り値によりマーカーを検出したかどうかを判断し、検出した場合にはARObjectクラスの「visible」をtureとします。また、このとき変換行列の計算を行い、ARObjectクラスのglMatrixに格納しています。

 convWorkerと同様に、カメラから取得可能なプレビューデータ全てからマーカー検出をするのは非効率なので、「detectMarkerWorker.nextFrame(image)」でマーカーを検出するフレームを制御しています。

 まとめると、CameraPreviewHandler.onPreviewFrame()ではプレビューのデータを取得し、2つのスレッドを使って以下の処理を行っています。

  • convWorkerでは、取得したプレビューのデータを「YCbCr 4:2:0 SP」からRGBに変換し、データを格納する(データは「AndARRenderer.onDrawFrame()」でレンダリングする際に利用)
  • detectMarkerWorkerは取得したプレビューのデータの中からマーカーが存在するかを検出し、存在した場合には変換行列を計算

 ここまでで、カメラプレビューの取得とプレビュー内でのマーカーの判定のロジックを確認していきました。次は、実際にマーカー検出時の3Dモデルのレンダリングについて見ていきます。

edu.dhbw.andar.AndARRenderer.onDrawFrame()の処理

 ここからは、マーカー検出時に、どのように3Dモデルをレンダリングしているのかについて説明します。

 3Dモデルのレンダリングには「AndARRenderer」クラスを利用します。先ほど、CustomAcitvityのonCreateでの初期設定により「AndARRenderer.onDrawFrame()」を定期的に呼ぶことは説明しました。

 「AndARRenderer.onDrawFrame()」では、具体的に何をしているのかソースコードを確認してみましょう。

    @Override
    public final void onDrawFrame(GL10 gl) {
        
        // カメラから取得したデータを2Dのテクスチャにしてレンダリングする処理
        ……(省略)……
 
        //3Dモデルをレンダリングする処理
        if(customRenderer != null)
                customRenderer.setupEnv(gl);
        else {
 
            // 光源の設定
            gl.glEnable(GL10.GL_LIGHTING);
            gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambientLightBuffer);
            gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffuseLightBuffer);
            gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specularLightBuffer);
            gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPositionBuffer);
            gl.glEnable(GL10.GL_LIGHT0);
        }
 
        markerInfo.draw(gl);
 
        // CustomRendererに描画処理が書かれていれば実行
        if(customRenderer != null)
            customRenderer.draw(gl);
 
        //スクリーンショットを取得する処理
        ……(省略)……
    }

 AndARRenderer.onDrawFrame()では大きく3つの処理を行っています。

  1. カメラから取得したデータを2Dのテクスチャにしてレンダリングする処理(convWorkerで取得したRGB形式のデータを利用)
  2. 3Dモデルをレンダリングする処理
  3. スクリーンショットを取得する処理

 今回は「2. 3Dモデルをレンダリングする」についてのみ説明します。3Dモデルをレンダリングしているのは、以下のメソッドです。

    markerInfo.draw(gl);

 markerInfoはedu.dhbw.andar.ARToolkitクラスのインスタンスです。「ARToolkit.draw()」は以下のようになっており、ARObjectクラスの配列「arobjects」内にあるARObjectのインスタンスを1つずつ取り出して、obj.isVisible()がtrueであるかチェックしています。

    public final void draw(GL10 gl) {
        if(initialized) {
            if(Config.DEBUG)
                Log.i("MarkerInfo", "going to draw opengl stuff now");
            for (ARObject obj : arobjects) {
                if(obj.isVisible())
                    obj.draw(gl);
            }
        }
    }

 arobjectsへのARObjectインスタンスの格納は、「edu.dhbw.andar.pub.CustomActivity」で行います。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        try {
            someObject = new CustomObject("test", "patt.hiro", 80.0, new double[]{0,0});
            artoolkit.registerARObject(someObject);
        } catch (AndARException ex){
            //handle the exception, that means: show the user what happened
            System.out.println("");
        }
    }

 具体的には、「artoolkit.registerARObject(someObject)」を呼ぶことによって、引数のsomeObjectをarobjectsに格納します。

 someObjectはARObjectクラスのサブクラスであるCustomObjectのインスタンスで、インスタンス生成時に検出したいマーカーの種類を第2引数(patt.hiro)で渡しています。ここで指定したマーカーを検出した場合には、someObject.isVisible()が「true」となります。

 つまり、ARToolkit.draw()でのobjはCustomObjectのインスタンスであり、マーカーを検出した場合にはobj.isVisible()がtrueとなるため、CustomObjectのdraw()を呼ぶことになります。以下はCustomObject.draw()のコードです。

    @Override
    public final void draw(GL10 gl) {
        
        super.draw(gl);
 
        // 質感の設定
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR,mat_flash);
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, mat_flash_shiny);    
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, mat_diffuse);  
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, mat_ambient);
 
        //draw cube
        gl.glColor4f(0, 1.0f, 0, 1.0f);
        gl.glTranslatef( 0.0f, 0.0f, 12.5f );
        box.draw(gl);
    }

 CustomObjectは3Dモデルの描画を一手に引き受けています。

 CustomObjectはARObjectを継承しているので、super.draw(gl)が呼ばれると、edu.dhbw.andar.ARObject.draw()を実行します。マーカー上に3Dモデルを表示するにはマーカーから検出した変換行列を適用する必要がありますが、それらの処理はARObject.draw()が行います。

    public synchronized void draw(GL10 gl) {
        if(!initialized) {
             init(gl);
             initialized = true;
        }
        if(glCameraMatrixBuffer != null) {
            glMatrixBuffer.put(glMatrix);
            glMatrixBuffer.position(0);
            //argDrawMode3D
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadIdentity();
            //argDraw3dCamera
            gl.glMatrixMode(GL10.GL_PROJECTION);
            gl.glLoadMatrixf( glCameraMatrixBuffer );
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadMatrixf(glMatrixBuffer);
        }
    }

 具体的には、「glMatrixBuffer.put(glMatrix)」で、検出したマーカー情報から取得した変換行列を適用しています。変換行列の適用が終われば、後は3Dモデルを描画するだけでマーカー上に3Dモデルを表示するようになります。

 「super.draw(gl)」を呼ぶだけで変換行列の適用まで行うので、ARObjectのサブクラスのdrawメソッドではsuper.draw(gl)を呼んだ後は、3Dモデルの描画のみに意識をすれば良くなります。

 今回、描画すべき3Dモデルは立方体ですが、それはbox.draw(gl)を呼ぶことにより実現します。詳細は割愛しますが、このメソッドはOpenGL ESを使って立方体を描画します。

 AndARでは、このようにして、カメラから取得した画像をレンダリングした後、その画像内のマーカー上に3Dモデルをレンダリングすることによってマーカー型のARを実現しています。

 次ページでは、ここで中身を紹介したサンプルアプリをカスタマイズしてオリジナルにする方法を解説します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。