文章目錄
  1. 1. Markless-AR原理
  2. 2. Android端移植
    1. 2.1. Android端算法移植的方法
    2. 2.2. Android Studio环境的配置
    3. 2.3. OpenCV的使用
    4. 2.4. Markless-AR的移植


作者:Frank
时间:2016-12-30

之前一直在看C++11的书籍和DSO的论文和源码分析,最近两天张罗着写写代码,刚好看到了关于Markless-AR的一些东西,所以就对其中的原理进行了分析,刚好发现Android Studio从2.2开始引入了CMAKE编译,所以就在Android端实现了一份简易的Markless-AR的演示程序,也算是对如何在Android Studio上使用Opencv等三方库的一个总结吧。

Markless-AR原理

Markless-AR是利用Opencv中的特征检测和特征匹配结合KNN所实现的一个简易的增强显示展示程序。其基本流程如下图所示:

  1. 模式图像选取和训练
    该算法需要首先输入一帧模式图像,该图片即为待检测的特征图像,算法会首先对模式图像进行特征提取,得到特征点后对特征点进行描述子计算,这里利用的特征检测算子为ORB,而描述子为BRIEF。其基本原理在之前的博客中已经讲过了,这里就不再赘述。在得到模式图像的描述子后,利用OpenCV自带的DescriptorMatch来对描述子进行训练,得到关于模板图像的训练集,算是基本数据库吧,由此,算法的基础就准备好了。

  2. 当前帧处理
    当新的图像进入算法时,同样会先对图像进行特征检测和描述子的计算,得到描述子后会利用DescriptorMatcher的knnMatch方法来将当前帧的描述子和模板图像描述子训练的特征库进行比对,来判定当前帧和模板帧是否存在重合部分,这基本算是一个特征匹配的过程,只不过是利用当前图像和设定好的模板图像进行匹配罢了。

  3. 提纯特征描述子
    当检测到当前帧和模板帧存在重合部分后,利用两帧间的描述子来计算两帧间的单应矩阵变换,利用该变换来对当前得到的匹配子进行过滤,去掉其中存在的错误的匹配点,这部分还存在一个二次单应矩阵变换,即是在第一个单应矩阵计算成功的基础上再进行一个单应矩阵的计算和匹配点的过滤,以尽可能的得到最佳的匹配点,这部分有点类似于提前的RANSAC的步骤;

  4. 相机位姿计算
    当得到最佳的匹配点时,利用这些匹配点来直接进行相机位姿的计算,这里计算的是基础矩阵,利用OpenCV自带的solvePnP即可实现。这里经过计算后就可以得到当前帧和模式帧之间的相对相机位姿,这也是用来叠加增强现实场景的基本所在;

  5. 场景叠加
    利用相机位姿可以构造OpenGL中的视景矩阵,视景矩阵可以将OpenGL中的场景进行矩阵变换,来得到当前相机的坐标系,其基本用法如下:

1
2
3
4
5
glMatrixMode(GL_PROJECTION);//加载投影矩阵
glLoadMatrixf(projectionMatrix.data);

glMatrixModruxiae(GL_MODELVIEW);//加载视景矩阵。
glLoadIdentity();

之后就可以在当前帧下叠加一些基本的增强现实场景,这里只做了基本的立方体的叠加。

以下是利用该算法在Ubuntu下实现的提取和计算:
当前帧:

匹配图像:

增强现实图像:

Android端移植

该项目除了试验基本的增强现实叠加方法之外,更重要的是展示如何利用Android Studio对一些主流的项目进行移植。因此,该部分将对会其中涉及的技术进行具体的讲解。

Android端算法移植的方法

