前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CameraX 封装二维码扫描组件

CameraX 封装二维码扫描组件

作者头像
逮虾户
发布2024-01-27 10:11:38
2160
发布2024-01-27 10:11:38
举报
文章被收录于专栏:逮虾户

简介

cameraX已经出来有一段时间了,现在已经从alpha版本到现在的beta3版本。其中内部的代码版本跨度特别大,而且资料相对来说只有官方的demo比较可以参考,所以最近完成了项目的开发之后,把经验分享一下提供给各位。

仓库地址

究极花里胡哨camreaX二维码扫描

二维码扫描小优化

去除zxing额外支持的格式(有争议的点,其实并没有特别大的差距)

MultiFormatReader的decodeWithState()是使用方的入口方法,内部调用了decodeInternal(),输入是相机的一帧数据,如果抛了NotFoundException,则表示没找到二维码;如果返回了Result,则表示找到了二维码,并解析完成。 其中,readers变量是一个数组,数组的大小表示支持的条码格式个数,zxing原本因为支持很多格式,因此这个数组长度比较长。当拿到相机的一帧数据后,需要去检测是否是所有支持格式的某一个格式,每一种格式的检测都需要花费一些时间,因此这个遍历对于我们是不必要的。如果将zxing内部定制成只支持QR Code格式,那么就免去了额外的格式检测。

扫描区域放大到全局

去除项目中的扫描区域,将图像识别区域放大到整张区域,这样增加了二维码的边界情况,不需要特意的对准屏幕的扫描区域。

将相机升级到jetpack的CameraX

谷歌已经在官方提供了对于camera2的整合包,集成在CamreaX,而且CameraX内部有对于图片分析的接口,所以我们在这个接口中会对原来的二维码扫描进行一次转移,然后构建一个线程池专门去处理二维码扫描的分析器。

代码语言:javascript
复制
class CameraXModule(private val view: AutoZoomScanView) {

private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
private var preview: Preview? = null
private var imageAnalyzer: ImageAnalysis? = null
private lateinit var cameraExecutor: ExecutorService
private var camera: Camera? = null
private lateinit var qrCodeAnalyzer: QRCodeAnalyzer
private lateinit var mLifecycleOwner: LifecycleOwner


fun bindWithCameraX(function: (Result) -> Unit, lifecycleOwner: LifecycleOwner) {
    mLifecycleOwner = lifecycleOwner
    val metrics = DisplayMetrics().also { view.display.getRealMetrics(it) }
    Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
    val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
    Log.i(TAG, "Preview aspect ratio: $screenAspectRatio")
    val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
    val cameraProviderFuture = ProcessCameraProvider.getInstance(view.context)
    cameraProviderFuture.addListener(
        Runnable {
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            // Preview
            val width = (view.measuredWidth * 1.5F).toInt()
            val height = (width * screenAspectRatio).toInt()
            preview = Preview.Builder()
                // We request aspect ratio but no resolution
                .setTargetResolution(Size(width, height))
                // Set initial target rotation
                .build()
            preview?.setSurfaceProvider(view.preView.createSurfaceProvider(null))

            cameraExecutor = Executors.newSingleThreadExecutor()
            qrCodeAnalyzer = QRCodeAnalyzer(this) { function(it) }
            // ImageAnalysis
            imageAnalyzer = ImageAnalysis.Builder()
                // We request aspect ratio but no resolution
                .setTargetResolution(Size(width, height))
                // Set initial target rotation, we will have to call this again if rotation changes
                // during the lifecycle of this use case
                .build()
                // The analyzer can then be assigned to the instance
                .also {
                    it.setAnalyzer(cameraExecutor, qrCodeAnalyzer)
                }

            // Must unbind the use-cases before rebinding them
            cameraProvider.unbindAll()

            try {
                // A variable number of use-cases can be passed here -
                // camera provides access to CameraControl & CameraInfo

                camera = cameraProvider.bindToLifecycle(
                    mLifecycleOwner, cameraSelector, preview, imageAnalyzer
                )
                qrCodeAnalyzer.camera = camera
                qrCodeAnalyzer.preview = preview
                setFocus(view.width.toFloat() / 2, view.height.toFloat() / 2)
                // camera?.cameraControl?.startFocusAndMetering(FocusMeteringAction.FLAG_AF)
                // Attach the viewfinder's surface provider to preview use case
            } catch (exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(view.context)
    )
}

fun setFocus(x: Float, y: Float) {
    val factory: MeteringPointFactory = SurfaceOrientedMeteringPointFactory(
        view.width.toFloat(), view.height.toFloat()
    )
    //create a point on the center of the view
    val autoFocusPoint = factory.createPoint(x, y)

    camera?.cameraControl?.startFocusAndMetering(
        FocusMeteringAction.Builder(
            autoFocusPoint,
            FocusMeteringAction.FLAG_AF
        ).apply {
            //auto-focus every 1 seconds
            setAutoCancelDuration(1, TimeUnit.SECONDS)
        }.build()
    )
}

private fun aspectRatio(width: Int, height: Int): Double {
    val previewRatio = max(width, height).toDouble() / min(width, height)
    if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
        return RATIO_4_3_VALUE
    }
    return RATIO_16_9_VALUE
}

@SuppressLint("RestrictedApi")
fun setZoomRatio(zoomRatio: Float) {
    if (zoomRatio > getMaxZoomRatio()) {
        return
    }
    val future: ListenableFuture<Void>? = camera?.cameraControl?.setZoomRatio(
        zoomRatio
    )
    future?.apply {
        Futures.addCallback(future, object : FutureCallback<Void?> {
            override fun onSuccess(result: Void?) {}
            override fun onFailure(t: Throwable) {}
        }, CameraXExecutors.directExecutor())
    }
}

fun getZoomRatio(): Float {
    return camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
}

fun getMaxZoomRatio(): Float {
    return camera?.cameraInfo?.zoomState?.value?.maxZoomRatio ?: 0F
}

fun stopCamera() {
    //   camera?.cameraControl?.
}

internal fun resetAnalyzer() {
    qrCodeAnalyzer.resetAnalyzer()
}

companion object {
    private const val TAG = "CameraXImp"
    private const val RATIO_4_3_VALUE = 4.0 / 3.0
    private const val RATIO_16_9_VALUE = 16.0 / 9.0
}
}

上述代码基于的是CameraX内的CameraView,其中的构建的宽高必须基于4:3或者16:9的格式。

自动放大

当二维码很小很远时,自动放大能大大加快检测二维码的速度。QRCodeReader的decode()是二维码检测的主方法,分为两步:

(1)大致判断是否存在二维码;

代码语言:javascript
复制
    val source = PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false)
    val binarizer = HybridBinarizer(source)
    val bitmap = BinaryBitmap(binarizer)
    val detectorResult = Detector(bitmap.blackMatrix).detect(map)
