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;
}
-
初始化相机流程:
- 打开相机
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; }
- 打开相机
-
加载识别目标图片
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; }
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
: 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()
取消设备传感器的监听
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_);
}
-
初始化 opengl 程序
void Renderer::init() { 1. 创建 gl 程序 2. 创建顶点渲染器,并编译 3. 创建片元渲染器,并编译 4. 绑定渲染器,链接使用 5. 绑定顶点、颜色、投影矩阵、模型变换矩阵 6. 创建模型的顶点坐标值,颜色坐标值 }
初始化 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 实现的功能
提供跟踪器,利用输入的图像目标,实时监测相机产生的帧数据,并识别帧数据中的目标对象
-
结合手机的传感器(
EasyAR.onResume
添加监听)、相机标定、相机产生的帧数据、识别出来的目标对象,生成虚拟场景的投影矩阵和对目标模型的模型变换矩阵红框为识别的目标对象
蓝框为构建的长方体模型(无光照、无法向)
提供渲染器 Argument 显示相机产生的帧数据
-
支持创建一个或多个跟踪器 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 收费版本 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));
-
初始化 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); } @Override protected 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- 创建跟踪器 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();
}
-
停止跟踪器,相机,帧生成器
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; } }
2.4 场景渲染流程
-
初始化 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 这种较高端的机器
参考
- EasyAR 开发文档