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

相关文章
相关标签/搜索