代码语言:javascript
复制
   private fun calculateDistance(resultPoint: Array<ResultPoint>): Int {
    val point1X = resultPoint[0].x.toInt()
    val point1Y = resultPoint[0].y.toInt()
    val point2X = resultPoint[1].x.toInt()
    val point2Y = resultPoint[1].y.toInt()
    return sqrt(
        (point1X - point2X.toDouble()).pow(2.0) + (point1Y - point2Y.toDouble()).pow(2.0)
    ).toInt()
}

先要获取到当前区域内是否存在二维码,其次计算二维码的距离。

(2)所以我们需要做的就是先检测该图像区域内是否有一个二维码,同时计算二维码的大小,和图像比例进行一次大小换算,如果发现二维码过小的情况下,自动放大图片区域。

代码语言:javascript
复制
 private fun zoomCamera(points: Array<ResultPoint>, image: BinaryBitmap): Boolean {
    val qrWidth = calculateDistance(points) * 2
    val imageWidth = image.blackMatrix.width.toFloat()
    val zoomInfo = camera?.cameraInfo?.zoomState?.value
    zoomInfo?.apply {
        if (qrWidth < imageWidth / 8) {
            Log.i("BarcodeAnalyzer", "resolved!!! = $qrWidth  imageWidth:${imageWidth}")
            val maxScale = zoomInfo.maxZoomRatio
            val curValue = zoomInfo.zoomRatio
            val gap = maxScale - curValue
            val upgradeRatio = if (gap / 4F * 3 > 3F) 3F else maxScale / 4F * 3
            module.setZoomRatio(curValue + upgradeRatio)
            return true
        }
    }
    return false
}

双击放大

当前二维码扫描中没有调整焦距的功能,所以我们在这次调整中对其进行了一次双击放大的开发。

通过监控双击事件实现对应监听。

代码语言:javascript
复制
 private val gestureDetector =
    GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
        override fun onDoubleTap(e: MotionEvent?): Boolean {
            cameraXModule.setZoomRatio(cameraXModule.getZoomRatio() + 1)
            return super.onDoubleTap(e)
        }

        override fun onSingleTapUp(e: MotionEvent?): Boolean {
            e?.apply {
                cameraXModule.setFocus(x, y)
            }
            return super.onSingleTapUp(e)
        }
    })

单击对焦

当前的对焦模式采取的是自动对焦,我们对对焦进行了一次增强,单击制定位置之后会对该区域进行一次对焦。

参考上面代码

简单使用

  1. 引入依赖
代码语言:javascript
复制
implementation 'com.github.leifzhang:QrCodeLibrary:0.0.1'
  1. 在布局xml中加入AutoZoomScanView
代码语言:javascript
复制
    <com.kronos.camerax.qrcode.AutoZoomScanView
        android:id="@+id/scanView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
  1. 先申请camera权限并绑定lifecycle
代码语言:javascript
复制
    AndPermission.with(this)
            .runtime()
            .permission(Permission.Group.CAMERA)
            .onGranted { permissions: List<String?>? ->
                scanView.bindWithLifeCycle(this@MainActivity)
            }
            .onDenied { permissions: List<String?>? -> }
            .start()
  1. 二维码结果回调,之后重新打开分析逻辑
代码语言:javascript
复制
 scanView.setOnQrResultListener { view: View, s: String ->
            Toast.makeText(
                this@MainActivity, s,
                Toast.LENGTH_LONG
            ).show()
            scanView.reStart()
        }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • 仓库地址
  • 二维码扫描小优化
  • 简单使用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档