Witaj świecie na Androidzie

Wprowadzenie

Witaj świecie! samouczek korzysta z platformy MediaPipe Framework do stworzenia aplikacji na Androida, która uruchamia graf MediaPipe na Androidzie.

Co utworzysz

Prosta aplikacja do wykrywania krawędzi Sobela w czasie rzeczywistym do wideo na żywo na urządzeniu z Androidem.

edge_detection_android_gpu_gif

Konfiguracja

  1. Zainstaluj w systemie MediaPipe Framework – patrz Instalacja Framework. .
  2. Zainstaluj pakiet Android Development SDK i Android NDK. Zobacz, jak to zrobić – [Przewodnik instalacji ram].
  3. Na urządzeniu z Androidem włącz opcje programisty.
  4. Aby skompilować i wdrożyć aplikację na Androida, skonfiguruj w systemie Bazel.

Wykres wykrywania krawędzi

Użyjemy tego wykresu: 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"
}

Wizualizacja wykresu znajduje się poniżej:

edge_detection_mobile_gpu

Ten wykres ma jeden strumień wejściowy o nazwie input_video dla wszystkich przychodzących klatek udostępniane przez aparat urządzenia.

Pierwszy węzeł na wykresie, LuminanceCalculator, przyjmuje pojedynczy pakiet (obraz ) i stosuje zmianę luminancji przy użyciu cieniowania OpenGL. W wyniku ramka obrazu jest wysyłana do strumienia wyjściowego luma_video.

Drugi węzeł (SobelEdgesCalculator) stosuje wykrywanie brzegowych do przychodzących pakiety w strumieniu luma_video i wyprowadzają w wyniku output_video .

Aplikacja dla systemu Android wyświetli wyjściowe ramki obrazu Strumień output_video.

Wstępna minimalna konfiguracja aplikacji

Zaczynamy od prostej aplikacji dla Androida wyświetlającej tekst „Hello World!”. na ekranie. Możesz pominąć ten krok, jeśli wiesz, jak tworzyć Androida aplikacji za pomocą interfejsu bazel.

Utwórz nowy katalog, w którym utworzysz aplikację na Androida. Dla: Pełny kod tego samouczka można znaleźć na stronie mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic Będziemy w całym ćwiczeniu z programowania używaj tej ścieżki: $APPLICATION_PATH.

Pamiętaj, że w ścieżce aplikacji:

  • Aplikacja nazywa się helloworld.
  • Elementem $PACKAGE_PATH aplikacji jest com.google.mediapipe.apps.basic. Jest on używany we fragmentach kodu w tym samouczku, więc pamiętaj, aby użyć własny $PACKAGE_PATH podczas kopiowania i używania fragmentów kodu.

Dodaj plik activity_main.xml do folderu $APPLICATION_PATH/res/layout. Wyświetlane TextView na pełnym ekranie aplikacji z ciągiem 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>

Dodaj do pliku $APPLICATION_PATH prosty MainActivity.java, który wczytuje treść układu activity_main.xml, jak widać poniżej:

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

Dodaj plik manifestu AndroidManifest.xml do $APPLICATION_PATH, który uruchamia usługę MainActivity po uruchomieniu aplikacji:

<?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>

Używamy w niej motywu Theme.AppCompat, dlatego potrzebujemy odpowiednie odniesienia do motywów. Dodaj colors.xml do $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>

Dodaj styles.xml do $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>

Aby skompilować aplikację, dodaj plik BUILD do $APPLICATION_PATH. Ciągi ${appName} i ${mainActivity} w pliku manifestu zostaną zastąpione ciągami znaków określono w BUILD, jak pokazano poniżej.

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",
    ],
)

Reguła android_library dodaje zależności dla plików zasobów typu MainActivity i AndroidManifest.xml.

Reguła android_binary używa biblioteki Androida basic_lib wygenerowanej do kompilować binarny pakiet APK do zainstalowania na urządzeniu z Androidem.

Aby utworzyć aplikację, użyj tego polecenia:

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

Zainstaluj wygenerowany plik APK za pomocą programu adb install. Na przykład:

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

Otwórz aplikację na urządzeniu. Powinien wyświetlić się ekran z tekstem Hello World!

bazel_hello_world_android

Korzystam z kamery za pomocą urządzenia CameraX

Uprawnienia do aparatu

Aby móc korzystać z kamery w naszej aplikacji, musimy poprosić użytkownika o dostępu do aparatu. Aby poprosić o uprawnienia do korzystania z aparatu, dodaj te elementy do AndroidManifest.xml:

<!-- For using the camera -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />

Zmień minimalną wersję pakietu SDK na 21 i wersję docelową pakietu SDK na 27 ten sam plik:

