android - 无法在叠加 Surfaceview 上的正确位置获得条形码边界框

标签 android barcode-scanner firebase-mlkit android-camerax google-mlkit

我将我的 CameraX 与 Firebase MLKit 条形码阅读器结合使用来检测条形码。应用 毫无问题地识别条形码。但我正在尝试添加边界框,它在 CameraX 预览中实时显示条形码区域。从条形码检测器函数中检索边界框信息。但是它没有正确的位置和大小,如下所示。

enter image description here

这是我的 Activity 布局。

<?xml version="1.0" encoding="utf-8"?>
<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">

    <Button
        android:id="@+id/camera_capture_button"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginBottom="50dp"
        android:scaleType="fitCenter"
        android:text="Take Photo"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:elevation="2dp" />

    <SurfaceView
        android:id="@+id/overlayView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

SurfaceView 就是用来绘制这个矩形的。

条形码检测发生在实现ImageAnalysis.AnalyzerBarcodeAnalyzer 类中。在覆盖的 analyze 函数中,我检索条形码数据,如下所示。

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

        val mediaImage = imageProxy.image

        val rotationDegrees = degreesToFirebaseRotation(imageProxy.imageInfo.rotationDegrees)

        if (mediaImage != null) {

            val analyzedImageHeight = mediaImage.height
            val analyzedImageWidth = mediaImage.width

            val image = FirebaseVisionImage
                .fromMediaImage(mediaImage,rotationDegrees)

            detector.detectInImage(image)
                .addOnSuccessListener { barcodes ->

                    for (barcode in barcodes) {
                        val bounds = barcode.boundingBox
                        val corners = barcode.cornerPoints
                        val rawValue = barcode.rawValue

                        if(::barcodeDetectListener.isInitialized && rawValue != null && bounds != null){
                            barcodeDetectListener.onBarcodeDetect(
                                rawValue,
                                bounds,
                                analyzedImageWidth,
                                analyzedImageHeight
                            )
                        }
                    }

                    imageProxy.close()

                }
                .addOnFailureListener {
                    Log.e(tag,"Barcode Reading Exception: ${it.localizedMessage}")
                    imageProxy.close()
                }
                .addOnCanceledListener {
                    Log.e(tag,"Barcode Reading Canceled")
                    imageProxy.close()
                }

        }
    }  

barcodeDetectListener 是对我创建的用于将此数据传回我的 Activity 的接口(interface)的引用。

interface BarcodeDetectListener {
    fun onBarcodeDetect(code: String, codeBound: Rect, imageWidth: Int, imageHeight: Int)
}

在我的主要 Activity 中,我将这些数据发送到实现 SurfaceHolder.CallbackOverlaySurfaceHolder。此类负责在叠加的 SurfaceView 上绘制边界框。

override fun onBarcodeDetect(code: String, codeBound: Rect, analyzedImageWidth: Int,
                                 analyzedImageHeight: Int) {

        Log.i(TAG,"barcode : $code")
        overlaySurfaceHolder.repositionBound(codeBound,previewView.width,previewView.height,
            analyzedImageWidth,analyzedImageHeight)
        overlayView.invalidate()

    }

正如您在此处看到的,我正在发送叠加的 SurfaceView 宽度和高度,以便在 OverlaySurfaceHolder 类中进行计算。

OverlaySurfaceHolder.kt

class OverlaySurfaceHolder: SurfaceHolder.Callback {

    var previewViewWidth: Int = 0
    var previewViewHeight: Int = 0
    var analyzedImageWidth: Int = 0
    var analyzedImageHeight: Int = 0

    private lateinit var drawingThread: DrawingThread
    private lateinit var barcodeBound :Rect

