0

I want to capture image with a given value of exposure time.

I start with opening the camera through the method below:

public boolean openCamera(@NonNull final AutoFitTextureView textureView) throws InterruptedException {
    this.textureView = textureView;
    if (isOpened()) {
      Timber.i("Camera is already open");
      return false;
    }
    final CameraManager manager =
      (CameraManager) this.context.getSystemService(Context.CAMERA_SERVICE);
    try {
      if (!this.cameraOpenCloseLock.tryAcquire(CAMERA_LOCK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
        throw new RuntimeException("Time out waiting to lock camera opening.");
      }
      startBackgroundThread();
      setupCameraOutputs();
      manager.openCamera(this.cameraId, this.stateCallback, this.cameraHandler);
      manager.registerAvailabilityCallback(this.availabilityCallback, this.cameraHandler);
    } catch (CameraAccessException e) {
      Timber.e(e, "Failed to open Camera");
      onCameraFailCallback();
    } catch (InterruptedException e) {
      Timber.e(e, "Interrupted while trying to lock camera opening.");
      onCameraFailCallback();
    }
    return this.countDownLatch.await(10, TimeUnit.SECONDS);
  }

/**
   * Start the camera thread
   */
  private void startBackgroundThread() {
    this.cameraThread = new HandlerThread("CameraThread");
    this.cameraThread.start();
    this.cameraHandler = new Handler(this.cameraThread.getLooper());
  }

This is the definition of setupCameraOutputs()

private void setupCameraOutputs() {
    final CameraManager manager =
      (CameraManager) this.context.getSystemService(Context.CAMERA_SERVICE);
    try {
      for (String cameraId : manager.getCameraIdList()) {
        final CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
        // Not using front camera
        if (null == characteristics.get(CameraCharacteristics.LENS_FACING)) {
          continue;
        }

        final Size[] sizes =
          characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
            .getOutputSizes(ImageFormat.JPEG);
        for (Size size : sizes) {
          Timber.d("Available sizes = %d, %d", size.getHeight(), size.getWidth());
        }

        Timber.i("Image size: %dx%d", WIDTH, HEIGHT);
        this.imageReader = ImageReader.newInstance(WIDTH, HEIGHT, ImageFormat.JPEG, 1);
        this.imageReader
          .setOnImageAvailableListener(this.imageAvailableListener, this.cameraHandler);
        this.cameraId = cameraId;
        Timber.i("Using Camera ID: %s", cameraId);
        return;
      }
    } catch (CameraAccessException e) {
      Timber.e("Failed to access Camera");
    } catch (NullPointerException e) {
      Timber.e(e, "Device doesn't support Camera2 API");
    }
  }

The state callback passed to manager.openCamera()

private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {

    @Override
    public void onOpened(@NonNull CameraDevice device) {
      Timber.i("Camera opened");
      CameraExecutor.this.isOpened.set(true);
      CameraExecutor.this.cameraOpenCloseLock.release();
      CameraExecutor.this.cameraDevice = device;
      createCameraSession();
    }
  };
/**
   * Create a camera capture session
   */
  private void createCameraSession() {
    try {
      SurfaceTexture texture = this.textureView.getSurfaceTexture();
      texture.setDefaultBufferSize(WIDTH, HEIGHT);
      this.previewSurface = new Surface(texture);
      final List<Surface> surfaces = Arrays.asList(this.previewSurface,
        this.imageReader.getSurface());
      this.cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(CameraCaptureSession session) {
          Timber.i("Capture session configured");
          CameraExecutor.this.previewCaptureSession = session;
          _createCameraPreview();
        }

        @Override
        public void onConfigureFailed(CameraCaptureSession session) {
          Timber.e("Capture session configuration failed");
        }

        @Override
        public void onReady(@NonNull CameraCaptureSession session) {
          super.onReady(session);
          Timber.e("Capture session ready");
          CameraExecutor.this.isDeviceSessionReady = true;
        }
      }, null);
    } catch (CameraAccessException e) {
      Timber.e(e, "Error while creating camera session");
    }
  }

  /**
   * Background camera preview to improve gains and lighting conditions.
   */
  private void _createCameraPreview() {
    try {
      final CaptureRequest.Builder previewSession =
        this.cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      previewSession.addTarget(this.previewSurface);
      previewSession.set(JPEG_ORIENTATION, 0);
      this.previewCaptureSession.setRepeatingRequest(previewSession.build(),
        null, this.cameraHandler);
    } catch (CameraAccessException e) {
      Timber.e(e, "Error on camera preview request");
    }
    this.countDownLatch.countDown();
  }