<uses-sdk
    android:minSdkVersion="21"
    android:targetSdkVersion="27" />

Dzięki temu użytkownik zobaczy prośbę o przyznanie uprawnień do korzystania z aparatu korzystamy z biblioteki CameraX do korzystania z aparatu.

Aby poprosić o uprawnienia do korzystania z aparatu, możemy użyć narzędzia udostępnianego przez MediaPipe Framework komponentów, czyli PermissionHelper. Aby jej użyć, dodaj zależność "//mediapipe/java/com/google/mediapipe/components:android_components" w: mediapipe_lib reguła w: BUILD.

Aby użyć PermissionHelper w narzędziu MainActivity, dodaj ten wiersz do Funkcja onCreate:

PermissionHelper.checkAndRequestCameraPermissions(this);

Spowoduje to wyświetlenie użytkownikowi na ekranie okna z prośbą o przyznanie uprawnień używać aparatu w tej aplikacji.

Dodaj ten kod do obsługi odpowiedzi użytkownika:

@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() {}

Na razie pozostawimy metodę startCamera() pustą. Gdy użytkownik zareaguje spowoduje to wznowienie działania funkcji MainActivity i wywołanie funkcji onResume(). Kod potwierdzi, że przyznano uprawnienia do korzystania z kamery. i włączy kamerę.

Ponownie skompiluj i zainstaluj aplikację. Powinien wyświetlić się komunikat z prośbą o dostępu aplikacji do aparatu.

Dostęp do aparatu

Mając uprawnienia dostępu do aparatu, możemy uruchamiać i pobierać klatki z aparat fotograficzny.

Do wyświetlania klatek z kamery użyjemy SurfaceView. Każda klatka z kamery będą zapisywane w obiekcie SurfaceTexture. Aby z nich korzystać, Najpierw musimy zmienić układ aplikacji.

Usuń cały blok kodu TextView z $APPLICATION_PATH/res/layout/activity_main.xml i dodaj ten kod zamiast:

<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>

Ten blok kodu ma nowy blok kodu FrameLayout o nazwie preview_display_layout i typie Zagnieżdżone w nim TextView o nazwie no_camera_access_preview. Gdy kamera nie zostaną przyznane uprawnienia dostępu, nasza aplikacja wyświetli TextView z komunikatem w postaci ciągu zapisanego w zmiennej no_camera_access. Dodaj do pliku $APPLICATION_PATH/res/values/strings.xml ten wiersz:

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

Jeśli użytkownik nie przyzna uprawnień do korzystania z aparatu, ekran będzie teraz wyglądać tak: to:

missing_camera_permission_android

Teraz dodamy obiekty SurfaceTexture i SurfaceView do MainActivity:

private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;

W funkcji onCreate(Bundle) dodaj te 2 wiersze przed prośba o dostęp do aparatu:

previewDisplayView = new SurfaceView(this);
setupPreviewDisplayView();

Teraz dodaj kod definiujący setupPreviewDisplayView():

private void setupPreviewDisplayView() {
  previewDisplayView.setVisibility(View.GONE);
  ViewGroup viewGroup = findViewById(R.id.preview_display_layout);
  viewGroup.addView(previewDisplayView);
}

Definiujemy nowy obiekt SurfaceView i dodajemy go do polecenia preview_display_layout FrameLayout, abyśmy mogli go użyć do wyświetlania ustaw klatki z aparatu za pomocą obiektu SurfaceTexture o nazwie previewFrameTexture.

Aby użyć narzędzia previewFrameTexture do pobierania klatek kamery, użyjemy CameraX. Platforma udostępnia narzędzie o nazwie CameraXPreviewHelper umożliwiające korzystanie z CameraX. Te zajęcia aktualizują detektor po uruchomieniu kamery przez onCameraStarted(@Nullable SurfaceTexture)

Aby korzystać z tego narzędzia, zmodyfikuj plik BUILD, dodając do niego zależność "//mediapipe/java/com/google/mediapipe/components:android_camerax_helper"

Teraz zaimportuj CameraXPreviewHelper i dodaj ten wiersz do MainActivity:

private CameraXPreviewHelper cameraHelper;

Teraz możemy dodać implementację do 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);
    });
}

Spowoduje to utworzenie nowego obiektu CameraXPreviewHelper i dodanie anonimowego obiektu lub detektorem obiektu. Gdy cameraHelper zasygnalizuje, że kamera została uruchomiona i surfaceTexture do przechwytywania klatek, zapisujemy to surfaceTexture jako previewFrameTexture i ustaw previewDisplayView widoczny, dzięki czemu możemy zacząć oglądać ramki z pliku previewFrameTexture.

