Android Stuido 導(dǎo)入OpenCv并使用的三種方式填坑版

一. 資料準(zhǔn)備

Android Studio 3.6
OpenCv Sdk下載:https://opencv.org/releases/#
選 OpenCV – 4.3.0 android下載,比較慢,可以考慮使用迅雷下載
下載解壓后有samples和sdk兩個(gè)目錄,主要關(guān)注sdk目錄:

在這里插入圖片描述

二.As使用opencv sdk的三種方式

2.1 在Java層使用 OpenCv Java API

  1. 新建普通的android app項(xiàng)目:


    在這里插入圖片描述

    看到已經(jīng)有l(wèi)ibs文件夾,但main中比ndk項(xiàng)目少了cpp目錄,且無(wú)cmakelist文件,先用著吧,后面看看是否可以手動(dòng)添加.

  2. file->new->import module,選擇下載解壓后的opencv sdk的如下目錄,導(dǎo)入opencv的java module,實(shí)際是opencv自己實(shí)現(xiàn)的調(diào)用JNI接口的java層:


    在這里插入圖片描述
  3. 修改java_opencv的模塊屬性:如下


    在這里插入圖片描述
  4. 打開(kāi)openCvDemo的 project sturcture 設(shè)置庫(kù)依賴(lài),當(dāng)然也可以在app的build gradle文件中dependencies {}中添加 " implementation project(path: ':java_opencv')" 這一行


    在這里插入圖片描述
  5. 將OpenCV-android-sdk/sdk/native/libs下的arm64-v8a 目錄拷貝到Android studio OpenCvDemo的libs文件夾中,在app的build.gradle中的android節(jié)點(diǎn)下添加
    jniLib.srcDir,該變量指明了JNI所需要的動(dòng)態(tài)庫(kù)目錄,如下:


    在這里插入圖片描述

    注意需要將libc++shared.so拷貝到j(luò)nilib目錄下,原因如圖所示,可以從其他項(xiàng)目中拷貝過(guò)來(lái),也可以從ndk的toolchain中尋找拷貝.

(

或者也可以這樣處理:

新建如上圖中的cpp目錄,或自定義目錄,寫(xiě)個(gè)空類(lèi)似ndk項(xiàng)目初始化時(shí)的cpp文件,注意,如果是一開(kāi)始建的是非ndk項(xiàng)目,建完自定義jni文件之后需要手動(dòng)制定jni.srcDirs

否則自己寫(xiě)的cpp無(wú)法編譯成動(dòng)態(tài)庫(kù)打包到apk中,具體cpp可以參考ndk項(xiàng)目默認(rèn)初始時(shí)的native-lib文件.在android節(jié)點(diǎn)里的defaultConfig節(jié)點(diǎn)里的externalNativeBuild節(jié)點(diǎn)里添加" arguments "-DANDROID_STL=c++_shared" "

這樣,在編譯native-lib時(shí)便會(huì)自動(dòng)拷貝libc++_static.so打包到apk中.如下圖

在這里插入圖片描述

這里有個(gè)小坑,從網(wǎng)上拷貝了一個(gè)

sourceSets.main{
XXXX
}

來(lái)寫(xiě),結(jié)果死活沒(méi)編到制定的jni.srcDirs目錄下的代碼,

改成

sourceSets {
    main{
XXXX
    }
}

醉了
)

  1. 開(kāi)始寫(xiě)代碼
    activity:
package com.chengang.opencvdemo;
 
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
 
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
 
public class MainActivity extends AppCompatActivity  implements View.OnClickListener{
 
    String TAG="MainActivity";
 
    ImageView img_after;
    TextView text_togray;
    Bitmap srcBitmap;
    Bitmap grayBitmap;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        img_after=(ImageView)findViewById(R.id.imageView_after);
        text_togray=(TextView)findViewById(R.id.textView_togray);
        text_togray.setOnClickListener(this);
 
        System.loadLibrary("opencv_java4");
    }
 
    @Override
    public void onClick(View v) {
        switch(v.getId())
        {
            case R.id.textView_togray:
                procSrc2Gray();
                img_after.setImageBitmap(grayBitmap) ;
                break;
        }
    }
 
    public void procSrc2Gray(){
        Mat rgbMat = new Mat();
        Mat grayMat = new Mat();
        srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.iu);
        grayBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.RGB_565);
        Utils.bitmapToMat(srcBitmap, rgbMat);//convert original bitmap to Mat, R G B.
        Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY);//rgbMat to gray grayMat
        Utils.matToBitmap(grayMat, grayBitmap); //convert mat to bitmap
        Log.i(TAG, "procSrc2Gray sucess...");
    }
 
    @Override
    public void onResume()
    {
        super.onResume();
    }
 
}

layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="0dp"
    android:paddingLeft="0dp"
    android:paddingRight="0dp"
    android:paddingTop="0dp"
    tools:context=".MainActivity">
 
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
 
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1">
 
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="原圖"
                android:id="@+id/textView" />
 
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/imageView_before"
                android:src="@drawable/iu"
                />
        </LinearLayout>
 
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="3dp"
            android:background="@color/material_grey_900"></LinearLayout>
 
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1">
 
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="灰度化"
                android:id="@+id/textView_togray" />
 
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/imageView_after" />
        </LinearLayout>
 
    </LinearLayout>
 
</RelativeLayout>

再放一張IU的圖片到res/drawable/下


在這里插入圖片描述

7.ok編譯運(yùn)行可執(zhí)行.


在這里插入圖片描述

2.2 自定義JNI使用Native API實(shí)現(xiàn)

上面這種方式需要導(dǎo)入opencv的java module,相當(dāng)于導(dǎo)入了opencv實(shí)現(xiàn)的調(diào)用native接口的jar包,有時(shí)候我們不需要那么多接口,也許只需要一個(gè)功能,導(dǎo)入這么多有點(diǎn)浪費(fèi)空間啊,

因此可以避免使用java module 直接使用opencv的native api去使用.嘗試一下,

看activity調(diào)用的opencv 方法:

/home/chengang/AndroidStudioProjects/OpenCvDemo/app/src/main/java/com/chengang/opencvdemo/MainActivity.java
public void procSrc2Gray(){
    Mat rgbMat = new Mat();
    Mat grayMat = new Mat();
    srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.iu);
    grayBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.RGB_565);
    Utils.bitmapToMat(srcBitmap, rgbMat);//convert original bitmap to Mat, R G B.
    Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY);//rgbMat to gray grayMat
    Utils.matToBitmap(grayMat, grayBitmap); //convert mat to bitmap
    Log.i(TAG, "procSrc2Gray sucess...");
}

主要是該方法實(shí)現(xiàn)
Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY);//rgbMat to gray grayMat
在java_opencv module中搜cvColor

/home/chengang/AndroidStudioProjects/OpenCvDemo/java_opencv/src/main/java/org/opencv/imgproc/Imgproc.java
/**
 * Converts an image from one color space to another.
....
 *
 * SEE: REF: imgproc_color_conversions
 */
public static void cvtColor(Mat src, Mat dst, int code) {
    cvtColor_1(src.nativeObj, dst.nativeObj, code);
}
在這里插入圖片描述

在sdk中搜頭文件:


在這里插入圖片描述
  1. 頭文件準(zhǔn)備
    在/home/chengang/AndroidStudioProjects/OpenCvDemo/app/src/main/cpp/下新建myIncludes文件夾將OpenCV-android-sdk/sdk/native/jni/include/下的opencv2目錄拷貝到myIncludes下,目錄在cmakelist.txt中要指明好,cpp下的CmakeList.txt修改如下圖::


    在這里插入圖片描述

    在這里插入圖片描述

    app module的build.gradle修改:


    在這里插入圖片描述
  2. 修改代碼
    好,開(kāi)始實(shí)現(xiàn),基于上面已完成的Maincativity修改如下:
/home/chengang/AndroidStudioProjects/OpenCvDemo/app/src/main/java/com/chengang/opencvdemo/MainActivity.java
// 1.添加如下JNI函數(shù)定義,之后IDE會(huì)提示實(shí)現(xiàn)該定義,則會(huì)在native-lib.cpp中去實(shí)現(xiàn),后面貼上
private native void cvtColorFromJNI2(Bitmap srcBitmap,Bitmap dstBitmap);
 
//改寫(xiě)procSrc2Gray為procSrc2Gray2
public void procSrc2Gray2(){
    srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.iu);
    grayBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.RGB_565);
    cvtColorFromJNI2(srcBitmap,grayBitmap);
    Log.i(TAG, "procSrc2Gray sucess...");
}
 
 
//3.調(diào)用的地方改為使用procSrc2Gray2
@Override
public void onClick(View v) {
    switch(v.getId())
    {
        case R.id.textView_togray:
            //procSrc2Gray();
            procSrc2Gray2();
            img_after.setImageBitmap(grayBitmap) ;
            break;
    }
}

