简体   繁体   English

用于二维码扫描的 Android ML Kit 库:如何通过降低图像分辨率来提高检测性能

[英]Android ML Kit library for QR code scanning: How to increase detection performance by reducing image resolution

This is my stripped down sourcecode for barcode scanning这是我用于条码扫描的精简源代码

build.gradle构建.gradle

dependencies {
    .....
     // MLKit Dependencies
    implementation 'com.google.android.gms:play-services-vision:20.1.3'

    implementation 'com.google.mlkit:barcode-scanning:17.0.2'
    def camerax_version = "1.1.0-beta01"
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
    implementation "androidx.camera:camera-lifecycle:${camerax_version}"
    implementation "androidx.camera:camera-video:${camerax_version}" 
    ......
}

ScanCameraFragment.kt ScanCameraFragment.kt

class ScanCameraFragment : BaseFragment() {
    private lateinit var binding: FragmentScanCameraBinding
    private lateinit var cameraExecutor: ExecutorService

    //region Lifecycle Methods
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?): View? {
        binding = FragmentScanCameraBinding.inflate(inflater, container, false)
        cameraExecutor = Executors.newSingleThreadExecutor()

        startCamera()
        return binding.root
    }

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

    companion object {
        fun newInstance() = ScanCameraFragment().apply {}
    }

    private fun startCamera() {
        context?.let { context ->
            val cameraProviderFuture = ProcessCameraProvider.getInstance(context)

            cameraProviderFuture.addListener({
                val cameraProvider = cameraProviderFuture.get()

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

                // Image analyzer
                val imageAnalyzer = ImageAnalysis.Builder()
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .build()
                    .also {
                        it.setAnalyzer(cameraExecutor,
                            QrCodeAnalyzer(context, binding.barcodeBoxView,
                                      binding.previewView.width.toFloat(), 
                                      binding.previewView.height.toFloat()
                            )
                        )
                    }

                // Select back camera as a default
                val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

                try {
                    // Unbind use cases before rebinding
                    cameraProvider.unbindAll()

                    // Bind use cases to camera
                    var camera = cameraProvider.bindToLifecycle(this, cameraSelector,
                                      preview, imageAnalyzer)

                } catch (exc: Exception) {
                    exc.printStackTrace()
                }
            }, ContextCompat.getMainExecutor(context))
        }
    }
}

QRCodeAnalyzer.kt二维码分析器.kt

class QrCodeAnalyzer(private val context: Context, 
    private val barcodeBoxView: BarcodeBoxView, private val previewViewWidth: Float,
    private val previewViewHeight: Float) : ImageAnalysis.Analyzer {

    private var scaleX = 1f
    private var scaleY = 1f

    private fun translateX(x: Float) = x * scaleX
    private fun translateY(y: Float) = y * scaleY

    private fun adjustBoundingRect(rect: Rect) = RectF(
        translateX(rect.left.toFloat()),
        translateY(rect.top.toFloat()),
        translateX(rect.right.toFloat()),
        translateY(rect.bottom.toFloat())
    )

    @SuppressLint("UnsafeOptInUsageError")
    override fun analyze(image: ImageProxy) {
        val img = image.image
        if (img != null) {
            // Update scale factors
            scaleX = previewViewWidth / img.height.toFloat()
            scaleY = previewViewHeight / img.width.toFloat()

            val inputImage = InputImage.fromMediaImage(img, 
                                image.imageInfo.rotationDegrees)

            // Process image searching for barcodes
            val options = BarcodeScannerOptions.Builder()
                .build()
            val scanner = BarcodeScanning.getClient(options)
            scanner.process(inputImage)
                .addOnSuccessListener { barcodes ->
                    for (barcode in barcodes) {
                        barcode?.rawValue?.let {
                            if (it.trim().isNotBlank()) {
                                Scanner.updateBarcode(it)
                                barcode.boundingBox?.let { rect ->
                                    barcodeBoxView.setRect(adjustBoundingRect(rect))
                                }
                            }
                            return@addOnSuccessListener
                        }
                    }
                    // coming here means no satisfiable barcode was found
                    barcodeBoxView.setRect(RectF())
                }
                .addOnFailureListener {
                    image.close()
                }
                .addOnFailureListener { }
        }

        image.close()
    }
}

