Hello World!(Android)

소개

Hello World! 튜토리얼은 MediaPipe 프레임워크를 사용하여 Android에서 MediaPipe 그래프를 실행합니다.

빌드 대상

라이브 동영상에 적용되는 실시간 Sobel 에지 감지를 위한 간단한 카메라 앱 Android 기기에서 스트리밍할 수 있습니다.

edge_detection_android_gpu_gif

설정

  1. 시스템에 MediaPipe 프레임워크를 설치합니다. 프레임워크 설치 참조 가이드를 참조하세요.
  2. Android 개발 SDK 및 Android NDK 설치 자세한 방법은 [프레임워크 설치 가이드]
  3. Android 기기에서 개발자 옵션을 사용 설정합니다.
  4. 시스템에 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"
}

다음은 그래프를 시각화한 것입니다.

edge_detection_mobile_gpu

이 그래프에는 모든 수신 프레임에 input_video라는 단일 입력 스트림이 있습니다. 기기의 카메라에서 제공합니다.

그래프의 첫 번째 노드인 LuminanceCalculator는 단일 패킷을 가져옵니다. OpenGL 셰이더를 사용하여 휘도 변경을 적용합니다. 결과 luma_video 출력 스트림으로 전송됩니다.

두 번째 노드 SobelEdgesCalculator는 수신에 에지 감지를 적용합니다. luma_video 스트림의 패킷 및 출력 결과 output_video 출력 있습니다.

Android 애플리케이션은 output_video 스트림.

초기 최소 애플리케이션 설정

먼저 'Hello World!'를 표시하는 간단한 Android 애플리케이션부터 시작해 보겠습니다. 화면에 나타납니다. Android 빌드에 익숙하다면 이 단계를 건너뛰어도 됩니다. bazel를 사용하는 애플리케이션.

Android 애플리케이션을 만들 새 디렉터리를 만듭니다. 대상 이 튜토리얼의 전체 코드는 다음 위치에서 확인할 수 있습니다. mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic YouTube에서는 Codelab 전체에서 이 경로를 $APPLICATION_PATH로 참조합니다.

애플리케이션 경로에서 다음 사항에 유의하세요.

  • 애플리케이션의 이름은 helloworld입니다.
  • 애플리케이션의 $PACKAGE_PATHcom.google.mediapipe.apps.basic입니다. 이 튜토리얼의 코드 스니펫에 사용되므로 코드 스니펫을 복사/사용할 때 자신의 $PACKAGE_PATH를 사용해야 합니다.

activity_main.xml 파일을 $APPLICATION_PATH/res/layout에 추가합니다. 이렇게 하면 애플리케이션 전체 화면에서 Hello World! 문자열이 있는 TextView

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

매니페스트 파일 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}가 문자열로 대체됩니다. 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 규칙은 MainActivity, 리소스 파일의 종속 항목을 추가합니다. 및 AndroidManifest.xml

android_binary 규칙은 다음을 위해 생성된 basic_lib Android 라이브러리를 사용합니다. Android 기기에 설치할 바이너리 APK를 빌드합니다.

앱을 빌드하려면 다음 명령어를 사용합니다.

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

생성된 APK 파일을 adb install를 사용하여 설치합니다. 예를 들면 다음과 같습니다.

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

기기에서 애플리케이션을 엽니다. 화면에 텍스트가 Hello World!

bazel_hello_world_android

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 프레임워크에서 제공하는 유틸리티를 사용하면 됩니다. 구성요소로, 즉 PermissionHelper입니다. 사용하려면 종속 항목을 추가합니다. "//mediapipe/java/com/google/mediapipe/components:android_components" BUILD에 규칙이 mediapipe_lib개 있습니다.

MainActivity에서 PermissionHelper를 사용하려면 다음 줄을 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>

이 코드 블록에는 preview_display_layout라는 새 FrameLayoutno_camera_access_preview로 이름이 지정된 TextView가 내부에 중첩되어 있습니다. 카메라에서 권한이 부여되지 않으면 애플리케이션은 문자열 메시지가 포함된 TextView. no_camera_access 변수에 저장됨 $APPLICATION_PATH/res/values/strings.xml 파일에 다음 줄을 추가합니다.

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

사용자가 카메라 권한을 부여하지 않으면 이제 화면이 다음과 같이 표시됩니다. 다음과 같습니다.

missing_camera_permission_android

이제 SurfaceTextureSurfaceView 객체를 MainActivity:

private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;

onCreate(Bundle) 함수에서 앞에 다음 두 줄을 추가합니다. 카메라 권한 요청:

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 객체를 사용하여 previewFrameTexture이라는 SurfaceTexture 객체를 사용하여 카메라 프레임을 조정합니다.

카메라 프레임을 가져오는 데 previewFrameTexture를 사용하려면 CameraX를 사용합니다. 프레임워크는 CameraX를 사용할 수 있도록 CameraXPreviewHelper라는 유틸리티를 제공합니다. 이 클래스는 다음을 통해 카메라가 시작될 때 리스너를 업데이트합니다. onCameraStarted(@Nullable SurfaceTexture)