Zanim jednak uruchomisz kamerę, musimy zdecydować, którą z nich chcemy i ich używanie. Funkcja CameraXPreviewHelper dziedziczy dane z elementu CameraHelper, co udostępnia 2 opcje FRONT i BACK. Możemy przekazać decyzję z pliku BUILD takie jak metadane, dzięki czemu utworzenie kolejnej wersji tagu nie wymaga żadnych zmian używając innego aparatu.

Zakładamy, że chcemy użyć kamery BACK do wykrywania krawędzi na scenie na żywo którą widać z kamery, dodaj metadane do tagu AndroidManifest.xml:

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

i określ wybór w regule binarnej Androida helloworld w polu BUILD z nowym wpisem w manifest_values:

manifest_values = {
    "applicationId": "com.google.mediapipe.apps.basic",
    "appName": "Hello World",
    "mainActivity": ".MainActivity",
    "cameraFacingFront": "False",
},

Teraz w aplikacji MainActivity, aby pobrać metadane określone w zasadzie manifest_values, dodaj obiekt ApplicationInfo:

private ApplicationInfo applicationInfo;

W funkcji onCreate() dodaj:

try {
  applicationInfo =
      getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
  Log.e(TAG, "Cannot find application info: " + e);
}

Teraz dodaj ten wiersz na końcu funkcji startCamera():

CameraHelper.CameraFacing cameraFacing =
    applicationInfo.metaData.getBoolean("cameraFacingFront", false)
        ? CameraHelper.CameraFacing.FRONT
        : CameraHelper.CameraFacing.BACK;
cameraHelper.startCamera(this, cameraFacing, /*unusedSurfaceTexture=*/ null);

W tym momencie aplikacja powinna się prawidłowo skompilować. Po uruchomieniu aplikacji na urządzeniu, zobaczysz czarny ekran (mimo że aparat przyznanych uprawnień). To dlatego, że chociaż zapisujemy zmiennej surfaceTexture dostarczanej przez CameraXPreviewHelper, previewSurfaceView nie używa jeszcze danych wyjściowych i nie wyświetla ich na ekranie.

Ponieważ w wykresie MediaPipe chcemy użyć ramek, nie będziemy dodawać kodu do możesz wyświetlać dane wyjściowe z kamery bezpośrednio w tym samouczku. Przechodzimy do tego, jak możemy wysyłać ramki kamery do przetworzenia na wykres MediaPipe danych z wykresu na ekranie.

Konfiguracja urządzenia ExternalTextureConverter

SurfaceTexture przechwytuje klatki obrazu ze strumienia jako środowisko OpenGL ES teksturę. Aby korzystać z wykresu MediaPipe, klatki przechwycone z aparatu powinny być zapisane w zwykłym obiekcie tekstury Open GL. Platforma zapewnia klasę, ExternalTextureConverter, aby przekonwertować obraz zapisany w SurfaceTexture na zwykły obiekt tekstury OpenGL.

Aby korzystać z ExternalTextureConverter, potrzebujemy też EGLContext, czyli które są tworzone i zarządzane przez obiekt EglManager. Dodaj zależność do funkcji BUILD aby używać: EglManager, "//mediapipe/java/com/google/mediapipe/glutil".

W pliku MainActivity dodaj te deklaracje:

private EglManager eglManager;
private ExternalTextureConverter converter;

W funkcji onCreate(Bundle) dodaj instrukcję, aby zainicjować eglManager obiekt przed poproszeniem o uprawnienia do korzystania z aparatu:

eglManager = new EglManager(null);

Przypomnijmy, że zdefiniowaliśmy funkcję onResume() w MainActivity, aby potwierdzić przyznano uprawnienia do kamery i wywołaj startCamera(). Przed sprawdź, dodaj ten wiersz w onResume(), aby zainicjować converter obiekt:

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

To urządzenie converter korzysta teraz z narzędzia GLContext, którym zarządza eglManager.

Musimy również zastąpić funkcję onPause() w obiekcie MainActivity, tak aby jeśli aplikacja zostanie wstrzymana, converter zostanie prawidłowo zamknięty:

@Override
protected void onPause() {
  super.onPause();
  converter.close();
}

Aby za pomocą potoku (converter) przenieść dane wyjściowe funkcji previewFrameTexture do funkcji converter, dodaj ten blok kodu do 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) {}
     });

W tym bloku kodu dodajemy niestandardowy element SurfaceHolder.Callback do previewDisplayView i zaimplementuj funkcję surfaceChanged(SurfaceHolder holder, int format, int width, int height), aby obliczyć odpowiedni rozmiar wyświetlanego obrazu ramki aparatu na ekranie urządzenia i żeby połączyć previewFrameTexture i wysyła ramki obliczonych displaySize do converter.

