소개
이 Hello World! 튜토리얼에서는 MediaPipe 프레임워크를 사용하여 Android에서 MediaPipe 그래프를 실행하는 Android 애플리케이션을 개발합니다.
빌드 대상
Android 기기의 실시간 동영상 스트림에 적용된 실시간 Sobel 에지 감지를 위한 간단한 카메라 앱.
설정
- 시스템에 MediaPipe 프레임워크를 설치합니다. 자세한 내용은 프레임워크 설치 가이드를 참고하세요.
- Android 개발 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
출력 스트림으로 전송됩니다.
두 번째 노드 SobelEdgesCalculator
는 luma_video
스트림의 수신 패킷에 에지 감지를 적용하고 output_video
출력 스트림을 출력합니다.
Android 애플리케이션은 output_video
스트림의 출력 이미지 프레임을 표시합니다.
초기 최소 애플리케이션 설정
먼저 화면에 'Hello World!'를 표시하는 간단한 Android 애플리케이션부터 시작해 보겠습니다. bazel
를 사용하여 Android 애플리케이션을 빌드하는 데 익숙한 경우 이 단계를 건너뛰어도 됩니다.
Android 애플리케이션을 만들 새 디렉터리를 만듭니다. 예를 들어 이 튜토리얼의 전체 코드는 mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic
에서 찾을 수 있습니다. Codelab 전체에서 이 경로를 $APPLICATION_PATH
라고 합니다.
애플리케이션 경로에서 다음을 확인합니다.
- 애플리케이션 이름은
helloworld
입니다. - 애플리케이션의
$PACKAGE_PATH
이com.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>
아래와 같이 activity_main.xml
레이아웃의 콘텐츠를 로드하는 간단한 MainActivity.java
를 $APPLICATION_PATH
에 추가합니다.
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);
}
}
애플리케이션 시작 시 MainActivity
를 실행하는 매니페스트 파일 AndroidManifest.xml
를 $APPLICATION_PATH
에 추가합니다.
<?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
테마를 사용하므로 적절한 테마 참조가 필요합니다. $APPLICATION_PATH/res/values/
에 colors.xml
를 추가합니다.
<?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
규칙은 Android 기기에 설치할 바이너리 APK를 빌드하기 위해 생성된 basic_lib
Android 라이브러리를 사용합니다.
앱을 빌드하려면 다음 명령어를 사용합니다.
bazel build -c opt --config=android_arm64 $APPLICATION_PATH:helloworld
adb install
를 사용하여 생성된 APK 파일을 설치합니다. 예를 들면 다음과 같습니다.
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
)를 사용하면 됩니다. 사용하려면 BUILD
의 mediapipe_lib
규칙에 종속 항목 "//mediapipe/java/com/google/mediapipe/components:android_components"
를 추가합니다.
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
객체에 저장됩니다. 이를 사용하려면 먼저 애플리케이션의 레이아웃을 변경해야 합니다.
$APPLICATION_PATH/res/layout/activity_main.xml
에서 전체 TextView
코드 블록을 삭제하고 대신 다음 코드를 추가합니다.
<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
라는 새로운 FrameLayout
와 그 안에 중첩된 TextView
인 no_camera_access_preview
가 있습니다. 카메라 액세스 권한이 부여되지 않으면 애플리케이션에서 no_camera_access
변수에 저장된 문자열 메시지와 함께 TextView
를 표시합니다.
$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)
함수에서 카메라 권한을 요청하기 전에 다음 두 줄을 추가합니다.
previewDisplayView = new SurfaceView(this);
setupPreviewDisplayView();
이제 setupPreviewDisplayView()
를 정의하는 코드를 추가합니다.
private void setupPreviewDisplayView() {
previewDisplayView.setVisibility(View.GONE);
ViewGroup viewGroup = findViewById(R.id.preview_display_layout);
viewGroup.addView(previewDisplayView);
}
previewFrameTexture
라는 SurfaceTexture
객체를 사용하여 카메라 프레임을 표시할 수 있도록 새 SurfaceView
객체를 정의하고 preview_display_layout
FrameLayout
객체에 추가합니다.
previewFrameTexture
를 사용하여 카메라 프레임을 가져오기 위해 CameraX를 사용합니다.
Framework는 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
를 사용할 수 있으면 surfaceTexture
를 previewFrameTexture
로 저장하고 previewFrameTexture
의 프레임을 볼 수 있도록 previewDisplayView
를 표시합니다.
그러나 카메라를 시작하기 전에 사용할 카메라를 결정해야 합니다. CameraXPreviewHelper
는 FRONT
및 BACK
의 두 가지 옵션을 제공하는 CameraHelper
에서 상속받습니다. 다른 카메라를 사용하여 다른 버전의 앱을 빌드하는 데 코드를 변경하지 않아도 되도록 BUILD
파일의 결정을 메타데이터로 전달할 수 있습니다.
카메라에서 보는 실시간 장면에서 BACK
카메라를 사용하여 에지 감지를 실행하려고 한다고 가정하고 메타데이터를 AndroidManifest.xml
에 추가합니다.
...
<meta-data android:name="cameraFacingFront" android:value="${cameraFacingFront}"/>
</application>
</manifest>
manifest_values
에 새 항목이 있는 helloworld
Android 바이너리 규칙의 BUILD
에서 선택 항목을 지정합니다.
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 그래프에서 프레임을 사용하려고 하므로 이 튜토리얼에서는 카메라 출력을 보는 코드를 직접 추가하지 않습니다. 대신 처리할 카메라 프레임을 MediaPipe 그래프로 전송하고 그래프의 출력을 화면에 표시하는 방법으로 건너뜁니다.
ExternalTextureConverter
설정
SurfaceTexture
는 스트림의 이미지 프레임을 OpenGL ES 텍스처로 캡처합니다. MediaPipe 그래프를 사용하려면 카메라에서 캡처한 프레임을 일반 Open GL 텍스처 객체에 저장해야 합니다. 프레임워크는 SurfaceTexture
객체에 저장된 이미지를 일반 OpenGL 텍스처 객체로 변환하는 ExternalTextureConverter
클래스를 제공합니다.
ExternalTextureConverter
를 사용하려면 EglManager
객체에서 만들고 관리하는 EGLContext
도 필요합니다. EglManager
, "//mediapipe/java/com/google/mediapipe/glutil"
를 사용하도록 BUILD
파일에 종속 항목을 추가합니다.
MainActivity
에서 다음 선언을 추가합니다.
private EglManager eglManager;
private ExternalTextureConverter converter;
onCreate(Bundle)
함수에서 카메라 권한을 요청하기 전에 eglManager
객체를 초기화하는 문을 추가합니다.
eglManager = new EglManager(null);
카메라 권한이 부여되었는지 확인하기 위해 MainActivity
에 onResume()
함수를 정의하고 startCamera()
를 호출했습니다. 이 확인 전에 onResume()
에 다음 줄을 추가하여 converter
객체를 초기화합니다.
converter = new ExternalTextureConverter(eglManager.getContext());
이제 이 converter
는 eglManager
에서 관리하는 GLContext
를 사용합니다.
또한 애플리케이션이 일시중지 상태가 되면 converter
를 올바르게 닫도록 MainActivity
에서 onPause()
함수를 재정의해야 합니다.
@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 그래프에서 카메라 프레임을 사용할 준비가 되었습니다.
Android에서 MediaPipe 그래프 사용
관련 종속 항목 추가
MediaPipe 그래프를 사용하려면 Android의 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,
)
BUILD
파일의 mediapipe_lib
빌드 규칙에 종속 항목 ":mediapipe_jni_lib"
를 추가합니다.
다음으로, 애플리케이션에서 사용할 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 모델과 같은 다른 애셋을 추가할 수도 있습니다.
또한 나중에 MainActivity
에서 검색할 수 있도록 그래프와 관련된 속성에 manifest_values
를 추가합니다.
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
필드에 의해 결정되는 바이너리 그래프의 파일 이름을 나타냅니다.
inputVideoStreamName
및 outputVideoStreamName
은 각각 그래프에 지정된 입력 및 출력 동영상 스트림 이름입니다.
이제 MainActivity
는 MediaPipe 프레임워크를 로드해야 합니다. 또한 프레임워크는 OpenCV를 사용하므로 MainActvity
도 OpenCV
를 로드해야 합니다. MainActivity
에서 다음 코드를 사용하여 (클래스 내, 함수 내부가 아님) 두 종속 항목을 모두 로드합니다.
static {
// Load all native libraries needed by the app.
System.loadLibrary("mediapipe_jni");
System.loadLibrary("opencv_java3");
}
MainActivity
의 그래프 사용
먼저 그래프의 .pbtxt
파일에서 컴파일된 .binarypb
가 포함된 애셋을 로드해야 합니다. MediaPipe 유틸리티 AndroidAssetUtil
를 사용하면 됩니다.
eglManager
를 초기화하기 전에 onCreate(Bundle)
에서 Asset Manager를 초기화합니다.
// Initialize asset manager so that MediaPipe native libraries can access the app assets, e.g.,
// binary graphs.
AndroidAssetUtil.initializeNativeAssetManager(this);
이제 converter
에서 준비된 카메라 프레임을 MediaPipe 그래프로 전송하고 그래프를 실행하고 출력을 준비한 다음 previewDisplayView
를 업데이트하여 출력을 표시하는 FrameProcessor
객체를 설정해야 합니다. 다음 코드를 추가하여 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"));
processor
는 처리를 위해 converter
에서 변환된 프레임을 사용해야 합니다. converter
를 초기화한 후 onResume()
에 다음 줄을 추가합니다.
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
가 생성되면 processor
의 VideoSurfaceOutput
에 관한 Surface
가 있었습니다. 소멸되면 processor
의 VideoSurfaceOutput
에서 삭제됩니다.
이상입니다 이제 기기에서 애플리케이션을 성공적으로 빌드하고 실행할 수 있으며 실시간 카메라 피드에서 Sobel 에지 감지가 실행되는 것을 확인할 수 있습니다. 축하합니다!
문제가 발생한 경우 여기에서 가이드의 전체 코드를 확인하세요.