カメラ プレビュー

注: このページでは、Camera2 パッケージについて説明します。アプリで Camera2 の特定の低レベルの機能を必要とする場合を除き、CameraX を使用することをおすすめします。CameraX と Camera2 は、どちらも Android 5.0(API レベル 21)以降に対応しています。

Android でカメラとカメラ プレビューの向きが常に同じではない できます。

カメラは、デバイスがスマートフォン、タブレット、パソコンのいずれであるかにかかわらず、デバイス上の固定位置にあります。デバイスの向きが変わると、 カメラの向きが変わります。

そのため、カメラアプリは通常、カメラアプリ デバイスの向きとカメラ プレビューのアスペクト比。特定の スマートフォンが縦向きになっているため、カメラ プレビューは縦長になる 幅を狭めると長くなりますスマートフォン(とカメラ)を横向きにすると、カメラ プレビューは縦よりも横が長くなります。

しかし、折りたたみ式デバイスなどの新しいフォーム ファクタや、マルチウィンドウマルチディスプレイなどの表示モードでは、これらの前提が当てはまりません。折りたたみ式デバイスでは、向きを変更せずにディスプレイのサイズとアスペクト比を変更できます。マルチウィンドウ モードでは、カメラアプリは画面の一部に制限され、デバイスの向きに関係なくカメラ プレビューがスケーリングされます。マルチディスプレイ モードでは、セカンダリ ディスプレイを使用できますが、 プライマリ ディスプレイと同じ向きにする必要があります。

カメラの向き

Android 互換性定義では、カメラのイメージ センサーの向きは、カメラの長辺と画面の長辺が平行となる向きにしなければならないと規定されています。つまり、デバイスが横向きで保持されている場合、カメラは横向きで画像をキャプチャしなければなりません。これは、デバイスの自然な状態にかかわらず、 向き:つまり、横向き主体のデバイスだけでなく、 自動的に選択されます。

カメラから画面への配置により、カメラの表示領域が最大化される ビューファインダーの画面ですまた、イメージ センサーは通常、 アスペクト比は 4:3 が一般的です

スマートフォンとカメラセンサーの両方が縦向きになっている。
図 1. スマートフォンとカメラセンサーの一般的な関係 方向です。

カメラセンサーの自然な向きは横向きです。図 1 のセンサーは、 カメラは前面カメラと同じ方向を向いているため、 がスマートフォンに対して 270 度回転している Android 互換性定義。

センサーの回転をアプリに公開するため、camera2 API には SENSOR_ORIENTATION 定数が含まれています。ほとんどのスマートフォンとタブレットでは、デバイスは前面カメラのセンサーの向きを 270 度、背面カメラのセンサーの向きを 90 度(デバイスの背面からの視点)と報告します。これにより、センサーの長辺がデバイスの長辺と揃います。ノートパソコンのカメラは通常、 0 度または 180 度に設定します。

カメラのイメージ センサーは、データ(イメージ バッファ)を センサーの自然な向き(横向き)では、画像バッファを カメラ プレビューの角度(SENSOR_ORIENTATION で指定された角度) デバイスの自然な向きで縦向きに表示できます。前面カメラの場合は 回転は反時計回りです。背面カメラの場合は時計回りで設定できます

たとえば、図 1 の前面カメラの場合、画像バッファは 次のような画像が表示されます。

カメラセンサーが回転して横向きに画像が表示される
            横、左上です。

プレビューの向きがデバイスの向きと一致するように、画像を反時計回りに 270 度回転する必要があります。

カメラセンサーが縦向き、画像が縦向きになっている。

背面カメラでは、同じ向きの画像バッファが生成されます。 上記のバッファと同じですが、SENSOR_ORIENTATION は 90 度です。その結果、バッファは時計回りに 90 度回転します。

デバイスの回転

デバイスの回転は、デバイスが自然な向きから回転した度数です。たとえば、横向きのスマートフォンには、 方向に応じて 90 度または 270 度回転できます。

カメラセンサーのイメージ バッファは、カメラセンサーのイメージ バッファを 回転(センサーの向きに加えて)が回転するように カメラ プレビューが上向きに表示されます。

