Home » Android » How to get Exposure Compensation level from Android phone camera, via Java, when picture is taken?

How to get Exposure Compensation level from Android phone camera, via Java, when picture is taken?

Posted by: admin May 14, 2020 Leave a comment

Questions:

How to get AutoExposureCompensation level (brightness) from Android phone when the picture is taken?

I can take a picture. I can access the Parameters of the Camera, including the Exposure Compensation (always zero when I check), but I need to get the AE Compensation level at the moment the picture is taken, not before and not afterward.

Background: I want all pictures, taken at a certain time, to use the same AE Compensation level the pictures are taken. I don’t want those hundreds of adjustments to the exposure level, or the white balance, that Android cameras typically do. I want to get once, and set for all the succeeding photos, the same settings.

I have tried using “intents” for pictures, OpenCV, fragments, etc. I can’t seem to get the AE compensation setting with any of these. Here’s the latest code I’ve tried, starting with the an extended version of JavaCameraView:

import org.opencv.android.JavaCameraView;
import android.content.Context;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.util.Log;
@SuppressWarnings("deprecation")
public class NewJavaCameraView extends JavaCameraView implements PictureCallback {

public int getExposureCompensation(){
    return mCamera.getParameters().getExposureCompensation();
}
 @SuppressWarnings("deprecation")
 public void takePicture(final String fileName) {
    Log.i(TAG, "Taking picture");
    this.mPictureFileName = fileName;

    Camera.Parameters params = mCamera.getParameters();
    int exposureComp = params.getExposureCompensation();
    mCamera.setPreviewCallback(null);

    // PictureCallback is implemented by the current class
    int otherexposureComp =this.getExposureCompensation();
    mCamera.takePicture(null, null, this);
}

 @SuppressWarnings("deprecation")
 @Override
 public void onPictureTaken(byte[] data, Camera camera) {

    Camera.Parameters params = mCamera.getParameters();
    int exposureComp = params.getExposureCompensation();
    int otherexposureComp =this.getExposureCompensation();
    mCamera.startPreview();
    mCamera.setPreviewCallback(this);

    // Write the image in a file (in jpeg format)
    try {
        FileOutputStream fos = new FileOutputStream(mPictureFileName);

        fos.write(data);
        fos.close();

    } catch (java.io.IOException e) {
        Log.e("Picture", "photoCallback", e);
    }
}

Here’s some of the code from the Android View that’s using the abovementioned class:

public class DiscPhoto extends Activity implements CvCameraViewListener2, OnTouchListener {
 private static final String TAG = "OCVSample::Activity";
 private NewJavaCameraView mOpenCvCameraView;
 private List<Size> mResolutionList;

 private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
    @Override
    public void onManagerConnected(int status) {
        switch (status) {
            case LoaderCallbackInterface.SUCCESS:
            {
                Log.i(TAG, "OpenCV loaded successfully");
                mOpenCvCameraView.enableView();
                mOpenCvCameraView.setOnTouchListener(DiscPhoto.this);
            } break;
            default:
            {
                super.onManagerConnected(status);
            } break;
        }
    }
};

public DiscPhoto() {
    Log.i(TAG, "Instantiated new " + this.getClass());
}

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    Log.i(TAG, "called onCreate");
    super.onCreate(savedInstanceState);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    setContentView(R.layout.activity_disc_photo);

    mOpenCvCameraView = (NewJavaCameraView) findViewById(R.id.discPhotoPage);
    mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
    mOpenCvCameraView.setCvCameraViewListener(this);
}

@SuppressLint("SimpleDateFormat")
@Override
public boolean onTouch(View v, MotionEvent event) {
    Log.i(TAG,"onTouch event");
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
    String currentDateandTime = sdf.format(new Date());
    String fileName = Environment.getExternalStorageDirectory().getPath() +
            "/sample_picture_" + currentDateandTime + ".jpg";
    mOpenCvCameraView.takePicture(fileName);
    Toast.makeText(this, fileName + " saved", Toast.LENGTH_SHORT).show();
    return false;
}
How to&Answers:

I think camera2 APIs (https://developer.android.com/reference/android/hardware/camera2/CaptureRequest.html) will suffice your need.

Source: https://developer.android.com/reference/android/hardware/camera2/CaptureRequest.html#CONTROL_AE_LOCK

Since the camera device has a pipeline of in-flight requests, the
settings that get locked do not necessarily correspond to the settings
that were present in the latest capture result received from the
camera device, since additional captures and AE updates may have
occurred even before the result was sent out. If an application is
switching between automatic and manual control and wishes to eliminate
any flicker during the switch, the following procedure is recommended:

  1. Starting in auto-AE mode:
  2. Lock AE
  3. Wait for the first result to be output that has the AE locked
  4. Copy exposure settings from that result into a request, set the request to manual AE
  5. Submit the capture request, proceed to run manual AE as desired.

Also as per the description of AE mode (same source)

When set to any of the ON modes, the values chosen by the camera
device auto-exposure routine for the overridden fields for a given
capture will be available in its CaptureResult.

So once you make the first CaptureRequest, you can use the TotalCaptureResult from following callback:

void onCaptureCompleted (CameraCaptureSession session, 
                CaptureRequest request, 
                TotalCaptureResult result)
{
       int aecompensationlevel = result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION)
}

