I am trying to implement a very basic CameraX Image Analysis use case where I would send the images received by the analyzer to a tflite classifier model. The model only accepting bitmap as inputs I thought I would set OUTPUT_IMAGE_FORMAT_RGBA_8888 when building the ImageAmalysis. Resulting media images are easier to convert to bitmaps than the standard YUV_420_888 ones. Problem is I get a error : "java.lang.IllegalStateException: maxImages (4) has already been acquired, call #close before acquiring more." Usually it would mean I have forgotten to close the imageProxy but it is not the case so I don't know what is wrong with my code. See below:
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.media.Image;
import android.os.Bundle;
import android.util.Size;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ExperimentalGetImage;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import com.google.common.util.concurrent.ListenableFuture;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private Executor analysisExecutor;
private PreviewView previewView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
analysisExecutor = Executors.newSingleThreadExecutor();
setContentView(R.layout.activity_main);
previewView = findViewById(R.id.viewFinder);
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
cameraProvider.unbindAll();
bindUseCases(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
// No errors need to be handled for this Future.
// This should never be reached.
}
}, ContextCompat.getMainExecutor(this));
}
void bindUseCases(@NonNull ProcessCameraProvider cameraProvider) {
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
//Preview
Preview preview = new Preview.Builder()
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
//ImageAnalysis
ImageAnalysis imageAnalysis =
new ImageAnalysis.Builder()
.setTargetResolution(new Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.build();
imageAnalysis.setAnalyzer(analysisExecutor, new ImageAnalysis.Analyzer() {
@Override
@ExperimentalGetImage
public void analyze(@NonNull ImageProxy imageProxy) {
//my code to turn media image into a bitmap
Image img = imageProxy.getImage();
Image.Plane[] planes = img.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * img.getWidth();
Bitmap bitmap = Bitmap.createBitmap(img.getWidth()+rowPadding/pixelStride, img.getHeight(), Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
//close img and imageProxy
img.close();
imageProxy.close();
}
});
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, imageAnalysis, preview);
}
}
And the complete error message:
E/ImageAnalysisAnalyzer: Failed to acquire image. java.lang.IllegalStateException: maxImages (4) has already been acquired, call #close before acquiring more. at android.media.ImageReader.acquireNextImage(ImageReader.java:527) at android.media.ImageReader.acquireLatestImage(ImageReader.java:411) at androidx.camera.core.AndroidImageReaderProxy.acquireLatestImage(AndroidImageReaderProxy.java:56) at androidx.camera.core.SafeCloseImageReaderProxy.acquireLatestImage(SafeCloseImageReaderProxy.java:67) at androidx.camera.core.ImageYuvToRgbConverter.convertYUVToRGB(ImageYuvToRgbConverter.java:83) at androidx.camera.core.ImageAnalysisAbstractAnalyzer.analyzeImage(ImageAnalysisAbstractAnalyzer.java:125) at androidx.camera.core.ImageAnalysisNonBlockingAnalyzer.onValidImageAvailable(ImageAnalysisNonBlockingAnalyzer.java:103) at androidx.camera.core.ImageAnalysisAbstractAnalyzer.onImageAvailable(ImageAnalysisAbstractAnalyzer.java:69) at androidx.camera.core.SafeCloseImageReaderProxy.lambda$setOnImageAvailableListener$1$SafeCloseImageReaderProxy(SafeCloseImageReaderProxy.java:170) at androidx.camera.core.-$$Lambda$SafeCloseImageReaderProxy$vlVuGMKvMVqmwbJFm3dTgGgUzu4.onImageAvailable(Unknown Source:4) at androidx.camera.core.AndroidImageReaderProxy.lambda$setOnImageAvailableListener$0$AndroidImageReaderProxy(AndroidImageReaderProxy.java:139) at androidx.camera.core.-$$Lambda$AndroidImageReaderProxy$ydxkGVJ03P0ZMYkq3dfSV-hzi3E.run(Unknown Source:4) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:923)