向きの計算

カメラ プレビューの適切な向きは、センサーの向きとデバイスの回転を考慮して決定されます。

センサー イメージ バッファの全体的な回転は、 式:

rotation = (sensorOrientationDegrees - deviceOrientationDegrees * sign + 360) % 360

ここで、sign は、前面カメラの場合は 1、背面カメラの場合は -1 です。

前面カメラの場合、画像バッファは反時計回りに回転します( センサーの自然な向き)が調整されます。背面カメラの場合、センサー画像バッファは時計回りに回転します。

deviceOrientationDegrees * sign + 360 は、背面カメラのデバイスの回転を反時計回りから時計回りに変換します(たとえば、反時計回りの 270 度を時計回りの 90 度に変換します)。モジュロ演算は、結果を 360 度未満にスケーリングします(たとえば、540 度の回転を 180 度にスケーリングします)。

API によってデバイスの回転の報告方法は異なります。

  • Display#getRotation() は、デバイスを反時計回りに回転します(ユーザーの視点から)。この値は、上記の式にそのまま挿入します。
  • OrientationEventListener#onOrientationChanged() は、ユーザーの視点から見たデバイスの時計回りの回転を返します。 上記の式で使用する値を否定します。

前面カメラ

カメラ プレビューとセンサーの両方が横向きで、センサーが正しい向きになっている。
図 2. スマートフォンのカメラ プレビューとセンサーを 90 度回転させて 横向き。

図 2 のカメラセンサーによって生成された画像バッファは次のとおりです。

横向きのカメラセンサーで、画像が縦向きになっている。

センサーの向きに合わせて調整するには、バッファを反時計回りに 270 度回転する必要があります(上記のカメラの向きを参照)。

カメラセンサーが回転し、画像が横向きである縦向きになる。
            選択します。

次に、デバイスの回転を考慮してバッファを反時計回りに 90 度回転させます。これにより、図 2 のようにカメラ プレビューの向きが正しくなります。

カメラセンサーが回転して横向きに画像が表示される
            まっすぐにする

ここでは、カメラを右を横向きにしています。

横向きのカメラ プレビューとセンサーの両方があるが、
            センサーが上下が逆になっています。
図 3. スマートフォンを 270 度回転させた状態でのカメラ プレビューとセンサー (または -90 度)を横向きに変更します。

画像バッファは次のとおりです。

カメラセンサーが横向きに回転し、画像が上下逆さまになっている。

センサーを調整するには、バッファを反時計回りに 270 度回転する必要があります 向き:

カメラセンサーは縦向きに評価され、画像が横向き、
            左上に表示されます

次に、バッファを反時計回りにさらに 270 度回転させ、 次のように指定します。

カメラセンサーが回転して横向きに画像が表示される
            まっすぐにする

背面カメラ

背面カメラのセンサーの向きは通常 90 度です(デバイスの背面から見た場合)。カメラ プレビューの向きを調整する際、 センサー イメージ バッファは、センサーの回転量だけ時計回りに回転しています (前面カメラのように反時計回りではなく)してから、画像を バッファがデバイスの回転量だけ反時計回りに回転します。

カメラ プレビューとセンサーの両方が横向きですが、センサーが上下逆になっています。
図 4. 背面カメラが横向きになっているスマートフォン (270 度または -90 度回転)。

図 4 のカメラセンサーからの画像バッファは次のとおりです。

カメラセンサーが横向きに回転し、画像が上下逆さまになっている。

センサーを調整するには、バッファを時計回りに 90 度回転する必要があります 向き:

カメラセンサーは、画像が横向きで左上に配置された縦向きに対応しています。

次に、デバイスを考慮して、バッファを反時計回りに 270 度回転させます。 ローテーション:

カメラセンサーが回転して横向きに画像が表示される
            まっすぐにする

アスペクト比

デバイスの画面の向きが変わるとディスプレイのアスペクト比が変化するだけでなく、 マルチウィンドウでウィンドウのサイズが変更されたときに、折りたたみ式デバイスでの折りたたみと展開 セカンダリ ディスプレイでアプリを開いたときに通知を受け取れます。

