NTTドコモR&Dの技術ブログです。

Android XR取り組み紹介(画像認識アプリ編)

※ 本記事は 2026/3/31 以前にNTTコノキューにて記載した記事になります

0.はじめに

この記事はNTTコノキュー アドベントカレンダー2025の記事です。

こんにちは。NTTコノキューの乗松です。

本記事は端末内に保存された画像をGeminiに送信し、画像の説明をアプリ上に表示させる機能の紹介になります。

1.開発環境

  • MacBook Apple M4
  • Android Studio Narwhal 4 Feature Drop | 2025.1.4 Canary 2
  • Jetpack XR SDK 1.0.0-alpha07

2.実装手順

2.1 画面の作成

まずは画像を取得を開始するボタンを実装します。

添付画像は作成したホームスペースの画面になります。

以前設定した音声認識ボタンの横に配置しています。

Row(
modifier = Modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center

) {
 -------------中略-------------
    Button(
        modifier = Modifier
            .width(300.dp),
        onClick = {
            // ボタンが押下された時の処理を記述
       // deviceImagePickerLauncherの解説は後述します
            deviceImagePickerLauncher.launch("image/*")
        }
    ) {
        Text(
            text = "画像",
            fontSize = 32.sp,
            color = Color.White,
        )
    }
}

2.2 Geminiのモデルのインスタンス作成

本記事はFirebase AI Logicを使用しています。

事前にFirebaseの公式ドキュメントを参考に、Firebaseプロジェクトを作成してアプリを接続した後、アプリにSDKをインストールしてください。

別記事でも記載していますが、下記メソッドでGeminiのモデルのインスタンスを作成することができます。

import com.google.firebase.Firebase
import com.google.firebase.ai.ai
import com.google.firebase.ai.GenerativeModel
import com.google.firebase.ai.type.GenerativeBackend

-------------中略-------------

val model: GenerativeModel = generativeModel()

-------------中略-------------

fun generativeModel(): GenerativeModel {
    return  Firebase.ai(backend = GenerativeBackend.googleAI())
        .generativeModel("gemini-2.5-flash")
}

2.3 Geminiへ画像データを送信する

Geminiへ画像を送信するメソッドを作成します。

fun sendImageToGemini(
    bitmap: Bitmap?,
    onResponse: (String) -> Unit
) {
    // バックグラウンドで非同期で動作させる
    viewModelScope.launch(Dispatchers.IO) {
        try {
            if (bitmap == null) return@launch

            // 引数のbitmapを使用して画像データを渡す
            val prompt = content {
                image(bitmap)

                // textは用途に合わせて変更してください
                text("これは何ですか?一言で答えてください")
            }
            val response = model.generateContent(prompt)
            val geminiResponseText = response.text ?: "No response text found."

            // メインスレッドでコールバック
            withContext(Dispatchers.Main) {
                onResponse(geminiResponseText)
            }
        } catch (e: Exception) {
            withContext(Dispatchers.Main) {
                onResponse("Error: ${e.message}")
            }
        }
    }
}

2.5 URIからBitmapに変換する

引数に設定した画像のURIからBitmapに変換するメソッドです。

bitmapというプライベート変数に代入していますが、適宜変更してください。

fun convertUriToBitmap(context: Context, imageUri: Uri) {
    val createdBitmap =
        BitmapFactory.decodeStream(context.contentResolver.openInputStream(imageUri))
    bitmap = createdBitmap
}

2.6 画像が選択された時のランチャーを作成

ランチャーを作成し、画像が選択された時の処理を記述します。

val deviceImagePickerLauncher =
    rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri: Uri? ->
        if (uri != null) {
            // uriをbitmapに変換
            convertUriToBitmap(context, uri)

            // 変換したbitmapを使ってGeminiにリクエスト
            sendImageToGemini(
                bitmap = bitmap,
                onResponse = { response ->
                    geminiResponse = response
                })
        }
    }

rememberLauncherForActivityResultの詳しい仕様は公式ドキュメントを参照してください。

2.1に記載している下記をボタン押下時に実装することで上記ランチャーが起動し、画像ギャラリーを開くことができます。

deviceImagePickerLauncher.launch("image/*")

2.7 画像を選択する

上記を実装すると、ボタン押下でこのような画面が表示されます。

右上の[︙]→[参照]を押下すれば画像をドラッグ&ドロップで格納できるようになります。

試しにQONOQのロゴを選択してみます。

「ロゴ」とレスポンスされましたね...

ここはGeminiに画像と一緒に送るプロンプトの文言でよしなに変更できるので、色々試してみてください。

補足として、エミュレータの動作は基本重いので扱う画像のサイズはできるだけ小さくすることをお勧めします。

3.まとめ

本記事では端末内に保存された画像をGeminiに送信し、画像の説明をアプリ上に表示させる機能を紹介しました。

画像をGeminiに送信するという機能は色々応用ができると思います。参考になれば幸いです。