Halo Dunia! di Android

Pengantar

Tutorial Hello World! ini menggunakan MediaPipe Framework untuk mengembangkan aplikasi Android yang menjalankan grafik MediaPipe di Android.

Yang akan Anda bangun

Aplikasi kamera sederhana untuk deteksi tepi Sobel secara real-time yang diterapkan ke streaming video live di perangkat Android.

edge_detection_android_gpu_gif

Penyiapan

  1. Instal MediaPipe Framework di sistem Anda, lihat Panduan penginstalan framework untuk mengetahui detailnya.
  2. Instal Android Development SDK dan Android NDK. Pelajari cara melakukannya juga di [Panduan penginstalan framework].
  3. Aktifkan opsi developer di perangkat Android Anda.
  4. Siapkan Bazel di sistem untuk membangun dan men-deploy aplikasi Android.

Grafik untuk deteksi tepi

Kita akan menggunakan grafik berikut, 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"
}

Visualisasi grafik ditampilkan di bawah ini:

edge_detection_mobile_gpu

Grafik ini memiliki satu aliran input bernama input_video untuk semua frame masuk yang akan disediakan oleh kamera perangkat Anda.

Node pertama dalam grafik, LuminanceCalculator, mengambil satu paket (frame gambar) dan menerapkan perubahan luminans menggunakan shader OpenGL. Frame gambar yang dihasilkan dikirim ke aliran output luma_video.

Node kedua, SobelEdgesCalculator, menerapkan deteksi tepi ke paket yang masuk di aliran luma_video dan menghasilkan output dalam aliran output output_video.

Aplikasi Android kita akan menampilkan frame gambar output dari aliran data output_video.

Penyiapan aplikasi minimal awal

Pertama-tama, kita akan mulai dengan aplikasi Android sederhana yang menampilkan "Hello World!" di layar. Anda dapat melewati langkah ini jika sudah terbiasa membangun aplikasi Android menggunakan bazel.

Buat direktori baru tempat Anda akan membuat aplikasi Android. Misalnya, kode lengkap tutorial ini dapat ditemukan di mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic. Kita akan menyebut jalur ini sebagai $APPLICATION_PATH di seluruh codelab.

Perhatikan, pada jalur ke aplikasi:

  • Aplikasi tersebut diberi nama helloworld.
  • $PACKAGE_PATH aplikasi adalah com.google.mediapipe.apps.basic. Kode ini digunakan dalam cuplikan kode dalam tutorial ini, jadi jangan lupa untuk menggunakan $PACKAGE_PATH Anda sendiri saat menyalin/menggunakan cuplikan kode tersebut.

Tambahkan file activity_main.xml ke $APPLICATION_PATH/res/layout. Cara ini akan menampilkan TextView di layar penuh aplikasi dengan string 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>

Tambahkan MainActivity.java sederhana ke $APPLICATION_PATH yang memuat konten tata letak activity_main.xml seperti yang ditunjukkan di bawah ini:

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

Tambahkan file manifes, AndroidManifest.xml ke $APPLICATION_PATH, yang meluncurkan MainActivity saat aplikasi dimulai:

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

Dalam aplikasi, kita menggunakan tema Theme.AppCompat di aplikasi sehingga kita memerlukan referensi tema yang sesuai. Tambahkan colors.xml ke $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>

Tambahkan styles.xml ke $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>

Untuk mem-build aplikasi, tambahkan file BUILD ke $APPLICATION_PATH, dan ${appName} serta ${mainActivity} dalam manifes akan diganti dengan string yang ditentukan dalam BUILD seperti ditunjukkan di bawah ini.

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

Aturan android_library menambahkan dependensi untuk MainActivity, file resource, dan AndroidManifest.xml.

Aturan android_binary menggunakan library Android basic_lib yang dihasilkan untuk mem-build APK biner yang akan diinstal di perangkat Android Anda.

Untuk membangun aplikasi, gunakan perintah berikut:

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

Instal file APK yang dihasilkan menggunakan adb install. Contoh:

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

Buka aplikasi di perangkat Anda. Layar dengan teks Hello World! akan ditampilkan.

