OpenCV On Android最佳環境配置指南(Android Studio篇)
簡介
本文是《OpenCV On Android最佳環境配置指南》系列教程第二篇,也是配置教程的最後一篇。通過對Android Studio裡OpenCV環境配置講解,快速幫新人解決入門配置問題。
本文內容是本人經過多次踩坑,並參考網上眾多OpenCV On Android的配置教程總結而來,盡希望能幫助學習移動影象處理的朋友們少走彎路,。
本篇文章寫的時候有點匆忙,肯定有遺漏之處,後續將持續補充。同時如有不足之處,希望各位dalao能夠指正,我也將及時修改。配置上如果遇到問題,也可以在評論裡留言,我將盡力幫助解決。
如果您使用的是Eclipse,請參考前一章OpenCV On Android最佳環境配置指南(Eclipse篇)
環境
電腦:Windows10
Java:jdk1.8.0_172
Android Studio:Version 3.1.3
SDK:Android Studio 3.1.3自帶的最新SDK(請不要與Eclipse同用一SDK,以免出錯)
。
NDK:Android Studio 3.1.3自帶的最新NDK
OpenCV:V3.4.1
注:以上配置基本上為最新版本
問答:
1、我已經會使用Eclipse搭建OpenCV Java和NDK環境了,為什麼還需要看你這篇文章?
答:原因有以下幾點:
1、現實原因,參見為什麼要用Android Studio?,可見Android Studio遲早會稱霸Android IDE界;
2、配置原因,OpenCV在Eclipse配置非常複雜(我當初配置了接近一個月),而Android Studio則簡單到令人淚奔。
3、除錯原因,也是最重要的一點,Eclipse 原生程式碼除錯能力弱,且容易出現誤報
2、我已經會使用Android Studio搭建OpenCV Java和NDK環境了,為什麼還需要看你這篇文章,你這篇文章有什麼特殊之處嗎?
答:我也不是一個喜歡重複造輪子的人,既然寫了這篇文章,肯定會去介紹一種不同的配置方式。正如本教程題目所提到的“最新”這個詞,我敢能保證我的配置方式是緊跟“時代潮流”。
傳統的Android Studio配置OpenCV環境與Eclipse無異,都是採用Android.mk檔案進行配置,這種方式配置的缺點在於原生程式碼不具有自動提示功能。而本文采用CMake進行配置,更加方便,簡潔,這種方式也是Android Studio所提倡的。
配置前說明:
本次配置不像上篇介紹Eclipse配置環境那樣編寫多個Demo,本次將使用一個Demo,將OpenCV Java和NDK配置方式完全包含,儘可能幫助大家去理解,請大家不要跳躍式地閱讀。
同時OpenCV Java庫和NDK庫的優缺點在上篇文章裡面已經提及,本文就不再贅述。
一、安裝必要組鍵
1、開啟Android Studio。如果是歡迎介面,選擇Configure->SDK Manager。如果是專案介面,選擇Tools->Android->SDK Manager。
2、將選項條切換到SDK Tools,勾上CMake和NDK,然後OK,開始下載(如果之前是勾選上的,則可以忽略此步)。
下載完後,則可以開始正式建立專案。
二、建立Android Studio工程
Create New Projec,這一步沒什麼好說的,由於是在一個Demo裡介紹Java和NDK配置,所以我們必須提前將Include C++ support勾選上(請注意我的專案名和包名,以免後續JNI編寫時出錯)
,如下圖:
接下來幾步都預設,但在Customize the Activity這步,我將Activity Name設定為JavaActivity,如下圖:
下一步,Customize C++ Support中,C++ Standard設定為C++14,下面兩個CheckBox都勾選上(別問我為什麼,我也不知道該咋解釋)。如下圖:
Finish,專案建立成功
三、OpenCV Java庫使用指南
環境配置:
第一步、將OpenCV Java庫作為Module匯入。具體步驟為:File->New->Import Module,然後將OpenCV-android-sdk\sdk\java目錄匯入,如下圖,然後Next->Finish。
如果Module匯入後提示下圖中的錯誤,你可以選擇點選藍色連結,Android Studio會自動下載所需的sdk版本。但我通常一般不會這樣做,因為目前市場上,99.9%的Android智慧手機系統版本都高於API15(Android4.0.3 IceCreamSandwich),我們也無需去相容過低的系統版本。
我的做法是,將檔案列表上方的顯示模式由Android切換至Project,並展開檔案列表。分別開啟app\build.gradle檔案和OpenCVLibrary341\build.gradle檔案,將OpenCVLibrary341\build.gradle的幾個版本號改成與app\build.gradle檔案裡相同即可,然後在文字域上方點選Try Again,如下圖:
第二步、給專案新增OpenCV Java庫依賴
選單File->Project Structure,在Modules裡選擇app,右側進入Dependencies,點選加號,選擇Module dependency,如下圖:
選擇OpenCVLibrary341,OK!
程式碼編寫:
在AndroidManifest.xml檔案中新增許可權:
....
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
....
將activity_java.xml內容修改為以下內容:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.opencv.android.JavaCameraView
android:id="@+id/javaCameraView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:camera_id="back"
app:show_fps="true" />
</LinearLayout>
將JavaActivity.java改為以下內容:
public class JavaActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {
private JavaCameraView javaCameraView;
private BaseLoaderCallback baseLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS: {
javaCameraView.enableView();
}
break;
default:
super.onManagerConnected(status);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//保持螢幕常亮
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_java);
javaCameraView = (JavaCameraView) findViewById(R.id.javaCameraView);
javaCameraView.setVisibility(SurfaceView.VISIBLE);
javaCameraView.setCvCameraViewListener(this);
}
@Override
public void onPause() {
super.onPause();
if (javaCameraView != null)
javaCameraView.disableView();
}
@Override
public void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_4_0, this, baseLoaderCallback);
} else {
baseLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}
@Override
public void onCameraViewStarted(int width, int height) {
}
@Override
public void onCameraViewStopped() {
}
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
return inputFrame.rgba();
}
}
在手機中安裝OpenCV Manager.apk,考慮到相容性,真機一般選擇armeabi或者armeabi-v7a,虛擬機器一般選擇x86,然後安裝我們的程式。如果出現安裝正常,但是執行程式報錯的情況,可能有以下原因:
1、未獲得攝像頭許可權
Android系統出於安全考慮,對許可權的的使用越來越嚴苛。在Android高版本中,僅僅在AndroidManifest.xml中宣告許可權並不能確保使用者一定授予,需要動態獲取許可權,或者使用者在設定中手動授權。
2、未能連線到OpenCV Manager
同樣是出於安全考慮,同時也防止應用之間相互喚醒(類似騰訊、百度全家桶,相互呼叫來保活),Android高版本限制了程序間的呼叫。具體解決方案請百度,因為每個手機開啟方式都不一樣。
免安裝OpenCV Manager
四、OpenCV NDK庫使用指南
環境配置:
Android Studio配置OpenCV環境灰常簡單(是的,沒錯),只需修改一個檔案便能成功配置環境,什麼Android.mk啊、Application.mk啊,全部滾蛋。
配置方式:開啟CMakeLists.txt,內容修改如下,記得將OpenCV_DIR設定為你的路徑,並注意斜槓方向,不要直接複製路徑,需要手動修改
:
cmake_minimum_required(VERSION 3.4.1)
# ##################### OpenCV 環境 ############################
#設定OpenCV-android-sdk路徑
set( OpenCV_DIR D:/OpenCV/OpenCV-android-sdk/sdk/native/jni )
find_package(OpenCV REQUIRED )
if(OpenCV_FOUND)
include_directories(${OpenCV_INCLUDE_DIRS})
message(STATUS "OpenCV library status:")
message(STATUS " version: ${OpenCV_VERSION}")
message(STATUS " libraries: ${OpenCV_LIBS}")
message(STATUS " include path: ${OpenCV_INCLUDE_DIRS}")
else(OpenCV_FOUND)
message(FATAL_ERROR "OpenCV library not found")
endif(OpenCV_FOUND)
# ###################### 專案原生模組 ###########################
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp)
target_link_libraries( native-lib
${OpenCV_LIBS}
log
jnigraphics)
OK,環境配置好了,嘿嘿嘿,接下來開始程式碼編寫。
程式碼編寫:
選單File->New->Activity->Empty Activity,建立一個新的Activity,其命名下如圖,並設定為啟動頁,Finish。
為了桌面上兩個程式入口不衝突,請在AndroidManifest.xml檔案中給兩個Activity指定label,如下圖:
佈局檔案activity_jni.xml內容如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<Button
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="show" />
<Button
android:id="@+id/process"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="process" />
</LinearLayout>
</RelativeLayout>
JniActivity.java內容如下:
public class JniActivity extends AppCompatActivity implements View.OnClickListener {
private ImageView imageView;
static {//載入so庫
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jni);
imageView = findViewById(R.id.imageView);
findViewById(R.id.show).setOnClickListener(this);
findViewById(R.id.process).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.show) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
imageView.setImageBitmap(bitmap);
} else {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
getEdge(bitmap);
imageView.setImageBitmap(bitmap);
}
}
//獲得Canny邊緣
native void getEdge(Object bitmap);
}
將一張名為test.png的圖片放置在drawable目錄下,我使用的是我的偶像照片,嘿嘿嘿\^_\^!
應用層寫好了,現在開始原生層操作:
第一步:生成標頭檔案
開啟Android Studio下方Terminal欄,輸入cd app\src\main\java(所有應用都一樣),回車。然後輸入javah 包名.類名
(我們這裡輸入的是javah com.demo.opencvdemo.JniActivity),回車後,將在app\src\main\java目錄下生成一個頭檔案,如下圖所示。最後將該標頭檔案Move到app\src\main\cpp目錄下。
第二步:編寫NDK程式碼
native-lib.cpp內容修改為:
#include "com_demo_opencvdemo_JniActivity.h"
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
using namespace cv;
extern "C" JNIEXPORT void
JNICALL Java_com_demo_opencvdemo_JniActivity_getEdge
(JNIEnv *env, jobject obj, jobject bitmap) {
AndroidBitmapInfo info;
void *pixels;
CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
info.format == ANDROID_BITMAP_FORMAT_RGB_565);
CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
CV_Assert(pixels);
if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
Mat temp(info.height, info.width, CV_8UC4, pixels);
Mat gray;
cvtColor(temp, gray, COLOR_RGBA2GRAY);
Canny(gray, gray, 125, 225);
cvtColor(gray, temp, COLOR_GRAY2RGBA);
} else {
Mat temp(info.height, info.width, CV_8UC2, pixels);
Mat gray;
cvtColor(temp, gray, COLOR_RGB2GRAY);
Canny(gray, gray, 125, 225);
cvtColor(gray, temp, COLOR_GRAY2RGB);
}
AndroidBitmap_unlockPixels(env, bitmap);
}
程式碼寫完後,就可以編譯執行程式了。這時可能會出現如下錯誤,這是由於OpenCV doesn’t link on Android x86/x86_64 with ndk r16,簡而言之,就是NDK r16預設使用的是c++_static
模板庫,而OpenCV3.4.1是由NDK r14預設的gnustl_static
模板庫所生成,這使得NDK r16編譯會出現異常。如果你使用的是ndk r14,將不會出現這種錯誤。
解決辦法有兩種:
1、更換成舊版本NDK
在SDK Manager中刪除NDK,然後重新下載NDK r14,並將其解壓到AndroidSDK目錄/ndk-bundle目錄中。重啟Android Studio即可(以後更新注意不要升級NDK)。
2、使用gnustl_static模板庫
要想使用gnustl_static模板庫進行編譯,只需要在app\build.gradle
中新增arguments引數,新增後內容如下:
...
externalNativeBuild {
cmake {
cppFlags "-std=c++14 -frtti -fexceptions"
arguments '-DANDROID_STL=gnustl_shared' //支援C++異常處理標準模板庫
}
}
...
繼續編譯執行,這時候又會出現下面的錯誤(方法2才會有),這是由x86 abi架構
造成的,原因未知。
解決方法有以下兩種:
1、不生成x86 abi架構
NDK r16能夠編譯"armeabi-v7a", "arm64-v8a", "x86_64","x86"
,我們只需指定編譯器生成其餘三種abi架構,就可以避免該bug。具體做法如下,在app\build.gradle
中增加abiFilters
欄位。內容如下:
...
externalNativeBuild {
cmake {
cppFlags "-std=c++14 -frtti -fexceptions"
arguments '-DANDROID_STL=gnustl_shared' //支援C++異常處理標準模板庫
abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
}
}
...
本方法的缺點是,不能相容x86架構的手機,並且不能在虛擬機器上執行(大部分虛擬機器都是x86架構)。
2、新增編譯引數
我比較喜歡這個方法,既能避免錯誤,有能相容大部分的Android手機。做法如下:在CmakeLists.txt檔案中新增如下內容:
...
endif(OpenCV_FOUND)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs,libippicv.a -Wl,--exclude-libs,libippiw.a")
# ###################### 專案原生模組 ###########################
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp)
...
然後編譯程式,將不再報錯,並能夠成功執行。注意,此時可能會預設開啟JavaActivity,退出應用,從桌面進入JniActivity,點選SHOW按鈕,效果如下:
點選PROCESS,效果如下:
額······我的偶像,即使變成被Canny檢測了邊緣,還是我的偶像,哈哈哈!!!
五、總結
OpenCV On Android 系列配置教程就到此為止,寫這兩篇文章確實也不容易,修改了很多遍,尤其是這篇Android Studio,算是百忙之中抽空完成的吧,也拖了很久。自己在配置的過程中踩了無數的坑,希望我的經驗能夠幫助到大家少走彎路,同時也虛心接受大家的批評與指正。移動端影象處理的路還長,我也將不斷去學習充實自己,這兩篇文章算是了卻我的一個心願,接下來的時間,我將全身心投入考研,反正大家都加油吧,下面是我創的學習群,我也將不定期幫助大家解決問題,請加群的人保持一個和善的心,分享經驗,共同進步,記住:別人幫助你不是必須的。