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.
Konfiguracja
- Zainstaluj w systemie MediaPipe Framework – patrz Instalacja Framework. .
- Zainstaluj pakiet Android Development SDK i Android NDK. Zobacz, jak to zrobić – [Przewodnik instalacji ram].
- Na urządzeniu z Androidem włącz opcje programisty.
- 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:
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 jestcom.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!
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:
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!
Jeśli napotkasz jakieś problemy, zapoznaj się z pełnym kodem samouczka tutaj.