アプリの基礎

CameraX とオンデバイス インテリジェンスでデバイス全体のカメラ エクスペリエンスを向上

スピーカー

概要

講演について

ユーザーは、照明条件やハードウェア層に関係なく、サードパーティ製アプリでもデバイスのネイティブ カメラと同じ高忠実度で写真や動画を撮影できることを期待しています。拡大する Android エコシステム全体でこの一貫性を実現するには、アーキテクチャに関する重要な考慮事項があります。このセッションでは、デバイスの機能を最大限に活用しながら、すべてのフォーム ファクタで信頼性を維持する、堅牢でプロフェッショナルなキャプチャ パイプラインを構築する方法を説明します。

CameraX の最新の進歩について説明し、モーション フォトや Google Play 開発者サービスによるオンデバイス AI 補正などの最先端の機能を統合する方法と、Jetpack XR SDK を使用した空間エクスペリエンスについて紹介します。また、選択したコーディング エージェントがこのセッションのコンセプトを自動的に実装できるようにする CameraX スキルなどについても説明します。

概要

  • Camera1 は非推奨となっているため、Android アプリには Jetpack CameraX を使用することをおすすめします。Google Play のポリシーの適用により、来年、Android 18 で Camera1 が無効になります。
  • CameraXViewfinder は、折りたたみ式デバイスなど、さまざまなフォーム ファクタでカメラ プレビューを調整します。CameraX 1.7 では、ジェスチャー コントロールと画面の点滅のサポートが追加されています。
  • オーディオ&ディスプレイ グラスは、ホスト スマートフォンでアプリを実行しながら、両方のデバイスでカメラを公開します。ProjectedDeviceContext を渡すと、CameraX はグラスから直接画像をキャプチャできます。
  • Feature Group API を使用すると、アプリはサポートされているカメラ機能の組み合わせを選択できます。CameraX 1.6 以降では、4K 録画と動画の手ぶれ補正の組み合わせがサポートされています。
  • オンデバイスの ML が、ML Kit と MediaPipe を通じてカメラ プレビューとキャプチャされたメディアを分析します。デベロッパーは、リアルタイムのジェスチャー検出から生成画像の記述まで、さまざまなタスクを実行できます。

リソース

Gemini Notebook

トピック

  • camerax
  • android-camera
  • on-device-ml
  • gemini-nano
  • mediapipe
  • ml-kit
  • android-xr

文字起こし

概要と CameraX の概要

  • 皆さん、こんにちは。

  • 本日はご参加いただき、ありがとうございます。

  • 私は Aman です。Scott も同席しています。

  • 本日は、CameraX とオンデバイス インテリジェンスを使用して、デバイス間でカメラ エクスペリエンスを向上させる方法について説明します。

  • では始めましょう。

  • スマートフォンをどのように使用しているかを考えてみましょう。

  • 私たちは、スマートフォンのカメラを使って、大切な瞬間を撮影し、大切な人と共有することがよくあります。

  • カメラはスマートフォン エクスペリエンスの中心となっています。

  • 実際、優れたカメラはスマートフォンを購入する際の重要な要素となることがよくあります。

  • 本日は、プレミアムなカメラ エクスペリエンスを構築するための 2 つの大きなテーマについて説明します。

  • まず、CameraX のいくつかの機能について説明し、次に、オンデバイスの機械学習手法について説明します。

  • まずは CameraX から始めましょう。

  • Jetpack CameraX は、Android のカメラ機能を強化します。

  • CameraX 以外にも、Android プラットフォーム フレームワークの一部である Camera1 と Camera2 があります。

  • では、どちらのライブラリを使用すればよいでしょうか。

  • Camera1 は、Android の最初の Camera API でした。

  • この API は非推奨となったため、新しいアプリでは Camera2 または CameraX を使用する必要があります。

  • Camera2 は Camera1 の後継です。

  • そのため、複雑なユースケースに対応するために低レベルのカメラ コントロールが必要な場合は、Camera2 が適しています。

  • ただし、デバイス固有の構成を自分で処理する必要があります。

  • 最後に、Jetpack CameraX は Camera2 をベースにしています。

  • 一貫した API を提供し、デバイス固有の構成を自動的に処理します。

  • ほとんどのデベロッパーにおすすめの選択肢です。

  • 非常に重要な注意点があります。

  • Google は Camera1 を無効にする予定です。これは、来年の Android 18 での Google Play ポリシーの適用から開始されます。

  • 以前の Camera1 API をまだ使用している場合は、CameraX または Camera2 に移行する必要があります。

  • では、CameraX を使用するメリットは何でしょうか。

  • まず、Android 7 以降をサポートしているため、アクティブな Android デバイスのほぼ 99% と互換性があります。

  • 2 つ目は、使いやすさです。プレビュー、キャプチャ、録画などの一般的なユースケースに対応するシンプルな API が用意されており、少ないコードで実装できます。

  • 3 つ目は、デバイス固有の問題(折りたたみ式デバイスやタブレットでよく見られるプレビューの引き伸ばしなど)を軽減することで、デバイス間で一貫したエクスペリエンスを提供することです。

  • 最後に、夜間モードやポートレートなどの高度な OEM 機能を簡単に実装できます。

  • それでは、Scott に CameraX の機能について話してもらいましょう。

  • Scott、よろしくお願いします。

  • Aman、ありがとう。

  • 皆さん、こんにちは。

  • Scott と申します。

  • 本日は、プロレベルのカメラ エクスペリエンスを実現する CameraX のいくつかの機能について説明します。

