Android平台中要实现二维码扫描功能的话,最常用的开源库要推zxing和zbar了。不过zbar已经好几年没有更新了,而zxing由Google开源并持续维护,所以本文就选择采用zxing来实现二维码扫描功能。
依赖
在zxing的github主页上查看接入指南,发现只有maven的依赖导入
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>(the current version)</version>
</dependency>
在Android工程中一般都是通过gradle管理依赖,所以根据maven和gradle的依赖管理规则对应关系,我们通过如下方式导入最新的库:
implementation "com.google.zxing:core:3.4.0"
因为墙的原因,依赖库可能下载不下来,我们可以从zxing的github主页中将core这个目录copy到自己的工程中,也可以去下载core.jar包
非相机应用
非相机app中要引入二维码扫描功能的话,zxing的使用是非常简单的,Google已经做了很完善的封装。除了core库的引入外,我们只需要将android目录copy到自己的工程,或者根据自己的需求单独引入android目录下的代码文件和资源文件
在需要打开扫描界面的地方直接跳转到CaptureActivity
Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
startActivityForResult(intent, REQUEST_CODE_SCAN);
在onActivityResult的回调中即可获取扫描内容
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_SCAN && resultCode == RESULT_OK) {
if (data != null) {
//返回的文本内容
String content = data.getStringExtra(DECODED_CONTENT_KEY);
//返回的Bitmap图像
Bitmap bitmap = data.getParcelableExtra(DECODED_BITMAP_KEY);
}
}
}
当然还需要动态申请相机权限,注册activity等
自定义相机
在自定义相机中,我们如何通过zxing实现二维码扫描功能呢?
流程分析
我们先参考一下官方的封装,看看整个流程是如何实现的。
在android/camera目录下看到,关于camera的封装采用的是camera1的api,拿到每一帧的预览数据后发送到解码线程去做识别。识别二维码核心代码为DecodeHandler#decode方法:
/**
* Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
* reuse the same reader objects from one decode to the next.
*
* @param data The YUV preview frame.
* @param width The width of the preview frame.
* @param height The height of the preview frame.
*/
private void decode(byte[] data, int width, int height) {
// ...省略部分代码
PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
if (source != null) {
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
try {
rawResult = multiFormatReader.decodeWithState(bitmap);
} catch (ReaderException re) {
// continue
} finally {
multiFormatReader.reset();
}
}
// ...省略部分代码
}
可以看到识别过程主要分为四个步骤:
第一步,构建Source,将数据源转为灰度图;
// 通过YUV进行二维码识别使用PlanarYUVLuminanceSource,
// 可以传一个Rect进行裁剪,对裁剪区进行识别以提高速度
PlanarYUVLuminanceSource(yuvData, width, height, top, left, width, height, false)
// 通过RGB进行二维码识别的话使用RGBLuminanceSource,
// 这个类在构造方法中将RGB转为灰度图
luminances[offset] = (byte) ((r + g2 + b) / 4);
第二步,选择识别算法;
目前在图形识别领域中,较常用的二维码识别算法主要有两种:
这两种算法都是基于二值化,即将图片的色域变为黑白两个颜色,然后提取图形中的二维码矩阵。
zxing中的HybridBinarizer继承自GlobalHistogramBinarizer,并在此基础上做了一些改进;
第三步,将二维码矩阵转为位图;
第四步,识别
以上的流程梳理清楚了,要在自定义相机中实现二维码功能就很简单了。
自定义扫描View
扫描UI主要需要绘制三个部分:半透明背景,扫描框和扫描条。
扫描框一般都是和相机预览界面居中对齐,如果我们需要在扫描框内做二维码识别的话,就需要根据扫描框的位置对预览YUV进行裁剪,为了方便映射UI和预览区域进行计算,就偷懒啦
,所以我们的ScannerView会根据previewSize重新测量宽高,最后的效果如下(扫描条没截到,就这样吧。。。):
启动预览
打开Camera,启动预览的步骤参考Android Camera2详解
获取预览YUV数据
Camera2中获取预览YUV数据参考Android Camera2中如何获取预览YUV数据
二维码扫描 将每一帧预览数据按照之前分析的四个步骤进行就ok了,
核心代码:
val yuvData = ByteArray(width * height * 3 / 2)
CommonUtil.readYuvDataToBuffer(image, ImageFormat.NV21, yuvData)
image.close()
val frameRect = qrScannerView.getFrameRect()
// 此处需要注意的是,预览YUV数据是横屏的,UI是竖屏的
// 所以在扫描框和预览区域居中对齐的时候,
// 裁剪区域的left,top参数为扫描框rect的top,left
val planarYUVLuminanceSource = PlanarYUVLuminanceSource(yuvData, width, height,
frameRect.top, frameRect.left, frameRect.width(), frameRect.height(),
false)
val binaryBitmap = BinaryBitmap(HybridBinarizer(planarYUVLuminanceSource))
var decodeResult: Result? = null
try {
decodeResult = multiFormatReader?.decodeWithState(binaryBitmap)
} catch (ex: ReaderException) {
// not found, throw ReaderException
} finally {
multiFormatReader?.reset()
}
Log.d(TAG, "decode result = ${decodeResult?.text}")
如果想要拿到识别到的二维码图片的话,通过Source对象获取:
val pixels = planarYUVLuminanceSource.renderThumbnail()
val thumbnailWidth = planarYUVLuminanceSource.thumbnailWidth
val thumbnailHeight = planarYUVLuminanceSource.thumbnailHeight
val bitmap = Bitmap.createBitmap(pixels, 0, thumbnailWidth,
thumbnailWidth, thumbnailHeight, Bitmap.Config.ARGB_8888)
DEMO
传送门:
https://github.com/sifutang/Camera2BasicKotlin.git