bazel_hello_world_android

Menggunakan kamera melalui CameraX

Izin Kamera

Untuk menggunakan kamera di aplikasi, kita perlu meminta pengguna memberikan akses ke kamera. Untuk meminta izin kamera, tambahkan hal berikut ke AndroidManifest.xml:

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

Ubah versi SDK minimum ke 21 dan versi SDK target ke 27 di file yang sama:

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

Hal ini memastikan bahwa pengguna diminta untuk meminta izin kamera dan memungkinkan kita menggunakan library CameraX untuk akses kamera.

Untuk meminta izin kamera, kita dapat menggunakan utilitas yang disediakan oleh komponen MediaPipe Framework, yaitu PermissionHelper. Untuk menggunakannya, tambahkan dependensi "//mediapipe/java/com/google/mediapipe/components:android_components" dalam aturan mediapipe_lib di BUILD.

Untuk menggunakan PermissionHelper di MainActivity, tambahkan baris berikut ke fungsi onCreate:

PermissionHelper.checkAndRequestCameraPermissions(this);

Tindakan ini akan meminta pengguna dengan dialog di layar untuk meminta izin menggunakan kamera dalam aplikasi ini.

Tambahkan kode berikut untuk menangani respons pengguna:

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

Kita akan membiarkan metode startCamera() kosong untuk saat ini. Saat pengguna merespons perintah tersebut, MainActivity akan dilanjutkan dan onResume() akan dipanggil. Kode ini akan mengonfirmasi bahwa izin untuk menggunakan kamera telah diberikan, lalu akan memulai kamera.

Build ulang dan instal aplikasi. Sekarang Anda akan melihat perintah yang meminta akses ke kamera untuk aplikasi.

Akses Kamera

Dengan izin kamera yang tersedia, kita dapat memulai dan mengambil frame dari kamera.

Untuk melihat frame dari kamera, kita akan menggunakan SurfaceView. Setiap frame dari kamera akan disimpan dalam objek SurfaceTexture. Untuk menggunakannya, kita perlu mengubah tata letak aplikasi terlebih dahulu.

Hapus seluruh blok kode TextView dari $APPLICATION_PATH/res/layout/activity_main.xml dan tambahkan kode berikut sebagai gantinya:

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

Blok kode ini memiliki FrameLayout baru bernama preview_display_layout dan TextView yang disusun bertingkat di dalamnya, bernama no_camera_access_preview. Jika izin akses kamera tidak diberikan, aplikasi kita akan menampilkan TextView dengan pesan string, yang disimpan dalam variabel no_camera_access. Tambahkan baris berikut di file $APPLICATION_PATH/res/values/strings.xml:

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

Jika pengguna tidak memberikan izin kamera, layar sekarang akan terlihat seperti ini:

missing_camera_permission_android

Sekarang, kita akan menambahkan objek SurfaceTexture dan SurfaceView ke MainActivity:

private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;

Dalam fungsi onCreate(Bundle), tambahkan dua baris berikut sebelum meminta izin kamera:

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

Sekarang, tambahkan kode yang menentukan setupPreviewDisplayView():

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

Kita menentukan objek SurfaceView baru dan menambahkannya ke objek preview_display_layout FrameLayout sehingga kita dapat menggunakannya untuk menampilkan frame kamera menggunakan objek SurfaceTexture bernama previewFrameTexture.

Untuk menggunakan previewFrameTexture guna mendapatkan frame kamera, kita akan menggunakan CameraX. Framework menyediakan utilitas bernama CameraXPreviewHelper untuk menggunakan CameraX. Class ini memperbarui pemroses saat kamera dimulai melalui onCameraStarted(@Nullable SurfaceTexture).

Untuk menggunakan utilitas ini, ubah file BUILD untuk menambahkan dependensi pada "//mediapipe/java/com/google/mediapipe/components:android_camerax_helper".

Sekarang impor CameraXPreviewHelper dan tambahkan baris berikut ke MainActivity:

private CameraXPreviewHelper cameraHelper;

Sekarang, kita dapat menambahkan implementasi ke 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);
    });
}