アダプティブ ビューファインダーの機能

  • まずは、アダプティブ ビューファインダーです。

  • さまざまなフォーム ファクタや画面サイズでのプレビューを処理する際の負担を完全に軽減するように設計されています。

  • ユーザーは現在、折りたたみ式デバイス、スマートフォン、ノートパソコン、XR デバイスなど、さまざまなデバイスを使用しています。

  • そのため、新しいフォーム ファクタに適応するカメラ エクスペリエンスを構築することが、これまで以上に重要になっています。

  • また、カメラ プレビューを正しく表示するのは非常に難しく、特にワイドスクリーンの折りたたみ式デバイスでは困難です。

  • これらのデバイスでは、開いたときに、インナーカメラは水平に、背面カメラは垂直に配置されます。

  • そのため、すべてのカメラセンサーが垂直に取り付けられていると仮定すると、空間的な不一致が生じ、歪みや横向きの原因となります。

  • CameraXViewfinder は、複雑な処理をすべて自動で行うように設計されているため、すべてのフォーム ファクタでカメラ プレビューがシームレスに動作します。

  • CameraXViewfinder コンポーザブルの使用方法を見てみましょう。

    var surfaceRequest by remember {
        mutableStateOf<SurfaceRequest?>(null)
    }
    
    LaunchedEffect(...) {
        preview.setSurfaceProvider { surfaceRequest = it }
        cameraProvider.bindToLifecycle(..., preview)
    }
    
    surfaceRequest?.let { request ->
        CameraXViewfinder(
            surfaceRequest = request,
            contentScale = ContentScale.Fit
        )
    }
  • まず、サーフェス リクエストを保持する surfaceRequest コンテンツ変数を宣言します。

  • 失礼。

  • 次に、LaunchedEffect で、サーフェス リクエスト オブジェクトをキャッチするようにサーフェス プロバイダを設定します。

  • 次に、ライフサイクルにバインドして、カメラを起動します。

  • 最後に、有効なサーフェス リクエストで状態が更新され、再コンポーズがトリガーされるたびに、リクエストを CameraXViewfinder に直接設定します。

  • ここで、好みに応じて contentScale をフィットまたは切り抜きに設定できます。

  • これで設定は完了です。

  • CameraX 1.7 では、CameraXViewfinder にいくつかの新機能が追加されています。

  • これには、タップしてフォーカスする、ピンチしてズームするなどの新しいジェスチャーが含まれます。

  • また、画面の点滅にも対応しており、画面を使って自撮り写真を明るくすることができます。

  • この機能の実装は簡単です。

    CameraXViewfinder(
        surfaceRequest = viewModel.surfaceRequest,
        contentScale = ContentScale.Fit,
        isTapToFocusEnabled = true,
        isPinchToZoomEnabled = true,
        onScreenFlashReady =
            {
               imageCapture.screenFlash = it
               imageCapture.flashMode = FLASH_MODE_SCREEN
            }
    )
  • タップしてフォーカスとピンチしてズームを true に設定するだけです。

  • 画面の点滅の場合は、onScreenFlashReady コールバックから画面の点滅 インスタンスを取得し、ImageCapture に渡して、フラッシュ モードを FLASH_MODE_SCREEN に設定します。

  • ユーザーはリンクをタップし このようなページに移動して参加するだけです

Android XR でのカメラ キャプチャ

  • CameraX を使用して、すべてのフォーム ファクタでシームレスに動作するカメラ プレビューを実現する方法を説明しました。

  • 次に、Android XR でのカメラ キャプチャについて詳しく見ていきましょう。

  • XR デバイスには 3 つのカテゴリがあります。1 つ目は XR ヘッドセットです。

  • これらは、カメラを使用して物理的な世界をキャプチャし、ヘッドセット内に表示する、完全に密閉された没入型ヘッドセットです。

  • 2 つ目は、有線 XR グラスです。

  • 本日、このカテゴリの基調講演で XREAL Aura を発表しました。

  • 基調講演でご覧いただいたように、これはコンピューティング パックに接続されたシースルー メガネで、現実世界にオーバーレイされた仮想コンテンツを表示できます。

  • 3 つ目は、オーディオ グラスとディスプレイ グラスです。

  • 軽量のメガネです。

  • 音声、カメラ、ディスプレイを組み合わせて、ハンズフリーで一目でわかるエクスペリエンスを提供できます。

  • 最初の 2 つのカテゴリである XR ヘッドセットと有線 XR グラスは、通常のスマートフォンと同様に Camera2 と CameraX もサポートしています。

  • 本日は、オーディオ グラスとディスプレイ グラスに焦点を当てます。

  • このグラスでは、アプリはグラスではなく、ホストの Android スマートフォンで実行されますが、スマートフォンのカメラとグラスのカメラの両方にアクセスできます。

  • 重要なのは、どのカメラと通信しているかをどのようにして知るかということです。

  • とてもシンプルです。

  • 標準の Context を使用すると、CameraX はスマートフォンのカメラにアクセスします。

  • また、ProjectedDeviceContext を使用して、CameraX がグラスのカメラにアクセスします。

  • そのため、コンテキストが異なると、ターゲット カメラも異なります。

  • カメラとグラスの権限は別々です。

  • スマートフォンとグラスの権限は別々であるため、グラスの権限を個別にリクエストする必要があります。

  • もう 1 つのオプションは、投影された Activity を使用することです。これもホストの Android デバイスで実行されますが、UI はグラスに投影されます。

  • ここでルールが逆になります。

  • 標準のコンテキストを使用している場合は、メガネのカメラにアクセスします。

  • スマートフォンのカメラにアクセスする場合は、HostContext を使用する必要があります。

  • コードを見て、グラスのカメラを使用してキャプチャする方法を確認しましょう。

    // In a coroutine
    val imageCapture = ImageCapture.Builder().build()
    
    ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
        .collectLatest { isConnected ->
            if (isConnected) {
                val projectedContext = ProjectedContext.createProjectedDeviceContext(activity)
                val cameraConfig = CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
                    .setRepeatingStreamForced(false)
                    .build()
                val cameraProvider = LifecycleCameraProvider.createInstance(projectedContext, cameraConfig)
                cameraProvider.bindToLifecycle(activity, DEFAULT_BACK_CAMERA, imageCapture)
            }
        }
    // Shutter button clicked
    imageCapture.takePicture(...)
  • まず、isProjectedDeviceConnected を呼び出して、グラスが接続されているかどうかを確認します。

  • 次に、createProjectedDeviceContext を呼び出して projectedContext を取得します。

  • 2 つ目は、ここでは ProcessCameraProvider ではなく LifecycleCameraProvider を使用して CameraX を初期化している点です。

  • ProcessCameraProvider はシングルトンであるため、初期化後に Context を変更することはできません。

  • LifecycleCameraProvider を使用すると、いつでも Context を変更できます。

  • 次に、CameraX で追加の繰り返しストリームを無効にする必要があります。

  • これは、現在のグラスがシングル ストリームのみをサポートしているために必要です。

  • この API は現在内部向けですが、まもなく一般公開される予定です。

  • ユーザーはリンクをタップし このようなページに移動して参加するだけです

  • ここからは標準の CameraX です。ImageCapture をバインドするだけで、グラスを介して撮影できるようになります。

