เกริ่นนำ
บทแนะนำ Hello World! นี้ใช้ MediaPipe Framework ในการพัฒนาแอปพลิเคชัน Android ที่เรียกใช้กราฟ MediaPipe บน Android
สิ่งที่คุณจะสร้าง
แอปกล้องที่ใช้งานง่ายสำหรับการตรวจจับขอบ Sobel แบบเรียลไทม์ซึ่งใช้กับสตรีมวิดีโอสดในอุปกรณ์ Android
ตั้งค่า
- ติดตั้งเฟรมเวิร์ก MediaPipe ในระบบของคุณ โปรดดูรายละเอียดที่คู่มือการติดตั้งเฟรมเวิร์ก
- ติดตั้ง Android Development SDK และ Android NDK ดูวิธีการได้ใน [คู่มือการติดตั้งเฟรมเวิร์ก]
- เปิดใช้ตัวเลือกสำหรับนักพัฒนาซอฟต์แวร์ในอุปกรณ์ Android
- ตั้งค่า 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"
}
การแสดงภาพกราฟจะแสดงที่ด้านล่าง
กราฟนี้มีสตรีมอินพุตเดียวที่ชื่อ input_video
สำหรับเฟรมขาเข้าทั้งหมดซึ่งกล้องของอุปกรณ์มีให้
โหนดแรกในกราฟ LuminanceCalculator
รับแพ็กเก็ตเดี่ยว (เฟรมรูปภาพ) และเปลี่ยนความสว่างโดยใช้ตัวปรับแสงเงา OpenGL ระบบจะส่งเฟรมรูปภาพที่ได้ไปยังสตรีมเอาต์พุต luma_video
โหนดที่ 2 SobelEdgesCalculator
จะใช้การตรวจจับ Edge กับแพ็กเก็ตขาเข้าในสตรีม luma_video
และเอาต์พุตจะทำให้มีสตรีมเอาต์พุต output_video
รายการ
แอปพลิเคชัน Android ของเราจะแสดงเฟรมรูปภาพเอาต์พุตของสตรีม output_video
การตั้งค่าแอปพลิเคชันขั้นต่ำเริ่มต้น
เราเริ่มต้นด้วยแอปพลิเคชัน Android ง่ายๆ ที่แสดงคำว่า "สวัสดีชาวโลก"
บนหน้าจอ คุณข้ามขั้นตอนนี้ได้หากคุ้นเคยกับการสร้างแอปพลิเคชัน 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!
กำลังใช้กล้องผ่าน 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>
เมื่อผู้ใช้ไม่ได้ให้สิทธิ์เข้าถึงกล้อง หน้าจอจะมีลักษณะดังนี้
ต่อไปเราจะเพิ่มออบเจ็กต์ 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
คลาสนี้จะอัปเดต Listener เมื่อกล้องเริ่มทำงานผ่าน 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
ซึ่งมี 2 ตัวเลือก ได้แก่ 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 หากต้องการใช้กราฟ 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 เราต้องเพิ่มทรัพยากร Dependency ไปยังเฟรมเวิร์ก 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
ในกฎบิลด์ไบนารีของ 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 ทำงานในฟีดกล้องแบบสด ยินดีด้วย!
หากพบปัญหา โปรดดูโค้ดทั้งหมดของบทแนะนำที่นี่