Android EasyAR demo 简析

1.0 免费版本

EasyAR Demo 下载页面

基础版 Demo 2.1.0 下载地址

Pro 版 Demo 2.1.0 下载地址

EasyAR 历史版本下载页面

基础版 Demo 2.0.0 下载地址

以下 Target Image 识别部分按照 2.0.0 基础版本解析
平面监测 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)

其中 key 在 EasyAR 应用创建 里面申请获取

1.1.2 相机、跟踪器、渲染器初始化

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. 初始化相机流程:

    • 打开相机 camera
    • 设置相机分辨率大小
    • 将相机绑定到场景跟踪器上 tracker_
    • 设置 tracker_ 可同时识别的目标数目为 4
    • 绑定相机至 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. 加载识别目标图片

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

    最终调用的是 EasyAR

    ImageTarget target;
    target.load(path.c_str(), EasyAR::kStorageAssets, targetname.c_str());
    tracker_.loadTarget(target, new HelloCallBack()); // HelloCallBack 用于处理识别目标加载成功或失败
    
    virtual bool load(const char* path, int storageType, const char* name = 0);
    

    其中 storageType 的枚举使用查看 StorageType

    目标对象的 json 内容

    {
      "images" :
      [
        {
          "image" : "sightplus/argame00.jpg",
          "name" : "argame"
        },
        {
          "image" : "idback.jpg",
          "name" : "idback",
          "size" : [8.56, 5.4],
          "uid" : "uid-string"
        }
      ]
    }
    
  3. 启动相机和场景跟踪器

    bool AR::start() {
        bool status = true;
        status &= camera_.start();
        // 设置相机为连续对焦
        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 标记屏幕方向

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

C++ 层做好标记,用于后续 OpenGL 中渲染过程中计算视口大小 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 方法的内部实现如下:

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 猜测需要处理获取渲染上下文环境和执行渲染任务;EasyARNative.onPause() 猜测需要放开渲染上下文环境,并挂起渲染任务的操作。

orientationEventListener.enable() 添加设备传感器的监听,orientationEventListener.disable() 取消设备传感器的监听

1.3 实现渲染器

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() 最终会调用 C++ 代码

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

    void Renderer::init() {
        1. 创建 gl 程序
        2. 创建顶点渲染器,并编译
        3. 创建片元渲染器,并编译
        4. 绑定渲染器,链接使用
        5. 绑定顶点、颜色、投影矩阵、模型变换矩阵
        6. 创建模型的顶点坐标值,颜色坐标值
    }
    
  2. 初始化 EasyAR 中的场景渲染器,并绑定相机

1.3.2 保存 GLSurfaceView 视窗变化

MainActivity.nativeResizeGL(w, h) 最终会调用如下方法

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

1.3.3 渲染 OpenGL 场景

MainActivity.nativeRender() 最终会调用如下方法

void HelloAR::render() {
    1. 设置清空颜色为黑色
    2. 清空颜色缓冲区和深度缓冲区
    3. 从 EasyAR 的渲染器中获取当前帧,并设置视口大小,绘制相机视频帧作为背景
    4. 设置 OpenGL 视口大小
    5. 遍历得到当前帧中的跟踪到的目标对象(前面设置的图像目标,目标对象最多 4 个,`tracker_.setSimultaneousNum(4)` 决定)
    6. 从目标对象中获取投影矩阵(EasyAR 内部实现)和目标对象在虚拟世界的大小(x,y 轴尺寸)
    7. 根据相机标定获取模型变换矩阵(EasyAR 内部实现)
    8. 利用前面初始化的模型顶点坐标、颜色坐标,和各矩阵,使用 OpenGL 绘制模型
}

1.4 EasyAR SDK 实现的功能

  1. 提供跟踪器,利用输入的图像目标,实时监测相机产生的帧数据,并识别帧数据中的目标对象

  2. 结合手机的传感器(EasyAR.onResume 添加监听)、相机标定、相机产生的帧数据、识别出来的目标对象,生成虚拟场景的投影矩阵和对目标模型的模型变换矩阵

    Android EasyAR demo 简析_第1张图片
    image
    Android EasyAR demo 简析_第2张图片
    image

    红框为识别的目标对象

    蓝框为构建的长方体模型(无光照、无法向)

  3. 提供渲染器 Argument 显示相机产生的帧数据

  4. 支持创建一个或多个跟踪器 Tracker,并分别设置同时可识别的目标对象和数目,不过识别数目越多,消耗越大,效果越差

    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,设置给 VideoPlayer,内部完成绑定逻辑

    gif
  6. 提供 BarCodeScanner 类,支持从帧数据中解析得到二维码等数据(不解析代码)

    barcode_index = frame.index();
    std::string text = frame.text();
    if (!text.empty()) {
        LOGI("got qrcode: %s", text.c_str());
    }
    
    gif
  7. 模型构建渲染、动画、选择等功能完全交由用户,提供了较大的自由度

  8. 需依赖 EasyAR 的 so,编写 C++ 层代码,因此在 AndroidStudio 上并没有现有的框架能集成第三方显示引擎

2 收费版本 v2.1

2.0 之后将部分 C++ 层的类封装到 java 层,为此不需要在 C 层编写代码处理模型构建渲染等逻辑,而可以直接使用 java 层的 android.opengl.GLES20

demo 解释以 HelloARSLAM 为例,SLAM 功能仅收费版本支持

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) 最终调用 EasyAR.initializeInner(...) 代码逻辑。逻辑类似 1.0 版本 EasyAR.initialize(...)

  2. 构建 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. 初始化相机、相机帧生成器

    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;
        }
    }
    
    • 创建相机和渲染器对象,并绑定相机至渲染器,用于显示相机视频帧
    • 打开相机,并设置分辨率
  4. 开启相机和相机帧生成器

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

    其中设置相机对焦模式为 连续对焦。其他对焦模式参见 CameraDeviceFocusMode Enum

  5. 点击开启跟踪器

    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

    • 创建跟踪器 tracker
    • 绑定相机帧生成器,streamer 的输出图像将被 tracker 使用
    • tracker.start 开启跟踪
    • 之后可以通过 FrameStreamer.peek 来获取一帧 Frame。Frame 中包含当前的 camera 图像和跟踪到的对象
    • 这里的 tracker 的跟踪,已使用 SLAM 计算相关的场景信息

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 停止跟踪器

对应开启跟踪器,停止跟踪器的逻辑如下:

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

2.3.2 停止和销毁跟踪器、相机和帧生成器

synchronized (helloAR) {
    helloAR.stop();
    helloAR.dispose();
}
  1. 停止跟踪器,相机,帧生成器

    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. 销毁跟踪器,相机,帧生成器,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:相机帧背景渲染器
    box_renderer:模型渲染器

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

  2. 记录当前视图的大小,用于后续计算视口大小

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

    逻辑同 1.3.3

    效果如图

    gif

    红米note 4;

    在较复杂背景平面效果较好,但平面监测功能看似较弱

    gif

    Google Pixel

    在背景平面简单的情况下,效果较差,即便是 Google Pixel 这种较高端的机器

参考

  • EasyAR 开发文档

你可能感兴趣的:(Android EasyAR demo 简析)