Android 无法在overlay Surfaceview上的正确位置获取条形码边界框

Android 无法在overlay Surfaceview上的正确位置获取条形码边界框,android,barcode-scanner,firebase-mlkit,android-camerax,google-mlkit,Android,Barcode Scanner,Firebase Mlkit,Android Camerax,Google Mlkit,我用我的CameraX和Firebase MLKit条形码阅读器来检测条形码。应用程序可以毫无问题地识别条形码。但我正在尝试添加边界框,它在CameraX预览中实时显示条形码区域。从条形码检测器功能中检索边界框信息。但它没有正确的位置和大小,正如你在下面看到的 这是我的活动布局 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:

我用我的CameraX和Firebase MLKit条形码阅读器来检测条形码。应用程序可以毫无问题地识别条形码。但我正在尝试添加边界框,它在CameraX预览中实时显示条形码区域。从条形码检测器功能中检索边界框信息。但它没有正确的位置和大小,正如你在下面看到的

这是我的活动布局

<?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>
interface BarcodeDetectListener {
    fun onBarcodeDetect(code: String, codeBound: Rect, imageWidth: Int, imageHeight: Int)
}
barcodeDetectListener
是对我创建的接口的引用,该接口用于将此数据传回我的活动

<?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>
interface BarcodeDetectListener {
    fun onBarcodeDetect(code: String, codeBound: Rect, imageWidth: Int, imageHeight: Int)
}
在我的主要活动中,我将这些数据发送到
overlysurfaceholder
,它实现了
SurfaceHolder.Callback
。此类负责在覆盖的
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
类中进行计算

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

                }
            }
        }

    }
}

有人能指出我做错了什么吗?

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

  • 调整xCoordinates时,如果previewWidth为0,则直接返回valueX.toFloat()。你能在日志中添加一些东西来看看它是否真的属于这种情况吗?另外,添加一些日志来打印分析和预览维度也会有所帮助

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


  • 希望或多或少能有所帮助。

    如果从位图中检测,您的重新定位方法将在我尝试时正确。

    我不再处理此项目。然而,我开发了一个使用Camera2API的camera应用程序。在该应用程序中,需要使用MLKit对象检测库检测对象,并在相机预览顶部显示这样的边界框。首先面对像这样的问题,并设法使其最终发挥作用。我将把我的方法留在这里。它可能会帮助某人

    与相机预览图像相比,任何检测库都将在小分辨率图像中执行检测过程。当检测库返回检测到的对象的组合时,我们需要放大以在正确的位置显示它。这叫做比例因子。为了简化计算,最好选择相同纵横比的分析图像大小和预览图像大小

    您可以使用下面的函数获得任意大小的纵横比

    fun gcd(a: Long, b: Long): Long {
        return if (b == 0L) a else gcd(b, a % b)
    }
        
    fun asFraction(a: Long, b: Long): Pair<Long,Long> {
        val gcd = gcd(a, b)
        return Pair((a / gcd) , b / gcd)
    }
    
    最后,当你有这两个值时,你可以像下面那样计算比例因子

    @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()
                    }
    
            }
        }  
    
    val previewFraction = DisplayUtils
                          .asFraction(previewSize!!.width.toLong(),previewSize!!.height.toLong())
        
    val analyzeImageSize = characteristics
                       .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
                       .getOutputSizes(ImageFormat.YUV_420_888)
                       .filter { DisplayUtils.asFraction(it.width.toLong(), it.height.toLong()) == previewFraction }
                       .sortedBy { it.height * it.width}
                       .first()
    
    val scaleFactor = previewSize.width / analyzedSize.width.toFloat()
    

    最后,在绘制边界框之前,将每个点与比例因子相乘,以获得正确的屏幕坐标。

    嘿,你找到解决方案了吗?我面临着同样的问题。不,我在处理MLKit对象检测库的同样问题。如果我在那里有任何进展,我会发布。酷,如果我有什么进展,我也会发布。谢谢:)