OpenCV On Android最佳環(huán)境配置指南(Android Studio篇)

聲明:由于簡(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)始下載尘应。

1.png

下載完后惶凝,就可以開(kāi)始創(chuàng)建項(xiàng)目了吼虎。


二、創(chuàng)建Android Studio工程

Create New Projec苍鲜,開(kāi)始選擇模板鲸睛,這時(shí)選擇最后一項(xiàng)Native C++,然后進(jìn)入配置界面坡贺。

2.png

這一步需要注意兩個(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肉津。

3.png

第二步、修改模塊名
默認(rèn)導(dǎo)入的模塊名為java舱沧,為了方便區(qū)分妹沙,建議修改成opencv,步驟如下:
java模塊右鍵Refactor->Rename

第二步熟吏、將導(dǎo)入的opencv模塊從application改成library距糖,步驟如下:

4

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撑蒜,如下圖:

5

勾選上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)入armeabiarmeabi-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"];
    }
}

如圖所示:

6

做完這一步弯屈,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藏斩。

7

為了分清楚桌面上兩個(gè)程序入口,請(qǐng)?jiān)?strong>AndroidManifest.xml文件中給兩個(gè)Activity指定label却盘,如下圖:

8

下面開(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目錄下,嘿嘿嘿黄橘!

9

應(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目錄下蜻牢。

10

第二步:編寫(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按鈕,效果如下:

11

點(diǎn)擊PROCESS笛谦,效果如下:

12

完美镀娶,收工,回家吃飯揪罕!

五梯码、總結(jié)

OpenCV On Android 系列配置教程就到此為止,寫(xiě)這兩篇文章確實(shí)也不容易好啰,修改了很多遍轩娶,尤其是這篇Android Studio,算是百忙之中抽空完成的吧框往,也拖了很久鳄抒。自己在配置的過(guò)程中踩了無(wú)數(shù)的坑,希望我的經(jīng)驗(yàn)?zāi)軌驇椭酱蠹疑僮邚澛罚瑫r(shí)也虛心接受大家的批評(píng)與指正许溅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瓤鼻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贤重,更是在濱河造成了極大的恐慌茬祷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件并蝗,死亡現(xiàn)場(chǎng)離奇詭異祭犯,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)滚停,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)沃粗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人键畴,你說(shuō)我怎么就攤上這事最盅。” “怎么了起惕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵涡贱,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我疤祭,道長(zhǎng),這世上最難降的妖魔是什么饵婆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任勺馆,我火速辦了婚禮,結(jié)果婚禮上侨核,老公的妹妹穿的比我還像新娘草穆。我一直安慰自己,他們只是感情好搓译,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布悲柱。 她就那樣靜靜地躺著,像睡著了一般些己。 火紅的嫁衣襯著肌膚如雪豌鸡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天段标,我揣著相機(jī)與錄音涯冠,去河邊找鬼。 笑死逼庞,一個(gè)胖子當(dāng)著我的面吹牛蛇更,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼派任,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼砸逊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起掌逛,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤师逸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后颤诀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體字旭,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年崖叫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了遗淳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡心傀,死狀恐怖屈暗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脂男,我是刑警寧澤养叛,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站宰翅,受9級(jí)特大地震影響弃甥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汁讼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一淆攻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘿架,春花似錦瓶珊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蝉娜,卻和暖如春唱较,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背召川。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工绊汹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扮宠。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓西乖,卻偏偏與公主長(zhǎng)得像狐榔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子获雕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容