简介
本文是《OpenCV On Android最佳环境配置指南》 系列教程第二篇,也是配置系列的最后一篇,适合使用Android Studio的开发人员学习。
本教程是经过本人多次踩坑,并结合网上众多OpenCV On Android的配置教程总结而来,尽希望能帮助学习OpenCV的朋友们少走弯路。如果配置上遇到问题,可在评论中留言,我将尽力帮助解决。
如果您使用的是Eclipse,请参考上一章OpenCV On Android最佳环境配置指南(Eclipse篇)。
如有转载,请标明出处
最近更新时间:2023-9-13
环境
- 电脑:Windows10
- Android Studio:Giraffe | 2022.3.1 Patch 1
- SDK:Android Studio 自带的最新SDK
(请不要与Eclipse同用一SDK,以免出错)
。 - NDK:Android Studio 自带的最新NDK
- Java:Android Studio 自带的最新JDK
- OpenCV:V4.8.0
注:以上配置向上兼容,读者可使用更新的版本,但低版本可能出现错误
配置前说明:
本次配置不像上篇介绍Eclipse配置环境那样编写多个Demo,而是通过一个Demo,将OpenCV的Java
和NDK
配置方式全部讲完,尽可能手把手讲解,请大家不要跳跃式地阅读。
一、安装必要组件
- 打开Android Studio设置界面,进入
Appearance & Behavior -> System Settings -> Android SDK
。 - 将选项条切换到
SDK Tools
,勾上左下角的Show Package Details
,然后按下图勾选:
点击OK,开始下载。下载完后,就可以开始创建项目了。
二、创建Android Studio工程
Create New Project
,选择最后面的Native C++
模板,然后进入配置界面。
这一步需要注意3个地方
1、包名:新手请尽量与我保持一致,否则容易出错。
2、最小SDK:OpenCV 4.8.0要求最小SDK不小于21
。
3、编译脚本语言选择Groovy DSL
,如果选择Kotlin DSL,后续导入OpenCV SDK后,会生成settings.gradle,跟settings.gradle.kt发生冲突。
然后选择C++标准,这里我们选择C++14。
点击Finish
,项目创建成功!
提示
:项目创建完成后,最好运行一下,确保基本环境没问题。
三、OpenCV Java库使用指南
注意
: 最新的OpenCV已经支持gradle
导入依赖,如果你只考虑使用Java方式调用OpenCV,那这就是最佳方式。
1、在app的gradle.build
中引入以下依赖
implementation("org.opencv:opencv:4.10.0")
2、在合适的地方调用以下方法,初始化OpenCV库
OpenCVLoader.initLocal()
3.1、环境配置
第一步:将OpenCV Java库作为Module导入。
具体步骤为:File->New->Import Module
,然后选择OpenCV-android-sdk\sdk目录,并将Module name
设置为opencv
。如下图,然后Finish
。
第二步:解决OpenCV Java库导入错误。
打开opencv模块
下的build.gradle
文件,修改以下内容:
红色
表示需要注释或删除的内容,黄色
表示需要增加的内容。
第三步:给项目添加opencv依赖
菜单File->Project Structure
,在Dependencies
中选择app,点击+
,选择Module dependency
,然后勾选opencv
模块,点击OK即可!如下图:
3.2、代码编写
在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
,这是为了解决黑屏的问题。如果不这样做,则需要手动处理权限,并调用JavaCameraView
的setCameraPermissionGranted()
方法。
3.3、运行程序
到了这一步,我们已经能够编译并运行app了。
如果运行时,弹出弹窗警告,要求安装OpenCV Manager.apk
,那意味着你使用了旧版本的OpenCV库。我们可通过以下3种方式来解决这个问题:
1、直接更新OpenCV版本
这是我最推荐的方式,因为很简单。
2、在手机中安装OpenCV Manager.apk
这种方式有以下几个缺点:
1、用户需要单独安装这个apk,且不同ARM架构的手机,apk也不一样。
2、在Android高版本中,一些手机厂商为了防止应用相互唤醒,对手机做出了一定限制通(比如华为手机管家中的启动管理),这可能会照成我们的App调用不到OpenCV Manager
3、OpenCV高版本现已不再提供 OpenCV Manager.apk
3、将libopencv_java4.so
导入到apk中
这种方式的缺点就是:麻烦!!!
,但如果我们已经确定了目标机型,这种方式无疑是比较好的。
通常来说,如果是真机,导入armeabi
或armeabi-v7a
架构的so文件;如果是虚拟机,则一般选择x86
架构的so文件。
方法 3 具体的实现步骤如下:
1、将OpenCV库中的OpenCV-android-sdk\sdk\native\libs
目录下4个子目录,copy到我们项目的libs目录下。
2、修改build.gradle文件,添加以下内容:
sourceSets{
main{
jniLibs.srcDirs = ["libs"];
}
}
如图所示:
做完这一步,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对应的目录下。
四、OpenCV NDK库使用指南
4.1、环境配置
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.2、编写应用层代码
菜单File->New->Activity->Empty Activity,创建一个新的Activity,其命名下如图,并设置为启动页,Finish。
为了分清楚桌面上两个程序入口,请在AndroidManifest.xml文件中给两个Activity指定label,如下图:
下面开始编写布局文件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目录下,嘿嘿嘿!
4.3、编写原生层代码
原生层代码,说白了,就是用C/C++编写程序。
1、生成方法名
由于NativeActivity中的getEdge
是一个native
方法,还没有具体的实现,所有在Android Studio中,会报红色警告。
此时,只需将鼠标点到该方法名,然后Alt
+Enter
,就会在native-lib.cpp
文件中生成该方法的声明。
注:如果无法生成,请使用javah
命令,具体请百度。
1、编写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);
}
注意,只替换内容,不要将方法名也替换了。
运行程序,效果如下:
完美,收工,回家吃饭!
五、总结
OpenCV On Android 系列配置教程就到此为止,写这两篇文章确实也不容易,修改了很多遍,尤其是这篇Android Studio,算是百忙之中抽空完成的吧,也拖了很久。自己在配置的过程中踩了无数的坑,希望我的经验能够帮助到大家少走弯路,同时也虚心接受大家的批评与指正。
移动端图像处理的路还长,我也将不断去学习充实自己,这两篇文章算是了却我的一个心愿,下面是我创的学习群,我也将不定期帮助大家解决问题。
请加群的人保持一个和善的心,分享经验,共同进步,记住:别人帮助你不是必须的
。