Android Studio 2.1.3 在gradle-experimental下编译使用OpenCV
Android Studio 进行jni的编程时有个实验性的gradle-experimental,用起来还是比较方便,不需要编写Android.mk文件和Application.mk文件,所有的配置的在app/build.gradle中完成,而且C++代码也可以自动提示。本文以编译使用OpenCV为示例,讲解Android Studio NDK开放环境的配置。
使用的开放环境如下:
- Android Studio 2.1.3
- gradle版本为:2.14
- gradle-experimental版本为:0.7.3
- OpenCV的版本为:3.1.0
新建工程
在AS中新建一Android工程,名字随意,我已OpenCV为工程名。
安装NDK
使用AS自带的NDK,因此第一步需要下载NDK。打开File >Settings>Appearance & Behavior > System Settings > Android SDK。在界面左边选择SDK Tools 选择NDK选项,如果没安装,点击OK后,系统便会进行NDK的按照。如下图所示:
按照完毕后打开File > Project Structrue > SDK Location,在右边的界面中 Android NDK Location 一栏中会显示安装的NDK路径,如下图所示:
修改工程目录下的build.gradle
打开工程名/build.gradle,注意不是app/build.gradle。将默认的
classpath ‘com.android.tools.build:gradle:2.1.3’ 修改为:
classpath ‘com.android.tools.build:gradle-experimental:0.7.3’
整个文件内容如下:
修改完成后进行sync会出现错误提示,正是下一步需要解决的问题。
注意:如果使用的不是gradle-experimental:0.7.3,则还需修改gradle的版本。打开File->Project Strcutrue->Project 在界面右边修改Gradle version的版本,以适合gradle-experimental的版本。
修改app/build.gradle
因为使用的的gradle-experimental,原有的build.gradle就不适用了,需要进行修改。基本的就将
apply plugin: ‘com.android.application’ 修改为:
apply plugin: ‘com.android.model.application’。
在原有的android节点外添加model节点,使model节点包含android节点,并且原有的属性配置需要添加等号。如下所示:
model {
android {
compileSdkVersion = 24
buildToolsVersion = "24.0.1"
defaultConfig.with {
applicationId = "com.leej.opencv"
minSdkVersion.apiLevel = 14
targetSdkVersion.apiLevel = 24
versionCode = 1
versionName = "1.0"
}
}
android.buildTypes {
release {
minifyEnabled = false
proguardFiles.add(file('proguard-rules.pro'))
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.0'
}
截至这里基本的配置完成,下面就是ndk的配置和OpenCV的编译使用了。
下载OpenCV for Android
到这里http://opencv.org/platforms/android.html下载OpenCV for Android 的合适版本。下载后解压,拷贝..\OpenCV-android-sdk\sdk\native 文件夹到建立的Android项目工程目录下。整个的工程目录结构如下:
创建JNI文件夹
在app/src/main下创建jni文件夹,本地C++文件都存于次,创建的方法如下:
NDK的配置
在gradle-experimental下,需在app/build.gradle中添加相应的配置,才能正确编译本地方法。
在model节点下添加:
android.ndk {
moduleName = "myOpenCV"
cppFlags.add("-std=c++11")
cppFlags.add("-frtti")
cppFlags.add("-fexceptions")
cppFlags.add("-I${file("../native/jni/include")}".toString())
cppFlags.add("-I${file("../native/jni/include/opencv")}".toString())
ldLibs.addAll(["android", "EGL", "GLESv2", "dl", "log", "z"]) //编译需要
stl = 'gnustl_shared' //能够实现C++代码自动提示的关键
}
android.productFlavors {
create("arm") {
ndk.with {
abiFilters.add("armeabi")
String libsDir = getProjectDir().getParent() + "\\native\\libs\\armeabi\\"
String thirdPartyDir = getProjectDir().getParent() + "\\native\\3rdparty\\libs\\armeabi\\"
ldFlags.add(libsDir + "libopencv_features2d.a")
ldFlags.add(libsDir + "libopencv_flann.a")
ldFlags.add(libsDir + "libopencv_imgproc.a")
ldFlags.add(libsDir + "libopencv_core.a")
ldFlags.add(libsDir + "libopencv_highgui.a")
ldFlags.add(libsDir + "libopencv_calib3d.a")//注意添加的先后顺序
ldFlags.add(thirdPartyDir + "libtbb.a")
}
}
create("armv7") {
ndk.with {
abiFilters.add("armeabi-v7a")
String libsDir = getProjectDir().getParent() + "\\native\\libs\\armeabi-v7a\\";
String thirdPartyDir = getProjectDir().getParent() + "\\native\\3rdparty\\libs\\armeabi-v7a\\";
ldFlags.add(libsDir + "libopencv_features2d.a")
ldFlags.add(libsDir + "libopencv_flann.a")
ldFlags.add(libsDir + "libopencv_imgproc.a")
ldFlags.add(libsDir + "libopencv_core.a")
ldFlags.add(libsDir + "libopencv_highgui.a")
ldFlags.add(libsDir + "libopencv_calib3d.a")
ldFlags.add(thirdPartyDir + "libtbb.a")
}
}
create("x86") {
ndk.with {
abiFilters.add("x86")
String libsDir = getProjectDir().getParent() + "\\native\\libs\\x86\\";
String thirdPartyDir = getProjectDir().getParent() + "\\native\\3rdparty\\libs\\x86\\";
ldFlags.add(libsDir + "libopencv_features2d.a")
ldFlags.add(libsDir + "libopencv_flann.a")
ldFlags.add(libsDir + "libopencv_imgproc.a")
ldFlags.add(libsDir + "libopencv_core.a")
ldFlags.add(libsDir + "libopencv_highgui.a")
ldFlags.add(libsDir + "libopencv_calib3d.a")
ldFlags.add(thirdPartyDir + "libtbb.a")
}
}
}
完成之后整个app/build.gradle文件为:
model {
android {
compileSdkVersion = 24
buildToolsVersion = "24.0.1"
defaultConfig.with {
applicationId = "com.leej.opencv"
minSdkVersion.apiLevel = 14
targetSdkVersion.apiLevel = 24
versionCode = 1
versionName = "1.0"
}
}
android.buildTypes {
release {
minifyEnabled = false
proguardFiles.add(file('proguard-rules.pro'))
}
}
android.ndk {
moduleName = "myOpenCV"
cppFlags.add("-std=c++11")
cppFlags.add("-frtti")
cppFlags.add("-fexceptions")
cppFlags.add("-I${file("../native/jni/include")}".toString())
cppFlags.add("-I${file("../native/jni/include/opencv")}".toString())
ldLibs.addAll(["android", "EGL", "GLESv2", "dl", "log", "z"]) //编译需要
stl = 'gnustl_shared' //能够实现C++代码自动提示的关键
}
android.productFlavors {
create("arm") {
ndk.with {
abiFilters.add("armeabi")
String libsDir = getProjectDir().getParent() + "\\native\\libs\\armeabi\\"
String thirdPartyDir = getProjectDir().getParent() + "\\native\\3rdparty\\libs\\armeabi\\"
ldFlags.add(libsDir + "libopencv_features2d.a")
ldFlags.add(libsDir + "libopencv_flann.a")
ldFlags.add(libsDir + "libopencv_imgproc.a")
ldFlags.add(libsDir + "libopencv_core.a")
ldFlags.add(libsDir + "libopencv_highgui.a")
ldFlags.add(libsDir + "libopencv_calib3d.a")//注意添加的先后顺序
ldFlags.add(thirdPartyDir + "libtbb.a")
}
}
create("armv7") {
ndk.with {
abiFilters.add("armeabi-v7a")
String libsDir = getProjectDir().getParent() + "\\native\\libs\\armeabi-v7a\\";
String thirdPartyDir = getProjectDir().getParent() + "\\native\\3rdparty\\libs\\armeabi-v7a\\";
ldFlags.add(libsDir + "libopencv_features2d.a")
ldFlags.add(libsDir + "libopencv_flann.a")
ldFlags.add(libsDir + "libopencv_imgproc.a")
ldFlags.add(libsDir + "libopencv_core.a")
ldFlags.add(libsDir + "libopencv_highgui.a")
ldFlags.add(libsDir + "libopencv_calib3d.a")
ldFlags.add(thirdPartyDir + "libtbb.a")
}
}
create("x86") {
ndk.with {
abiFilters.add("x86")
String libsDir = getProjectDir().getParent() + "\\native\\libs\\x86\\";
String thirdPartyDir = getProjectDir().getParent() + "\\native\\3rdparty\\libs\\x86\\";
ldFlags.add(libsDir + "libopencv_features2d.a")
ldFlags.add(libsDir + "libopencv_flann.a")
ldFlags.add(libsDir + "libopencv_imgproc.a")
ldFlags.add(libsDir + "libopencv_core.a")
ldFlags.add(libsDir + "libopencv_highgui.a")
ldFlags.add(libsDir + "libopencv_calib3d.a")
ldFlags.add(thirdPartyDir + "libtbb.a")
}
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.0'
}
编写本地方法
创建一个新的类编写两个本方法,如下图所示:
“myOpenCV”是生成的库文件的名字,与app/build.gradle 中的moduleName名字相同。getStringFromNative方法测试由C++来创建字符串;gray方法测试OpenCV的边缘检测。从图中可以看到标注为红色的方法, 将光标定位到getStringFromNative方法,并按快捷键Alt + Enter,此时会弹出下图的提示:
在弹出的框中点击第一项,会在jni文件中创建一个.c的文件,并且会创建一个本地方法,如下图所示:
注意:由于示例需要使用OpenCV,因此将.c文件改成了.cpp文件。
另外一个方法也采用同样的方式生成。
完成后的.cpp文件的内容如下:
#include <jni.h>
#include <vector>
#include <string>
#include <opencv2/core/core.hpp>
#include <cv.h>
using namespace std;
using namespace cv;
IplImage* change4ChannelTo3InIplImage(IplImage * src);
JNIEXPORT jstring JNICALL
Java_com_leej_opencv_lib_OpenCV_getStringFromNative(JNIEnv *env, jclass type) {
// TODO
Mat mat(33, 22, 1);
string s = "Cpp v1 - succ, row#:" + mat.rows;
return env->NewStringUTF(s.data());
}
JNIEXPORT jintArray JNICALL
Java_com_leej_opencv_lib_OpenCV_gray(JNIEnv *env, jobject instance, jintArray buf_, jint w, jint h) {
jint *buf = env->GetIntArrayElements(buf_, false);
if (buf == NULL)
return 0;
Mat myImg(h, w, CV_8UC4, (unsigned char *)buf);
IplImage image = IplImage(myImg);
IplImage* image3Channel = change4ChannelTo3InIplImage(&image);
IplImage* pCannyImage = cvCreateImage(cvGetSize(image3Channel), IPL_DEPTH_8U, 1);
cvCanny(image3Channel, pCannyImage, 50, 150, 3);
int* outImage = new int[w * h];
for (int i = 0; i < w * h; i ++) {
outImage[i] = (int)pCannyImage->imageData[i];
}
int size = w * h;
jintArray result = env->NewIntArray(size);
env->SetIntArrayRegion(result, 0, size, outImage);
// TODO
env->ReleaseIntArrayElements(buf_, buf, 0);
return result;
}
IplImage* change4ChannelTo3InIplImage(IplImage * src) {
if (src -> nChannels != 4) {
return NULL;
}
IplImage* destImg = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
for (int row = 0; row < src->height; row ++) {
for (int col = 0; col < src -> width; col ++) {
CvScalar s = cvGet2D(src, row, col);
cvSet2D(destImg, row, col, s);
}
}
return destImg;
}
编译
点击Build-> Make Project对工程进行编译,在配置都正确的情况下,编译是成功的。
至此ndk的配置和OpenCV的使用就到此结束。可以自行编写程序测试这个方法是否成功,本人测试以通过。