カメラセンサーのイメージ バッファの向きとスケーリングは、 ビューファインダー UI 要素の向きとアスペクト比 画面の向きを動的に変える(デバイスの変更の有無にかかわらず) 方向です。

新しいフォーム ファクタ、またはマルチウィンドウまたはマルチディスプレイ環境で、 アプリは、カメラ プレビューの向きがデバイスと同じであると想定する (縦向きまたは横向き)で、プレビューの向きが正しくない、拡大縮小される場合があります 間違っているかもしれません。

縦向きのカメラ プレビューが横向きになった、折りたたみ式デバイスの展開状態。
図 5. 折りたたみ式デバイスのアスペクト比が縦向きから横向きに切り替わっても、カメラセンサーは縦向きのままです。

図 5 では、デバイスはデバイスを 90° 回転させたと誤って想定されていました。 度(反時計回り)そのため、アプリはプレビューを同じ量だけ回転しました。

広げた折りたたみ式デバイス(カメラ プレビューはまっすぐに立てた状態で押しつぶされている)
            スケーリングが不正確になります
図 6. 折りたたみ式デバイスの縦向きから横向きへの遷移 カメラセンサーは縦向きのままです。

図 6 のアプリでは、カメラ プレビュー UI 要素の新しいサイズに合わせて適切にスケーリングできるように、画像バッファのアスペクト比が調整されていません。

向きが固定されているカメラアプリでは、折りたたみ式デバイスで問題が発生することが一般的です。 ノートパソコンなど、その他の大画面デバイス:

ノートパソコンのカメラ プレビューは縦向きなのに、アプリの UI は横向きです。
図 7. ノートパソコンで固定縦向きのアプリを使用している。

図 7 では、アプリの向きが縦向きに限定されているため、カメラアプリの UI が横向きになっています。ビューファインダー画像の向きが正しい カメラセンサーに対して相対的に調整できます。

ポートレート モードをインセット

マルチ ウィンドウ モードをサポートしていないカメラアプリ (resizeableActivity="false") 画面の向きを制限して (screenOrientation="portrait") または screenOrientation="landscape") 大画面のデバイスで縦向きモードをインセットにして配置することで、 カメラ プレビュー。

ディスプレイのアスペクト比が横向きでも、縦向き専用アプリを縦向きでレターボックス表示(インセット)します。横向きのみのアプリは、横向きではレターボックス表示されますが、 ディスプレイのアスペクト比が縦向きです。カメラの画像が回転して配置されます アプリの UI が表示され、カメラ プレビューのアスペクト比に合わせて切り抜かれている 拡大縮小されます。

ポートレート モードのインセットは、カメラ画像のアスペクト比が一定になったときにトリガーされます アプリのプライマリ アクティビティのアスペクト比が一致しません。

適切な縦向きのノートパソコンで、カメラ プレビューとアプリの UI。
            ワイド プレビュー画像は、縦向きに収まるように拡大縮小され、切り抜かれます。
図 8. インセット縦向きモードの固定縦向き縦向きアプリ ノートパソコン。

図 8 では、縦向き専用のカメラアプリが回転され、ラップトップのディスプレイに UI が縦向きで表示されています。縦向きのアプリと横向きのディスプレイのアスペクト比が異なるため、アプリがレターボックス表示になっています。カメラ プレビュー画像は、アプリの UI の回転(インセット縦向きモードによる)を補正するために回転されています。また、画像は縦向きに収まるように切り抜かれ、スケーリングされているため、画角が狭くなっています。

回転、切り抜き、サイズ変更

横向きのアスペクト比のディスプレイで、縦向き専用のカメラアプリに対して、インセット ポートレート モードが呼び出されます。

ノートパソコンのカメラ プレビューは縦向きになっているが、アプリの UI が横向きになっている。
図 9. ノートパソコン上の向きが固定された縦向きのアプリ。

アプリが縦向きでレターボックス表示されます。

アプリが縦向きに回転し、レターボックス表示されます。画像:
            作成できます

アプリの向きが変更されたため、カメラ画像が 90 度回転します。

センサー画像を 90 度回転させて、正しい向きにしました。