Tindakan ini akan membuat objek CameraXPreviewHelper baru dan menambahkan pemroses anonim pada objek tersebut. Saat cameraHelper yang menandakan bahwa kamera telah dimulai dan surfaceTexture untuk mengambil frame tersedia, kita akan menyimpan surfaceTexture tersebut sebagai previewFrameTexture, dan membuat previewDisplayView terlihat sehingga kita dapat mulai melihat frame dari previewFrameTexture.

Namun, sebelum memulai kamera, kita perlu memutuskan kamera mana yang ingin digunakan. CameraXPreviewHelper mewarisi dari CameraHelper yang menyediakan dua opsi, FRONT dan BACK. Kita dapat meneruskan keputusan dari file BUILD sebagai metadata sehingga tidak ada perubahan kode yang diperlukan untuk mem-build versi lain aplikasi menggunakan kamera berbeda.

Dengan asumsi kita ingin menggunakan kamera BACK untuk melakukan deteksi tepi pada adegan live yang kita lihat dari kamera, tambahkan metadata ke dalam AndroidManifest.xml:

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

dan tentukan pemilihan di BUILD dalam aturan biner Android helloworld dengan entri baru di manifest_values:

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

Sekarang, di MainActivity untuk mengambil metadata yang ditentukan dalam manifest_values, tambahkan objek ApplicationInfo:

private ApplicationInfo applicationInfo;

Di fungsi onCreate(), tambahkan:

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

Sekarang tambahkan baris berikut di akhir fungsi startCamera():

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

Pada tahap ini, aplikasi seharusnya berhasil dibangun. Namun, saat menjalankan aplikasi di perangkat, Anda akan melihat layar hitam (meskipun izin kamera telah diberikan). Hal ini karena meskipun kita menyimpan variabel surfaceTexture yang disediakan oleh CameraXPreviewHelper, previewSurfaceView belum menggunakan outputnya dan menampilkannya di layar.

Karena kita ingin menggunakan frame dalam grafik MediaPipe, kita tidak akan menambahkan kode untuk melihat output kamera secara langsung dalam tutorial ini. Sebagai gantinya, kita akan membahas cara mengirim frame kamera untuk diproses ke grafik MediaPipe dan menampilkan output grafik di layar.

Penyiapan ExternalTextureConverter

SurfaceTexture mengambil frame gambar dari aliran sebagai tekstur OpenGL ES. Untuk menggunakan grafik MediaPipe, frame yang diambil dari kamera harus disimpan dalam objek tekstur Open GL biasa. Framework ini menyediakan class, ExternalTextureConverter untuk mengonversi gambar yang disimpan dalam objek SurfaceTexture menjadi objek tekstur OpenGL biasa.

Untuk menggunakan ExternalTextureConverter, kita juga memerlukan EGLContext, yang dibuat dan dikelola oleh objek EglManager. Tambahkan dependensi ke file BUILD untuk menggunakan EglManager, "//mediapipe/java/com/google/mediapipe/glutil".

Di MainActivity, tambahkan deklarasi berikut:

private EglManager eglManager;
private ExternalTextureConverter converter;

Dalam fungsi onCreate(Bundle), tambahkan pernyataan untuk menginisialisasi objek eglManager sebelum meminta izin kamera:

eglManager = new EglManager(null);

Ingat bahwa kita menentukan fungsi onResume() di MainActivity untuk mengonfirmasi bahwa izin kamera telah diberikan dan memanggil startCamera(). Sebelum pemeriksaan ini, tambahkan baris berikut di onResume() untuk menginisialisasi objek converter:

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

converter ini sekarang menggunakan GLContext yang dikelola oleh eglManager.

Kita juga perlu mengganti fungsi onPause() di MainActivity sehingga jika aplikasi beralih ke status dijeda, kita akan menutup converter dengan benar:

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

Untuk menyalurkan output previewFrameTexture ke converter, tambahkan blok kode berikut ke 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) {}
     });

Dalam blok kode ini, kita menambahkan SurfaceHolder.Callback kustom ke previewDisplayView dan menerapkan fungsi surfaceChanged(SurfaceHolder holder, int format, int width, int height) untuk menghitung ukuran tampilan frame kamera yang sesuai di layar perangkat, serta mengikat objek previewFrameTexture dan mengirim frame displaySize yang dihitung ke converter.

