3

Hello I'm using android mlkit barcode scanner without firebase with androidx ,I follow this code https://medium.com/@surya.n1447/google-vision-ml-kit-with-camerax-64bbbfd4c6fd When I scanning qrcode it is too slow and I don't know how to improve scanning speed is there some tricks or something like that ? Or is better switch to zxing or Google vision ? I using xiaomi mi 10 t pro

class ScanPersonFragment : Fragment() {

private var processingBarcode = AtomicBoolean(false)
private var mediaPlayer: MediaPlayer? = null
private lateinit var cameraExecutor: ExecutorService
private lateinit var scanBarcodeViewModel: ScanPersonViewModel


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    cameraExecutor = Executors.newSingleThreadExecutor()
    scanBarcodeViewModel = ViewModelProvider(this).get(ScanPersonViewModel::class.java)
}

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val v = inflater.inflate(R.layout.fragment_scan_person_destination, container, false)
    mediaPlayer = MediaPlayer.create(context, R.raw.beep)
    scanBarcodeViewModel.progressState.observe(viewLifecycleOwner, {
        v.fragment_scan_person_barcode_progress_bar.visibility = if (it) View.VISIBLE else View.GONE
    })

    scanBarcodeViewModel.navigation.observe(viewLifecycleOwner, { navDirections ->
        navDirections?.let {
            findNavController().navigate(navDirections)
            scanBarcodeViewModel.doneNavigating()
        }
    })

    return v
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    if (allPermissionsGranted()) {
        startCamera()
    } else {
        requestPermissions(
            REQUIRED_PERMISSIONS,
            REQUEST_CODE_PERMISSIONS
        )
    }
}

override fun onResume() {
    super.onResume()
    processingBarcode.set(false)
}

private fun startCamera() {
    // Create an instance of the ProcessCameraProvider,
    // which will be used to bind the use cases to a lifecycle owner.
    val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())




    val imageCapture = ImageCapture.Builder()
        .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
        //.setTargetResolution(Size(400, 400))
        .build()

    // Add a listener to the cameraProviderFuture.
    // The first argument is a Runnable, which will be where the magic actually happens.
    // The second argument (way down below) is an Executor that runs on the main thread.
    cameraProviderFuture.addListener({
        // Add a ProcessCameraProvider, which binds the lifecycle of your camera to
        // the LifecycleOwner within the application's life.
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
        // Initialize the Preview object, get a surface provider from your PreviewView,
        // and set it on the preview instance.
        val preview = Preview.Builder().build().also {
            it.setSurfaceProvider(
                fragment_scan_person_barcode_preview_view.surfaceProvider
            )
        }
        // Setup the ImageAnalyzer for the ImageAnalysis use case
        val imageAnalysis = ImageAnalysis.Builder()
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            .build()
            .also {
                it.setAnalyzer(cameraExecutor, BarcodeAnalyzer { barcode ->
                    if (processingBarcode.compareAndSet(false, true)) {
                        mediaPlayer?.start()
                        searchBarcode(barcode)
                    }
                })
            }

        // Select back camera
        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
        try {
            // Unbind any bound use cases before rebinding
            cameraProvider.unbindAll()
            // Bind use cases to lifecycleOwner
            cameraProvider.bindToLifecycle(this, cameraSelector, preview,  imageAnalysis)
        } catch (e: Exception) {
            Log.e("PreviewUseCase", "Binding failed! :(", e)
        }
    }, ContextCompat.getMainExecutor(requireContext()))
}

private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
    ContextCompat.checkSelfPermission(
        requireContext(), it
    ) == PackageManager.PERMISSION_GRANTED
}

override fun onRequestPermissionsResult(
    requestCode: Int, permissions: Array<String>, grantResults:
    IntArray
) {
    if (requestCode == REQUEST_CODE_PERMISSIONS) {
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            Toast.makeText(
                requireContext(),
                "Permissions not granted by the user.",
                Toast.LENGTH_SHORT
            ).show()
        }
    }
}

private fun searchBarcode(barcode: String) {
    scanBarcodeViewModel.searchBarcode(barcode)
}

override fun onDestroy() {
    cameraExecutor.shutdown()
    super.onDestroy()
}

companion object {
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
    private const val REQUEST_CODE_PERMISSIONS = 10
}

class BarcodeAnalyzer(private val barcodeListener: BarcodeListener) : ImageAnalysis.Analyzer {

@SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
    val mediaImage = imageProxy.image
    if (mediaImage != null) {
        val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
        val options = BarcodeScannerOptions.Builder().setBarcodeFormats(Barcode.FORMAT_QR_CODE).build()
        val scanner = BarcodeScanning.getClient(options)
        // Pass image to the scanner and have it do its thing
        scanner.process(image)
            .addOnSuccessListener { barcodes ->
                // Task completed successfully
                for (barcode in barcodes) {

                    barcodeListener(barcode.rawValue ?: "")
                }
            }
            .addOnFailureListener {
                // You should really do something about Exceptions
            }
            .addOnCompleteListener {
                // It's important to close the imageProxy
                imageProxy.close()
            }
    }
}

}

