专栏首页sickworm二维码扫描开源库ZXing定制化

二维码扫描开源库ZXing定制化

最近在用ZXing这个开源库做二维码的扫描模块,开发过程的一些代码修改和裁剪的经验和大家分享一下。

建议:

如果需要集成到自己的app上,而不是做一个demo,不推荐用ZXing的Android外围开发模块,只用核心的core目录的代码就好了。android和android-core的代码设计的不好,扩展性太差了(我在后期开发新需求的时候改修改了很多CameraManager的逻辑)。只使用core目录的集成方法很简单,参考:

https://github.com/zxing/zxing/blob/master/android/src/com/google/zxing/client/android/DecodeHandler.java

中的decode函数,把摄像头数据转换成二值化图像,然后传入MultiFormatReader解码。

以下是正文:

我的代码库:(基于官方3.2.0)

https://github.com/SickWorm/ZXingDialog

代码没有在github维护,所以没有log。但是所有修改的地方我都加上了“@ch”的注释,以方便定位

官方源码:

https://github.com/zxing/zxing

实现功能:

1、功能裁剪(只保留QRCode二维码扫描功能,去掉条形码等其他码扫描功能)

2、移除资源依赖,提供Dialog形式的扫码功能

3、API兼容(源码只兼容4.0以上,现兼容至2.1)

4、转换为竖屏(源码为横屏)

5、扫码速度优化(主要分三点,现只完成了一点)

6、设备兼容(针对低分辨率设备)

本文还会提到:

7、自定义界面

8、优化调试方法

1、建立工程

ZXing源码并没有提供一个完整的实例工程给我们使用,构建一个工程我们需要源码下的三个文件夹的文件:

core/

android-core/

android/

大概步骤如下:

1、创建一个新工程

2、把android目录下的所有文件覆盖到新工程(内含有资源文件和AndroidManifest.xml等构建app所需的文件)

3、把android-core所有Java文件拷入到src目录下(注意!android-core中的src文件夹需要进行一些改动,原来路径是android-coresrcmainjavacomgooglezxingclientandroidcamera,我们要把中间的mainjava两层文件夹去掉,不然在Eclipse中无法识别包路径)

4、把core目录下的所有Java文件拷入到src目录下(注意!和步骤3一样需要去掉mainjava两层文件夹)。这样ZXing已经可以运行了,我的src目录是这样的:

可以直接运行,效果还不错。如果你遇到一些错误,有可能是编译的JDK版本低于1.7导致的。源码里使用了ArrayList<>这样的写法,1.7以前是不支持的。你可以选择修改源码或者提高编译JDK版本。

但你可能不满足于这个界面,扫描框太大了,而且是横屏全屏的,还要求API 15(Android 4.0.3)。下面我们会对这些需求进行修改。

2、代码优化

1、功能裁剪(只保留QRCode二维码扫描功能,去掉条形码等其他码扫描功能)

我的目标是只保留二维码识别,不需要其他多余的功能。这一部分的步骤我不打算详细说明,因为我已经不记得了。。大家可以直接看我的代码的结果。

可以直接删掉的是:

com.google.zxing.aztec.**  aztec格式的二维码 com.google.zxing.client.android.book.* Google 图书相关的功能 com.google.zxing.client.android.clickboard.*  不清楚,复制黏贴? com.google.zxing.client.android.encode.*  用于生成各种码 com.google.zxing.client.android.history.*  保存扫码记录 com.google.zxing.client.android.result.**  扫码应用功能相关的功能性代码 com.google.zxing.client.android.share.*  分享功能 com.google.zxing.client.android.wifi.*  WiFi相关,不清楚具体用途 com.google.zxing.datamatrix.**  datamatrix格式二维码 com.google.zxing.maxicode.**  maxicode格式二维码 com.google.zxing.multi.**  貌似是用于多格式支持的?我没有用到这个包,如果有了解的麻烦告知 com.google.zxing.oned.**  one dimension一维码,也就是条形码(你去百度搜oned会发现奇怪的东西。。) com.google.zxing.pdf417.**  PDF417格式条形码