Sekarang kita siap menggunakan bingkai kamera dalam grafik MediaPipe.

Menggunakan grafik MediaPipe di Android

Menambahkan dependensi yang relevan

Untuk menggunakan grafik MediaPipe, kita perlu menambahkan dependensi ke framework MediaPipe di Android. Pertama-tama, kita akan menambahkan aturan build untuk mem-build cc_binary menggunakan kode JNI dalam framework MediaPipe, lalu membuat aturan cc_library untuk menggunakan biner ini dalam aplikasi kita. Tambahkan blok kode berikut ke file BUILD Anda:

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

Tambahkan dependensi ":mediapipe_jni_lib" ke aturan build mediapipe_lib dalam file BUILD.

Selanjutnya, kita perlu menambahkan dependensi khusus untuk grafik MediaPipe yang ingin kita gunakan dalam aplikasi.

Pertama, tambahkan dependensi ke semua kode kalkulator dalam aturan build libmediapipe_jni.so:

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

Grafik MediaPipe adalah file .pbtxt, tetapi untuk menggunakannya dalam aplikasi, kita perlu menggunakan aturan build mediapipe_binary_graph untuk menghasilkan file .binarypb.

Dalam aturan build biner Android helloworld, tambahkan target mediapipe_binary_graph khusus untuk grafik sebagai aset:

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

Di aturan build assets, Anda juga dapat menambahkan aset lain seperti model TensorFlowLite yang digunakan dalam grafik.

Selain itu, tambahkan manifest_values lain untuk properti khusus grafik, yang akan diambil nanti dalam 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",
},

Perhatikan bahwa binaryGraphName menunjukkan nama file grafik biner, yang ditentukan oleh kolom output_name pada target mediapipe_binary_graph. inputVideoStreamName dan outputVideoStreamName adalah nama aliran video input dan output yang ditentukan dalam grafik.

Sekarang, MainActivity perlu memuat framework MediaPipe. Selain itu, framework menggunakan OpenCV, jadi MainActvity juga harus memuat OpenCV. Gunakan kode berikut di MainActivity (di dalam class, tetapi bukan di dalam fungsi apa pun) untuk memuat kedua dependensi:

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

Gunakan grafik dalam MainActivity

Pertama, kita harus memuat aset yang berisi .binarypb yang dikompilasi dari file .pbtxt grafik. Untuk melakukannya, kita dapat menggunakan utilitas MediaPipe, AndroidAssetUtil.

Lakukan inisialisasi pengelola aset di onCreate(Bundle) sebelum melakukan inisialisasi eglManager:

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

Sekarang, kita perlu menyiapkan objek FrameProcessor yang mengirim frame kamera yang disiapkan oleh converter ke grafik MediaPipe dan menjalankan grafik, menyiapkan output, lalu memperbarui previewDisplayView untuk menampilkan output. Tambahkan kode berikut untuk mendeklarasikan FrameProcessor:

private FrameProcessor processor;

dan lakukan inisialisasi di onCreate(Bundle) setelah melakukan inisialisasi eglManager:

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

processor perlu menggunakan frame yang dikonversi dari converter untuk diproses. Tambahkan baris berikut ke onResume() setelah melakukan inisialisasi converter:

converter.setConsumer(processor);

processor harus mengirimkan outputnya ke previewDisplayView Untuk melakukannya, tambahkan definisi fungsi berikut ke SurfaceHolder.Callback kustom kita:

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

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

Saat SurfaceHolder dibuat, kita menempatkan Surface ke VideoSurfaceOutput dari processor. Jika dihancurkan, kami akan menghapusnya dari VideoSurfaceOutput processor.

Selesai. Sekarang Anda telah berhasil mem-build dan menjalankan aplikasi di perangkat serta melihat deteksi tepi Sobel yang berjalan di feed kamera live. Selamat!

edge_detection_android_gpu_gif

Jika Anda mengalami masalah, baca kode lengkap tutorial di sini.