1. はじめに
MediaPipe とは
MediaPipe Solutions を使用すると、アプリに機械学習(ML)ソリューションを適用できます。このソリューションが提供するフレームワークでは、事前構築済みの処理パイプラインを構成して、ユーザーに有益で魅力的な出力を迅速に配信できます。これらのソリューションを MediaPipe Model Maker でカスタマイズし、デフォルトのモデルを更新することもできます。
画像分類は、MediaPipe Solutions が提供できる ML ビジョンタスクの一つです。MediaPipe Tasks は、Android、iOS、Python(Raspberry Pi を含む)、ウェブで使用できます。
この Codelab では、画面に数字を描画できる Android アプリから始めて、それらの描画された数字を 0 ~ 9 の 1 つの値として分類する機能を追加します。
学習内容
- MediaPipe Tasks を使用して Android アプリに画像分類タスクを組み込む方法
必要なもの
- Android Studio のインストール バージョン(この Codelab は Android Studio Giraffe を使用して記述、テストされています)。
- アプリを実行するための Android デバイスまたはエミュレータ。
- Android 開発に関する基本的な知識(これは「Hello World」ではありませんが、それほど変わりません)。
2. MediaPipe Tasks を Android アプリに追加する
Android スターター アプリをダウンロードする
この Codelab では、画面に描画できる既製のサンプルを使用します。開始用アプリは、こちらの公式 MediaPipe サンプル リポジトリにあります。リポジトリのクローンを作成するか、[コード] >ZIP をダウンロード。
アプリを Android Studio にインポートする
- Android Studio を開きます。
- [Welcome to Android Studio] 画面で、右上にある [Open] を選択します。
- リポジトリのクローンを作成またはダウンロードした場所に移動し、codelabs/digitclassifier/android/start ディレクトリを開きます。
- Android Studio の右上にある緑色の実行矢印()をクリックして、すべてが正しく開いたことを確認します。
- アプリが開き、描画できる黒い画面と、その画面をリセットする [Clear] ボタンが表示されます。その画面に描画することもできますが、それ以外は大きな効果がないため、これからこの問題を修正します。
モデル
アプリを初めて実行すると、mnist.tflite という名前のファイルがダウンロードされて、アプリの assets ディレクトリに保存されていることがわかります。わかりやすくするため、数字を分類する既知のモデル MNIST を、プロジェクトで download_models.gradle スクリプトを使用してアプリに追加しています。手書き文字用のモデルなど、独自のカスタムモデルをトレーニングする場合は、download_models.gradle ファイルを削除し、アプリレベルの build.gradle ファイルでそのモデルへの参照を削除し、コードの後半(特に DigitClassifierHelper.kt ファイル内で)でモデルの名前を変更します。
build.gradle を更新する
MediaPipe Tasks を使い始める前に、ライブラリをインポートする必要があります。
- app モジュールにある build.gradle ファイルを開き、execution ブロックまで下にスクロールします。
- このブロックの下部に「// STEP 1 Dependency Import」というコメントが表示されます。
- この行を次の実装に置き換えます。
implementation("com.google.mediapipe:tasks-vision:latest.release")
- Android Studio の上部にあるバナーに表示される [Sync Now] ボタンをクリックして、この依存関係をダウンロードします。
3. MediaPipe Tasks の数字分類ヘルパーを作成する
次のステップでは、ML の分類に関する手間のかかる作業を担当するクラスに記入します。DigitClassifierHelper.kt を開いて、始めましょう。
- クラスの先頭にある「// STEP 2 Create リスナー」というコメントを探します。
- この行を次のコードに置き換えます。これにより、DigitClassifierHelper クラスからの結果をリッスンしている場所に、結果を渡すために使用されるリスナーが作成されます(ここでは DigitCanvasFragment クラスですが、まもなく実装されます)。
// STEP 2 Create listener
interface DigitClassifierListener {
fun onError(error: String)
fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
)
}
- クラスのオプション パラメータとして DigitClassifierListener も受け取る必要があります。
class DigitClassifierHelper(
val context: Context,
val digitClassifierListener: DigitClassifierListener?
) {
- // STEP 3 Define classifier とある行まで次の行を追加して、このアプリで使用する ImageClassifier のプレースホルダを作成します。
// ステップ 3 分類器の定義
private var digitClassifier: ImageClassifier? = null
- 「// ステップ 4 分類器を設定する」というコメントがある次の関数を追加します。
// STEP 4 set up classifier
private fun setupDigitClassifier() {
val baseOptionsBuilder = BaseOptions.builder()
.setModelAssetPath("mnist.tflite")
// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
.setRunningMode(RunningMode.IMAGE)
.setBaseOptions(baseOptionsBuilder.build())
try {
digitClassifier =
ImageClassifier.createFromOptions(
context,
optionsBuilder.build()
)
} catch (e: IllegalStateException) {
digitClassifierListener?.onError(
"Image classifier failed to initialize. See error logs for " +
"details"
)
Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}
}
上記のセクションでは、いくつかのことを行うので、もう少し詳しく見てみましょう。
val baseOptionsBuilder = BaseOptions.builder()
.setModelAssetPath("mnist.tflite")
// Describe additional options
val optionsBuilder = ImageClassifierOptions.builder()
.setRunningMode(RunningMode.IMAGE)
.setBaseOptions(baseOptionsBuilder.build())
このブロックは、ImageClassifier で使用するパラメータを定義します。これには、アプリ(mnist.tflite)内の BaseOptions に格納されたモデルと、ImageClassifierOptions の下の RunningMode が含まれます。この場合は IMAGE ですが、VIDEO と LIVE_STREAM も追加オプションとして利用できます。その他の利用可能なパラメータには、モデルが返す結果を最大数に制限する MaxResults と、ScoreThreshold があります。ScoreThreshold は、モデルが結果を返す前に必要となる、最小の信頼度を設定するパラメータです。
try {
digitClassifier =
ImageClassifier.createFromOptions(
context,
optionsBuilder.build()
)
} catch (e: IllegalStateException) {
digitClassifierListener?.onError(
"Image classifier failed to initialize. See error logs for " +
"details"
)
Log.e(TAG, "MediaPipe failed to load model with error: " + e.message)
}
構成オプションを作成したら、コンテキストとオプションを渡して新しい ImageClassifier を作成できます。この初期化プロセスで問題が発生すると、DigitClassifierListener からエラーが返されます。
- ImageClassifier は使用前に初期化する必要があるため、init ブロックを追加して setupDigitClassifier() を呼び出すことができます。
init {
setupDigitClassifier()
}
- 最後に、「ステップ 5 分類関数を作成」と記載されたコメントまで下にスクロールし、次のコードを追加します。この関数は描画された数字である Bitmap を受け取り、MediaPipe Image オブジェクト(MPImage)に変換してから、ImageClassifier を使用してその画像を分類し、DigitClassifierListener を介して結果を返す前に推論にかかる時間を記録します。
// STEP 5 create classify function
fun classify(image: Bitmap) {
if (digitClassifier == null) {
setupDigitClassifier()
}
// Convert the input Bitmap object to an MPImage object to run inference.
// Rotating shouldn't be necessary because the text is being extracted from
// a view that should always be correctly positioned.
val mpImage = BitmapImageBuilder(image).build()
// Inference time is the difference between the system time at the start and finish of the
// process
val startTime = SystemClock.uptimeMillis()
// Run image classification using MediaPipe Image Classifier API
digitClassifier?.classify(mpImage)?.also { classificationResults ->
val inferenceTimeMs = SystemClock.uptimeMillis() - startTime
digitClassifierListener?.onResults(classificationResults, inferenceTimeMs)
}
}
ヘルパー ファイルについては以上です。次のセクションでは、描画された数値の分類を開始する最後のステップに取り組みます。
4. MediaPipe Tasks を使用して推論を実行する
このセクションを開始するには、Android Studio で DigitCanvasFragment クラスを開きます。このクラスで、すべての処理が行われます。
- このファイルの一番下に、「// STEP 6 Set up リスナー」というコメントがあります。ここでは、リスナーに関連付けられた onResults() 関数と onError() 関数を追加します。
// STEP 6 Set up listener
override fun onError(error: String) {
activity?.runOnUiThread {
Toast.makeText(requireActivity(), error, Toast.LENGTH_SHORT).show()
fragmentDigitCanvasBinding.tvResults.text = ""
}
}
override fun onResults(
results: ImageClassifierResult,
inferenceTime: Long
) {
activity?.runOnUiThread {
fragmentDigitCanvasBinding.tvResults.text = results
.classificationResult()
.classifications().get(0)
.categories().get(0)
.categoryName()
fragmentDigitCanvasBinding.tvInferenceTime.text = requireActivity()
.getString(R.string.inference_time, inferenceTime.toString())
}
}
onResults() は、ImageClassifier から受け取った結果を表示するため、特に重要です。このコールバックはバックグラウンド スレッドからトリガーされるため、Android の UI スレッドでも UI の更新を実行する必要があります。
- 上記の手順でインターフェースから新しい関数を追加する場合は、クラスの先頭に実装宣言も追加する必要があります。
class DigitCanvasFragment : Fragment(), DigitClassifierHelper.DigitClassifierListener
- クラスの一番上に、「// STEP 7a Initialize classifier」というコメントが表示されます。このファイルに DigitClassifierHelper の宣言を配置します。
// STEP 7a Initialize classifier.
private lateinit var digitClassifierHelper: DigitClassifierHelper
- // ステップ 7b の [分類器を初期化する] に進むと、onViewCreated() 関数内で digitClassifierHelper を初期化できます。
// STEP 7b Initialize classifier
// Initialize the digit classifier helper, which does all of the
// ML work. This uses the default values for the classifier.
digitClassifierHelper = DigitClassifierHelper(
context = requireContext(), digitClassifierListener = this
)
- 最後のステップとして、コメント「// STEP 8a*: classification*」を見つけて、以下のコードを追加して、後日追加する新しい関数を呼び出します。このコードブロックは、アプリの描画領域から指を離すと分類をトリガーします。
// STEP 8a: classify
classifyDrawing()
- 最後に、コメント // STEP 8b classify を探して、新しい classifyDrawing()関数を追加します。これにより、キャンバスからビットマップが抽出され、DigitClassifierHelper に渡され、分類が実行されて onResults() インターフェース関数で結果を受け取ります。
// STEP 8b classify
private fun classifyDrawing() {
val bitmap = fragmentDigitCanvasBinding.digitCanvas.getBitmap()
digitClassifierHelper.classify(bitmap)
}
5. アプリをデプロイしてテストする
これで、画面上に描画された数字を分類できるアプリが完成しました。次に、アプリを Android Emulator または実際の Android デバイスにデプロイしてテストします。
- Android Studio ツールバーの実行アイコン()をクリックしてアプリを実行します。
- 描画パッドに任意の数字を描いて、アプリが認識できるかどうかを確認します。モデルが描画したと考えている数字と、その数字の予測にかかった時間の両方を表示する必要があります。
6. 完了
これで完了です。この Codelab では、Android アプリに画像分類を追加する方法、特に MNIST モデルを使用して手描きの数字を分類する方法を学習しました。
次のステップ
- 数字を分類できるようになったので、独自のモデルをトレーニングして、描画された文字や動物、その他無限にあるアイテムを分類できるようにしましょう。MediaPipe Model Maker を使用して新しい画像分類モデルをトレーニングする方法については、developers.google.com/mediapipe ページをご覧ください。
- 顔のランドマーク検出、ジェスチャー認識、音声分類など、Android で使用できるその他の MediaPipe タスクについて学習する。
皆様の素晴らしい作品づくりを楽しみにしています。