プロダクト ニュース

Low Light Boost でリアルタイムのカメラフィードを明るくする

所要時間: 7 分
Donovan McMurray
デベロッパー リレーション エンジニア

 先日、Instagram で夜間モードを使用して暗い場所でも美しい写真を撮影できるようになったことをご紹介しました。この機能は、複数の露出を組み合わせて高品質の静止画を作成する時間がある静止画に最適です。では、写真と写真の間の瞬間はどうでしょうか? ユーザーは、シャッター ボタンを押す瞬間だけでなく、カメラを操作する必要があります。また、プレビューを使用してシーンを構成したり、QR コードをスキャンしたりします。

今回は、リアルタイムのカメラ ストリームを明るくする強力な機能であるLow Light Boost(LLB)について詳しく説明します。静止画のキャプチャ時間を必要とする夜間モードとは異なり、Low Light Boost はライブ プレビューと動画録画で瞬時に動作します。LLB は、利用可能な光に基づいて必要な明るさ調整を自動的に行うため、あらゆる環境に最適化されています。

最近のアップデートにより、LLB を使用して Instagram ユーザーは完璧なショットを撮影できるようになりました。既存の夜間モードの実装により、1 年以上ユーザーが楽しんでいるのと同じ高品質の低照度写真が撮影できます。

リアルタイムの明るさが重要な理由

夜間モードは最終的な画質の向上を目的としていますが、Low Light Boost は暗い環境での使いやすさとインタラクティビティを目的としています。もう 1 つ考慮すべき重要な要素は、LLB と夜間モードは非常にうまく連携しますが、個別に使用できることです。これらのユースケースでは、夜間モードの写真が不要な場合でも、LLB には独自の価値があります。LLB がユーザー エクスペリエンスをどのように改善するかを以下に示します。

  • フレーミングとキャプチャの改善: 薄暗いシーンでは、標準のカメラ プレビューが真っ黒になることがあります。LLB はビューファインダーを明るくするため、ユーザーはシャッター ボタンを押す前にフレーミングを確認できます。このエクスペリエンスでは、夜間モードを使用して最高の画質の低照度写真を得ることも、LLB を使用して「見たまま」の写真を得ることもできます。
  • 信頼性の高いスキャン: QR コードはどこにでもありますが、暗いレストランや屋内駐車場でスキャンするのは難しいことがよくあります。カメラフィードが大幅に明るくなると、スキャン アルゴリズムは非常に暗い環境でも QR コードを確実に検出してデコードできます。
  • インタラクションの強化: ライブ動画インタラクション(AI アシスタントやビデオ通話など)を含むアプリの場合、LLB は認識可能な情報の量を増やし、コンピュータ ビジョン モデルが十分なデータを処理できるようにします。

Instagram の違い

LLB_IG_demo_white_background.gif

Android 版 Instagram アプリのエンジニアリング チームは、ユーザーに最先端のカメラ エクスペリエンスを提供するために常に尽力しています。上の例では、LLB が Google Pixel 10 Pro にどのような違いをもたらすかを確認できます。

lowlight.png

これにより、ユーザー エクスペリエンスがどのように変わるか簡単に想像できます。ユーザーがキャプチャしている内容を確認できない場合、キャプチャを中止する可能性が高くなります。

lowlight1.png

実装の選択

Low Light Boost を実装して、幅広いデバイスで最高のユーザー エクスペリエンスを提供する方法は 2 つあります。

  1. Low Light Boost AE モード: これはハードウェア レイヤの自動露出モードです。画像信号処理装置(ISP)パイプラインを直接調整するため、最高の品質とパフォーマンスを実現します。最初に必ず確認してください。
  2. Google Low Light Boost: デバイスが AE モードをサポートしていない場合は、Google Play 開発者サービスから提供されるこのソフトウェア ベースのソリューションにフォールバックできます。カメラ ストリームに後処理を適用して明るくします。オールソフトウェア ソリューションとして、より多くのデバイスで利用できるため、この実装により、LLB を搭載したより多くのデバイスにリーチできます。

Low Light Boost AE モード(ハードウェア)

メカニズム:
このモードは Android 15 以降を搭載したデバイスでサポートされており、OEM が HAL にサポートを実装している必要があります(現在は Google Pixel 10 デバイスで利用可能)。カメラの画像信号処理装置(ISP)と直接統合されます。CaptureRequest.CONTROL_AE_MODECameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY に設定すると、カメラ システムが制御します。