キャプチャ機能と API

  • 違いますね。

  • 次に、CameraX を使用して、連携する機能の組み合わせを見つけ、デバイスを最大限に活用する方法を見てみましょう。

  • 最新のカメラアプリは、強力な機能によって定義されます。ユーザーは、HDR、60 FPS、プレビューの手ぶれ補正などの機能を同時にサポートすることをカメラに期待しています。

  • しかし、デベロッパーの皆様にとって、これらの機能を組み合わせることは必ずしも簡単ではありません。

  • 個々のサポート機能のサポートを確認しても、それらが連携して動作するとは限りません。

  • この不確実性により、デベロッパーは保守的にならざるを得ず、高性能なデバイスでユーザーが体験できることが制限されていました。

  • この問題を解決するために、Camera2 機能の組み合わせクエリ フレームワークを利用した Feature Group API を導入しました。

  • これを使用する方法は 2 つあります。1 つ目は、requiredFeatureGroup を使用して、サポートされている機能の組み合わせを確認して有効にする方法です。

  • 2 つ目は、preferredFeatureGroup で機能の優先度を指定すると、CameraX が最適なサポート対象の組み合わせを選択します。

  • 以下に例を示します。

  • 右側、失礼、左側は、何も有効にせず、通常のスマートフォンで通常の録画を行っています。

  • 左側では、HDR、60 FPS、プレビューの手ぶれ補正をすべて有効にしています。

  • 明るく、滑らかで、手ぶれのない動画で、品質が大幅に向上していることがわかります。

  • コードを見てみましょう。

    cameraProvider.bindToLifecycle(
        lifecycleOwner,
        cameraSelector,
        SessionConfig(
            useCases = listOf(preview, videoCapture),
            // HDR_HLG10 > FPS_60 > Preview Stabilization.
            preferredFeatureGroup =
               listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
        )
    )
  • ここでは、preferredFeatureGroup を使用して、HDR、60 FPS、プレビューの手ぶれ補正の順に機能の優先度リストを指定しています。

  • CameraX は組み合わせをチェックし、デバイスでサポートされている最適なものを選択します。

  • CameraX 1.6 以降では、4K 録画と動画の手ぶれ補正が Feature Group API に直接追加されています。

    // CameraX 1.6+ supports:
    // 4K, video stabilization & Effect
    val sessionConfig = SessionConfig(
       useCases = listOf(preview, videoCapture),
       requiredFeatureGroup =
         setOf(UHD_RECORDING, VIDEO_STABILIZATION),
       effects = listOf(overlayEffect)
    )
  • 4K と動画手ぶれ補正の組み合わせは、多くのプレミアム デバイスでサポートされています。

  • Feature Group API を使用すると、これらを安心して有効にできます。

  • さらに、以前は不可能だった Feature Group API を使用したリアルタイム効果の適用を柔軟に行えるようになりました。

  • 次に、CameraX で高品質の画像キャプチャを実現する方法について説明します。

  • CameraX で DNG RAW キャプチャがサポートされるようになりました。

  • 未処理、非圧縮の RAW 画像にカメラセンサーから直接アクセスできるため、プロレベルの編集が可能です。

  • 次に、夜間モード拡張機能が改善されました。

  • この拡張機能により、フラッシュを使用せずに、暗い場所でも明るく鮮明な写真を撮影できます。

  • また、新しい Night Mode Indicator API を導入しました。

  • 現在の環境が暗く、夜間モードに適しているかどうかをモニタリングし、夜間モードのインジケーターを表示するために使用できます。

  • また、CameraX 拡張機能でウルトラ HDR がサポートされるようになりました。

  • これにより、夜間モードのダイナミック レンジが広がり、色が鮮やかになります。ウルトラ HDR を使用した夜間モードは、現在、多くの最新のプレミアム デバイスでサポートされています。

  • 通常モード、夜間モード、ウルトラ HDR を使用した夜間モードの違いをここで確認できます。

  • では、これを実装する方法を見てみましょう。

    val cameraInfo = cameraProvider.getCameraInfo(cameraSelector,
            ExtensionSessionConfig(ExtensionMode.NIGHT, extensionsManager))
    
    val imageCapabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)
    
    val imageCapture = ImageCapture.Builder().apply {
           if (imageCapabilities.supportedOutputFormats
                    .contains(OUTPUT_FORMAT_JPEG_ULTRA_HDR)) {
               setOutputFormat(OUTPUT_FORMAT_JPEG_ULTRA_HDR)
           }
         }.build()
    
    val extensionSessionConfig = ExtensionSessionConfig(
        ExtensionMode.NIGHT, extensionsManager, preview, imageCapture
    )
    
    cameraProvider.bindToLifecycle(...,  extensionSessionConfig)
  • まず、夜間モード用の ExtensionSessionConfig を作成し、それを使用して CameraInfo インスタンスを取得します。

  • 次に、CameraInfo を使用して画像キャプチャ機能を取得し、ウルトラ HDR がサポートされているかどうかをクエリします。

  • そして、出力形式をウルトラ HDR に設定します。

  • 最後に、ユースケースを使用して ExtensionSessionConfig を再度作成し、ライフサイクルにバインドします。

  • これで、ウルトラ HDR 写真の夜間モードで撮影する準備が整いました。

  • また、CameraX でモーション フォトがサポートされるようになりました。

  • モーション フォトは、写真の周囲の音と動きをキャプチャし、静止画像だけでなく、その瞬間を保存します。

  • Google は、さまざまなスマートフォンや Android デバイスでこの形式を標準化し、モーション フォトをサポートしやすくしています。

  • モーション フォトは 2027 年第 1 四半期に CameraX に導入される予定です。

  • この機能が利用可能になったら、ImageCapture.Builder でモーション フォト モードを設定するだけで、モーション フォトの撮影を開始できます。

    val imageCapture = ImageCapture.Builder()
            .setMotionPhotoMode(
                 ImageCapture.MOTION_PHOTO_MODE_ON,
                 customConfig
             )
            .build()
    
    cameraProvider.bindToLifecycle(.. imageCapture)
    imageCapture.takePicture(...)
  • 次に、CameraX で高速動画撮影を可能にする方法を見てみましょう。

  • 動きの速いアクションを鮮明に記録することは、最新のカメラアプリに不可欠な機能です。

  • スポーツなど、動きの速いシーンで特に役立ちます。

  • これは、120 fps または 240 fps の高速キャプチャによって実現されます。

  • これには 2 つの方法があります。

  • まず、同じフレームレートでエンコードしてから、真の高フレームレート動画を作成します。

  • もう 1 つの方法は、30 fps でエンコードして、すぐに再生できるスローモーション動画を作成することです。

  • たとえば、120 fps で録画した 1 秒間の動画を 30 fps で再生すると 4 秒かかります。

  • この時間の引き伸ばしによって、スローモーション効果が生まれます。

  • 高速録画を実装するには、videoCapture を使用して HighSpeedVideoSessionConfig を作成します。

    val recorder = Recorder.Builder().build()
    val videoCapture = VideoCapture.withOutput(recorder)
    
    val frameRateRange = cameraInfo.getSupportedFrameRateRanges(
           HighSpeedVideoSessionConfig(videoCapture, preview)).first()
    
    val sessionConfig = HighSpeedVideoSessionConfig(
        videoCapture,
        preview,
        frameRateRange = frameRateRange,
        isSlowMotionEnabled = true
    )
    
    cameraProvider.bindToLifecycle(
         lifecycleOwner, cameraSelector, sessionConfig)
    
    // Start recording slow motion videos.
    val recording = recorder.prepareRecording(context, outputOption)
          .start(executor, {})
  • isSlowMotionEnabled を true に設定すると、30 fps でエンコードしてスローモーション動画を作成できます。false に設定すると、高フレームレートの動画を作成できます。

  • スローモーションが有効になっている場合、Google フォトは自動的にそれを識別し、サムネイルにスローモーション バッジを追加します。

  • Google フォトでの動作を見てみましょう。

  • このスローモーションは、猫にドラマチックな効果を与えています。

  • うちの猫は触られるのを嫌がります。

  • 申し訳ありません。

  • 最後に、同時実行 API である Concurrent Camera API の改善点を見てみましょう。

  • Concurrent API を使用すると、アプリは前面カメラと背面カメラから同時にストリーミングできます。

  • 今年、Concurrent Camera API にいくつかの改善を導入しました。

  • ライフサイクルにバインドしてカメラを開いた後、カメラのコンポジションを動的に更新できます。

  • これには、位置、サイズ、レンダリング順序の変更が含まれます。

  • 角丸、枠線の太さ、枠線の色などのスタイルを追加することもできます。

  • その方法は、CompositionSettings を使用して角の丸み、枠線の太さ、色などのスタイルを設定し、setZOrder を使用してレンダリング順序を調整するだけです。

    val concurrentCamera =
            cameraProvider.bindToLifecycle(list(config1, config2))
    // CameraX 1.7 features: rounded corner, border width & color
    val pipSettings = CompositionSettings.Builder()
        .setOffset(0.2f, 0.2f)
        .setScale(0.35f, 0.35f)
        .setRoundedCornerRatio(0.15f)
        .setBorderWidthRatio(0.02f)
        .setBorderColor(Color.WHITE)
        .setZOrder(1)
        .build()
    // CameraX 1.7 features: dynamical composition update
    concurrentCamera.setCompositionSettings(
    listOf(mainSettings, pipSettings))
  • 次に、アクティブな同時カメラで setCompositionSettings を呼び出して、コンポジションを動的に更新します。

  • ユーザーはリンクをタップし このようなページに移動して参加するだけです

  • コンポジションを動的に更新できます。

  • 違いますね。

  • これで、すべての機能を確認しました。

  • ここで重要な注意点があります。CameraX 1.6 以降にアップグレードすることを強くおすすめします。

  • これにより、新しい形式である Eclipsa Video をサポートする今後のデバイスでのクラッシュを回避し、今後の Google Pixel アップデートでも CameraX 拡張機能が引き続き動作するようにできます。

  • これで最初のセッションは終了です。

  • ここまで、プロレベルのカメラ エクスペリエンスを構築するのに役立つ CameraX の機能について説明してきました。ここからは、オンデバイス AI に関する次のセッションについて Aman に説明してもらいます。

