CameraX & Barcode scanner - Barcode scanner doesn't read

1.2k views Asked by At

I'm currently experimenting with CameraX API and ML Kit for barcode scanning. I'm not receiving any data from PreviewView when I'm pointing camera to the QR code. Let me share with you my code, I think I'm so close, but sadly couldn't figure it out. :/ I'm using Preview and Image Analysis cases

MainActivity with ImageAnalysis.Analyser

class MainActivity : AppCompatActivity() {

    private lateinit var outputDirectory: File
    private lateinit var cameraExecutor: ExecutorService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        outputDirectory = getOutputDirectory()
        cameraExecutor = Executors.newSingleThreadExecutor()

        // Request camera permissions
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(
                this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
            )
        }

    }

    private fun getOutputDirectory(): File {
        val mediaDir = externalMediaDirs.firstOrNull()?.let {
            File(it, resources.getString(R.string.app_name)).apply { mkdirs() }
        }
        return if (mediaDir != null && mediaDir.exists())
            mediaDir else filesDir
    }

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

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

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(previewView.surfaceProvider)
                }

            // Image Analyzer
            val imageAnalyzer = ImageAnalysis.Builder()
                .build()
                .also {
                    it.setAnalyzer(cameraExecutor, MyImageAnalyser())
                }

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageAnalyzer
                )

            } catch (exc: Exception) {
                Log.e(Constants.TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }


    inner class MyImageAnalyser : ImageAnalysis.Analyzer {

        @SuppressLint("UnsafeExperimentalUsageError")
        override fun analyze(imageProxy: ImageProxy) {
            val mediaImage = imageProxy.image
            if (mediaImage != null) {
                val image =
                    InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
                initializeBarcodeScanner(image)
            }
        }

        private fun initializeBarcodeScanner(inputImage: InputImage) {
            val options = BarcodeScannerOptions.Builder()
                .setBarcodeFormats(
                    Barcode.QR_CODE,
                    Barcode.WIFI,
                    Barcode.URL
                )
                .build()

            val scanner = BarcodeScanning.getClient(options)

            scanner.process(inputImage)
                .addOnSuccessListener { barcodes ->
                    for (barcode in barcodes) {
                        val bounds = barcode.boundingBox
                        val corners = barcode.cornerPoints
                        val rawValue = barcode.rawValue

                        Log.d("BARCODE", barcode.valueType.toString())
                        when (barcode.valueType) {
                            Barcode.WIFI -> {
                                val ssid = barcode.wifi!!.ssid
                                val password = barcode.wifi!!.password
                                val type = barcode.wifi!!.encryptionType

                                Log.d("BARCODE", ssid.toString())
                                Log.d("BARCODE", password.toString())
                                Log.d("BARCODE", type.toString())
                            }
                            Barcode.URL -> {
                                val title = barcode.url!!.title
                                val url = barcode.url!!.url

                                Log.d("BARCODE", title.toString())
                                Log.d("BARCODE", url.toString())
                            }
                            Barcode.QR_CODE -> {
                                Log.d("BARCODE", barcode.rawValue.toString())
                            }
                            else -> {
                                Log.d("BARCODE", "Else")
                            }
                        }
                    }
                }
                .addOnFailureListener {
                    Log.d("BARCODE", it.message.toString())
                }
        }

    }

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

}

Constants.kt

class Constants {

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

}

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </androidx.camera.view.PreviewView>

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="200dp"
        android:layout_marginStart="20dp"
        android:layout_marginEnd="20dp"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Dependencies

 // Use this dependency to bundle the model with your app
    implementation 'com.google.mlkit:barcode-scanning:16.0.3'


    def camerax_version = "1.0.0-beta10"
    //  CameraX core library using camera2 implementation
    implementation "androidx.camera:camera-camera2:$camerax_version"
    //  CameraX Lifecycle Library
    implementation "androidx.camera:camera-lifecycle:$camerax_version"
    //  CameraX View class
    implementation "androidx.camera:camera-view:1.0.0-alpha17"
1

There are 1 answers

1
Husayn Hakeem On

Every image the analyzer receives must be closed before it can receive new images. The documentation says:

It is the responsibility of the application to close the image once done with it. If the images are not closed then it may block further images from being produced

Closing an image is achieved via ImageProxy.close(). Since you're using MLKit, you can add a task completion listener, and close the image inside it. The listener is called when the task (image processing) is completed successfully or with an error.

Task.addOnSuccessListener { barcodes -> ... }
    .addOnFailureListener { exception -> }
    .addOnCompleteListener { imageProxy.close() }