สวัสดีชาวโลก! บน Android

บทนำ

สวัสดีโลกนี้ บทแนะนำ ใช้เฟรมเวิร์ก MediaPipe เพื่อพัฒนาแอปพลิเคชัน Android ที่ เรียกใช้กราฟ MediaPipe บน Android

สิ่งที่คุณจะสร้าง

แอปกล้องที่ใช้งานง่ายสำหรับการตรวจจับขอบของ Sobel แบบเรียลไทม์ที่ใช้กับวิดีโอสด สตรีมบนอุปกรณ์ Android

edge_detection_android_gpu_gif

ตั้งค่า

  1. ติดตั้งเฟรมเวิร์ก MediaPipe ในระบบของคุณ โปรดดูหัวข้อการติดตั้งเฟรมเวิร์ก เพื่อดูรายละเอียด
  2. ติดตั้ง Android Development SDK และ Android NDK และดูวิธีได้ใน [คู่มือการติดตั้งเฟรมเวิร์ก]
  3. เปิดใช้ตัวเลือกสำหรับนักพัฒนาซอฟต์แวร์ในอุปกรณ์ Android
  4. ตั้งค่า Bazel ในระบบเพื่อสร้างและทำให้แอป Android ใช้งานได้

กราฟสำหรับการตรวจจับขอบ

เราจะใช้กราฟ 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"
}

การแสดงภาพกราฟจะแสดงที่ด้านล่างนี้

edge_detection_mobile_gpu

กราฟนี้มีสตรีมอินพุตเดี่ยวชื่อ input_video สำหรับเฟรมขาเข้าทั้งหมด ที่จะได้จากกล้องของอุปกรณ์

โหนดแรกในกราฟ LuminanceCalculator ใช้แพ็กเก็ตเดียว (ภาพ เฟรม) และนำการเปลี่ยนแปลงความสว่างไปใช้โดยใช้ตัวปรับแสงสี OpenGL ผลลัพธ์ที่ได้ ระบบจะส่งเฟรมรูปภาพไปยังสตรีมเอาต์พุต luma_video

โหนดที่ 2 SobelEdgesCalculator ใช้การตรวจหา Edge กับขาเข้า แพ็กเก็ตในสตรีมและเอาต์พุตของ luma_video ส่งผลให้เกิดเอาต์พุต output_video สตรีม

แอปพลิเคชัน Android ของเราจะแสดงเฟรมภาพเอาต์พุตของ สตรีม output_video

การตั้งค่าแอปพลิเคชันขั้นต่ำเบื้องต้น

ก่อนอื่น เราเริ่มต้นด้วยแอปพลิเคชัน Android ง่ายๆ ที่แสดงข้อความ " Hello World!" บนหน้าจอ คุณอาจข้ามขั้นตอนนี้ได้หากคุ้นเคยกับการสร้าง Android แอปพลิเคชันที่ใช้ bazel

สร้างไดเรกทอรีใหม่ที่จะใช้สร้างแอปพลิเคชัน Android สำหรับ ตัวอย่างเช่น โค้ดทั้งหมดของบทแนะนำนี้อยู่ที่ mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic เราจะ ก็อ้างอิงเส้นทางนี้เป็น $APPLICATION_PATH ตลอดทั้ง Codelab

โปรดทราบว่าในเส้นทางไปยังแอปพลิเคชัน:

  • แอปพลิเคชันนี้มีชื่อว่า helloworld
  • $PACKAGE_PATH ของแอปพลิเคชันคือ com.google.mediapipe.apps.basic ซึ่งจะใช้ในข้อมูลโค้ดในบทแนะนำนี้ ดังนั้น อย่าลืมใช้ $PACKAGE_PATH ของคุณเองเมื่อคุณคัดลอก/ใช้ข้อมูลโค้ด

เพิ่มไฟล์ activity_main.xml ไปยัง $APPLICATION_PATH/res/layout วิธีนี้แสดง TextView ในโหมดเต็มหน้าจอของแอปพลิเคชันที่มีสตริง 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>

เพิ่ม MainActivity.java แบบง่ายไปยัง $APPLICATION_PATH ซึ่งจะโหลดเนื้อหา ของเลย์เอาต์ activity_main.xml ดังที่แสดงด้านล่าง

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);
  }
}

เพิ่มไฟล์ Manifest AndroidManifest.xml ไปยัง $APPLICATION_PATH ซึ่ง เปิดใช้งาน MainActivity เมื่อเริ่มแอปพลิเคชัน:

<?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>