オンデバイスの機械学習

  • Scott、CameraX の機能について説明してくれてありがとう。

  • 次に、オンデバイス機械学習を活用してインテリジェントなカメラ エクスペリエンスを構築する方法を見ていきましょう。

  • オンデバイスの機械学習により、主に 2 つの領域で幅広いカメラのユースケースが実現します。

  • 1 つ目はプリキャプチャです。

  • ここでは、オブジェクト検出などの処理を行うためにカメラのプレビューをリアルタイムで分析したり、プレビュー自体を拡張したりできます。

  • 2 つ目はキャプチャ後です。

  • 画像をキャプチャしたら、画質を向上させたり、画像の説明を生成したりできます。

  • では、カメラアプリ向けのオンデバイス機械学習ソリューションをいくつかご紹介します。

  • 最初に紹介するオンデバイス ML ライブラリは ML Kit です。

  • ML Kit は主に、テキスト認識、顔検出、言語翻訳など、ビジョンと自然言語の領域における一般的な問題を解決するために構築されました。

  • 昨年、ML Kit を導入しました。

  • ML Kit Vision API の例としては、フェイスメッシュ検出、被写体セグメンテーション、リアルタイム オブジェクト検出などがあります。

  • はい。

  • 昨年、Google は GenAI API を導入しました。

  • これらの API は Gemini Nano のパワーを活用して、テキスト要約や画像の説明などの機能を実現します。

  • 以下に、Prompt API を使用した画像の説明の例を示します。

  • テキスト プロンプトを含む画像ビットマップをモデルに渡すと、結果の説明には、サボテンのようなデザインの小さな緑色の Android ロボットが黒い表面に座っていると表示されます。

  • これらはすべてデバイス上で行われます。

  • これはとても便利です

  • Adobe Photoshop は、Prompt API を使用してアプリのユーザー エクスペリエンスを向上させています。

  • アセットを編集する際、ユーザーは Photoshop アプリで複数のレイヤを管理しなければならないことがよくあります。

  • Prompt API を使用することで、アプリは整理されていないレイヤの名前を自動的に変更できるようになりました。

  • このアプリは、Prompt API を使用して画像のキャプションとハッシュタグを生成することもできます。これは、ソーシャル メディアで共有する際に非常に便利です。

  • Prompt API を使用して画像の説明を取得するコードを見てみましょう。

    // 1. Initialize the on-device GenerativeModel client
    val generativeModel = Generation.getClient()
    
    // 2. Check whether Gemini Nano is available on the device
    val status = generativeModel.checkStatus()
    when (status) {
        FeatureStatus.UNAVAILABLE -> {}
        FeatureStatus.DOWNLOADABLE -> {}
        FeatureStatus.DOWNLOADING -> {}
        FeatureStatus.AVAILABLE -> {}
    }
  • まず、generativeModel クライアント オブジェクトが必要です。次に、デバイスで Gemini Nano が利用可能か、ダウンロード可能かを確認します。

  • 次に、画像とテキスト プロンプトを組み合わせてコンテンツ リクエストを生成し、最後にこのリクエストを generateContent 呼び出しに渡して Gemini Nano で推論を実行し、画像の説明を取得します。

    // 3. Build a multimodal request with the image and text prompt
    val request = generateContentRequest(
        ImagePart(bitmap),
        TextPart("Describe this image in detail.")
    ) {}
    
    // 4. Run inference on-device and get the description
    val response = generativeModel.generateContent(request)
    val description = response.candidates.first().text
  • はい。次に紹介するオンデバイス ML ライブラリは MediaPipe です。

  • MediaPipe は、アプリケーション向けの幅広い機械学習ソリューションを提供しています。

  • MediaPipe のウェブサイトにアクセスすると、顔検出、オブジェクト検出、画像セグメンテーションなど、さまざまなサンプルをすぐに試すことができます。

  • これらのユースケースはすべて ML Kit とよく似ていますが、どちらのライブラリを使用すればよいでしょうか。

  • MediaPipe と ML Kit は同様の問題を解決しますが、いくつかの重要な違いがあります。

  • 1 つ目はプラットフォーム サポートです。

  • MediaPipe は Android、iOS、ウェブ、Python で動作しますが、ML Kit は Android と iOS のみに対応しています。

  • 次に、MediaPipe はモデルのカスタマイズを提供しますが、ML Kit はすぐに使用できる API を提供します。

  • 最後に、MediaPipe はオープンソースですが、ML Kit は Google SDK です。

  • 以下に、MediaPipe Vision API を使用したジェスチャー認識の例を示します。

  • この API は、カメラ入力からリアルタイムでハンド ジェスチャーを検出します。

  • たとえば、スマートフォンから離れた場所に立っているときに、簡単なハンド ジェスチャーでカメラの録画を開始または停止できます。

  • コードを見てみましょう。

  • まず、モデルパス、ライブ ストリーミング モード、結果リスナーを指定して、GestureRecognizerOptions オブジェクトをビルドする必要があります。

    // 1. Build GestureRecognizerOptions
    val options = GestureRecognizerOptions.builder().setBaseOptions(
        BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK).build())
        .setRunningMode(RunningMode.LIVE_STREAM)
        .setResultListener { result, image -> /* Handle detected gesture */ }
        .build()
    
    // 2. Create the GestureRecognizer
    val gestureRecognizer = GestureRecognizer.createFromOptions(context, options)
    
    // 3. Convert CameraX ImageProxy to MediaPipe MPImage
    val bitmap = rotateBitmap(imageProxy.toBitmap(),
    imageProxy.imageInfo.rotationDegrees)
    val mpImage = BitmapImageBuilder(bitmap).build()
    
    // 4. Run gesture recognition on the image
    gestureRecognizer.recognizeAsync(mpImage, frameTime)
  • 次に、オプションからジェスチャー認識ツール オブジェクトを作成します。

  • 次に、CameraX ImageProxy を取得してビットマップに変換し、そのビットマップを使用して MediaPipe mpImage オブジェクトを生成します。

  • 最後に、MediaPipe mpImage オブジェクトを recognizeAsync 呼び出しに渡して、ジェスチャーをリアルタイムで検出する必要があります。