動作:
HAL/ISP はシーンを分析し、センサーと処理パラメータを調整します。多くの場合、露出時間を長くして画像を明るくします。デジタル センサー ゲイン(ISO)を増やすのではなく、露出時間を長くすることで、センサーがより多くの光情報をキャプチャできるため、信号対雑音比(SNR)が大幅に向上したフレームが得られます。

メリット:
専用のハードウェア パスを利用するため、画質と電力効率が向上する可能性があります。

トレードオフ:
センサーが光をキャプチャするのに時間がかかるため、非常に暗い場所ではフレームレートが低下する可能性があります。非常に暗い場所では、フレームレートが 10 FPS まで低下することがあります。

Google Low Light Boost(Google Play 開発者サービス経由のソフトウェア)

メカニズム:
このソリューションは、Google Play 開発者サービスを介してオプション モジュールとして配布され、カメラ ストリームに後処理を適用します。HDRNet と呼ばれる高度なリアルタイム画像補正技術を使用します。

Google HDRNet:
このディープ ラーニング モデルは、低解像度で画像を分析して、コンパクトなパラメータ セット(バイラテラル グリッド)を予測します。このグリッドは、GPU 上でフル解像度画像の効率的で空間的に変化する補正をガイドします。このモデルは、顔の視認性に重点を置いて、暗い場所での画質を明るくして向上させるようにトレーニングされています。

プロセス オーケストレーション:
HDRNet モデルとその付随するロジックは、Low Light Boost プロセッサによってオーケストレーションされます。以下が該当します。

  1. シーン分析:
    カメラのメタデータ(センサー感度、露出時間など)と画像コンテンツを使用して、実際のシーンの明るさを推定するカスタム計算ツール。この分析により、ブーストレベルが決まります。
  2. HDRNet 処理:
    HDRNet モデルを適用してフレームを明るくします。使用されるモデルは、暗いシーンに合わせて調整され、リアルタイム パフォーマンス向けに最適化されています。
  3. ブレンド:
    元のフレームと HDRNet で処理されたフレームがブレンドされます。適用されるブレンドの量は、シーンの明るさ計算ツールによって動的に制御され、ブーストされた状態とブーストされていない状態の間でスムーズに移行できます。
low-light-boost-processor-diagram.png

メリット:
特定の HAL サポートを必要とせずに、幅広いデバイス(現在は Samsung S22 Ultra、S23 Ultra、S24 Ultra、S25 Ultra、Google Pixel 6 ~ Google Pixel 9 をサポート)で動作します。後処理効果であるため、カメラのフレームレートを維持します。

トレードオフ:
後処理方法であるため、品質はセンサーから配信されるフレーム内の情報によって制限されます。センサーレベルで極端な暗さのために失われた詳細を復元することはできません。

Low Light Boost は、ハードウェアとソフトウェアの両方のパスを提供することで、Android エコシステム全体で低照度カメラのパフォーマンスを向上させるスケーラブルなソリューションを提供します。デベロッパーは、利用可能な場合は AE モードを優先し、Google Low Light Boost を堅牢なフォールバックとして使用する必要があります。

アプリに Low Light Boost を実装する

次に、LLB の両方のサービスを実装する方法について説明します。アプリで CameraX と Camera2 のどちらを使用する場合でも、次のことを実装できます。最適な結果を得るには、ステップ 1 とステップ 2 の両方を実装することをおすすめします。

ステップ 1: Low Light Boost AE モード

Android 15 以降を搭載した一部のデバイスで利用できる LLB AE モードは、特定の自動露出(AE)モードとして機能します。

1. 利用可能かどうかを確認する

まず、カメラ デバイスが LLB AE モードをサポートしているかどうかを確認します。

  val cameraInfo = cameraProvider.getCameraInfo(cameraSelector)
val isLlbSupported = cameraInfo.isLowLightBoostSupported

2. モードを有効にする

サポートされている場合は、CameraX の CameraControl オブジェクトを使用して LLB AE モードを有効にできます。

  // After setting up your camera, use the CameraInfo object to enable LLB AE Mode.
camera = cameraProvider.bindToLifecycle(...)

if (isLlbSupported) {
  try {
    // The .await() extension suspends the coroutine until the
    // ListenableFuture completes. If the operation fails, it throws
    // an exception which we catch below.
    camera?.cameraControl.enableLowLightBoostAsync(true).await()
  } catch (e: IllegalStateException) {
    Log.e(TAG, "Failed to enable low light boost: not available on this device or with the current camera configuration", e)
  } catch (e: CameraControl.OperationCanceledException) {
    Log.e(TAG, "Failed to enable low light boost: camera is closed or value has changed", e)
  }
}

3. 状態をモニタリングする