    private  val tag = OverlaySurfaceHolder::class.java.simpleName

    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {

    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {

        var retry = true
        drawingThread.running = false

        while (retry){
            try {
                drawingThread.join()
                retry = false
            } catch (e: InterruptedException) {
            }
        }
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
        drawingThread = DrawingThread(holder)
        drawingThread.running = true
        drawingThread.start()
    }

    fun repositionBound(codeBound: Rect, previewViewWidth: Int, previewViewHeight: Int,
                        analyzedImageWidth: Int, analyzedImageHeight: Int){

        this.barcodeBound = codeBound
        this.previewViewWidth = previewViewWidth
        this.previewViewHeight = previewViewHeight
        this.analyzedImageWidth = analyzedImageWidth
        this.analyzedImageHeight = analyzedImageHeight
    }

    inner class DrawingThread(private val holder: SurfaceHolder?): Thread() {

        var running = false

        private fun adjustXCoordinates(valueX: Int): Float{

            return if(previewViewWidth != 0){
                (valueX / analyzedImageWidth.toFloat()) * previewViewWidth.toFloat()
            }else{
                valueX.toFloat()
            }
        }

        private fun adjustYCoordinates(valueY: Int): Float{

            return if(previewViewHeight != 0){
                (valueY / analyzedImageHeight.toFloat()) * previewViewHeight.toFloat()
            }else{
                valueY.toFloat()
            }
        }

        override fun run() {

            while(running){

                if(::barcodeBound.isInitialized){

                    val canvas = holder!!.lockCanvas()

                    if (canvas != null) {

                        synchronized(holder) {

                            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

                            val myPaint = Paint()
                            myPaint.color = Color.rgb(20, 100, 50)
                            myPaint.strokeWidth = 6f
                            myPaint.style = Paint.Style.STROKE

                            val refinedRect = RectF()
                            refinedRect.left = adjustXCoordinates(barcodeBound.left)
                            refinedRect.right = adjustXCoordinates(barcodeBound.right)
                            refinedRect.top = adjustYCoordinates(barcodeBound.top)
                            refinedRect.bottom = adjustYCoordinates(barcodeBound.bottom)

                            canvas.drawRect(refinedRect,myPaint)
                        }

                        holder.unlockCanvasAndPost(canvas)

                    }else{
                        Log.e(tag, "Cannot draw onto the canvas as it's null")
                    }

                    try {
                        sleep(30)
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    }

                }
            }
        }

    }
}

谁能指出我做错了什么?

最佳答案

我没有很清楚的线索,但这里有一些你可以尝试的东西:

  1. 调整X坐标时,如果previewWidth为0,则直接返回valueX.toFloat()。您能否添加一些日志记录以查看它实际上属于这种情况?另外添加一些日志来打印分析和预览维度也可能会有帮助。

  2. 另一件值得注意的事情是,您发送到检测器的图像可能与预览 View 区域具有不同的纵横比。例如,如果您的相机拍摄一张 4:3 的照片,它会将它发送到检测器。但是,如果您的查看区域是 1:1,它会裁剪部分照片以显示在那里。在这种情况下,您在调整坐标时也需要考虑到这一点。根据我的测试,图像将适合基于 CENTER_CROP 的 View 区域。如果您想非常小心,可能值得检查相机开发站点中是否记录了这一点。

希望对您有所帮助,或多或少。

关于android - 无法在叠加 Surfaceview 上的正确位置获得条形码边界框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61932528/

相关文章:

java - 当用户触摸屏幕时如何避免关闭我的进度对话框?

android - 触发几个高优先级通知后 Firebase 推送通知延迟

android - 如何实时获取拨出电话时长?

c++ - Zebra KBTools MC92N0 键盘重新映射 C++ -- "error.h"没有这样的文件或目录

cordova - 电话间隙 : BarcodeScanner Sharing Plugin

android - 将 TensorFlow python 代码与 android 应用程序一起使用

java - 在上下文中获取 NullPointerException

android - 从库项目注册 GCM

java - 带有 TextWatcher 的 EditText 显示有线行为并在递归循环中移动

android - Firebase ML Kit : "Internal Error" Exception, 但良好的输入和良好的配置