画像と動画の拡張

  • 次に、Google Play 開発者サービス API を介して利用できる、デバイス上の画像と動画の補正機能について説明します。

  • このモデルは、ほとんどのプレミアム デバイスでトーン マッピング、ブレ軽減、高画質化などの機能を提供します。

  • このモデルを使用してトーン マッピングとアップスケールを行った画像の例を次に示します。

  • 補正された画像では、空と海の色彩がより豊かになり、シャープネスが向上していることがはっきりとわかります。

  • 画像拡張のコードを見てみましょう。

    // Step 1: Verify device support & install module
    val enhancementClient = Enhancement.getClient(context)
    
    suspend fun prepare(): Boolean {
        if (!enhancementClient.isDeviceSupported().await()) return false
        if (!enhancementClient.isModuleInstalled().await()) {
            check(enhancementClient.installModule().await()) {
                "Enhancement module install failed"
            }
        }
        return true
    }
  • まず、基盤となるデバイスがサポートしているかどうかをクエリする必要があります。必要に応じて、デバイスに拡張モジュールをインストールする必要があります。

  • 次に、ビットマップと、有効にする補正を指定して、EnhancementOptions オブジェクトを作成します。

    // Step 2: Select Enhancement options & create session
    val options = EnhancementOptions(
        width = bitmap.width,
        height = bitmap.height,
        enhancementMode = EnhancementMode.BITMAP,
        isTonemappingEnabled = true,
        isDeblurAndDenoisePhotoEnabled = true,
        isDeblurAndDenoiseVideoEnabled = false,
        isUpscalePhotoEnabled = false,
        isUpscaleVideoEnabled = false
    )
    
    val enhancementSession = enhancementClient
        .createSessionAsync(options, enhancementExecutor)
  • 次に、Google Play 開発者サービス API を使用してクライアント セッションを作成し、結果のビットマップを処理するコールバックを定義します。最後に、ビットマップと画像を補正するオプションを指定して process 関数を呼び出します。

    // Step 3: Process Enhancement
    val enhancedBitmap = suspendCancellableCoroutine { continuation ->
        val enhancementCallback = object : EnhancementCallback {
            override fun onBitmapProcessed(result: Bitmap) {
                if (continuation.isActive) continuation.resume(result)
            }
            override fun onError(statusCode: Int) {
                if (continuation.isActive) {
                    continuation.resumeWithException(
                        Exception("Bitmap processing failed: $statusCode")
                    )
                }
            }
            override fun onSurfaceProcessed(timestamp: Long) {}
        }
    
        enhancementSession.process(bitmap, options, enhancementCallback)
    }
  • 最近、動画拡張 API が Jetpack Media3 に統合されました。

  • Image and Video Enhancement API について詳しくは、メディアに関するトークをご覧ください。

  • 最後にご紹介するオンデバイス ML 機能は、ローライト ブーストです。

  • 暗い場所での撮影は、スマートフォン ユーザーにとってよくある問題です。

  • たとえば、薄暗い環境で写真を撮影したり、ビデオ通話を行ったりする場合です。

  • Low Light Boost は、カメラのプレビューをリアルタイムで明るくすることで、この問題を解決します。

  • ローライト ブーストには、ハードウェアとソフトウェアの 2 つのバリエーションがあります。

  • ハードウェア バージョンは、OEM ハードウェアを使用して、機械学習を使用せずに暗いフレームを補正します。

  • 一方、ソフトウェア バージョンでは、オンデバイス ML モデルを使用してフレームを補正します。

  • CameraX をソフトウェアの Low Light Boost(Google Low Light Boost とも呼ばれます)と統合する方法を簡単に見てみましょう。

  • Google Low Light Boost には、2 つの主要なサーフェスが必要です。まず、未加工のカメラフレーム用の入力サーフェスと、強化された出力フレーム用の出力ターゲット サーフェスが必要です。

  • CameraX と統合するには、Google の低照度ブースト パイプラインを CameraEffect 内にラップするだけです。

  • CameraX は、サーフェスのライフサイクルとルーティングを自動的かつシームレスに処理します。

  • 最後に、2026 年第 4 四半期に CameraX で低照度ブースト効果をリリースします。

    // Integrates SW Low Light Boost
    val lowLightBoostEffect =
        LowLightBoostEffect(
            CameraEffect.PREVIEW or
            CameraEffect.VIDEO_CAPTURE or
            CameraEffect.IMAGE_CAPTURE
        )
    
    val sessionConfig = SessionConfig(
        useCases = listOf(preview, imageCapture),
        effects = lowLightBoostEffect
    )
    
    cameraProvider.bindToLifecycle(.. sessionConfig)
  • これにより、ローライト ブースト オブジェクトを作成してセッション構成に追加し、セッション構成をカメラのライフサイクルにバインドするだけで、サポートされているデバイスで動作するようになります。

