MLKit Android inverted datamatrix scan takes too long time

215 views Asked by At

First of all - sorry for my english :)

I'm using MLKit in project to scan barcodes (datamatrix). But i have white datamatrix on black background like this Datamatrix image

In my code I crop image before scanning. After this i invert colors of this cropped image and put this image into scanner.

The problem i have is that scanner just can't define code having good image. It's going through few analysis and reading it from 6-10th analys.

For example. This is screenshot of datamatrix scanning. There is square image in the center. It is imageView that gets cropped and inverted image that goes to scanner. But it doesn't give quick result. I need to wait for 3-5 second to get code from there. While its scanning Scanner makes 3-4 analysis in 1 sec.

I have iOS App alternative of this MLKit scanning. It does the same steps (crop and invert) but iOS app is scanning this for 1 sec, maybe faster.

I tried everything to speed scanning up, but nothing helped. Pls help

I put my classes in git. So i put short code here.

This is my ScanFragment.java with initializations

    private CamcorderProfile camProfile;
    private ListenableFuture cameraProviderFuture;
    private ExecutorService cameraExecutor;
    private PreviewView previewView;
    private ImageAnalyser analyser;
    private androidx.camera.core.Camera camera;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_scan_ui, container, false);
        previewView = view.findViewById(R.id.previewview);

        requireActivity().getWindow().setFlags(1024, 1024);
        cameraExecutor = Executors.newSingleThreadExecutor();
        cameraProviderFuture = ProcessCameraProvider.getInstance(requireActivity());
        try {
            processCameraProvider = (ProcessCameraProvider) cameraProviderFuture.get();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cameraProviderFuture.addListener(() -> {
                if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) != (PackageManager.PERMISSION_GRANTED)) {
                    requestPermissionLauncher.launch(Manifest.permission.CAMERA);
                } else {
                    bindpreview();
                }
        }, ContextCompat.getMainExecutor(requireContext()));

        return view;
    }

    private void setTabDatamatrix() {
        camProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
        BarcodeScannerOptions barcodeScanOptions = new BarcodeScannerOptions.Builder()
                .setBarcodeFormats(
                        Barcode.FORMAT_DATA_MATRIX
                ).build();
        if (analyser == null)
            analyser = new ImageAnalyser(getParentFragmentManager(), barcodeScanOptions, this, this);
        else
            analyser.setScanOptions(barcodeScanOptions, ScanType.DATAMATRIX);
        scanType = ScanType.DATAMATRIX;

        hideProgressBar();
        bottomBar.performShow();
        showCursor();
        setTabBackground(ivScanTabDatamatrix);
        tvScanHintMoveCamera.setVisibility(View.VISIBLE);
        tvScanHintMoveCamera.setText(Utils.getString(R.string.scanner_tab_mark_hint, requireContext()));
        tooltip.show(ivScanTabDatamatrix, Utils.getString(R.string.scanner_tab_mark, requireContext()));

        showHint(coordinatorLayout, ScanHintView.ScanHintType.MARK);
        bindpreview();
    }

    private void bindpreview() {
        Preview preview = new Preview.Builder().build();
        CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
        preview.setSurfaceProvider(previewView.getSurfaceProvider());
        ImageCapture imageCapture = new ImageCapture.Builder().build();
        ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                .setTargetResolution(new Size(camProfile.videoFrameHeight, camProfile.videoFrameWidth))  //480 320
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build();
        imageAnalysis.setAnalyzer(cameraExecutor, analyser);
        processCameraProvider.unbindAll();
        camera = processCameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalysis);
    }