ในแอปพลิเคชันของเรา เราใช้ธีม Theme.AppCompat ในแอป เราจึงต้อง การอ้างอิงธีมที่เหมาะสม เพิ่ม colors.xml ไว้ใน $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>

เพิ่ม styles.xml ไปยัง $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>

หากต้องการสร้างแอปพลิเคชัน ให้เพิ่มไฟล์ BUILD ไปยัง $APPLICATION_PATH และ ระบบจะแทนที่ ${appName} และ ${mainActivity} ในไฟล์ Manifest ด้วยสตริง ที่ระบุใน BUILD ดังที่แสดงด้านล่าง

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",
    ],
)

กฎ android_library จะเพิ่มทรัพยากร Dependency สำหรับ MainActivity ไฟล์ทรัพยากร และ AndroidManifest.xml

กฎ android_binary ใช้ไลบรารี Android basic_lib ที่สร้างขึ้นเพื่อ สร้าง APK แบบไบนารีสำหรับติดตั้งในอุปกรณ์ Android

ใช้คำสั่งต่อไปนี้เพื่อสร้างแอป

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

ติดตั้งไฟล์ APK ที่สร้างขึ้นโดยใช้ adb install เช่น

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

เปิดแอปพลิเคชันในอุปกรณ์ ระบบควรแสดงหน้าจอที่มีข้อความ Hello World!

bazel_hello_world_android

การใช้กล้องผ่าน CameraX

สิทธิ์เข้าถึงกล้อง

ในการใช้กล้องในแอปพลิเคชันของเรา จะต้องขอให้ผู้ใช้ระบุ เข้าถึงกล้อง หากต้องการขอสิทธิ์ใช้กล้อง ให้เพิ่มรายการต่อไปนี้ใน AndroidManifest.xml:

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

เปลี่ยนเวอร์ชัน SDK ขั้นต่ำเป็น 21 และเวอร์ชัน SDK เป้าหมายเป็น 27 ใน ไฟล์เดียวกัน:

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

การดำเนินการนี้ช่วยให้มั่นใจว่าผู้ใช้ได้รับแจ้งให้ขอสิทธิ์ใช้กล้อง และเปิดใช้ ให้เราใช้ไลบรารี CameraX สำหรับการเข้าถึงกล้อง

หากต้องการขอสิทธิ์เข้าถึงกล้อง เราใช้ยูทิลิตีที่ MediaPipe Framework มีให้ ซึ่งได้แก่ PermissionHelper หากต้องการใช้งาน ให้เพิ่มทรัพยากร Dependency "//mediapipe/java/com/google/mediapipe/components:android_components" ในช่วง กฎ mediapipe_lib รายการใน BUILD

หากต้องการใช้ PermissionHelper ใน MainActivity ให้เพิ่มบรรทัดต่อไปนี้ในส่วน ฟังก์ชัน onCreate:

PermissionHelper.checkAndRequestCameraPermissions(this);

การดำเนินการนี้จะแสดงกล่องโต้ตอบบนหน้าจอเพื่อขอสิทธิ์ที่จะ ใช้กล้องในแอปพลิเคชันนี้

เพิ่มโค้ดต่อไปนี้เพื่อจัดการคำตอบของผู้ใช้

@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() {}

ขณะนี้เราจะเว้นเมธอด startCamera() ว่างไว้ เมื่อผู้ใช้ตอบกลับ เมื่อมีข้อความแจ้ง ระบบจะเรียกให้ MainActivity กลับมาทำงาน และระบบจะเรียกใช้ onResume() โค้ดจะยืนยันว่าได้ให้สิทธิ์ในการใช้กล้องแล้ว และจะเริ่มต้นกล้อง

สร้างแอปพลิเคชันใหม่และติดตั้ง ตอนนี้คุณควรเห็นข้อความแจ้งให้ขอ เข้าถึงกล้องของแอปพลิเคชัน

สิทธิ์เข้าถึงกล้อง

เมื่อมีสิทธิ์เข้าถึงกล้อง เราสามารถเริ่มและดึงข้อมูลเฟรมจาก กล้อง

หากต้องการดูเฟรมจากกล้อง เราจะใช้ SurfaceView แต่ละเฟรม จากกล้องจะจัดเก็บอยู่ในออบเจ็กต์ SurfaceTexture ในการใช้ข้อมูลเหล่านี้ เรา เราต้องทำการเปลี่ยนเลย์เอาต์ของแอปพลิเคชันของเราก่อน

นำโค้ดบล็อกของ TextView ทั้งหมดออกจาก $APPLICATION_PATH/res/layout/activity_main.xml และเพิ่มโค้ดต่อไปนี้ แทน:

<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>

