QR代码扫描的Android ML套件库:如何通过减少图像分辨率来提高检测性能

发布于 2025-02-10 22:36:40 字数 5878 浏览 2 评论 0原文

这是我用于条形码扫描

build.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

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

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

此代码有效的,我可以扫描barodes。但是有时,条形码检测很慢。 文档说提高性能的一种方法是限制限制图像分辨率。

不要以相机的本地分辨率捕获输入。在某些方面 设备,在本机分辨率上捕获输入非常生产 大型(10百万像素)图像,导致延迟非常差 对准确性无益。相反,仅要求相机的大小 这是条形码检测所需的,通常不超过2 百万像素。

如果扫描速度很重要,则可以进一步降低图像 捕获分辨率。但是,请记住最小条形码大小 上面概述的要求。

不幸的是,该文档未指定如何减少图像分辨率。我的一些最终用户正在使用具有功能强大的相机的高端设备,因此我们认为性能差是由于图像尺寸。

如何将图像的分辨率减少到固定值(例如1024 x 768),而不是默认的摄像头分辨率?

This is my stripped down sourcecode for barcode scanning

build.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

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

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. Instead, only request the size from the camera
that's required for barcode detection, which is usually no more than 2
megapixels.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

人事已非 2025-02-17 22:36:40

您可以使用ImageAnalyzer Builder Bij将其设置
.setTargetResolution(size)

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

或在您的情况下

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

You can set it on the imageAnalyzer builder bij using
.setTargetResolution(Size)

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()
            )
        )
    }
随遇而安 2025-02-17 22:36:40

用户Harmenh的答案正确地讲述了如何设置图像分辨率,因此我在这里不重复。

事实证明,我最后的性能问题不是因为图像解决。看来我过早地关闭了图像。

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

User HarmenH's answer correctly tells how to set the image resolution, so I am not repeating it here.

As it turns out, the performance issue on my end was not because of image resolution. It seems I was closing the imageProxy prematurely.

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()
}
楠木可依 2025-02-17 22:36:40

将您的image -proxy转换为位图,然后它将快速起作用。不要忘记关闭图像代理

if (image.image != null && image.format == ImageFormat.YUV_420_888) {
                


                scanner.process(InputImage.fromBitmap(image.toBitmap(), image.imageInfo.rotationDegrees))
                    .addOnSuccessListener { barcodes ->
                        if (barcodes.isNotEmpty()) {

                            if (firstCall){


                                for (barcode in barcodes) {
                                    

                                    // Handle received barcodes...


                                    Log.d("ValueXXXX ",barcode.format.toString())

                                    if (barcode.format==Barcode.FORMAT_QR_CODE){
                                        var data = barcode.displayValue.toString().replace("\\", "")
                                        //var json=org.json.JSONObject(data)

                                        Log.d("TAG","DATA1111 "+data)

                                        if(Utils.urlPattern.matcher(data).matches()) {
                                            firstCall=false


                                            showPopupForInvalidQRCode(R.string.DISPLAY_INVALID_QR_CODE,context)



                                            break
                                        }
                                        if (!data.isNullOrEmpty() && Utils.isJSONValid(data)){
                                            firstCall=false
                                            try {
                                                val json = JSONObject(data)
                                                Log.d("TAG","json "+json)
                                                val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
                                                    if (Build.VERSION.SDK_INT >= 26) {
                                                        vibrator.vibrate(VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE))
                                                    } else {
                                                        vibrator.vibrate(300)
                                                    }

                                                 //parse your json here   

                                                break
                                            }catch (e:Exception){
                                                e.printStackTrace()
                                                showPopupForInvalidQRCode(R.string.DISPLAY_INVALID_QR_CODE,context)
                                            }
                                        }

                                        break
                                    }



                                }
                            }

                        } else {
                            // Remove bounding rect
                            barcodeBoxView.setRect(RectF())
                        }
                    }
                    .addOnFailureListener {
                        firstCall=true
                        Log.d("EXCEPTION",it.message.toString())
                        //image.close()

                    }
                    

                    }
            }

Convert your ImageProxy into bitmap then it will work fast.and do not forget to closde image proxy

if (image.image != null && image.format == ImageFormat.YUV_420_888) {
                


                scanner.process(InputImage.fromBitmap(image.toBitmap(), image.imageInfo.rotationDegrees))
                    .addOnSuccessListener { barcodes ->
                        if (barcodes.isNotEmpty()) {

                            if (firstCall){


                                for (barcode in barcodes) {
                                    

                                    // Handle received barcodes...


                                    Log.d("ValueXXXX ",barcode.format.toString())

                                    if (barcode.format==Barcode.FORMAT_QR_CODE){
                                        var data = barcode.displayValue.toString().replace("\\", "")
                                        //var json=org.json.JSONObject(data)

                                        Log.d("TAG","DATA1111 "+data)

                                        if(Utils.urlPattern.matcher(data).matches()) {
                                            firstCall=false


                                            showPopupForInvalidQRCode(R.string.DISPLAY_INVALID_QR_CODE,context)



                                            break
                                        }
                                        if (!data.isNullOrEmpty() && Utils.isJSONValid(data)){
                                            firstCall=false
                                            try {
                                                val json = JSONObject(data)
                                                Log.d("TAG","json "+json)
                                                val vibrator = context?.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
                                                    if (Build.VERSION.SDK_INT >= 26) {
                                                        vibrator.vibrate(VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE))
                                                    } else {
                                                        vibrator.vibrate(300)
                                                    }

                                                 //parse your json here   

                                                break
                                            }catch (e:Exception){
                                                e.printStackTrace()
                                                showPopupForInvalidQRCode(R.string.DISPLAY_INVALID_QR_CODE,context)
                                            }
                                        }

                                        break
                                    }



                                }
                            }

                        } else {
                            // Remove bounding rect
                            barcodeBoxView.setRect(RectF())
                        }
                    }
                    .addOnFailureListener {
                        firstCall=true
                        Log.d("EXCEPTION",it.message.toString())
                        //image.close()

                    }
                    

                    }
            }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文