はじめに
この Hello World!このチュートリアルでは、MediaPipe Framework を使用して、 Android で MediaPipe グラフを実行します。
作業内容
ライブ動画に適用するリアルタイムの Sobel エッジ検出機能を備えたシンプルなカメラアプリ Android デバイスでストリーミングできます
セットアップ
- MediaPipe Framework をシステムにインストールします。詳しくは、フレームワークのインストールをご覧ください。 ガイドをご覧ください。
- Android Development 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
は、1 つのパケット(画像
フレームなど)を指定し、OpenGL シェーダーを使用して輝度の変化を適用します。結果
画像フレームが luma_video
出力ストリームに送信されます。
2 番目のノード SobelEdgesCalculator
は、受信インスタンスにエッジ検出を適用します。
パケットが luma_video
ストリームで出力され、結果が output_video
出力に出力されます。
。
出力された画像フレームが Android アプリに表示され、
output_video
ストリーム。
最小限のアプリケーションの初期設定
最初に、「Hello World!」と表示するシンプルな Android アプリケーションから始めます。
表示されます。Android のビルドに慣れている場合は、この手順をスキップしてもかまいません。
bazel
を使用するアプリケーション。
Android アプリを作成する新しいディレクトリを作成します。対象
このチュートリアルのコードの全文は
mediapipe/examples/android/src/java/com/google/mediapipe/apps/basic
。Google は
Codelab 全体で、このパスを $APPLICATION_PATH
と表記します。
アプリケーションへのパスは次のとおりです。
- アプリケーションの名前は
helloworld
です。 - アプリケーションの
$PACKAGE_PATH
はcom.google.mediapipe.apps.basic
です。 これは、このチュートリアルのコード スニペットで使用するため、 コード スニペットをコピーまたは使用する場合は、独自の$PACKAGE_PATH
を使用してください。
ファイル activity_main.xml
を $APPLICATION_PATH/res/layout
に追加します。これにより、
アプリの全画面表示での TextView
で、文字列 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>
コンテンツを読み込むシンプルな 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>
$APPLICATION_PATH/res/values/
に styles.xml
を追加します。
<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
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
です。これを使用するには、依存関係を
"//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
という名前の新しい FrameLayout
と、
その中にネストされ、no_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>
ユーザーがカメラへのアクセスを許可しない場合、画面は次のようになります。 これを次のように使用します。
次に、SurfaceTexture
オブジェクトと SurfaceView
オブジェクトを
MainActivity
:
private SurfaceTexture previewFrameTexture;
private SurfaceView previewDisplayView;
onCreate(Bundle)
関数の、前に次の 2 行を追加します。
カメラへのアクセス権限をリクエストしています。
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
オブジェクトが作成されて、匿名 ID が追加されます。
設定されます。cameraHelper
がカメラの起動を通知したとき
フレームを取得するための surfaceTexture
が使用でき、
surfaceTexture
を previewFrameTexture
として、previewDisplayView
にします。
previewFrameTexture
からフレームを確認できるようにしています。
ただし、カメラを起動する前に、使用するカメラを決定する必要があります
あります。CameraXPreviewHelper
は、次の 2 つを提供する CameraHelper
から継承します。
FRONT
と BACK
を使用します。BUILD
ファイルから決定を渡すことができます。
別バージョンのバージョンをビルドするためにコードを変更せずに、
別のカメラで撮影した動画。
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 グラフではフレームを使用するので、ここでは このチュートリアルでは直接カメラ出力を確認できます。その代わりに 事前構築済み API の 処理するカメラフレームを MediaPipe グラフに送信し、 画面上でのグラフの出力です
ExternalTextureConverter
のセットアップ
SurfaceTexture
は、OpenGL ES としてストリームから画像フレームをキャプチャします。
テクスチャです。MediaPipe グラフを使用するには、カメラからキャプチャしたフレームを
標準 Open GL テクスチャ オブジェクトに格納されます。このフレームワークには、
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());
この converter
は、eglManager
が管理する 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.Callback
を
previewDisplayView
を実行し、適切な表示サイズを計算する 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_jni_lib"
を次の mediapipe_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
フィールドによって決まります。
inputVideoStreamName
と outputVideoStreamName
は入力と出力です。
グラフで指定された動画ストリーム名を 1 つ作成します。
次に、MainActivity
が MediaPipe フレームワークを読み込む必要があります。また、
フレームワークは OpenCV を使用するため、MainActvity
も OpenCV
を読み込む必要があります。こちらの
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"));
processor
は、converter
から変換されたフレームを
あります。初期化後に、次の行を 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
を
processor
の VideoSurfaceOutput
。破壊されると
processor
の VideoSurfaceOutput
。
これで完了です。Cloud Shell をビルドし、正常に実行できるはずです。 Sobel Edge Detection アプリがライブカメラで実行されていることを確認する フィード!おめでとうございます!
問題が発生した場合は、チュートリアルの完全なコードをご覧ください。 こちらをご覧ください。