This code works and I am able to scan barcodes.此代码有效,我能够扫描条形码。 But sometimes, the barcode detection is slow.但有时,条码检测速度很慢。 The documentation says one way to increase performance is to limit the image resolution. 文档说提高性能的一种方法是限制图像分辨率。

Don't capture input at the camera's native resolution.不要以相机的原始分辨率捕获输入。 On some devices, capturing input at the native resolution produces extremely large (10+ megapixels) images, which results in very poor latency with no benefit to accuracy.在某些设备上,以原始分辨率捕获输入会产生非常大的(10+ 百万像素)图像,这会导致非常差的延迟,并且不会提高准确性。 Instead, only request the size from the camera that's required for barcode detection, which is usually no more than 2 megapixels.相反,只向相机请求条码检测所需的尺寸,通常不超过 2 兆像素。

If scanning speed is important, you can further lower the image capture resolution.如果扫描速度很重要,您可以进一步降低图像捕获分辨率。 However, bear in mind the minimum barcode size requirements outlined above.但是,请记住上述最低条码尺寸要求。

Unfortunately, the documentation doesn't specify how to reduce the image resolution.不幸的是,文档没有说明如何降低图像分辨率。 And some of my end users are using high end devices with powerful camera, so we assume the poor performance is because of the image size.我的一些最终用户正在使用配备强大摄像头的高端设备,因此我们认为性能不佳是因为图像大小。

How can I reduce the resolution of the image to a fixed value (something like 1024 x 768) rather than the default camera resolution?如何将图像的分辨率降低到固定值(例如 1024 x 768)而不是默认的相机分辨率?

You can set it on the imageAnalyzer builder bij using .setTargetResolution(Size)您可以使用 .setTargetResolution(Size) 在 imageAnalyzer builder bij 上设置它

val imageAnalysisUseCaseBuilder = ImageAnalysis.Builder()
imageAnalysisUseCaseBuilder.setTargetResolution(Size(1024, 768))
    
imageAnalysisUseCase = imageAnalysisUseCaseBuilder.build()

or in you case或者在你的情况下

val imageAnalyzer = ImageAnalysis.Builder()
    .setTargetResolution(Size(1024, 768))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
    .also {
        it.setAnalyzer(cameraExecutor,
            QrCodeAnalyzer(context, binding.barcodeBoxView,
                binding.previewView.width.toFloat(), 
                binding.previewView.height.toFloat()
            )
        )
    }

User HarmenH's answer correctly tells how to set the image resolution, so I am not repeating it here.用户 HarmenH 的回答正确地告诉了如何设置图像分辨率,所以我在这里不再重复。

As it turns out, the performance issue on my end was not because of image resolution.事实证明,我的性能问题不是因为图像分辨率。 It seems I was closing the imageProxy prematurely.看来我过早地关闭了 imageProxy。

override fun analyze(image: ImageProxy) {
    val img = image.image
    if (img != null) {
        // Update scale factors
        scaleX = previewViewWidth / img.height.toFloat()
        scaleY = previewViewHeight / img.width.toFloat()

        val inputImage = InputImage.fromMediaImage(img,
            image.imageInfo.rotationDegrees)

        // Process image searching for barcodes
        val options = BarcodeScannerOptions.Builder()
            .build()
        val scanner = BarcodeScanning.getClient(options)
        scanner.process(inputImage)
            .addOnSuccessListener { barcodes - >
                for (barcode in barcodes) {
                    barcode?.rawValue?.let {
                       if (it.trim().isNotBlank()) {
                           Scanner.updateBarcode(it)
                           barcode.boundingBox?.let { rect - >
                              barcodeBoxView.setRect(adjustBoundingRect(rect))
                           }
                       }
                       return @addOnSuccessListener
                    }
                }
                // coming here means no satisfiable barcode was found
                barcodeBoxView.setRect(RectF())
            }
            .addOnFailureListener {
                image.close()
            }
            .addOnFailureListener {
                //added this here.
                image.close()
            }
    }
    //Removed this because we don't close the 
    //imageProxy before analysis completes
    //image.close()
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM