Android EasyAR demo 簡析

1.0 免費(fèi)版本

EasyAR Demo 下載頁面

基礎(chǔ)版 Demo 2.1.0 下載地址

Pro 版 Demo 2.1.0 下載地址

EasyAR 歷史版本下載頁面

基礎(chǔ)版 Demo 2.0.0 下載地址

以下 Target Image 識別部分按照 2.0.0 基礎(chǔ)版本解析
平面監(jiān)測 Demo 按 2.1.0 Pro 版本解析

1.1 初始化

public class MainActivity extends ActionBarActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        
        ...

        EasyAR.initialize(this, key);
        nativeInit();

        GLView glView = new GLView(this);
        glView.setRenderer(new Renderer());
        glView.setZOrderMediaOverlay(true);

        ((ViewGroup) findViewById(R.id.preview)).addView(glView,
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        nativeRotationChange(getWindowManager().getDefaultDisplay().getRotation() == android.view.Surface.ROTATION_0 ||
                getWindowManager().getDefaultDisplay().getRotation() == Surface.ROTATION_180);
    }
}

1.1.1 EasyAR 初始化

EasyAR.initialize(this, key)

其中 keyEasyAR 應(yīng)用創(chuàng)建 里面申請獲取

1.1.2 相機(jī)、跟蹤器、渲染器初始化

EasyAR::samples::HelloAR ar;
JNIEXPORT jboolean JNICALL JNIFUNCTION_NATIVE(nativeInit(JNIEnv*, jobject)) {
    bool status = ar.initCamera();
    ar.loadFromJsonFile("targets.json", "argame");
    ar.loadFromJsonFile("targets.json", "idback");
    ar.loadAllFromJsonFile("targets2.json");
    ar.loadFromImage("namecard.jpg");
    status &= ar.start();
    return status;
}
  1. 初始化相機(jī)流程:

    • 打開相機(jī) camera
    • 設(shè)置相機(jī)分辨率大小
    • 將相機(jī)綁定到場景跟蹤器上 tracker_
    • 設(shè)置 tracker_ 可同時(shí)識別的目標(biāo)數(shù)目為 4
    • 綁定相機(jī)至 augmenter_ (渲染器)。從 tracker_ 獲取圖像幀,然后將 camera 的圖像作為 AR 場景的背景渲染出來。通常在渲染線程中使用
    bool AR::initCamera() {
        bool status = true;
        status &= camera_.open();
        camera_.setSize(Vec2I(1280, 720));
        status &= tracker_.attachCamera(camera_);
        tracker_.setSimultaneousNum(4);
        status &= augmenter_.attachCamera(camera_);
        return status;
    }
    
  2. 加載識別目標(biāo)圖片

    ar.loadFromJsonFile("targets.json", "argame");
    ar.loadFromJsonFile("targets.json", "idback");
    ar.loadAllFromJsonFile("targets2.json");
    ar.loadFromImage("namecard.jpg");
    

    最終調(diào)用的是 EasyAR

    ImageTarget target;
    target.load(path.c_str(), EasyAR::kStorageAssets, targetname.c_str());
    tracker_.loadTarget(target, new HelloCallBack()); // HelloCallBack 用于處理識別目標(biāo)加載成功或失敗
    
    virtual bool load(const char* path, int storageType, const char* name = 0);
    

    其中 storageType 的枚舉使用查看 StorageType

    目標(biāo)對象的 json 內(nèi)容

    {
      "images" :
      [
        {
          "image" : "sightplus/argame00.jpg",
          "name" : "argame"
        },
        {
          "image" : "idback.jpg",
          "name" : "idback",
          "size" : [8.56, 5.4],
          "uid" : "uid-string"
        }
      ]
    }
    
  3. 啟動(dòng)相機(jī)和場景跟蹤器

    bool AR::start() {
        bool status = true;
        status &= camera_.start();
        // 設(shè)置相機(jī)為連續(xù)對焦
        camera_.setFocusMode(CameraDevice::kFocusModeContinousauto);
        status &= tracker_.start();
        return status;
    }
    

1.1.3 添加視圖

GLView glView = new GLView(this);
glView.setRenderer(new Renderer());
glView.setZOrderMediaOverlay(true);

((ViewGroup) findViewById(R.id.preview)).addView(glView,
        new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

1.1.4 標(biāo)記屏幕方向

nativeRotationChange(getWindowManager().getDefaultDisplay().getRotation() == Surface.ROTATION_0 ||
        getWindowManager().getDefaultDisplay().getRotation() == Surface.ROTATION_180);
void AR::setPortrait(bool portrait) {
    portrait_ = portrait;
}

C++ 層做好標(biāo)記,用于后續(xù) OpenGL 中渲染過程中計(jì)算視口大小 viewport_

void AR::resizeGL(int width, int height) {
    Vec2I size = Vec2I(1, 1);
    if(camera_.isOpened())
        size = camera_.size();
    if (size[0] == 0 || size[1] == 0)
        return;
    if (portrait_)
        std::swap(size[0], size[1]);
    float scaleRatio = std::max((float)width / (float)size[0], (float)height / (float)size[1]);
    Vec2I viewport_size = Vec2I((int)(size[0] * scaleRatio), (int)(size[1] * scaleRatio));
    viewport_ = Vec4I(0, height - viewport_size[1], viewport_size[0], viewport_size[1]);
}

1.2 處理頁面生命周期

@Override
protected void onResume() {
    super.onResume();
    EasyAR.onResume();
}

@Override
protected void onPause() {
    super.onPause();
    EasyAR.onPause();
}

EasyAR: onResumeonPause 方法的內(nèi)部實(shí)現(xiàn)如下:

public class EasyAR {
    public static void onResume() {
        EasyARNative.onResume();
        if(orientationEventListener != null) {
            orientationEventListener.enable();
        }
    }

    public static void onPause() {
        EasyARNative.onPause();
        if(orientationEventListener != null) {
            orientationEventListener.disable();
        }
    }
}

EasyARNative.onResume 猜測需要處理獲取渲染上下文環(huán)境和執(zhí)行渲染任務(wù);EasyARNative.onPause() 猜測需要放開渲染上下文環(huán)境,并掛起渲染任務(wù)的操作。

orientationEventListener.enable() 添加設(shè)備傳感器的監(jiān)聽,orientationEventListener.disable() 取消設(shè)備傳感器的監(jiān)聽

1.3 實(shí)現(xiàn)渲染器

public class Renderer implements GLSurfaceView.Renderer {

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        MainActivity.nativeInitGL();
    }

    public void onSurfaceChanged(GL10 gl, int w, int h) {
        MainActivity.nativeResizeGL(w, h);
    }

    public void onDrawFrame(GL10 gl) {
        MainActivity.nativeRender();
    }
}

1.3.1 初始化 OpenGL

MainActivity.nativeInitGL() 最終會調(diào)用 C++ 代碼

void HelloAR::initGL() {
    renderer.init();
    augmenter_ = Augmenter();
    augmenter_.attachCamera(camera_);
}
  1. 初始化 opengl 程序

    void Renderer::init() {
        1. 創(chuàng)建 gl 程序
        2. 創(chuàng)建頂點(diǎn)渲染器,并編譯
        3. 創(chuàng)建片元渲染器,并編譯
        4. 綁定渲染器,鏈接使用
        5. 綁定頂點(diǎn)、顏色、投影矩陣、模型變換矩陣
        6. 創(chuàng)建模型的頂點(diǎn)坐標(biāo)值,顏色坐標(biāo)值
    }
    
  2. 初始化 EasyAR 中的場景渲染器,并綁定相機(jī)

1.3.2 保存 GLSurfaceView 視窗變化

MainActivity.nativeResizeGL(w, h) 最終會調(diào)用如下方法

void HelloAR::resizeGL(int width, int height) {
    view_size = Vec2I(width, height);
}

1.3.3 渲染 OpenGL 場景

MainActivity.nativeRender() 最終會調(diào)用如下方法

void HelloAR::render() {
    1. 設(shè)置清空顏色為黑色
    2. 清空顏色緩沖區(qū)和深度緩沖區(qū)
    3. 從 EasyAR 的渲染器中獲取當(dāng)前幀,并設(shè)置視口大小,繪制相機(jī)視頻幀作為背景
    4. 設(shè)置 OpenGL 視口大小
    5. 遍歷得到當(dāng)前幀中的跟蹤到的目標(biāo)對象(前面設(shè)置的圖像目標(biāo),目標(biāo)對象最多 4 個(gè),`tracker_.setSimultaneousNum(4)` 決定)
    6. 從目標(biāo)對象中獲取投影矩陣(EasyAR 內(nèi)部實(shí)現(xiàn))和目標(biāo)對象在虛擬世界的大?。▁,y 軸尺寸)
    7. 根據(jù)相機(jī)標(biāo)定獲取模型變換矩陣(EasyAR 內(nèi)部實(shí)現(xiàn))
    8. 利用前面初始化的模型頂點(diǎn)坐標(biāo)、顏色坐標(biāo),和各矩陣,使用 OpenGL 繪制模型
}

1.4 EasyAR SDK 實(shí)現(xiàn)的功能

  1. 提供跟蹤器,利用輸入的圖像目標(biāo),實(shí)時(shí)監(jiān)測相機(jī)產(chǎn)生的幀數(shù)據(jù),并識別幀數(shù)據(jù)中的目標(biāo)對象

  2. 結(jié)合手機(jī)的傳感器(EasyAR.onResume 添加監(jiān)聽)、相機(jī)標(biāo)定、相機(jī)產(chǎn)生的幀數(shù)據(jù)、識別出來的目標(biāo)對象,生成虛擬場景的投影矩陣和對目標(biāo)模型的模型變換矩陣

    image
    image

    紅框?yàn)樽R別的目標(biāo)對象

    藍(lán)框?yàn)闃?gòu)建的長方體模型(無光照、無法向)

  3. 提供渲染器 Argument 顯示相機(jī)產(chǎn)生的幀數(shù)據(jù)

  4. 支持創(chuàng)建一個(gè)或多個(gè)跟蹤器 Tracker,并分別設(shè)置同時(shí)可識別的目標(biāo)對象和數(shù)目,不過識別數(shù)目越多,消耗越大,效果越差

    status &= tracker_.attachCamera(camera_);
    status &= tracker2_.attachCamera(camera_);
    tracker_.setSimultaneousNum(1);
    tracker2_.setSimultaneousNum(2);
    
  5. 提供 VideoPlayer 類,支持取出每一幀用于 OpenGL 紋理貼圖

    void ARVideo::openVideoFile(const std::string& path, int texid) {
        if(!callback_)
            callback_ = new CallBack(this);
        path_ = path;
        player_.setRenderTexture(texid);
        player_.setVideoType(VideoPlayer::kVideoTypeNormal);
        player_.open(path.c_str(), kStorageAssets, callback_);
    }
    

    支持傳入生成的紋理 id,設(shè)置給 VideoPlayer,內(nèi)部完成綁定邏輯

    gif
  6. 提供 BarCodeScanner 類,支持從幀數(shù)據(jù)中解析得到二維碼等數(shù)據(jù)(不解析代碼)

    barcode_index = frame.index();
    std::string text = frame.text();
    if (!text.empty()) {
        LOGI("got qrcode: %s", text.c_str());
    }
    
    gif
  7. 模型構(gòu)建渲染、動(dòng)畫、選擇等功能完全交由用戶,提供了較大的自由度

  8. 需依賴 EasyAR 的 so,編寫 C++ 層代碼,因此在 AndroidStudio 上并沒有現(xiàn)有的框架能集成第三方顯示引擎

2 收費(fèi)版本 v2.1

2.0 之后將部分 C++ 層的類封裝到 java 層,為此不需要在 C 層編寫代碼處理模型構(gòu)建渲染等邏輯,而可以直接使用 java 層的 android.opengl.GLES20

demo 解釋以 HelloARSLAM 為例,SLAM 功能僅收費(fèi)版本支持

2.1 初始化

if (!Engine.initialize(this, key)) {
    Log.e("HelloAR", "Initialization Failed.");
}

glView = new GLView(this);

...
    
