聲明:由于簡(jiǎn)書(shū)寫(xiě)作不便狱窘,后續(xù)將在掘金上更新和發(fā)布文章,包括本文毫胜。
掘金賬號(hào):徒步青云
簡(jiǎn)介
本文是《OpenCV On Android最佳環(huán)境配置指南》 系列教程第二篇永脓,也是配置系列的最后一篇,適合使用Android Studio的開(kāi)發(fā)人員學(xué)習(xí)浆兰。
本教程是經(jīng)過(guò)本人多次踩坑,并結(jié)合網(wǎng)上眾多OpenCV On Android的配置教程總結(jié)而來(lái)珊豹,盡希望能幫助學(xué)習(xí)OpenCV的朋友們少走彎路簸呈。如果配置上遇到問(wèn)題,可在評(píng)論中留言店茶,我將盡力幫助解決蜕便。
如果您使用的是Eclipse,請(qǐng)參考上一章OpenCV On Android最佳環(huán)境配置指南(Eclipse篇)忽妒。
環(huán)境
電腦:Windows10
Java:jdk1.8.0_172
Android Studio:Version 4.0.2
SDK:Android Studio 4.0.2自帶的最新SDK(請(qǐng)不要與Eclipse同用一SDK玩裙,以免出錯(cuò))
。
NDK:Android Studio 4.0.2自帶的最新NDK
OpenCV:V4.4.0
注:以上配置向上兼容段直,讀者可使用更新的版本吃溅,但低版本可能出現(xiàn)錯(cuò)誤
(更新時(shí)間:2020-10-12)
配置前說(shuō)明:
本次配置不像上篇介紹Eclipse配置環(huán)境那樣編寫(xiě)多個(gè)Demo,本次將使用一個(gè)Demo鸯檬,將OpenCV Java和NDK配置方式完全包含决侈,盡可能幫助大家去理解,請(qǐng)大家不要跳躍式地閱讀喧务。
同時(shí)OpenCV Java庫(kù)和NDK庫(kù)的優(yōu)缺點(diǎn)在上篇文章里面已經(jīng)提及赖歌,本文就不再贅述。
一功茴、安裝必要組件
1庐冯、打開(kāi)Android Studio。如果是歡迎界面坎穿,選擇Configure->SDK Manager展父。如果是項(xiàng)目界面,選擇Tools->Android->SDK Manager玲昧。
2栖茉、將選項(xiàng)條切換到SDK Tools,勾上左下角的Show Package Details孵延,然后勾選以下四項(xiàng)吕漂,然后OK,開(kāi)始下載尘应。
下載完后惶凝,就可以開(kāi)始創(chuàng)建項(xiàng)目了吼虎。
二、創(chuàng)建Android Studio工程
Create New Projec苍鲜,開(kāi)始選擇模板鲸睛,這時(shí)選擇最后一項(xiàng)Native C++,然后進(jìn)入配置界面坡贺。
這一步需要注意兩個(gè)地方
1、包名:請(qǐng)盡量與我保持一致箱舞,否則新手容易出錯(cuò)遍坟。
2、最小SDK:OpenCV 4.2.0要求最小SDK必須大于21晴股。
下一步直接Finish愿伴,項(xiàng)目創(chuàng)建成功!
項(xiàng)目創(chuàng)建完成后电湘,最好運(yùn)行一下隔节,確保基本環(huán)境沒(méi)問(wèn)題
三寂呛、OpenCV Java庫(kù)使用指南
3.1怎诫、環(huán)境配置:
第一步、將OpenCV Java庫(kù)作為Module導(dǎo)入贷痪。具體步驟為:File->New->Import Module
幻妓,然后將OpenCV-android-sdk\sdk\java目錄導(dǎo)入。如下圖劫拢,然后Next->Finish肉津。
第二步、修改模塊名
默認(rèn)導(dǎo)入的模塊名為java舱沧,為了方便區(qū)分妹沙,建議修改成opencv,步驟如下:
java模塊右鍵Refactor->Rename
第二步熟吏、將導(dǎo)入的opencv模塊從application改成library距糖,步驟如下:
1、將文件預(yù)覽方式切換至Android分俯。
2肾筐、打開(kāi)opencv的build.gradle文件。
3缸剪、將apply plugin: 'com.android.application'
修改成apply plugin: 'com.android.library'
吗铐。
4、刪除(或注釋)掉defaultConfig
內(nèi)容
5杏节、將Run/Debug Configurations從opencv
切換到app
6唬渗、點(diǎn)擊Sync Now使修改生效典阵。
第三步、給項(xiàng)目添加opencv依賴(lài)
菜單File->Project Structure
镊逝,在Dependencies中選擇app壮啊,點(diǎn)擊+
,選擇Module dependency撑蒜,如下圖:
勾選上opencv模塊歹啼,點(diǎn)擊OK即可!
3.2座菠、代碼編寫(xiě):
在A(yíng)ndroidManifest.xml文件中添加權(quán)限:
....
<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_main.xml內(nèi)容修改為以下內(nèi)容:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<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" />
</FrameLayout>
將MainActivity.java改為以下內(nèi)容:
public class MainActivity extends CameraActivity 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 List<? extends CameraBridgeViewBase> getCameraViewList() {
List<CameraBridgeViewBase> list = new ArrayList<>();
list.add(javaCameraView);
return list;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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, 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.gray();
}
}
這里注意一下狸眼,讓我們自己的Activity繼承至opencv的CameraActivity
,是一個(gè)十分不好的做法浴滴,請(qǐng)讀者務(wù)必看懂CameraActivity源碼拓萌,做到活學(xué)活用。
做到這一步升略,我們已經(jīng)能夠編譯出一個(gè)app微王,但是它并不能正常運(yùn)行,這是因?yàn)槲覀冎皇菍pencv的api模塊導(dǎo)入品嚣,但具體代碼沒(méi)包含在apk中炕倘。
我們現(xiàn)在可通過(guò)以下兩種方式來(lái)解決這個(gè)問(wèn)題:
1、在手機(jī)中安裝OpenCV Manager.apk
這種方式及其不好翰撑,原因有以下幾點(diǎn):
1激才、用戶(hù)需要單獨(dú)安裝這個(gè)apk,且不同CPU架構(gòu)的手機(jī)额嘿,apk也不一樣瘸恼。
2、Android高版本中册养,即使安裝了這個(gè)apk东帅,我們的程序也可能不會(huì)正常運(yùn)行。這是因?yàn)橐恍┦謾C(jī)廠(chǎng)商球拦,為了防止應(yīng)用通過(guò)相互喚醒來(lái)實(shí)現(xiàn)應(yīng)用笨勘眨活,所以對(duì)不同應(yīng)用進(jìn)程間調(diào)用進(jìn)行了限制坎炼。比如華為手機(jī)管家中的啟動(dòng)管理愧膀。
3、OpenCV高版本現(xiàn)已不再提供OpenCV Manager.apk
2谣光、將libopencv_java4.so導(dǎo)入到apk中
這種方式的缺點(diǎn)就是:麻煩!!!
如果我們已經(jīng)確定了目標(biāo)機(jī)型檩淋,這種方式無(wú)疑是比較好的。通常來(lái)說(shuō)萄金,如果是真機(jī)蟀悦,導(dǎo)入armeabi或armeabi-v7a架構(gòu)的so文件媚朦;如果是虛擬機(jī),則一般選擇x86架構(gòu)的so文件日戈。
方法2實(shí)現(xiàn)步驟如下:
1询张、將OpenCV庫(kù)中的OpenCV-android-sdk\sdk\native\libs
目錄下4個(gè)子目錄,copy到我們項(xiàng)目的libs目錄下浙炼。
2份氧、修改build.gradle文件,添加以下內(nèi)容:
sourceSets{
main{
jniLibs.srcDirs = ["libs"];
}
}
如圖所示:
做完這一步弯屈,libopencv_java4.so將被自動(dòng)打包進(jìn)apk中半火,但是依舊不能正常運(yùn)行,提示缺少c++_shared季俩,這需要我們?cè)俅涡薷腷uild.gradle文件,添加arguments:
android {
//......
defaultConfig {
//......
externalNativeBuild {
cmake {
cppFlags ""
arguments "-DANDROID_STL=c++_shared"
}
}
}
}
做完以上內(nèi)容梅掠,基本上就OK了酌住。
其實(shí)這里還有一個(gè)坑,由于我們從一開(kāi)始就創(chuàng)建的是一個(gè)Native C++項(xiàng)目阎抒,所以通過(guò)在build.gradle文件中添加arguments參數(shù)酪我,就能將c++_shared.so打包進(jìn)apk,但是如果創(chuàng)建的是普通項(xiàng)目且叁,此方式將無(wú)效都哭,需要手動(dòng)將c++_shared.so添加到libs對(duì)應(yīng)的目錄下
四、OpenCV NDK庫(kù)使用指南
4.1逞带、環(huán)境配置:
Android Studio配置OpenCV環(huán)境灰常簡(jiǎn)單(是的欺矫,沒(méi)錯(cuò)),只需修改一個(gè)文件便能成功配置環(huán)境展氓,什么Android.mk啊穆趴、Application.mk啊,全部滾蛋遇汞。
配置方式:打開(kāi)CMakeLists.txt未妹,內(nèi)容修改如下,(將OpenCV_DIR設(shè)置為你的路徑空入,注意分隔符络它,使用'/'或'\\')
:
cmake_minimum_required(VERSION 3.4.1)
# ##################### OpenCV 環(huán)境 ############################
#設(shè)置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)
# ###################### 項(xiàng)目原生模塊 ###########################
add_library( native-lib
SHARED
src/main/cpp/native-lib.cpp)
target_link_libraries( native-lib
${OpenCV_LIBS}
log
jnigraphics)
OK,環(huán)境配置好了歪赢,嘿嘿嘿化戳,接下來(lái)開(kāi)始代碼編寫(xiě)。
4.2埋凯、代碼編寫(xiě):
菜單File->New->Activity->Empty Activity迂烁,創(chuàng)建一個(gè)新的Activity看尼,其命名下如圖,并設(shè)置為啟動(dòng)頁(yè)盟步,Finish藏斩。
為了分清楚桌面上兩個(gè)程序入口,請(qǐng)?jiān)?strong>AndroidManifest.xml文件中給兩個(gè)Activity指定label却盘,如下圖:
下面開(kāi)始編寫(xiě)布局文件activity_native.xml狰域,內(nèi)容如下:
<?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>
NativeActivity.java內(nèi)容如下:
public class NativeActivity extends AppCompatActivity implements View.OnClickListener {
private ImageView imageView;
static {//加載so庫(kù)
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_native);
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.jpg的圖片放置在drawable目錄下,嘿嘿嘿黄橘!
應(yīng)用層寫(xiě)好了兆览,現(xiàn)在開(kāi)始原生層操作:
第一步:生成頭文件
打開(kāi)Android Studio下方Terminal欄,輸入cd app\src\main\java
(所有應(yīng)用都一樣)塞关,回車(chē)抬探。然后輸入javah -encoding UTF-8 包名.類(lèi)名
(我們這里輸入的是javah -encoding UTF-8 com.demo.opencv.NativeActivity
,注意帆赢,類(lèi)名后面不要加.java或.class)小压,回車(chē)后,將在app\src\main\java
目錄下生成一個(gè)頭文件椰于,如果提示類(lèi)文件不存在怠益,則需先build,再執(zhí)行上述操作瘾婿。最后將該頭文件移動(dòng)到app\src\main\cpp
目錄下蜻牢。
第二步:編寫(xiě)NDK代碼
native-lib.cpp內(nèi)容修改為:
#include "com_demo_opencv_NativeActivity.h"
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
using namespace cv;
extern "C" JNIEXPORT void
JNICALL Java_com_demo_opencv_NativeActivity_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, 45, 75);
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, 45, 75);
cvtColor(gray, temp, COLOR_GRAY2RGB);
}
AndroidBitmap_unlockPixels(env, bitmap);
}
注意,naive-lib.cpp中只有一個(gè)函數(shù)偏陪,這個(gè)函數(shù)名必須與生成的頭文件中定義的一致
運(yùn)行程序抢呆,點(diǎn)擊SHOW按鈕,效果如下:
點(diǎn)擊PROCESS笛谦,效果如下:
完美镀娶,收工,回家吃飯揪罕!
五梯码、總結(jié)
OpenCV On Android 系列配置教程就到此為止,寫(xiě)這兩篇文章確實(shí)也不容易好啰,修改了很多遍轩娶,尤其是這篇Android Studio,算是百忙之中抽空完成的吧框往,也拖了很久鳄抒。自己在配置的過(guò)程中踩了無(wú)數(shù)的坑,希望我的經(jīng)驗(yàn)?zāi)軌驇椭酱蠹疑僮邚澛罚瑫r(shí)也虛心接受大家的批評(píng)與指正许溅。