モードをリクエストしたからといって、現在「ブースト」されているとは限りません。システムは、シーンが実際に暗い場合にのみブーストを有効にします。Observer を設定して UI を更新(月のアイコンを表示するなど)するか、拡張関数 asFlow() を使用して Flow に変換できます。

  if (isLlbSupported) {
  camera?.cameraInfo.lowLightBoostState.asFlow().collectLatest { state ->
    // Update UI accordingly
    updateMoonIcon(state == LowLightBoostState.ACTIVE)
  }
}

 Low Light Boost AE モードの詳細なガイドはこちらをご覧ください

ステップ 2: Google Low Light Boost

ハードウェア AE モードをサポートしていないデバイスの場合、Google Low Light Boost は強力なフォールバックとして機能します。LowLightBoostSession を使用してストリームをインターセプトし、明るくします。

1. 依存関係を追加する

この機能は Google Play 開発者サービスを介して提供されます。

  implementation("com.google.android.gms:play-services-camera-low-light-boost:16.0.1-beta06")
// Add coroutines-play-services to simplify Task APIs
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.10.2")

2. クライアントの初期化

カメラを起動する前に、LowLightBoostClient を使用して、モジュールがインストールされ、デバイスがサポートされていることを確認します。

  val llbClient = LowLightBoost.getClient(context)

// Check support and install if necessary
val isSupported = llbClient.isCameraSupported(cameraId).await()
val isInstalled = llbClient.isModuleInstalled().await()

if (isSupported && !isInstalled) {
    // Trigger installation
    llbClient.installModule(installCallback).await()
}

3. LLB セッションを作成する

Google LLB は各フレームを処理するため、ディスプレイ Surface を LowLightBoostSession に渡す必要があります。これにより、明るくした Surface が返されます。Camera2 アプリの場合は、CaptureRequest.Builder.addTarget() を使用して、結果の Surface を追加できます。CameraX の場合、この処理パイプラインは CameraEffect クラスに最適です。このクラスでは、SurfaceProcessor を使用してエフェクトを適用し、このコードに示すように SurfaceProvider を使用して Preview に戻すことができます。

  // With a SurfaceOutput from SurfaceProcessor.onSurfaceOutput() and a
// SurfaceRequest from Preview.SurfaceProvider.onSurfaceRequested(),
// create a LLB Session.
suspend fun createLlbSession(surfaceRequest: SurfaceRequest, outputSurfaceForLlb: Surface) {
  // 1. Create the LLB Session configuration
  val options = LowLightBoostOptions(
    outputSurfaceForLlb,
    cameraId,
    surfaceRequest.resolution.width,
    surfaceRequest.resolution.height,
    true // Start enabled
  )

  // 2. Create the session.
  val llbSession = llbClient.createSession(options, callback).await()

  // 3. Get the surface to use.
  val llbInputSurface = llbSession.getCameraSurface()

  // 4. Provide the surface to the CameraX Preview UseCase.
  surfaceRequest.provideSurface(llbInputSurface, executor, resultListener)

  // 5. Set the scene detector callback to monitor how much boost is being applied.
  val onSceneBrightnessChanged = object : SceneDetectorCallback {
    override fun onSceneBrightnessChanged(
      session: LowLightBoostSession,
      boostStrength: Float
    ) {
      // Monitor the boostStrength from 0 (no boosting) to 1 (maximum boosting)
    }
  }
  llbSession.setSceneDetectorCallback(onSceneBrightnessChanged, null)
}

4. メタデータを渡す

アルゴリズムが機能するには、カメラの自動露出状態を分析する必要があります。キャプチャ結果を LLB セッションに渡す必要があります。CameraX では、Preview.Builder を Camera2Interop.Extender.setSessionCaptureCallback() で拡張することで、これを行うことができます。

  Camera2Interop.Extender(previewBuilder).setSessionCaptureCallback(
  object : CameraCaptureSession.CaptureCallback() {
    override fun onCaptureCompleted(
      session: CameraCaptureSession,
      request: CaptureRequest,
      result: TotalCaptureResult
    ) {
      super.onCaptureCompleted(session, request, result)
      llbSession?.processCaptureResult(result)
    }
  }
)

クライアントとセッションの詳細な実装手順については、 Google Low Light Boost ガイドをご覧ください。

次のステップ

これらの 2 つのオプションを実装することで、照明条件に関係なく、ユーザーがはっきりと見ることができ、確実にスキャンして、効果的に操作できるようになります。

完全な本番環境対応のコードベースでこれらの機能の動作を確認するには、GitHub の Jetpack カメラアプリ をご覧ください。LLB AE モードGoogle LLBの両方を実装しているため、独自の統合の参考になります。

作成者:

続きを読む