Android - Barcode scanning with MLKit not accurate for Barcode.FORMAT_CODE_39

1k Views Asked by At

I am Implementing Barcode Scanning functionality in my Android App. its working great for basic QR Code or Barcode FORMAT-128. But its not Accurate for Barcode FORMAT-39. Sometimes its captured correct value and some times wrong value. (especially when barcode value contains repeated values like A00000190) below are codes i tried and respective scanned values.

  1. Barcode value : AA00000029001

enter image description here

scan results randomly picking one of the following : AA00000029001, AA000029001, A100029001, AA00029001 .. etc

below is my code snippet.

class MainActivity3 : AppCompatActivity(), BarcodeScannerListener {

    companion object {
        private const val CAMERA_REQUEST_CODE = 101
        private const val TAG = "MAIN_ACTIVITY"
    }

    private lateinit var viewBinding: ActivityMain3Binding

    private val executorService: ExecutorService by lazy {
        Executors.newSingleThreadExecutor()
    }

    private val barcodeScanner by lazy {
        BarcodeScanner(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewBinding = ActivityMain3Binding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        setupCamera()
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
                                            grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == CAMERA_REQUEST_CODE && isCameraPermissionGranted()) {
            startCamera()
        }
    }

    private fun setupCamera() {
        if (isCameraPermissionGranted()) {
            startCamera()
        } else {
            requestPermission()
        }
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder().build().apply {
                setSurfaceProvider(viewBinding.cameraPreview.surfaceProvider)
            }
            val imageAnalyzer = ImageAnalysis.Builder().build().apply {
                setAnalyzer(executorService, getImageAnalyzerListener())
            }

            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    this,
                    CameraSelector.DEFAULT_BACK_CAMERA,
                    preview,
                    imageAnalyzer
                )
            } catch (throwable: Throwable) {
                Log.e(TAG, "Use case binding failed", throwable)
            }
        }, ContextCompat.getMainExecutor(this))
    }

    @SuppressLint("UnsafeOptInUsageError")
    private fun getImageAnalyzerListener(): ImageAnalysis.Analyzer {
        return ImageAnalysis.Analyzer { imageProxy ->
            val image = imageProxy.image ?: return@Analyzer
            val inputImage = InputImage.fromMediaImage(image, imageProxy.imageInfo.rotationDegrees)
            barcodeScanner.scanImage(inputImage) {
                imageProxy.close()
            }
        }
    }

    override fun onSuccessScan(result: List<Barcode>) {
        result.forEachIndexed { index, barcode ->
            Log.e("MainActivity3","Barcode value : ${barcode.rawValue}")
            Toast.makeText(this, "Barcode value: ${barcode.rawValue}", Toast.LENGTH_SHORT).show()
        }
    }

    override fun onScanFailed() {
        Toast.makeText(this, "Fail", Toast.LENGTH_SHORT).show()
    }

    private fun isCameraPermissionGranted(): Boolean {
        return ContextCompat.checkSelfPermission(this,
            Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
    }

    private fun requestPermission() {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA),
            CAMERA_REQUEST_CODE)
    }

    override fun onDestroy() {
        super.onDestroy()
        barcodeScanner.closeScanner()
        executorService.shutdown()
    }
}

Scanner Functionality :

class BarcodeScanner(private val barcodeScannerListener: BarcodeScannerListener) {

    private val barcodeScanner: BarcodeScanner by lazy {
        constructBarcodeScanner()
    }

    private val executorService: ExecutorService by lazy {
        Executors.newSingleThreadExecutor()
    }

    fun scanImage(inputImage: InputImage, onScanComplete: (() -> Unit)? = null) {
        barcodeScanner.process(inputImage)
            .addOnCompleteListener {
                onScanComplete?.invoke()
            }
            .addOnSuccessListener {
                barcodeScannerListener.onSuccessScan(it)
            }
            .addOnFailureListener {
                Log.e("Scanner fail", "caused:", it)
                barcodeScannerListener.onScanFailed()
            }
    }

    fun closeScanner() {
        barcodeScanner.close()
        executorService.shutdown()
    }

    private fun constructBarcodeScanner(): BarcodeScanner {
        val barcodeScannerOptions = BarcodeScannerOptions.Builder()
            .setExecutor(executorService)
            .setBarcodeFormats(
              //  Barcode.FORMAT_ALL_FORMATS,
                Barcode.FORMAT_CODE_39
            )
            .build()
        return BarcodeScanning.getClient(barcodeScannerOptions)
    }
}

Below are Some other Barcodes I tested and scan results

  1. Barcode value : R000000590

enter image description here

scan results randomly picking one of the following : R000000590, R00000590, V00000590, 40000590, 200000590 .. etc

  1. Barcode value : ABC-1200000034

enter image description here scan results randomly picking one of the following : ABC-1200000034, ABC-12000034, ABC-1200000034, BP712000034, BP-12000034, ABC712000034 .. etc

Can someone please help to resolve these Barcodes with format-39 issue. Other types like QR Code or format-128 are working fine.

1

There are 1 best solutions below

3
Hossam Waziry On

The data is repeated because the analyzer reads the image more than once. The image must be closed when the reading is complete

 fun scanImage(inputImage: InputImage, onScanComplete: (() -> Unit)? = null) {
        barcodeScanner.process(inputImage)
            .addOnCompleteListener {
                 barcodeScannerListener.onSuccessScan(it)
            //imageProxy should be closed here 
            }
            .addOnFailureListener {
                Log.e("Scanner fail", "caused:", it)
                barcodeScannerListener.onScanFailed()
            }
    }

Or try this and see the result:

 override fun onSuccessScan(result: List<Barcode>) {
        Log.e("MainActivity3","Barcode value : ${result.toString()}")
  
}