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

简介

本文是《OpenCV On Android最佳环境配置指南》系列教程第二篇,也是配置教程的最后一篇。通过对Android Studio里OpenCV环境配置讲解,快速帮新人解决入门配置问题。
本文内容是本人经过多次踩坑,并参考网上众多OpenCV On Android的配置教程总结而来,尽希望能帮助学习移动图像处理的朋友们少走弯路,如有转载,请标明出处http://www.jianshu.com/p/6e16c0429044
本篇文章写的时候有点匆忙,肯定有遗漏之处,后续将持续补充。同时如有不足之处,希望各位dalao能够指正,我也将及时修改。配置上如果遇到问题,也可以在评论里留言,我将尽力帮助解决。
如果您使用的是Eclipse,请参考前一章OpenCV On Android最佳环境配置指南(Eclipse篇)


环境

电脑:Windows10
Java:jdk1.8.0_172
Android Studio:Version 3.1.3
SDK:Android Studio 3.1.3自带的最新SDK(请不要与Eclipse同用一SDK,以免出错)
NDK:Android Studio 3.1.3自带的最新NDK
OpenCV:V3.4.1

注:以上配置基本上为最新版本


问答:

1、我已经会使用Eclipse搭建OpenCV Java和NDK环境了,为什么还需要看你这篇文章?

答:原因有以下几点:
1、现实原因,参见为什么要用Android Studio?,可见Android Studio迟早会称霸Android IDE界;
2、配置原因,OpenCV在Eclipse配置非常复杂(我当初配置了接近一个月),而Android Studio则简单到令人泪奔。
3、调试原因,也是最重要的一点,Eclipse 原生代码调试能力弱,且容易出现误报,相比较,Android Studio稳定多了,且调试非常方便。

2、我已经会使用Android Studio搭建OpenCV Java和NDK环境了,为什么还需要看你这篇文章,你这篇文章有什么特殊之处吗?

答:我也不是一个喜欢重复造轮子的人,既然写了这篇文章,肯定会去介绍一种不同的配置方式。正如本教程题目所提到的"最新"这个词,我敢能保证我的配置方式是紧跟“时代潮流”。
传统的Android Studio配置OpenCV环境与Eclipse无异,都是采用Android.mk文件进行配置,这种方式配置的缺点在于原生代码不具有自动提示功能。而本文采用CMake进行配置,更加方便,简洁,这种方式也是Android Studio所提倡的。


配置前说明:

本次配置不像上篇介绍Eclipse配置环境那样编写多个Demo,本次将使用一个Demo,将OpenCV Java和NDK配置方式完全包含,尽可能帮助大家去理解,请大家不要跳跃式地阅读。
同时OpenCV Java库和NDK库的优缺点在上篇文章里面已经提及,本文就不再赘述。


一、安装必要组键

1、打开Android Studio。如果是欢迎界面,选择Configure->SDK Manager。如果是项目界面,选择Tools->Android->SDK Manager
2、将选项条切换到SDK Tools,勾上CMake和NDK,然后OK,开始下载(如果之前是勾选上的,则可以忽略此步)。

1.png

下载完后,则可以开始正式创建项目。


二、创建Android Studio工程

Create New Projec,这一步没什么好说的,由于是在一个Demo里介绍Java和NDK配置,所以我们必须提前将Include C++ support勾选上(请注意我的项目名和包名,以免后续JNI编写时出错),如下图:

2.png

接下来几步都默认,但在Customize the Activity这步,我将Activity Name设置为JavaActivity,如下图:

3.png

下一步,Customize C++ Support中,C++ Standard设置为C++14,下面两个CheckBox都勾选上(别问我为什么,我也不知道该咋解释)。如下图:

4.png

Finish,项目创建成功


三、OpenCV Java库使用指南

环境配置:

第一步、将OpenCV Java库作为Module导入。具体步骤为:File->New->Import Module,然后将OpenCV-android-sdk\sdk\java目录导入,如下图,然后Next->Finish

5.png

如果Module导入后提示下图中的错误,你可以选择点击蓝色链接,Android Studio会自动下载所需的sdk版本。但我通常一般不会这样做,因为目前市场上,99.9%的Android智能手机系统版本都高于API15(Android4.0.3 IceCreamSandwich),我们也无需去兼容过低的系统版本。

6.png

我的做法是,将文件列表上方的显示模式由Android切换至Project,并展开文件列表。分别打开app\build.gradle文件和OpenCVLibrary341\build.gradle文件,将OpenCVLibrary341\build.gradle的几个版本号改成与app\build.gradle文件里相同即可,然后在文本域上方点击Try Again,如下图:

7.png

第二步、给项目添加OpenCV Java库依赖
菜单File->Project Structure,在Modules里选择app,右侧进入Dependencies,点击加号,选择Module dependency,如下图:

8.png

选择OpenCVLibrary341,OK!

代码编写:

在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_java.xml内容修改为以下内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <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" />

</LinearLayout>

将JavaActivity.java改为以下内容:

public class JavaActivity extends AppCompatActivity 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 void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //保持屏幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_java);
        javaCameraView = (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_3_4_0, 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.rgba();
    }
}

