The memo blog.

About programming language and useful library.

Vuforia-II: Camera

| Comments

為了能夠完全掌握vuforia怎樣在Android上使用,個人的風格就是逐步拆解範例程式的Code然後做一個通盤了解,不僅止於在能修改這上面。在vuforia的範例程式當中,主要有分幾個部分:

  • 版權聲明
  • 讀取畫面
  • 開啟camera
  • 執行AR演算法
  • 是否有找到標籤,有則Render物件沒有則跳過

以上大概是每個範例程式所會進行的事情,那在這當中最基本的大概就是開啟Camera吧!在建立完專案之後,先加入一下使用Camera的權限到Android程式當中。

AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.vuforiacamera"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="15"
        android:targetSdkVersion="15" />
    <!-- Add permission of camera -->
    <uses-feature android:name="android.hardware.camera" />
    <uses-permission android:name="android.permission.CAMERA" />


    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.vuforiacamera.MainActivity"
            android:configChanges="orientation|keyboardHidden"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

根據之前分析的範例程式的結果,裡面分為幾個步驟:Init_QCAR->Init_Tracker->InitApp_AR->InitLoader_Tracker詳細過程內容,看看之後有沒有興趣補上,以上名字不一定正確因為是自己寫的簡解XD,但是都在vuforia範例程式當中可以找到。那在Init_QCAR以及Init_Tracker當中,主要是讀取QCARLibrary也就是vuforiaInit_Tracker當中,為讀取資料庫,資料庫是什麼東西?可以參考Vuforia-I的文章。 在這範例程式當中,使用了OpenGL ES來當作繪圖的主要工具之一,其中也有一大段的Code在偵測平台支援OpenGL ES 1.1 or OpenGL ES 2.0在Camera讀取影像並且貼到GLSurfaceView這部分是必需要能釐清的部分,而這中間包含了一些OpenGL ES的寫法,說真的也不是很了解XD。總覺得需要寫很久…就分部分寫吧!先來了解一下Sample Code它所提寫的QCARSampleGLView.java,其實裡面有很多設定完全不知所以然,在這邊就慢慢解說和了解這Code所需要完成的事情。了解之後也比較好釐清一個OpenGL ES程式該怎樣寫。整段貼上,連同聲明!

QCARSampleGLView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*==============================================================================
            Copyright (c) 2010-2012 QUALCOMM Austria Research Center GmbH.
            All Rights Reserved.
            Qualcomm Confidential and Proprietary
==============================================================================*/

package com.qualcomm.QCARSamples.ImageTargets;

import com.qualcomm.QCAR.QCAR;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;

import android.content.Context;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;


/** QCARSampleGLView is a support class for the QCAR samples applications.
 *
 *  Responsible for setting up and configuring the OpenGL surface view.
 *
 * */
public class QCARSampleGLView extends GLSurfaceView
{
    private static boolean mUseOpenGLES2 = true;

    /** Constructor. */
    public QCARSampleGLView(Context context)
    {
        super(context);
    }


    /** Initialization. */
    public void init(int flags, boolean translucent, int depth, int stencil)
    {
        // By default GLSurfaceView tries to find a surface that is as close
        // as possible to a 16-bit RGB frame buffer with a 16-bit depth buffer.
        // This function can override the default values and set custom values.

        // Extract OpenGL ES version from flags
        mUseOpenGLES2 = (flags & QCAR.GL_20) != 0;

        // By default, GLSurfaceView() creates a RGB_565 opaque surface.
        // If we want a translucent one, we should change the surface's
        // format here, using PixelFormat.TRANSLUCENT for GL Surfaces
        // is interpreted as any 32-bit surface with alpha by SurfaceFlinger.

        DebugLog.LOGI("Using OpenGL ES " + (mUseOpenGLES2 ? "2.0" : "1.x"));
        DebugLog.LOGI("Using " + (translucent ? "translucent" : "opaque") +
            " GLView, depth buffer size: " + depth + ", stencil size: " +
            stencil);

        // If required set translucent format to allow camera image to
        // show through in the background
        if (translucent)
        {
            this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
        }

        // Setup the context factory for 1.x / 2.0 rendering
        setEGLContextFactory(new ContextFactory());

        // We need to choose an EGLConfig that matches the format of
        // our surface exactly. This is going to be done in our
        // custom config chooser. See ConfigChooser class definition
        // below.
        setEGLConfigChooser( translucent ?
                             new ConfigChooser(8, 8, 8, 8, depth, stencil) :
                             new ConfigChooser(5, 6, 5, 0, depth, stencil) );
    }


