Android二维码的优化大法

关键时刻,第一时间送达!

现如今zxing是作为普遍二维码库扫描封装的较好的库了,但是我们都知道zxing代码是开源的,是可以更改的,那么我就来看看要提高识别率的问题要怎么处理?

扫描精度问题:

PlanarYUVLuminanceSource

官方的解码程序主要是下边这段代码:

再来看看YUV亮度源是怎么构造的,在CameraManager里,首先获取预览图像的聚焦框矩形getFramingRect(),这个聚焦框的矩形大小是根据屏幕的宽高值来做计算的,官方定义了最小和最大的聚焦框大小,分别是240240和1200675,即最多的聚焦框大小为屏幕宽高的5/8。获取屏幕的聚焦框大小后,还需要做从屏幕分辨率到相机分辨率的转换才能得到预览聚焦框的大小,这个转换在getFramingRectInPreview()里完成。这样便完成了亮度源的构造。

解决办法:

解决办法很简单,就是不仅仅使用聚焦框里的图像数据,而是采用全幅图像的数据。

DecodeHintType

在使用zxing解析二维码时,允许事先进行相关配置,这个文件通过Map键值对来保存,然后使用方法public void setHints(Map hints)来设置到相应的解码器中。DecodeHintType是一个枚举类,其中有几个重要的枚举值。

POSSIBLE_FORMATS(List.class)

TRY_HARDER(Void.class)

是否使用HARDER模式来解析数据,如果启用,则会花费更多的时间去解析二维码,对精度有优化,对速度则没有。

CHARACTER_SET(String.class)

解析的字符集。这个对解析也比较关键,最好定义需要解析数据对应的字符集。

如果项目仅仅用来解析二维码,完全没必要支持所有的格式,也没有必要使用MultiFormatReader来解析。所以在配置的过程中,我移除了所有与二维码不相关的代码。直接使用QRCodeReader类来解析,字符集采用utf-8,使用Harder模式,并且把可能的解析格式只定义为BarcodeFormat.QR_CODE,这对于直接二维码扫描解析无疑是帮助最大的。

二维码识别经度探究:

图像/像素编码格式

Android相机预览的时候支持几种不同的格式,从图像的角度(ImageFormat)来说有NV16、NV21、YUY2、YV12、RGB_565和JPEG,从像素的角度(PixelFormat)来说,有YUV422SP、YUV420SP、YUV422I、YUV420P、RGB565和JPEG,它们之间的对应关系可以从Camera.Parameters.cameraFormatForPixelFormat(int)方法中得到。

目前大部分Android手机摄像头设置的默认格式是yuv420sp,其原理可参考文章《图文详解YUV420数据格式》。编码成YUV的所有像素格式里,yuv420sp占用的空间是最小的。既然如此,zxing当然会考虑到这种情况。因此针对YUV编码的数据,有PlanarYUVLuminanceSource这个类去处理,而针对RGB编码的数据,则使用RGBLuminanceSource去处理。在下节介绍的图像识别算法中我们可以知道,大部分二维码的识别都是基于二值化的方法,在色域的处理上,YUV的二值化效果要优于RGB,并且RGB图像在处理中不支持旋转。因此,一种优化的思路是讲所有ARGB编码的图像转换成YUV编码,再使用PlanarYUVLuminanceSource去处理生成的结果。

Android中读取一张图片一般是通过BitmapFactory.decodeFile(imgPath, options)这个方法去得到这张图片的Bitmap数据,Bitmap是由ARGB值编码得到的,因此如果需要转换成YUV,还需要做一点小小的变换。

这里面有几个坑,在方法里已经列出来了。首先,如果每次都生成新的YUV数组,不知道在扫一扫解码时要进行GC多少次。。。所以就采用了静态的数组变量来存储数据,只有当当前的长宽乘积超过数组大小时,才重新生成新的yuvs。其次,如果鉴于YUV的特性,长宽只能是偶数个像素点,否则可能会造成数组溢出(不信可以尝试)。最后,使用完了Bitmap要记得回收,那玩意吃内存不是随便说说的。

二维码图像识别算法选择

二维码扫描精度和许多因素有关,最关键的因素是扫描算法。目前在图形识别领域中,较常用的二维码识别算法主要有两种,分别是HybridBinarizer和GlobalHistogramBinarizer,这两种算法都是基于二值化,即将图片的色域变为黑白两个颜色,然后提取图形中的二维码矩阵。实际上,zxing中的HybridBinarizer继承自GlobalHistogramBinarizer,并在此基础上做了功能性的改进。援引官方介绍:

This Binarizer(GlobalHistogramBinarizer) implementation uses the old ZXing global histogram approach. It is suitable for low-end mobile devices which don’t have enough CPU or memory to use a local thresholding algorithm. However, because it picks a global black point, it cannot handle difficult shadows and gradients. Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.

This class(HybridBinarizer) implements a local thresholding algorithm, which while slower than the GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for high frequency images of barcodes with black data on white backgrounds. For this application, it does a much better job than a global blackpoint with severe shadows and gradients. However it tends to produce artifacts on lower frequency images and is therefore not a good general purpose binarizer for uses outside ZXing. This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already inherently local, and only fails for horizontal gradients. We can revisit that problem later, but for now it was not a win to use local blocks for 1D. ···

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