如果想在Android端实现和PC端相同的算法,一般来说主要有两种方式,一种是将当前算法完全以Java版本重写一遍以实现在Android端的运行,该方法对于一些简单的算法可能还行之有效,但对于复杂的算法,特别是存在三方依赖库的复杂算法,其基本是不可实现的,在SLAM领域,存在众多的三方依赖库,如矩阵处理库Eigen,Sophus,图优化库G2O等,这些库都是SLAM中最基础的依赖,实现方式为C++ ,想要将其改写为Java版本,工作量是非常大的,得不偿失。这就自然而然的引出了NDK的概念。NDK是利用jni为桥梁,将Java端代码和native端的C++代码进行融合调用的方式,这样,我们就可以将利用C++实现的主体代码,开放接口函数,利用NDK为桥梁来供给Android端调用,而在Android端则只需要实现界面交互的主体逻辑即可,这样能极大的减少编码的复杂度,唯一的困难就是需要将C++代码的依赖库也同时移植到Android端,但相比直接改写而言已经简单了太多。
Android开发所用到的IDE主要有Eclipse和Android Studio,在AS出现之前,Eclipse一直都是Android开发的必备IDE,但Eclipse有些时候使用起来很不方便,所以现在的Android开发基本都在Android Studio上实现。本人限于电脑原因,一直使用的都是Eclipse,用起来也还凑合,在Eclipse上进行NDK开发主要是使用一种类CMAKE的Android.mk文件方式,基本思路和cmake的语法也相差不大,其基本用法可以参考我的ORBSLAM2_Android的实现:点这里。而在Android Studio中,一直使用的也是Android makefile的方式,直到AS2.2的出现,在AS2.2中,引入的一个重要的特性就是对Cmake的支持,这样对于原来熟悉Cmake开发的人员来说,就简便了很多。这里将会演示如何在AS中使用OpenCV和进行算法的移植。同时,我认为的另外一个重要的特性是Android Studio中增加了LLDB组件,用来支持对NDK中C++ 代码的单步调试,这就使得Android中native代码的调试非常的方便快捷,更是开发人员选择AS的不二选择

Android Studio环境的配置

关于Android环境的配置在网上可能一搜就有一大堆,这里也不会过多描述,只说明进行NDK和Cmake开发所需要的基本组件和步骤。
Android Studio在安装时就会默认需要下载Android的SDK manager,这个是对Android环境支持的最重要的组件,当SDK安装完成后,Android Studio就可以启动了,接下来说明如何实现对NDK和CMAKE的支持。
点击IDE菜单中的Tools->Android->Android SDK,将得到以下界面:

其中的三个选项分别为SDK platforms,SDK Tools和SDK Update Sites。SDK platforms主要是Google官方发布的各Android版本的SDK和一些编译工具及镜像,读者可以按照自己的需要来下载所需的SDK(或者你只想体验一把最新的Android系统:))而关于NDK和CMAKE的支持全部在SDK Tools选项中,其中,我们选择CMAKE,LLDB和NDK三项,点击Apply,就会下载对应的NDK工具包,native代码调试工具和CMAKE工具了。这样,就已经完成了NDK+CMAKE开发的基本环境搭建。
为了在自己的Android工程中实现对C++ 代码的支持,在Android Studio中新建项目时,需要勾选Include C++ Support选项,则会在你的app module下的main目录下生成对应的cpp文件夹,这里对应的就是原Eclipse中的jni文件夹,如下所示:


而在app目录下同样生成了CMakeLists.txt文件,其基本内容就是cmake的最小组成部分,以下贴出的是项目Markless-AR中的CMakeLists文件的基本设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

set(PROJECT_NAME native-lib)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE on)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")

set(PROJECT_PATH /home/lab213/AndroidStudioProjects/MarklessAR)
set(OPENCV_PATH /home/lab213/Documents/libraries/OpenCV-android-sdk)

list(APPEND LINK_LIBS "-lEGL;-lGLESv1_CM")


# add opencv library
add_library(lib_opencv SHARED IMPORTED)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${PROJECT_PATH}/app/src/main/jniLibs/${ANDROID_ABI}/libopencv_java.so)

find_library( log-lib log )

# add ml-ar library
SET(ML_CPP_ROOT ${PROJECT_PATH}/app/src/main/cpp)
list(APPEND ML_SOURCES
${ML_CPP_ROOT}/src/ARDrawingContext.cpp
${ML_CPP_ROOT}/src/CameraCalibration.cpp
${ML_CPP_ROOT}/src/GeometryTypes.cpp
${ML_CPP_ROOT}/src/ARPipeline.cpp
${ML_CPP_ROOT}/src/Pattern.cpp
${ML_CPP_ROOT}/src/PatternDetector.cpp)

list(APPEND ML_INCLUDES
${ML_CPP_ROOT}/include
${OPENCV_PATH}/sdk/native/jni/include)

include_directories(${ML_INCLUDES})
add_library(ml_ar SHARED ${ML_SOURCES})
target_link_libraries(ml_ar ${log-lib} lib_opencv ${LINK_LIBS})

# include_directories(${OPENCV_PATH}/sdk/native/jni/include)
add_library(${PROJECT_NAME} SHARED src/main/cpp/ml_ar.cpp )
target_link_libraries(${PROJECT_NAME} ${log-lib} ml_ar ${LINK_LIBS})

同时,在新建的工程中存在几个基本需要修改的地方,都在app下的build.gradle文件下,主要是两个:

