Search code examples
androidandroid-cameraandroid-camerax

How to take multiple images using CameraX?


My use case is to take two images at one time. first one at 2x zoom and second one at 1x zoom level. Also, I want to save images to files.

My idea of doing it was to take the first image at 2x zoom and when the image is saved set the zoom level at 1x and take the second image when the lens has zoomed to 1x zoom level.

However, when I take the first image the preview is stuck at the first image and callback from setting 1x zoom never happens.

This is how I create the capture use cases.

private void createImageCaptureUseCases() {
    ImageCapture imageCapture1 = new ImageCapture.Builder()
        .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
        .build();

    ImageCapture imageCapture2 = new ImageCapture.Builder()
        .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
        .build();

    imageCaptureUseCases.clear();
    imageCaptureUseCases.add(imageCapture1);
    imageCaptureUseCases.add(imageCapture2);

This is how I first start the camera session.

ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(getContext());

cameraProviderFuture.addListener(() -> {
    try {
        cameraProvider = cameraProviderFuture.get();
        preview = new Preview.Builder().build();

        cameraSelector = new CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build();

        Camera camera = cameraProvider.bindToLifecycle(
            ((LifecycleOwner) this),
            cameraSelector,
            preview,
            imageCapture);

        camera.getCameraControl().setZoomRatio(2f);

        preview.setSurfaceProvider(previewView.createSurfaceProvider(camera.getCameraInfo()));
    } catch (InterruptedException | ExecutionException e) {}
}, ContextCompat.getMainExecutor(getContext()));

this is how the capture images is called.

private void captureImage(ImageCapture imageCapture) {

    File pictureFile = ImageUtils.createImageFile(getActivity());
    ImageCapture.OutputFileOptions options = new 
        ImageCapture.OutputFileOptions.Builder(pictureFile).build();

    final Activity activity = getActivity();

    imageCapture.takePicture(options, ContextCompat.getMainExecutor(activity),
        new ImageCapture.OnImageSavedCallback() {

            @Override
            public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults){
                Log.i("my tag", "image Saved: " + pictureFile.getAbsolutePath());

                int index = imageCaptureUseCases.indexOf(imageCapture);
                cameraProvider.unbind(imageCapture);
                if (index < imageCaptureUseCases.size() - 1) {
                    Camera camera = cameraProvider.bindToLifecycle(
                    (LifecycleOwner) activity,
                    cameraSelector,
                    imageCaptureUseCases.get(index + 1));

                    ListenableFuture future = camera.getCameraControl().setZoomRatio(1f);
                    future.addListener(() -> captureImage(imageCaptureUseCases.get(index + 1)),
                                    ContextCompat.getMainExecutor(activity));
                } else {
                    createImageCaptureUseCases();
                    cameraProvider.unbindAll();
                    Camera camera = cameraProvider.bindToLifecycle(
                        (LifecycleOwner) activity,
                        cameraSelector,
                        preview,
                        imageCaptureUseCases.get(0));

                    camera.getCameraControl().setZoomRatio(2f);
                }
            }

            @Override
            public void onError(@NonNull ImageCaptureException exception) {
                Log.i("my tag", "image save error: " + pictureFile.getAbsolutePath());
            }
    });
}

Solution

    • You don't need multiple ImageCapture instances to capture an image with 2 zoom ratios, you can use the same instance, ImageCapture handles taking a picture and saving it/providing it, irrelevant to parameters such as the zoom ratio.

    • Looking at your code sample, it seems you might not being binding a Preview use case the second time you try to capture an image (with a different zoom ratio). This would explain why your preview is getting stuck after the first image capture. Keep in mind that an ImageCapture use case cannot be bound on its own, it must be bound with at least 1 Preview or ImageAnalysis use case.

    Below is a sample to capture 2 images, each with a different zoom ratio. The code contains some repetition, and is all in 1 block, so it can definitely be improved.

        private fun setUpCamera() {
           val mainExecutor = ContextCompat.getMainExecutor(this)
           val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
           cameraProviderFuture.addListener(Runnable {
              // Wait for the camera provider to be retrieved
              val cameraProvider = cameraProviderFuture.get()
    
              // Build your use cases
              val preview = Preview.Builder().build()
              val imageCapture = ImageCapture.Builder().build()
    
              // Get a camera selector to use
              val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
    
              // Bind the use cases to a lifecycle
              val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
    
              // Set the preview surface provider
    preview.setSurfaceProvider(previewView.createSurfaceProvider(camera.cameraInfo))
    
              // Set the zoom ratio for the first photo
              val cameraControl = camera.cameraControl
              cameraControl.setZoomRatio(1F)
    
              // When the previewView is clicked, take the photos
              previewView.setOnClickListener {
                 imageCapture.takePicture(createOutputFilesOptions(), mainExecutor, object : ImageCapture.OnImageSavedCallback {
                    override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
    
                       // First image captured and saved successfully
                       Log.d(TAG, "OnImageSavedCallback.onImageSaved: Image saved with zoom ratio 1F")
    
                       // Set a new zoom ratio for the second image capture
                       cameraControl.setZoomRatio(2F)
    
                       // Capture the second picture with a different zoom ratio
                       imageCapture.takePicture(createOutputFilesOptions(), mainExecutor, object : ImageCapture.OnImageSavedCallback {
                          override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
    
                             // Second image captured and saved successfully
                             Log.d(TAG, "OnImageSavedCallback.onImageSaved: Image saved with zoom ratio 2F")
                          }
    
                          override fun onError(exception: ImageCaptureException) {
                             Log.e(TAG, "OnImageSavedCallback.onError", exception)
                          }
                       })
                    }
    
                    override fun onError(exception: ImageCaptureException) {
                       Log.e(TAG, "OnImageSavedCallback.onError", exception)
                    }
                 })
              }
           }, mainExecutor)
        }
    }