画像はカメラ プレビューのアスペクト比に合わせて切り抜かれた後、次のサイズに拡大されます。 プレビュー全体に表示されます(画角が小さくなります)。

切り抜かれたカメラ画像が、カメラ プレビューに収まるように拡大されています。

折りたたみ式デバイスでは、カメラ センサーの向きは縦向きでも、ディスプレイのアスペクト比は横向きでもかまいません。

広げた状態の広いディスプレイで、カメラ プレビューとアプリの UI が横向きになっている。
図 10. デバイスを開いた状態で、縦向き専用カメラアプリが表示されている カメラセンサーとディスプレイのアスペクト比

カメラ プレビューはセンサーの向きに合わせて回転するため、ビューファインダーでは画像が正しい向きで表示されますが、縦向き限定のアプリは横向きになります。

インセットの縦向きモードでは、アプリとカメラ プレビューの向きを正しくするために、縦向きでアプリをレターボックス表示するだけで済みます。

折りたたみ式デバイスで縦向きのアプリがレターボックス表示され、カメラ プレビューが縦向きになっている。

API

Android 12(API レベル 31)以降では、アプリは CaptureRequest クラスの SCALER_ROTATE_AND_CROP プロパティを使用して、インセット縦向きモードを明示的に制御することもできます。

デフォルト値は SCALER_ROTATE_AND_CROP_AUTO で、システムが切り欠き縦向きモードを呼び出すことができます。SCALER_ROTATE_AND_CROP_90 は、上記の説明のとおり、インセット縦向きモードの動作です。

すべてのデバイスがすべての SCALER_ROTATE_AND_CROP 値をサポートしているわけではありません。リストを取得するには 値、参照先 CameraCharacteristics#SCALER_AVAILABLE_ROTATE_AND_CROP_MODES

CameraX

Jetpack CameraX ライブラリ センサーの向きに合わせてカメラのビューファインダーを作成し デバイスの回転は簡単です。

PreviewView レイアウト要素はカメラ プレビューを作成し、センサーの向き、デバイスの回転、拡大縮小を自動的に調整します。PreviewView は、FILL_CENTER スケールタイプを適用してカメラ画像のアスペクト比を維持します。このスケールタイプでは、画像が中央に配置されますが、PreviewView のサイズに合わせて切り抜かれることがあります。カメラ画像をレターボックス表示するには、スケールタイプを FIT_CENTER に設定します。

PreviewView でカメラ プレビューを作成する基本については、以下をご覧ください。 プレビューを実装する

完全なサンプル実装については、GitHub の CameraXBasic リポジトリをご覧ください。

CameraViewfinder

プレビューのユースケースと同様に、CameraViewfinder ライブラリには、カメラ プレビューの作成を簡素化する一連のツールが用意されています。CameraX Core に依存しないため、お使いの Google Cloud 環境にシームレスに統合できます。 既存の Camera2 コードベースを使用します。

Surface を直接使用する代わりに、CameraViewfinder ウィジェットを使用して Camera2 のカメラフィードを表示できます。

CameraViewfinder は内部で TextureView または SurfaceView を使用してカメラフィードを表示し、必要な変換を適用してビューファインダーを正しく表示します。これには、アスペクト比、スケール、回転の修正が含まれます。

CameraViewfinder オブジェクトからサーフェスをリクエストするには、次のようにします。 ViewfinderSurfaceRequest を作成します。

このリクエストには、CameraCharacteristics のサーフェス解像度とカメラ デバイス情報に関する要件が含まれています。

requestSurfaceAsync() を呼び出すと、リクエストがサーフェス プロバイダ(TextureView または SurfaceView)に送信され、SurfaceListenableFuture が取得されます。

markSurfaceSafeToRelease() を呼び出しています サーフェスが不要であることを、サーフェス プロバイダに通知します。 リソースを解放できます

Kotlin

fun startCamera(){
    val previewResolution = Size(width, height)
    val viewfinderSurfaceRequest =
        ViewfinderSurfaceRequest(previewResolution, characteristics)
    val surfaceListenableFuture =
        cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest)

    Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface> {
        override fun onSuccess(surface: Surface) {
            /* create a CaptureSession using this surface as usual */
        }
        override fun onFailure(t: Throwable) { /* something went wrong */}
    }, ContextCompat.getMainExecutor(context))
}