需要修改的是:

com.google.zxing.client.android.CaptureActivity:去掉其他功能的相关代码,只保留核心功能,即扫码功能。界面为一个FrameLayout里面包含一个SurfaceView。代码移除就不详细说了,直接看上传的代码吧,这个文件我参考了http://www.cnblogs.com/keyindex/archive/2011/06/08/2074900.html这个链接里的CaptureActivity的修改。

com.google.zxing.MultiFormatReader:这个是指定支持解码的格式,需要把除QR_CODE以外的格式全部去掉,否则会因为删掉了解码包而报错。具体也请看上传的代码。

com.google.zxing.client.result.ProductResultParser:parse函数中,同上。

com.google.zxing.client.android.DecodeThread:构造器中,同上。

另外:

com.google.zxing.client.result 我没有删减这个包的代码,应该也是能优化的

2、移除资源依赖,提供Dialog形式的扫码功能

经过了第1步的精简,其实只剩下了2个地方需要修改:

1.扫描界面

2.扫描成功时播放的beep声音文件

1:去除了其余功能后,对于核心功能我们只需要一个SurfaceView和一个画界面的View就可以了。代码如下:

/** * use Java code to build layout instead of xml file * @ch */ private void buildLayout() { requestWindowFeature(Window.FEATURE_NO_TITLE); FrameLayout layout = new FrameLayout(this); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); layout.setLayoutParams(params); surfaceView = new SurfaceView(this); surfaceView.setLayoutParams(params); layout.addView(surfaceView); viewfinderView = new ViewfinderView(this, null); layout.addView(viewfinderView); setContentView(layout); }

12345678910111213

/** * use Java code to build layout instead of xml file * @ch */private void buildLayout() {    requestWindowFeature(Window.FEATURE_NO_TITLE);    FrameLayout layout = new FrameLayout(this);     LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);     layout.setLayoutParams(params);     surfaceView = new SurfaceView(this);     surfaceView.setLayoutParams(params);     layout.addView(surfaceView);     viewfinderView = new ViewfinderView(this, null);     layout.addView(viewfinderView);     setContentView(layout); }

ViewfinderView是ZXing自带的View,如果要修改界面,直接修改它就可以了,我们第7点会提到。

2:由于我最终的目的是能打包成jar包,所以beep文件不能放在res里,而是放在assets里。

//待补充

3、API兼容(源码只兼容4.0以上,现兼容至2.1)

这部分修改在源码中标记为//@ch api compatible。

CaptureActivity.java:

if (VERSION.SDK_INT < 11) { //surfaceview will push buffer automatically surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } //API 11之前需要手动设置,否则会无法显示。API11之后为默认设置。 CameraConfigurationManager.java: //@ch api compatible if (VERSION.SDK_INT < 13) { theScreenResolution.x = display.getWidth(); theScreenResolution.y = display.getHeight(); } else { display.getSize(theScreenResolution); } //getSize是API 13之后的新API,之前需要用getWidth和getHeight。

12345678910

if (VERSION.SDK_INT < 11) {    //surfaceview will push buffer automatically    surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); }     //API 11之前需要手动设置,否则会无法显示。API11之后为默认设置。    CameraConfigurationManager.java: //@ch api compatible    if (VERSION.SDK_INT < 13) {         theScreenResolution.x = display.getWidth();         theScreenResolution.y = display.getHeight(); } else { display.getSize(theScreenResolution);     }     //getSize是API 13之后的新API,之前需要用getWidth和getHeight。

AutoFocusManager.java:

if (VERSION.SDK_INT > 11) { newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { newTask.execute(); } // 源码中这里设置了多线程的模式,THREAD_POOL_EXECUTOR表示这个task最多只有5个线程同时运行,超过5个的就要等待。在低于API 11的版本中,此为默认选项。其实这里只有单线程,所以随便执行吧。

1234567

if (VERSION.SDK_INT > 11) {     newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else {     newTask.execute(); }  // 源码中这里设置了多线程的模式,THREAD_POOL_EXECUTOR表示这个task最多只有5个线程同时运行,超过5个的就要等待。在低于API 11的版本中,此为默认选项。其实这里只有单线程,所以随便执行吧。

OpenCameraInterface.java:

if (VERSION.SDK_INT < 9) { return openWithLowApi(); } ...... /** * for lower than API 9 * @ch api compatible */ public static Camera openWithLowApi() { //If the device does not have a back-facing camera, this returns null Camera camera = Camera.open(); return camera; } //源码的打开摄像头是能区分前后摄像头的,然而API 9之前并没有前置摄像头这个概念,所以做了一下处理

12345678910

if (VERSION.SDK_INT < 9) {     return openWithLowApi(); } ...... /** * for lower than API 9 * @ch api compatible */    public static Camera openWithLowApi() {     //If the device does not have a back-facing camera, this returns null    Camera camera = Camera.open();     return camera; } //源码的打开摄像头是能区分前后摄像头的,然而API 9之前并没有前置摄像头这个概念,所以做了一下处理

就几个地方,不过也找了我个把小时了。

4、转换为竖屏(源码为横屏)

ZXing默认是横屏,但是我们一般的APP都会做成竖屏,如果扫码的时候强制切换成横屏那样体验就不好了。在修改ZXing的竖屏的时候,我按照的是一般APP的竖屏设置方法,结果发现没有源码的效果好,需要把码放到很小才能完成。后面在调试过程中发现扫码解析的区域和屏幕画出来的区域不一样,才知道这部分的修改出了问题。然后我搜索找到一篇前辈的文章,参考了一下发现没有改完全。附上文件链接:

http://blog.csdn.net/aaawqqq/article/details/24804939

其中第五点我没有修改,文章中的源码可能比较旧,并不适合替换。第5点不替换替换是没有问题的。

此外还有一点需要修改的是:

CameraManager.java:

//@ch change 240 to 120 for 320x240 machine private static final int MIN_FRAME_WIDTH = 120; private static final int MIN_FRAME_HEIGHT = 120; //@ch change to vertical private static final int MAX_FRAME_HEIGHT = (int) 1080; private static final int MAX_FRAME_WIDTH = (int) 1080; //此处为扫描框最大边界和最小边界的界定。但因为我最后做成了正方形,所以这里数值是一样的。如果为矩形,需要把两个值交换一下。 //设置最小值是为了保证解码的成功率,毕竟分辨率太小就没法识别了。最大值是为了保证解码速度。其实最大值应该通过插值来重新构图,不然框的大小不一致体验就不好了。不过这样的分辨率起码是2K屏以上了,所以最大值设定不会有什么影响

12345

//@ch change 240 to 120 for 320x240 machineprivate static final int MIN_FRAME_WIDTH = 120; private static final int MIN_FRAME_HEIGHT = 120; //@ch change to verticalprivate static final int MAX_FRAME_HEIGHT = (int) 1080; private static final int MAX_FRAME_WIDTH = (int) 1080; //此处为扫描框最大边界和最小边界的界定。但因为我最后做成了正方形,所以这里数值是一样的。如果为矩形,需要把两个值交换一下。 //设置最小值是为了保证解码的成功率,毕竟分辨率太小就没法识别了。最大值是为了保证解码速度。其实最大值应该通过插值来重新构图,不然框的大小不一致体验就不好了。不过这样的分辨率起码是2K屏以上了,所以最大值设定不会有什么影响

5、扫码速度优化(主要分三点,现只完成了一点)

1.二值化算法优化。ZXing一共提供了两种二值化算法,一种是HybridBinarizer,另一种是GlobalHistogramBinarizer,默认使用的是HybridBinarizer。我搜集资料的时候发现,HybridBinarizer算法用了更高级的算法,运算要求更高,而总体来讲GlobalHistogramBinarizer识别率要更高。在我的实际测试中(我用的Nexus5,Android 5.0.1),GlobalHistogramBinarizer效果确实是要好不少。

这里的修改很简单,换一个类就可以了: DecodeHandler.java:

private void decode(byte[] data, int width, int height) { ...... //@ch you can use HybridBinarizer or GlobalHistogramBinarizer //but in most of situations HybridBinarizer is shit BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source)); ...... }

1234567

private void decode(byte[] data, int width, int height) {    ......     //@ch you can use HybridBinarizer or GlobalHistogramBinarizer     //but in most of situations HybridBinarizer is shit    BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));     ...... }

2.对焦优化。ZXing中的对焦功能在AutoFocusManager.java中,功能非常简单,设置自动对焦并2秒对焦一次。但自动对焦可能会带来一个问题,如下图:

(图片源自网络)

把二维码当作图中的那朵花,自动对焦则容易使摄像头对焦到背景(图中女性)中去。我在测试中使用三星S4的自动对焦经常对不了二维码。这里我们可以使用区域对焦的方法(对焦的区域即是扫描框的区域):

//定点对焦的代码 private void pointFocus(int x, int y) { if (cameraParameters.getMaxNumMeteringAreas() > 0) { List<Camera.Area> areas = new ArrayList<Camera.Area>(); Rect area = new Rect(x - 100, y - 100, x + 100, y + 100); areas.add(new Camera.Area(area, 600)); cameraParameters.setMeteringAreas(areas); } mCamera.cancelAutoFocus(); cameraParameters.setFocusMode(Parameters.FOCUS_MODE_AUTO); mCamera.setParameters(cameraParameters); mCamera.autoFocus(autoFocusCallBack); }

12345678910111213

//定点对焦的代码 private void pointFocus(int x, int y) {    if (cameraParameters.getMaxNumMeteringAreas() > 0) {         List<Camera.Area> areas = new ArrayList<Camera.Area>();         Rect area = new Rect(x - 100, y - 100, x + 100, y + 100);         areas.add(new Camera.Area(area, 600));        cameraParameters.setMeteringAreas(areas);     }     mCamera.cancelAutoFocus();     cameraParameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);     mCamera.setParameters(cameraParameters);     mCamera.autoFocus(autoFocusCallBack); }

区域对焦需要API 14以上。关于对焦暂时没有更多的想法,但我觉得确实是有优化的余地的。

3.算法优化。算法主要分两部分,第一部分是二值化,第二部分是提取码值。第二部分又分为1.寻找定位符,2.寻找校正符,3.转换矩阵。在测试过程中,影响识别的最大问题就是找不到定位符,即二维码左上角、右上角、左下角的三个黑白相间的矩形点。比较大的原因可能是二值化部分的问题。这一部分暂时也还没有深入。

6、设备兼容(针对低分辨率设备)

CameraManager.java中有设置最小扫描框大小的参数MIN_FRAME_WIDTH和MIN_FRAME_HEIGHT。默认是320×240。这对于屏幕分辨率为320×240的设备,扫描框就会变成全屏的。这里我改成了120×120,实际在屏幕分辨率为320×240的设备上也可以扫到二维码。

7、自定义界面

界面写在ViewFinderView.java中。扫描框大小由CameraManager决定。修改的时候需要注意和CameraManager的配置关联起来,否则会出现扫描框和实际解码的区域不一致。(ZXing的android外围模块代码默认使用的是全屏,如果你想改为非全屏(比如加一个action bar),肯定会造成扫描框区域和实际解码的区域不一致的问题。这也是文首建议只使用core模块的原因)

8、优化调试方法

为了应对7可能带来的问题,我自己做了一个调试的方法,以保证扫描框内容和实际解码内容一致。首先在CaptureActivity.java初始化cameraManager的地方,把

cameraManager = new CameraManager(getApplication())

改为cameraManager = new CameraManager(this)

这样做的目的是:传入了activity给CameraManager,使得后面在CameraManager中的调试内容可以直接输出在activity上(dialog也一样,改为getContext()即可)。但注意这样会带来Activity生命周期问题和因为互相引用导致内存泄漏的问题。所以只能在调试阶段修改成上面的写法。

然后在CameraManager.buildLumianceSource中加入:

if (context instanceof Activity) { final Bitmap bitmap = Bitmap.createBitmap(BitmapUtil.createBitmapfromYUV420(data, width, height), rect.left, rect.top, rect.width(), rect.height()); CaptureActivity activity = ((CaptureActivity) context); final ViewfinderView view = activity.getViewfinderView(); activity.runOnUiThread(new Runnable() { @Override public void run() { view.drawResultBitmap(bitmap); } }); }

1234567891011

if (context instanceof Activity) {    final Bitmap bitmap = Bitmap.createBitmap(BitmapUtil.createBitmapfromYUV420(data, width, height), rect.left, rect.top, rect.width(), rect.height());     CaptureActivity activity = ((CaptureActivity) context);     final ViewfinderView view = activity.getViewfinderView();     activity.runOnUiThread(new Runnable() {         @Override         public void run() {             view.drawResultBitmap(bitmap);         }     }); }

这样,屏幕上的扫描框中就会显示出实际解码的图像,你可以通过比对预览图像查看是否对齐。

记得调试完改回来getApplication()

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Sublime Android Studio Logcat 高亮语法插件

    网上找的都是旧的 Logcat 格式,Android Studio 日志格式不适配。于是自己改了一个:

    sickworm
  • FIDO UAF Client端工作流程介绍

    根据FIDO UAF文档介绍,FIDO UAF在移动设备上的实现将分为三层:Client,ASM,Authenticator。

    sickworm
  • linux中运行zipalign/aapt提示:No such file or directory解决办法

    最近在用Docker+Jenkins做持续集成(CI),中间有个步骤需要调用zipalign对齐jar包,但我运行zipalign的时候却提示: No suc...

    sickworm
  • Android平台上OpenCV 深度网络实现对象检测

    Android平台上OpenCV 深度网络实现对象检测 自OpenCV3.3发布包含深度神经网络(DNN)模块的SDK以后,OpenCV4Android SDK...

    OpenCV学堂
  • 1005. 继续(3n+1)猜想 (25)

    卡拉兹(Callatz)猜想已经在1001中给出了描述。在这个题目里,情况稍微有些复杂。

    AI那点小事
  • HSV颜色直方图

    package com.imageretrieval.features; import java.awt.Color; import com.imagere...

    Venyo
  • 荐读|十个让你惊讶的深度学习案例

    1、如果你急需使用一张照片,但是这张照片分辨率很低。没关系,深度学习算法已经能够为低分辨率的照片提高分辨率。首先来看一下效果。 ? Github上有两个案例供...

    灯塔大数据
  • Harbor .v1.10.2 私有镜像仓库的自签CA证书、安装使用【超详细官方文档翻译说明】

    在以前搭建docker镜像私有仓库的时候,我都是使用registery搭建。本篇章来尝试另一个新的镜像仓库Harbor。

    Devops海洋的渔夫
  • Android Library上传到JCenter仓库实践

    用户1130025
  • Real time是如何帮助Pxiar 完成他们作品的

    这里是说的real-time不是unity引擎渲染的那种real-time,是在Pixar的每个制作环节中无时无刻都在实现的real-time,包含了电影中复杂...

    企鹅号小编

扫码关注云+社区

领取腾讯云代金券