โค้ดบล็อกนี้มี FrameLayout ใหม่ชื่อ preview_display_layout และ TextView ซ้อนอยู่ข้างใน ชื่อว่า no_camera_access_preview เมื่อใช้กล้อง ไม่ได้รับสิทธิ์ในการเข้าถึง แอปพลิเคชันของเราจะแสดง TextView ด้วยข้อความสตริง ซึ่งจัดเก็บไว้ในตัวแปร no_camera_access เพิ่มบรรทัดต่อไปนี้ในไฟล์ $APPLICATION_PATH/res/values/strings.xml

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

เมื่อผู้ใช้ไม่ได้ให้สิทธิ์กล้องถ่ายรูป หน้าจอจะมีลักษณะดังนี้ ดังนี้

missing_camera_permission_android

ตอนนี้เราจะเพิ่มออบเจ็กต์ SurfaceTexture และ SurfaceView ไปยัง MainActivity:

private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;

ในฟังก์ชัน onCreate(Bundle) ให้เพิ่ม 2 บรรทัดต่อไปนี้ก่อน กำลังขอสิทธิ์ใช้กล้อง:

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

แล้วเพิ่มโค้ดที่กำหนด setupPreviewDisplayView():

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

เรากำหนดออบเจ็กต์ SurfaceView ใหม่และเพิ่มลงใน preview_display_layout FrameLayout เพื่อให้เราใช้เพื่อแสดง เฟรมกล้องที่ใช้ออบเจ็กต์ SurfaceTexture ชื่อ previewFrameTexture

ในการใช้ previewFrameTexture เพื่อรับเฟรมกล้อง เราจะใช้ CameraX เฟรมเวิร์กมียูทิลิตีชื่อ CameraXPreviewHelper เพื่อใช้ CameraX ชั้นเรียนนี้จะอัปเดตผู้ฟังเมื่อกล้องเริ่มทำงานผ่าน onCameraStarted(@Nullable SurfaceTexture)

หากต้องการใช้ยูทิลิตีนี้ ให้แก้ไขไฟล์ BUILD เพื่อเพิ่มทรัพยากร Dependency "//mediapipe/java/com/google/mediapipe/components:android_camerax_helper"

นำเข้า CameraXPreviewHelper แล้วเพิ่มบรรทัดต่อไปนี้ลงใน MainActivity:

private CameraXPreviewHelper cameraHelper;

ตอนนี้เราสามารถเพิ่มการติดตั้งใช้งานไปยัง 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);
    });
}

การดำเนินการนี้จะสร้างออบเจ็กต์ CameraXPreviewHelper ใหม่และเพิ่มแบบไม่ระบุตัวตน Listener ของออบเจ็กต์ เมื่อ cameraHelper ส่งสัญญาณว่ากล้องเริ่มทำงานแล้ว และ surfaceTexture ที่คว้าเฟรมได้ เราบันทึก surfaceTexture เป็น previewFrameTexture และทำให้ previewDisplayView แสดงเพื่อให้เราเริ่มเห็นเฟรมจาก previewFrameTexture

แต่ก่อนที่จะเริ่มกล้อง เราต้องเลือกว่าจะใช้กล้องตัวไหน CameraXPreviewHelper รับค่าจาก CameraHelper ซึ่งมี ตัวเลือก FRONT และ BACK เราสามารถตัดสินได้จากไฟล์ BUILD เป็นข้อมูลเมตาที่ไม่จำเป็นต้องเปลี่ยนแปลงโค้ดเพื่อสร้าง แอปที่ใช้กล้องอื่น

สมมติว่าเราต้องการใช้กล้อง BACK เพื่อตรวจจับขอบในฉากสด ที่เราดูจากกล้อง ให้เพิ่มข้อมูลเมตาลงใน AndroidManifest.xml

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

และระบุการเลือกใน BUILD ในกฎไบนารี Android helloworld รายการใหม่ใน manifest_values:

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

ตอนนี้ที่ MainActivity เพื่อเรียกข้อมูลเมตาที่ระบุไว้ใน manifest_values เพิ่มออบเจ็กต์ ApplicationInfo:

private ApplicationInfo applicationInfo;

เพิ่มข้อมูลต่อไปนี้ในฟังก์ชัน onCreate()

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

ตอนนี้ให้เพิ่มบรรทัดต่อไปนี้ที่ส่วนท้ายของฟังก์ชัน startCamera()

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