Answer:

OK, the question came up how to set in concrete the exposure, sensor sensitivity and other variables when writing code to control an Android camera. This will only work in Lollipop or later. There’s to much code to post, but I’ll try to put in the highlights

In short, I use a TextureView (AutoFitTextureView) with a CameraManager. When I open the camera, I call a void function called createPreviewSessions()

  void openCamera() {
    try {
        mManager.openCamera(mCameraId, new CameraDevice.StateCallback() {
            @Override
            public void onOpened(CameraDevice camera) {
                createPreviewSession();
            }
        }

private void createPreviewSession() {
    try {
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        final List<Surface> surfaceList = new ArrayList<>();
        Surface surface = mImageReader.getSurface();
        surfaceList.add(surface);

        mCamera.createCaptureSession(surfaceList, new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(CameraCaptureSession session) {
                mSession = session;

                CaptureRequest request = createRequest(surfaceList, milliSecond, sensorSetting); //module variables
            } ...
       } ...
}
private CaptureRequest createRequest(List<Surface> surfaces, int milliSeconds, int sensorSetting) {
    Log.v("createRequest","here");
    try {
        CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        builder.set(CaptureRequest.CONTROL_EFFECT_MODE, CameraMetadata.CONTROL_EFFECT_MODE_OFF);
        for (Surface surface : surfaces)
            builder.addTarget(surface);
        int exposureTime = milliSeconds * (milliSecondFactor); //billionth
        CaptureRequestSettings.SetRequestBuilder(builder,CONTROL_AWB_MODE_DAYLIGHT);

        builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, Long.valueOf(exposureTime));       //set hard values based on settings caught when photo taken
        builder.set(CaptureRequest.SENSOR_SENSITIVITY, Integer.valueOf(sensorSetting));     //same thing
        builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        builder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF);  //CaptureRequest.CONTROL_AWB_MODE_OFF); //off here just like video mode
        builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);    //off ... don't want auto exposure

        return builder.build();
    } catch (CameraAccessException e) {
        Log.e("CaptureRequest", "CameraAccessException: " +e.getMessage());
    } catch (Exception e) {
        Log.e("CaptureRequest", "Regular Exception: " +e.getMessage());
    }
    Log.v("createRequest","shouldn't get here");
    return null;
}

Answer:

OK, I’m going to answer this myself for whoever finds a similar problem.

Manish got close with his answer, but even when used in the onCaptureSession event, only 0 (zero) is returned by CONTROL_AE_EXPOSURE_COMPENSATION, which is of no use; that’s just the default starting value for the camera.

However, the CameraCaptureSession.CaptureCallback (onCaptureSession event) does let you get the values from SENSOR_EXPOSURE_TIME and SENSOR_SENSITIVITY to create a work around to the auto exposure problem of working with Android cameras.

Below is a snippet of the code I used:

private void captureStillPicture() {
    try {
          ...
        CameraCaptureSession.CaptureCallback CaptureCallback
                = new CameraCaptureSession.CaptureCallback() {

            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                           @NonNull CaptureRequest request,
                                           @NonNull TotalCaptureResult result) {

                long sensorTime= result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
                long sensorSensitivity = result.get(CaptureResult.SENSOR_SENSITIVITY);

                int ONE_SECOND = 1000000000; //1 billion nanoseconds
                int MICRO_SECOND = 1000000;
                int MILLI_SECOND = 1000;
                String exposureText = "";
                if (sensorTime > ONE_SECOND) {
                    exposureText = String.format("%.2f s", sensorTime / 1e9);
                } else if (sensorTime > MILLI_SECOND) {
                    exposureText = String.format("%.2f ms", sensorTime / 1e6);
                } else if (sensorTime > MICRO_SECOND) {
                    exposureText = String.format("%.2f us", sensorTime / 1e3);
                } else {
                    exposureText = String.format("%d ns", sensorTime);
                }

                int aecompensationlevel=result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION); //only returns zero
                showToast("Saved: " + mFile +" | " +exposureText );
                Log.d(TAG, mFile.toString());

            }
        };

Here’s a picture of the results from my debugger:

enter image description here