上一教程: Android 开发简介
下一教程: 如何在 Android 设备上运行深度神经网络
| |
原作者 | Alexander Panov, Rostislav Vasilikhin |
兼容性 | OpenCV >= 4.9.0 |
本教程旨在帮助您在 Android 项目中使用 OpenCV 库。
本指南在 Ubuntu 上进行了测试,但不包含任何平台相关的部分,因此应该与 Android Studio 和 OpenCV4Android SDK 支持的任何操作系统兼容。
本教程假设您已安装并配置以下内容:
如果您需要任何上述方面的帮助,您可以参考我们的 Android 开发简介 指南。
如果您在仔细按照这些步骤后遇到任何错误,请随时通过 OpenCV 论坛 联系我们。我们将尽力帮助您。
使用 SDK 的 Hello OpenCV 示例
在本节中,我们将创建一个简单的应用程序,它除了加载 OpenCV 外什么也不做。在下一节中,我们将扩展它以支持摄像头。
除了本指南之外,您还可以参考一些视频教程,例如 这个视频
- 打开 Android Studio 并选择“Empty Views Activity”创建一个空项目
- 设置项目
- 选择 Java 语言
- 选择 Groovy DSL 构建配置语言
- 选择 最小 SDK 版本号,该版本号不小于 OpenCV 4 Android 构建时使用的版本号。
- 如果您不知道,可以在 `OpenCV-android-sdk/sdk/build.gradle` 文件的 `android -> defaultConfig -> minSdkVersion` 中找到它。
- 点击 文件 -> 新建 -> 导入模块… 并选择 OpenCV SDK 路径。
- 将模块名称设置为 `OpenCV` 并按 `完成`。
OpenCV 还提供实验性的 Kotlin 支持。请将 Android Kotlin 插件添加到 `MyApplication/OpenCV/build.gradle` 文件中。
plugins {
id 'org.jetbrains.kotlin.android' version '1.7.10' #您的设置版本可能不同
}
如下所示:
如果不这样做,您可能会收到错误消息:
任务执行失败。
-----------
* 位置
构建文件 '/home/alexander/AndroidStudioProjects/MyApplication/opencv/build.gradle' 第 4 行
* 错误信息
评估项目 ':opencv' 时出现问题。
> 找不到 ID 为 'kotlin-android' 的插件。
解决方法见 此处
OpenCV 项目使用 `buildConfig` 功能。请在 `MyApplication/OpenCV/build.gradle` 文件的 `android` 块中启用它。
buildFeatures{
buildConfig true
}
如下所示:
如果不这样做,您可能会收到错误消息:
JavaCameraView.java:15: 错误: 找不到符号 import org.opencv.BuildConfig; ^ 符号: 类 BuildConfig 位置: 包 org.opencv
解决方法见 此处 和 此处
- 将模块添加到项目
- 点击 文件 -> 项目结构… -> 依赖项 -> 所有模块 -> +(添加依赖项按钮)-> 模块依赖项
- 在使用任何 OpenCV 函数之前,您必须先加载库。如果您的应用程序包含其他依赖于 OpenCV 的原生库,则应在 OpenCV 初始化 *之后* 加载它们。在应用程序启动时添加以下代码来加载库:
if (OpenCVLoader.initLocal()) {
Log.i(TAG, "OpenCV 加载成功");
} else {
Log.e(TAG, "OpenCV 初始化失败!");
(Toast.makeText(this, "OpenCV 初始化失败!", Toast.LENGTH_LONG)).show();
return;
}
如下所示:
- 选择一个设备来检查示例,然后按 `运行` 按钮运行代码。
使用 Maven Central 的 Hello OpenCV 示例
从 OpenCV 4.9.0 开始,Android OpenCV 包可通过 Maven Central 获取,并可作为 Gradle 依赖项自动安装。在本节中,我们将创建一个简单的应用程序,它仅使用 Maven Central 加载 OpenCV。
- 打开 Android Studio 并选择“Empty Views Activity”创建一个空项目
- 设置项目
- 选择 Java 语言
- 选择 Groovy DSL 构建配置语言
- 选择 最小 SDK 版本号,该版本号不小于 OpenCV 支持的版本号。对于 4.9.0,最小 SDK 版本为 21。
- 编辑 `build.gradle` 并将 OpenCV 库添加到依赖项列表中,如下所示:
dependencies {
implementation 'org.opencv:opencv:4.9.0'
}
`4.9.0` 可以替换为任何可用的版本,请参考 官方发布。
- 在使用任何 OpenCV 函数之前,您必须先加载库。如果您的应用程序包含其他依赖于 OpenCV 的原生库,则应在 OpenCV 初始化 *之后* 加载它们。在应用程序启动时添加以下代码来加载库:
if (OpenCVLoader.initLocal()) {
Log.i(TAG, "OpenCV 加载成功");
} else {
Log.e(TAG, "OpenCV 初始化失败!");
(Toast.makeText(this, "OpenCV 初始化失败!", Toast.LENGTH_LONG)).show();
return;
}
如下所示:
- 选择一个设备来检查示例,然后按 `运行` 按钮运行代码。
摄像头视图示例
在本节中,我们将扩展上一节中创建的空 OpenCV 应用程序以支持摄像头。我们将获取摄像头帧并在屏幕上显示它们。
- 告诉系统我们需要摄像头权限。将以下代码添加到 `MyApplication/app/src/main/AndroidManifest.xml` 文件中:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.hardware.camera.front" android:required="false"/>
<uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/>
如下所示:
- 转到 `activity_main.xml` 布局并删除显示“Hello World!”的 TextView。
这也可以在代码或拆分模式下完成,方法是从 XML 文件中删除 `TextView` 块。
将摄像头视图添加到布局
- 在布局描述中添加一个scheme
xmlns:opencv="http://schemas.android.com/apk/res-auto"
- 将
TextView
替换为org.opencv.android.JavaCameraView
小部件<org.opencv.android.JavaCameraView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone"
android:id="@+id/tutorial1_activity_java_surface_view"
opencv:show_fps="true"
opencv:camera_id="any" />
- 如果收到布局警告,请将
fill_parent
的值替换为android:layout_width
和android:layout_height
属性的match_parent
你将得到如下代码
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:opencv="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<org.opencv.android.JavaCameraView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone"
android:id="@+id/tutorial1_activity_java_surface_view"
opencv:show_fps="true"
opencv:camera_id="any" />
</FrameLayout>
- 让主类继承自
org.opencv.android.CameraActivity
。CameraActivity实现了相机权限请求以及CV应用程序所需的其他一些实用程序。我们感兴趣需要重写的方法是onCreate
、onDestroy
、onPause
、onResume
和getCameraViewList
- 实现接口
org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2
,onCameraFrame
方法应返回包含渲染内容的Mat
对象。示例只是返回相机帧以进行预览:return inputFrame.rgba();
- 分配
org.opencv.android.CameraBridgeViewBase
对象
- 它应该在应用程序启动时创建(
onCreate
方法),并且此类应该设置为侦听器
- 在暂停/恢复时(
onPause
、onResume
方法),它应该被禁用/启用
- 应用程序结束时应禁用(
onDestroy
方法)
- 应该在
getCameraViewList
中返回
可以选择禁止手机屏幕变暗或锁定
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
最终你将得到类似于此的源代码
package org.opencv.samples.tutorial1;
import org.opencv.android.CameraActivity;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.widget.Toast;
import java.util.Collections;
import java.util.List;
public class Tutorial1Activity extends CameraActivity implements CvCameraViewListener2 {
private static final String TAG = "OCVSample::Activity";
private CameraBridgeViewBase mOpenCvCameraView;
public Tutorial1Activity() {
Log.i(TAG, "Instantiated new " + this.getClass());
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "called onCreate");
super.onCreate(savedInstanceState);
if (OpenCVLoader.initLocal()) {
Log.i(TAG, "OpenCV 加载成功");
} else {
Log.e(TAG, "OpenCV 初始化失败!");
(Toast.makeText(this, "OpenCV 初始化失败!", Toast.LENGTH_LONG)).show();
return;
}
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.tutorial1_surface_view);
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.tutorial1_activity_java_surface_view);
mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
mOpenCvCameraView.setCvCameraViewListener(this);
}
@Override
public void onPause()
{
super.onPause();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
@Override
public void onResume()
{
super.onResume();
if (mOpenCvCameraView != null)
mOpenCvCameraView.enableView();
}
@Override
protected List<? extends CameraBridgeViewBase> getCameraViewList() {
return Collections.singletonList(mOpenCvCameraView);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
@Override
public void onCameraViewStarted(int width, int height) {
}
@Override
public void onCameraViewStopped() {
}
@Override
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
return inputFrame.rgba();
}
}
就是这样!现在你可以在你的设备上运行代码来检查它。
让我们讨论一些最重要的步骤
每个带有UI的Android应用程序都必须实现Activity和View。通过第一步,我们创建了一个空白活动和默认视图布局。最简单的以OpenCV为中心的应用程序必须执行OpenCV初始化,创建一个视图以显示来自摄像头的预览,并实现CvCameraViewListener2
接口以从摄像头获取帧并处理它们。
首先,我们使用XML布局创建应用程序视图。我们的布局仅由一个全屏组件组成,该组件属于org.opencv.android.JavaCameraView
类。此OpenCV类继承自CameraBridgeViewBase
,后者扩展了SurfaceView
,并在底层使用标准的Android相机API。
CvCameraViewListener2
接口允许您在从相机抓取帧之后以及在屏幕上渲染之前添加一些处理步骤。最重要的方法是onCameraFrame
。这是一个回调函数,它在从相机检索帧时调用。它期望onCameraFrame
函数返回将在屏幕上绘制的RGBA帧。
回调将来自摄像头的帧作为CvCameraViewFrame
类的对象传递给我们的类。此对象具有rgba()
和gray()
方法,允许用户将彩色或单通道灰度帧作为Mat
类对象获取。
- 注意
- 不要在
onCameraFrame
回调之外保存或使用CvCameraViewFrame
对象。此对象没有自己的状态,其在回调之外的行为是不可预测的!