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.
Penyiapan
- Instal MediaPipe Framework di sistem Anda, lihat Panduan penginstalan framework untuk mengetahui detailnya.
- Instal Android Development SDK dan Android NDK. Pelajari cara melakukannya juga di [Panduan penginstalan framework].
- Aktifkan opsi developer di perangkat Android Anda.
- 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:
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 adalahcom.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.
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:
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!
Jika Anda mengalami masalah, baca kode lengkap tutorial di sini.