1
2
compileSdkVersion 24
buildToolsVersion "25.0.2"

这里需要改成当前IDE中对应的版本。这样基本的Android+NDK+CMAKE工程就完成了

OpenCV的使用

OpenCV官方提供了对Android的支持,因此,需要先将OpenCV4Android下载下来,这里我使用的是OpenCV4Android2.4.11。该SDK提供了两个版本,JAVA版和C++ 版,JAVA版实现了OpenCV中大多数函数的java落地,并添加了对Android中基本组件和硬件的支持。而C++ 端则是基本移植了OpenCV本身的源码。因为代码中需要利用Java端的OpenCV进行摄像头的获取和参数的传递,而在native端则依赖OpenCV进行基础处理,因此这里将从两方面介绍。

  1. Android Module的引入
    在AS中,Module相当于原来Eclipse中的工程,也可以作为library来使用,因此,java版本的OpenCV引入是非常简单的,只需要点击File->New->Import Module,找到下载的OpenCV4Android目录下的sdk/java文件夹,点击ok即可引入,相关依赖AS会自动将其在build.gradle中生成。这样,你就可以在项目中自由的使用opencv的java端代码了。

  2. native端的引入
    在OpenCV4Android中,本身提供了已编译好的Android端so文件,因此,我们只需要将这些so文件引入即可,首先,我们可以在main文件夹下建立jniLibs文件夹,然后将OpenCV4Android下sdk/native/libs目录下的所有目录copy到jniLibs下即可,同时需要在CMakeList中引入OpenCV,基本代码如下:

    1
    2
    3
    # add opencv library
    add_library(lib_opencv SHARED IMPORTED)
    set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${PROJECT_PATH}/app/src/main/jniLibs/${ANDROID_ABI}/libopencv_java.so)

这里就将opencv直接作为了一个library供其他执行程序使用,同时,别忘记在使用时includeopencv的头文件,如下:

1
include_directories(${OPENCV_PATH}/sdk/native/jni/include)

最后一步,我们需要让工程读取到jniLibs目录,因此,需要在app的build.gradle中android部分引入如下内容:

1
2
3
4
5
sourceSets {
main {
jniLibs.srcDirs = ['/home/lab213/AndroidStudioProjects/MarklessAR/app/src/main/jniLibs']
}
}

如果你需要为CMake增加一些flags,也可以在build.gradle的android.defaultConfig中实现,如下:

1
2
3
4
5
6
7
8
9
10
android {
defaultConfig {
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
abiFilters 'armeabi', 'armeabi-v7a'
}
}
}
}

这样就可以在native端的代码中使用OpenCV了,同理,其他依赖库也可以通过该方式来实现移植。

Markless-AR的移植

因为OpenCV的依赖已经搞定,因此,只需要将Markless-AR的源码导入到cpp文件夹下,并按照上述CmakeLists.txt的方式编译即可,同时,为了实现java端对native端的调用,在java端定义了几个native函数并在native端进行了实现,其中涉及的原理在我ORBSLAM2_Android的博客中有讲述:传送门。基本实现过程也和ORBSLAM2类似,代码见我的github:https://github.com/FangGet/ORB_SLAM2_Android ,不再赘述。这里讲一下该app如何使用:当app打开后,界面如下:

任意点击,就进入了系统的主流程,左上角有个窗口显示了当前的图像帧,当摄像头对准你需要识别的物体后,点击以下小窗口,会对当前图像进行保存并作为算法的模板图像使用,界面如下:

之后就会对摄像头提取的每一帧进行处理,来得到相机的位姿,并在该位姿下进行简单的增强场景的展示,如下:

在移植中涉及的主要的就是native相关的几个函数,其java端声明在NDKLoader类下,而native端实现则在ml_ar.cpp文件下,都比较简单,有兴趣的可以去对应的文件看看写法。

题外话:这里只是在现实场景下叠加了一个简单的坐标轴和立方体,如果需要加载三维模型的童鞋可以参考以下几份代码:
https://github.com/takmin/OpenCV-Marker-less-AR
https://github.com/meiroo/opencv-markerless-AR-Mobile
这里提供了加载mqo文件的方法以及IOS端实现的代码,有兴趣的可以看一看。



转载请注明出处

文章目錄
  1. 1. Markless-AR原理
  2. 2. Android端移植
    1. 2.1. Android端算法移植的方法
    2. 2.2. Android Studio环境的配置
    3. 2.3. OpenCV的使用
    4. 2.4. Markless-AR的移植