[置顶] 简单的调用OpenCV库的Android NDK开发 工具Android Studio
2018-01-23 14:16
1016 查看
前言
本博客写于2017/08/11, 博主非专业搞安卓开发, 只是工作的需要倒腾了下Android NDK相关的开发, 博文中有什么不正确、不严格的地方欢迎指正哈 本文后续也许还会有删改,就这样。
一、工具、开发环境
博主的操作系统是Windows 10 x64位,虽然感觉Windows 7更适合用来搞开发, 但是用着Win 10也是挺好使的(没必要在操作系统上有很大纠结,Win 10和Win 7都可以)。
1.搭建并测试JAVA开发环境
首先要搭建Java开发环境,Java开发环境的搭建教程网上可以找到,这里不予赘述; 测试是否搭建成功的时候,需要打开命令提示符依次输入java、javac、javah命令进行测试,显示如下界面结果就成功了:图一 java命令测试
图二 javac命令测试
图三 javah命令测试
2.搭建Android开发环境
很长一段时间内,Eclipse + ADT插件是Android开发的主流,不过现在流行的是Android Studio。 还记得Android Studio刚出来的时候很是不被看好, 不过现在它也是很好用的安卓开发工具了,此文介绍的所需要的工具只有Android Studio。网上有介绍说设置ndk-build环境变量的, 但是要弄明白一件事, 他们设置这个变量是因为他们的"NDK"这个依赖包是单独下载的, 并没有在Android Studio里面下载, 本文的"NDK"依赖包是在Android Studio里面下载的, 因此并不需要设置ndk-build环境变量。
开始正式搭建环境,我用的是Android Studio 2.2.3版本。 安装完Android Studio之后, 选择"File" -> "Settings" -> "Appearence & Behavior" -> "System Settings" -> "Android SDK", 配置 "SDK Platforms"
和 "SDK Tools" :
(1)"SDK
Platforms":这个是选择安卓SDK版本,根据自己想法跟需要下载,我下载了安卓4.4、5.0、7.1.1
(2)"SDK Tools":这个是选择SDK相关开发工具,基础的"NDK"包(必选)是必须有的,刚安装上Android
Studio的时候默认有什么包我忘了,"Android SDK Build-Tools"这个包应该不用自己勾选,默认就有。NDK开发现在提供了CMake的方式来进行编译调试,通过下载"CMake"跟"LLDB"这俩(可选)就可以用CMake来开发了,本文未基于CMake的方式进行编译调试,而是采用的传统的gradle方式(相对而言,我其实更推荐使用CMake的方式)。
(3)正如(2)中所说的, 我采用的是gradle的方式进行的开发, 本文不介绍CMake的方式。 所以在采用gradle的基础上,才有了这第(3)步骤。
这一步就是为了更方便的进行gradle方式的开发而写的。
可以将gradle经常用到的一些命令简化到菜单中,具体设置在"File" -> "Settings" -> "Tools" -> "External Tools"这里:
1) 添加javah,它用于生成头文件
Program:
$JDKPath$/bin/javah
Parameters:
-d ../jni -jni $FileClass$
Working directory:
$SourcepathEntry$\..\java
最后单击"OK"按钮进行保存。
2) 添加ndk-build,它用于构建so包
Program:
你的NDK目录\build\ndk-build.cmd
Parameters: 什么都不用填
Working directory:
$ModuleFileDir$\src\main
最后单击"OK"按钮进行保存。
3) 添加ndk-build
clean,它用于清理so包
Program:
你的NDK目录\build\ndk-build.cmd
Parameters:
clean
Working directory:
$ModuleFileDir$\src\main
最后单击"OK"按钮进行保存。
3.来一个栗子
一个NDK开发项目总体就分为生成so库、调用so库两部分,本博客栗子就是先将基于OpenCV的C++代码编译生成so库,再通过jni接口来实现对安卓摄像头的灰度化处理。
(1)首先新建一个工程
1)选择新建项目:
2)填入一些基本信息, 不勾选"Include C++ Support", 然后Next:
3)默认就可以了,继续Next:
4)继续Next:
5)最后Finish:
(2)项目创建完毕了,开始向项目中导入OpenCV库
1)去OpenCV官网下载OpenCV for Android版本, 我下载的是2.4.9版本,下载下来的是一个压缩包, 名字是"OpenCV-2.4.9-android-sdk", 我们将其解压放到某个位置, 待会会用到。 我将其解压缩到了D:\Usual\Android目录下面。
2)选择导入组件:
3)选择文件夹
在我这里, 就找到刚才解压位置OpenCV for Android包那里, 找到java文件夹然后"OK"开始确认。
接下来默认即可, "next"然后"finish"。
4)接下来还需要将OpenCV库添加到依赖:
选择Project Structure之后, 弹出以下画面, 按图示进行操作来添加依赖:
5)进行一些必要的小修改
切换到"Project"视图界面,
将openCVLibrary249目录下的build.gradle向"app"->"src"下的build.gradle看齐, 也就是将前者的这四项参数修改的和后者一样:
修改之后还需要单击"Sync Now"来更新一下:
至此,算是正式导入成功OpenCV库。
(3)开始生成so库
首先分析一下项目, 整个项目要实现对安卓摄像头的实时灰度化, 我们在java类里面来实现摄像头画面的获取, 用C/C++代码来实现对一帧图像(一幅图像)的灰度化。
NDK开发将C/C++代码生成so库, 留一个对单幅图片进行灰度化的接口, 然后我们在Java层获取摄像头数据, 通过JNI调用所生成so库的接口, 对每一帧摄像头数据进行灰度化。
那么现在可以生成so库了。
1)在app->src->main右键新建一个jni文件夹, 操作指示如图:
单击"JNI Folder"会出现一个界面来确认操作, 选择界面上的"Finish"结束即可。
2)新建一个类OpencvClass,负责与C/C++代码对接。
3)开始生成C/C++头文件
打开新创建的类文件,添加一行代码:
public native static int convertGray(long matAddrRgba, long matAddrgray);
接下来,右键单击类OpencvClass,来生成C/C++头文件
生成成功后会在main文件夹下多出来一个jni文件夹,里面有个头文件:
接下来新建一个.cpp文件,名字和该头文件一样,如下图所示操作:
输入.cpp名字, 然后单击"OK"结束。
4)开始写.c/.cpp源文件
上一步生成了头文件, 此头文件内容是:
#include <jni.h> /* Header for class com_hoos_grayprocessing_OpencvClass */ #ifndef _Included_com_hoos_grayprocessing_OpencvClass #define _Included_com_hoos_grayprocessing_OpencvClass #ifdef __cplusplus extern "C" { #endif /* * Class: com_hoos_grayprocessing_OpencvClass * Method: convertGray * Signature: (JJ)I */ JNIEXPORT jint JNICALL Java_com_hoos_grayprocessing_OpencvClass_convertGray (JNIEnv *, jclass, jlong, jlong); #ifdef __cplusplus } #endif #endif
其中, JNIEXPORT是接口声明, 我们需要在cpp源文件里将它实现, 这里当然需要我们有C/C++基础, 可以写出cpp文件的框架了:
/ Created by HooS on 2017/8/14. #include <com_hoos_grayprocessing_OpencvClass.h> JNIEXPORT jint JNICALL Java_com_hoos_grayprocessing_OpencvClass_convertGray (JNIEnv *, jclass, jlong, jlong){ }
在实现时, 接口的两个jlong类型需要用来传输OpenCV里面的Mat类型数据, 所以要略微修改一下那里:
// Created by HooS on 2017/8/14. // #include <com_hoos_grayprocessing_OpencvClass.h> JNIEXPORT jint JNICALL Java_com_hoos_grayprocessing_OpencvClass_convertGray (JNIEnv *, jclass, jlong addrRgba, jlong addrGray){ }然后完善代码,完善JNI接口数据转换,实现单幅图像的灰度化功能:
// Created by HooS on 2017/7/25. // #include <com_hoos_ndkopencvtest_OpencvNativeClass.h> JNIEXPORT jint JNICALL Java_com_hoos_ndkopencvtest_OpencvNativeClass_convertGray (JNIEnv *, jclass, jlong addrRgba, jlong addrGray){ // 实现jlong类型到Mat类型的转换 Mat& mRgb = *(Mat*)addrRgba; Mat& mGray = *(Mat*)addrGray; // 定义 int 和 jint 类型来接收函数返回值 int conv; jint retVal; // 此处调用了toGray函数来对图片进行灰度化处理 conv = toGray(mRgb, mGray); retVal = (jint)conv; return retVal; }看注释可以知道, 我们还需要实现toGray函数来实现单幅图片的灰度化, 这个实现代码很简单, 为啥非得再麻烦些折腾些呢, 因为大项目往往会有好多源文件, 相互调用, 所以之前最好有多源文件编译的机会来锻炼一下, nao, 自己创造机会。
接下来,新建gray.h、gray.cpp文件来实现toGray这个函数。按照刚才新建.c/.cpp文件的操作来新建:
gray.h 和 gray.cpp里面就是跟纯C/C++编程一样了, 写一个对单个图像进行灰度化的函数:
#include <stdio.h> #include <opencv2/opencv.hpp> using namespace cv; using namespace std; bool toGray(Mat img, Mat& gray);
#include "gray.h" bool toGray(Mat img, Mat& gray) { if (img.channels() == 3) cvtColor(img, gray, CV_BGR2GRAY); else if (img.channels() == 4) cvtColor(img, gray, CV_BGRA2GRAY); else if (img.channels() == 1) gray = img; if(gray.rows == img.rows && gray.cols == img.cols) return true; else return false; }
然后再修改下com_hoos_ndkopencvtest_OpencvNativeClass.h文件就好了,添加如下代码(在哪里添加应该都懂):
using namespace cv; using namespace std;
至此,C/C++源文件搞定。
5)写 Android.mk 和 Application.mk 文件
因为本文采用的gradle的方式来进行NDK开发的, 所以我们会用到 Android.mk 和 Application.mk文件,开始新建这两个文件:
创建Application.mk文件的方法也是这样,我就不予赘述了。两个文件内容如下,需要修改的地方都注释了:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #opencv OPENCVROOT:= D:/Usual/Android/OpenCV-2.4.9-android-sdk #这句代码等号右边写上自己的OpenCV安卓开发包的位置 OPENCV_CAMERA_MODULES:=on OPENCV_INSTALL_MODULES:=on OPENCV_LIB_TYPE:=SHARED #思考下,参考我的修改一下就好了 include D:/Usual/Android/OpenCV-2.4.9-android-sdk/sdk/native/jni/OpenCV.mk #这里写上自己的开发需要的源文件, 我这里需要两个 LOCAL_SRC_FILES := com_hoos_grayprocessing_OpencvClass.cpp gray.cpp #设置生成的so库的名字 前面的lib和后缀名不用写 LOCAL_MODULE := MyLibs LOCAL_LDLIBS += -llog include $(BUILD_SHARED_LIBRARY)
APP_STL := gnustl_static APP_CPPFLAGS := -frtti -fexceptions #这句是设置生成的cpu指令类型,可以根据自己需求来设置生成的平台, 一般这两个就够了 APP_ABI := armeabi-v7a x86 APP_PLATFORM := android-8 #这句是设置最低安卓平台,可以不弄
6)开始生成so库
可以看到,多出来了一个libs文件夹,如图:
至此,so库生成完成。
(4)JNI调用so库
这一部分将大体分析一下, 毕竟实现不同的功能类有不同的写法, 本文主要提供一个思路, 以待能够举一反三写出其他NDK开发实例来。这是这一部分的五个关键文件:
1)activity_main.xml
源内容如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.hoos.grayprocessing.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> </RelativeLayout>
根据实际情况, 我们不需要TextView, 而需要一个JavaCameraView来显示摄像头帧, 控件ID为"java_camera_view", 修改如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.hoos.grayprocessing.MainActivity"> <org.opencv.android.JavaCameraView android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/java_camera_view" /> </RelativeLayout>
2)AndroidManifest.xml
原内容是:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.hoos.grayprocessing"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
因为应用需要摄像头权限, 所以需要在这个文件添加摄像头权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.hoos.grayprocessing"> <uses-permission android:name="android.permission.CAMERA"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
3)gradle.propertise
需要添加对以前版本的支持,在此文件内容的最后添加一行:
android.useDepredcatedNdk=true
4)build.gradle
原内容:
android { compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { applicationId "com.hoos.grayprocessing" minSdkVersion 21 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:26.0.0-alpha1' testCompile 'junit:junit:4.12' compile project(':openCVLibrary249') }
添加内容, 指明生成的so库的路径:
android { compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { applicationId "com.hoos.grayprocessing" minSdkVersion 21 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } sourceSets.main{ jniLibs.srcDirs = ['src/main/libs'] jni.srcDirs = [] } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:26.0.0-alpha1' testCompile 'junit:junit:4.12' compile project(':openCVLibrary249') }
5)MainActivity.java
部分注释在代码中,
import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import org.opencv.android.BaseLoaderCallback; import org.opencv.android.CameraBridgeViewBase; import org.opencv.android.JavaCameraView; import org.opencv.android.LoaderCallbackInterface; import org.opencv.android.OpenCVLoader; import org.opencv.core.CvType; import org.opencv.core.Mat; public class MainActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2{ private static String TAG = "MainActivity"; // 初始化一个实例,用来获取摄像头帧 JavaCameraView javaCameraView; Mat mRgba; // 用来存储原始摄像头数据 Mat mGray; // 用来存储灰度化后的帧数据 // 静态加载之前生成的so库——Mylins static{ System.loadLibrary("MyLibs"); } // 回调开初始化帧数据 BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) { @Override public void onManagerConnected(int status) { switch(status){ case BaseLoaderCallback.SUCCESS: javaCameraView.enableView(); break; default: super.onManagerConnected(status); break; } super.onManagerConnected(status); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 与java_camera_view控件绑定 javaCameraView = (JavaCameraView)findViewById(R.id.java_camera_view); javaCameraView.setVisibility(View.VISIBLE); javaCameraView.setCvCameraViewListener(this); } @Override protected void onPause(){ super.onPause(); if(javaCameraView!=null) javaCameraView.disableView(); } protected void onDestroy(){ super.onDestroy(); if(javaCameraView!=null) javaCameraView.disableView(); } // 判断是否加载成功OpenCV库 protected void onResume(){ super.onResume(); if(OpenCVLoader.initDebug()){ Log.i(TAG, "Opencv loaded successfully"); mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS); } else{ Log.i(TAG, "Opencv not loaded"); OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_9, this, mLoaderCallback); } } @Override public void onCameraViewStarted(int width, int height) { // 初始化这两个Mat类型 mRgba = new Mat(height, width, CvType.CV_8UC4); mGray = new Mat(height, width, CvType.CV_8UC1); } @Override public void onCameraViewStopped() { mRgba.release(); } @Override public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) { // 得到摄像头原始帧数据 mRgba = inputFrame.rgba(); // 调用OpencvClass类的方法来对帧数据进行灰度化 OpencvClass.convertGray(mRgba.getNativeObjAddr(), mGray.getNativeObjAddr()); return mGray; } }
此外, 出现了如下提示"Gradle files have changed ...", 请单击右边的"Sync Now"来进行更新。
(4)在x86的Android 5.0的虚拟机器上运行了下, 木有问题
当然了,想要虚拟安卓机能用电脑或者usb外置摄像头需要修改虚拟机一点参数:
1)鼠标左键单击下图红色标记按钮:
2)鼠标左键单击下图红色标记按钮(前提是你已经创建了一个虚拟机,
建议创建x86的虚拟机):
3) 鼠标左键单击下图红色标记按钮:
4)将参数 "Front" 或者 "Back" 的其中任意一个改成Webcam0就可以了, 这样只要电脑内置或者外置摄像头可用虚拟机就可以调用了。
相关文章推荐
- 谷歌开发工具 Android Studio 使用简单教程
- [置顶] 安卓开发工具快速配置环境变量--jdk安装步骤及配置环境变量、Android Studio、Eclipse配置环境变量
- [置顶] 开发工具使用(Android Studio、SQLite Expert 、TortoiseSVN)
- 简单实现Android NDK编译jni调用动态库开发
- Android开发工具之Android Studio--调用系统隐藏方法之操作aidl文件步骤
- [置顶] Android Studio开发工具常用快捷键。部分总结,不全面,只包含新手可能少用的
- Android NDK开发(1)----- Java与C互相调用实例详解
- [置顶] arcgis api for js入门开发系列十三 通过Ajax的调用执行GP服务 (含源代码)
- Android NDK 开发(三)JNI 调用Java属性和方法
- linux安装安卓开发工具android studio
- 在无VS开发环境的情况下调用Asp.net网站配置工具{转}
- Android Studio开发环境集成OpenCV for Android详情
- Android NDK开发----- Java与C互相调用实例详解
- 以简单的方式消除Java冗余--开发常用小工具集
- 工欲善其事必先利其器-简单几步打造顺手的python开发工具(windows,Linux多版本)
- Android NDK开发之Jni调用Java对象
- [置顶] Android Studio使用新的Gradle构建工具配置NDK环境(三)
- android NDK开发整合opencv开发——环境搭建(window环境)
- Android(安卓)开发通过NDK调用JNI,使用opencv做本地c++代码开发配置方法 边缘检测 范例代码