Giới thiệu
Hướng dẫn Hello World! sử dụng Khung MediaPipe để phát triển một ứng dụng Android chạy biểu đồ MediaPipe trên Android.
Sản phẩm bạn sẽ tạo ra
Một ứng dụng máy ảnh đơn giản giúp phát hiện cạnh Sobel theo thời gian thực, áp dụng cho luồng video trực tiếp trên thiết bị Android.
Thiết lập
- Cài đặt MediaPipe Framework trên hệ thống của bạn, xem phần Hướng dẫn cài đặt Frame để biết thông tin chi tiết.
- Cài đặt SDK phát triển Android và Android NDK. Hãy xem thêm cách thực hiện trong phần [Hướng dẫn cài đặt khung].
- Bật tuỳ chọn cho nhà phát triển trên thiết bị Android của bạn.
- Thiết lập Bazel trên hệ thống của bạn để xây dựng và triển khai ứng dụng Android.
Biểu đồ cho tính năng phát hiện cạnh
Chúng ta sẽ sử dụng biểu đồ sau đây, 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"
}
Hình ảnh của biểu đồ được hiển thị dưới đây:
Biểu đồ này có một luồng đầu vào duy nhất tên là input_video
cho tất cả khung hình đến mà máy ảnh của thiết bị cung cấp.
Nút đầu tiên trong biểu đồ, LuminanceCalculator
, lấy một gói duy nhất (khung hình ảnh) và áp dụng thay đổi về độ chói bằng chương trình đổ bóng OpenGL. Khung hình ảnh thu được sẽ được gửi đến luồng đầu ra luma_video
.
Nút thứ hai, SobelEdgesCalculator
áp dụng tính năng phát hiện cạnh cho các gói đến trong luồng luma_video
và xuất kết quả là luồng đầu ra output_video
.
Ứng dụng Android của chúng ta sẽ hiển thị các khung hình ảnh đầu ra của luồng output_video
.
Thiết lập ứng dụng tối thiểu ban đầu
Trước tiên, chúng ta bắt đầu bằng một ứng dụng Android đơn giản hiển thị "Hello World!" trên màn hình. Bạn có thể bỏ qua bước này nếu đã quen với việc tạo ứng dụng Android bằng bazel
.
Tạo thư mục mới để tạo ứng dụng Android. Ví dụ: bạn có thể tìm thấy mã đầy đủ của hướng dẫn này tại mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic
. Chúng ta sẽ gọi đường dẫn này là $APPLICATION_PATH
trong suốt lớp học lập trình này.
Lưu ý rằng trong đường dẫn đến ứng dụng:
- Ứng dụng này có tên là
helloworld
. $PACKAGE_PATH
của ứng dụng làcom.google.mediapipe.apps.basic
. Mã này được sử dụng trong các đoạn mã ở hướng dẫn này, vì vậy, hãy nhớ sử dụng$PACKAGE_PATH
của riêng bạn khi sao chép/sử dụng các đoạn mã.
Thêm tệp activity_main.xml
vào $APPLICATION_PATH/res/layout
. Thao tác này sẽ hiển thị một TextView
trên toàn màn hình của ứng dụng cùng với chuỗi 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>
Thêm một MainActivity.java
đơn giản vào $APPLICATION_PATH
để tải nội dung
của bố cục activity_main.xml
như minh hoạ dưới đây:
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);
}
}
Thêm tệp kê khai AndroidManifest.xml
vào $APPLICATION_PATH
. Tệp này sẽ chạy MainActivity
khi khởi động ứng dụng:
<?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>
Trong ứng dụng này, chúng ta đang dùng giao diện Theme.AppCompat
trong ứng dụng, vì vậy, chúng ta cần có các tham chiếu giao diện phù hợp. Thêm colors.xml
vào $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>
Thêm styles.xml
vào $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>
Để tạo ứng dụng, hãy thêm tệp BUILD
vào $APPLICATION_PATH
, đồng thời ${appName}
và ${mainActivity}
trong tệp kê khai sẽ được thay thế bằng các chuỗi được chỉ định trong BUILD
như thể hiện dưới đây.
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",
],
)
Quy tắc android_library
sẽ thêm các phần phụ thuộc cho MainActivity
, tệp tài nguyên và AndroidManifest.xml
.
Quy tắc android_binary
sử dụng thư viện Android basic_lib
được tạo để tạo một tệp APK nhị phân nhằm cài đặt trên thiết bị Android của bạn.
Để tạo ứng dụng, hãy dùng lệnh sau:
bazel build -c opt --config=android_arm64 $APPLICATION_PATH:helloworld
Cài đặt tệp APK đã tạo bằng adb install
. Ví dụ:
adb install bazel-bin/$APPLICATION_PATH/helloworld.apk
Mở ứng dụng trên thiết bị. Ứng dụng sẽ hiển thị một màn hình có văn bản Hello World!
.
Đang sử dụng máy ảnh qua CameraX
Quyền truy cập máy ảnh
Để dùng máy ảnh trong ứng dụng, chúng ta cần yêu cầu người dùng cấp quyền truy cập vào máy ảnh. Để yêu cầu cấp quyền truy cập vào máy ảnh, hãy thêm đoạn mã sau vào AndroidManifest.xml
:
<!-- For using the camera -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
Thay đổi phiên bản SDK tối thiểu thành 21
và phiên bản SDK mục tiêu thành 27
trong cùng một tệp:
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="27" />
Điều này đảm bảo rằng người dùng được nhắc yêu cầu quyền truy cập vào máy ảnh và cho phép chúng tôi sử dụng thư viện CameraX để truy cập vào máy ảnh.
Để yêu cầu quyền truy cập vào máy ảnh, chúng ta có thể sử dụng một tiện ích do các thành phần của MediaPipe Framework cung cấp, cụ thể là PermissionHelper
. Để sử dụng đối tượng này, hãy thêm phần phụ thuộc "//mediapipe/java/com/google/mediapipe/components:android_components"
vào quy tắc mediapipe_lib
trong BUILD
.
Để sử dụng PermissionHelper
trong MainActivity
, hãy thêm dòng sau vào hàm onCreate
:
PermissionHelper.checkAndRequestCameraPermissions(this);
Thao tác này sẽ nhắc người dùng thông qua một hộp thoại trên màn hình để yêu cầu cấp quyền sử dụng máy ảnh trong ứng dụng này.
Thêm mã sau đây để xử lý phản hồi của người dùng:
@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() {}
Tạm thời, chúng ta sẽ để trống phương thức startCamera()
. Khi người dùng phản hồi lời nhắc, MainActivity
sẽ tiếp tục và onResume()
sẽ được gọi.
Mã này sẽ xác nhận rằng quyền sử dụng máy ảnh đã được cấp, sau đó sẽ khởi động máy ảnh.
Tạo lại và cài đặt ứng dụng. Bây giờ, bạn sẽ thấy một lời nhắc yêu cầu ứng dụng này được cấp quyền truy cập vào máy ảnh.
Quyền truy cập máy ảnh
Khi có các quyền truy cập vào máy ảnh, chúng ta có thể bắt đầu và tìm nạp khung hình từ máy ảnh.
Để xem các khung hình bằng máy ảnh, chúng ta sẽ sử dụng SurfaceView
. Mỗi khung hình của máy ảnh sẽ được lưu trữ trong một đối tượng SurfaceTexture
. Để sử dụng các bố cục này, trước tiên, chúng ta cần thay đổi bố cục của ứng dụng.
Xoá toàn bộ khối mã TextView
khỏi $APPLICATION_PATH/res/layout/activity_main.xml
rồi thêm mã sau:
<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>
Khối mã này có một FrameLayout
mới tên là preview_display_layout
và một TextView
được lồng bên trong nó tên là no_camera_access_preview
. Khi quyền truy cập vào máy ảnh không được cấp, ứng dụng của chúng tôi sẽ hiển thị TextView
kèm theo một thông báo chuỗi, được lưu trữ trong biến no_camera_access
.
Thêm dòng sau vào tệp $APPLICATION_PATH/res/values/strings.xml
:
<string name="no_camera_access" translatable="false">Please grant camera permissions.</string>
Khi người dùng không cấp quyền truy cập vào camera, màn hình sẽ có dạng như sau:
Bây giờ, chúng ta sẽ thêm các đối tượng SurfaceTexture
và SurfaceView
vào MainActivity
:
private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;
Trong hàm onCreate(Bundle)
, hãy thêm hai dòng sau trước khi yêu cầu quyền truy cập vào máy ảnh:
previewDisplayView = new SurfaceView(this);
setupPreviewDisplayView();
Giờ thì hãy thêm mã để xác định setupPreviewDisplayView()
:
private void setupPreviewDisplayView() {
previewDisplayView.setVisibility(View.GONE);
ViewGroup viewGroup = findViewById(R.id.preview_display_layout);
viewGroup.addView(previewDisplayView);
}
Chúng ta xác định một đối tượng SurfaceView
mới và thêm đối tượng đó vào đối tượng preview_display_layout
FrameLayout
để có thể sử dụng đối tượng này nhằm hiển thị khung máy ảnh bằng cách sử dụng đối tượng SurfaceTexture
có tên là previewFrameTexture
.
Nếu muốn dùng previewFrameTexture
để lấy khung máy ảnh, chúng ta sẽ sử dụng CameraX.
Khung cung cấp một tiện ích có tên CameraXPreviewHelper
để sử dụng CameraX.
Lớp này sẽ cập nhật một trình nghe khi máy ảnh được khởi động qua onCameraStarted(@Nullable SurfaceTexture)
.
Để sử dụng tiện ích này, hãy sửa đổi tệp BUILD
để thêm phần phụ thuộc vào "//mediapipe/java/com/google/mediapipe/components:android_camerax_helper"
.
Giờ hãy nhập CameraXPreviewHelper
rồi thêm dòng sau vào MainActivity
:
private CameraXPreviewHelper cameraHelper;
Bây giờ, chúng ta có thể thêm phương thức triển khai vào 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);
});
}
Thao tác này sẽ tạo một đối tượng CameraXPreviewHelper
mới và thêm một trình nghe ẩn danh trên đối tượng đó. Khi cameraHelper
báo hiệu rằng máy ảnh đã khởi động và có một surfaceTexture
để lấy khung hình, chúng ta sẽ lưu surfaceTexture
đó dưới dạng previewFrameTexture
và hiển thị previewDisplayView
để có thể bắt đầu xem các khung hình từ previewFrameTexture
.
Tuy nhiên, trước khi khởi động máy ảnh, chúng ta cần quyết định máy ảnh mà mình muốn sử dụng. CameraXPreviewHelper
kế thừa từ CameraHelper
, cung cấp 2 tuỳ chọn là FRONT
và BACK
. Chúng ta có thể truyền quyết định từ tệp BUILD
dưới dạng siêu dữ liệu để không yêu cầu thay đổi mã để tạo một phiên bản khác của ứng dụng bằng máy ảnh khác.
Giả sử chúng ta muốn sử dụng camera BACK
để thực hiện tính năng phát hiện cạnh trên cảnh trực tiếp mà chúng ta xem từ camera, hãy thêm siêu dữ liệu vào AndroidManifest.xml
:
...
<meta-data android:name="cameraFacingFront" android:value="${cameraFacingFront}"/>
</application>
</manifest>
và chỉ định lựa chọn trong BUILD
trong quy tắc nhị phân Android helloworld
với một mục mới trong manifest_values
:
manifest_values = {
"applicationId": "com.google.mediapipe.apps.basic",
"appName": "Hello World",
"mainActivity": ".MainActivity",
"cameraFacingFront": "False",
},
Giờ đây, trong MainActivity
để truy xuất siêu dữ liệu được chỉ định trong manifest_values
, hãy thêm đối tượng ApplicationInfo
:
private ApplicationInfo applicationInfo;
Trong hàm onCreate()
, hãy thêm:
try {
applicationInfo =
getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
Log.e(TAG, "Cannot find application info: " + e);
}
Bây giờ, hãy thêm dòng sau vào cuối hàm startCamera()
:
CameraHelper.CameraFacing cameraFacing =
applicationInfo.metaData.getBoolean("cameraFacingFront", false)
? CameraHelper.CameraFacing.FRONT
: CameraHelper.CameraFacing.BACK;
cameraHelper.startCamera(this, cameraFacing, /*unusedSurfaceTexture=*/ null);
Tại thời điểm này, ứng dụng sẽ tạo bản dựng thành công. Tuy nhiên, khi chạy ứng dụng trên thiết bị, bạn sẽ thấy màn hình màu đen (mặc dù quyền máy ảnh đã được cấp). Điều này là do mặc dù chúng ta lưu biến surfaceTexture
do CameraXPreviewHelper
cung cấp, nhưng previewSurfaceView
chưa sử dụng kết quả của nó và hiển thị biến đó trên màn hình.
Vì muốn sử dụng các khung hình trong biểu đồ MediaPipe, nên chúng ta sẽ không thêm mã để xem trực tiếp đầu ra máy ảnh trong hướng dẫn này. Thay vào đó, chúng ta chuyển sang phần cách gửi khung máy ảnh để xử lý đến biểu đồ MediaPipe và hiển thị kết quả của biểu đồ trên màn hình.
Thiết lập ExternalTextureConverter
SurfaceTexture
chụp khung hình ảnh từ một luồng dưới dạng kết cấu OpenGL ES. Để sử dụng biểu đồ MediaPipe, khung hình đã chụp từ máy ảnh phải được lưu trữ trong đối tượng hoạ tiết Open GL thông thường. Khung này cung cấp một lớp ExternalTextureConverter
để chuyển đổi hình ảnh được lưu trữ trong đối tượng SurfaceTexture
thành đối tượng hoạ tiết OpenGL thông thường.
Để sử dụng ExternalTextureConverter
, chúng ta cũng cần có EGLContext
do đối tượng EglManager
tạo và quản lý. Thêm phần phụ thuộc vào tệp BUILD
để sử dụng EglManager
, "//mediapipe/java/com/google/mediapipe/glutil"
.
Trong MainActivity
, hãy thêm các nội dung khai báo sau:
private EglManager eglManager;
private ExternalTextureConverter converter;
Trong hàm onCreate(Bundle)
, hãy thêm một câu lệnh để khởi chạy đối tượng eglManager
trước khi yêu cầu quyền truy cập vào máy ảnh:
eglManager = new EglManager(null);
Hãy nhớ rằng chúng ta đã xác định hàm onResume()
trong MainActivity
để xác nhận các quyền truy cập vào máy ảnh đã được cấp và gọi startCamera()
. Trước bước kiểm tra này, hãy thêm dòng sau vào onResume()
để khởi chạy đối tượng converter
:
converter = new ExternalTextureConverter(eglManager.getContext());
converter
này hiện sử dụng GLContext
do eglManager
quản lý.
Chúng ta cũng cần ghi đè hàm onPause()
trong MainActivity
để nếu ứng dụng chuyển sang trạng thái tạm dừng, chúng ta sẽ đóng converter
đúng cách:
@Override
protected void onPause() {
super.onPause();
converter.close();
}
Để chuyển đầu ra của previewFrameTexture
sang converter
, hãy thêm khối mã sau vào 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) {}
});
Trong khối mã này, chúng ta thêm một SurfaceHolder.Callback
tuỳ chỉnh vào previewDisplayView
và triển khai hàm surfaceChanged(SurfaceHolder holder, int
format, int width, int height)
để tính toán kích thước hiển thị phù hợp của khung máy ảnh trên màn hình thiết bị, đồng thời liên kết đối tượng previewFrameTexture
và gửi các khung của displaySize
đã tính toán đến converter
.
Chúng ta hiện đã sẵn sàng sử dụng khung máy ảnh trong biểu đồ MediaPipe.
Sử dụng biểu đồ MediaPipe trong Android
Thêm các phần phụ thuộc có liên quan
Để sử dụng biểu đồ MediaPipe, chúng ta cần thêm các phần phụ thuộc vào khung MediaPipe trên Android. Trước tiên, chúng ta sẽ thêm quy tắc xây dựng để tạo cc_binary
bằng mã JNI của khung MediaPipe, sau đó tạo một quy tắc cc_library
để dùng tệp nhị phân này trong ứng dụng. Thêm khối mã dưới đây vào tệp 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,
)
Thêm phần phụ thuộc ":mediapipe_jni_lib"
vào quy tắc bản dựng mediapipe_lib
trong tệp BUILD
.
Tiếp theo, chúng ta cần thêm các phần phụ thuộc dành riêng cho biểu đồ MediaPipe mà chúng ta muốn sử dụng trong ứng dụng.
Trước tiên, hãy thêm phần phụ thuộc vào tất cả mã tính toán trong quy tắc xây dựng libmediapipe_jni.so
:
"//mediapipe/graphs/edge_detection:mobile_calculators",
Biểu đồ MediaPipe là các tệp .pbtxt
, nhưng để sử dụng các tệp này trong ứng dụng, chúng ta cần sử dụng quy tắc bản dựng mediapipe_binary_graph
để tạo tệp .binarypb
.
Trong quy tắc tạo tệp nhị phân Android helloworld
, hãy thêm mục tiêu mediapipe_binary_graph
dành riêng cho biểu đồ dưới dạng tài sản:
assets = [
"//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
],
assets_dir = "",
Trong quy tắc tạo assets
, bạn cũng có thể thêm các thành phần khác như mô hình TensorFlowLite dùng trong biểu đồ.
Ngoài ra, hãy thêm manifest_values
bổ sung cho các thuộc tính cụ thể cho biểu đồ để truy xuất sau này trong 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",
},
Lưu ý rằng binaryGraphName
cho biết tên tệp của biểu đồ nhị phân, được xác định bằng trường output_name
trong mục tiêu mediapipe_binary_graph
.
inputVideoStreamName
và outputVideoStreamName
lần lượt là tên luồng video đầu vào và đầu ra được chỉ định trong biểu đồ.
MainActivity
cần tải khung MediaPipe. Ngoài ra, khung này sử dụng OpenCV, vì vậy, MainActvity
cũng sẽ tải OpenCV
. Hãy dùng đoạn mã sau trong MainActivity
(bên trong lớp, nhưng không bên trong bất kỳ hàm nào) để tải cả hai phần phụ thuộc:
static {
// Load all native libraries needed by the app.
System.loadLibrary("mediapipe_jni");
System.loadLibrary("opencv_java3");
}
Sử dụng biểu đồ trong MainActivity
Trước tiên, chúng ta cần tải thành phần chứa .binarypb
được biên dịch từ tệp .pbtxt
của biểu đồ. Để thực hiện việc này, chúng ta có thể dùng tiện ích MediaPipe, AndroidAssetUtil
.
Khởi chạy trình quản lý thành phần trong onCreate(Bundle)
trước khi khởi chạy
eglManager
:
// Initialize asset manager so that MediaPipe native libraries can access the app assets, e.g.,
// binary graphs.
AndroidAssetUtil.initializeNativeAssetManager(this);
Bây giờ, chúng ta cần thiết lập một đối tượng FrameProcessor
sẽ gửi các khung máy ảnh do converter
chuẩn bị đến biểu đồ MediaPipe và chạy biểu đồ, chuẩn bị đầu ra rồi cập nhật previewDisplayView
để hiển thị kết quả đầu ra. Thêm mã sau để khai báo FrameProcessor
:
private FrameProcessor processor;
và khởi tạo trong onCreate(Bundle)
sau khi khởi chạy eglManager
:
processor =
new FrameProcessor(
this,
eglManager.getNativeContext(),
applicationInfo.metaData.getString("binaryGraphName"),
applicationInfo.metaData.getString("inputVideoStreamName"),
applicationInfo.metaData.getString("outputVideoStreamName"));
processor
cần sử dụng khung hình đã chuyển đổi từ converter
để xử lý. Thêm dòng sau vào onResume()
sau khi khởi chạy converter
:
converter.setConsumer(processor);
processor
sẽ gửi đầu ra của mình đến previewDisplayView
. Để thực hiện việc này, hãy thêm các định nghĩa hàm sau vào SurfaceHolder.Callback
tuỳ chỉnh của chúng ta:
@Override
public void surfaceCreated(SurfaceHolder holder) {
processor.getVideoSurfaceOutput().setSurface(holder.getSurface());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
processor.getVideoSurfaceOutput().setSurface(null);
}
Khi SurfaceHolder
được tạo, chúng ta đã có Surface
cho VideoSurfaceOutput
của processor
. Khi nó bị huỷ, chúng tôi sẽ xoá nó khỏi VideoSurfaceOutput
của processor
.
Chỉ vậy thôi! Giờ đây, bạn có thể tạo và chạy thành công ứng dụng trên thiết bị, cũng như thấy tính năng phát hiện cạnh Sobel chạy trên nguồn cấp dữ liệu camera trực tiếp! Xin chúc mừng!
Nếu bạn gặp vấn đề, vui lòng xem mã đầy đủ của hướng dẫn tại đây.