Hello World! trên Android

Giới thiệu

Đây là Hello World! sử dụng Khung MediaPipe để phát triển một ứng dụng Android chạy biểu đồ MediaPipe trên Android.

Sản phẩm bạn sẽ tạo ra

Ứng dụng máy ảnh đơn giản giúp phát hiện cạnh Sobel theo thời gian thực, áp dụng cho video trực tiếp phát trực tuyến trên thiết bị Android.

edge_detection_android_gpu_gif

Thiết lập

  1. Cài đặt Khung MediaPipe trên hệ thống của bạn, xem phần Cài đặt khung để biết thông tin chi tiết.
  2. Cài đặt Android NDK và SDK Phát triển Android. Xem cách thực hiện việc này cũng trong [Hướng dẫn cài đặt khung].
  3. Bật tuỳ chọn cho nhà phát triển trên thiết bị Android.
  4. Thiết lập Bazel trên hệ thống của bạn để xây dựng và triển khai ứng dụng Android.

Biểu đồ phát hiện cạnh

Chúng ta sẽ sử dụng biểu đồ sau đây, edge_detection_mobile_gpu.pbtxt:

# MediaPipe graph that performs GPU Sobel edge detection on a live video stream.
# Used in the examples in
# mediapipe/examples/android/src/java/com/mediapipe/apps/basic and
# mediapipe/examples/ios/edgedetectiongpu.

# Images coming into and out of the graph.
input_stream: "input_video"
output_stream: "output_video"

# Converts RGB images into luminance images, still stored in RGB format.
node: {
  calculator: "LuminanceCalculator"
  input_stream: "input_video"
  output_stream: "luma_video"
}

# Applies the Sobel filter to luminance images stored in RGB format.
node: {
  calculator: "SobelEdgesCalculator"
  input_stream: "luma_video"
  output_stream: "output_video"
}

Biểu đồ trực quan được hiển thị dưới đây:

edge_detection_mobile_gpu

Biểu đồ này có một luồng đầu vào duy nhất tên là input_video cho tất cả các khung hình sắp tới do máy ảnh trên thiết bị của bạn cung cấp.

Nút đầu tiên trong biểu đồ, LuminanceCalculator, nhận một gói duy nhất (hình ảnh ) và áp dụng thay đổi về độ chói bằng chương trình đổ bóng OpenGL. Kết quả khung hình ảnh này sẽ được gửi đến luồng đầu ra luma_video.

Nút thứ hai, SobelEdgesCalculator áp dụng tính năng phát hiện cạnh cho lệnh đến các gói trong luồng luma_video và xuất ra kết quả đầu ra output_video luồng.

Ứng dụng Android của chúng ta sẽ hiển thị khung hình ảnh đầu ra của Luồng output_video.

Thiết lập ứng dụng tối thiểu ban đầu

Đầu tiên, chúng ta bắt đầu với một ứng dụng Android đơn giản hiển thị thông báo "Hello World!" trên màn hình. Bạn có thể bỏ qua bước này nếu bạn quen với việc xây dựng Android bằng bazel.

Tạo một thư mục mới để tạo ứng dụng Android. Cho Ví dụ: bạn có thể tìm thấy mã hoàn chỉnh của hướng dẫn này tại mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic. Chúng tôi sẽ hãy gọi đường dẫn này là $APPLICATION_PATH trong suốt lớp học lập trình.

Lưu ý rằng trong đường dẫn đến ứng dụng:

  • Ứng dụng này có tên là helloworld.
  • $PACKAGE_PATH của ứng dụng này là com.google.mediapipe.apps.basic. Cụm từ này được sử dụng trong các đoạn mã của hướng dẫn này, vì vậy hãy nhớ sử dụng $PACKAGE_PATH của riêng bạn khi sao chép/sử dụng các đoạn mã.

Thêm một tệp activity_main.xml vào $APPLICATION_PATH/res/layout. Màn hình này TextView trên toàn màn hình của ứng dụng có chuỗi Hello World!:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Thêm một MainActivity.java đơn giản vào $APPLICATION_PATH để tải nội dung của bố cục activity_main.xml như minh hoạ dưới đây:

package com.google.mediapipe.apps.basic;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

/** Bare-bones main activity. */
public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }
}