/home/chengang/AndroidStudioProjects/OpenCvDemo/app/src/main/cpp/native-lib.cpp實(shí)現(xiàn)如下:
主要完成:
a. native實(shí)現(xiàn)Bitmap2mat和mat2bitmap的實(shí)現(xiàn),基于opencv基本是對(duì)mat數(shù)據(jù)結(jié)構(gòu)處理的要求.
b.直接調(diào)用opencv的cv::cvtColor()函數(shù)實(shí)現(xiàn)置灰操作.

#include <jni.h>
#include <string>
#include <opencv2/imgproc.hpp>
#include <opencv2/core/mat.hpp>
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs/legacy/constants_c.h>
 
 
using namespace cv;
using namespace std;
 
void BitmapToMat2(JNIEnv *env, jobject& bitmap, Mat& mat, jboolean needUnPremultiplyAlpha) {
    AndroidBitmapInfo info;
    void *pixels = 0;
    Mat &dst = mat;
 
    try {
        //LOGD("nBitmapToMat");
        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);
        dst.create(info.height, info.width, CV_8UC4);
        if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
           // LOGD("nBitmapToMat: RGBA_8888 -> CV_8UC4");
            Mat tmp(info.height, info.width, CV_8UC4, pixels);
            if (needUnPremultiplyAlpha) cvtColor(tmp, dst, COLOR_mRGBA2RGBA);
            else tmp.copyTo(dst);
        } else {
            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
           // LOGD("nBitmapToMat: RGB_565 -> CV_8UC4");
            Mat tmp(info.height, info.width, CV_8UC2, pixels);
            cvtColor(tmp, dst, COLOR_BGR5652RGBA);
        }
        AndroidBitmap_unlockPixels(env, bitmap);
        return;
    } catch (const cv::Exception &e) {
        AndroidBitmap_unlockPixels(env, bitmap);
       // LOGE("nBitmapToMat catched cv::Exception: %s", e.what());
        jclass je = env->FindClass("org/opencv/core/CvException");
        if (!je) je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return;
    } catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        //LOGE("nBitmapToMat catched unknown exception (...)");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nBitmapToMat}");
        return;
    }
}
 
void BitmapToMat(JNIEnv *env, jobject& bitmap, Mat& mat) {
    BitmapToMat2(env, bitmap, mat, false);
}
 
void MatToBitmap2
        (JNIEnv *env, Mat& mat, jobject& bitmap, jboolean needPremultiplyAlpha) {
    AndroidBitmapInfo info;
    void *pixels = 0;
    Mat &src = mat;
 
    try {
       // LOGD("nMatToBitmap");
        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(src.dims == 2 && info.height == (uint32_t) src.rows &&
                  info.width == (uint32_t) src.cols);
        CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
        CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
        CV_Assert(pixels);
        if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
            Mat tmp(info.height, info.width, CV_8UC4, pixels);
            if (src.type() == CV_8UC1) {
              //  LOGD("nMatToBitmap: CV_8UC1 -> RGBA_8888");
                cvtColor(src, tmp, COLOR_GRAY2RGBA);
            } else if (src.type() == CV_8UC3) {
               // LOGD("nMatToBitmap: CV_8UC3 -> RGBA_8888");
                cvtColor(src, tmp, COLOR_RGB2RGBA);
            } else if (src.type() == CV_8UC4) {
               // LOGD("nMatToBitmap: CV_8UC4 -> RGBA_8888");
                if (needPremultiplyAlpha)
                    cvtColor(src, tmp, COLOR_RGBA2mRGBA);
                else
                    src.copyTo(tmp);
            }
        } else {
            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
            Mat tmp(info.height, info.width, CV_8UC2, pixels);
            if (src.type() == CV_8UC1) {
               // LOGD("nMatToBitmap: CV_8UC1 -> RGB_565");
                cvtColor(src, tmp, COLOR_GRAY2BGR565);
            } else if (src.type() == CV_8UC3) {
               // LOGD("nMatToBitmap: CV_8UC3 -> RGB_565");
                cvtColor(src, tmp, COLOR_RGB2BGR565);
            } else if (src.type() == CV_8UC4) {
                //LOGD("nMatToBitmap: CV_8UC4 -> RGB_565");
                cvtColor(src, tmp, COLOR_RGBA2BGR565);
            }
        }
        AndroidBitmap_unlockPixels(env, bitmap);
        return;
    } catch (const cv::Exception &e) {
        AndroidBitmap_unlockPixels(env, bitmap);
        //LOGE("nMatToBitmap catched cv::Exception: %s", e.what());
        jclass je = env->FindClass("org/opencv/core/CvException");
        if (!je) je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return;
    } catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        //LOGE("nMatToBitmap catched unknown exception (...)");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
        return;
    }
}
 
