Android Studio 2.2 使用 OpenCV 的两种方式(傻瓜式教程)
阅读原文时间:2021年04月20日阅读:1

本系列文章由 youngpan1101 出品,转载请注明出处。
文章链接: http://blog.csdn.net/youngpan1101/article/details/53614588
作者:宋洋鹏(youngpan1101)
邮箱: yangpeng_song@163.com


简要介绍

Android Studio 2.2 正式版发布后,默认使用 CMake 编译原生库,对于在 Linux 上使用 CMake 来构建工程的小伙伴来说会有种很熟悉的感觉。 下面将介绍在 Android Studio 上使用 OpenCV 库的两种方式(两种方式都无需在手机端安装 OpenCVManager,安装这个东东真挺影响体验的),当然我的亲测也是参考了其他的博客的,这里我先列出可供参考的博客或代码链接:

使用 OpenCV 库的两种方式:
1. 使用动态的 OpenCV 库的方式。
2. 同时使用 Java 的 API 又使用 JNI 的接口的方式,此时编译时一般使用的是动态链接 OpenCV 库的方式。


——————————– 分割线<< 家有小武,如有一母 >>分割线 ——————————–


配置环境

  • Android Studio 2.2, SDK, JDK, NDK 和其他插件下载

  • AS安装可以参考:Windows 环境下 Android Studio v1.0 安装教程

  • 下载 NDK 和构建工具

    NDK: 让你能在 Android 上面使用 C 和 C++ 代码的工具集。
    CMake: 外部构建工具。如果你准备只使用 ndk-build 的话,可以不使用它。
    LLDB: Android Studio 上面调试本地代码的工具。
    你可以使用 SDK Manager 来安装上述组件:

    • 打开一个项目,从菜单栏中选择 Tools > Android > SDK Manager
    • 点击 SDK Tools 选项卡
    • 勾选 LLDB,CMake 和 NDK ,然后安装
  • opencv for android (这里使用的是 opencv 2.4.11)


——————————– 分割线<< 家有小武,如有一母 >>分割线 ——————————–


预备工作

正式介绍三种方式之前,我们需要做一些预备工作。

  1. 新建工程,命令为 OpenCVJNI , 这里 不需要勾选 Include C++ Support

  2. 切换到 工程视图

  3. 添加 javah 命令: File >> Settings >> External Tools >> [+]

    Tool settings
    Program            $JDKPath$\bin\javah
    Parameters         -d $ModuleFileDir$\src\main\jni -jni $FileClass$
    Working directory  $ModuleFileDir$\build\intermediates\classes\debug
  4. 新建 hwj.opencvjni.OpenCVHelper.java

    public class OpenCVHelper {
        public static native String getStringTmp();
    }
  5. Build >> Make Project

  6. 点选 OpenCVHelper.java , 右键 External Tools >> javah ,生成 hwj_opencvjni_OpenCVHelper.h 文件。

  7. 新建 jni / MyLib.cpp 文件

       #include <iostream>
       #include <sstream>
       #include "hwj_opencvjni_OpenCVHelper.h"
       JNIEXPORT jstring JNICALL
       Java_hwj_opencvjni_OpenCVHelper_getStringTmp(JNIEnv *env, jclass instance){
           std::stringstream ss;
           ss << "Hello from c++ " << std::endl;
           return env->NewStringUTF(ss.str().c_str());
       }
  8. 新建 app / CMakeLists.txt :

      # cmake version
      cmake_minimum_required(VERSION 3.4.1)
      # 支持 -std=gnu++11
      set(CMAKE_VERBOSE_MAKEFILE on)
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
      # CMAKE_SOURCE_DIR 是 CMakeLists.txt 所在的目录
      add_library( my-lib SHARED ${CMAKE_SOURCE_DIR}/src/main/jni/MyLib.cpp )
      find_library( log-lib log )
      target_link_libraries( my-lib ${log-lib} )      
  9. app / build.gradle 添加代码:

    apply plugin: 'com.android.application'     
    android {
        compileSdkVersion 24
        buildToolsVersion "24.0.2"    
        defaultConfig {
            applicationId "hwj.opencvjni"
            minSdkVersion 15
            targetSdkVersion 24
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    
            // code block added by myself
            externalNativeBuild {
                cmake {
                    // 指定编译器参数
                    cppFlags "-std=c++11 -frtti -fexceptions"
                    // 生成.so库的目标平台
                    abiFilters 'armeabi', 'armeabi-v7a'
                }
            }           
        }                   
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }       
        // code block added by myself
        // 指定 CMakeLists.txt 相对此 build.gradle 文件的路径
        externalNativeBuild {
            cmake {
                path "CMakeLists.txt"
            }
        }
    }           
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:24.2.1'
        testCompile 'junit:junit:4.12'
    }   
  10. 编译出 libmy-lib.so 文件

    • View >> Tool Windows >> Gradle >> build 生活生成 so 库
    • so 库的存储路径
  11. 修改 activity_main.xml 文件

    <?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:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="hwj.opencvjni.MainActivity">
     <TextView
            android:id="@+id/sample_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
    </RelativeLayout>
  12. 修改 OpenCVHelper.java :

    public class OpenCVHelper {
        static {
            System.loadLibrary("my-lib");
        }
        public static native String getStringTmp();
    }
  13. 修改 MainActivity.java :

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView tv = (TextView) findViewById(R.id.sample_text);
            tv.setText(OpenCVHelper.getStringTmp());
        }
    }
  14. 在手机端跑 Demo

    到这里算是一个 JNI 的调用 CMake 方式编译出来的 so 库的一个小 demo ,通过
    Build >> Analyze APK 可以看到 APK 中是否打包了 so 库:


——————————– 分割线<< 家有小武,如有一母 >>分割线 ——————————–


使用动态 OpenCV 库的方式

  1. 新建 main / OpenCVLib 文件夹, 将 OpenCV SDK 目录 sdk\native\libs 下的 armeabiarmeabi-v7a 复制到 OpenCVLib 目录下:

  2. app / build.gradle

    apply plugin: 'com.android.application'
    android {
        compileSdkVersion 24
        buildToolsVersion "24.0.2"
        defaultConfig {
            applicationId "hwj.opencvjni"
            minSdkVersion 15
            targetSdkVersion 24
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            externalNativeBuild {
                cmake {
                    cppFlags "-std=c++11 -frtti -fexceptions"
                    abiFilters 'armeabi', 'armeabi-v7a'
                }
            }
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
        externalNativeBuild {
            cmake {
                path "CMakeLists.txt"
            }
        }
        // adding
        sourceSets{
            main{
                // let gradle pack the shared library into apk
                jniLibs.srcDirs = ['src/main/OpenCVLib']
            }
        }
    }
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:24.2.1'
        testCompile 'junit:junit:4.12'
    }
  3. CMakeLists.txt :

    # cmake version
    
    cmake_minimum_required(VERSION 3.4.1)
    
    # 支持 -std=gnu++11
    
    set(CMAKE_VERBOSE_MAKEFILE on)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
    
    # opencv import libs
    
    set(pathToOpenCV D:/Opencv/OpenCV-2.4.11-android-sdk)
    include_directories( ${pathToOpenCV}/sdk/native/jni/include )
    add_library( lib_opencv SHARED IMPORTED )
    set_target_properties( lib_opencv PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/OpenCVLib/${ANDROID_ABI}/libopencv_java.so )
    
    # build application's shared lib
    
    add_library( my-lib SHARED ${CMAKE_SOURCE_DIR}/src/main/jni/MyLib.cpp )
    find_library( log-lib log )
    target_link_libraries( my-lib ${log-lib} lib_opencv)
  4. OpenCVHelper.java :

    public class OpenCVHelper {
        static {
            System.loadLibrary("my-lib");
        }
        public static native String getStringTmp();
        // image gray processing 
        public native int[] getGrayImage(int[] pixels, int w, int h);
    }
  5. hwj_opencvjni_OpenCVHelper.h 添加 :

    JNIEXPORT jintArray JNICALL Java_hwj_opencvjni_OpenCVHelper_getGrayImage
      (JNIEnv *env, jobject, jintArray buf, int w, int h);
  6. MyLib.cpp 添加 :

     #include <iostream>
     #include <sstream>
     #include <jni.h>
     #include <opencv2/opencv.hpp>
     #include "hwj_opencvjni_OpenCVHelper.h"
     JNIEXPORT jstring JNICALL
     Java_hwj_opencvjni_OpenCVHelper_getStringTmp(JNIEnv *env, jclass thiz){
        std::stringstream ss;
        ss << "Hello from c++ " << std::endl;
        return env->NewStringUTF(ss.str().c_str());
     }
     JNIEXPORT jintArray JNICALL
     Java_hwj_opencvjni_OpenCVHelper_getGrayImage(JNIEnv *env, jobject, jintArray buf, int w, int h){
        jint *pixels = env->GetIntArrayElements(buf, NULL);
        if(pixels == NULL){
            return NULL;
        }
        cv::Mat imgData(h, w, CV_8UC4, pixels);
        uchar *ptr = imgData.ptr(0);
        for(int i=0; i<w*h; i++){
            int grayScale = (int)(ptr[4*i+2]*0.299 + ptr[4*i+1]*0.587 + ptr[4*i+0]*0.114);
            ptr[4*i+0] = (uchar)grayScale;
            ptr[4*i+1] = (uchar)grayScale;
            ptr[4*i+2] = (uchar)grayScale;
        }
        int size = w * h;
        jintArray result = env->NewIntArray(size);
        env->SetIntArrayRegion(result, 0, size, pixels);
        env->ReleaseIntArrayElements(buf, pixels, 0);
        return result;
    }
  7. 复制一个 jpg 格式的图片到 res / drawable 文件夹下 :

  8. activity_main.xml 添加 ImageView 控件 :

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="hwj.opencvjni.MainActivity">
        <TextView
            android:id="@+id/sample_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
        <ImageView
            android:id="@+id/img"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/Boy"
            android:layout_centerInParent="true" />
        <Button
            android:id="@+id/bt_Gray"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true"
            android:text="Gray"  />
    </RelativeLayout>
  9. MainActivity.java

    public class MainActivity extends AppCompatActivity {
        private Button bt_photo = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView tv = (TextView) findViewById(R.id.sample_text);
            tv.setText(OpenCVHelper.getStringTmp());
            // adding 
            bt_photo = (Button) findViewById(R.id.bt_Gray);
            bt_photo.setOnClickListener(new Button.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    ImageView img = (ImageView)findViewById(R.id.img);
                    Bitmap bitmap = ((BitmapDrawable) getResources().getDrawable(
                            R.drawable.boy)).getBitmap();
                    int w = bitmap.getWidth(), h = bitmap.getHeight();
                    int[] pix = new int[w * h];
                    bitmap.getPixels(pix, 0, w, 0, 0, w, h);
                    int[] resultPixes = OpenCVHelper.getGrayImage(pix,w,h);
                    Bitmap result = Bitmap.createBitmap(w,h, Bitmap.Config.RGB_565);
                    result.setPixels(resultPixes, 0, w, 0, 0,w, h);
                    img.setImageBitmap(result);
                }
            });
        }
    }
  10. 运行程序