概要と移行

  • これで、両方のセクションが終了しました。

  • 本日の内容を簡単にまとめましょう。

  • 本日は、CameraX とオンデバイスの機械学習(ML)を使用して、デバイス間でカメラ エクスペリエンスを向上させる方法について説明しました。

  • まず、CameraXViewfinder を使用すると、さまざまなフォーム ファクタに適応するようにカメラ プレビューを有効にできます。

  • 次に、CameraX を使用すると XR デバイスでも撮影できることを確認しました。

  • 次に、Feature Group API を使用して、ハードウェア機能の最適な組み合わせを見つけます。

  • また、画像キャプチャのアップグレードと、スローモーション動画用の高速動画撮影についても学びました。

  • 最後に、同時カメラのプレビューを動的に強化する方法を学びました。

  • 次に、インテリジェントなビジョン エクスペリエンスを構築するための ML Kit や MediaPipe などのオンデバイス機械学習ソリューションについて説明しました。

  • 最後に、デバイス上の画像と動画拡張機能、およびカメラのプレビューをリアルタイムで明るくするローライト ブーストについて説明しました。

  • 前述のとおり、Camera1 は無効化される予定であるため、CameraX への移行を強くおすすめします。

  • この移行をサポートするため、最近 CameraX スキルをリリースしました。

  • また、移行ガイドとカメラのサンプル リポジトリも用意されています。

  • こちらのリンクから、本日ご紹介した機能に関するその他のリソースにアクセスできます。

  • ドキュメント、ブログ、移行リソースへのリンクがすべて 1 か所にまとめられています。

  • 少し時間を取って、一時停止します。

  • 違いますね。

  • 皆様 ありがとうございました

  • これで、このセッションは終了です。