This is ImageAnalyser.java class. I analyze scanned images here.

    @Override
    public void analyze(@NonNull ImageProxy image) {
        scanbarcode(image);
    }

    private void scanbarcode(ImageProxy image) {
        @SuppressLint("UnsafeOptInUsageError") Bitmap bitmap = BitmapUtils.getBitmap(image);
        assert bitmap != null;
        Bitmap cropped = Bitmap.createBitmap(bitmap, bitmap.getWidth() / 4, (bitmap.getHeight() / 2) - bitmap.getWidth() / 4, bitmap.getWidth() / 2, bitmap.getWidth() / 2);
        Bitmap inverted = inverseBitmapColors(cropped);
        InputImage invertedImage = InputImage.fromBitmap(inverted, image.getImageInfo().getRotationDegrees());
        BarcodeScanner scanner = BarcodeScanning.getClient(barcodeScannerOptions);
        scanner.process(invertedImage)
                .addOnSuccessListener(barcodes -> {
                    if (active) {
                        readerBarcodeData(barcodes);
                    }
                })
                .addOnFailureListener(e -> {
                })
                .addOnCompleteListener(task -> image.close());

    }

    private Bitmap inverseBitmapColors(Bitmap bitmap) {
        Bitmap invBitmap = bitmap.copy(bitmap.getConfig(), true);
        for (int i = 0; i < invBitmap.getWidth(); i++) {
            for (int j = 0; j < invBitmap.getHeight(); j++) {
                invBitmap.setPixel(i, j, invBitmap.getPixel(i, j) ^ 0x00ffffff);
            }
        }
        return invBitmap;
    }

I tried many manipulations with cropping, chaning quality, tried many methods of inverting colors. I am expecting that it will scan like iOS app in 1 sec or faster. Not 3-5 secs.

1

There are 1 answers

0
Anatoliy Voronin On

I make InputImage like this.

@SuppressLint("UnsafeOptInUsageError") Image image = imageProxy.getImage();
        assert image != null;
        byte[] imageByteArray = YUV_420_888toNV21(image);
        int size = min(image.getWidth(), image.getHeight()) / 2;
        byte[] cropped = cropNV21(imageByteArray, image.getWidth(), image.getHeight(), (image.getWidth() - size) / 2, (image.getHeight() - size) / 2, size, size);
        assert cropped != null;
        InputImage inputImage = InputImage.fromByteArray(cropped, size, size, imageProxy.getImageInfo().getRotationDegrees(), InputImage.IMAGE_FORMAT_NV21);
        InputImage invertedImage = InputImage.fromByteArray(inverse(cropped), size, size, imageProxy.getImageInfo().getRotationDegrees(), InputImage.IMAGE_FORMAT_NV21);
        

private byte[] YUV_420_888toNV21(Image image) {
        byte[] nv21;
        ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
        ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
        ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();

        int ySize = yBuffer.remaining();
        int uSize = uBuffer.remaining();
        int vSize = vBuffer.remaining();

        nv21 = new byte[ySize + uSize + vSize];

        //U and V are swapped
        yBuffer.get(nv21, 0, ySize);
        vBuffer.get(nv21, ySize, vSize);
        uBuffer.get(nv21, ySize + vSize, uSize);

        return nv21;
    }

    private byte[] inverse(byte[] bytes) {
        byte[] inverted = new byte[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            inverted[i] = (byte) (bytes[i] ^ 0xff);
        }
        return inverted;
    }

    private byte[] cropNV21(byte[] src, int width, int height, int left, int top, int clip_w, int clip_h) {
        if (left > width || top > height) {
            return null;
        }
        // Take the couple
        int x = left * 2 / 2, y = top * 2 / 2;
        int w = clip_w * 2 / 2, h = clip_h * 2 / 2;
        int y_unit = w * h;
        int src_unit = width * height;
        int uv = y_unit >> 1;
        byte[] nData = new byte[y_unit + uv];


        for (int i = y, len_i = y + h; i < len_i; i++) {
            for (int j = x, len_j = x + w; j < len_j; j++) {
                nData[(i - y) * w + j - x] = src[i * width + j];
                nData[y_unit + ((i - y) / 2) * w + j - x] = src[src_unit + i / 2 * width + j];
            }
        }

        return nData;
    }