下面先看下识别效果
这个demo是直接基于官方demo修改而成的,有兴趣的小伙伴可以直接上官网下载去
一.获取图像数据
首先我们需要知道Camerax功能分成三个用例了,分别是:预览,分析,拍照,由于我们做的是二维码分析,所以只需要用到预览和分析两个用例
1.导入CameraX所需用到的包
//CameraX依赖模块
def camerax_version = '1.0.0-beta04'
implementation "androidx.camera:camera-core:$camerax_version"
// CameraX Camera2 拓展
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View
implementation 'androidx.camera:camera-view:1.0.0-alpha11'
2.布局,这边布局很简单就一个PreviewView
3.初始化相机,和Camera2比起来,确实少了很多代码,而且不需要自己管理相机的生命周期,属实有点舒服,这里权限申请什么的我就不细说了
private fun bindCameraUseCases() {
// 获取用于设置全屏分辨率相机的屏幕值
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
//获取使用的屏幕比例分辨率属性
val screenAspectRatio = aspectRatio(metrics.widthPixels / 2, metrics.heightPixels / 2)
val width = viewFinder.measuredWidth
val height = if (screenAspectRatio == AspectRatio.RATIO_16_9) {
(width * RATIO_16_9_VALUE).toInt()
} else {
(width * RATIO_4_3_VALUE).toInt()
}
val size = Size(width, height)
//获取旋转角度
val rotation = viewFinder.display.rotation
//和Fragment的生命周期绑定
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()//设置所选相机
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
// 预览用例
preview = Preview.Builder()
.setTargetResolution(size)
.setTargetRotation(rotation)
.build()
//拍照用例
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setTargetResolution(size)
.setTargetRotation(rotation)
.build()
// 图像分析用例
imageAnalyzer = ImageAnalysis.Builder()
.setTargetResolution(size)
.setTargetRotation(rotation)
.build()
.apply {//核心类BarCodeAnalyzer
setAnalyzer(cameraExecutor, BarCodeAnalyzer(object : OnBack {
override fun back(code: String) {
activity?.apply {
runOnUiThread {
Toast.makeText(this, code, Toast.LENGTH_SHORT).show()
}
}
}
}))
}
// 必须在重新绑定用例之前取消之前绑定
cameraProvider.unbindAll()
try {
//获取相机实例
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
//设置预览的view
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider())
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
这些代码是初始化相机的代码,因为是官方demo,所以比较详细,一共包含了preview,imageCupture,imageAnalyzer三个用例,分别是预览用例,拍照用例,分析用例,我们二维码分析只需要使用preview,imageAnalyzer用例,此时我们初始化的环节已经做好了,重点部分来了,我们来看看我们的测试用例类BaeCodeAnalyzer.class,代码如下
private class BarCodeAnalyzer(val onBack: OnBack) : ImageAnalysis.Analyzer {
private val reader: MultiFormatReader = initReader()
/**
* 将buffer写入数组
*/
private fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
override fun analyze(image: ImageProxy) {//图片分析
//如果不是yuv_420_888格式直接不处理
if (ImageFormat.YUV_420_888 != image.format) {
Log.e("BarcodeAnalyzer", "expect YUV_420_888, now = ${image.format}")
image.close()
return
}
//将buffer数据写入数组
val data = image.planes[0].buffer.toByteArray()
//获取图片宽高
val height = image.height
val width = image.width
//将图片旋转,这是竖屏扫描的关键一步,因为默认输出图像是横的,我们需要将其旋转90度
val rotationData = ByteArray(data.size)
Log.i(TAG, "rotationDataSize :${data.size} ## height:$height ## width:$width")
var j: Int
var k: Int
for (y in 0 until height) {
for (x in 0 until width) {
j = x * height + height - y - 1
k = x + y * width
rotationData[j] = data[k]
}
}
//zxing核心解码块,因为图片旋转了90度,所以宽高互换,最后一个参数是左右翻转
val source = PlanarYUVLuminanceSource(rotationData, height, width, 0, 0, height, width, false)
val bitmap = BinaryBitmap(HybridBinarizer(source))
try {
val result = reader.decode(bitmap)
Log.i("Resultkkk", ":扫码成功: ${result.text}")
onBack.back(result.text)
} catch (e: Exception) {
image.close()
} finally {
image.close()
}
}
private fun initReader(): MultiFormatReader {
val formatReader = MultiFormatReader()
val hints = Hashtable()
val decodeFormats = Vector()
//添加一维码解码格式
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS)
//这个不知道干啥的,可以不加
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS)
//添加二维码解码格式
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS)
hints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats
//设置解码的字符类型
hints[DecodeHintType.CHARACTER_SET] = "UTF-8"
//这边是焦点回调,就是找到那个条码的所在位置,这里我不处理
// hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK] = mPointCallBack
formatReader.setHints(hints)
return formatReader
}
}
我们目前只需要关注analyze这个方法,这是个抽象方法,这个方法的就类似于传统相机的Camera.PreviewCallback的回调,用于分析图片,这是个默认是个阻塞式方法,里面包含的ImageProxy就是我们需要使用的了,此时我们已经完成了图像数据获取了
二.分析利用zxing分析图像数据
1.初始化Reader解码器
这里我们设置一下我们需要解析的条码类型
private val reader: MultiFormatReader = initReader()
private fun initReader(): MultiFormatReader {
val formatReader = MultiFormatReader()
val hints = Hashtable()
val decodeFormats = Vector()
//添加一维码解码格式
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS)
//这个不知道干啥的,可以不加
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS)
//添加二维码解码格式
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS)
hints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats
//设置解码的字符类型
hints[DecodeHintType.CHARACTER_SET] = "UTF-8"
//这边是焦点回调,就是找到那个条码的所在位置,这里我不处理
// hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK] = mPointCallBack
formatReader.setHints(hints)
return formatReader
}
2.开始解析
在Analyze方法中,首先我们将ImageProxy包含的数据获取下来,此处默认返回类型是YUV_420_888,如果不是这种格式直接返回,如果是这种格式,那么我们获取它的数据写入到数组当中,并且获取宽高,此时注意我们写入的数据是横屏的图片数据
//如果不是yuv_420_888格式直接不处理
if (ImageFormat.YUV_420_888 != image.format) {
Log.e("BarcodeAnalyzer", "expect YUV_420_888, now = ${image.format}")
image.close()
return
}
//将buffer数据写入数组
val data = image.planes[0].buffer.toByteArray()
//获取图片宽高
val height = image.height
val width = image.width
将图片旋转90度
//将图片旋转,这是竖屏扫描的关键一步,因为默认输出图像是横的,我们需要将其旋转90度
val rotationData = ByteArray(data.size)
Log.i(TAG, "rotationDataSize :${data.size} ## height:$height ## width:$width")
var j: Int
var k: Int
for (y in 0 until height) {
for (x in 0 until width) {
j = x * height + height - y - 1
k = x + y * width
rotationData[j] = data[k]
}
}
此时我们获取到了rotationData,这就是旋转之后的数据了,我们现在开始使用我们的reader开始解析
//zxing核心解码块,因为图片旋转了90度,所以宽高互换,最后一个参数是左右翻转
val source = PlanarYUVLuminanceSource(rotationData, height, width, 0, 0, height, width, false)
val bitmap = BinaryBitmap(HybridBinarizer(source))
try {
val result = reader.decode(bitmap)
Log.i("Resultkkk", ":扫码成功: ${result.text}")
onBack.back(result.text)
} catch (e: Exception) {
image.close()
} finally {
image.close()
}
当没有异常抛出的时候,这时候我们就是解析成功了,此时大功告成。
项目地址:https://gitee.com/gongzhaoyang/demo.git