一段时间以前,“如何在 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 传输数据。
- 在首次运行时,您需要向应用程序授予摄像头和外部存储读取权限。如果您在提供权限后看到黑屏或摄像头错误,请尝试再次运行该应用程序。