Thêm tệp kê khai, AndroidManifest.xml vào $APPLICATION_PATH khởi chạy MainActivity khi khởi động ứng dụng:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.mediapipe.apps.basic">

  <uses-sdk
      android:minSdkVersion="19"
      android:targetSdkVersion="19" />

  <application
      android:allowBackup="true"
      android:label="${appName}"
      android:supportsRtl="true"
      android:theme="@style/AppTheme">
      <activity
          android:name="${mainActivity}"
          android:exported="true"
          android:screenOrientation="portrait">
          <intent-filter>
              <action android:name="android.intent.action.MAIN" />
              <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
      </activity>
  </application>

</manifest>

Trong ứng dụng này, chúng ta đang dùng giao diện Theme.AppCompat trong ứng dụng nên cần tham chiếu chủ đề thích hợp. Thêm colors.xml vào $APPLICATION_PATH/res/values/:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>

Thêm styles.xml vào $APPLICATION_PATH/res/values/:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

Để xây dựng ứng dụng, hãy thêm tệp BUILD vào $APPLICATION_PATH${appName}${mainActivity} trong tệp kê khai sẽ được thay thế bằng chuỗi được chỉ định trong BUILD như minh hoạ dưới đây.

android_library(
    name = "basic_lib",
    srcs = glob(["*.java"]),
    manifest = "AndroidManifest.xml",
    resource_files = glob(["res/**"]),
    deps = [
        "//third_party:android_constraint_layout",
        "//third_party:androidx_appcompat",
    ],
)

android_binary(
    name = "helloworld",
    manifest = "AndroidManifest.xml",
    manifest_values = {
        "applicationId": "com.google.mediapipe.apps.basic",
        "appName": "Hello World",
        "mainActivity": ".MainActivity",
    },
    multidex = "native",
    deps = [
        ":basic_lib",
    ],
)

Quy tắc android_library thêm các phần phụ thuộc cho MainActivity, các tệp tài nguyên và AndroidManifest.xml.

Quy tắc android_binary sử dụng thư viện Android basic_lib được tạo để tạo APK nhị phân để cài đặt trên thiết bị Android của bạn.

Để tạo bản dựng ứng dụng, hãy dùng lệnh sau:

bazel build -c opt --config=android_arm64 $APPLICATION_PATH:helloworld

Cài đặt tệp APK đã tạo bằng adb install. Ví dụ:

adb install bazel-bin/$APPLICATION_PATH/helloworld.apk

Mở ứng dụng trên thiết bị. Nút này sẽ hiển thị một màn hình có văn bản Hello World!.

bazel_hello_world_android

Đang sử dụng máy ảnh qua CameraX

Quyền truy cập máy ảnh

Để sử dụng camera trong ứng dụng của chúng ta, chúng ta cần yêu cầu người dùng cung cấp quyền truy cập vào máy ảnh. Để yêu cầu quyền truy cập vào camera, hãy thêm thông tin sau vào AndroidManifest.xml:

<!-- For using the camera -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />

Thay đổi phiên bản SDK tối thiểu thành 21 và nhắm mục tiêu phiên bản SDK thành 27 trong cùng một tệp:

<uses-sdk
    android:minSdkVersion="21"
    android:targetSdkVersion="27" />

Điều này đảm bảo rằng người dùng được nhắc yêu cầu quyền truy cập vào camera và bật chúng tôi dùng thư viện CameraX để truy cập máy ảnh.

Để yêu cầu quyền truy cập vào camera, chúng ta có thể dùng một tiện ích do Khung MediaPipe cung cấp thành phần chính, cụ thể là PermissionHelper. Để sử dụng công cụ này, hãy thêm phần phụ thuộc "//mediapipe/java/com/google/mediapipe/components:android_components" trong Quy tắc mediapipe_lib trong BUILD.

Để sử dụng PermissionHelper trong MainActivity, hãy thêm dòng sau vào phương thức Hàm onCreate:

PermissionHelper.checkAndRequestCameraPermissions(this);

Thao tác này sẽ nhắc người dùng thông qua một hộp thoại trên màn hình để yêu cầu cấp quyền sử dụng máy ảnh trong ứng dụng này.

Thêm mã sau đây để xử lý phản hồi của người dùng:

@Override
public void onRequestPermissionsResult(
    int requestCode, String[] permissions, int[] grantResults) {
  super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  PermissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

@Override
protected void onResume() {
  super.onResume();
  if (PermissionHelper.cameraPermissionsGranted(this)) {
    startCamera();
  }
}

public void startCamera() {}

Tạm thời, chúng ta sẽ để trống phương thức startCamera(). Khi người dùng trả lời vào lời nhắc, MainActivity sẽ tiếp tục và onResume() sẽ được gọi. Mã này sẽ xác nhận rằng bạn đã được cấp quyền sử dụng camera, sau đó sẽ khởi động máy ảnh.

Tạo lại và cài đặt ứng dụng. Bây giờ, bạn sẽ thấy một lời nhắc yêu cầu quyền truy cập vào máy ảnh dành cho ứng dụng.

Quyền truy cập máy ảnh

Khi có quyền truy cập vào máy ảnh, chúng ta có thể bắt đầu và tìm nạp khung hình từ máy ảnh.

Để xem các khung hình từ máy ảnh, chúng ta sẽ sử dụng SurfaceView. Mỗi khung từ máy ảnh sẽ được lưu trữ trong đối tượng SurfaceTexture. Để sử dụng các tính năng này, chúng tôi trước tiên cần thay đổi bố cục của ứng dụng.

Xoá toàn bộ khối mã TextView khỏi $APPLICATION_PATH/res/layout/activity_main.xml rồi thêm mã sau thay vào đó:

<FrameLayout
    android:id="@+id/preview_display_layout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1">
    <TextView
        android:id="@+id/no_camera_access_view"
        android:layout_height="fill_parent"
        android:layout_width="fill_parent"
        android:gravity="center"
        android:text="@string/no_camera_access" />
</FrameLayout>

Khối mã này có một FrameLayout mới tên là preview_display_layout và một TextView được lồng bên trong đó, có tên là no_camera_access_preview. Khi camera chưa được cấp quyền truy cập, ứng dụng của chúng tôi sẽ hiển thị TextView với một thông báo chuỗi, được lưu trữ trong biến no_camera_access. Thêm dòng sau vào tệp $APPLICATION_PATH/res/values/strings.xml:

<string name="no_camera_access" translatable="false">Please grant camera permissions.</string>

Khi người dùng không cấp quyền truy cập vào camera, màn hình giờ đây sẽ trông giống như sau:

missing_camera_permission_android

Bây giờ, chúng ta sẽ thêm các đối tượng SurfaceTextureSurfaceView vào MainActivity:

private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;

Trong hàm onCreate(Bundle), hãy thêm hai dòng sau trước yêu cầu quyền truy cập vào camera:

previewDisplayView = new SurfaceView(this);
setupPreviewDisplayView();

Và giờ hãy thêm mã xác định setupPreviewDisplayView():

private void setupPreviewDisplayView() {
  previewDisplayView.setVisibility(View.GONE);
  ViewGroup viewGroup = findViewById(R.id.preview_display_layout);
  viewGroup.addView(previewDisplayView);
}

Chúng ta xác định một đối tượng SurfaceView mới và thêm đối tượng đó vào preview_display_layout FrameLayout sao cho chúng ta có thể dùng đối tượng này để hiển thị khung máy ảnh bằng cách sử dụng đối tượng SurfaceTexture có tên previewFrameTexture.

Để dùng previewFrameTexture nhằm lấy khung hình máy ảnh, chúng ta sẽ dùng CameraX. Khung cung cấp một tiện ích có tên CameraXPreviewHelper để sử dụng CameraX. Lớp này cập nhật một trình nghe khi máy ảnh được khởi động qua onCameraStarted(@Nullable SurfaceTexture).

Để sử dụng phần mềm tiện ích này, hãy sửa đổi tệp BUILD để thêm phần phụ thuộc vào "//mediapipe/java/com/google/mediapipe/components:android_camerax_helper"

Bây giờ, hãy nhập CameraXPreviewHelper và thêm dòng sau vào MainActivity:

private CameraXPreviewHelper cameraHelper;

Bây giờ, chúng ta có thể thêm phương thức triển khai vào startCamera():

public void startCamera() {
  cameraHelper = new CameraXPreviewHelper();
  cameraHelper.setOnCameraStartedListener(
    surfaceTexture -> {
      previewFrameTexture = surfaceTexture;
      // Make the display view visible to start showing the preview.
      previewDisplayView.setVisibility(View.VISIBLE);
    });
}

Thao tác này sẽ tạo một đối tượng CameraXPreviewHelper mới và thêm một đối tượng ẩn danh trình nghe trên đối tượng. Khi cameraHelper báo hiệu rằng camera đã khởi động và có sẵn surfaceTexture để lấy các khung hình, chúng tôi sẽ lưu surfaceTexture thành previewFrameTexture và tạo previewDisplayView để chúng ta có thể bắt đầu thấy các khung hình từ previewFrameTexture.

Tuy nhiên, trước khi khởi động camera, chúng ta cần quyết định xem mình muốn camera nào sử dụng. CameraXPreviewHelper kế thừa từ CameraHelper, cung cấp hai FRONTBACK. Chúng ta có thể chuyển quyết định từ tệp BUILD làm siêu dữ liệu sao cho không cần thay đổi mã để tạo một phiên bản khác của thông qua một camera khác.

Giả sử chúng ta muốn sử dụng camera BACK để thực hiện phát hiện cạnh trên một cảnh trực tiếp mà chúng ta xem từ camera, hãy thêm siêu dữ liệu vào AndroidManifest.xml:

      ...
      <meta-data android:name="cameraFacingFront" android:value="${cameraFacingFront}"/>
  </application>
</manifest>

và chỉ định lựa chọn trong BUILD trong quy tắc nhị phân helloworld của android với một mục mới trong manifest_values:

manifest_values = {
    "applicationId": "com.google.mediapipe.apps.basic",
    "appName": "Hello World",
    "mainActivity": ".MainActivity",
    "cameraFacingFront": "False",
},

Giờ đây, trong MainActivity để truy xuất siêu dữ liệu được chỉ định trong manifest_values, thêm một đối tượng ApplicationInfo:

private ApplicationInfo applicationInfo;

Trong hàm onCreate(), hãy thêm:

try {
  applicationInfo =
      getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
  Log.e(TAG, "Cannot find application info: " + e);
}

Giờ thì hãy thêm dòng sau vào cuối hàm startCamera():

CameraHelper.CameraFacing cameraFacing =
    applicationInfo.metaData.getBoolean("cameraFacingFront", false)
        ? CameraHelper.CameraFacing.FRONT
        : CameraHelper.CameraFacing.BACK;
cameraHelper.startCamera(this, cameraFacing, /*unusedSurfaceTexture=*/ null);

Tại thời điểm này, ứng dụng sẽ được tạo thành công. Tuy nhiên, khi chạy ứng dụng trên thiết bị của mình, bạn sẽ thấy màn hình đen (mặc dù máy ảnh đã được cấp quyền). Đó là vì mặc dù chúng tôi lưu Biến surfaceTexture được cung cấp bởi CameraXPreviewHelper, previewSurfaceView chưa sử dụng dữ liệu đầu ra và chưa hiển thị kết quả đó trên màn hình.

Vì chúng ta muốn sử dụng các khung trong đồ thị MediaPipe, chúng ta sẽ không thêm mã vào hãy xem đầu ra của camera ngay trong hướng dẫn này. Thay vào đó, chúng ta sẽ chuyển sang cách chúng ta có thể gửi khung máy ảnh để xử lý lên đồ thị MediaPipe và hiển thị đầu ra của biểu đồ trên màn hình.

Thiết lập ExternalTextureConverter

SurfaceTexture chụp khung hình ảnh từ một luồng dưới dạng OpenGL ES hoạ tiết. Để sử dụng biểu đồ MediaPipe, khung hình được chụp từ máy ảnh phải được lưu trữ trong đối tượng kết cấu Open GL thông thường. Khung này cung cấp một lớp, ExternalTextureConverter để chuyển đổi hình ảnh được lưu trữ trong SurfaceTexture thành đối tượng hoạ tiết OpenGL thông thường.

Để sử dụng ExternalTextureConverter, chúng ta cũng cần một EGLContext, do đối tượng EglManager tạo và quản lý. Thêm phần phụ thuộc vào BUILD để sử dụng EglManager, "//mediapipe/java/com/google/mediapipe/glutil".

Trong MainActivity, hãy thêm các nội dung khai báo sau:

private EglManager eglManager;
private ExternalTextureConverter converter;

Trong hàm onCreate(Bundle), hãy thêm một câu lệnh để khởi chạy eglManager trước khi yêu cầu quyền truy cập vào camera:

eglManager = new EglManager(null);

Hãy nhớ rằng chúng ta đã xác định hàm onResume() trong MainActivity để xác nhận quyền truy cập vào camera đã được cấp và gọi startCamera(). Trước đây hãy kiểm tra, hãy thêm dòng sau vào onResume() để khởi chạy converter đối tượng:

converter = new ExternalTextureConverter(eglManager.getContext());

converter này hiện sử dụng GLContext do eglManager quản lý.

Chúng ta cũng cần ghi đè hàm onPause() trong MainActivity để nếu ứng dụng chuyển sang trạng thái tạm dừng, chúng ta sẽ đóng converter đúng cách:

@Override
protected void onPause() {
  super.onPause();
  converter.close();
}

Để chuyển đầu ra của previewFrameTexture vào converter, hãy thêm phương thức khối mã sau vào setupPreviewDisplayView():

previewDisplayView
 .getHolder()
 .addCallback(
     new SurfaceHolder.Callback() {
       @Override
       public void surfaceCreated(SurfaceHolder holder) {}

       @Override
       public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
         // (Re-)Compute the ideal size of the camera-preview display (the area that the
         // camera-preview frames get rendered onto, potentially with scaling and rotation)
         // based on the size of the SurfaceView that contains the display.
         Size viewSize = new Size(width, height);
         Size displaySize = cameraHelper.computeDisplaySizeFromViewSize(viewSize);

         // Connect the converter to the camera-preview frames as its input (via
         // previewFrameTexture), and configure the output width and height as the computed
         // display size.
         converter.setSurfaceTextureAndAttachToGLContext(
             previewFrameTexture, displaySize.getWidth(), displaySize.getHeight());
       }

       @Override
       public void surfaceDestroyed(SurfaceHolder holder) {}
     });

