Pengantar
Halo Dunia! menggunakan Kerangka Kerja MediaPipe untuk mengembangkan aplikasi Android yang menjalankan grafik MediaPipe di Android.
Yang akan Anda build
Aplikasi kamera sederhana untuk deteksi tepi Sobel secara real-time yang diterapkan ke video live melakukan streaming di perangkat Android.

Penyiapan
- Instal Framework MediaPipe di sistem Anda, lihat Penginstalan framework untuk mengetahui detailnya.
- Instal Android Development SDK dan Android NDK. Lihat cara melakukannya juga di [Panduan penginstalan framework].
- Aktifkan opsi developer di perangkat Android Anda.
- Siapkan Bazel di sistem Anda 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 yang masuk
yang akan disediakan oleh kamera perangkat Anda.
Node pertama dalam grafik, LuminanceCalculator, mengambil satu paket (gambar
) dan menerapkan perubahan luminans menggunakan shader OpenGL. Hasil
frame gambar dikirim ke aliran data output luma_video.
Node kedua, SobelEdgesCalculator, menerapkan deteksi edge ke peristiwa masuk
paket di aliran data luma_video dan menghasilkan output berupa output output_video
feed.
Aplikasi Android kita akan menampilkan bingkai gambar output dari
Feed output_video.
Penyiapan aplikasi minimal awal
Pertama-tama, kita mulai dengan aplikasi Android sederhana yang menampilkan "Hello World!" (Halo Dunia)
di layar. Anda dapat melewati langkah ini jika sudah terbiasa membangun Android
aplikasi menggunakan bazel.
Buat direktori baru tempat Anda akan membuat aplikasi Android. Sebagai
contoh, kode lengkap dari tutorial ini dapat ditemukan di
mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic. Kami akan
lihat jalur ini sebagai $APPLICATION_PATH di seluruh codelab.
Perhatikan bahwa dalam jalur ke aplikasi:
- Aplikasinya bernama
helloworld. $PACKAGE_PATHaplikasi adalahcom.google.mediapipe.apps.basic. Ini digunakan dalam cuplikan kode di tutorial ini, jadi harap ingat untuk menggunakan$PACKAGE_PATHAnda sendiri saat menyalin/menggunakan cuplikan kode.
Tambahkan file activity_main.xml ke $APPLICATION_PATH/res/layout. Ini 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>
Menambahkan MainActivity.java sederhana ke $APPLICATION_PATH yang memuat konten
dari 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);
}
}
Menambahkan 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>
Di aplikasi, kita menggunakan tema Theme.AppCompat di aplikasi sehingga kita perlu
referensi tema yang tepat. 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 membangun aplikasi, tambahkan file BUILD ke $APPLICATION_PATH, dan
${appName} dan ${mainActivity} dalam manifes akan diganti dengan string
ditentukan di BUILD seperti yang ditunjukkan di bawah ini.
android_library(
name = "basic_lib",
srcs = glob(["*.java"]),
manifest = "AndroidManifest.xml",
resource_files = glob(["res/**"]),
deps = [
"@maven//:androidx_appcompat_appcompat",
"@maven//:androidx_constraintlayout_constraintlayout",
],
)
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 dibuat untuk
membuat APK biner untuk 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 akan ditampilkan dengan teks
Hello World!.