Java

    void startCamera(){
        Size previewResolution = new Size(width, height);
        ViewfinderSurfaceRequest viewfinderSurfaceRequest =
                new ViewfinderSurfaceRequest(previewResolution, characteristics);
        ListenableFuture<Surface> surfaceListenableFuture =
                cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest);

        Futures.addCallback(surfaceListenableFuture, new FutureCallback<Surface>() {
            @Override
            public void onSuccess(Surface result) {
                /* create a CaptureSession using this surface as usual */
            }
            @Override public void onFailure(Throwable t) { /* something went wrong */}
        },  ContextCompat.getMainExecutor(context));
    }

SurfaceView

SurfaceView は、プレビューの処理が不要でアニメーション化されていない場合に、カメラ プレビューを作成する簡単な方法です。

SurfaceView は、画像に合わせてカメラセンサーのイメージ バッファを自動的に回転させます。 センサーの向きとデバイスの両方を考慮したディスプレイの向き 作成できます。ただし、画像バッファは、アスペクト比を考慮せずに SurfaceView のサイズに合わせてスケーリングされます。

イメージ バッファのアスペクト比とアスペクト比を一致させる必要があります。 SurfaceView の比率。これはコンテンツをスケーリングすることで実現できます。 コンポーネントの SurfaceView onMeasure() メソッド:

computeRelativeRotation() のソースコードは、以下の相対回転にあります)。

Kotlin

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val width = MeasureSpec.getSize(widthMeasureSpec)
    val height = MeasureSpec.getSize(heightMeasureSpec)

    val relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees)

    if (previewWidth > 0f && previewHeight > 0f) {
        /* Scale factor required to scale the preview to its original size on the x-axis. */
        val scaleX =
            if (relativeRotation % 180 == 0) {
                width.toFloat() / previewWidth
            } else {
                width.toFloat() / previewHeight
            }
        /* Scale factor required to scale the preview to its original size on the y-axis. */
        val scaleY =
            if (relativeRotation % 180 == 0) {
                height.toFloat() / previewHeight
            } else {
                height.toFloat() / previewWidth
            }

        /* Scale factor required to fit the preview to the SurfaceView size. */
        val finalScale = min(scaleX, scaleY)

        setScaleX(1 / scaleX * finalScale)
        setScaleY(1 / scaleY * finalScale)
    }
    setMeasuredDimension(width, height)
}

Java

@Override
void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = MeasureSpec.getSize(heightMeasureSpec);

    int relativeRotation = computeRelativeRotation(characteristics, surfaceRotationDegrees);

    if (previewWidth > 0f && previewHeight > 0f) {

        /* Scale factor required to scale the preview to its original size on the x-axis. */
        float scaleX = (relativeRotation % 180 == 0)
                       ? (float) width / previewWidth
                       : (float) width / previewHeight;

        /* Scale factor required to scale the preview to its original size on the y-axis. */
        float scaleY = (relativeRotation % 180 == 0)
                       ? (float) height / previewHeight
                       : (float) height / previewWidth;

        /* Scale factor required to fit the preview to the SurfaceView size. */
        float finalScale = Math.min(scaleX, scaleY);

        setScaleX(1 / scaleX * finalScale);
        setScaleY(1 / scaleY * finalScale);
    }
    setMeasuredDimension(width, height);
}

SurfaceView をカメラ プレビューとして実装する方法について詳しくは、以下をご覧ください。 カメラの向き

TextureView

TextureViewSurfaceView よりもパフォーマンスが低く、作業量も増えますが、カメラ プレビューを最大限に制御できます。

TextureView は、センサーの向きに基づいてセンサー画像バッファを回転しますが、デバイスの回転やプレビューのスケーリングは処理しません。

スケーリングと回転は、行列変換でエンコードできます。TextureView を正しく拡大縮小して回転する方法については、カメラアプリでサイズ変更可能なサーフェスをサポートするをご覧ください。

相対回転

カメラセンサーの相対回転は、カメラセンサーの出力をデバイスの向きに合わせるために必要な回転量です。

相対回転は SurfaceViewTextureView などのコンポーネントで使用される プレビュー画像の x と y のスケーリング ファクタを決定します。また、kubectl の「get」コマンドや センサー イメージ バッファの回転を指定します。

CameraCharacteristics および Surface クラスを使用すると、 カメラセンサーの相対回転:

Kotlin

/**
 * Computes rotation required to transform the camera sensor output orientation to the
 * device's current orientation in degrees.
 *
 * @param characteristics The CameraCharacteristics to query for the sensor orientation.
 * @param surfaceRotationDegrees The current device orientation as a Surface constant.
 * @return Relative rotation of the camera sensor output.
 */
public fun computeRelativeRotation(
    characteristics: CameraCharacteristics,
    surfaceRotationDegrees: Int
): Int {
    val sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!

    // Reverse device orientation for back-facing cameras.
    val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT
    ) 1 else -1

    // Calculate desired orientation relative to camera orientation to make
    // the image upright relative to the device orientation.
    return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360
}

Java

/**
 * Computes rotation required to transform the camera sensor output orientation to the
 * device's current orientation in degrees.
 *
 * @param characteristics The CameraCharacteristics to query for the sensor orientation.
 * @param surfaceRotationDegrees The current device orientation as a Surface constant.
 * @return Relative rotation of the camera sensor output.
 */
public int computeRelativeRotation(
    CameraCharacteristics characteristics,
    int surfaceRotationDegrees
){
    Integer sensorOrientationDegrees =
        characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

    // Reverse device orientation for back-facing cameras.
    int sign = characteristics.get(CameraCharacteristics.LENS_FACING) ==
        CameraCharacteristics.LENS_FACING_FRONT ? 1 : -1;

    // Calculate desired orientation relative to camera orientation to make
    // the image upright relative to the device orientation.
    return (sensorOrientationDegrees - surfaceRotationDegrees * sign + 360) % 360;
}

ウィンドウ指標

カメラのビューファインダーのサイズを決定する際に画面サイズを使用しないでください。カメラアプリは、モバイル デバイスのマルチウィンドウ モードまたは ChromeOS のフリーフォーム モードで、画面の一部で実行されている場合があります。

WindowManager#getCurrentWindowMetrics()(API レベル 30 で追加)は、画面のサイズではなく、アプリケーション ウィンドウのサイズを返します。Jetpack WindowManager ライブラリのメソッド WindowMetricsCalculator#computeCurrentWindowMetrics() および WindowInfoTracker#currentWindowMetrics() API レベル 14 との下位互換性により、同様のサポートが提供されます。

180 度回転

デバイスを 180 度回転しても(たとえば、自然な向きから自然な向きを上下逆にした状態にした場合)、onConfigurationChanged() コールバックはトリガーされません。その結果、カメラのプレビューが上下逆になることがあります。

180 度回転を検出するには、DisplayListener を実装し、onDisplayChanged() コールバックで Display#getRotation() を呼び出してデバイスの回転を確認します。

限定リソース

Android 10 より前は、マルチウィンドウ環境で最上位に表示されているアクティビティのみが RESUMED 状態になっていました。これはユーザーの混乱を招いていました どの活動が再開されたかを示すものがありませんでした。

Android 10(API レベル 29)では、表示されているすべてのアクティビティが RESUMED 状態になるマルチ再開が導入されました。表示中のアクティビティは引き続き PAUSED に入ることができます アクティビティの上に透明なアクティビティがある場合や、 アクティビティがフォーカス可能でない場合。たとえば、ピクチャー イン ピクチャー モードの場合( ピクチャー イン ピクチャーのサポート)。

カメラ、マイク、または専用または API レベル 29 以降のシングルトン リソースは、複数のアプリの再開をサポートする必要があります。たとえば、3 つの再開アクティビティがカメラを使用する場合、この排他的リソースにアクセスできるのは 1 つだけです。各アクティビティでは、 onDisconnected() より優先度の高いカメラへのプリエンプティブ アクセスを常に認識 できます。

詳細については、次をご覧ください: 複数のアプリの再開

参考情報