一段时间以前,“如何在 x86 Android 上使用 OpenVINO 创建应用程序” 这篇文章已发布。但是,大多数 Android 设备使用基于 ARM 的芯片,因此我们决定将该指令移植到该平台。
OpenVINO 通过 ARM 插件 支持在 ARM 平台上进行 DL 网络推理,因此在基于 ARM 的 Android 设备上推断网络没有技术限制。
在本文中,我们将构建 OpenVINO,其中包含 Java 绑定和 ARM 插件模块,用于 ARM Android 平台,并创建一个面部检测 Android 应用程序。该应用程序使用 OpenCV 从摄像头读取帧,使用 DL 网络识别帧中的人脸,并显示带有边界框的帧。
该指令已在 Ubuntu 18.04 上测试,但可以轻松地适应其他操作系统。
为 ARM 上的 Android 构建 OpenVINO
以下步骤说明如何构建 OpenVINO,其中包含 Java 绑定和 ARM 插件,用于 ARM Android 平台。
- 安装 OpenJDK 8 并定义 JAVA_PATH
sudo apt-get install -y openjdk-8-jdk export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
- 创建一个工作目录,并将 OpenVINO 和 OpenVINO Contrib 存储库克隆到其中。OpenVINO Contrib 存储库包含 Java 绑定和 ARM 插件模块。
mkdir ~/android_ov && cd ~/android_ov git clone --recurse-submodules --shallow-submodules --depth 1 --branch=2021.4.1 https://github.com/openvinotoolkit/openvino.git git clone --recurse-submodules --shallow-submodules --depth 1 --branch=2021.4 https://github.com/openvinotoolkit/openvino_contrib.git
- 下载并解压缩 Android NDK
wget https://dl.google.com/android/repository/android-ndk-r20-linux-x86_64.zip unzip android-ndk-r20-linux-x86_64.zip
- 创建一个目录并为 Android 构建 OpenVINO
mkdir openvino/build && cd openvino/build cmake -DCMAKE_BUILD_TYPE=Release \ -DTHREADING=SEQ \ -DIE_EXTRA_MODULES=.../openvino_contrib/modules \ -DBUILD_java_api=ON \ -DCMAKE_TOOLCHAIN_FILE=.../android-ndk-r20/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=29 \ -DANDROID_STL=c++_shared \ -DENABLE_SAMPLES=OFF \ -DENABLE_OPENCV=OFF \ -DENABLE_CLDNN=OFF \ -DENABLE_VPU=OFF \ -DENABLE_GNA=OFF \ -DENABLE_MYRIAD=OFF \ -DENABLE_TESTS=OFF \ -DENABLE_GAPI_TESTS=OFF \ -DENABLE_BEH_TESTS=OFF .. && make --jobs=$(nproc --all)
- 可以剥离 OpenVINO 二进制文件以减小其大小
~/android_ov/android-ndk-r20/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/aarch64-linux-android/bin/strip ../bin/aarch64/Release/lib/*.so
创建 Android 应用程序
OpenVINO 库已准备就绪,因此让我们创建 Android 应用程序并利用 OpenVINO 库来推断面部检测模型。
- 在您的 PC 上下载并安装 Android Studio。
- 启动一个新项目,选择“空活动”,使用 Java 语言和 SDK 版本 23
- 修改 app/src/main/AndroidManifest.xml:在“<activity>”标签中添加“<uses-permission>”标签和“android:screenOrientation”属性
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.app"> <uses-permission android:name="android.permission.CAMERA"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.App"> <activity android:name=".MainActivity" android:screenOrientation="landscape"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
- 在“app/src/main”中创建“jniLibs/arm64-v8a”目录,并将以下文件复制到该目录
~/android_ov/openvino/bin/aarch64/Release/lib/*.so ~/android_ov/android-ndk-r20/sources/cxx-stl/llvm-libc++/libs/arm64-v8a/libc++_shared.so
- 将“~/android_ov/openvino/bin/aarch64/Release/lib/inference_engine_java_api.jar”复制到“app/libs”文件夹
- 下载 适用于 Android 的 OpenCV SDK 并解压缩它
- 导入 OpenCV 模块:选择“文件 -> 新建 -> 导入模块”,指定解压缩的 SDK 的路径(例如“opencv-4.5.0-android-sdk/OpenCV-android-sdk/sdk”),并将模块名称设置为“ocv”。
- 某些版本的 Android Studio 不允许加载 OpenCV。如果发生这种情况,则
- 创建一个新模块(文件 – 新建 – 新模块)
- 将它命名为相同名称(例如“ocv”)
- 从“opencv-4.5.0-android-sdk/OpenCV-android-sdk/sdk”复制或替换所有文件
- 重新构建 Gradle 项目
- 将 OpenCV 作为主项目的依赖项安装:“
- 文件 – 项目结构 – 依赖项 – <您的应用程序(例如 app)> – 右键单击 – 模块依赖项 – ocv”
- 修改 app/src/main/res/layout/activity_main.xml: 为 OpenCV 摄像头支持添加“JavaCameraView”
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <org.opencv.android.JavaCameraView android:id="@+id/CameraView" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="visible" /> </androidx.constraintlayout.widget.ConstraintLayout>
- 更新 build.gradle 文件(模块:<application_name>.app)– 在“依赖项”部分添加以下行
implementation "androidx.camera:camera-camera2:1.0.0" implementation "androidx.camera:camera-lifecycle:1.0.0" implementation "androidx.camera:camera-view:1.0.0-alpha25" implementation files('libs/inference_engine_java_api.jar') implementation project(path: ':ocv')
- 修改 app/src/main/java/com/example/<application_name>/MainActivity.java
import androidx.annotation.NonNull; import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.widget.Toast; import org.opencv.android.CameraActivity; import org.opencv.android.CameraBridgeViewBase; import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame; import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2; import org.opencv.core.Core; import org.opencv.core.Mat; import org.opencv.core.Point; import org.opencv.core.Scalar; import org.opencv.core.Size; import org.opencv.imgproc.Imgproc; import org.intel.openvino.*; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; public class MainActivity extends CameraActivity implements CvCameraViewListener2 { private void copyFiles() { String[] fileNames = {MODEL_BIN, MODEL_XML, PLUGINS_XML}; for (String fileName: fileNames) { String outputFilePath = modelDir + "/" + fileName; File outputFile = new File(outputFilePath); if (!outputFile.exists()) { try { InputStream inputStream = getApplicationContext().getAssets().open(fileName); OutputStream outputStream = new FileOutputStream(outputFilePath); byte[] buffer = new byte[5120]; int length = inputStream.read(buffer); while (length > 0) { outputStream.write(buffer, 0, length); length = inputStream.read(buffer); } outputStream.flush(); outputStream.close(); inputStream.close(); } catch (Exception e) { Log.e("CopyError", "Copying model has failed."); System.exit(1); } } } } private void processNetwork() { // Set up camera listener. mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.CameraView); mOpenCvCameraView.setVisibility(CameraBridgeViewBase.VISIBLE); mOpenCvCameraView.setCvCameraViewListener(this); mOpenCvCameraView.setCameraPermissionGranted(); // Initialize model copyFiles(); IECore core = new IECore(modelDir + "/" + PLUGINS_XML); CNNNetwork net = core.ReadNetwork(modelDir + "/" + MODEL_XML); Map<String, InputInfo> inputsInfo = net.getInputsInfo(); inputName = new ArrayList<String>(inputsInfo.keySet()).get(0); InputInfo inputInfo = inputsInfo.get(inputName); inputInfo.getPreProcess().setResizeAlgorithm(ResizeAlgorithm.RESIZE_BILINEAR); inputInfo.setPrecision(Precision.U8); ExecutableNetwork executableNetwork = core.LoadNetwork(net, DEVICE_NAME); inferRequest = executableNetwork.CreateInferRequest(); Map<String, Data> outputsInfo = net.getOutputsInfo(); outputName = new ArrayList<>(outputsInfo.keySet()).get(0); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try{ System.loadLibrary(OPENCV_LIBRARY_NAME); System.loadLibrary(IECore.NATIVE_LIBRARY_NAME); } catch (UnsatisfiedLinkError e) { Log.e("UnsatisfiedLinkError", "Failed to load native OpenVINO libraries\n" + e.toString()); System.exit(1); } modelDir = this.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(); if(checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.CAMERA}, 0); } else { processNetwork(); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { Log.e("PermissionError", "The application can't work without camera permissions"); System.exit(1); } processNetwork(); } @Override public void onResume() { super.onResume(); mOpenCvCameraView.enableView(); } @Override public void onCameraViewStarted(int width, int height) {} @Override public void onCameraViewStopped() {} @Override public Mat onCameraFrame(CvCameraViewFrame inputFrame) { Mat frame = inputFrame.rgba(); Mat frameBGR = new Mat(); Imgproc.cvtColor(frame, frameBGR, Imgproc.COLOR_RGBA2RGB); int[] dimsArr = {1, frameBGR.channels(), frameBGR.height(), frameBGR.width()}; TensorDesc tDesc = new TensorDesc(Precision.U8, dimsArr, Layout.NHWC); Blob imgBlob = new Blob(tDesc, frameBGR.dataAddr()); inferRequest.SetBlob(inputName, imgBlob); inferRequest.Infer(); Blob outputBlob = inferRequest.GetBlob(outputName); float[] scores = new float[outputBlob.size()]; outputBlob.rmap().get(scores); int numDetections = outputBlob.size() / 7; int confidentDetections = 0; for (int i = 0; i < numDetections; i++) { float confidence = scores[i * 7 + 2]; if (confidence > CONFIDENCE_THRESHOLD) { float xmin = scores[i * 7 + 3] * frameBGR.cols(); float ymin = scores[i * 7 + 4] * frameBGR.rows(); float xmax = scores[i * 7 + 5] * frameBGR.cols(); float ymax = scores[i * 7 + 6] * frameBGR.rows(); Imgproc.rectangle(frame, new Point(xmin, ymin), new Point(xmax, ymax), new Scalar(0, 0, 255), 6); confidentDetections++; } } Imgproc.putText(frame, String.valueOf(confidentDetections), new Point(10, 40), Imgproc.FONT_HERSHEY_COMPLEX, 1.8, new Scalar(0, 255, 0), 6); return frame; } private CameraBridgeViewBase mOpenCvCameraView; private InferRequest inferRequest; private String inputName; private String outputName; private String modelDir; public static final double CONFIDENCE_THRESHOLD = 0.5; public static final String OPENCV_LIBRARY_NAME = "opencv_java4"; public static final String PLUGINS_XML = "plugins.xml"; public static final String MODEL_XML = "face-detection-adas-0001.xml"; public static final String MODEL_BIN = "face-detection-adas-0001.bin"; public static final String DEVICE_NAME = "CPU"; }
- 从 Open Model Zoo 下载模型“face-detection-adas-0001”
git clone --depth 1 https://github.com/openvinotoolkit/open_model_zoo cd open_model_zoo/tools/downloader python3 -m pip install -r requirements.in python3 downloader.py --name face-detection-adas-0001
- 在项目中创建资产文件夹:选择“文件 – 新建 – 文件夹 – 资产文件夹”,并将以下文件复制到此文件夹
~/android_ov/openvino/bin/aarch64/Release/lib/plugins.xml ~/android_ov /open_model_zoo/tools/downloader/intel/face-detection-adas-0001/FP32/face-detection-adas-0001.xml ~/android_ov/open_model_zoo/tools/downloader/intel/face-detection-adas-0001/FP32/face-detection-adas-0001.bin
- 构建项目并在您的 Android 设备上运行该应用程序。为此,将 Android 设备连接到 PC,在工具栏上的下拉菜单中选择它,然后按“运行”按钮。请注意!对于三星设备,请添加权限以帮助 USB 传输数据。
- 在首次运行时,您需要向应用程序授予摄像头和外部存储读取权限。如果您在提供权限后看到黑屏或摄像头错误,请尝试再次运行该应用程序。