How to take a picture where all settings are set manually including the flash without missing the image that contains the full flash?

Viewed 179

I used the latest Camera2Basic sample program as a source for my trials:

https://github.com/android/camera-samples.git

Basically I configured the CaptureRequest before I call the capture() function in the takePhoto() function like this:

    private fun prepareCaptureRequest(captureRequest: CaptureRequest.Builder) {
    //set all needed camera settings here
    captureRequest.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF)

    captureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
    //captureRequest.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
    //captureRequest.set(CaptureRequest.CONTROL_AWB_LOCK, true);
    captureRequest.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF);
    captureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
    //captureRequest.set(CaptureRequest.CONTROL_AE_LOCK, true);
    //captureRequest.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL);
    //captureRequest.set(CaptureRequest.NOISE_REDUCTION_MODE, CaptureRequest.NOISE_REDUCTION_MODE_FAST);

    //flash
    if (mState == CaptureState.PRECAPTURE){
        //captureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
        captureRequest.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF)
    }
    if (mState == CaptureState.TAKEPICTURE) {
        //captureRequest.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE)
        //captureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
        captureRequest.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE)
    }

    val iso = 100
    captureRequest.set(CaptureRequest.SENSOR_SENSITIVITY, iso)

    val fractionOfASecond = 750.toLong()
    captureRequest.set(CaptureRequest.SENSOR_EXPOSURE_TIME, 1000.toLong() * 1000.toLong() * 1000.toLong() / fractionOfASecond)
    //val exposureTime = 133333.toLong()
    //captureRequest.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposureTime)

    //val characteristics = cameraManager.getCameraCharacteristics(cameraId)
    //val configs: StreamConfigurationMap? = characteristics[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
    //val frameDuration = 33333333.toLong()
    //captureRequest.set(CaptureRequest.SENSOR_FRAME_DURATION, frameDuration)

    val focusDistanceCm = 20.0.toFloat()    //20cm 
    captureRequest.set(CaptureRequest.LENS_FOCUS_DISTANCE, 100.0f / focusDistanceCm)

    //captureRequest.set(CaptureRequest.COLOR_CORRECTION_MODE, CameraMetadata.COLOR_CORRECTION_MODE_FAST)
    captureRequest.set(CaptureRequest.COLOR_CORRECTION_MODE, CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX)
    val colorTemp = 8000.toFloat();
    val rggb = colorTemperature(colorTemp)
    //captureRequest.set(CaptureRequest.COLOR_CORRECTION_TRANSFORM, colorTransform);
    captureRequest.set(CaptureRequest.COLOR_CORRECTION_GAINS, rggb);
}

but the picture that is returned never is the picture where the flash is at its brightest. This is on a Google Pixel 2 device. As I only take one picture I am also not sure how to check some CaptureResult states to find the correct one as there is only one. I already looked at the other solutions to similar problems here but they were either never really solved or somehow took the picture during capture preview which I don't want.

Other strange observations are that on different devices the images are taken (also not always at the right moment), but then the manual values I set are not observed in the JPEG metadata of the image.

If needed I can put my git fork on github.

1 Answers

Long exposure time in combination with flash seems to be the basic issue and when the results are not that good, this means that the timing of your preset isn't that good. You'd have to optimize the exposure time's duration, in relation to the flash's timing (just check the EXIF of some photos for example values). You could measure the luminosity with an ImageAnalysis.Analyzer (this had been removed from the sample application, but elder revisions still have an example). And I've tried with the default Motorola camera app; there the photo also seems to be taken shortly after the flash, when the brightness is already decaying (in order to avoid the dazzling bright). That's the CaptureState.PRECAPTURE, where you switch the flash off. Flashing in two stages is rather the default and this might yield better results.

If you want it to be dazzlingly bright (even if this is generally not desired), you could as well first switch on the torch, that the image, switch off the torch again (I use something alike this, but only for barcode scanning). This would at least prevent any expose/flash timing issues.

When changed values are not represented in EXIF, you'd need to use ExifInterface, in order to update them (there's an example which updates the orientation, but one can update any value).

Related