ณ จุดนี้ แอปพลิเคชันควรจะสร้างได้สำเร็จ อย่างไรก็ตาม เมื่อคุณเรียกใช้ แอปพลิเคชันบนอุปกรณ์ของคุณ คุณจะเห็นหน้าจอสีดำ (แม้ว่าจะเป็นกล้อง ได้รับสิทธิ์แล้ว) เพราะแม้ว่าเราจะบันทึก surfaceTexture ได้มาจาก CameraXPreviewHelper, previewSurfaceView ยังไม่ได้ใช้เอาต์พุตและแสดงบนหน้าจอ

เนื่องจากเราต้องการใช้เฟรมในกราฟ MediaPipe เราจะไม่เพิ่มโค้ดลงใน ดูเอาต์พุตจากกล้องได้โดยตรงในบทแนะนำนี้ แต่เราจะข้ามไปที่ เราสามารถส่งเฟรมกล้องเพื่อประมวลผลไปยังกราฟ MediaPipe และแสดง เอาต์พุตของกราฟบนหน้าจอ

การตั้งค่า ExternalTextureConverter

SurfaceTexture จับภาพเฟรมภาพจากสตรีมเป็น OpenGL ES ของ Google ในการใช้กราฟ MediaPipe เฟรมที่บันทึกจากกล้องควร ที่เก็บไว้ในออบเจ็กต์พื้นผิว Open GL ปกติ เฟรมเวิร์กนี้จะมอบคลาส ExternalTextureConverter เพื่อแปลงรูปภาพที่เก็บไว้ใน SurfaceTexture เป็นออบเจ็กต์พื้นผิว OpenGL ปกติ

หากต้องการใช้ ExternalTextureConverter เราต้องมี EGLContext ด้วย สร้างและจัดการโดยออบเจ็กต์ EglManager เพิ่มการอ้างอิงไปยัง BUILD เพื่อใช้ EglManager, "//mediapipe/java/com/google/mediapipe/glutil"

ใน MainActivity ให้เพิ่มประกาศต่อไปนี้

private EglManager eglManager;
private ExternalTextureConverter converter;

ในฟังก์ชัน onCreate(Bundle) ให้เพิ่มคำสั่งเพื่อเริ่มต้น eglManager ออบเจ็กต์ก่อนขอสิทธิ์กล้อง:

eglManager = new EglManager(null);

เรียกคืนว่าเรากำหนดฟังก์ชัน onResume() ใน MainActivity เพื่อยืนยัน ให้สิทธิ์เข้าถึงกล้องและโทรหา startCamera() แล้ว ก่อนหน้านี้ ตรวจสอบ เพิ่มบรรทัดต่อไปนี้ใน onResume() เพื่อเริ่มต้น converter ออบเจ็กต์:

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

ตอนนี้ converter นี้ใช้ GLContext ที่จัดการโดย eglManager

นอกจากนี้ เรายังต้องลบล้างฟังก์ชัน onPause() ใน MainActivity เพื่อให้ หากแอปพลิเคชันอยู่ในสถานะหยุดชั่วคราว เราจะปิด converter อย่างถูกต้อง:

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

หากต้องการเชื่อมต่อเอาต์พุตของ previewFrameTexture ไปยัง converter ให้เพิ่มพารามิเตอร์ บล็อกโค้ดต่อไปนี้ไปยัง 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) {}
     });

ในโค้ดบล็อกนี้ เราจะเพิ่ม SurfaceHolder.Callback ที่กำหนดเองลงใน previewDisplayViewและใช้ฟังก์ชัน surfaceChanged(SurfaceHolder holder, int format, int width, int height) เพื่อคำนวณขนาดการแสดงผลที่เหมาะสม ของเฟรมกล้องบนหน้าจออุปกรณ์และผูกpreviewFrameTexture และส่งเฟรมของ displaySize ที่คำนวณไปยัง converter

ตอนนี้เราพร้อมที่จะใช้เฟรมกล้องในกราฟ MediaPipe แล้ว

การใช้กราฟ MediaPipe ใน Android

เพิ่มทรัพยากร Dependency ที่เกี่ยวข้อง

ในการใช้กราฟ MediaPipe เราต้องเพิ่มการอ้างอิงลงในเฟรมเวิร์ก MediaPipe บน Android เราจะเพิ่มกฎของบิลด์เพื่อสร้าง cc_binary โดยใช้โค้ด JNI ก่อน ของเฟรมเวิร์ก MediaPipe แล้วสร้างกฎ cc_library เพื่อใช้ไบนารีนี้ ในแอปพลิเคชันของเรา เพิ่มโค้ดบล็อกต่อไปนี้ลงในไฟล์ 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,
)

เพิ่มทรัพยากร Dependency ":mediapipe_jni_lib" ไปยังกฎบิลด์ mediapipe_lib ใน ไฟล์ BUILD

