Camera2实现二维码扫描功能(qrcode scanner)

实现二维码识别的流程

  1. 开启Camera
  2. 创建ImageReader
  3. 通过ImageReader获取surface对像
  4. 设置surface对象给camera并启动preview
  5. 当有preview数据产生时ImageReader的onImageAvailable回调会被调用
  6. 调用ImageReader 的acquireLatestImage()方法获取image数据
  7. 把image数据传递给QRCodeReader进行识别

启动camera

这里我已经把Camera2封装成CameraHolder
,通过CameraHolder可以方便的操作Camera。首先生成一个CameraHolder的对象,然后在activity调用onCreate的时候开启camera。

    private val cameraHolder by lazy {
        CameraHolder(glSurfaceView.context)
    }
    
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        cameraHolder.cameraId = CAMERA_REAR//设置后置摄像头启动
        cameraHolder.open().invalidate()//启动camera
    }

定义QRCodeDecoder用于解析二维码数据

这个类还是比较简单的,首先需要生成一个ImageReader对象用于接收camera的preview的数据,然后还需要一个QRCodeReader对象用于解析二维码。我们创建ImageReader的时候需要指定图像的宽高和编码格式,我设置的编码格式为ImageFormat.YUV_420_888。编码格式的设置影响到ImageReader的输出Buffer内容。我们需要根据设置的编码格式从ImageReader输出的Buffer中读取图像数据。然后将Buffer中读取的数据传递给QRCodeReader进行解析。


class QRCodeDecoder(w: Int, h: Int, private val callback: (String) -> Unit) : ImageReader.OnImageAvailableListener {
    private val frameThread = HandlerThread("frame thread")
    private val frameHandler: Handler
    private val imageReader: ImageReader
    private val bufferByte: ByteArray
    private var qrCodeResult: String? = null
    private val qrCodeReader = QRCodeReader()
    private val hints: Hashtable<DecodeHintType, Any> = Hashtable<DecodeHintType, Any>()

    init {
        frameThread.start()
        frameHandler = Handler(frameThread.looper)
        imageReader = ImageReader.newInstance(w, h, ImageFormat.YUV_420_888, 2)
        imageReader.setOnImageAvailableListener(this, frameHandler)
        bufferByte = ByteArray(w * h * 2)
        hints[DecodeHintType.CHARACTER_SET] = "utf-8" // 设置二维码内容的编码
        hints[DecodeHintType.POSSIBLE_FORMATS] = BarcodeFormat.QR_CODE
        Log.d("QRCodeDecoder", " w:$w h:$h")
    }
    //当有解析结果后如果还需要继续解析时,我们需要调用这个方法进行重置。重置后decoder才可以继续工作。
    fun reset() {
        qrCodeResult = null
    }

    //camera启动preview的时候需要添加这个surface到camera用于接收数据
    fun getSurface(): Surface = imageReader.surface

    override fun onImageAvailable(reader: ImageReader?) {
        reader?.let {
            //获取image队列中最新的一条数据并释放老的数据
            val image = it.acquireLatestImage()
            if (TextUtils.isEmpty(qrCodeResult)) {
                var offset = 0
                //读取y数据
                image.planes[0].buffer.get(bufferByte, offset, image.planes[0].buffer.limit())
                offset += image.planes[0].buffer.limit()
                //读取u数据
                image.planes[1].buffer.get(bufferByte, offset, image.planes[1].buffer.limit())
                offset += image.planes[1].buffer.limit()
                //读取v数据
                image.planes[2].buffer.get(bufferByte, offset, image.planes[2].buffer.limit())
                //通过yuv数据buffer生成qrCodeReader的输入对象
                val source = PlanarYUVLuminanceSource(bufferByte, image.width, image.height, 0, 0, image.width, image.height, false)
                //二值化后的位图数据
                val tempBitmap = BinaryBitmap(HybridBinarizer(source))
                try {
                    qrCodeResult = qrCodeReader.decode(tempBitmap, hints)?.text
                    callback.invoke(qrCodeResult ?: "")
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
            //读完image数据后需要回收image。
            image.close()
        }
    }

    fun release() {
        imageReader.close()
        frameThread.quitSafely()
    }
}

启动camera的preview

QRCodeDecoder创建了一个ImageReader,我们可以将ImageReader的surface设置给camera用于接收数据。这里设置了两个surface,另一个surface用于将preview内容显示在手机屏幕上。为了提高二维码的识别速度,我将ImageReader的surface设置的更小一些。CameraHolder设置surface后自动进入preview模式。

nodesRender.runInRender {
            //屏幕显示用的preview最大为1920
            var size = cameraHolder.previewSizes.first { size -> size.width <= 1920 && size.height <= 1920 }
            updatePreviewNode(
                size.width,
                size.height
            )
            //QRCodeDecoder解析的图片尺寸最大为640
            size = cameraHolder.previewSizes.first { size -> size.width <= 640 && size.height <= 640 }
            qrCodeDecoder?.release()
            qrCodeDecoder = QRCodeDecoder(size.width, size.height) { ret ->
                //当二维码识别成功后这个回调会被调用
                showQRCodeResult(ret)
            }
            cameraHolder.setSurface(
                cameraPreviewNode!!.combineSurfaceTexture.surface,
                qrCodeDecoder?.getSurface()
            ).invalidate()
        }

总结

整个流程还是比较简单的,并且经过测试发现识别速度还是挺快的。跟zxing的demo程序比较,这个实现还是比较简单的。并且没有过多的冗余内容,比较适合集成到应用中实现简单的二维码识别功能。

git地址

https://github.com/mjlong123123/Render/tree/prototype/qrcode

我的公众号已经开通,公众号会同步发布。
欢迎关注我的公众号

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mjlong123123

你的鼓励时我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值