CameraX 下使用 OpenCV 微信二维码识别

Android OpenCV 专栏收录该内容
68 篇文章 24 订阅

前言

前面,我们已经介绍了两种集成 wechat_qrcode 微信二维码识别能力的做法:

  • 完整编译 OpenCV 和 OpenCV Contrib
  • Native C++ 单独集成 wechat_qrcode 模块

这两种方式的预览和识别均是基于 OpenCV 提供的 JavaCamera2View。

今天介绍一下如何通过 CameraX 框架调用 wechat_qrcode 模块能力。

CameraX 基本使用

CameraX 具有以下最低版本要求:

  • Android API 级别 21
  • Android 架构组件 1.1.1

添加依赖

allprojects {
    repositories {
        google()
        jcenter()
    }
}
compileOptions {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
    jvmTarget = "1.8"
}
val cameraXVersion = "1.0.0-rc05"
implementation ("androidx.camera:camera-core:${cameraXVersion}")
implementation("androidx.camera:camera-camera2:$cameraXVersion")
implementation("androidx.camera:camera-lifecycle:$cameraXVersion")
implementation("androidx.camera:camera-view:1.0.0-alpha20")
implementation("androidx.camera:camera-extensions:1.0.0-alpha24")

预览

添加PreviewView至布局

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

请求 CameraProvider并检查可用性

/** Initialize CameraX, and prepare to bind the camera use cases  */
private fun setUpCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
    cameraProviderFuture.addListener({
        // CameraProvider
        cameraProvider = cameraProviderFuture.get()
        // Build and bind the camera use cases
        bindCameraUseCases()
    }, ContextCompat.getMainExecutor(this))
}

选择相机并绑定声明周期和用例

val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()

// Preview
preview = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation)
    .build()
……
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try {
    camera = cameraProvider.bindToLifecycle(
        this, cameraSelector, preview, imageCapture, imageAnalyzer
    )
    preview?.setSurfaceProvider(mBinding.viewFinder.surfaceProvider)
} catch (exc: Exception) {
    Log.e(App.TAG, "Use case binding failed", exc)
}

拍摄照片

创建用例并绑定生命周期

// ImageCapture
imageCapture = ImageCapture.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation)
    .build()
……
camera = cameraProvider.bindToLifecycle(
                this, cameraSelector, preview, imageCapture, imageAnalyzer
            )
……

执行拍照

private fun takePhoto() {
    // Get a stable reference of the modifiable image capture use case
    val imageCapture = imageCapture ?: return

    // Create time-stamped output file to hold the image
    val photoFile = File(
        outputDirectory,
        SimpleDateFormat(
            FILENAME_FORMAT, Locale.US
        ).format(System.currentTimeMillis()) + ".jpg"
    )

    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

    imageCapture.takePicture(
        outputOptions,
        ContextCompat.getMainExecutor(this),
        object : ImageCapture.OnImageSavedCallback {
            override fun onError(exc: ImageCaptureException) {
                Log.e(App.TAG, "Photo capture failed: ${exc.message}", exc)
            }

            override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                val savedUri = Uri.fromFile(photoFile)
                val msg = "Photo capture succeeded: $savedUri"
                Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                Log.d(App.TAG, msg)
            }
        })
}

图片分析

图片分析用例为您的应用提供可供 CPU 访问的图片来执行图片处理、计算机视觉或机器学习推断。应用会实现对每个帧运行的 analyze 方法。

实现Analyzer接口

public interface Analyzer {
    void analyze(@NonNull ImageProxy image);
}
private class WeChatAnalyzer(
    private val weChatQRCode: WeChatQRCode,
    private val listener: (results: List<String>) -> Unit
) : ImageAnalysis.Analyzer {
    override fun analyze(image: ImageProxy) {
        Log.d(App.TAG, "size = ${image.width} * ${image.height}")
        Log.d(App.TAG, "rotationDegrees = ${image.imageInfo.rotationDegrees}")

        val rectangles = ArrayList<Mat>()
        val results = weChatQRCode.detectAndDecode(gray(image), rectangles)
        listener(results)
        image.close()
    }
    ……
}

创建用例并绑定生命周期

// ImageAnalysis
imageAnalyzer = ImageAnalysis.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation)
    .build()
    // The analyzer can then be assigned to the instance
    .also {
        it.setAnalyzer(
            cameraExecutor,
            WeChatAnalyzer(
                mWeChatQRCode,
            ) { results ->
                runOnUiThread {
                    if (results.isNotEmpty()) {
                        Toast.makeText(this, results.toString(), Toast.LENGTH_SHORT).show()
                        finish()
                    }
                }
            })
    }

……
camera = cameraProvider.bindToLifecycle(
                this, cameraSelector, preview, imageCapture, imageAnalyzer
            )
……

识别二维码

识别接口采用上一篇文章内通过 Native C++ 方式单独集成的微信二维码识别接口。识别过程主要分为两个步骤:

  • 数据格式转换
  • 调用识别API

数据格式转换

CameraX 中的 analyze 接口传递给我们的数据封装在 ImageProxy 类中,数据格式为 YUV_420_888 格式。YUV_420_888 格式是一种 YCbCr 的泛化格式,能够表示任何 4:2:0 的平面和半平面格式,每个分量用 8 bits 表示。带有这种格式的图像使用3个独立的Buffer表示,每一个Buffer表示一个颜色平面(Plane)。而我们的接口参数需要传入的是 Mat 类型,所以需要将 YUV_420_888 格式的数据转换为 Mat。

fun gray(image: ImageProxy): Mat {
    val planeProxy = image.planes // 获取分量数组
    val width = image.width // 获取宽高
    val height = image.height
    val yPlane = planeProxy[0].buffer // 获取亮度分量方便我们转为灰度图
    val yPlaneStep = planeProxy[0].rowStride // 获取步长
    return Mat(height, width, CvType.CV_8UC1, yPlane, yPlaneStep.toLong())
}

调用API

val results = weChatQRCode.detectAndDecode(gray(image), rectangles)

效果

扫码结果

源码

https://github.com/onlyloveyd/LearningAndroidOpenCV

  • 2
    点赞
  • 4
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值