Der Bildschirmblitz, auch Frontblitz oder Selfieblitz genannt, nutzt die Bildschirmhelligkeit eines Smartphones, um das Motiv beim Aufnehmen von Bildern mit der Frontkamera bei schlechten Lichtverhältnissen auszuleuchten. Es ist in vielen nativen Kamera-Apps und Social-Media-Apps verfügbar. Da die meisten Menschen ihr Smartphone nah genug halten, wenn sie ein Selbstporträt aufnehmen, ist dieser Ansatz effektiv.
Für Entwickler ist es jedoch schwierig, die Funktion korrekt zu implementieren und auf allen Geräten eine gute Aufnahmequalität aufrechtzuerhalten. In diesem Leitfaden wird beschrieben, wie Sie diese Funktion mit Camera2, der Low-Level-API für das Kamera-Framework von Android, richtig implementieren.
Allgemeiner Workflow
Die beiden wichtigsten Faktoren für eine ordnungsgemäße Implementierung der Funktion sind die Verwendung der Messsequenz für die Voraberfassung (automatische Belichtungsvoraberfassung) und die Zeitangaben für die Messungen. Der allgemeine Workflow ist in Abbildung 1 dargestellt.
Die folgenden Schritte werden verwendet, wenn ein Bild mit der Bildschirmblitzfunktion aufgenommen werden muss.
- Nimm Änderungen an der Benutzeroberfläche für den Bildschirmblitz vor, der ausreichend Licht für die Aufnahme von Fotos über den Gerätebildschirm bietet. Für allgemeine Anwendungsfälle schlägt Google die folgenden Änderungen an der Benutzeroberfläche vor, die in unseren Tests verwendet werden:
- Der App-Bildschirm ist mit einem weißen Overlay überdeckt.
- Die Bildschirmhelligkeit ist optimiert.
- Setzen Sie den automatischen Belichtungsmodus auf
CONTROL_AE_MODE_ON_EXTERNAL_FLASH
, falls dies unterstützt wird. - Lösen Sie mit
CONTROL_AE_PRECAPTURE_TRIGGER
eine Messsequenz für die Aufnahme vor der Aufnahme aus. Warten Sie, bis die automatische Belichtung (AE) und der automatische Weißabgleich (AWB) konvergiert sind.
Nach dem Konvergieren wird der übliche Ablauf der Fotoaufnahme der App verwendet.
Aufnahmeanfrage an Framework senden.
Warten Sie auf das Erfassungsergebnis.
AE-Modus zurücksetzen, wenn
CONTROL_AE_MODE_ON_EXTERNAL_FLASH
festgelegt wurdeÄnderungen an der Benutzeroberfläche für das Aufleuchten des Bildschirms löschen.
Camera2-Beispielcodes
App-Bildschirm mit weißem Overlay
Fügen Sie in der Layout-XML-Datei Ihrer Anwendung eine Ansicht hinzu. Die Ansicht ist so hoch, dass sie während der Bildschirmaufnahme über allen anderen UI-Elementen liegt. Sie ist standardmäßig unsichtbar und wird nur sichtbar gemacht, wenn Änderungen an der Bildschirmblitz-UI vorgenommen werden.
Im folgenden Codebeispiel wird die Farbe weiß (#FFFFFF
) als Beispiel für die Ansicht verwendet. Anwendungen können Nutzern je nach Anforderungen die Farbe auswählen oder 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" />
Displayhelligkeit maximieren
Es gibt mehrere Möglichkeiten, die Bildschirmhelligkeit in einer Android-App zu ändern. Eine direkte Möglichkeit besteht darin, den WindowManager-Parameter screenBrightness in der Activity Window-Referenz 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
einstellen
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üfe daher, ob er verfügbar ist, und lege 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 wiederkehrende Erfassungsanfrage festgelegt ist (dies ist für die Vorschau erforderlich), muss der AE-Modus auf die wiederkehrende Anfrage eingestellt werden. Andernfalls wird er in der nächsten wiederholten Aufnahme möglicherweise durch einen Standard- oder einen anderen vom Nutzer festgelegten AE-Modus überschrieben. In diesem Fall hat die Kamera möglicherweise nicht genügend Zeit, um alle Vorgänge auszuführen, die sie normalerweise für einen externen Blitz-AE-Modus benötigt.
Damit die Kamera die Anfrage zur Aktualisierung des AE-Modus vollständig verarbeitet, prüfen Sie das Aufnahmeergebnis im wiederholten Callback der Aufnahme und warten Sie, bis der AE-Modus im Ergebnis aktualisiert wurde.
Callback erfassen, der auf die Aktualisierung des AE-Modus warten kann
Das folgende Code-Snippet zeigt, wie dies erreicht werden kann.
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();
Setze eine wiederkehrende Anfrage, um den AE-Modus zu aktivieren oder zu deaktivieren
Nachdem der Capture-Callback eingerichtet wurde, zeigen die folgenden Codebeispiele, wie Sie eine wiederkehrende 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 für Vorabaufnahme auslösen
Wenn Sie eine Messsequenz für die Erfassung auswählen möchten, können Sie einen CaptureRequest
senden, bei dem 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 zusammenlaufen.
Obwohl die Voraberfassung mit einer einzelnen Erfassungsanfrage ausgelöst wird, ist das Warten auf die AE- und AWB-Konvergenz komplexer. Sie können den AE-Status und den AWB-Status mit einem Erfassungs-Callback verfolgen, der auf eine wiederkehrende Anfrage gesetzt ist.
Wenn Sie denselben wiederkehrenden Callback aktualisieren, ist der Code einfacher. Anwendungen erfordern oft eine Vorschau, für die beim Einrichten der Kamera eine wiederkehrende Anfrage eingerichtet wird. Sie können also den wiederkehrenden Capture-Callback einmal auf die ursprüngliche, sich wiederholende Anfrage festlegen und ihn dann zur Ergebnisprüfung und zum Abwarten wiederverwenden.
Aktualisierung des Callback-Codes erfassen, um auf Konvergenz zu warten
Verwenden Sie das folgende Code-Snippet, um den wiederkehrenden Capture-Callback 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); } }
Beim Einrichten der Kamera einen Rückruf auf eine wiederkehrende Anfrage festlegen
Mit dem folgenden Codebeispiel können Sie den Callback auf eine sich wiederholende Anfrage während der Initialisierung festlegen.
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(); }
Sequenzierung vor der Aufnahme auslösen und warten
Wenn der Callback festgelegt ist, können Sie das folgende Codebeispiel verwenden, damit eine Sequenz für die Voraberfassung ausgelöst und gewartet wird.
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 Hauptkomponenten bereit sind und ein Bild aufgenommen werden muss, z. B. wenn ein Nutzer auf die Aufnahmeschaltfläche klickt, um ein Bild aufzunehmen, können alle Schritte in der Reihenfolge ausgeführt werden, die in der vorherigen Diskussion und den Codebeispielen angegeben wurde.
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
Die folgenden Beispiele zeigen, was passiert, wenn der Bildschirmblitz falsch implementiert wurde, und wann er richtig implementiert wurde.
Falsche Vorgehensweise
Wenn der Blitz für den Bildschirm nicht richtig implementiert ist, erhalten Sie bei mehreren Aufnahmen, Geräten und Lichtverhältnissen inkonsistente Ergebnisse. Oft liegt ein Problem mit der Belichtung oder der Färbung vor. Bei einigen Geräten treten solche Insekten bei bestimmten Lichtverhältnissen deutlicher auf, z. B. in einer schlechten Lichtumgebung statt in einer vollkommen dunklen Umgebung.
Die folgende Tabelle enthält Beispiele für solche Probleme. Sie werden in der KameraX-Laborinfrastruktur aufgenommen, wobei die Lichtquellen in einem warm-weißen Licht geblieben sind. Mit dieser warm-weißen Lichtquelle können Sie sehen, dass die blaue Färbung ein tatsächliches Problem und nicht ein Nebeneffekt einer Lichtquelle ist.
Umgebung | Unterbelichtung | Überbelichtung | Farbton |
---|---|---|---|
Dunkle Umgebung (außer dem Smartphone keine Lichtquelle) | |||
Wenig Licht (zusätzliche Lichtquelle ~3 Lux) |
Richtig gemacht
Wenn die Standardimplementierung für dieselben Geräte und Bedingungen verwendet wird, sehen Sie die Ergebnisse in der folgenden Tabelle.
Umgebung | Unterbelichtung (fest) | Überbelichtung (fest) | Farbton (fest) |
---|---|---|---|
Dunkle Umgebung (außer dem Smartphone keine Lichtquelle) | |||
Wenig Licht (zusätzliche Lichtquelle ~3 Lux) |
Wie beobachtet, verbessert sich die Bildqualität mit der Standardimplementierung erheblich.