——————————– 分割线<< 家有小武,如有一母 >>分割线 ——————————–


JAVA API 使用 OpenCV 库

在前一种方法的基础上,使用纯java层代码进行处理

  1. import module

  2. 修改 opencv 下的 build.gradle 文件(参考 Module: app 下的 build.gradle 文件):

    apply plugin: 'com.android.library'
    android {
        compileSdkVersion 24
        buildToolsVersion "24.0.2"
        defaultConfig {
            minSdkVersion 15
            targetSdkVersion 24
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            }
        }
    }
    dependencies {
    }
  3. Project View >> openCVLibrary2411 右击 >> Open Module Settings >> app >> Dependencies >> + >> Module dependency

  4. 复制 xiao_wu.jpg 到 res / drawable 文件夹下,添加控件(activity_main.xml):

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="hwj.opencvjni.MainActivity">
        <TextView
            android:id="@+id/sample_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
        <ImageView
            android:id="@+id/img"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/boy"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/bt_Gray2"
            />
        <Button
            android:id="@+id/bt_Gray"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true"
            android:text="Gray"  />
        <Button
            android:id="@+id/bt_Gray2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_below="@id/bt_Gray"
            android:text="Gray2"  />
        <ImageView
            android:id="@+id/img2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/xiao_wu"
            android:layout_below="@+id/bt_Gray2"
            android:layout_centerHorizontal="true" />
    </RelativeLayout>
  5. MainActivity.java

    public class MainActivity extends AppCompatActivity {
        private ImageView img = null;
        private Button bt_photo = null;
        private ImageView img2 = null;
        private Button bt_Gray2 = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView tv = (TextView) findViewById(R.id.sample_text);
            tv.setText(OpenCVHelper.getStringTmp());
            img = (ImageView)findViewById(R.id.img);
            img2 = (ImageView)findViewById(R.id.img2);
            bt_photo = (Button) findViewById(R.id.bt_Gray);
            bt_photo.setOnClickListener(new Button.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    Bitmap bitmap = ((BitmapDrawable) getResources().getDrawable(
                            R.drawable.boy)).getBitmap();
                    int w = bitmap.getWidth(), h = bitmap.getHeight();
                    int[] pix = new int[w * h];
                    bitmap.getPixels(pix, 0, w, 0, 0, w, h);
                    int[] resultPixes = OpenCVHelper.getGrayImage(pix,w,h);
                    Bitmap result = Bitmap.createBitmap(w,h, Bitmap.Config.RGB_565);
                    result.setPixels(resultPixes, 0, w, 0, 0,w, h);
                    img.setImageBitmap(result);
                }
            });
            bt_Gray2 = (Button) findViewById(R.id.bt_Gray2);
            bt_Gray2.setOnClickListener(new Button.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO Auto-generated method stub
                    OpenCVLoader.initDebug();
                    Mat rgbMat = new Mat();
                    Mat grayMat = new Mat();
                    Bitmap srcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xiao_wu);
                    Bitmap 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
                    img2.setImageBitmap(grayBitmap);
                }
            });
        }
    }
  6. 运行程序


    以上的 Demo 工程可通过 Baidu云 下载(包括opencv_2.4.11 for Android)