Menggunakan kamera melalui CameraX
Izin Akses Kamera
Untuk menggunakan kamera di aplikasi, kita perlu meminta pengguna untuk menyediakan
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 menjadi 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 mengaktifkan kami untuk menggunakan library CameraX untuk akses kamera.
Untuk meminta izin kamera, kita dapat menggunakan utilitas yang disediakan oleh MediaPipe Framework
komponen, yaitu PermissionHelper. Untuk menggunakannya, tambahkan dependensi
"//mediapipe/java/com/google/mediapipe/components:android_components" dalam
mediapipe_lib aturan di BUILD.
Untuk menggunakan PermissionHelper di MainActivity, tambahkan baris berikut ke elemen
Fungsi onCreate:
PermissionHelper.checkAndRequestCameraPermissions(this);
Tindakan ini meminta izin kepada pengguna dengan dialog di layar untuk menggunakan kamera di 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() {}
Biarkan metode startCamera() kosong untuk saat ini. Ketika pengguna merespons
perintah tersebut, MainActivity akan dilanjutkan dan onResume() akan dipanggil.
Kode itu akan mengonfirmasi bahwa izin akses
untuk menggunakan kamera telah diberikan,
kemudian kita akan memulai kamera.
Build ulang dan instal aplikasi. Anda akan melihat dialog yang meminta akses ke kamera untuk aplikasi.
Akses Kamera
Dengan izin kamera yang tersedia, kita dapat memulai dan mengambil {i>frame<i} dari kamera.
Untuk melihat frame dari kamera, kita akan menggunakan SurfaceView. Setiap frame
dari kamera akan disimpan dalam objek SurfaceTexture. Untuk menggunakannya, kita
kita perlu mengubah
tata letak aplikasi kita 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 bersarang di dalamnya, bernama no_camera_access_preview. Saat kamera
izin akses tidak diberikan, aplikasi kita akan menampilkan
TextView dengan pesan string, 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;
Pada 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
preview_display_layout FrameLayout agar kita dapat menggunakannya untuk menampilkan
frame kamera menggunakan objek SurfaceTexture bernama previewFrameTexture.
Untuk menggunakan previewFrameTexture guna mendapatkan bingkai kamera, kita akan menggunakan CameraX.
Framework menyediakan utilitas bernama CameraXPreviewHelper untuk menggunakan CameraX.
Class ini mengupdate 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 kita 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 objek anonim
pemroses pada objek. Saat cameraHelper memberikan sinyal bahwa kamera telah dimulai
dan surfaceTexture untuk mengambil {i>frame<i} juga tersedia, kita menyimpannya
surfaceTexture sebagai previewFrameTexture, dan buat previewDisplayView
terlihat sehingga kita bisa mulai melihat frame dari previewFrameTexture.
Namun, sebelum memulai kamera, kita
perlu memutuskan kamera mana
gunakan. CameraXPreviewHelper mewarisi dari CameraHelper yang memberikan dua
opsi, FRONT dan BACK. Kita dapat meneruskan keputusan dari file BUILD
seperti metadata sehingga tidak diperlukan perubahan kode untuk membangun versi lain dari
aplikasi menggunakan kamera yang berbeda.
Dengan asumsi kita ingin menggunakan kamera BACK untuk melakukan deteksi tepi pada adegan live
yang dilihat dari kamera, tambahkan metadata ke 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 di manifest_values,
tambahkan objek ApplicationInfo:
private ApplicationInfo applicationInfo;
Dalam 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 akan berhasil dibuat. Namun, ketika Anda menjalankan
aplikasi di perangkat, Anda akan melihat layar hitam (meskipun kamera
izin telah diberikan). Hal ini karena meskipun kita menyimpan
Variabel surfaceTexture disediakan oleh CameraXPreviewHelper,
previewSurfaceView belum menggunakan outputnya dan menampilkannya di layar.
Karena kita ingin menggunakan {i>frame<i} dalam grafik MediaPipe, kita tidak akan menambahkan kode ke melihat output kamera langsung dalam tutorial ini. Sebagai gantinya, kita langsung membahas cara kita dapat mengirim {i>frame<i} kamera untuk diproses ke grafik MediaPipe dan menampilkan yang dihasilkan dari grafik di layar.
Penyiapan ExternalTextureConverter
SurfaceTexture menangkap frame gambar dari streaming sebagai OpenGL ES
tekstur. Untuk menggunakan grafik MediaPipe, bingkai yang diambil dari kamera harus
disimpan dalam objek tekstur Open GL biasa. Framework ini menyediakan kelas,
ExternalTextureConverter untuk mengonversi gambar yang disimpan di SurfaceTexture
ke objek tekstur OpenGL reguler.
Untuk menggunakan ExternalTextureConverter, kita juga memerlukan EGLContext, yang
yang dibuat dan dikelola oleh objek EglManager. Menambahkan dependensi ke BUILD
file ini 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 melakukan inisialisasi pada
Objek eglManager sebelum meminta izin kamera:
eglManager = new EglManager(null);
Ingat bahwa kita telah menentukan fungsi onResume() di MainActivity untuk mengonfirmasi
izin kamera telah diberikan dan memanggil startCamera(). Sebelumnya
centang, tambahkan baris berikut di onResume() untuk melakukan inisialisasi 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 berstatus dijeda, kita 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) {}
});
Di blok kode ini, kita menambahkan SurfaceHolder.Callback kustom untuk
previewDisplayView dan mengimplementasikan fungsi surfaceChanged(SurfaceHolder holder, int
format, int width, int height) untuk menghitung ukuran tampilan yang sesuai
frame kamera di layar perangkat dan untuk mengaitkan previewFrameTexture
dan mengirim frame displaySize yang telah dihitung ke converter.
Sekarang kita siap menggunakan frame 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 membangun cc_binary menggunakan kode JNI
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 di
file BUILD.
Selanjutnya, kita perlu menambahkan dependensi khusus untuk grafik MediaPipe yang ingin digunakan dalam aplikasi.
Pertama, tambahkan dependensi ke semua kode kalkulator di libmediapipe_jni.so
aturan build:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Grafik MediaPipe adalah file .pbtxt, tetapi untuk menggunakannya dalam aplikasi, kita perlu
untuk menggunakan aturan build mediapipe_binary_graph untuk menghasilkan file .binarypb.
Dalam aturan build biner Android helloworld, tambahkan mediapipe_binary_graph
menargetkan 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 TensorFlowLite
yang digunakan dalam grafik Anda.
Selain itu, tambahkan manifest_values tambahan untuk properti khusus untuk
grafik, yang nantinya dapat diambil di 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 dari grafik biner,
ditentukan oleh kolom output_name di target mediapipe_binary_graph.
inputVideoStreamName dan outputVideoStreamName adalah input dan output
nama streaming video yang ditentukan masing-masing 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 tidak 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");
}
Menggunakan grafik dalam MainActivity
Pertama, kita perlu memuat aset yang berisi .binarypb yang dikompilasi dari
file .pbtxt dari grafik. Untuk melakukan ini, kita dapat
menggunakan utilitas MediaPipe,
AndroidAssetUtil
Melakukan 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
disiapkan oleh converter ke grafik MediaPipe dan menjalankan grafik, menyiapkan
output, lalu mengupdate previewDisplayView untuk menampilkan output. Tambah
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 memiliki Surface ke
VideoSurfaceOutput dari processor. Setelah dihancurkan, kita menghapusnya dari
VideoSurfaceOutput dari processor.
Selesai. Sekarang Anda telah berhasil membangun dan menjalankan di perangkat dan melihat deteksi tepi Sobel yang berjalan di kamera live umpan! Selamat!

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