Android二维码原理与优化方向

困惑

做过Android的二维码扫描的童鞋可能会遇到过,很多二维码识别不了,或者识别速度慢。一般造成这个识别不出来的原因,大概以下几点:

  • Android手机配置不一样,手机像素高低也有不同,有的手机还不支持自动对焦
  • 环境影响也特别大,亮光,反光、粉尘、灰尘、距离都会导致识别不佳
  • A4纸张打印的标签二维码,本来打印就不是特别清晰,有些像素点,不一定都打印了出来

但是用微信扫一扫,却能很快的识别出上面几种情况造成的二维码;又或者用支付宝或者钉钉二维码扫描,一样也能识别出来;还有iOS也就是调用系统的扫描,也一样能够扫描出来,为啥我们自己的不行?老板不管这些,只是说了,别人的可以,为啥你的不可以,那就是你的问题…

于是网上找了很多各种几千个赞的第三方集成的二维码,发现也不怎么理想,总是比不上微信、支付宝等。Github上何种上千Star的第三方库都是基于ZXing或者ZBar,最后一圈下来你得出结论:ZXing和ZBar不行。你会想:这微信和支付宝都是基于啥开发的,如果能开源一下那就太好了。下面我们就聊一聊微信扫一扫与支付宝扫一扫的原理~

微信扫一扫

微信官方公众号“微信派”就特别介绍了微信二维码扫描功能的一些技术细节。

预判算法

微信扫码使用了自家开发的QBar引擎,并计入了预判算法,在识别条码之前会过滤无码图像,只识别有意义的内容(二维码和条形码)。

整个扫码预判模块位于核心识别引擎之前,不再需要对输入的视频中的每一帧图像进行检测识别,能实现快速过滤大量无码图像,减少后续不必要的定位和识别对扫码客户端造成的阻塞,使响应更加及时,增加扫码过程中的流畅度,而这就是微信扫码快速的关键原因。

微信团队分析数据显示,该引擎在识别正常图片时的解码速度,iOS可缩短至5毫秒,安卓也仅仅约12毫秒,当然这也和手机配置尤其是摄像头有很大关系。

容错性解码、多语言字符检测算法

QBar扫码引擎对二维码容错性解码算法、多语言字符检测算法等均使用进行了数十项优化,在识别率和识别速度上得到了提升。

一些二维码就算出现穿孔、污损或者弯折,还是一样可以识读,这是因为二维码中存储的信息通常都经过了纠错编码,是有冗余的。

一个二维码所能表示的比特数是固定的,包含的信息越多那么冗余度就越小,反之亦然。微信二维码中包含的信息量并不需要很大,这意味着编码的冗余度可以做得较高,所以即使损毁面积达到30%也依旧可以恢复。简单来说,QBar识别及解码的流程包括:

  • 读取视频或图像,通过灰度化处理得到单张灰度图;
  • 对灰度图进行处理得到二值图像(二值化是引擎在识别前把图像转换成01图像的过程);
  • 将二值图输入不同的解码器识别是否存在二维码;
  • 如果检测到存在某种编码,即通过相应的解码器进行解码,并返回解码结果。

在提高成功解码的概率上,微信的另一个做法就是给每个步骤做上“标签”,目的是找出错误信息,对失败信息再设置相应的二次检测流程,在失败的步骤处进行更”努力”地尝试,且越到后面的步骤,表明图中存在二维码的概率越大。

通过这样每步找错、多次识别解码,大大提高了手机扫描二维码的成功率。

什么是QBar

上面说的QBar好像很牛逼,微信又不开源,说的越牛逼越觉得坑爹:你这么牛逼我又用不了。

我们尝试着去窥探微信安装包,打开目录lib\armeabi。里面包涵了微信安卓应用所用到的C/C++动态链接库。大概扫一下,看到了libwechatQrMod.so,应该就是用于二维码的了。用atom打开,居然发现了这个:

在这里插入图片描述
微信的1D/2D barcode解码居然用的是开源的ZXing!突然对ZXing燃起了希望。没错,QBar的底层就是ZXing,不过微信团队做了非常多的优化。当然了,我们也是可以优化的,下一步可以好好研究优化方向了~~

支付宝扫一扫

支付宝扫一扫是基于libqrencode 库集成的,既然ZXing可以做到这个效果,libqrencode 就没必要再过多研究。

ZXing扫码优化

一般我们做二维码扫描的功能,会到https://github.com/zxing/zxing 拉代码,然后取出Android部分的demo运行,成功之后便开始移植到自己的工程。如果是这样,那么恭喜,你已经入坑了。官方的demo,扫码功能无可厚非是没问题的,但是因为是一个大而全的demo,更多考虑的是功能的集成。实际上我们每个项目的需求不同,当你的需求考虑上扫码速度与识别率的时候,官方的demo就会显得有点跟不上。你会发现:

微信扫一扫是基于ZXing,你的扫一扫也是基于ZXing,为啥识别速度与识别效率天差地别?

所以我们需要做大量的定制型优化

减少解码格式提高解码速度