Trong khối mã này, chúng ta thêm một SurfaceHolder.Callback tuỳ chỉnh vào previewDisplayView và triển khai hàm surfaceChanged(SurfaceHolder holder, int format, int width, int height) để tính toán kích thước hiển thị phù hợp khung máy ảnh trên màn hình thiết bị và để buộc previewFrameTexture và gửi khung của displaySize đã tính toán đến converter.

Chúng ta hiện đã sẵn sàng sử dụng khung máy ảnh trong biểu đồ MediaPipe.

Sử dụng biểu đồ MediaPipe trong Android

Thêm phần phụ thuộc có liên quan

Để sử dụng biểu đồ MediaPipe, chúng ta cần thêm các phần phụ thuộc vào khung MediaPipe trên Android. Trước tiên, chúng ta sẽ thêm quy tắc bản dựng để tạo cc_binary bằng mã JNI của khung MediaPipe rồi xây dựng một quy tắc cc_library để sử dụng tệp nhị phân này trong ứng dụng của chúng tôi. Thêm khối mã dưới đây vào tệp BUILD:

cc_binary(
    name = "libmediapipe_jni.so",
    linkshared = 1,
    linkstatic = 1,
    deps = [
        "//mediapipe/java/com/google/mediapipe/framework/jni:mediapipe_framework_jni",
    ],
)

