@zyl06
2017-10-22T04:43:12.000000Z
字数 9123
阅读 2413
Android AR
以下 Target Image 识别部分按照 2.0.0 基础版本解析
平面监测 Demo 按 2.1.0 Pro 版本解析
public class MainActivity extends ActionBarActivity {@Overrideprotected 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);}}
EasyAR.initialize(this, key)
其中 key 在 EasyAR 应用创建 里面申请获取
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;}
初始化相机流程:
cameratracker_tracker_ 可同时识别的目标数目为 4augmenter_ (渲染器)。从 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;}
加载识别目标图片
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"}]}
启动相机和场景跟踪器
bool AR::start() {bool status = true;status &= camera_.start();// 设置相机为连续对焦camera_.setFocusMode(CameraDevice::kFocusModeContinousauto);status &= tracker_.start();return status;}
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() == 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]);}
@Overrideprotected void onResume() {super.onResume();EasyAR.onResume();}@Overrideprotected void onPause() {super.onPause();EasyAR.onPause();}
EasyAR: onResume 和 onPause 方法的内部实现如下:
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() 取消设备传感器的监听
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();}}
MainActivity.nativeInitGL() 最终会调用 C++ 代码
void HelloAR::initGL() {renderer.init();augmenter_ = Augmenter();augmenter_.attachCamera(camera_);}
初始化 opengl 程序
void Renderer::init() {1. 创建 gl 程序2. 创建顶点渲染器,并编译3. 创建片元渲染器,并编译4. 绑定渲染器,链接使用5. 绑定顶点、颜色、投影矩阵、模型变换矩阵6. 创建模型的顶点坐标值,颜色坐标值}
初始化 EasyAR 中的场景渲染器,并绑定相机
MainActivity.nativeResizeGL(w, h) 最终会调用如下方法
void HelloAR::resizeGL(int width, int height) {view_size = Vec2I(width, height);}
MainActivity.nativeRender() 最终会调用如下方法
void HelloAR::render() {1. 设置清空颜色为黑色2. 清空颜色缓冲区和深度缓冲区3. 从 EasyAR 的渲染器中获取当前帧,并设置视口大小,绘制相机视频帧作为背景4. 设置 OpenGL 视口大小5. 遍历得到当前帧中的跟踪到的目标对象(前面设置的图像目标,目标对象最多 4 个,`tracker_.setSimultaneousNum(4)` 决定)6. 从目标对象中获取投影矩阵(EasyAR 内部实现)和目标对象在虚拟世界的大小(x,y 轴尺寸)7. 根据相机标定获取模型变换矩阵(EasyAR 内部实现)8. 利用前面初始化的模型顶点坐标、颜色坐标,和各矩阵,使用 OpenGL 绘制模型}
结合手机的传感器(EasyAR.onResume 添加监听)、相机标定、相机产生的帧数据、识别出来的目标对象,生成虚拟场景的投影矩阵和对目标模型的模型变换矩阵
![]()
![]()
红框为识别的目标对象
蓝框为构建的长方体模型(无光照、无法向)
支持创建一个或多个跟踪器 Tracker,并分别设置同时可识别的目标对象和数目,不过识别数目越多,消耗越大,效果越差
status &= tracker_.attachCamera(camera_);status &= tracker2_.attachCamera(camera_);tracker_.setSimultaneousNum(1);tracker2_.setSimultaneousNum(2);
提供 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,内部完成绑定逻辑

提供 BarCodeScanner 类,支持从帧数据中解析得到二维码等数据(不解析代码)
barcode_index = frame.index();std::string text = frame.text();if (!text.empty()) {LOGI("got qrcode: %s", text.c_str());}

模型构建渲染、动画、选择等功能完全交由用户,提供了较大的自由度
需依赖 EasyAR 的 so,编写 C++ 层代码,因此在 AndroidStudio 上并没有现有的框架能集成第三方显示引擎
2.0 之后将部分 C++ 层的类封装到 java 层,为此不需要在 C 层编写代码处理模型构建渲染等逻辑,而可以直接使用 java 层的 android.opengl.GLES20
demo 解释以 HelloARSLAM 为例,SLAM 功能仅收费版本支持
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));
初始化 EasyAR SDK
Engine.initialize(this, key) 最终调用 EasyAR.initializeInner(...) 代码逻辑。逻辑类似 1.0 版本 EasyAR.initialize(...)
构建 GLView
public class GLView extends GLSurfaceView {public GLView(Context context) {setEGLContextFactory(new ContextFactory());setEGLConfigChooser(new ConfigChooser());helloAR = new HelloAR();this.setRenderer(...);this.setZOrderMediaOverlay(true);}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();synchronized (helloAR) {if (helloAR.initialize()) {helloAR.start();}}}}
初始化相机、相机帧生成器
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;}}
开启相机和相机帧生成器
public boolean start() {boolean status = true;status &= (camera != null) && camera.start();status &= (streamer != null) && streamer.start();camera.setFocusMode(CameraDeviceFocusMode.Continousauto);return status;}
其中设置相机对焦模式为 连续对焦。其他对焦模式参见 CameraDeviceFocusMode Enum
点击开启跟踪器
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
public class GLView extends GLSurfaceView {@Overridepublic void onResume() {super.onResume();Engine.onResume();}@Overridepublic void onPause() {Engine.onPause();super.onPause();}...}
处理逻辑同 1.2
对应开启跟踪器,停止跟踪器的逻辑如下:
public boolean stopTracker() {boolean status = true;if (tracker != null) {status &= tracker.stop();tracker.dispose();tracker = null;}return status;}
synchronized (helloAR) {helloAR.stop();helloAR.dispose();}
停止跟踪器,相机,帧生成器
public boolean stop() {boolean status = true;if (tracker != null) {status &= tracker.stop();}status &= (streamer != null) && streamer.stop();status &= (camera != null) && camera.stop();return status;}
销毁跟踪器,相机,帧生成器,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;}}
初始化 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
记录当前视图的大小,用于后续计算视口大小
public void resizeGL(int width, int height) {view_size = new Vec2I(width, height);viewport_changed = true;}
渲染视图
逻辑同 1.3.3
效果如图

红米note 4;
在较复杂背景平面效果较好,但平面监测功能看似较弱
![]()
Google Pixel
在背景平面简单的情况下,效果较差,即便是 Google Pixel 这种较高端的机器