EagleCode
  • 125
  • 1
  • 9
  • 1
    Can you confirm the image size for the image? The Xiaomi Mi 10t pro has a 108 mega pixel camera. In this case, we might want to limit the image size. I see you comment out the setTargetResolution line, that maybe an issue. Please check https://developers.google.com/ml-kit/vision/barcode-scanning/android#input-image-guidelines for guidance on the appropriate resolution. – Hoi Jan 12 '21 at 17:03
  • 1
    Also, if you are using firebase ML Kit currently, you should migrate to ML Kit(without Firebase Branding). We have made improvement to barcode scanning latency in the new ML Kit. Here's the migration guide: https://developers.google.com/ml-kit/migration – Chenxi Song Jan 12 '21 at 17:29
  • I have ml kit without firebase cloud, i reduce my qr data,I switch resolution to 1920x1080 I try with some qr code and what i found is when I have a big qr code is good but when i have small qrcode on 18 mm tape is to slow for reeding its like 50 to 50 one time recognized after zoom one time not and for my situation i need to reed qr codes from 18mm tape I found something like boofcv and work good and I don't understand why? I was thinging google mlkit is the one of best but for me is not speed enough if you have some tricks how to qr code reading fast like scandit I will be happy – EagleCode Jan 13 '21 at 18:45
  • 1
    @EagleCode Are you using the bundled or unbundled variant of the API? In the bundled variant we have an updated implementation that should work much faster with small barcodes (relative to the overall image), since we detect the barcode and then crop it before sending to decoder. We are in the process of updating the unbundled implementation in the coming months. Also, it would help to provide some example images (in the resolution you use) so we can reproduce here. Thanks! – Chrisito Jan 15 '21 at 20:36
  • hey @Chrisito I have filed a bug report along with the images for reproducing in the below link, please find the link below. https://issuetracker.google.com/u/1/issues/180881635 – Laxmikanth Madhyastha Feb 22 '21 at 18:34

3 Answers3

5

To summarise some of the answers:

  • For very large images, say 108 megapixel camera, it is helpful to reduce the resolution. For typical usage, we find 1280x720 or 1920x1080 resolution to be sufficient.

  • In the near term, try using the "bundled" version of barcode SDK which has V2 of the barcode model:

    implementation 'com.google.mlkit:barcode-scanning:16.1.0'
    

    Barcode V2 implementation is faster and more accurate but it adds about 2.2 MB to your app size as a "bundled" model. The team working to bring this to the Google Play Services version (i.e. unbundled) and remove the need for the app to bundle the 2.2 MB model in the coming months.

    For more information between bundled and unbundled version, check out the table at the top of this page.

Hoi
  • 599
  • 2
  • 7
1

First convert image proxy into bitmap. Second do not forget to close image proxy.then it will improve performance.

if (image.image != null && image.format == ImageFormat.YUV_420_888) {
                


                scanner.process(InputImage.fromBitmap(image.toBitmap(), image.imageInfo.rotationDegrees))
                    .addOnSuccessListener { barcodes ->
                        if (barcodes.isNotEmpty()) {

                            if (firstCall){


                                for (barcode in barcodes) {
                                    

                                    // Handle received barcodes...


                                    Log.d("ValueXXXX ",barcode.format.toString())

                                    if (barcode.format==Barcode.FORMAT_QR_CODE){
                                        var data = barcode.displayValue.toString().replace("\\", "")
                                        //var json=org.json.JSONObject(data)

                                        Log.d("TAG","DATA1111 "+data)

                                        if(Utils.urlPattern.matcher(data).matches()) {
                                            firstCall=false


                                            showPopupForInvalidQRCode(R.string.DISPLAY_INVALID_QR_CODE,context)



                                            break
                                        }
                                        if (!data.isNullOrEmpty() && Utils.isJSONValid(data)){
                                            firstCall=false
                                            try {
                                                val json = JSONObject(data)
                                                Log.d("TAG","json "+json)
                                                val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
                                                    if (Build.VERSION.SDK_INT >= 26) {
                                                        vibrator.vibrate(VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE))
                                                    } else {
                                                        vibrator.vibrate(300)
                                                    }

                                                 //parse your json here   

                                                break
                                            }catch (e:Exception){
                                                e.printStackTrace()
                                                showPopupForInvalidQRCode(R.string.DISPLAY_INVALID_QR_CODE,context)
                                            }
                                        }

                                        break
                                    }



                                }
                            }

                        } else {
                            // Remove bounding rect
                            barcodeBoxView.setRect(RectF())
                        }
                    }
                    .addOnFailureListener {
                        firstCall=true
                        Log.d("EXCEPTION",it.message.toString())
                        //image.close()

                    }
                    

                    }
            }
Anshul Nema
  • 281
  • 3
  • 3
0

This was the important part for me!

Please note close the ImageProxy and Image after success or failure else the code will run only once, its just fires once and forget.

image.image?.close()                                           
image.close()
Erkan
  • 140
  • 2
  • 11