介绍
在本教程中,您将了解如何使用 OpenCV 深度学习模块在 Android 设备上运行深度学习网络。教程是为 Android Studio 2022.2.1 编写的。
要求
- 从 https://developer.android.com/studio 下载并安装 Android Studio。
- 从 Releases · opencv/opencv · GitHub 获取最新的预构建的 OpenCV for Android 版本并将其解压缩(例如,需要最低版本 4.9)。
opencv-4.X.Y-android-sdk.zip
- 从 GitHub - chuanqi305/MobileNet-SSD: Caffe implementation of Google MobileNet SSD detection network, with pretrained weights on VOC0712 and mAP=0.727. 下载 MobileNet 对象检测模型。配置文件和模型权重是必需的。
MobileNetSSD_deploy.prototxt
MobileNetSSD_deploy.caffemodel
创建一个空的 Android Studio 项目并添加 OpenCV 依赖项
使用 Android Development with OpenCV 教程初始化项目并添加 OpenCV。
制作应用
我们的示例将从相机中获取照片,将其转发到深度网络中,并接收 [0, 1] 范围内的一组矩形、类标识符和置信度值。
- 首先,我们需要添加一个必要的小部件来显示处理后的帧。修改:
app/src/main/res/layout/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”xmlns:tools=“http://schemas.android.com/tools”安卓:layout_width=“match_parent”安卓:layout_height=“match_parent”tools:context=“org.opencv.samples.opencv_mobilenet。主活动”><org.opencv.android.JavaCameraViewandroid:id=“@+id/CameraView”安卓:layout_width=“match_parent”安卓:layout_height=“match_parent”android:visibility=“可见” /></FrameLayout(框架布局)>- 修改以启用全屏模式,设置正确的屏幕方向并允许使用相机。
/app/src/main/AndroidManifest.xml
<?xml version=“1.0” encoding=“utf-8”?><清单 xmlns:android=“http://schemas.android.com/apk/res/android”><应用android:label=“@string/app_name”><活动android:exported=“true”android:name=“。主活动”android:screenOrientation=“landscape”> <!--屏幕方向--><意图过滤器><操作 android:name=“android.intent.action.MAIN” /><类别 android:name=“android.intent.category.LAUNCHER” /></意图过滤器></活动></应用><!--允许使用相机--><使用权限 android:name=“android.permission.CAMERA”/><使用功能 android:name=“android.hardware.camera” android:required=“false”/><使用功能 android:name=“android.hardware.camera.autofocus” android:required=“false”/><使用功能 android:name=“android.hardware.camera.front” android:required=“false”/><使用功能 android:name=“android.hardware.camera.front.autofocus” android:required=“false”/></清单>- 如有必要,替换以下内容并设置自定义包名称:
app/src/main/java/com/example/myapplication/MainActivity.java
软件包 com.example.myapplication;导入 android.content.Context;导入 android.content.res.AssetManager;导入 android.os.Bundle;导入 android.util.Log;导入 android.widget.Toast;导入 org.opencv.android.CameraActivity;导入 org.opencv.android.CameraBridgeViewBase;导入 org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;导入 org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;导入 org.opencv.android.OpenCVLoader;导入 org.opencv.core.Core;导入 org.opencv.core.Mat;导入 org.opencv.core.MatOfByte;导入 org.opencv.core.Point;导入 org.opencv.core.Scalar;导入 org.opencv.core.Size;进口 org.opencv.dnn.Net;导入 org.opencv.dnn.Dnn;导入 org.opencv.imgproc.Imgproc;导入 java.io.InputStream;导入 java.io.IOException;导入 java.util.Collections;导入 java.util.List;公共类 MainActivity 扩展 CameraActivity 实现 CvCameraViewListener2 {@Override公共无效 onResume() {super.onResume();if (mOpenCvCameraView != 空)mOpenCvCameraView.enableView();}@Override受保护的 void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);如果 (OpenCVLoader.initLocal()) {Log.i(TAG, “OpenCV 加载成功”);} 还 {Log.e(TAG, “OpenCV 初始化失败!”);(Toast.makeText(this, “OpenCV 初始化失败!”, Toast.LENGTH_LONG)).show();返回;}mModelBuffer = loadFileFromResource(R.raw.mobilenet_iter_73000);mConfigBuffer = loadFileFromResource(R.raw.deploy);if (mModelBuffer == null || mConfigBuffer == null) {Log.e(TAG,“无法从资源加载模型”);} 还Log.i(TAG, “模型文件加载成功”);net = Dnn.readNet(“caffe”, mModelBuffer, mConfigBuffer);Log.i(TAG, “网络加载成功”);setContentView(R.layout.activity_main);设置摄像头侦听器。mOpenCvCameraView = (CameraBridgeViewBase)findViewById(R.id.CameraView);mOpenCvCameraView.setVisibility(CameraBridgeViewBase.VISIBLE);mOpenCvCameraView.setCvCameraViewListener(这个);}@Overridepublic void onPause(){super.onPause();if (mOpenCvCameraView != 空)mOpenCvCameraView.disableView();}@Override保护列表<?扩展 CameraBridgeViewBase> getCameraViewList() {返回集合.singletonList(mOpenCvCameraView);}公共无效 onDestroy() {super.onDestroy();if (mOpenCvCameraView != 空)mOpenCvCameraView.disableView();mModelBuffer.release();mConfigBuffer.release();}加载网络。public void onCameraViewStarted(int width, int height) {}公共垫 onCameraFrame(CvCameraViewFrame inputFrame) {最终 int IN_WIDTH = 300;最终 int IN_HEIGHT = 300;最终浮点WH_RATIO=(浮点)IN_WIDTH/IN_HEIGHT;最终双IN_SCALE_FACTOR = 0.007843;最终双MEAN_VAL = 127.5;最终双精度阈值 = 0.2;获取新框架Log.d(TAG, “处理新帧!”);垫子框架 = inputFrame.rgba();Imgproc.cvtColor(帧,帧,Imgproc.COLOR_RGBA2RGB);通过网络转发图像。Mat blob = Dnn.blobFromImage(frame, IN_SCALE_FACTOR,new Size(IN_WIDTH, IN_HEIGHT),new 标量(MEAN_VAL, MEAN_VAL, MEAN_VAL), /*swapRB*/false, /*裁剪*/false);net.setInput(blob);垫子检测 = net.forward();int cols = frame.cols();int rows = frame.rows();检测 = detections.reshape(1, (int)detections.total() / 7);for (int i = 0; i < detections.rows(); ++i) {双倍置信度 = detections.get(i, 2)[0];if (置信度>阈值) {int classId = (int)detections.get(i, 1)[0];int left = (int)(detections.get(i, 3)[0] * cols);int top = (int)(detections.get(i, 4)[0] * 行);int right = (int)(detections.get(i, 5)[0] * cols);int bottom = (int)(detections.get(i, 6)[0] * 行);在检测到的物体周围绘制矩形。Imgproc.rectangle(frame, new Point(left, top), new Point(right, bottom),新标量(0, 255, 0));字符串标签 = classNames[classId] + “: ” + 置信度;int[] baseLine = 新 int[1];大小 labelSize = Imgproc.getTextSize(label, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine);为标签绘制背景。Imgproc.rectangle(frame, new Point(left, top - labelSize.height),new Point(left + labelSize.width, top + baseLine[0]),新标量(255, 255, 255), Imgproc.FILLED);写下类名和置信度。Imgproc.putText(frame, label, new Point(left, top),Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 新标量(0, 0, 0));}}返回帧;}public void onCameraViewStopped() {}私人MatOfByte loadFileFromResource(int id) {byte[] 缓冲区;尝试 {从应用程序资源加载级联文件InputStream 是 = getResources().openRawResource(id);int 大小 = is.available();缓冲区 = 新字节 [大小];int bytesRead = is.read(缓冲区);is.close();} catch (IOException e) {e.printStackTrace();Log.e(TAG,“无法从资源ONNX模型!抛出异常:“ + e);(Toast.makeText(this, “无法从资源ONNX模型!”, Toast.LENGTH_LONG)).show();返回 null;}返回 new MatOfByte(buffer);}private static final 字符串 TAG = “OpenCV-MobileNet”;private static final String[] classNames = {“background”,“飞机”, “自行车”, “鸟”, “船”,“瓶子”, “公共汽车”, “汽车”, “猫”, “椅子”,“牛”, “餐桌”, “狗”, “马”,“摩托车”, “人”, “盆栽”,“绵羊”, “沙发”, “火车”, “TVMONITOR”};私人MatOfByte mConfigBuffer;私人MatOfByte mModelBuffer;私人净净值;私人CameraBridgeViewBase mOpenCvCameraView;}
- 将下载并放入文件夹中。OpenCV DNN 模型主要用于从文件加载 ML 和 DNN 模型。现代 Android 不允许在没有额外权限的情况下使用它,但提供了 Java API 来从资源中加载字节。此示例使用替代 DNN API,该 API 从内存中缓冲区而不是文件初始化模型。以下函数从资源中读取模型文件,并将其转换为(在 C++ 世界中模拟)适合 OpenCV Java API 的对象:
deploy.prototxt
mobilenet_iter_73000.caffemodel
app/src/main/res/raw
MatOfBytes
std::vector<char>
私人MatOfByte loadFileFromResource(int id) {byte[] 缓冲区;尝试 {从应用程序资源加载级联文件InputStream 是 = getResources().openRawResource(id);int 大小 = is.available();缓冲区 = 新字节 [大小];int bytesRead = is.read(缓冲区);is.close();} catch (IOException e) {e.printStackTrace();Log.e(TAG,“无法从资源ONNX模型!抛出异常:“ + e);(Toast.makeText(this, “无法从资源ONNX模型!”, Toast.LENGTH_LONG)).show();返回 null;}返回 new MatOfByte(buffer);}然后使用以下行完成网络初始化:
mModelBuffer = loadFileFromResource(R.raw.mobilenet_iter_73000);mConfigBuffer = loadFileFromResource(R.raw.deploy);if (mModelBuffer == null || mConfigBuffer == null) {Log.e(TAG,“无法从资源加载模型”);} 还Log.i(TAG, “模型文件加载成功”);net = Dnn.readNet(“caffe”, mModelBuffer, mConfigBuffer);Log.i(TAG, “网络加载成功”);另请参阅有关资源的 Android 文档
- 看看 DNN 模型输入是如何准备的,推理结果是如何解释的:
Mat blob = Dnn.blobFromImage(frame, IN_SCALE_FACTOR,new Size(IN_WIDTH, IN_HEIGHT),new 标量(MEAN_VAL, MEAN_VAL, MEAN_VAL), /*swapRB*/false, /*裁剪*/false);net.setInput(blob);垫子检测 = net.forward();int cols = frame.cols();int rows = frame.rows();检测 = detections.reshape(1, (int)detections.total() / 7);for (int i = 0; i < detections.rows(); ++i) {双倍置信度 = detections.get(i, 2)[0];if (置信度>阈值) {int classId = (int)detections.get(i, 1)[0];int left = (int)(detections.get(i, 3)[0] * cols);int top = (int)(detections.get(i, 4)[0] * 行);int right = (int)(detections.get(i, 5)[0] * cols);int bottom = (int)(detections.get(i, 6)[0] * 行);在检测到的物体周围绘制矩形。Imgproc.rectangle(frame, new Point(left, top), new Point(right, bottom),新标量(0, 255, 0));字符串标签 = classNames[classId] + “: ” + 置信度;int[] baseLine = 新 int[1];大小 labelSize = Imgproc.getTextSize(label, Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 1, baseLine);为标签绘制背景。Imgproc.rectangle(frame, new Point(left, top - labelSize.height),new Point(left + labelSize.width, top + baseLine[0]),新标量(255, 255, 255), Imgproc.FILLED);写下类名和置信度。Imgproc.putText(frame, label, new Point(left, top),Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, 新标量(0, 0, 0));}}
Dnn.blobFromImage
将相机帧转换为神经网络输入张量。应用调整大小和统计归一化。网络输出张量的每一行都包含有关一个检测到对象的信息,顺序如下:范围 [0, 1] 的置信度、类 ID、左、上、右、下框坐标。所有坐标都在 [0, 1] 范围内,应在渲染前缩放到图像大小。
- 启动应用程序并带来乐趣!