[关闭]
@zyl06 2017-10-22T12:43:12.000000Z 字数 9123 阅读 2102

Android EasyAR demo 简析

Android AR


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 初始化

  1. public class MainActivity extends ActionBarActivity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. ...
  5. EasyAR.initialize(this, key);
  6. nativeInit();
  7. GLView glView = new GLView(this);
  8. glView.setRenderer(new Renderer());
  9. glView.setZOrderMediaOverlay(true);
  10. ((ViewGroup) findViewById(R.id.preview)).addView(glView,
  11. new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
  12. nativeRotationChange(getWindowManager().getDefaultDisplay().getRotation() == android.view.Surface.ROTATION_0 ||
  13. getWindowManager().getDefaultDisplay().getRotation() == Surface.ROTATION_180);
  14. }
  15. }

1.1.1 EasyAR 初始化

  1. EasyAR.initialize(this, key)

其中 keyEasyAR 应用创建 里面申请获取

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

  1. EasyAR::samples::HelloAR ar;
  2. JNIEXPORT jboolean JNICALL JNIFUNCTION_NATIVE(nativeInit(JNIEnv*, jobject)) {
  3. bool status = ar.initCamera();
  4. ar.loadFromJsonFile("targets.json", "argame");
  5. ar.loadFromJsonFile("targets.json", "idback");
  6. ar.loadAllFromJsonFile("targets2.json");
  7. ar.loadFromImage("namecard.jpg");
  8. status &= ar.start();
  9. return status;
  10. }
  1. 初始化相机流程:

    • 打开相机 camera
    • 设置相机分辨率大小
    • 将相机绑定到场景跟踪器上 tracker_
    • 设置 tracker_ 可同时识别的目标数目为 4
    • 绑定相机至 augmenter_ (渲染器)。从 tracker_ 获取图像帧,然后将 camera 的图像作为 AR 场景的背景渲染出来。通常在渲染线程中使用
    1. bool AR::initCamera() {
    2. bool status = true;
    3. status &= camera_.open();
    4. camera_.setSize(Vec2I(1280, 720));
    5. status &= tracker_.attachCamera(camera_);
    6. tracker_.setSimultaneousNum(4);
    7. status &= augmenter_.attachCamera(camera_);
    8. return status;
    9. }
  2. 加载识别目标图片

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

    最终调用的是 EasyAR

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

    其中 storageType 的枚举使用查看 StorageType

    目标对象的 json 内容

    1. {
    2. "images" :
    3. [
    4. {
    5. "image" : "sightplus/argame00.jpg",
    6. "name" : "argame"
    7. },
    8. {
    9. "image" : "idback.jpg",
    10. "name" : "idback",
    11. "size" : [8.56, 5.4],
    12. "uid" : "uid-string"
    13. }
    14. ]
    15. }
  3. 启动相机和场景跟踪器

    1. bool AR::start() {
    2. bool status = true;
    3. status &= camera_.start();
    4. // 设置相机为连续对焦
    5. camera_.setFocusMode(CameraDevice::kFocusModeContinousauto);
    6. status &= tracker_.start();
    7. return status;
    8. }

1.1.3 添加视图

  1. GLView glView = new GLView(this);
  2. glView.setRenderer(new Renderer());
  3. glView.setZOrderMediaOverlay(true);
  4. ((ViewGroup) findViewById(R.id.preview)).addView(glView,
  5. new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

1.1.4 标记屏幕方向

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

C++ 层做好标记,用于后续 OpenGL 中渲染过程中计算视口大小 viewport_

  1. void AR::resizeGL(int width, int height) {
  2. Vec2I size = Vec2I(1, 1);
  3. if(camera_.isOpened())
  4. size = camera_.size();
  5. if (size[0] == 0 || size[1] == 0)
  6. return;
  7. if (portrait_)
  8. std::swap(size[0], size[1]);
  9. float scaleRatio = std::max((float)width / (float)size[0], (float)height / (float)size[1]);
  10. Vec2I viewport_size = Vec2I((int)(size[0] * scaleRatio), (int)(size[1] * scaleRatio));
  11. viewport_ = Vec4I(0, height - viewport_size[1], viewport_size[0], viewport_size[1]);
  12. }

1.2 处理页面生命周期

  1. @Override
  2. protected void onResume() {
  3. super.onResume();
  4. EasyAR.onResume();
  5. }
  6. @Override
  7. protected void onPause() {
  8. super.onPause();
  9. EasyAR.onPause();
  10. }

EasyAR: onResumeonPause 方法的内部实现如下:

  1. public class EasyAR {
  2. public static void onResume() {
  3. EasyARNative.onResume();
  4. if(orientationEventListener != null) {
  5. orientationEventListener.enable();
  6. }
  7. }
  8. public static void onPause() {
  9. EasyARNative.onPause();
  10. if(orientationEventListener != null) {
  11. orientationEventListener.disable();
  12. }
  13. }
  14. }

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

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

1.3 实现渲染器

  1. public class Renderer implements GLSurfaceView.Renderer {
  2. public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  3. MainActivity.nativeInitGL();
  4. }
  5. public void onSurfaceChanged(GL10 gl, int w, int h) {
  6. MainActivity.nativeResizeGL(w, h);
  7. }
  8. public void onDrawFrame(GL10 gl) {
  9. MainActivity.nativeRender();
  10. }
  11. }

1.3.1 初始化 OpenGL

MainActivity.nativeInitGL() 最终会调用 C++ 代码

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

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

1.3.2 保存 GLSurfaceView 视窗变化

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

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

1.3.3 渲染 OpenGL 场景

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

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

