Android平台搭建和NDK环境配置
系统:windows7 32bit
IDE:Eclipse Luna
环境工具: ADT24.0.2、Android SDK、NDK r10b
PS:不推荐使用集成了ADT环境的Eclipse版本,因为在NDK编译的时候可能会报各种莫名其妙的错。同时默认JDK环境已经装好。
组件下载地址:百度网盘(包括ADT、NDK。SDK太大,请自行下载)
步骤:
- 安装Eclipse,注意区分Eclipse是32位还是64位的,下载对应版本安装即可。下载地址在前面给出。
- 下载ADT,不需要解压。打开安装好的Eclipse,在菜单栏点击Help–>Install new software,弹出如下界面:
-
点击右上角Add,出现:
第一栏随便填个名字,例如ADT,第二栏点击Archive,选择你下载的ADT压缩包,确定。在出现的列表中,全部选中,并取消勾选Contact all update sites….。点击next,则开始了ADT的安装,大概需要10分钟。可能还需要accept 协议什么的,这里略过不表。安装完成后提示重启Eclipse即安装成功。 - 下载Android SDK,并解压(路径不要有中文)。在Eclipse中选择Window–>preferences。在左边栏选中Android,在SDK Location中填入你解压文件夹的根目录,点击apply和OK,则Android环境配置基本完成。这是你的菜单栏应该有了这两项
- 更新SDK。点击Window–>Android SDK manager,弹出如下界面:
可能由于墙的问题会弹出列表无法加载,需要用代理或镜像,当然如果你有梯子也可以。这里介绍些镜像吧:
g.cn:80,在SDK Manager 中点击tools–>options:
按照上图设置即可。其他的镜像源还有北京化工大学镜像站,请自行谷狗。设置好后点击Packages–>reload即可得到对应的镜像。有默认选中的一些选项,一般选中一个sdk tools,一份sdk build-platform-tools,一个sdk build tools,然后一个Android版本即可(一般4.0以上)。选中后点击Install就会从镜像中下载对应的SDK,这个过程有长有短,视网络而定。
当上述步骤都完成后,Android开发环境就配置好了。 - NDK环境配置:从上面给的百度网盘中下载ndk,解压(规则同sdk),打开Eclipse,点击Window–>Preferences,选择Android–>NDK,在右边界面中填入NDK Location即可。
Android移植基础
NDK是集成的Android中调用C++代码的工具包,核心是JNI(Java Native Interface)技术,具体这里略过不表。只说说NDK开发的基本步骤:
1. 编写Java代码:在Java中定义一个类,比如说叫NDKHelper吧,里面定义几个java的方法,只需要声明,不需要实现,如下所示:
<code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NDKHelper</span> {</span> <span class="hljs-comment">//NDK示例方法1</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ndkOne</span>(<span class="hljs-keyword">int</span> a,<span class="hljs-keyword">long</span> b); <span class="hljs-comment">//NDK示例方法2</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">int</span> <span class="hljs-title">ndkTwo</span>(String a,String b); }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>
native标识符表示该函数将会利用C++代码完成实现。
接下来在工程上右键,Android Tools–>Add native support,出现如下界面:
名字就是最后我们要生成的库的名字,随便填,可修改。点击确定就会给你的工程添加C++编译支持,菜单栏会多了个小锤子:
这个是用来编译C++的快捷键。在你的工程目录下会新建jni目录和obj目录,其中jni目录用来存放和C++代码有关的东西,obj则存放C++进行编译时产生的中间件,最后生成的library会写入到libs文件夹下。
在jni文件夹中生成了如下文件,一个.cpp,一个Android.mk,其中.cpp是自动生成的,是用来编写C++部分的,而Android.mk类似C++里面的CMakeList,用来指定需要编译的文件和编译生成的模块名,一个最简单的Android.mk文件如下所示:
<code class="hljs ruby has-numbering"><span class="hljs-constant">LOCAL_PATH</span> <span class="hljs-symbol">:</span>= <span class="hljs-variable">$(</span>call my-dir) <span class="hljs-keyword">include</span> <span class="hljs-variable">$(</span><span class="hljs-constant">CLEAR_VARS</span>) <span class="hljs-constant">LOCAL_MODULE</span> <span class="hljs-symbol">:</span>= <span class="hljs-constant">NDKTest</span> <span class="hljs-constant">LOCAL_SRC_FILES</span> <span class="hljs-symbol">:</span>= <span class="hljs-constant">NDKTest</span>.cpp <span class="hljs-keyword">include</span> <span class="hljs-variable">$(</span><span class="hljs-constant">BUILD_SHARED_LIBRARY</span>)</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>
LOCAL_PATH :=$(call my-dir)表示包含当前目录。
include $(CLEAR_VARS)表示清除全部非系统变量和部分系统变量;
LOCAL_MODULE := NDKTest 表示当前生成的模块名,最终会生成libNDKTest.so文件
LOCAL_SRC_FILES := NDKTest.cpp 表示当前需要编译的cpp文件;
include $(BUILD_SHARED_LIBRARY) 表示生成共享库,需要生成静态库请修改成BUILD_STATIC_LIBRARY。
其他基础命令:
LOCAL_C_INCLUDES:= 表示添加头文件进入编译环境
LOCAL_LDLIBS:= 表示添加系统静态库
LOCAL_SHARED_LIBRARIES:= 表示添加共享库
其他命令请自行查看API文档。
这里指定了进行编译时的各项条件,如果需要指定编译器版本和编译目标平台等信息,则需要在jni目录下新建Application.mk文件,基本语句如下:
<code class="hljs makefile has-numbering"><span class="hljs-constant">APP_STL</span> := gnustl_static <span class="hljs-constant">APP_CPPFLAGS</span> := -frtti -fexceptions <span class="hljs-constant">NDK_TOOLCHAIN_VERSION</span> := 4.8 <span class="hljs-constant">APP_ABI</span> :=armeabi-v7a</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>
APP_STL :=表示使用stl库,APP_CPPFLAGS表示一些CPP编译参数,NDK_TOOLCHAIN_VERSION 表示NDK使用的编译器版本,APP_ABI表示编译的目标平台,可以指定多个平台,平台之间用空格隔开,或者指定all则为全平台编译(armeabi,armeabi-v7a,mips,x86)。其他命令请自行查看API。
接下来编写对应的C++文件。
打开eclipse,点击Project–>build Project(若build automatically已勾选则会自动编译)打开命令行,cd到你的工程文件夹下的bin–>classes文件夹下,输入如下命令:
<code class="hljs avrasm has-numbering">javah <span class="hljs-keyword">com</span><span class="hljs-preprocessor">.example</span><span class="hljs-preprocessor">.ndktest</span><span class="hljs-preprocessor">.NDKHelper</span></code><ul class="pre-numbering"><li>1</li></ul><ul class="pre-numbering"><li>1</li></ul>
回车,则在你的classes文件夹下会生成对应的头文件。这里com.example.ndktest是你的package名字,NDKHelper是你的NDK函数的类名。
生成的头文件如下:
<code class="hljs vala has-numbering"><span class="hljs-comment">/* DO NOT EDIT THIS FILE - it is machine generated */</span> <span class="hljs-preprocessor">#include <jni.h></span> <span class="hljs-comment">/* Header for class com_example_ndktest_NDKHelper */</span> <span class="hljs-preprocessor">#ifndef _Included_com_example_ndktest_NDKHelper</span> <span class="hljs-preprocessor">#define _Included_com_example_ndktest_NDKHelper</span> <span class="hljs-preprocessor">#ifdef __cplusplus</span> extern <span class="hljs-string">"C"</span> { <span class="hljs-preprocessor">#endif</span> <span class="hljs-comment">/* * Class: com_example_ndktest_NDKHelper * Method: ndkOne * Signature: (IJ)V */</span> JNIEXPORT <span class="hljs-keyword">void</span><span class="hljs-constant"> JNICALL </span>Java_com_example_ndktest_NDKHelper_ndkOne (JNIEnv *, jclass, jint, jlong); <span class="hljs-comment">/* * Class: com_example_ndktest_NDKHelper * Method: ndkTwo * Signature: (Ljava/lang/String;Ljava/lang/String;)I */</span> JNIEXPORT jint<span class="hljs-constant"> JNICALL </span>Java_com_example_ndktest_NDKHelper_ndkTwo (JNIEnv *, jclass, jstring, jstring); <span class="hljs-preprocessor">#ifdef __cplusplus</span> } <span class="hljs-preprocessor">#endif</span> <span class="hljs-preprocessor">#endif</span> </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li></ul>
其他不用管,我们关注中间的两个函数声明:
<code class="hljs cs has-numbering">JNIEXPORT <span class="hljs-keyword">void</span> JNICALL Java_com_example_ndktest_NDKHelper_ndkOne(JNIEnv *, jclass, jint, jlong);</code><ul class="pre-numbering"><li>1</li></ul><ul class="pre-numbering"><li>1</li></ul>
这个函数就是NDKHelper类中ndkOne函数对应的C++版本,其中JNIEXPORT和JNICALL是固定字段,void是函数返回值,函数名由Java字段+包名+类名+函数名组成,参数则多了几个JNI的系统参数JNIEnv 和jclass,其他的就是NDKHelper类中的对应参数,ndk会对该函数进行解析和链接,实现java和C++的对接。
将生成的.h头文件复制到jni目录下,新建对应的cpp文件,将该头文件include进来并对对应函数进行实现,实现过程就视函数功能而定。
这些工作完成后需要修改你的Android.mk文件,将刚刚新建的cpp和h文件包括进来。
然后点击开始那个小锤子或者直接项目右键RunAs–>Android Application,则C++部分会开始编译,编译具体过程可以在Eclipse下方Console窗口看到(如果没有Console窗口则点击Window–>Show Views,选择Console确定即可)。
编译完成后会生成对应的库存放在libs目录下,则你可以开始在Java里面调用刚才定义的ndkOne和ndkTwo函数实现具体的功能。
NDK基础到此为止,更深入的学习可以下载Android官方给的ndk samples.
ORB_SLAM2的移植
不想知道移植过程的童鞋可以直接下载我的Github源码:https://github.com/FangGet/ORB_SLAM2_Android 直接按照步骤进行即可。
移植过程
先看目录:
分为ORB和ThirdParty,其中ThirdParty包括boost clapack DBow2 g2o eigen3。
clapack和eigen来自于一个github的开源库:https://github.com/simonlynen/android_libs 这里集成了一些经典的C++库的ndk版本,下载即可使用。g2o和DBoW2则来自于ORB_SLAM2原作者的github地址,Boost是自己编译的lib,这里只介绍clapack和opencv的库配置。
clapack配置
从前述的开源库中将clapack目录拷贝到Thirdparty的对应目录下,clapack中已经包含了对当前目录极其子目录的编译过程,我们在jni目录下的Android.mk文件中加入如下内容:
<code class="hljs ruby has-numbering"><span class="hljs-keyword">include</span> <span class="hljs-variable">$(</span><span class="hljs-constant">CLEAR_VARS</span>) <span class="hljs-constant">MAINDIR</span><span class="hljs-symbol">:</span>= <span class="hljs-variable">$(</span><span class="hljs-constant">LOCAL_PATH</span>) <span class="hljs-keyword">include</span> <span class="hljs-variable">$(</span><span class="hljs-constant">MAINDIR</span>)/<span class="hljs-constant">Thirdparty</span>/clapack/<span class="hljs-constant">Android</span>.mk <span class="hljs-constant">LOCAL_PATH</span> <span class="hljs-symbol">:</span>= <span class="hljs-variable">$(</span><span class="hljs-constant">MAINDIR</span>) <span class="hljs-keyword">include</span> <span class="hljs-variable">$(</span><span class="hljs-constant">CLEAR_VARS</span>) <span class="hljs-constant">MAINDIR</span><span class="hljs-symbol">:</span>= <span class="hljs-variable">$(</span><span class="hljs-constant">LOCAL_PATH</span>) <span class="hljs-constant">LOCAL_MODULE</span><span class="hljs-symbol">:</span>= lapack <span class="hljs-constant">LOCAL_SHORT_COMMANDS</span> <span class="hljs-symbol">:</span>= <span class="hljs-keyword">true</span> <span class="hljs-constant">LOCAL_STATIC_LIBRARIES</span> <span class="hljs-symbol">:</span>= tmglib clapack blas f2c <span class="hljs-constant">LOCAL_EXPORT_C_INCLUDES</span> <span class="hljs-symbol">:</span>= <span class="hljs-variable">$(</span><span class="hljs-constant">LOCAL_C_INCLUDES</span>) <span class="hljs-constant">LOCAL_PATH</span> <span class="hljs-symbol">:</span>= <span class="hljs-variable">$(</span><span class="hljs-constant">MAINDIR</span>) <span class="hljs-keyword">include</span> <span class="hljs-variable">$(</span><span class="hljs-constant">BUILD_SHARED_LIBRARY</span>)</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li></ul>
这里的基本命令之前都已经讲过了,只补充如下几点内容:
- LOCAL_SHORT_COMMANDS是为了防止Windows对g++编译命令长度的限制而设置的参数,该参数会拖慢整个编译过程,因此请谨慎使用;
- LOCAL_EXPORT_C_INCLUDES表示将当前库的头文件EXPORT给系统,让程序代码中能实现<>的调用过程,若不设置这一参数则在cpp文件中可能无法引用该库;
- LOCAL_STATIC_LIBRARIES := tmglib clapack blas f2c是引用lapack子目录中编译好的一些依赖模块
这里会编译出一个名为lapack的库工程,该工程就可以作为依赖项被ORB所引用。
OpenCV的编译
opencv4Android是opencv官网为了对Android的支持而推出的一个工具集,可以在opencv官网进行下载。其目录结构如下:
其中sdk为核心部分,opencv4Android包含两个版本,一个是opencv为java做的本地化sdk,另一个是opencv利用ndk编译C++版本得到的库工程。我们将opencv4android解压后放置到ORB_SLAM2项目的同级目录下,如下所示:
之后在jni目录下的Android.mk中需要引用到OpenCV的地方加入如下代码:
<code class="hljs ruby has-numbering"><span class="hljs-constant">OPENCV_LIB_TYPE</span><span class="hljs-symbol">:</span>=<span class="hljs-constant">STATIC</span> ifeq (<span class="hljs-string">"$(wildcard $(OPENCV_MK_PATH))"</span>,<span class="hljs-string">""</span>) <span class="hljs-comment">#try to load OpenCV.mk from default install location </span> <span class="hljs-keyword">include</span> <span class="hljs-constant">E</span><span class="hljs-symbol">:/ORB_SLAM2/OpenCV-</span><span class="hljs-number">2.4</span>.<span class="hljs-number">9</span>-android-sdk/sdk/native/jni/<span class="hljs-constant">OpenCV</span>.mk <span class="hljs-keyword">else</span> <span class="hljs-keyword">include</span> <span class="hljs-variable">$(</span><span class="hljs-constant">OPENCV_MK_PATH</span>) endif </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>
这里opencv.mk我给的是绝对地址,其实相对地址也是可以的。上面这段引用会将opencv进行编译并引入到当前的工作模块上来,这里就完成了opencv库的基本调用。如果为了方便还可以将opencv自身单独编译成一个库工程并开放给其他模块引用。
其他libraries的编译过程和上述工程大同小异,其主要步骤可以概括如下:
- 将当前库复制到jni的特定目录下;
- 在Android.mk中新建一个模块并对模块进行命名;
- LOCAL_C_INCLUDE引入库的头文件,LOCAL_SRC_FILES引入库的cpp文件;
- LOCAL_LDLIBS/LOCAL_SHARED_LIBRARIES/LOCAL_STATIC_LIBRARIES引入依赖库;
- LOCAL_C_FLAGS设置编译参数;
ORB_SLAM2的编译
这里我们将ORB_SLAM2的源文件也编译为一个library以供调用,其编译过程和上面雷同,需要注意的是,由于pangolin编译有问题,我拆了源文件的pangolin部分并注释了对应的部分代码,同时引入了opengl es 来进行map和pose的绘制。同时,为了完成特征检测图像的回调,我改变了System.cc中TrackMonocular的返回值,将其返回值改成了Mat。
当上述过程完成后,我们的C++编译工作就基本完成了,最后也是最重要的一步是为Java中定义的native方法做C++的实现,在JAVA中,我定义了如下native函数:
<code class="hljs java has-numbering"> <span class="hljs-javadoc">/** * jni中初始化SLAM系统 *<span class="hljs-javadoctag"> @param</span> VOCPath *<span class="hljs-javadoctag"> @param</span> calibrationPath */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initSystemWithParameters</span>(String VOCPath,String calibrationPath); <span class="hljs-javadoc">/** * Dataset模式中ORB系统的start函数 *<span class="hljs-javadoctag"> @param</span> curTimeStamp *<span class="hljs-javadoctag"> @param</span> data *<span class="hljs-javadoctag"> @param</span> w *<span class="hljs-javadoctag"> @param</span> h *<span class="hljs-javadoctag"> @return</span> */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">int</span>[] <span class="hljs-title">startCurrentORB</span>(<span class="hljs-keyword">double</span> curTimeStamp,<span class="hljs-keyword">int</span>[] data,<span class="hljs-keyword">int</span> w,<span class="hljs-keyword">int</span> h); <span class="hljs-javadoc">/** * Camera模式中ORB系统的start *<span class="hljs-javadoctag"> @param</span> curTimeStamp *<span class="hljs-javadoctag"> @param</span> addr *<span class="hljs-javadoctag"> @param</span> w *<span class="hljs-javadoctag"> @param</span> h *<span class="hljs-javadoctag"> @return</span> */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span>[] <span class="hljs-title">startCurrentORBForCamera</span>(<span class="hljs-keyword">double</span> curTimeStamp,<span class="hljs-keyword">long</span> addr,<span class="hljs-keyword">int</span> w,<span class="hljs-keyword">int</span> h); <span class="hljs-javadoc">/** * Opengl es 的初始化 */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">glesInit</span>(); <span class="hljs-javadoc">/** * opengl es绘制更新 */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">glesRender</span>(); <span class="hljs-javadoc">/** * 防止opengl es窗口resize带来的影响 *<span class="hljs-javadoctag"> @param</span> width *<span class="hljs-javadoctag"> @param</span> height */</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">native</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">glesResize</span>(<span class="hljs-keyword">int</span> width, <span class="hljs-keyword">int</span> height);</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li></ul>
其对应的C++代码为:
<code class="hljs cpp has-numbering"><span class="hljs-comment">/* * Class: orb_slam2_android_nativefunc_OrbNdkHelper * Method: initSystemWithParameters * Signature: (Ljava/lang/String;Ljava/lang/String;)V */</span> JNIEXPORT <span class="hljs-keyword">void</span> JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_initSystemWithParameters (JNIEnv * env, jclass cls, jstring VOCPath, jstring calibrationPath) { <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *calChar = env->GetStringUTFChars(calibrationPath, JNI_FALSE); <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *vocChar = env->GetStringUTFChars(VOCPath, JNI_FALSE); <span class="hljs-comment">// use your string</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> voc_string(vocChar); <span class="hljs-built_in">std</span>::<span class="hljs-built_in">string</span> cal_string(calChar); env->GetJavaVM(&jvm); jvm->AttachCurrentThread(&env, NULL); s=<span class="hljs-keyword">new</span> ORB_SLAM2::System(voc_string,cal_string,ORB_SLAM2::System::MONOCULAR,<span class="hljs-keyword">true</span>); env->ReleaseStringUTFChars(calibrationPath, calChar); env->ReleaseStringUTFChars(VOCPath, vocChar); init_end=<span class="hljs-keyword">true</span>; } <span class="hljs-comment">/* * Class: orb_slam2_android_nativefunc_OrbNdkHelper * Method: startCurrentORB * Signature: (DDD[I)[I */</span> JNIEXPORT jintArray JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_startCurrentORB( JNIEnv * env, jclass cls, jdouble curTimeStamp, jintArray buf, jint w, jint h) { jint *cbuf; cbuf = env->GetIntArrayElements(buf, <span class="hljs-keyword">false</span>); <span class="hljs-keyword">if</span> (cbuf == NULL) { <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; } <span class="hljs-keyword">int</span> size = w * h; cv::Mat myimg(h, w, CV_8UC4, (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span>*) cbuf); cv::Mat ima = s->TrackMonocular(myimg, curTimeStamp); jintArray resultArray = env->NewIntArray(ima.rows * ima.cols); jint *resultPtr; resultPtr = env->GetIntArrayElements(resultArray, <span class="hljs-keyword">false</span>); <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < ima.rows; i++) <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j < ima.cols; j++) { <span class="hljs-keyword">int</span> R = ima.at < Vec3b > (i, j)[<span class="hljs-number">0</span>]; <span class="hljs-keyword">int</span> G = ima.at < Vec3b > (i, j)[<span class="hljs-number">1</span>]; <span class="hljs-keyword">int</span> B = ima.at < Vec3b > (i, j)[<span class="hljs-number">2</span>]; resultPtr[i * ima.cols + j] = <span class="hljs-number">0xff000000</span> + (R << <span class="hljs-number">16</span>) + (G << <span class="hljs-number">8</span>) + B; } env->ReleaseIntArrayElements(resultArray, resultPtr, <span class="hljs-number">0</span>); env->ReleaseIntArrayElements(buf, cbuf, <span class="hljs-number">0</span>); <span class="hljs-keyword">return</span> resultArray; } <span class="hljs-comment">/* * Class: orb_slam2_android_nativefunc_OrbNdkHelper * Method: glesInit * Signature: ()V */</span> JNIEXPORT <span class="hljs-keyword">void</span> JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_glesInit (JNIEnv *env, jclass cls) { <span class="hljs-comment">// 启用阴影平滑</span> glShadeModel(GL_SMOOTH); <span class="hljs-comment">// 黑色背景</span> glClearColor(<span class="hljs-number">1.0f</span>, <span class="hljs-number">1.0f</span>, <span class="hljs-number">1.0f</span>, <span class="hljs-number">0.0f</span>); <span class="hljs-comment">// 设置深度缓存</span> glClearDepthf(<span class="hljs-number">1.0f</span>); <span class="hljs-comment">// 启用深度测试</span> glEnable(GL_DEPTH_TEST); <span class="hljs-comment">// 所作深度测试的类型</span> glDepthFunc(GL_LEQUAL); <span class="hljs-comment">// 告诉系统对透视进行修正</span> glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); } <span class="hljs-comment">/* * Class: orb_slam2_android_nativefunc_OrbNdkHelper * Method: glesRender * Signature: ()V */</span> JNIEXPORT <span class="hljs-keyword">void</span> JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_glesRender (JNIEnv * env, jclass cls) { glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); <span class="hljs-keyword">if</span>(init_end) s->drawGL(); } <span class="hljs-comment">/* * Class: orb_slam2_android_nativefunc_OrbNdkHelper * Method: glesResize * Signature: (II)V */</span> JNIEXPORT <span class="hljs-keyword">void</span> JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_glesResize (JNIEnv *env, jclass cls, jint width, jint height) { <span class="hljs-comment">//图形最终显示到屏幕的区域的位置、长和宽</span> glViewport (<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,width,height); <span class="hljs-comment">//指定矩阵</span> glMatrixMode (GL_PROJECTION); <span class="hljs-comment">//将当前的矩阵设置为glMatrixMode指定的矩阵</span> glLoadIdentity (); glOrthof(-<span class="hljs-number">2</span>, <span class="hljs-number">2</span>, -<span class="hljs-number">2</span>, <span class="hljs-number">2</span>, -<span class="hljs-number">2</span>, <span class="hljs-number">2</span>); } <span class="hljs-comment">/* * Class: orb_slam2_android_nativefunc_OrbNdkHelper * Method: readShaderFile * Signature: (Landroid/content/res/AssetManager;)V */</span> JNIEXPORT jintArray JNICALL Java_orb_slam2_android_nativefunc_OrbNdkHelper_startCurrentORBForCamera (JNIEnv *env, jclass cls,jdouble timestamp, jlong addr,jint w,jint h) { <span class="hljs-keyword">const</span> cv::Mat *im = (cv::Mat *) addr; cv::Mat ima = s->TrackMonocular(*im, timestamp); jintArray resultArray = env->NewIntArray(ima.rows * ima.cols); jint *resultPtr; resultPtr = env->GetIntArrayElements(resultArray, <span class="hljs-keyword">false</span>); <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < ima.rows; i++) <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j < ima.cols; j++) { <span class="hljs-keyword">int</span> R = ima.at < Vec3b > (i, j)[<span class="hljs-number">0</span>]; <span class="hljs-keyword">int</span> G = ima.at < Vec3b > (i, j)[<span class="hljs-number">1</span>]; <span class="hljs-keyword">int</span> B = ima.at < Vec3b > (i, j)[<span class="hljs-number">2</span>]; resultPtr[i * ima.cols + j] = <span class="hljs-number">0xff000000</span> + (R << <span class="hljs-number">16</span>) + (G << <span class="hljs-number">8</span>) + B; } env->ReleaseIntArrayElements(resultArray, resultPtr, <span class="hljs-number">0</span>); <span class="hljs-keyword">return</span> resultArray; }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li><li>89</li><li>90</li><li>91</li><li>92</li><li>93</li><li>94</li><li>95</li><li>96</li><li>97</li><li>98</li><li>99</li><li>100</li><li>101</li><li>102</li><li>103</li><li>104</li><li>105</li><li>106</li><li>107</li><li>108</li><li>109</li><li>110</li><li>111</li><li>112</li><li>113</li><li>114</li><li>115</li><li>116</li><li>117</li><li>118</li><li>119</li><li>120</li><li>121</li><li>122</li><li>123</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li><li>89</li><li>90</li><li>91</li><li>92</li><li>93</li><li>94</li><li>95</li><li>96</li><li>97</li><li>98</li><li>99</li><li>100</li><li>101</li><li>102</li><li>103</li><li>104</li><li>105</li><li>106</li><li>107</li><li>108</li><li>109</li><li>110</li><li>111</li><li>112</li><li>113</li><li>114</li><li>115</li><li>116</li><li>117</li><li>118</li><li>119</li><li>120</li><li>121</li><li>122</li><li>123</li></ul>
这里解释下Dataset和Camera模式下start方法的区别。其实就是图像参数传递的方式不一样。在DataSet模式中,我们是用ImageView显示图片,用Bitmap读取文件中的图片,而非基本类型的数据都是不能被jni接口所接受的因此我们需要利用Bitmap的getPixels方法将其转换成int[]型数据进行传递,在jni中int[]对应的数据类型为jintArray,我们可以在获取到数据后将jintArray转换成Mat进行后续处理;而在Camera模式中我们是利用opencv android sdk中的cvCameraView 来直接进行摄像头的调用和图像的显示。其onCameraFrame(CvCameraViewFrame inputFrame)中的inputfram可以通过rgba()方法转换成Mat类型数据,而Mat类型同样不被jni识别,因此需要利用Mat的getNativeObjAddr方法获取Mat数据的long型指针传递到jni中进行处理。关键代码如下:
DataSet 的Java部分:
<code class="hljs cs has-numbering"><span class="hljs-keyword">int</span> w = tmp.getWidth(), h = tmp.getHeight();<span class="hljs-comment">//其中tmp为bitmap</span> <span class="hljs-keyword">int</span>[] pix = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[w * h]; tmp.getPixels(pix, <span class="hljs-number">0</span>, w, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, w, h);</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>
C++部分:
<code class="hljs objectivec has-numbering">jint *cbuf; cbuf = env->GetIntArrayElements(buf, <span class="hljs-literal">false</span>); <span class="hljs-keyword">if</span> (cbuf == <span class="hljs-literal">NULL</span>) { <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; } <span class="hljs-keyword">int</span> size = w * h; cv::Mat myimg(h, w, CV_8UC4, (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">char</span>*) cbuf);</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>
Camera的Java部分:
<code class="hljs java has-numbering">Mat im=inputFrame.rgba(); <span class="hljs-keyword">synchronized</span> (im) { addr=im.getNativeObjAddr();<span class="hljs-comment">//addr为函数传递的图像参数</span> }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>
C++部分:
<code class="hljs ruby has-numbering">const <span class="hljs-symbol">cv:</span><span class="hljs-symbol">:Mat</span> *im = (<span class="hljs-symbol">cv:</span><span class="hljs-symbol">:Mat</span> *) addr;<span class="hljs-regexp">//addr</span>为传入的图像参数</code><ul class="pre-numbering"><li>1</li></ul><ul class="pre-numbering"><li>1</li></ul>
结尾
当上述步骤都完成后,我们会得到最终生成的sdk。Android部分的布局文件和对应activity文件在这里也略过不表。当得到最终生成的apk后,我们如果要测试Camera模式,需要先将opencv4Android中apk文件夹中对应类型的opencv manager安装到手机中并预先打开才能使用,否则会提示找不到opencv的支持库;若只需测试Dataset模式则无需上述步骤。
水平有限,如有错误请不吝指正,谢谢。