コード

コード スニペット

「アダプティブ ビューファインダーの機能」の 4 分 18 秒の Kotlin スニペット
var surfaceRequest by remember {
    mutableStateOf<SurfaceRequest?>(null)
}

LaunchedEffect(...) {
    preview.setSurfaceProvider { surfaceRequest = it }
    cameraProvider.bindToLifecycle(..., preview)
}

surfaceRequest?.let { request ->
    CameraXViewfinder(
        surfaceRequest = request,
        contentScale = ContentScale.Fit
    )
}
「アダプティブ ビューファインダーの機能」の 5 分 14 秒の Kotlin スニペット
CameraXViewfinder(
    surfaceRequest = viewModel.surfaceRequest,
    contentScale = ContentScale.Fit,
    isTapToFocusEnabled = true,
    isPinchToZoomEnabled = true,
    onScreenFlashReady =
        {
           imageCapture.screenFlash = it
           imageCapture.flashMode = FLASH_MODE_SCREEN
        }
)
「Android XR でのカメラ キャプチャ」の 7 分 30 秒の Kotlin スニペット
// In a coroutine
val imageCapture = ImageCapture.Builder().build()

ProjectedContext.isProjectedDeviceConnected(activity, coroutineContext)
    .collectLatest { isConnected ->
        if (isConnected) {
            val projectedContext = ProjectedContext.createProjectedDeviceContext(activity)
            val cameraConfig = CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
                .setRepeatingStreamForced(false)
                .build()
            val cameraProvider = LifecycleCameraProvider.createInstance(projectedContext, cameraConfig)
            cameraProvider.bindToLifecycle(activity, DEFAULT_BACK_CAMERA, imageCapture)
        }
    }
// Shutter button clicked
imageCapture.takePicture(...)
「キャプチャ機能と API」の 09:42 の Kotlin スニペット
cameraProvider.bindToLifecycle(
    lifecycleOwner,
    cameraSelector,
    SessionConfig(
        useCases = listOf(preview, videoCapture),
        // HDR_HLG10 > FPS_60 > Preview Stabilization.
        preferredFeatureGroup =
           listOf(HDR_HLG10, FPS_60, PREVIEW_STABILIZATION),
    )
)
「キャプチャ機能と API」の 09:58 の Kotlin スニペット
// CameraX 1.6+ supports:
// 4K, video stabilization & Effect
val sessionConfig = SessionConfig(
   useCases = listOf(preview, videoCapture),
   requiredFeatureGroup =
     setOf(UHD_RECORDING, VIDEO_STABILIZATION),
   effects = listOf(overlayEffect)
)
「キャプチャ機能と API」の 11:20 の Kotlin スニペット
val cameraInfo = cameraProvider.getCameraInfo(cameraSelector,
        ExtensionSessionConfig(ExtensionMode.NIGHT, extensionsManager))

val imageCapabilities = ImageCapture.getImageCaptureCapabilities(cameraInfo)

val imageCapture = ImageCapture.Builder().apply {
       if (imageCapabilities.supportedOutputFormats
                .contains(OUTPUT_FORMAT_JPEG_ULTRA_HDR)) {
           setOutputFormat(OUTPUT_FORMAT_JPEG_ULTRA_HDR)
       }
     }.build()

val extensionSessionConfig = ExtensionSessionConfig(
    ExtensionMode.NIGHT, extensionsManager, preview, imageCapture
)

cameraProvider.bindToLifecycle(...,  extensionSessionConfig)
「キャプチャ機能と API」の 12:11 の Kotlin スニペット
val imageCapture = ImageCapture.Builder()
        .setMotionPhotoMode(
             ImageCapture.MOTION_PHOTO_MODE_ON,
             customConfig
         )
        .build()