ZXing默认支持15种格式,支持格式有QR Code、Aztec、Code 128、Code 39、EAN-8 等等。然后我们在实际中用不到这么多解码样式,我们常见的二维码格式是QR Code,一维码格式为Code 128, 如果无特殊要求,这两种格式就能满足一般的条码与二维码的需求。 在解码过程中减少一种解码,就会减少解析时间,提高解码速度。所以我们在实践过程中可以根据实际减少解码样式,提高解码速度,如果app实际只有二维码扫码,甚至可以只保留QR Code这一种解码格式。

ZXing 我们可以修改DecodeFormatManager 及DecodeThread这两个类减少解码种类

//DecodeFormatManager.java 只保留二维码相关
static {
   
	...
    QR_CODE_FORMATS = new Vector<BarcodeFormat>(1);
    QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);
    ...
}
//DecodeThread.java 只保留二维码相关
if (decodeFormats == null || decodeFormats.isEmpty()) {
   
    decodeFormats = new Vector<BarcodeFormat>();
    decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
}

解码算法优化

目前我们在Zxing我们能看到HybridBinarizer及GlobalHistogramBinarizer,HybridBinarizer继承自GlobalHistogramBinarizer,在其基础上做了功能改进。这两个类都是Binarizer的实现类,都是基于二值化,将图片的色域变成黑白两个颜色,然后提取图形中的二维码矩阵。

官网上介绍GlobalHistogramBinarizer算法适合低端设备,对手机CPU和内存要求不高。但它选择了全部的黑点来计算,因此无法处理阴影和渐变这两种情况。HybridBinarizer的算法在执行效率上要慢于GlobalHistogramBinarizer算法,但识别相对更加有效,它专门以白色为背景的连续黑块二维码图像解析而设计,也更适合来解析更具有严重阴影和渐变的二维码图像。

zxing项目官方默认使用的是HybridBinarizer二值化方法。然而目前的大部分二维码都是黑色二维码,白色背景的。不管是二维码扫描还是二维码图像识别,使用GlobalHistogramBinarizer算法的效果要稍微比HybridBinarizer好一些,识别的速度更快,对低分辨的图像识别精度更高。可以在DecodeHandler 中更改算法:

private void decode(byte[] data, int width, int height) {
   
    //优先GlobalHistogramBinarizer解码,解码失败转为HybridBinarizer解码
    BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
    if(bitmap  == null){
   
    	bitmap = new BinaryBitmap(new HybridBinarizer(source));
    }
}

便提下,微信扫码使用了自家开发基于ZXing的QBar引擎,并导入了预判算法,在识别条码之前会过滤无码图像,只识别有意义的内容——二维码和条形码。整个扫码预判模块位于核心识别引擎之前,不再需要对输入的视频中的每一帧图像进行检测识别,能实现快速过滤大量无码图像,减少后续不必要的定位和识别对扫码客户端造成的阻塞,使响应更加及时,增加扫码过程中的流畅度,而这就是微信扫码快速的关键原因。

减少解码数据

现在的手机拍照的照片像素都很高,目前市场上好一点手机像素都上千万,拍摄一张照片的就十几M, 这个大的数据量对解码很有压力,我们在开发过程有必要采取措施减少解码数据量。

官方为了减少解码的数据,提高解码效率和速度,利用扫码区域范围来裁剪裁剪无用区域,减少解码数据。我们在开发过程可以调整好扫码区域,减少解码的数据量。

private void decode(byte[] data, int width, int height) {
   
   //只识别的识别框的区域
	scanBoxAreaRect = mScanBoxView.getScanBoxAreaRect(height);
	PlanarYUVLuminanceSource  = new PlanarYUVLuminanceSource(
		data, 
		width, 
		height, 
		scanBoxAreaRect.left, 
		scanBoxAreaRect.top, 
		scanBoxAreaRect.width(), 
		scanBoxAreaRect.height(), 
		false
	);
}

将处理相机帧从串行改为并行

在这里插入图片描述
ZXing的demo每次从onPreviewFrame()中获取一帧数据,发送R.id.decode的handler消息队列,然后调用zxing的decode解析二维码,如果成功,则返回;如果失败,则调用setOneShotPreviewCallback( ),重新调用一次onPreviewFrame( )。

缺点是如果处理一帧数据时间很长,会阻碍下一帧的处理,比如上一帧是没有二维码的,而下一帧是有二维码的,如果上一帧处理时间较长,那么虽然用户对准了二维码,但是实际处理的还是上一帧,因此不太合理。

//DecodeHandler.java
@Override
public void handleMessage(Message message) {
   
    if (message.what == R.id.decode) {
   
        decode((byte[]) message.obj, message.arg1, message.arg2);
    }
}

我们将串行处理改成并行处理,一旦从onPreviewFrame( )获取一帧数据,将decode任务丢进线程池,并立即调用setOneShotPreviewCallback( )获取下一帧数据。一旦某个任务检测到二维码,立即将isSuccess变量置为true,忽略其他任务。这样能够大大加快二维码检测的速度。

@Override
public void onPreviewFrame(final byte[] data, final Camera camera) {
   
    ...
    mProcessDataTask = new ProcessDataTask(camera, data, this,HDQRCodeUtil.isPortrait(getContext())).perform();
}
评论 57
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值