美文网首页
Android Stuido 导入OpenCv并使用的三种方式填

Android Stuido 导入OpenCv并使用的三种方式填

作者: 马小藤 | 来源:发表于2020-04-28 19:01 被阅读0次

    一. 资料准备

    Android Studio 3.6
    OpenCv Sdk下载:https://opencv.org/releases/#
    选 OpenCV – 4.3.0 android下载,比较慢,可以考虑使用迅雷下载
    下载解压后有samples和sdk两个目录,主要关注sdk目录:

    在这里插入图片描述

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

    2.1 在Java层使用 OpenCv Java API

    1. 新建普通的android app项目:


      在这里插入图片描述

      看到已经有libs文件夹,但main中比ndk项目少了cpp目录,且无cmakelist文件,先用着吧,后面看看是否可以手动添加.

    2. file->new->import module,选择下载解压后的opencv sdk的如下目录,导入opencv的java module,实际是opencv自己实现的调用JNI接口的java层:


      在这里插入图片描述
    3. 修改java_opencv的模块属性:如下


      在这里插入图片描述
    4. 打开openCvDemo的 project sturcture 设置库依赖,当然也可以在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节点下添加
      jniLib.srcDir,该变量指明了JNI所需要的动态库目录,如下:


      在这里插入图片描述

      注意需要将libc++shared.so拷贝到jnilib目录下,原因如图所示,可以从其他项目中拷贝过来,也可以从ndk的toolchain中寻找拷贝.

    (

    或者也可以这样处理:

    新建如上图中的cpp目录,或自定义目录,写个空类似ndk项目初始化时的cpp文件,注意,如果是一开始建的是非ndk项目,建完自定义jni文件之后需要手动制定jni.srcDirs

    否则自己写的cpp无法编译成动态库打包到apk中,具体cpp可以参考ndk项目默认初始时的native-lib文件.在android节点里的defaultConfig节点里的externalNativeBuild节点里添加" arguments "-DANDROID_STL=c++_shared" "

    这样,在编译native-lib时便会自动拷贝libc++_static.so打包到apk中.如下图

    在这里插入图片描述
    这里有个小坑,从网上拷贝了一个
    sourceSets.main{
    XXXX
    }
    

    来写,结果死活没编到制定的jni.srcDirs目录下的代码,

    改成

    sourceSets {
        main{
    XXXX
        }
    }
    

    醉了
    )

    1. 开始写代码
      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编译运行可执行.


    在这里插入图片描述

    2.2 自定义JNI使用Native API实现

    上面这种方式需要导入opencv的java module,相当于导入了opencv实现的调用native接口的jar包,有时候我们不需要那么多接口,也许只需要一个功能,导入这么多有点浪费空间啊,

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

    看activity调用的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...");
    }
    

    主要是该方法实现
    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. 头文件准备
      在/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. 修改代码
      好,开始实现,基于上面已完成的Maincativity修改如下:
    /home/chengang/AndroidStudioProjects/OpenCvDemo/app/src/main/java/com/chengang/opencvdemo/MainActivity.java
    // 1.添加如下JNI函数定义,之后IDE会提示实现该定义,则会在native-lib.cpp中去实现,后面贴上
    private native void cvtColorFromJNI2(Bitmap srcBitmap,Bitmap dstBitmap);
     
    //改写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.调用的地方改为使用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实现如下:
    主要完成:
    a. native实现Bitmap2mat和mat2bitmap的实现,基于opencv基本是对mat数据结构处理的要求.
    b.直接调用opencv的cv::cvtColor()函数实现置灰操作.

    #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);
    }
    

    这样,是没有使用java的api的,我们自定义了一个JNI函数来自己实现调用opencv的native接口函数处理,
    这时,将app module的build.gradle中的dependencies节点中的"implementation project(path: ':java_opencv')"给删掉或注释掉,去掉依赖java module,
    再在MainActivity中关于java_opencv module的使用地方给删掉或注释掉,
    即不再使用opencv提供的java module api.

    1. 可以编译成功,安装运行成功.

    2.3 使用openCv静态库编译动态库实现

    opencv sdk中有提供静态库及所依赖的3rdparty的静态库,可以利用这些静态库链接实现功能的动态库native-lib.so

    基于上一步的代码这么干:
    1.把OpenCV-android-sdk/sdk/native/3rdparty/libs 下需要的arm64-v8a 架构的文件夹拷贝到app目录下的libs文件夹下

    2.把OpenCV-android-sdk/sdk/native/下的staticlibs文件夹拷贝到app目录下的libs文件夹下.当然,可以只保留我们需要的arm64-v8a 架构,其他架构的可以删掉(基于手上有的机型abi架构是arm64的,只需要arm64的即可).

    3.在cpp目录下cmakelist.txt文件进行配置如下图:


    在这里插入图片描述
    在这里插入图片描述

    4.修改代码,把libs下的opencv的动态库libopencv_java4.so删掉,不需要啦,opencv的功能已经通过静态链接从opencv的静态库中链接到了
    我们自己的native-lib.so中了,最终只需一个native-lib.so动态库.


    在这里插入图片描述
    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 动态库
    }
    
    1. 运行吧,安装成功,运行成功.

    三.对比Apk size

    第一种使用Java Module实现Java API调用OpenCv 动态库Native API方式:
    apk size:20.7MB


    在这里插入图片描述

    第二种使用自定义JNI So 调用 OpenCv 动态库Native API方:
    apk size:20.4MB


    在这里插入图片描述
    第三种使用opencv 静态库链接JNI 动态库native-lib.so方式:
    apk size:5.6MB
    在这里插入图片描述

    看出前两种相差不大,都包含一个完整的opencv动态库,而第三种只有一个libnative-lib.so,而libnative-lib.so包含的是静态链接来的cpp中仅仅使用到的opencv的功能模块,我们这个demo仅仅用了下置灰功能,
    看样子还是第三种比较实在,在size上完胜前两种.

    相关文章

      网友评论

          本文标题:Android Stuido 导入OpenCv并使用的三种方式填

          本文链接:https://www.haomeiwen.com/subject/dmvbwhtx.html