1

I am a beginner developer. I'm sorry for my english. I am trying to make a barcode reader application. I use MLKit and CameraX. I want to analyze only the part of the preview that is in the rectangle. Now the preview is being analyzed in full. I only want to analize what is in the rectangle. I tried to use the ViewPort, but it seems I didn't quite understand what it was for, because it could not solve the problem. I looked for solutions on the Internet, but my problem remained relevant. I think that before analysis it is necessary to crop the image and only then analyze it, but is this true? My layout:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="false"
        tools:background="@android:color/white"
        tools:context=".ui.BarcodeScanningFragment">
    
        <androidx.camera.view.PreviewView
            android:id="@+id/viewFinder"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <com.t_ovchinnikova.android.scandroid_2.ui.ViewFinderOverlay
            android:id="@+id/overlay"
            android:layerType="software"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </FrameLayout>

Camera class:

    class CameraSource (private val overlay: ViewFinderOverlay) {

    private lateinit var cameraExecutor: ExecutorService

    val preview : Preview = Preview.Builder()
        .build()

    fun startCamera() {

        val cameraProviderFuture = ProcessCameraProvider.getInstance(overlay.context)

        cameraProviderFuture.addListener(Runnable {
            //Используется для привязки жизненного цикла камер к владельцу жизненного цикла
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            bindCameraUseCases(cameraProvider)
        }, ContextCompat.getMainExecutor(overlay.context))
    }

    @SuppressLint("UnsafeOptInUsageError")
    private fun bindCameraUseCases(cameraProvider: ProcessCameraProvider) {

        val imageAnalysis = ImageAnalysis.Builder()
            .setTargetResolution(Size(overlay.width, overlay.height))
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            .build()

        val orientationEventListener = object : OrientationEventListener(overlay.context) {
            override fun onOrientationChanged(orientation : Int) {
                // Monitors orientation values to determine the target rotation value
                val rotation : Int = when (orientation) {
                    in 45..134 -> Surface.ROTATION_270
                    in 135..224 -> Surface.ROTATION_180
                    in 225..314 -> Surface.ROTATION_90
                    else -> Surface.ROTATION_0
                }

                imageAnalysis.targetRotation = rotation
            }
        }
        orientationEventListener.enable()

        cameraExecutor = Executors.newSingleThreadExecutor()

        val analyzer = BarcodeAnalyzer()

        imageAnalysis.setAnalyzer(cameraExecutor, analyzer)

        var cameraSelector : CameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()

        val useCaseGroup = UseCaseGroup.Builder()
            .addUseCase(preview)
            .addUseCase(imageAnalysis)
            .build()

        cameraProvider.bindToLifecycle(overlay.context as LifecycleOwner, cameraSelector, useCaseGroup)
    }
}

class ViewFinderOverlay:

    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_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()

    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()
    }

    override fun draw(canvas: Canvas) {
        super.draw(canvas)
        boxRect?.let {

            canvas.drawRect(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat(), scrimPaint)

            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)
        }
    }
}

ViewFinderOverlay - a view that is superimposed on the camera preview. Screen: [1]: https://i.stack.imgur.com/cYvFP.jpg

Image analyzer:

        @SuppressLint("UnsafeOptInUsageError")
    override fun analyze(imageProxy: ImageProxy) {

        val mediaImage = imageProxy.image
        if (mediaImage != null) {
            val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

            val scanner = BarcodeScanning.getClient()

            scanner.process(image)
                .addOnSuccessListener { barcodes ->
                    barcodes?.firstOrNull().let { barcode ->
                        val rawValue = barcode?.rawValue
                        rawValue?.let {
                        }
                    }
                }
            imageProxy.close()
        }
    }

Help me please. I would be glad to any advice.

1 Answers1

0

You can take a look at LifecycleCameraController. It takes care of creating use cases as well as configuring ViewPort.

However AFAIK, MLKit doesn't respect the crop rect. What you can do is checking the MLKit result, and discard any barcode whose coordinates are outside of the crop rect.

Xi 张熹
  • 10,492
  • 18
  • 58
  • 86