After this point , I have the preview configured and running with auto-exposure enabled.

Now, when I want to capture an image with a given value of exposure time.

I first change the preview via the below method:

public void changePreviewWithExposure(final long exposureTime) {
    try {
      this.isDeviceSessionReady = false;
      Timber.d("Stop requested");
      this.previewCaptureSession.stopRepeating();
            //isDeviceSessionReady, this attribute is set to true in the onReady callback 
            //of the device captureSession
      while (!this.isDeviceSessionReady) ;
      Timber.d("Stop received");
      final CaptureRequest.Builder previewRequest =
        this.cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      previewRequest.addTarget(this.previewSurface);
      previewRequest.set(JPEG_ORIENTATION, 0);
      //previewRequest.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_MANUAL);
      previewRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
      previewRequest.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposureTime);
      this.previewCaptureSession.setRepeatingRequest(previewRequest.build(),
        null, this.cameraHandler);
    } catch (CameraAccessException e) {
      Timber.e(e, "Error on camera preview request");
    }
  }

After this, the preview is changed with the given value of exposure time.

Now, I want to capture an image with the same value of exposure time. For that I call this method.

public void captureImage(final long exposureTime) {
    if (null == this.cameraDevice) {
      Timber.d("camera device null");
      return;
    }
    if (!isOpened()) {
      return;
    }

    Timber.d("Capture request received");
    try {
      final CaptureRequest.Builder captureRequest =
        this.cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
      captureRequest.addTarget(this.imageReader.getSurface());
      captureRequest.set(JPEG_ORIENTATION, 0);
      captureRequest.set(CaptureRequest.JPEG_QUALITY, (byte) 100);

            //Checked the available control modes of the camera using
            //final int[] availableControlModes = characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_MODES);
            //this array has only one element with the value of 1, which corresponds to CONTROL_MODE_AUTO
            //therefore, not setting the CaptureRequest.CONTROL_MODE to OFF.

            //setting the auto exposure to off mode
      captureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
            //setting the exposure value
      captureRequest.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposureTime);
      if (null != this.previewCaptureSession && isOpened()) {
                //capture command
        this.previewCaptureSession.capture(captureRequest.build(),
          this.captureCallback, this.cameraHandler);
      }
    } catch (CameraAccessException e) {
      Timber.e(e, "Cannot access camera while capture image request");
    } catch (Exception e) {
      Timber.e(e, "Error capturing image");
    }
  }

When the image is available on the imageReader , this callback is called, as configured in the setupCameraOutputs():

/**
     * Callback on {@code imageReader}
     */
  private final ImageReader.OnImageAvailableListener imageAvailableListener = imageReader -> {
    final Image image = imageReader.acquireLatestImage();
    if (null == image) {
      return;
    }
    Image.Plane[] planes = image.getPlanes();
    ByteBuffer buffer = planes[0].getBuffer();
    byte[] bytes = new byte[buffer.capacity()];
    buffer.get(bytes);

    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    //Bitmap rotated = rotateImage(bitmap);
    this.bitmapAvailableListener.onBitmapAvailable(bitmap);
    image.close();
  };

And I get the bitmap.

Coming to the problem.

I am able to capture images at different exposure times. But, in some instances I get images which are horizontally shifted and have some random color layer on top of them.

For example, the below image. shifted image

For clarity, this is the image of the same scene with lower exposure time than the shifted one. lower exposure time image This is the image of the same scene with higher exposure time than the shifted one. higher exposure time image In some images, along with the image shift there some changes in the color as well, as shown in this image. shifted image with red tint What I have tried doing.

By looking at the reddish image above, I changed the auto white balance from auto mode to a preset value, but it didn't work out.

Next, I put a time gap between the change in preview and capturing of image. I varied this time gap from 200 milli seconds to 5000 milli seconds but I was still getting these erroneous images.

I am unable to figure out, what is causing the image to shift and the change in color.

4

0 回答 0