实现二维码识别的流程
- 开启Camera
- 创建ImageReader
- 通过ImageReader获取surface对像
- 设置surface对象给camera并启动preview
- 当有preview数据产生时ImageReader的onImageAvailable回调会被调用
- 调用ImageReader 的acquireLatestImage()方法获取image数据
- 把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程序比较,这个实现还是比较简单的。并且没有过多的冗余内容,比较适合集成到应用中实现简单的二维码识别功能。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。