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

簡介

本文是《OpenCV On Android最佳環境配置指南》 系列教程第二篇,也是配置系列的最後一篇,適合使用Android Studio的開發人員學習。java

本教程是通過本人屢次踩坑,並結合網上衆多OpenCV On Android的配置教程總結而來,盡但願能幫助學習OpenCV的朋友們少走彎路。若是配置上遇到問題,可在評論中留言,我將盡力幫助解決。android

若是您使用的是Eclipse,請參考上一章OpenCV On Android最佳環境配置指南(Eclipse篇)c++

若有轉載,請標明出處api

(更新時間:2020-10-12)markdown


環境

  • 電腦:Windows10
  • Java:jdk1.8.0_172
  • Android Studio:Version 4.0.2
  • SDK:Android Studio 4.0.2自帶的最新SDK (請不要與Eclipse同用一SDK,以避免出錯)
  • NDK:Android Studio 4.0.2自帶的最新NDK
  • OpenCV:V4.4.0

注:以上配置向上兼容,讀者可以使用更新的版本,但低版本可能出現錯誤架構


配置前說明:

本次配置不像上篇介紹Eclipse配置環境那樣編寫多個Demo,而是經過一個Demo,將OpenCV的JavaNDK配置方式所有講完,儘量手把手講解,請你們不要跳躍式地閱讀。app


1、安裝必要組件

  1. 打開Android Studio設置界面,進入Appearance & Behavior -> System Settings -> Android SDK
  2. 將選項條切換到SDK Tools,勾上左下角的Show Package Details,而後按下圖勾選:

1.png

點擊OK,開始下載。下載完後,就能夠開始建立項目了。ide


2、建立Android Studio工程

Create New Project,選擇最後面的Native C++模板,而後進入配置界面。oop

2.png

這一步須要注意兩個地方佈局

一、包名:請儘可能與我保持一致,不然新手容易出錯。
二、最小SDK:OpenCV 4.2.0要求最小SDK不小於21

直接Finish,項目建立成功!

提示:項目建立完成後,最好運行一下,確保基本環境沒問題。


3、OpenCV Java庫使用指南

3.一、環境配置

第一步:將OpenCV Java庫做爲Module導入。

具體步驟爲:File->New->Import Module,而後將OpenCV-android-sdk\sdk\java目錄導入。以下圖,而後Next->Finish

3.png

第二步:修改模塊名。

默認導入的模塊名爲java,爲了方便區分,建議修改爲opencv,只需在java模塊右鍵,而後Refactor->Rename

第二步:將導入的opencv模塊從application改爲library,步驟以下:

4

一、將文件預覽方式切換至Android。
二、打開opencv的build.gradle文件。
三、將apply plugin: 'com.android.application'修改爲apply plugin: 'com.android.library'
四、刪除(或註釋)掉defaultConfig內容。
五、將Run/Debug Configurations從opencv切換到app
六、點擊Sync Now使修改生效。

第三步:給項目添加opencv依賴

菜單File->Project Structure,在Dependencies中選擇app,點擊+,選擇Module dependency,而後勾選opencv模塊,點擊OK便可!以下圖:

5

3.二、代碼編寫

在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_main.xml內容修改成如下內容:

<?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改成如下內容:

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();
    }
}
複製代碼

這裏須要注意,咱們的MainActivity繼承至CameraActivity,這是爲了解決黑屏的問題。若是不這樣作,則須要手動處理權限,並調用JavaCameraViewsetCameraPermissionGranted()方法。

3.三、運行程序

到了這一步,咱們已經可以編譯出一個app,可是它並不能正常運行,這是由於咱們只是將opencv的api模塊導入,但具體代碼並未包含在apk中。

咱們如今可經過如下兩種方式來解決這個問題:

一、在手機中安裝OpenCV Manager.apk

這種方式有如下幾個缺點:

一、用戶須要單獨安裝這個apk,且不一樣ARM架構的手機,apk也不同。
二、在Android高版本中,一些手機廠商爲了防止應用相互喚醒,對手機作出了必定限制通(好比華爲手機管家中的啓動管理),這可能會照成咱們的App調用不到OpenCV Manager
三、OpenCV高版本現已再也不提供 OpenCV Manager.apk


二、將libopencv_java4.so導入到apk中

這種方式的缺點就是:麻煩!!!,但若是咱們已經肯定了目標機型,這種方式無疑是比較好的。

一般來講,若是是真機,導入armeabiarmeabi-v7a架構的so文件;若是是虛擬機,則通常選擇x86架構的so文件。

方法 2 具體的實現步驟以下:

一、將OpenCV庫中的OpenCV-android-sdk\sdk\native\libs目錄下4個子目錄,copy到咱們項目的libs目錄下。

二、修改build.gradle文件,添加如下內容:

sourceSets{
    main{
        jniLibs.srcDirs = ["libs"];
    }
}
複製代碼

如圖所示:

6

作完這一步,libopencv_java4.so將被自動打包進apk中,但可能仍是會報錯。

通常來講,會提示缺乏c++_shared,這就須要咱們再次修改build.gradle文件,添加arguments:

android {
    //......
    defaultConfig {
        //......
        externalNativeBuild {
            cmake {
                cppFlags ""
                arguments "-DANDROID_STL=c++_shared"
            }
        }
    }
}
複製代碼

作完以上內容,基本上就OK了。

其實這裏還有一個坑,因爲咱們從一開始就建立的是一個Native C++項目,因此經過在build.gradle文件中添加arguments參數,就能將c++_shared.so打包進apk,可是若是建立的是普通項目,此方式將無效,須要手動將c++_shared.so添加到libs對應的目錄下。


4、OpenCV NDK庫使用指南

4.一、環境配置

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,環境配置好了,嘿嘿嘿,接下來開始編寫OpenCV代碼了。

4.二、編寫應用層代碼

菜單File->New->Activity->Empty Activity,建立一個新的Activity,其命名下如圖,並設置爲啓動頁,Finish

7

爲了分清楚桌面上兩個程序入口,請在AndroidManifest.xml文件中給兩個Activity指定label,以下圖:

8

下面開始編寫佈局文件activity_native.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>
複製代碼

NativeActivity.java內容以下:

public class NativeActivity 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_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

4.三、編寫原生層代碼

原生層代碼,說白了,就是用C/C++編寫程序。

一、生成方法名

因爲NativeActivity中的getEdge是一個native方法,尚未具體的實現,全部在Android Studio中,會報紅色警告。

此時,只需將鼠標點到該方法名,而後Alt+Enter,就會在native-lib.cpp文件中生成該方法的聲明。

注:若是沒法生成,請使用javah命令,具體請百度。

一、編寫NDK代碼

native-lib.cpp內容修改成:

#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);
}
複製代碼

注意,只替換內容,不要將方法名也替換了。

運行程序,效果以下:

11

完美,收工,回家吃飯!

5、總結

OpenCV On Android 系列配置教程就到此爲止,寫這兩篇文章確實也不容易,修改了不少遍,尤爲是這篇Android Studio,算是百忙之中抽空完成的吧,也拖了好久。本身在配置的過程當中踩了無數的坑,但願個人經驗可以幫助到你們少走彎路,同時也虛心接受你們的批評與指正。

移動端圖像處理的路還長,我也將不斷去學習充實本身,這兩篇文章算是了卻個人一個心願,下面是我創的學習羣,我也將不按期幫助你們解決問題。

請加羣的人保持一個和藹的心,分享經驗,共同進步,記住:別人幫助你不是必須的

OpenCV On Android學習羣.png

相關文章
相關標籤/搜索