    /** Creates OpenGL contexts. */
    private static class ContextFactory implements
        GLSurfaceView.EGLContextFactory
    {
        private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
        public EGLContext createContext(EGL10 egl, EGLDisplay display,
            EGLConfig eglConfig)
        {
            EGLContext context;
            if (mUseOpenGLES2)
            {
                DebugLog.LOGI("Creating OpenGL ES 2.0 context");
                checkEglError("Before eglCreateContext", egl);
                int[] attrib_list_gl20 = {EGL_CONTEXT_CLIENT_VERSION, 2,
                    EGL10.EGL_NONE};
                context = egl.eglCreateContext(display, eglConfig,
                    EGL10.EGL_NO_CONTEXT, attrib_list_gl20);
            }
            else
            {
                DebugLog.LOGI("Creating OpenGL ES 1.x context");
                checkEglError("Before eglCreateContext", egl);
                int[] attrib_list_gl1x = {EGL_CONTEXT_CLIENT_VERSION, 1,
                    EGL10.EGL_NONE};
                context = egl.eglCreateContext(display, eglConfig,
                    EGL10.EGL_NO_CONTEXT, attrib_list_gl1x);
            }

            checkEglError("After eglCreateContext", egl);
            return context;
        }

        public void destroyContext(EGL10 egl, EGLDisplay display,
            EGLContext context)
        {
            egl.eglDestroyContext(display, context);
        }
    }


    /** Checks the OpenGL error. */
    private static void checkEglError(String prompt, EGL10 egl)
    {
        int error;
        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS)
        {
            DebugLog.LOGE(String.format("%s: EGL error: 0x%x", prompt, error));
        }
    }


    /** The config chooser. */
    private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser
    {
        public ConfigChooser(int r, int g, int b, int a, int depth, int stencil)
        {
            mRedSize = r;
            mGreenSize = g;
            mBlueSize = b;
            mAlphaSize = a;
            mDepthSize = depth;
            mStencilSize = stencil;
        }


        private EGLConfig getMatchingConfig(EGL10 egl, EGLDisplay display,
            int[] configAttribs)
        {
            // Get the number of minimally matching EGL configurations
            int[] num_config = new int[1];
            egl.eglChooseConfig(display, configAttribs, null, 0, num_config);

            int numConfigs = num_config[0];
            if (numConfigs <= 0)
                throw new IllegalArgumentException("No matching EGL configs");

            // Allocate then read the array of minimally matching EGL configs
            EGLConfig[] configs = new EGLConfig[numConfigs];
            egl.eglChooseConfig(display, configAttribs, configs, numConfigs,
                num_config);

            // Now return the "best" one
            return chooseConfig(egl, display, configs);
        }


        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display)
        {
            if (mUseOpenGLES2)
            {
                // This EGL config specification is used to specify 2.0
                // rendering. We use a minimum size of 4 bits for
                // red/green/blue, but will perform actual matching in
                // chooseConfig() below.
                final int EGL_OPENGL_ES2_BIT = 0x0004;
                final int[] s_configAttribs_gl20 =
                {
                    EGL10.EGL_RED_SIZE, 4,
                    EGL10.EGL_GREEN_SIZE, 4,
                    EGL10.EGL_BLUE_SIZE, 4,
                    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
                    EGL10.EGL_NONE
                };

                return getMatchingConfig(egl, display, s_configAttribs_gl20);
            }
            else
            {
                final int EGL_OPENGL_ES1X_BIT = 0x0001;
                final int[] s_configAttribs_gl1x =
                {
                    EGL10.EGL_RED_SIZE, 5,
                    EGL10.EGL_GREEN_SIZE, 6,
                    EGL10.EGL_BLUE_SIZE, 5,
                    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES1X_BIT,
                    EGL10.EGL_NONE
                };

                return getMatchingConfig(egl, display, s_configAttribs_gl1x);
            }
        }


        public EGLConfig chooseConfig(
            EGL10 egl, EGLDisplay display, EGLConfig[] configs)
        {
            for(EGLConfig config : configs)
            {
                int d = findConfigAttrib(egl, display, config,
                        EGL10.EGL_DEPTH_SIZE, 0);
                int s = findConfigAttrib(egl, display, config,
                        EGL10.EGL_STENCIL_SIZE, 0);

                // We need at least mDepthSize and mStencilSize bits
                if (d < mDepthSize || s < mStencilSize)
                    continue;

                // We want an *exact* match for red/green/blue/alpha
                int r = findConfigAttrib(egl, display, config,
                        EGL10.EGL_RED_SIZE, 0);
                int g = findConfigAttrib(egl, display, config,
                            EGL10.EGL_GREEN_SIZE, 0);
                int b = findConfigAttrib(egl, display, config,
                            EGL10.EGL_BLUE_SIZE, 0);
                int a = findConfigAttrib(egl, display, config,
                        EGL10.EGL_ALPHA_SIZE, 0);

                if (r == mRedSize &&
                    g == mGreenSize &&
                    b == mBlueSize &&
                    a == mAlphaSize)
                    return config;
            }

            return null;
        }


        private int findConfigAttrib(
            EGL10 egl, EGLDisplay display, EGLConfig config, int attribute,
            int defaultValue)
        {

            if (egl.eglGetConfigAttrib(display, config, attribute, mValue))
                return mValue[0];

            return defaultValue;
        }


        // Subclasses can adjust these values:
        protected int mRedSize;
        protected int mGreenSize;
        protected int mBlueSize;
        protected int mAlphaSize;
        protected int mDepthSize;
        protected int mStencilSize;
        private int[] mValue = new int[1];
    }
}