1.4 EasyAR SDK 实现的功能

  1. 提供跟踪器,利用输入的图像目标,实时监测相机产生的帧数据,并识别帧数据中的目标对象
  2. 结合手机的传感器(EasyAR.onResume 添加监听)、相机标定、相机产生的帧数据、识别出来的目标对象,生成虚拟场景的投影矩阵和对目标模型的模型变换矩阵

    image

    image

    红框为识别的目标对象

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

  3. 提供渲染器 Argument 显示相机产生的帧数据
  4. 支持创建一个或多个跟踪器 Tracker,并分别设置同时可识别的目标对象和数目,不过识别数目越多,消耗越大,效果越差

    1. status &= tracker_.attachCamera(camera_);
    2. status &= tracker2_.attachCamera(camera_);
    3. tracker_.setSimultaneousNum(1);
    4. tracker2_.setSimultaneousNum(2);
  5. 提供 VideoPlayer 类,支持取出每一帧用于 OpenGL 纹理贴图

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

    支持传入生成的纹理 id,设置给 VideoPlayer,内部完成绑定逻辑

    gif

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

    1. barcode_index = frame.index();
    2. std::string text = frame.text();
    3. if (!text.empty()) {
    4. LOGI("got qrcode: %s", text.c_str());
    5. }

    gif

  7. 模型构建渲染、动画、选择等功能完全交由用户,提供了较大的自由度

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

2 收费版本 v2.1

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

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

2.1 初始化

  1. if (!Engine.initialize(this, key)) {
  2. Log.e("HelloAR", "Initialization Failed.");
  3. }
  4. glView = new GLView(this);
  5. ...
  6. ViewGroup preview = ((ViewGroup) findViewById(R.id.preview));
  7. 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

    1. public class GLView extends GLSurfaceView {
    2. public GLView(Context context) {
    3. setEGLContextFactory(new ContextFactory());
    4. setEGLConfigChooser(new ConfigChooser());
    5. helloAR = new HelloAR();
    6. this.setRenderer(...);
    7. this.setZOrderMediaOverlay(true);
    8. }
    9. @Override
    10. protected void onAttachedToWindow() {
    11. super.onAttachedToWindow();
    12. synchronized (helloAR) {
    13. if (helloAR.initialize()) {
    14. helloAR.start();
    15. }
    16. }
    17. }
    18. }
  3. 初始化相机、相机帧生成器

    1. public class HelloAR {
    2. public boolean initialize() {
    3. camera = new CameraDevice();
    4. streamer = new CameraFrameStreamer();
    5. streamer.attachCamera(camera);
    6. boolean status = true;
    7. status &= camera.open(CameraDeviceType.Default);
    8. camera.setSize(new Vec2I(1280, 720));
    9. return status;
    10. }
    11. }
    • 创建相机和渲染器对象,并绑定相机至渲染器,用于显示相机视频帧
    • 打开相机,并设置分辨率
  4. 开启相机和相机帧生成器

    1. public boolean start() {
    2. boolean status = true;
    3. status &= (camera != null) && camera.start();
    4. status &= (streamer != null) && streamer.start();
    5. camera.setFocusMode(CameraDeviceFocusMode.Continousauto);
    6. return status;
    7. }

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

  5. 点击开启跟踪器

    1. public boolean startTracker() {
    2. boolean status = true;
    3. if (tracker != null) {
    4. tracker.stop();
    5. tracker.dispose();
    6. }
    7. tracker = new ARSceneTracker();
    8. tracker.attachStreamer(streamer);
    9. if (tracker != null) {
    10. status &= tracker.start();
    11. }
    12. return status;
    13. }

    streamer: CameraFrameStreamer
    tracker: ARSceneTracker

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

2.2 处理页面生命周期

  1. public class GLView extends GLSurfaceView {
  2. @Override
  3. public void onResume() {
  4. super.onResume();
  5. Engine.onResume();
  6. }
  7. @Override
  8. public void onPause() {
  9. Engine.onPause();
  10. super.onPause();
  11. }
  12. ...
  13. }

处理逻辑同 1.2

2.3 停止或销毁

2.3.1 停止跟踪器

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

  1. public boolean stopTracker() {
  2. boolean status = true;
  3. if (tracker != null) {
  4. status &= tracker.stop();
  5. tracker.dispose();
  6. tracker = null;
  7. }
  8. return status;
  9. }

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

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

    1. public boolean stop() {
    2. boolean status = true;
    3. if (tracker != null) {
    4. status &= tracker.stop();
    5. }
    6. status &= (streamer != null) && streamer.stop();
    7. status &= (camera != null) && camera.stop();
    8. return status;
    9. }
  2. 销毁跟踪器,相机,帧生成器,OpenGL 场景渲染器

    1. public void dispose() {
    2. if (tracker != null) {
    3. tracker.dispose();
    4. tracker = null;
    5. }
    6. box_renderer = null;
    7. if (videobg_renderer != null) {
    8. videobg_renderer.dispose();
    9. videobg_renderer = null;
    10. }
    11. if (streamer != null) {
    12. streamer.dispose();
    13. streamer = null;
    14. }
    15. if (camera != null) {
    16. camera.dispose();
    17. camera = null;
    18. }
    19. }

2.4 场景渲染流程

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

    1. public void initGL() {
    2. if (videobg_renderer != null) {
    3. videobg_renderer.dispose();
    4. }
    5. videobg_renderer = new Renderer();
    6. box_renderer = new BoxRenderer();
    7. box_renderer.init();
    8. }

    videobg_renderer:相机帧背景渲染器
    box_renderer:模型渲染器

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

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

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

    逻辑同 1.3.3

    效果如图

    gif

    红米note 4;

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

    gif

    Google Pixel

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

参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注