ต่อไป เราต้องเพิ่มทรัพยากร Dependency เฉพาะสำหรับกราฟ MediaPipe ที่ต้องการใช้ ในแอปพลิเคชัน

ก่อนอื่นให้เพิ่มทรัพยากร Dependency ลงในรหัสเครื่องคำนวณทั้งหมดใน libmediapipe_jni.so กฎการสร้าง:

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

กราฟ MediaPipe เป็นไฟล์ .pbtxt แต่การนำไปใช้ในแอปพลิเคชันนั้น เราจำเป็นต้อง เพื่อใช้กฎบิลด์ mediapipe_binary_graph เพื่อสร้างไฟล์ .binarypb

ในกฎบิลด์ไบนารีของ Android helloworld ให้เพิ่ม mediapipe_binary_graph กำหนดเป้าหมายให้กราฟเป็นเนื้อหาโดยเฉพาะ:

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

นอกจากนี้ในกฎบิลด์ assets คุณยังเพิ่มเนื้อหาอื่นๆ เช่น TensorFlowLite ได้อีกด้วย แบบจำลองที่ใช้ในกราฟของคุณ

นอกจากนี้ ให้เพิ่ม manifest_values สำหรับพร็อพเพอร์ตี้เฉพาะ กราฟ สำหรับเรียกดูในภายหลังใน 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",
},

โปรดทราบว่า binaryGraphName จะระบุชื่อไฟล์ของกราฟไบนารี กำหนดโดยฟิลด์ output_name ในเป้าหมาย mediapipe_binary_graph inputVideoStreamName และ outputVideoStreamName เป็นอินพุตและเอาต์พุต ชื่อสตรีมวิดีโอที่ระบุในกราฟตามลำดับ

ตอนนี้ MainActivity ต้องโหลดเฟรมเวิร์ก MediaPipe นอกจากนี้ เฟรมเวิร์กใช้ OpenCV ดังนั้น MainActvity ควรโหลด OpenCV ด้วย ใช้เมนู รหัสต่อไปนี้ใน MainActivity (ในชั้นเรียน แต่ไม่ได้อยู่ในฟังก์ชันใดๆ) เพื่อโหลดทรัพยากร Dependency ทั้ง 2 แบบดังนี้

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

ใช้กราฟใน MainActivity

ก่อนอื่น เราต้องโหลดเนื้อหาซึ่งมี .binarypb ที่คอมไพล์จาก ไฟล์ .pbtxt ของกราฟ ในการดำเนินการดังกล่าว เราสามารถใช้ยูทิลิตี MediaPipe AndroidAssetUtil

เริ่มต้นเครื่องมือจัดการเนื้อหาใน onCreate(Bundle) ก่อนเริ่มต้น eglManager:

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

ตอนนี้เราจำเป็นต้องตั้งค่าออบเจ็กต์ FrameProcessor ที่ส่งเฟรมกล้อง ที่ converter จัดเตรียมไว้สำหรับกราฟ MediaPipe และเรียกใช้กราฟ เตรียม เอาต์พุตแล้วอัปเดต previewDisplayView เพื่อแสดงเอาต์พุต เพิ่ม โค้ดต่อไปนี้เพื่อประกาศ FrameProcessor:

private FrameProcessor processor;

และเริ่มต้นใน onCreate(Bundle) หลังจากกำหนดค่า eglManager:

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

processor ต้องใช้เฟรมที่แปลงจาก converter สำหรับ การประมวลผล เพิ่มบรรทัดต่อไปนี้ใน onResume() หลังจากกำหนดค่าเริ่มต้น converter:

converter.setConsumer(processor);

processor ควรส่งเอาต์พุตไปยัง previewDisplayView หากต้องการดำเนินการนี้ ให้เพิ่ม คำจำกัดความของฟังก์ชันต่อไปนี้สำหรับ SurfaceHolder.Callback ที่กำหนดเองของเรา

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

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

เมื่อสร้าง SurfaceHolder แล้ว เรามี Surface ไปยัง VideoSurfaceOutput ของ processor เมื่อถูกทำลาย เราจะนำออกจาก VideoSurfaceOutput ของ processor

เพียงเท่านี้ก็เรียบร้อยแล้ว ตอนนี้คุณควรจะสร้างและเรียกใช้ บนอุปกรณ์ และเห็นว่าการตรวจจับขอบของ Sobel ทำงานบนกล้องแบบสด ฟีด! ยินดีด้วย

edge_detection_android_gpu_gif

หากพบปัญหา โปรดดูโค้ดแบบเต็มของบทแนะนำ ที่นี่