@zyl06
2017-10-22T12:43:12.000000Z
字数 9123
阅读 2102
Android
AR
以下 Target Image 识别部分按照 2.0.0 基础版本解析
平面监测 Demo 按 2.1.0 Pro 版本解析
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);
}
}
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;
}
初始化相机流程:
camera
tracker_
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]);
}
@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()
取消设备传感器的监听
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);
}
@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
public class GLView extends GLSurfaceView {
@Override
public void onResume() {
super.onResume();
Engine.onResume();
}
@Override
public 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 这种较高端的机器