Teraz na wykresie MediaPipe możemy używać ramek aparatu.

Korzystanie z grafu MediaPipe na Androidzie

Dodaj odpowiednie zależności

Aby użyć grafu MediaPipe, trzeba dodać zależności do platformy MediaPipe na Androidzie. Najpierw dodamy regułę kompilacji, aby utworzyć cc_binary za pomocą kodu JNI platformy MediaPipe, a następnie utwórz regułę cc_library, aby użyć tego pliku binarnego w naszej aplikacji. Dodaj do pliku BUILD ten blok kodu:

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,
)

Dodaj zależność ":mediapipe_jni_lib" do reguły kompilacji mediapipe_lib w w pliku BUILD.

Następnie musimy dodać zależności specyficzne dla grafu MediaPipe, którego chcemy użyć w aplikacji.

Najpierw dodaj zależności do całego kodu kalkulatora w komponencie libmediapipe_jni.so reguła kompilacji:

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

Wykresy MediaPipe to pliki w formacie .pbtxt, ale aby można ich było użyć w aplikacji, aby użyć reguły kompilacji mediapipe_binary_graph do wygenerowania pliku .binarypb.

W regule kompilacji binarnej Androida helloworld dodaj mediapipe_binary_graph. dla danego wykresu jako zasobu:

assets = [
  "//mediapipe/graphs/edge_detection:mobile_gpu_binary_graph",
],
assets_dir = "",

W regule kompilacji assets możesz też dodać inne zasoby, takie jak TensorFlowLite modele używane na wykresie.

Dodatkowo dodaj dodatkowe manifest_values dla właściwości charakterystycznych dla do pobrania później w 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 wskazuje nazwę pliku grafu binarnego, określane przez pole output_name w celu mediapipe_binary_graph. inputVideoStreamName i outputVideoStreamName to dane wejściowe i wyjściowe nazwy strumienia wideo określonej na wykresie.

Teraz MainActivity musi wczytać platformę MediaPipe. Dodatkowo platforma korzysta z OpenCV, dlatego MainActvity również powinien wczytać się OpenCV. Użyj następujący kod w elemencie MainActivity (w klasie, ale nie w żadnej funkcji) aby wczytać obie zależności:

static {
  // Load all native libraries needed by the app.
  System.loadLibrary("mediapipe_jni");
  System.loadLibrary("opencv_java3");
}

Użyj wykresu w: MainActivity

Najpierw musimy wczytać zasób, który zawiera komponenty .binarypb kompilowane z pliku .pbtxt wykresu. Użyjemy do tego narzędzia MediaPipe, AndroidAssetUtil

Zanim zainicjujesz menedżera zasobów, zainicjuj go w: onCreate(Bundle) eglManager:

// Initialize asset manager so that MediaPipe native libraries can access the app assets, e.g.,
// binary graphs.
AndroidAssetUtil.initializeNativeAssetManager(this);

Teraz musimy skonfigurować obiekt FrameProcessor, który wysyła ramki kamery przygotowany przez converter na wykresie MediaPipe i uruchamia wykres, przygotowuje dane wyjściowe, a następnie aktualizuje previewDisplayView, aby je wyświetlić. Dodaj ten kod do zadeklarowania FrameProcessor:

private FrameProcessor processor;

i zainicjuj go w programie onCreate(Bundle) po zainicjowaniu programu eglManager:

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

Element processor musi przetworzyć przekonwertowane klatki z komponentu converter na o przetwarzaniu danych. Dodaj poniższy wiersz do onResume() po zainicjowaniu converter:

converter.setConsumer(processor);

processor powinien wysłać dane wyjściowe do previewDisplayView. Aby to zrobić, dodaj te definicje funkcji w niestandardowym SurfaceHolder.Callback:

@Override
public void surfaceCreated(SurfaceHolder holder) {
  processor.getVideoSurfaceOutput().setSurface(holder.getSurface());
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
  processor.getVideoSurfaceOutput().setSurface(null);
}

Po utworzeniu elementu SurfaceHolder przydzielono wartość Surface do VideoSurfaceOutput z processor. Po zniszczeniu usuwamy go VideoSurfaceOutput: processor.

To wszystko. Powinno być już możliwe skompilowanie i uruchomienie i zobacz, jak działa wykrywanie krawędzi Sobel w kamerze na żywo. kanał! Gratulacje!

edge_detection_android_gpu_gif

Jeśli napotkasz jakieś problemy, zapoznaj się z pełnym kodem samouczka tutaj.