このドキュメントは、作成したAndroidカメラアプリケーションの全体的なアーキテクチャ、主要機能、および各コンポーネントの役割について詳細に説明します。
このアプリは、カメラからの映像にリアルタイムで画像処理を施し、その結果をプレビュー、保存できる拡張性の高いプラットフォームです。単なるカメラアプリではなく、様々な画像処理モジュール(プロセッサ)を「プラグイン」のように追加・組み合わせて利用できることを目的としています。
-
リアルタイム画像処理: カメラのプレビュー映像に対し、選択された複数の画像処理モジュール(プロセッサ)をリアルタイムに適用し、結果を画面に表示します。これにより、ユーザーは撮影前に処理結果をインタラクティブに確認できます。
-
プロセッサの選択と順序付け: ユーザーは利用可能なプロセッサを一覧から選択し、重ねがけする際の優先順位を自由に入れ替えることができます。これにより、「顔を検出してから、その部分にエフェクトをかける」といった順序が重要な処理を実現します。
-
撮影とプレビュー: 処理が適用された状態で写真を撮影し、保存する前に処理後の画像を確認できます。
-
モジュール式の保存: 選択された各プロセッサは、それぞれ指定された個別のディレクトリに処理後の画像を保存します。これにより、処理結果の管理が容易になります。
-
ハードウェアアクセラレーション: PixelデバイスのTPU(Tensor Processing Unit)などを活用するため、NNAPIデリゲートを使用した推論の高速化に対応しています。これにより、複雑な機械学習モデルもリアルタイムで効率的に実行できます。
このアプリケーションは、Googleが推奨する**MVVM (Model-View-ViewModel)**に似た、UI、状態管理、ビジネスロジックを分離したモダンなアーキテクチャを採用しています。これにより、各コンポーネントの独立性が高まり、テストや機能追加が容易になっています。
- CameraX: カメラの複雑な制御を抽象化し、プレビュー、画像解析、撮影といった機能を簡単に実装するために使用します。
- OpenCV: 画像の回転、切り抜き、描画など、高度な画像操作を行うために使用します。
- MediaPipe: 顔のランドマーク検出など、高速で高精度な機械学習タスクを実行するために使用します。
- TensorFlow Lite: カスタムの機械学習モデル(CNNなど)をモバイルデバイス上で効率的に実行するために使用します。
CameraFragmentがCameraXのImageAnalysisユースケースを起動します。ImageAnalysisはカメラからフレームを継続的に受け取ります。- 各フレームは
SharedViewModelを通じて取得したプロセッサのリスト(順序付け済み)に渡されます。 - リストの順序に従い、各
ImgAnalyzerのprocessFrameForDisplayが連鎖的に実行されます。 - 最終的に加工された画像が
ImageView(オーバーレイ) に描画され、ユーザーに表示されます。
- ユーザーが
CameraFragmentのシャッターボタンを押します。 - CameraXの
ImageCaptureユースケースが高解像度の画像を撮影します。 - 撮影された画像(
Bitmap)はSharedViewModelにセットされます。 - 画面が
PreviewFragmentに遷移します。 PreviewFragmentはSharedViewModelから画像とプロセッサリストを取得します。- 各
ImgAnalyzerのprocessFrameForSavingが実行され、最終的な保存用画像が生成されます。 - 生成された画像が
RecyclerViewに一覧表示されます。 - ユーザーが保存ボタンを押すと、
MediaStoreAPIを通じて各画像がそれぞれの指定ディレクトリに保存されます。
MainActivity.kt:- アプリの唯一のActivityであり、
FragmentContainerViewを使ってCameraFragmentとPreviewFragmentをホストします。画面遷移の司令塔の役割を担います。
- アプリの唯一のActivityであり、
CameraFragment.kt:- 役割: アプリのメイン画面であり、カメラの制御、リアルタイムプレビュー、撮影のトリガーを担当します。
- 詳細:
onCreateViewでView Bindingを使い、レイアウトファイル(fragment_camera.xml)内のUI要素に安全にアクセスします。- CameraXのライフサイクル管理をFragmentのライフサイクルに紐付けることで、アプリがバックグラウンドに回った際などにカメラリソースが自動的に解放されるようにしています。
- プロセッサ選択ボタンが押された際に
ProcessorSelectionDialogFragmentをインスタンス化し、ProcessorSelectionListenerインターフェースを通じて結果を受け取ります。
PreviewFragment.kt:- 役割: 撮影された画像のプレビュー表示と保存処理を担当します。
- 詳細:
RecyclerViewとPreviewAdapterを使い、処理された画像のリストを効率的に表示します。- 画像の保存には
MediaStoreAPIを使用し、Android 10以降のScoped Storageに対応した形で実装しています。これにより、アプリがアンインストールされても画像は端末に残り続けます。
ProcessorSelectionDialogFragment.kt:- 役割: ユーザーが画像処理プロセッサを選択し、その適用順序を並べ替えるためのUIを提供します。
- 詳細:
DialogFragmentを継承することで、モーダルウィンドウとして表示されます。ProcessorAdapterがリストの表示と、上下ボタンによる並べ替えロジックを管理します。ProcessorSelectionListenerインターフェースを介して、選択・並べ替え後のプロセッサリストを呼び出し元のCameraFragmentに通知します。このインターフェースによる疎結合な設計により、再利用性が高まっています。
SharedViewModel.kt:- 役割:
CameraFragmentとPreviewFragmentという、ライフサイクルが異なるコンポーネント間で安全にデータを共有します。 - 詳細:
LiveDataを使用することで、データが変更された際にUI(Fragment)が自動的に通知を受け取ることができます。また、UIが非表示の状態(例:バックグラウンド)では通知が保留されるため、不要な処理やクラッシュを防ぎます。
- 役割:
ProcessorRepository.kt:- 役割: アプリ内で利用可能な全ての
ImgProcessorを動的に読み込み、一元管理します。 - 詳細:
- Javaのリフレクションという仕組みを使い、
processorClassNamesリストに文字列として記述されたクラスを、実行時にインスタンス化します。これにより、新しいプロセッサを追加する際にRepositoryのコードを直接修正する必要がなくなり、メンテナンス性が向上します。
- Javaのリフレクションという仕組みを使い、
- 役割: アプリ内で利用可能な全ての
ImgProcessor.kt(Interface):- 役割: 全ての画像処理モジュールが従うべき共通のルール(インターフェース)を定義します。
- 詳細: このインターフェースがあることで、
CameraFragmentは各プロセッサの具体的な処理内容(顔検出なのか、色調補正なのか等)を知らなくても、一貫した方法で処理を呼び出すことができます。これはポリモーフィズムの原則に基づいた、拡張性の高い設計の核となる部分です。
models/**/ImgAnalyzer.kt:- 役割:
ImgProcessorインターフェースの具体的な実装です。各ファイルが、一つの独立した画像処理機能(モジュール)となります。 - 例 (
tongue_detector/ImgAnalyzer.kt):setup():FaceLandmarkerモデルとTfliteHelperを初期化します。processFrameForDisplay(): リアルタイム処理を担当。OpenCVで画像を回転・切り出し、TfliteHelperに渡して判定を行い、その結果(OK/NG)に応じて描画する四角形の色を変えます。processFrameForSaving(): 保存用処理を担当。高解像度のBitmapを受け取り、同様に切り出し処理を行って、最終的な画像を返します。
- 役割:
models/tongue_detector/TfliteHelper.kt:- 役割: TensorFlow Liteモデルの読み込みと推論実行という、専門的な処理をカプセル化(隠蔽)します。
- 詳細:
Interpreter.Options().addDelegate(NnApiDelegate())というコードにより、NNAPIデリゲートを有効化します。これにより、Android OSは利用可能なハードウェア(TPU, GPU, DSP)を自動的に検出し、最も効率的なプロセッサに推論処理を割り当てます。classify()メソッド内で、入力されたBitmapをモデルが必要とする形式(224x224のTensor)にリサイズ・正規化する前処理も行います。
このアプリの最大の特長は、新しい画像処理機能を簡単に追加できる点です。新しいプロセッサを追加する手順は以下の通りです。
- パッケージの作成:
modelsディレクトリ内に、新しい機能の名前でパッケージを作成します。(例:models/smile_detector) ImgAnalyzerの実装: 新しいパッケージ内にImgAnalyzer.ktを作成し、ImgProcessorインターフェースを実装します。nameやsaveDirectoryNameを定義し、processFrameForDisplayとprocessFrameForSavingに独自の画像処理ロジックを記述します。- モデルの配置(任意): もし機械学習モデルを使用する場合は、
assetsフォルダ内にモデルファイル(.tfliteなど)を配置し、setup()メソッド内で読み込むようにします。 Repositoryへの登録:ProcessorRepository.ktを開き、processorClassNamesリストに、新しく作成したImgAnalyzerの完全修飾クラス名(例:"com.example.AnyAICam.models.smile_detector.ImgAnalyzer")を文字列として1行追加します。
以上の手順だけで、アプリを再起動すると、新しいプロセッサが選択画面に自動的に表示され、利用可能になります。