이 유틸리티를 사용하려면 BUILD 파일을 수정하여 유틸리티에 종속 항목을 추가합니다. "//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 객체가 생성되고 익명처리됩니다. 리스너입니다. cameraHelper에서 카메라가 시작되었음을 알리는 경우 프레임을 가져올 surfaceTexture가 있으면 저장합니다. surfaceTexturepreviewFrameTexture로 설정하고 previewDisplayViewpreviewFrameTexture에서 프레임을 볼 수 있습니다.

그러나 카메라를 시작하기 전에 어느 카메라를 사용할지 결정해야 합니다. 사용합니다 CameraXPreviewHelper는 다음 두 가지를 제공하는 CameraHelper에서 상속됩니다. 옵션, FRONT, BACK입니다. BUILD 파일에서 결정을 전달할 수 있습니다. 다른 버전의 API를 빌드하기 위해 코드를 변경할 필요가 없도록 앱이 다른 카메라를 사용 중입니다.

BACK 카메라를 사용하여 실시간 장면에서 가장자리 감지를 실행한다고 가정 다음과 같이 메타데이터를 AndroidManifest.xml에 추가합니다.

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

helloworld Android 바이너리 규칙의 BUILD에서 선택을 지정합니다. 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);

이제 애플리케이션이 성공적으로 빌드됩니다. 그러나 앱이 실행되는 경우 검은색 화면이 표시됩니다. 권한이 부여됨). 왜냐하면 우리가 이 객체를 CameraXPreviewHelper에서 제공하는 surfaceTexture 변수 previewSurfaceView는 아직 출력을 사용하지 않으며 화면에 표시하지 않습니다.

MediaPipe 그래프에서 프레임을 사용하려고 하므로 이 튜토리얼에서 직접 카메라 출력을 볼 수 있습니다. 대신 Kubernetes에서 처리할 카메라 프레임을 MediaPipe 그래프로 보내고 출력됩니다.

ExternalTextureConverter 설정

SurfaceTexture는 스트림에서 이미지 프레임을 OpenGL ES로 캡처합니다. 만들 수도 있습니다 MediaPipe 그래프를 사용하려면 카메라에서 캡처한 프레임이 일반 Open GL 텍스처 객체에 저장됩니다. Framework는 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);

MainActivity에서 onResume() 함수를 정의해 확인했음을 떠올려 보세요. 카메라 권한이 부여되었으며 startCamera()을(를) 호출합니다. 변경 전 onResume()에 다음 줄을 추가하여 converter를 초기화합니다. 객체:

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

이제 이 convertereglManager에서 관리하는 GLContext를 사용합니다.

또한 MainActivity에서 onPause() 함수를 재정의해야 합니다. 애플리케이션이 일시중지 상태가 되면 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.CallbackpreviewDisplayView하고 surfaceChanged(SurfaceHolder holder, int format, int width, int height) 함수를 구현하여 적절한 디스플레이 크기 계산 기기 화면의 카메라 프레임 중 하나에서 previewFrameTexture 객체를 사용하고 계산된 displaySize의 프레임을 converter에 전송합니다.

이제 MediaPipe 그래프에서 카메라 프레임을 사용할 준비가 되었습니다.

Android에서 MediaPipe 그래프 사용

관련 종속 항목 추가

MediaPipe 그래프를 사용하려면 MediaPipe 프레임워크에 종속 항목을 추가해야 합니다. . 먼저 JNI 코드를 사용하여 cc_binary를 빌드하는 빌드 규칙을 추가합니다. 그런 다음 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,
)

mediapipe_lib 빌드 규칙에 종속 항목 ":mediapipe_jni_lib"를 추가합니다. BUILD 파일

다음으로 사용하려는 MediaPipe 그래프 관련 종속 항목을 추가해야 합니다. 사용할 수 없습니다.

먼저 libmediapipe_jni.so의 모든 계산기 코드에 종속 항목을 추가합니다. 빌드 규칙:

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

MediaPipe 그래프는 .pbtxt 파일이지만 애플리케이션에서 사용하려면 mediapipe_binary_graph 빌드 규칙을 사용하여 .binarypb 파일을 생성합니다.

helloworld Android 바이너리 빌드 규칙에 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는 바이너리 그래프의 파일 이름을 나타냅니다. mediapipe_binary_graph 타겟의 output_name 필드에 의해 결정됩니다. inputVideoStreamNameoutputVideoStreamName는 입력과 출력임 각각 그래프에 지정된 동영상 스트림 이름입니다.

이제 MainActivity가 MediaPipe 프레임워크를 로드해야 합니다. 또한 프레임워크는 OpenCV를 사용하므로 MainActvityOpenCV를 로드해야 합니다. 사용 MainActivity의 다음 코드 (클래스 내부, 함수 내부 아님) 를 사용하여 두 종속 항목을 모두 로드합니다.

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;

eglManager를 초기화한 후 onCreate(Bundle)에서 초기화합니다.

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

processorconverter에서 변환된 프레임을 가장 적합합니다 다음을 초기화한 후 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 processorVideoSurfaceOutput입니다. 정보가 파기되면 processorVideoSurfaceOutput입니다.

이상입니다 이제 Google Cloud 콘솔에서 라이브 카메라에서 실행되는 Sobel 에지 감지를 확인합니다. 피드에! 축하합니다.

edge_detection_android_gpu_gif

문제가 발생한 경우 튜토리얼의 전체 코드를 확인하세요. 여기에서 확인할 수 있습니다.