How to read the barcode only within a specific area by using CameraX and MlKit?

I’m trying to use MlKit to read barcodes in my Android application, I draw an overlay with a rectangle box in it’s middle, which should be the area where the barcode must be read.

Here is my overlay code:

class ViewFinderOverlay(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private val boxPaint: Paint = Paint().apply {
        color = ContextCompat.getColor(context, R.color.barcode_reticle_stroke)
        style = Paint.Style.STROKE
        strokeWidth = context.resources.getDimensionPixelOffset(R.dimen.barcode_reticle_stroke_width).toFloat()
    }

    private val scrimPaint: Paint = Paint().apply {
        color = ContextCompat.getColor(context, R.color.barcode_reticle_background)
    }

    private val eraserPaint: Paint = Paint().apply {
        strokeWidth = boxPaint.strokeWidth
        xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
    }

    private val boxCornerRadius: Float =
        context.resources.getDimensionPixelOffset(R.dimen.barcode_reticle_corner_radius).toFloat()

    private var boxRect: RectF? = null

    fun setViewFinder() {
        val overlayWidth = width.toFloat()
        val overlayHeight = height.toFloat()
        val boxWidth = overlayWidth * 80 / 100
        val boxHeight = overlayHeight * 36 / 100
        val cx = overlayWidth / 2
        val cy = overlayHeight / 2
        boxRect = RectF(cx - boxWidth / 2, cy - boxHeight / 2, cx + boxWidth / 2, cy + boxHeight / 2)

        invalidate()
    }

fun getViewFinder(): Rect {
    val overlayWidth = width.toFloat()
    val overlayHeight = height.toFloat()
    val boxWidth = overlayWidth * 80 / 100
    val boxHeight = overlayHeight * 36 / 100
    val cx = overlayWidth / 2
    val cy = overlayHeight / 2

    return Rect(
        (cx - boxWidth / 2).roundToInt(),
        (cy - boxHeight / 2).roundToInt(),
        (cx + boxWidth / 2).roundToInt(),
        (cy + boxHeight / 2).roundToInt()
    )
}

    override fun draw(canvas: Canvas) {
        super.draw(canvas)
        boxRect?.let {
            // Draws the dark background scrim and leaves the box area clear.
            canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), scrimPaint)
            // As the stroke is always centered, so erase twice with FILL and STROKE respectively to clear
            // all area that the box rect would occupy.
            eraserPaint.style = Paint.Style.FILL
            canvas.drawRoundRect(it, boxCornerRadius, boxCornerRadius, eraserPaint)
            eraserPaint.style = Paint.Style.STROKE
            canvas.drawRoundRect(it, boxCornerRadius, boxCornerRadius, eraserPaint)
            // Draws the box.
            canvas.drawRoundRect(it, boxCornerRadius, boxCornerRadius, boxPaint)
        }
    }
}

Then in the Analyzer i try to use the data about the boxRect in the way to check if the barcode boudings are contained:

@ExperimentalGetImage
override fun analyze(imageProxy: ImageProxy) {
    val mediaImage = imageProxy.image
    if (mediaImage != null && !isScanning) {
        val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

        isScanning = true
        scanner.process(image)
            .addOnSuccessListener { barcodes ->
                barcodes.firstOrNull().let { barcode ->
                    val bounds = barcode?.boundingBox // This is a Rect object
                    bounds?.let {
                        viewFinder?.let {
                            if (viewFinder.contains(bounds)) { // Here i check if the barcode is contained in the ViewFinder box
                                barcode.rawValue?.let { rawValue ->
                                    if (barcode.format == Barcode.FORMAT_CODE_39) {
                                        val code32 = Utils.code39ToCode32(rawValue)
                                        listener.onScanned(code32)
                                    } else {
                                        listener.onScanned(rawValue)
                                    }
                                }

                            }
                        }

                    }
                }

                isScanning = false
                imageProxy.close()
            }
            .addOnFailureListener {
                // Task failed with an exception
                // ...
                isScanning = false
                imageProxy.close()
            }
    }
}

Then in the fragment all is set in that way:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.overlay.post {
            binding.overlay.setViewFinder()
        }
        ...
    }

   private fun bindPreview() {
        val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.")
 
        ...

        val analyzer: ImageAnalysis.Analyzer = MLKitBarcodeAnalyzer(ScanningListener(), binding.overlay.getViewFinder())

        imageAnalysis.setAnalyzer(cameraExecutor, analyzer)

        ...

    }

The issue is that the .contains() check is never successful.

Update:

In a view like the one in the screenshot, the coordinates results like:

viewFinder: Rect(108, 647 - 972, 1376), bounds: Rect(751, 1673 - 1442, 2052)

enter image description here

  • Have you looked at the coordinates of the bounding box and the viewfinder to see if it’s just a matter of differences in scale or something like that?

    – 

  • @Michael the differenze if huge: viewFinder: Rect(108, 647 – 972, 1376), bounds: Rect(295, 1748 – 785, 2051) But i had to change the code as getViewFinder was returning null, so I’ve just updated the code where i build the Rect based on same data from setViewFinder.

    – 

Leave a Comment