在手机中安装OpenCV Manager.apk,考虑到兼容性,真机一般选择armeabi或者armeabi-v7a,虚拟机一般选择x86,然后安装我们的程序。如果出现安装正常,但是运行程序报错的情况,可能有以下原因:
1、未获得摄像头权限
Android系统出于安全考虑,对权限的的使用越来越严苛。在Android高版本中,仅仅在AndroidManifest.xml中声明权限并不能确保用户一定授予,需要动态获取权限,或者用户在设置中手动授权。
2、未能连接到OpenCV Manager
同样是出于安全考虑,同时也防止应用之间相互唤醒(类似腾讯、百度全家桶,相互调用来保活),Android高版本限制了进程间的调用。具体解决方案请百度,因为每个手机打开方式都不一样。

免安装OpenCV Manager

此处参见OpenCV On Android最佳环境配置指南(Eclipse篇)OpenCV Java库使用指南最后部分。


四、OpenCV NDK库使用指南

环境配置:

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,环境配置好了,嘿嘿嘿,接下来开始代码编写。

代码编写:

菜单File->New->Activity->Empty Activity,创建一个新的Activity,其命名下如图,并设置为启动页,Finish

9.png

为了桌面上两个程序入口不冲突,请在AndroidManifest.xml文件中给两个Activity指定label,如下图:

10.png

布局文件activity_jni.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>

JniActivity.java内容如下:

public class JniActivity 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_jni);
        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.png的图片放置在drawable目录下,我使用的是我的偶像照片,嘿嘿嘿^_^!

test.png

应用层写好了,现在开始原生层操作:

第一步:生成头文件
打开Android Studio下方Terminal栏,输入cd app\src\main\java(所有应用都一样),回车。然后输入javah 包名.类名(我们这里输入的是javah com.demo.opencvdemo.JniActivity),回车后,将在app\src\main\java目录下生成一个头文件,如下图所示。最后将该头文件Move到app\src\main\cpp目录下。

12.png

第二步:编写NDK代码
native-lib.cpp内容修改为:

#include "com_demo_opencvdemo_JniActivity.h"
#include <android/bitmap.h>
#include <opencv2/opencv.hpp>

using namespace cv;

extern "C" JNIEXPORT void
JNICALL Java_com_demo_opencvdemo_JniActivity_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, 125, 225);
        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, 125, 225);
        cvtColor(gray, temp, COLOR_GRAY2RGB);
    }
    AndroidBitmap_unlockPixels(env, bitmap);
}

代码写完后,就可以编译运行程序了。这时可能会出现如下错误,这是由于OpenCV doesn't link on Android x86/x86_64 with ndk r16,简而言之,就是NDK r16默认使用的是c++_static模板库,而OpenCV3.4.1是由NDK r14默认的gnustl_static模板库所生成,这使得NDK r16编译会出现异常。如果你使用的是ndk r14,将不会出现这种错误。

13.png

解决办法有两种:
1、更换成旧版本NDK
在SDK Manager中删除NDK,然后重新下载NDK r14,并将其解压到AndroidSDK目录/ndk-bundle目录中。重启Android Studio即可(以后更新注意不要升级NDK)。
2、使用gnustl_static模板库
要想使用gnustl_static模板库进行编译,只需要在app\build.gradle中添加arguments参数,添加后内容如下:

...
externalNativeBuild {
      cmake {
           cppFlags "-std=c++14 -frtti -fexceptions"
           arguments '-DANDROID_STL=gnustl_shared'   //支持C++异常处理标准模板库
      }
 }
...

继续编译运行,这时候又会出现下面的错误(方法2才会有),这是由x86 abi架构造成的,原因未知。

14.png

解决方法有以下两种:
1、不生成x86 abi架构
NDK r16能够编译"armeabi-v7a", "arm64-v8a", "x86_64","x86",我们只需指定编译器生成其余三种abi架构,就可以避免该bug。具体做法如下,在app\build.gradle中增加abiFilters字段。内容如下:

...
externalNativeBuild {
      cmake {
           cppFlags "-std=c++14 -frtti -fexceptions"
           arguments '-DANDROID_STL=gnustl_shared'   //支持C++异常处理标准模板库
           abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
      }
 }
...

本方法的缺点是,不能兼容x86架构的手机,并且不能在虚拟机上运行(大部分虚拟机都是x86架构)。

2、添加编译参数
我比较喜欢这个方法,既能避免错误,有能兼容大部分的Android手机。做法如下:在CmakeLists.txt文件中添加如下内容:

...
endif(OpenCV_FOUND)

set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs,libippicv.a -Wl,--exclude-libs,libippiw.a")
# ###################### 项目原生模块 ###########################

add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp)
...

然后编译程序,将不再报错,并能够成功运行。注意,此时可能会默认打开JavaActivity,退出应用,从桌面进入JniActivity,点击SHOW按钮,效果如下:

15.png

点击PROCESS,效果如下:

16.png

额······我的偶像,即使变成被Canny检测了边缘,还是我的偶像,哈哈哈!!!

五、总结

OpenCV On Android 系列配置教程就到此为止,写这两篇文章确实也不容易,修改了很多遍,尤其是这篇Android Studio,算是百忙之中抽空完成的吧,也拖了很久。自己在配置的过程中踩了无数的坑,希望我的经验能够帮助到大家少走弯路,同时也虚心接受大家的批评与指正。移动端图像处理的路还长,我也将不断去学习充实自己,这两篇文章算是了却我的一个心愿,接下来的时间,我将全身心投入考研,反正大家都加油吧,下面是我创的学习群,我也将不定期帮助大家解决问题,请加群的人保持一个和善的心,分享经验,共同进步,记住:别人帮助你不是必须的



作者:徒步青云
链接:https://www.jianshu.com/p/6e16c0429044
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

猜你喜欢

转载自blog.csdn.net/qq_16149777/article/details/82258168