cc_library(
    name = "mediapipe_jni_lib",
    srcs = [":libmediapipe_jni.so"],
    alwayslink = 1,
)

Thêm phần phụ thuộc ":mediapipe_jni_lib" vào quy tắc bản dựng mediapipe_lib trong tệp BUILD.

Tiếp theo, chúng ta cần thêm các phần phụ thuộc cụ thể vào biểu đồ MediaPipe mà chúng ta muốn sử dụng trong ứng dụng.

Trước tiên, hãy thêm các phần phụ thuộc vào tất cả mã tính toán trong libmediapipe_jni.so quy tắc tạo:

"//mediapipe/graphs/edge_detection:mobile_calculators",

Biểu đồ MediaPipe là các tệp .pbtxt, nhưng để sử dụng chúng trong ứng dụng, chúng ta cần để sử dụng quy tắc xây dựng mediapipe_binary_graph nhằm tạo tệp .binarypb.

Trong quy tắc bản dựng tệp nhị phân helloworld cho Android, hãy thêm mediapipe_binary_graph mục tiêu cụ thể cho biểu đồ dưới dạng nội dung:

assets = [
  "//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
],
assets_dir = "",

Trong quy tắc xây dựng assets, bạn cũng có thể thêm các thành phần khác như TensorFlowLite mô hình được sử dụng trong biểu đồ của bạn.

