一段時間以前,“如何在 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 傳輸資料。
- 在首次執行時,您需要嚮應用程式授予攝像頭和外部儲存讀取許可權。如果您在提供許可權後看到黑屏或攝像頭錯誤,請嘗試再次執行該應用程式。