网上对这两种算法的解析并不多,目前仅找到一篇文章详解了GlobalHistogramBinarizer算法,详见http://kuangjianwei.blog.163.com/blog/static/190088953201361015055110/。有时间再看一下相关源码。

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

除了这两种算法,我相信在图像识别领域肯定还有更好的算法存在,目前受限于知识水平,对二值化算法这一块还比较陌生,期待以后能够深入理解并改进目前的开源算法(^__^)

图像大小对识别精度的影响

这点是测试中无意发现的。现在的手机摄像头拍照出现的照片像素都很高,动不动就1200W像素,1600W像素,甚至是2000W都不稀奇,但照片的成像质量不一定高。将一张高分辨率的图片按原分辨率导入Android手机,很容易产生OOM。我们来计算一下,导入一张1200W像素的图片需要的内存,假设图片是4000px3000px,如果导入的图片采用ARGB_8888编码形式,则每个像素需要占用4个Bytes(分别存储ARGB值)来存储,则需要40003000*4bytes=45.776MB的内存,这在有限的移动资源里,显然是不能忍受的。

通过上一节对图像算法的简单研究,在GlobalHistogramBinarizer中,是从图像中均匀取5行(覆盖整个图像高度),每行取中间五分之四作为样本;以灰度值为X轴,每个灰度值的像素个数为Y轴建立一个直方图,从直方图中取点数最多的一个灰度值,然后再去给其他的灰度值进行分数计算,按照点数乘以与最多点数灰度值的距离的平方来进行打分,选分数最高的一个灰度值。接下来在这两个灰度值中间选取一个区分界限,取的原则是尽量靠近中间并且要点数越少越好。界限有了以后就容易了,与整幅图像的每个点进行比较,如果灰度值比界限小的就是黑,在新的矩阵中将该点置1,其余的就是白,为0。(摘自zxing源码分析——QR码部分)

根据算法的实现,可以知道图像的分辨率对二维码的取值是有影响的。并不是图像的分辨率越高就越容易取到二维码。高分辨率的图像对Android的内存资源占用也很可怕。所以在测试的过程中,我尝试将图片压缩成不同大小分辨率,然后再进行图片的二维码识别。

Android图片优化需要通过在解析图片的时候,设置BitmapFactory.Options.inSampleSize的值,根据比例压缩图片大小。在进行图片二维码解析的线程中,通过设置不同的图片大小,来测试二维码的识别率。这个测试过程我忘记保存了,只记得测试了压缩成最大宽高值为2048、1024、512、256和128像素的包含二维码的图片,但实际的测试结果是,当MAXPICTURE_PIXEL=256的时候识别率最高。

此结论不具备理论支持,有兴趣的童鞋可以自己动手尝试。

相机预览倍数设置及聚焦时间调整

如果使用zxing默认的相机配置,会发现需要离二维码很近才能够识别出来,但这样会带来一个问题——聚焦困难。解决办法就是调整相机预览倍数以及减小相机聚焦的时间。

通过测试可以发现,每个手机的最大放大倍数几乎是不一样的,这可能和摄像头的型号有关。如果设置成一个固定的值,那可能会产生在某些手机上过度放大,某些手机上放大的倍数不够。索性相机的参数设定里给我们提供了最大的放大倍数值,通过取放大倍数值的N分之一作为当前的放大倍数,就完美地解决了手机的适配问题。

zxing默认的相机聚焦时间是2s,可以根据扫描的视觉适当调整。聚焦时间的调整也很简单,在AutoFocusCallback这个类里,调整AUTO_FOCUS_INTERVAL_MS这个值就可以了。

二维码扫描视觉调整

二维码扫描视觉的绘制在ViewfinderView.java完成,官方是继承了View然后在onDraw()方法中实现了视图的绘制。

我给它做了一点小改变,效果差不多,代码更简洁一些。由于代码中我不是根据屏幕的宽高动态计算聚焦框的大小,因此这里省去了从CameraManager获取FramingRect和FramingRectInPreview这两个矩形的过程。我在聚焦框外加了四个角,目前大部分二维码产品基本都是这么设计的吧,当然也可以使用图片来代替。总之视觉定制是因人而异,这里不做过多介绍。

在As中引入Zxing开源库需要注意的一些问题:

在eclipse时代,我们通常使用jar包的方式导入zxing的包,但是在As中,如果当前As的gradle编译版本比较高级,那么它们会自动携带Zxing的开源库,此时如果不把之前引入的jar包移除,就会报jar包冲突问题。

解决方法:

1.移除原来的zxing 的jar包

2.在Project.gradle文件中引入zxing最新的开源库

3.如果gradle构建版本过低,导致没法找到google原生的zxing库,也可以去网上下载最新的zxing使用jar包,按原来导入jar包的方式导入。

总结

使用zxing进行二维码的编解码是非常方便的,zxing的API覆盖了多种主流编程语言,具有良好的扩展性和可定制性。文中进行了二维码基本功能介绍,zxing项目基本使用方法,zxing项目中目前存在的缺点及改进方案,以及自己在进行zxing项目二次开发的摸索过程中总结出的提高二维码扫描的方法。文中还有许多不足的地方,对源码的理解还不够深,特别是二维码解析关键算法(GlobalHistogramBinarizer和HybridBinarizer)。这些算法需要投入额外的时间去理解,对于目前以业务为导向的App开发来说,还存在优化的空间,期待将来有一天能像微信的二维码扫描一样快速,精确。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180309B0EBV200?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券