Ngoài ra, hãy thêm manifest_values bổ sung cho những cơ sở lưu trú dành riêng cho biểu đồ để truy xuất sau trong MainActivity:

manifest_values = {
    "applicationId": "com.google.mediapipe.apps.basic",
    "appName": "Hello World",
    "mainActivity": ".MainActivity",
    "cameraFacingFront": "False",
    "binaryGraphName": "mobile_gpu.binarypb",
    "inputVideoStreamName": "input_video",
    "outputVideoStreamName": "output_video",
},

Lưu ý rằng binaryGraphName cho biết tên tệp của biểu đồ nhị phân, được xác định bởi trường output_name trong mục tiêu mediapipe_binary_graph. inputVideoStreamNameoutputVideoStreamName là dữ liệu đầu vào và đầu ra tên luồng video tương ứng được chỉ định trong biểu đồ.

MainActivity hiện cần tải khung MediaPipe. Ngoài ra, khung sử dụng OpenCV, vì vậy MainActvity cũng sẽ tải OpenCV. Sử dụng mã sau trong MainActivity (bên trong lớp, nhưng không bên trong bất kỳ hàm nào) để tải cả hai phần phụ thuộc:

static {
  // Load all native libraries needed by the app.
  System.loadLibrary("mediapipe_jni");
  System.loadLibrary("opencv_java3");
}

Sử dụng biểu đồ trong MainActivity

Trước tiên, chúng ta cần tải thành phần chứa .binarypb được biên dịch từ tệp .pbtxt của biểu đồ. Để thực hiện việc này, chúng ta có thể sử dụng tiện ích MediaPipe, AndroidAssetUtil.

Khởi chạy trình quản lý thành phần trong onCreate(Bundle) trước khi khởi chạy eglManager:

// Initialize asset manager so that MediaPipe native libraries can access the app assets, e.g.,
// binary graphs.
AndroidAssetUtil.initializeNativeAssetManager(this);

Bây giờ, chúng ta cần thiết lập một đối tượng FrameProcessor sẽ gửi khung máy ảnh do converter chuẩn bị cho biểu đồ MediaPipe rồi chạy biểu đồ, chuẩn bị kết quả đầu ra rồi cập nhật previewDisplayView để hiện kết quả. Thêm mã sau đây để khai báo FrameProcessor:

private FrameProcessor processor;

và khởi tạo trong onCreate(Bundle) sau khi khởi tạo eglManager:

processor =
    new FrameProcessor(
        this,
        eglManager.getNativeContext(),
        applicationInfo.metaData.getString("binaryGraphName"),
        applicationInfo.metaData.getString("inputVideoStreamName"),
        applicationInfo.metaData.getString("outputVideoStreamName"));

processor cần sử dụng các khung hình đã chuyển đổi từ converter để đang xử lý. Thêm dòng sau vào onResume() sau khi khởi tạo converter:

converter.setConsumer(processor);

processor cần gửi kết quả đến previewDisplayView. Để thực hiện việc này, hãy thêm các định nghĩa hàm sau đây cho SurfaceHolder.Callback tuỳ chỉnh của chúng ta:

@Override
public void surfaceCreated(SurfaceHolder holder) {
  processor.getVideoSurfaceOutput().setSurface(holder.getSurface());
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
  processor.getVideoSurfaceOutput().setSurface(null);
}

Khi SurfaceHolder được tạo, chúng ta đã có Surface cho VideoSurfaceOutput/processor. Khi tài sản bị huỷ, chúng tôi sẽ xoá nó khỏi VideoSurfaceOutput của processor.

Chỉ vậy thôi! Bây giờ, bạn có thể tạo và chạy thành công trên thiết bị và thấy tính năng phát hiện cạnh Sobel chạy trên camera trực tiếp nguồn cấp dữ liệu! Xin chúc mừng!

edge_detection_android_gpu_gif

Nếu bạn gặp bất kỳ sự cố nào, vui lòng xem mã đầy đủ của hướng dẫn tại đây.