cameraProvider.bindToLifecycle(.. imageCapture)
imageCapture.takePicture(...)
「キャプチャ機能と API」の 13:06 の Kotlin スニペット
val recorder = Recorder.Builder().build()
val videoCapture = VideoCapture.withOutput(recorder)

val frameRateRange = cameraInfo.getSupportedFrameRateRanges(
       HighSpeedVideoSessionConfig(videoCapture, preview)).first()

val sessionConfig = HighSpeedVideoSessionConfig(
    videoCapture,
    preview,
    frameRateRange = frameRateRange,
    isSlowMotionEnabled = true
)

cameraProvider.bindToLifecycle(
     lifecycleOwner, cameraSelector, sessionConfig)

// Start recording slow motion videos.
val recording = recorder.prepareRecording(context, outputOption)
      .start(executor, {})
「キャプチャ機能と API」の 14:18 の Kotlin スニペット
val concurrentCamera =
        cameraProvider.bindToLifecycle(list(config1, config2))
// CameraX 1.7 features: rounded corner, border width & color
val pipSettings = CompositionSettings.Builder()
    .setOffset(0.2f, 0.2f)
    .setScale(0.35f, 0.35f)
    .setRoundedCornerRatio(0.15f)
    .setBorderWidthRatio(0.02f)
    .setBorderColor(Color.WHITE)
    .setZOrder(1)
    .build()
// CameraX 1.7 features: dynamical composition update
concurrentCamera.setCompositionSettings(
listOf(mainSettings, pipSettings))
「デバイス上の ML」の 17:51 の Kotlin スニペット
// 1. Initialize the on-device GenerativeModel client
val generativeModel = Generation.getClient()

// 2. Check whether Gemini Nano is available on the device
val status = generativeModel.checkStatus()
when (status) {
    FeatureStatus.UNAVAILABLE -> {}
    FeatureStatus.DOWNLOADABLE -> {}
    FeatureStatus.DOWNLOADING -> {}
    FeatureStatus.AVAILABLE -> {}
}
「オンデバイス機械学習」の 18:06 の Kotlin スニペット
// 3. Build a multimodal request with the image and text prompt
val request = generateContentRequest(
    ImagePart(bitmap),
    TextPart("Describe this image in detail.")
) {}

// 4. Run inference on-device and get the description
val response = generativeModel.generateContent(request)
val description = response.candidates.first().text
「オンデバイスの ML」の 19:38 にある Kotlin スニペット
// 1. Build GestureRecognizerOptions
val options = GestureRecognizerOptions.builder().setBaseOptions(
    BaseOptions.builder().setModelAssetPath(MP_RECOGNIZER_TASK).build())
    .setRunningMode(RunningMode.LIVE_STREAM)
    .setResultListener { result, image -> /* Handle detected gesture */ }
    .build()

// 2. Create the GestureRecognizer
val gestureRecognizer = GestureRecognizer.createFromOptions(context, options)

// 3. Convert CameraX ImageProxy to MediaPipe MPImage
val bitmap = rotateBitmap(imageProxy.toBitmap(),
imageProxy.imageInfo.rotationDegrees)
val mpImage = BitmapImageBuilder(bitmap).build()

// 4. Run gesture recognition on the image
gestureRecognizer.recognizeAsync(mpImage, frameTime)
「画像と動画の拡張」の 20:44 の Kotlin スニペット
// Step 1: Verify device support & install module
val enhancementClient = Enhancement.getClient(context)

suspend fun prepare(): Boolean {
    if (!enhancementClient.isDeviceSupported().await()) return false
    if (!enhancementClient.isModuleInstalled().await()) {
        check(enhancementClient.installModule().await()) {
            "Enhancement module install failed"
        }
    }
    return true
}
「画像と動画拡張」の 20:57 の Kotlin スニペット
// Step 2: Select Enhancement options & create session
val options = EnhancementOptions(
    width = bitmap.width,
    height = bitmap.height,
    enhancementMode = EnhancementMode.BITMAP,
    isTonemappingEnabled = true,
    isDeblurAndDenoisePhotoEnabled = true,
    isDeblurAndDenoiseVideoEnabled = false,
    isUpscalePhotoEnabled = false,
    isUpscaleVideoEnabled = false
)

val enhancementSession = enhancementClient
    .createSessionAsync(options, enhancementExecutor)
「画像と動画の拡張」の 21:05 の Kotlin スニペット
// Step 3: Process Enhancement
val enhancedBitmap = suspendCancellableCoroutine { continuation ->
    val enhancementCallback = object : EnhancementCallback {
        override fun onBitmapProcessed(result: Bitmap) {
            if (continuation.isActive) continuation.resume(result)
        }
        override fun onError(statusCode: Int) {
            if (continuation.isActive) {
                continuation.resumeWithException(
                    Exception("Bitmap processing failed: $statusCode")
                )
            }
        }
        override fun onSurfaceProcessed(timestamp: Long) {}
    }

    enhancementSession.process(bitmap, options, enhancementCallback)
}
「画像と動画拡張」の 22:51 の Kotlin スニペット
// Integrates SW Low Light Boost
val lowLightBoostEffect =
    LowLightBoostEffect(
        CameraEffect.PREVIEW or
        CameraEffect.VIDEO_CAPTURE or
        CameraEffect.IMAGE_CAPTURE
    )

val sessionConfig = SessionConfig(
    useCases = listOf(preview, imageCapture),
    effects = lowLightBoostEffect
)

cameraProvider.bindToLifecycle(.. sessionConfig)