ViewGroup preview = ((ViewGroup) findViewById(R.id.preview));
preview.addView(glView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));  
  1. 初始化 EasyAR SDK

    Engine.initialize(this, key) 最終調(diào)用 EasyAR.initializeInner(...) 代碼邏輯。邏輯類似 1.0 版本 EasyAR.initialize(...)

  2. 構(gòu)建 GLView

    public class GLView extends GLSurfaceView {
    
        public GLView(Context context) {
            setEGLContextFactory(new ContextFactory());
            setEGLConfigChooser(new ConfigChooser());
    
            helloAR = new HelloAR();
    
            this.setRenderer(...);
            this.setZOrderMediaOverlay(true);
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            synchronized (helloAR) {
                if (helloAR.initialize()) {
                    helloAR.start();
                }
            }
        }
    }
    
  3. 初始化相機(jī)、相機(jī)幀生成器

    public class HelloAR {
    
        public boolean initialize() {
            camera = new CameraDevice();
            streamer = new CameraFrameStreamer();
            streamer.attachCamera(camera);
    
            boolean status = true;
            status &= camera.open(CameraDeviceType.Default);
            camera.setSize(new Vec2I(1280, 720));
    
            return status;
        }
    }
    
    • 創(chuàng)建相機(jī)和渲染器對象,并綁定相機(jī)至渲染器,用于顯示相機(jī)視頻幀
    • 打開相機(jī),并設(shè)置分辨率
  4. 開啟相機(jī)和相機(jī)幀生成器

    public boolean start() {
        boolean status = true;
        status &= (camera != null) && camera.start();
        status &= (streamer != null) && streamer.start();
        camera.setFocusMode(CameraDeviceFocusMode.Continousauto);
        return status;
    }
    

    其中設(shè)置相機(jī)對焦模式為 連續(xù)對焦。其他對焦模式參見 CameraDeviceFocusMode Enum

  5. 點(diǎn)擊開啟跟蹤器

    public boolean startTracker() {
        boolean status = true;
        if (tracker != null) {
            tracker.stop();
            tracker.dispose();
        }
        tracker = new ARSceneTracker();
        tracker.attachStreamer(streamer);
        if (tracker != null) {
            status &= tracker.start();
        }
        return status;
    }
    

    streamer: CameraFrameStreamer
    tracker: ARSceneTracker

    • 創(chuàng)建跟蹤器 tracker
    • 綁定相機(jī)幀生成器,streamer 的輸出圖像將被 tracker 使用
    • tracker.start 開啟跟蹤
    • 之后可以通過 FrameStreamer.peek 來獲取一幀 Frame。Frame 中包含當(dāng)前的 camera 圖像和跟蹤到的對象
    • 這里的 tracker 的跟蹤,已使用 SLAM 計(jì)算相關(guān)的場景信息

2.2 處理頁面生命周期

public class GLView extends GLSurfaceView {
    @Override
    public void onResume() {
        super.onResume();
        Engine.onResume();
    }
    
    @Override
    public void onPause() {
        Engine.onPause();
        super.onPause();
    }
    ...
}

處理邏輯同 1.2

2.3 停止或銷毀

2.3.1 停止跟蹤器

對應(yīng)開啟跟蹤器,停止跟蹤器的邏輯如下:

public boolean stopTracker() {
    boolean status = true;
    if (tracker != null) {
        status &= tracker.stop();
        tracker.dispose();
        tracker = null;
    }
    return status;
}

2.3.2 停止和銷毀跟蹤器、相機(jī)和幀生成器

synchronized (helloAR) {
    helloAR.stop();
    helloAR.dispose();
}
  1. 停止跟蹤器,相機(jī),幀生成器

    public boolean stop() {
        boolean status = true;
        if (tracker != null) {
            status &= tracker.stop();
        }
        status &= (streamer != null) && streamer.stop();
        status &= (camera != null) && camera.stop();
        return status;
    }
    
  2. 銷毀跟蹤器,相機(jī),幀生成器,OpenGL 場景渲染器

    public void dispose() {
        if (tracker != null) {
            tracker.dispose();
            tracker = null;
        }
        box_renderer = null;
        if (videobg_renderer != null) {
            videobg_renderer.dispose();
            videobg_renderer = null;
        }
        if (streamer != null) {
            streamer.dispose();
            streamer = null;
        }
        if (camera != null) {
            camera.dispose();
            camera = null;
        }
    }
    

2.4 場景渲染流程

  1. 初始化 OpenGL 背景和模型渲染器

    public void initGL() {
        if (videobg_renderer != null) {
            videobg_renderer.dispose();
        }
        videobg_renderer = new Renderer();
        box_renderer = new BoxRenderer();
        box_renderer.init();
    }
    

    videobg_renderer:相機(jī)幀背景渲染器
    box_renderer:模型渲染器

    其中 box_renderer.init() 處理流程同 1.3.1 初始化 OpenGL

  2. 記錄當(dāng)前視圖的大小,用于后續(xù)計(jì)算視口大小

    public void resizeGL(int width, int height) {
        view_size = new Vec2I(width, height);
        viewport_changed = true;
    }
    
  3. 渲染視圖

    邏輯同 1.3.3

    效果如圖

    gif

    紅米note 4;

    在較復(fù)雜背景平面效果較好,但平面監(jiān)測功能看似較弱

    gif

    Google Pixel

    在背景平面簡單的情況下,效果較差,即便是 Google Pixel 這種較高端的機(jī)器

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容