void MatToBitmap(JNIEnv *env, Mat& mat, jobject& bitmap) {
    MatToBitmap2(env, mat, bitmap, false);
}
 
extern "C"
JNIEXPORT void JNICALL
Java_com_chengang_opencvdemo_MainActivity_cvtColorFromJNI2(JNIEnv *env, jobject thiz,
                                                           jobject src_bitmap, jobject dst_bitmap) {
    // TODO: implement cvtColorFromJNI2()
    Mat srcImageMat;
    BitmapToMat(env,src_bitmap,srcImageMat);
    Mat dstImageMat;
    cv::cvtColor(srcImageMat,dstImageMat,COLOR_RGB2GRAY);
    MatToBitmap(env,dstImageMat,dst_bitmap);
}

這樣,是沒(méi)有使用java的api的,我們自定義了一個(gè)JNI函數(shù)來(lái)自己實(shí)現(xiàn)調(diào)用opencv的native接口函數(shù)處理,
這時(shí),將app module的build.gradle中的dependencies節(jié)點(diǎn)中的"implementation project(path: ':java_opencv')"給刪掉或注釋掉,去掉依賴(lài)java module,
再在MainActivity中關(guān)于java_opencv module的使用地方給刪掉或注釋掉,
即不再使用opencv提供的java module api.

  1. 可以編譯成功,安裝運(yùn)行成功.

2.3 使用openCv靜態(tài)庫(kù)編譯動(dòng)態(tài)庫(kù)實(shí)現(xiàn)

opencv sdk中有提供靜態(tài)庫(kù)及所依賴(lài)的3rdparty的靜態(tài)庫(kù),可以利用這些靜態(tài)庫(kù)鏈接實(shí)現(xiàn)功能的動(dòng)態(tài)庫(kù)native-lib.so

基于上一步的代碼這么干:
1.把OpenCV-android-sdk/sdk/native/3rdparty/libs 下需要的arm64-v8a 架構(gòu)的文件夾拷貝到app目錄下的libs文件夾下

2.把OpenCV-android-sdk/sdk/native/下的staticlibs文件夾拷貝到app目錄下的libs文件夾下.當(dāng)然,可以只保留我們需要的arm64-v8a 架構(gòu),其他架構(gòu)的可以刪掉(基于手上有的機(jī)型abi架構(gòu)是arm64的,只需要arm64的即可).

3.在cpp目錄下cmakelist.txt文件進(jìn)行配置如下圖:


在這里插入圖片描述

在這里插入圖片描述

4.修改代碼,把libs下的opencv的動(dòng)態(tài)庫(kù)libopencv_java4.so刪掉,不需要啦,opencv的功能已經(jīng)通過(guò)靜態(tài)鏈接從opencv的靜態(tài)庫(kù)中鏈接到了
我們自己的native-lib.so中了,最終只需一個(gè)native-lib.so動(dòng)態(tài)庫(kù).


在這里插入圖片描述

activty:
/home/chengang/AndroidStudioProjects/OpenCvDemo/app/src/main/java/com/chengang/opencvdemo/MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
 
    img_after=(ImageView)findViewById(R.id.imageView_after);
    text_togray=(TextView)findViewById(R.id.textView_togray);
    text_togray.setOnClickListener(this);
 
    //System.loadLibrary("opencv_java4");//記得不需要load opencv_java4了
    System.loadLibrary("native-lib"); //記得需要load 包含opencv功能的native-lib 動(dòng)態(tài)庫(kù)
}
  1. 運(yùn)行吧,安裝成功,運(yùn)行成功.

三.對(duì)比Apk size

第一種使用Java Module實(shí)現(xiàn)Java API調(diào)用OpenCv 動(dòng)態(tài)庫(kù)Native API方式:
apk size:20.7MB


在這里插入圖片描述

第二種使用自定義JNI So 調(diào)用 OpenCv 動(dòng)態(tài)庫(kù)Native API方:
apk size:20.4MB


在這里插入圖片描述

第三種使用opencv 靜態(tài)庫(kù)鏈接JNI 動(dòng)態(tài)庫(kù)native-lib.so方式:
apk size:5.6MB
在這里插入圖片描述

看出前兩種相差不大,都包含一個(gè)完整的opencv動(dòng)態(tài)庫(kù),而第三種只有一個(gè)libnative-lib.so,而libnative-lib.so包含的是靜態(tài)鏈接來(lái)的cpp中僅僅使用到的opencv的功能模塊,我們這個(gè)demo僅僅用了下置灰功能,
看樣子還是第三種比較實(shí)在,在size上完勝前兩種.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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