首先在這Code當中,主要是用來Initialization GLView,也許是因為OpenGL ES版本差異?Initialization主要有三件事情:

  • 偵測並確認OpenGL ES版本
  • EGLContextFactory
  • EGLConfigChooser

根據尋找的結果是,在OpenGL當中,在進行Render(渲染?)需要先進行EGLContextFactoryEGLConfigChooser,什麼是Render?可以參考一下渲染-Wiki,主要就是:幾何、視點、紋理以及照明訊息。老實說,完全不明白…看樣子需要去學個電腦圖學會更清楚才對。說真的OpenGL的程式,真的跟天書一樣…後來了解到,這只是設定OpenGL ES所可能需要用到的東西,大概是因為需要進行3D的貼圖吧!所以才需要這些幾何、視點等資料,為了讓Render的物件更逼真!?後來看到主程式的Code大概也說明了這件事情。

ImageTargets.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    private void initApplicationAR()
    {
        // Do application initialization in native code (e.g. registering
        // callbacks, etc.):
        initApplicationNative(mScreenWidth, mScreenHeight);

        // Create OpenGL ES view:
        int depthSize = 16;
        int stencilSize = 0;
        boolean translucent = QCAR.requiresAlpha();

        mGlView = new QCARSampleGLView(this);
        mGlView.init(mQCARFlags, translucent, depthSize, stencilSize);

        mRenderer = new ImageTargetsRenderer();
        mRenderer.mActivity = this;
        mGlView.setRenderer(mRenderer);

        LayoutInflater inflater = LayoutInflater.from(this);
        mUILayout = (RelativeLayout) inflater.inflate(R.layout.camera_overlay,
                null, false);

        mUILayout.setVisibility(View.VISIBLE);
        mUILayout.setBackgroundColor(Color.BLACK);

        // Gets a reference to the loading dialog
        mLoadingDialogContainer = mUILayout.findViewById(R.id.loading_indicator);

        // Shows the loading indicator at start
        loadingDialogHandler.sendEmptyMessage(SHOW_LOADING_DIALOG);

        // Adds the inflated layout to the view
        addContentView(mUILayout, new LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT));
    }

還有另外一個Code專門處理Render,在這主要的initApplicationAR當中,先建立一個GLView然後是先前貼的QCARSampleGLView.java接著在initialization放入需要的參數。接著建立RendererCodeImageTargetsRenderer.java寫到這邊我突然很想要把這些Code直接複製就好了,感覺應該是要可以通用才對。總之,建立完GLView需要加入Renderer而在ImageTargetsRenderer.java當中,在繪圖的functionrenderFrame()這部分是Native code之後會再繼續補充。最後會把GLView加入到ContentView當中。 addContentView(mGlView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

ImageTargetsRenderer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/*==============================================================================
            Copyright (c) 2010-2012 QUALCOMM Austria Research Center GmbH.
            All Rights Reserved.
            Qualcomm Confidential and Proprietary

@file
    ImageTargetsRenderer.java

@brief
    Sample for ImageTargets

==============================================================================*/


package com.qualcomm.QCARSamples.ImageTargets;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;

import com.qualcomm.QCAR.QCAR;


/** The renderer class for the ImageTargets sample. */
public class ImageTargetsRenderer implements GLSurfaceView.Renderer
{
    public boolean mIsActive = false;

    /** Reference to main activity **/
    public ImageTargets mActivity;

    /** Native function for initializing the renderer. */
    public native void initRendering();

    /** Native function to update the renderer. */
    public native void updateRendering(int width, int height);


    /** Called when the surface is created or recreated. */
    public void onSurfaceCreated(GL10 gl, EGLConfig config)
    {
        DebugLog.LOGD("GLRenderer::onSurfaceCreated");

        // Call native function to initialize rendering:
        initRendering();

        // Call QCAR function to (re)initialize rendering after first use
        // or after OpenGL ES context was lost (e.g. after onPause/onResume):
        QCAR.onSurfaceCreated();
    }


    /** Called when the surface changed size. */
    public void onSurfaceChanged(GL10 gl, int width, int height)
    {
        DebugLog.LOGD("GLRenderer::onSurfaceChanged");

        // Call native function to update rendering when render surface
        // parameters have changed:
        updateRendering(width, height);

        // Call QCAR function to handle render surface size changes:
        QCAR.onSurfaceChanged(width, height);
    }


    /** The native render function. */
    public native void renderFrame();


    /** Called to draw the current frame. */
    public void onDrawFrame(GL10 gl)
    {
        if (!mIsActive)
            return;

        // Update render view (projection matrix and viewport) if needed:
        mActivity.updateRenderView();

        // Call our native function to render content
        renderFrame();
    }
}

最後我決定,我把這些現有的Code都先複製好了。感覺應該是可以重複使用才對,剩下的就是jni的部分了!讓問題變簡單一點。等之後對OpenGL有更新的認識時,就可以回來補充了!

Comments