1

实现二维码识别的流程

  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/mjlong1231...


mjlong123
4 声望3 粉丝