Der Bildschirmblitz, auch Frontblitz oder Selfieblitz genannt, nutzt die Bildschirmhelligkeit eines Smartphones, um das Motiv zu beleuchten, wenn Bilder mit der Frontkamera bei wenig Licht aufgenommen werden. Er ist in vielen nativen Kamera-Apps und Social-Media-Apps verfügbar. Da die meisten Nutzer ihr Smartphone nah genug halten, wenn sie ein Selbstporträt aufnehmen, ist dieser Ansatz effektiv.
Es ist jedoch schwierig für Entwickler, die Funktion richtig zu implementieren und eine gute Aufnahmequalität auf allen Geräten beizubehalten. In dieser Anleitung wird gezeigt, wie Sie diese Funktion mit Camera2, der Android-Kamera-Framework-API auf niedriger Ebene, richtig implementieren.
Allgemeiner Workflow
Für die richtige Implementierung der Funktion sind zwei Schlüsselfaktoren entscheidend: die Verwendung der Messsequenz vor der Aufnahme (automatische Belichtung vor der Aufnahme) und der Zeitpunkt der Vorgänge. Der allgemeine Workflow ist in Abbildung 1 dargestellt.
Die folgenden Schritte werden verwendet, wenn ein Bild mit der Bildschirmblitzfunktion aufgenommen werden muss.
- Nehmen Sie die für den Bildschirmblitz erforderlichen UI-Änderungen vor, die ausreichend Licht für die Aufnahme von Fotos mit dem Gerätebildschirm bieten können. Für allgemeine Anwendungsfälle schlägt Google die folgenden UI-Änderungen vor, die in unseren Tests verwendet wurden:
- Der App-Bildschirm ist mit einer weißen Farbüberlagerung bedeckt.
- Die Bildschirmhelligkeit ist maximiert.
- Setzen Sie den Modus für die automatische Belichtung (AE) auf
CONTROL_AE_MODE_ON_EXTERNAL_FLASH, falls unterstützt. - Lösen Sie eine Messsequenz vor der Aufnahme mit
CONTROL_AE_PRECAPTURE_TRIGGERaus. Warten Sie, bis die automatische Belichtung (AE) und der automatische Weißabgleich (AWB) konvergiert sind.
Sobald die Konvergenz erreicht ist, wird der übliche Fotoaufnahmeprozess der App verwendet.
Senden Sie eine Aufnahmeanfrage an das Framework.
Warten Sie, bis das Aufnahmeergebnis eingegangen ist.
Setzen Sie den AE-Modus zurück, wenn
CONTROL_AE_MODE_ON_EXTERNAL_FLASHfestgelegt wurde.Entfernen Sie die UI-Änderungen für das Aufleuchten bei Benachrichtigungen.
Camera2-Beispielcode
App-Bildschirm mit einer weißen Farbüberlagerung bedecken
Fügen Sie der Layoutdatei (XML) Ihrer Anwendung eine Ansicht hinzu. Die Ansicht hat eine ausreichende Höhe, um bei der Aufnahme mit dem Aufleuchten bei Benachrichtigungen über allen anderen UI-Elementen zu liegen. Sie ist standardmäßig unsichtbar und wird nur sichtbar, wenn die UI-Änderungen für den Bildschirmblitz angewendet werden.
Im folgenden Codebeispiel wird die weiße Farbe (#FFFFFF) als Beispiel für die Ansicht verwendet. Anwendungen können die Farbe auswählen oder Nutzern je nach ihren Anforderungen mehrere Farben anbieten.
<View android:id="@+id/white_color_overlay" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFF" android:visibility="invisible" android:elevation="8dp" />
Bildschirmhelligkeit maximieren
Es gibt mehrere Möglichkeiten, die Bildschirmhelligkeit in einer Android-App zu ändern. Eine direkte Möglichkeit besteht darin, den screenBrightness Parameter von WindowManager in der Fensterreferenz der Aktivität zu ändern.
Kotlin
private var previousBrightness: Float = -1.0f private fun maximizeScreenBrightness() { activity?.window?.let { window -> window.attributes?.apply { previousBrightness = screenBrightness screenBrightness = 1f window.attributes = this } } } private fun restoreScreenBrightness() { activity?.window?.let { window -> window.attributes?.apply { screenBrightness = previousBrightness window.attributes = this } } }
Java
private float mPreviousBrightness = -1.0f; private void maximizeScreenBrightness() { if (getActivity() == null || getActivity().getWindow() == null) { return; } Window window = getActivity().getWindow(); WindowManager.LayoutParams attributes = window.getAttributes(); mPreviousBrightness = attributes.screenBrightness; attributes.screenBrightness = 1f; window.setAttributes(attributes); } private void restoreScreenBrightness() { if (getActivity() == null || getActivity().getWindow() == null) { return; } Window window = getActivity().getWindow(); WindowManager.LayoutParams attributes = window.getAttributes(); attributes.screenBrightness = mPreviousBrightness; window.setAttributes(attributes); }
AE-Modus auf CONTROL_AE_MODE_ON_EXTERNAL_FLASH setzen
CONTROL_AE_MODE_ON_EXTERNAL_FLASH ist mit API-Level 28 oder höher verfügbar.
Dieser AE-Modus ist jedoch nicht auf allen Geräten verfügbar. Prüfen Sie daher, ob der AE-Modus verfügbar ist, und legen Sie den Wert entsprechend fest. Verwenden Sie CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES, um die Verfügbarkeit zu prüfen.
Kotlin
private val characteristics: CameraCharacteristics by lazy { cameraManager.getCameraCharacteristics(cameraId) } @RequiresApi(Build.VERSION_CODES.P) private fun isExternalFlashAeModeAvailable() = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES) ?.contains(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) ?: false
Java
try { mCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId); } catch (CameraAccessException e) { e.printStackTrace(); } @RequiresApi(Build.VERSION_CODES.P) private boolean isExternalFlashAeModeAvailable() { int[] availableAeModes = mCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES); for (int aeMode : availableAeModes) { if (aeMode == CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) { return true; } } return false; }
Wenn für die Anwendung eine wiederholte Aufnahmeanfrage festgelegt ist (für die Vorschau erforderlich), muss der AE-Modus auf die wiederholte Anfrage gesetzt werden. Andernfalls wird er bei der nächsten wiederholten Aufnahme möglicherweise durch einen Standard-AE-Modus oder einen anderen vom Nutzer festgelegten AE-Modus überschrieben. In diesem Fall hat die Kamera möglicherweise nicht genug Zeit, alle Vorgänge auszuführen, die sie normalerweise für einen AE-Modus mit externem Blitz ausführt.
Damit die Kamera die Anfrage zur Aktualisierung des AE-Modus vollständig verarbeitet, prüfen Sie das Aufnahmeergebnis im Callback für die wiederholte Aufnahme und warten Sie, bis der AE-Modus im Ergebnis aktualisiert wurde.
Aufnahme-Callback, das auf die Aktualisierung des AE-Modus warten kann
Das folgende Code-Snippet zeigt, wie das geht.
Kotlin
private val repeatingCaptureCallback = object : CameraCaptureSession.CaptureCallback() { private var targetAeMode: Int? = null private var aeModeUpdateDeferred: CompletableDeferred? = null suspend fun awaitAeModeUpdate(targetAeMode: Int) { this.targetAeMode = targetAeMode aeModeUpdateDeferred = CompletableDeferred() // Makes the current coroutine wait until aeModeUpdateDeferred is completed. It is // completed once targetAeMode is found in the following capture callbacks aeModeUpdateDeferred?.await() } private fun process(result: CaptureResult) { // Checks if AE mode is updated and completes any awaiting Deferred aeModeUpdateDeferred?.let { val aeMode = result[CaptureResult.CONTROL_AE_MODE] if (aeMode == targetAeMode) { it.complete(Unit) } } } override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) process(result) } }
Java
static class AwaitingCaptureCallback extends CameraCaptureSession.CaptureCallback { private int mTargetAeMode; private CountDownLatch mAeModeUpdateLatch = null; public void awaitAeModeUpdate(int targetAeMode) { mTargetAeMode = targetAeMode; mAeModeUpdateLatch = new CountDownLatch(1); // Makes the current thread wait until mAeModeUpdateLatch is released, it will be // released once targetAeMode is found in the capture callbacks below try { mAeModeUpdateLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } private void process(CaptureResult result) { // Checks if AE mode is updated and decrements the count of any awaiting latch if (mAeModeUpdateLatch != null) { int aeMode = result.get(CaptureResult.CONTROL_AE_MODE); if (aeMode == mTargetAeMode) { mAeModeUpdateLatch.countDown(); } } } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); process(result); } } private final AwaitingCaptureCallback mRepeatingCaptureCallback = new AwaitingCaptureCallback();
Wiederholte Anfrage zum Aktivieren oder Deaktivieren des AE-Modus festlegen
Mit dem Aufnahme-Callback können Sie mit den folgenden Codebeispielen eine wiederholte Anfrage festlegen.
Kotlin
/** [HandlerThread] where all camera operations run */ private val cameraThread = HandlerThread("CameraThread").apply { start() } /** [Handler] corresponding to [cameraThread] */ private val cameraHandler = Handler(cameraThread.looper) private suspend fun enableExternalFlashAeMode() { if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) { session.setRepeatingRequest( camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(previewSurface) set( CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH ) }.build(), repeatingCaptureCallback, cameraHandler ) // Wait for the request to be processed by camera repeatingCaptureCallback.awaitAeModeUpdate(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) } } private fun disableExternalFlashAeMode() { if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) { session.setRepeatingRequest( camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(previewSurface) }.build(), repeatingCaptureCallback, cameraHandler ) } }
Java
private void setupCameraThread() { // HandlerThread where all camera operations run HandlerThread cameraThread = new HandlerThread("CameraThread"); cameraThread.start(); // Handler corresponding to cameraThread mCameraHandler = new Handler(cameraThread.getLooper()); } private void enableExternalFlashAeMode() { if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) { try { CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.addTarget(mPreviewSurface); requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH); mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } // Wait for the request to be processed by camera mRepeatingCaptureCallback.awaitAeModeUpdate(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH); } } private void disableExternalFlashAeMode() { if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) { try { CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.addTarget(mPreviewSurface); mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } }
Sequenz vor der Aufnahme auslösen
Um eine Messsequenz vor der Aufnahme auszulösen, können Sie eine
CaptureRequest senden, bei der der Wert CONTROL_AE_PRECAPTURE_TRIGGER_START für die Anfrage festgelegt ist. Sie müssen warten, bis die Anfrage verarbeitet wurde, und dann warten, bis AE und AWB konvergiert sind.
Obwohl Auslöser vor der Aufnahme mit einer einzelnen Aufnahmeanfrage ausgelöst werden, ist das Warten auf die Konvergenz von AE und AWB komplexer. Sie können den AE-Status und den AWB-Status mit einem Aufnahme-Callback verfolgen, das auf eine wiederholte Anfrage gesetzt ist.
Wenn Sie dasselbe wiederholte Callback aktualisieren, bleibt der Code einfach. Anwendungen benötigen oft eine Vorschau, für die sie eine wiederholte Anfrage einrichten, während sie die Kamera einrichten. Sie können das Aufnahme-Callback für die wiederholte Aufnahme also einmal auf diese erste wiederholte Anfrage setzen und es dann für die Ergebnisprüfung und Wartezwecke wiederverwenden.
Code für Aufnahme-Callback aktualisieren, um auf Konvergenz zu warten
Verwenden Sie das folgende Code-Snippet, um das Aufnahme-Callback für die wiederholte Aufnahme zu aktualisieren.
Kotlin
private val repeatingCaptureCallback = object : CameraCaptureSession.CaptureCallback() { private var targetAeMode: Int? = null private var aeModeUpdateDeferred: CompletableDeferred? = null private var convergenceDeferred: CompletableDeferred? = null suspend fun awaitAeModeUpdate(targetAeMode: Int) { this.targetAeMode = targetAeMode aeModeUpdateDeferred = CompletableDeferred() // Makes the current coroutine wait until aeModeUpdateDeferred is completed. It is // completed once targetAeMode is found in the following capture callbacks aeModeUpdateDeferred?.await() } suspend fun awaitAeAwbConvergence() { convergenceDeferred = CompletableDeferred() // Makes the current coroutine wait until convergenceDeferred is completed, it will be // completed once both AE & AWB are reported as converged in the capture callbacks below convergenceDeferred?.await() } private fun process(result: CaptureResult) { // Checks if AE mode is updated and completes any awaiting Deferred aeModeUpdateDeferred?.let { val aeMode = result[CaptureResult.CONTROL_AE_MODE] if (aeMode == targetAeMode) { it.complete(Unit) } } // Checks for convergence and completes any awaiting Deferred convergenceDeferred?.let { val aeState = result[CaptureResult.CONTROL_AE_STATE] val awbState = result[CaptureResult.CONTROL_AWB_STATE] val isAeReady = ( aeState == null // May be null in some devices (e.g. legacy camera HW level) || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED ) val isAwbReady = ( awbState == null // May be null in some devices (e.g. legacy camera HW level) || awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED ) if (isAeReady && isAwbReady) { // if any non-null convergenceDeferred is set, complete it it.complete(Unit) } } } override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) process(result) } }
Java
static class AwaitingCaptureCallback extends CameraCaptureSession.CaptureCallback { private int mTargetAeMode; private CountDownLatch mAeModeUpdateLatch = null; private CountDownLatch mConvergenceLatch = null; public void awaitAeModeUpdate(int targetAeMode) { mTargetAeMode = targetAeMode; mAeModeUpdateLatch = new CountDownLatch(1); // Makes the current thread wait until mAeModeUpdateLatch is released, it will be // released once targetAeMode is found in the capture callbacks below try { mAeModeUpdateLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } public void awaitAeAwbConvergence() { mConvergenceLatch = new CountDownLatch(1); // Makes the current coroutine wait until mConvergenceLatch is released, it will be // released once both AE & AWB are reported as converged in the capture callbacks below try { mConvergenceLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } private void process(CaptureResult result) { // Checks if AE mode is updated and decrements the count of any awaiting latch if (mAeModeUpdateLatch != null) { int aeMode = result.get(CaptureResult.CONTROL_AE_MODE); if (aeMode == mTargetAeMode) { mAeModeUpdateLatch.countDown(); } } // Checks for convergence and decrements the count of any awaiting latch if (mConvergenceLatch != null) { Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE); boolean isAeReady = ( aeState == null // May be null in some devices (e.g. legacy camera HW level) || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED ); boolean isAwbReady = ( awbState == null // May be null in some devices (e.g. legacy camera HW level) || awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED ); if (isAeReady && isAwbReady) { mConvergenceLatch.countDown(); mConvergenceLatch = null; } } } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); process(result); } }
Callback während der Kameraeinrichtung auf eine wiederholte Anfrage setzen
Mit dem folgenden Codebeispiel können Sie das Callback während der Initialisierung auf eine wiederholte Anfrage setzen.
Kotlin
// Open the selected camera camera = openCamera(cameraManager, cameraId, cameraHandler) // Creates list of Surfaces where the camera will output frames val targets = listOf(previewSurface, imageReaderSurface) // Start a capture session using our open camera and list of Surfaces where frames will go session = createCameraCaptureSession(camera, targets, cameraHandler) val captureRequest = camera.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(previewSurface) } // This will keep sending the capture request as frequently as possible until the // session is torn down or session.stopRepeating() is called session.setRepeatingRequest(captureRequest.build(), repeatingCaptureCallback, cameraHandler)
Java
// Open the selected camera mCamera = openCamera(mCameraManager, mCameraId, mCameraHandler); // Creates list of Surfaces where the camera will output frames Listtargets = new ArrayList<>(Arrays.asList(mPreviewSurface, mImageReaderSurface)); // Start a capture session using our open camera and list of Surfaces where frames will go mSession = createCaptureSession(mCamera, targets, mCameraHandler); try { CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.addTarget(mPreviewSurface); // This will keep sending the capture request as frequently as possible until the // session is torn down or session.stopRepeating() is called mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); }
Sequenz vor der Aufnahme auslösen und warten
Wenn das Callback festgelegt ist, können Sie das folgende Codebeispiel verwenden, um eine Sequenz vor der Aufnahme auszulösen und zu warten.
Kotlin
private suspend fun runPrecaptureSequence() { // Creates a new capture request with CONTROL_AE_PRECAPTURE_TRIGGER_START val captureRequest = session.device.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW ).apply { addTarget(previewSurface) set( CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START ) } val precaptureDeferred = CompletableDeferred() session.capture(captureRequest.build(), object: CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { // Waiting for this callback ensures the precapture request has been processed precaptureDeferred.complete(Unit) } }, cameraHandler) precaptureDeferred.await() // Precapture trigger request has been processed, we can wait for AE & AWB convergence now repeatingCaptureCallback.awaitAeAwbConvergence() }
Java
private void runPrecaptureSequence() { // Creates a new capture request with CONTROL_AE_PRECAPTURE_TRIGGER_START try { CaptureRequest.Builder requestBuilder = mSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.addTarget(mPreviewSurface); requestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); CountDownLatch precaptureLatch = new CountDownLatch(1); mSession.capture(requestBuilder.build(), new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { Log.d(TAG, "CONTROL_AE_PRECAPTURE_TRIGGER_START processed"); // Waiting for this callback ensures the precapture request has been processed precaptureLatch.countDown(); } }, mCameraHandler); precaptureLatch.await(); // Precapture trigger request has been processed, we can wait for AE & AWB convergence now mRepeatingCaptureCallback.awaitAeAwbConvergence(); } catch (CameraAccessException | InterruptedException e) { e.printStackTrace(); } }
Alles zusammenfügen
Wenn alle wichtigen Komponenten bereit sind, können alle Schritte in der Reihenfolge ausgeführt werden, die in der vorherigen Diskussion und den Codebeispielen beschrieben ist, wenn ein Bild aufgenommen werden muss, z. B. wenn ein Nutzer auf die Aufnahmeschaltfläche klickt.
Kotlin
// User clicks captureButton to take picture captureButton.setOnClickListener { v -> // Apply the screen flash related UI changes whiteColorOverlayView.visibility = View.VISIBLE maximizeScreenBrightness() // Perform I/O heavy operations in a different scope lifecycleScope.launch(Dispatchers.IO) { // Enable external flash AE mode and wait for it to be processed enableExternalFlashAeMode() // Run precapture sequence and wait for it to complete runPrecaptureSequence() // Start taking picture and wait for it to complete takePhoto() disableExternalFlashAeMode() v.post { // Clear the screen flash related UI changes restoreScreenBrightness() whiteColorOverlayView.visibility = View.INVISIBLE } } }
Java
// User clicks captureButton to take picture mCaptureButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Apply the screen flash related UI changes mWhiteColorOverlayView.setVisibility(View.VISIBLE); maximizeScreenBrightness(); // Perform heavy operations in a different thread Executors.newSingleThreadExecutor().execute(() -> { // Enable external flash AE mode and wait for it to be processed enableExternalFlashAeMode(); // Run precapture sequence and wait for it to complete runPrecaptureSequence(); // Start taking picture and wait for it to complete takePhoto(); disableExternalFlashAeMode(); v.post(() -> { // Clear the screen flash related UI changes restoreScreenBrightness(); mWhiteColorOverlayView.setVisibility(View.INVISIBLE); }); }); } });
Beispielbilder
An den folgenden Beispielen sehen Sie, was passiert, wenn das Aufleuchten bei Benachrichtigungen falsch und wenn es richtig implementiert wird.
Falsche Implementierung
Wenn der Bildschirmblitz nicht richtig implementiert ist, erhalten Sie bei mehreren Aufnahmen, Geräten und Lichtverhältnissen inkonsistente Ergebnisse. Oft haben die aufgenommenen Bilder eine schlechte Belichtung oder einen Farbstich. Bei einigen Geräten werden diese Fehler bei bestimmten Lichtverhältnissen deutlicher, z. B. bei wenig Licht anstelle von völliger Dunkelheit.
Die folgende Tabelle enthält Beispiele für solche Probleme. Sie wurden in der CameraX-Laborinfrastruktur aufgenommen, wobei die Lichtquellen eine warmweiße Farbe beibehalten haben. An dieser warmweißen Lichtquelle können Sie sehen, dass der blaue Farbstich ein tatsächliches Problem ist und kein Nebeneffekt einer Lichtquelle.
| Umgebung | Unterbelichtung | Überbelichtung | Farbstich |
|---|---|---|---|
| Dunkle Umgebung (keine Lichtquelle außer dem Smartphone) |
|
|
|
| Wenig Licht (zusätzliche Lichtquelle mit ~3 Lux) |
|
|
|
Richtige Implementierung
Wenn die Standardimplementierung für dieselben Geräte und Bedingungen verwendet wird, sehen Sie die Ergebnisse in der folgenden Tabelle.
| Umgebung | Unterbelichtung (behoben) | Überbelichtung (behoben) | Farbstich (behoben) |
|---|---|---|---|
| Dunkle Umgebung (keine Lichtquelle außer dem Smartphone) |
|
|
|
| Wenig Licht (zusätzliche Lichtquelle mit ~3 Lux) |
|
|
|
Wie zu sehen ist, verbessert sich die Bildqualität mit der Standardimplementierung deutlich.