zxing 一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

时间:2022-04-30 01:58:13

  打算修改zxing 源码应用到其它方面,所以最近花了点时间阅读其源码,无意中找到这篇博客,条码扫描二维码扫描——ZXing android 简化源码分析 对过程的分析还是可以参考的.原作者给出的一个基本的UML序列图:

zxing  一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

(图像引用自http://blog.csdn.net/doonly2009/article/details/12175997)

结合上面的序列图,本文将本zxing 一维码部分的源码进行解析,有不对的地方忘大家给予指正,所有内容仅供大家参考.更正上图的一个小错误,DecodeThead 是被CaptureActivityHandler 调用 decodeThread.start()方法启动的,而不是在构造方法中触发的. 

部分一 ,环境搭建  2014.1.14

  从这里下载工程文件,导入到Eclipse中(我的环境,windows,eclipse).这个工程文件是把一些代码打包成了jar文件,这反而不利于文件的分析.我们这里利用从官网下载的源码重新建立一个库工程文件,方便我们的代码分析.

1,core.jar 文件打包过程 .

  这里的core.jar  和网上的zxing.jar core.jar 类似 ,不过网上下载的都是简化过的.过程如下:  

  ①,新建android 工程 ,不需要勾选 Create activty

  ②,右键工程中的src  -->new-->packages   命名为 com.google.zxing

  ③,右键 com.google.zxing --> import --> File System -->找到 zxing 源码  .. java\com\google\zxing 即可.

  ④,右键工程 选择 android 标签,勾选 Is Library 如图.

  zxing  一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

打开你的工程文件,可以看到生成的jar文件了.如图:

  zxing  一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

2,使用自己的 core.jar文件

  打开之前下载的工程文件(已经导入到eclipse)删除之前引用的 zxing.jar 文件 .右键BarCodeTest 工程文件-->Properties-->Android 选项    -->在Libarary 选项中添加 . 这里会自动找到刚创建的包工程文件.

  zxing  一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

强调一点,若其它工程文件引用这个jar工程文件,则这个jar工程文件必须是打开状态.经过测试 zxing 2.3  和 1.6 版本的 core 文件都可以在上面下载到的简化工程使用.环境配置完成 ,下面将进行核心代码的分析.

部分二,源码分析 2014.1.21

  经过一段时间阅读和分析源码,下面以程序执行的大体顺序进行源码的分析.我们从获取一帧数据开始分析,流程图如下:

  zxing  一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

这里按这个流程图进行代码的分析.

1 ,过程①,获取最原始的数据,数据存储在 byte[] data 中.

2,过程②,通过传递 handler, 当有消息时,会自动跳转到 public void handleMessage(Message message) {}处执行.

 DecodeHandler.handleMessage(Message message) 在restartPreviewAndDecode()方法中被传递,其过程如下图:

  zxing  一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

  zxing中主要采用 Message  来传递消息 ,这里有两个继承handler 的类,分别为CaptureActivityHandlerDecodeHandler.消息实例的  创建分别在:

 public void onPreviewFrame(byte[] data, Camera camera) {
Point cameraResolution = configManager.getCameraResolution();
if (!useOneShotPreviewCallback) {
camera.setPreviewCallback(null);
}
if (previewHandler != null) {
//从这里传递 message 参数 ,创建Message对象,其Handler.obtainMessage可以调用Message.obtain来创建消息。
Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
9 cameraResolution.y, data);
//Sends this Message to the Handler specified by getTarget().
//Throws a null pointer exception if this field has not been set.
//who use the getTarget() function?
//通过Message.sendToTarget向消息队列插入消息;
message.sendToTarget();
previewHandler = null;
} else {
Log.d(TAG, "Got preview callback, but no handler for it");
}
}

  和,位于 com.zxing.decoding.DecodeHandler 类中的方法

 private void decode(byte[] data, int width, int height) {

         .........
Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, rawResult);
.........
}

  这样当有message 到来时便会自动调用 相应类中的handleMessage() 方法,实现对消息的处理.

3,过程③,这里实现对数据的解码,如果解码不成功,则进行下次解码,否则将会发送消息给CaptureActivityHandler 对象.

4,过程④,这里是实现解码的核心方法,包括数据的二值化和最内层的解码方法 Result result = decodeRow(rowNumber, row, hints),因为我这里只分析一维码的部分,所以预先设定的解码类型,这里会跳转到 这个类MultiFormatUPCEANReader中的decodeRow 方法.

OneDReader.doDecode(BinaryBitmap image, Map<DecodeHintType, ?> hints) throws NotFoundException 对每一次获取的帧进行最大15次的解码,这里的解码只是针对一行数据.从中间区域开始,分别对上下依次获取的行数据进行解码,如果解码成功则返回.

强调一点,zxing 不对获取的图片数据进行旋转,虽然支持旋转.但是支持对转动180 的一维码的解码,代码如下:

 if (attempt == 1) { // trying again?
row.reverse(); // reverse the row and continue
// This means we will only ever draw result points *once* in the life of this method
// since we want to avoid drawing the wrong points after flipping the row, and,
// don't want to clutter with noise from every single row scan -- just the scans
// that start on the center line.
if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
Map<DecodeHintType,Object> newHints = new EnumMap<DecodeHintType,Object>(DecodeHintType.class);
newHints.putAll(hints);
newHints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
hints = newHints;
}
}

这里主要的就是这句 row.reverse().

5,过程⑤,zxing 对行数据二值化的代码如:

 public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
LuminanceSource source = getLuminanceSource();
int width = source.getWidth();
if (row == null || row.getSize() < width) {
row = new BitArray(width);
} else {
row.clear();
} initArrays(width);
//在这里 luminances 被赋值
//y=180;
byte[] localLuminances = source.getRow(y, luminances);
//统计并建立直方图
int[] localBuckets = buckets;
/*---------modify here ,just for analysis .--------------------*/
int i_row[] = new int [width];
for(int x=0;x<width;x++){
i_row[x]=localLuminances[x] & 0xff;
}
/*-------------------------------------------------------------*/
for (int x = 0; x < width; x++) {
int pixel = localLuminances[x] & 0xff;
localBuckets[pixel >> LUMINANCE_SHIFT]++;
}
//According to the histogram, find the threshold.
int blackPoint = estimateBlackPoint(localBuckets);
//Based on threshold ,set 1 bit. 21.1.2014
int left = localLuminances[0] & 0xff;
int center = localLuminances[1] & 0xff;
for (int x = 1; x < width - 1; x++) {
int right = localLuminances[x + 1] & 0xff;
// A simple -1 4 -1 box filter with a weight of 2.
int luminance = ((center << 2) - left - right) >> 1;
if (luminance < blackPoint) {
row.set(x);
}
left = center;
center = right;
}
return row;
}

代码的基本过程包括,统计并建立直方图-->根据直方图找到白与黑的中间阀值-->大于阀值的为白色,反之黑色.在这个方法中, int luminance = ((center << 2) - left - right) >> 1; 这句代码我起初理解为对图像进行降噪处理,后来经过数学推导不合理,所以便在zxing group 进行了提问,zxing 成员给予了热心的回复:

zxing  一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

6,过程⑥,zxing 黑白阀值的寻找比较巧妙,其代码如下 :

  private static int estimateBlackPoint(int[] buckets) throws NotFoundException {
// Find the tallest peak in the histogram.
int numBuckets = buckets.length;
int maxBucketCount = 0;
int firstPeak = 0;
int firstPeakSize = 0;
//找到 数组中最多像素点的个数(maxBucketCount) 和 对应的 序号,序号其实就是亮度值(区间)firstPeak.
//这里的 firstPeak 即可能是 黑色的区间,也可能是白色的区间.
for (int x = 0; x < numBuckets; x++) {
if (buckets[x] > firstPeakSize) {
firstPeak = x;
firstPeakSize = buckets[x];
}
if (buckets[x] > maxBucketCount) {
maxBucketCount = buckets[x];
}
} // Find the second-tallest peak which is somewhat far from the tallest peak.
int secondPeak = 0;
int secondPeakScore = 0;
for (int x = 0; x < numBuckets; x++) {
int distanceToBiggest = x - firstPeak;
// Encourage more distant second peaks by multiplying by square of distance.
int score = buckets[x] * distanceToBiggest * distanceToBiggest;
if (score > secondPeakScore) {
secondPeak = x;
secondPeakScore = score;
}
} // Make sure firstPeak corresponds to the black peak.
if (firstPeak > secondPeak) {
int temp = firstPeak;
firstPeak = secondPeak;
secondPeak = temp;
} // If there is too little contrast in the image to pick a meaningful black point, throw rather
// than waste time trying to decode the image, and risk false positives.
if (secondPeak - firstPeak <= numBuckets >> 4) {
throw NotFoundException.getNotFoundInstance();
} // Find a valley between them that is low and closer to the white peak.
int bestValley = secondPeak - 1;
int bestValleyScore = -1;
for (int x = secondPeak - 1; x > firstPeak; x--) {
int fromFirst = x - firstPeak;
//这里找 到两个峰比较远,同时又点少的区间.这里 fromFist 采用平方的形式,可以理解更接近于白色.
int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
if (score > bestValleyScore) {
bestValley = x;
bestValleyScore = score;
}
} return bestValley << LUMINANCE_SHIFT;
}

代码还是比较清晰的,因为对于一维码和二维码,图像的主要颜色为黑白色,所以直方图呈现双峰结构.知道这个特性,其算法思想也就明子了.基本过程:根据直方图找到最多的像素区域--->根据 int score = buckets[x] * distanceToBiggest*distanceToBiggest 这个评分,找到第二个像素区域.可以理解为距离白色(黑色)比较远且个数比较多的像素区域.--->根据 int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);这个评分,找到距离两个峰比较远且比较少的区域为阀值. 这里fromFist 采用平方的形式,可以理解更接近于白色.距离比重越大,其阀值便越靠近另外一个峰.

代码分析基本到这里,我这里将对zxing 原码进行修改,进行二次开发应用到其它方面.

2014.2.13

  经过一段时间的程序开发,基于android 的数卡程序终于完工了,为了解化程序的开发,这里手机照的照片再打印出来,如下图:

zxing  一维码部分深入分析与实际应用,识别卡片数量,Android数卡器

 下面的日志显示了程序的识别效果,识别左上角的卡片.

  02-13 18:22:13.438: D/Correct rate(30802): Successful frequency 4.171029.
  02-13 18:22:13.478: D/Correct rate(30802): The times of success 985,The times of failed 6,The correct rate 0.99394554.
  02-13 18:22:13.478: D/Correct rate(30802): Successful frequency 4.174507.
  02-13 18:22:13.569: D/Correct rate(30802): The times of success 986,The times of failed 6,The correct rate 0.9939516.
  02-13 18:22:13.569: D/Correct rate(30802): Successful frequency 4.177081.

从日志里可以看出,识别能够得到不错的效果. 使用时要求卡片明亮清洁,扫描背景最好为黑灰色.卡片数量大于3 张小于50张.

程序算法及思想:

  程序先根据二值化后的数据,先均匀先取5 行-->找总亮度值最小的前3行-->根据第2和3行号  取2行数据 -->找到这两行的起始位置

   -->从起始位置找 白色卡片的数量(这里是经过二值后的数据) -->  如果最终两行计算的数据相等则返回结果 和结束位置.

本人面向对象的编程能力实在不敢恭维,android 程序写出了 c 语言风格.所以我这里便不公开源码,代码写的很乱.

这里本人上传 打包的apk 程序和图片素材.

安装包和测试文件安装包和测试图片4.rar

算法经过优化之后的安装包和测试文件5.rar

功能展示

最佳性能测试视频. 距离图片50cm ,纯黑白条件下,白色条纹宽3mm 缝隙为 0.8mm

博文为本人所写,转载请表明出处,博客园梦工厂2012.

推荐阅读

  http://blog.csdn.net/doonly2009/article/details/12175997

  http://kuangjianwei.blog.163.com/blog/static/190088953201361015055110/

  http://www.cnblogs.com/zdwillie/p/3331250.html

  

zxing 一维码部分深入分析与实际应用,识别卡片数量,Android数卡器的更多相关文章

  1. 使用Zxing 一维码

    最近看到满大街的二维码扫码有惊喜,对二维码也有过一些了解,想看看到底是什么原理,在网上找了一些资料,自己弄了一个实例,采用的是MVC,贴出来分享一下 一维码生成 Controller public A ...

  2. java利用zxing编码解码一维码与二维码

    最近琢磨了一下二维码.一维码的编码.解码方法,感觉google的zxing用起来还是比较方便. 本人原创,欢迎转载,转载请标注原文地址:http://wallimn.iteye.com/blog/20 ...

  3. ZXing拍摄代码扫描之后以区分一维码、二维码、其他代码

    我怎么有二维码没有联系,最近遇到一个问题,,如何推断条码扫描到一维代码或者二维代码,辛苦了一个下午下班后自己,加上网上跟踪信息. 总结出两种方式能够解决该问题(推荐採用另外一种方式): 1.改动源代码 ...

  4. C&num; 使用ZXing&period;NET生成一维码、二维码

    以上图片是本示例中的实际运行效果,在生活中我们的一维码(也就是条形码).二维码 使用已经非常广泛,那么如何使用c#.net来进行生成一维码(条形码).二维码呢? 使用ZXing来生成是非常方便的选择, ...

  5. zxing解析生成一维码二维码

    @web界面实现扫一扫 二维码工具类 package util; import java.awt.BasicStroke; import java.awt.Graphics; import java. ...

  6. (zxing&period;net)一维码ITF的简介、实现与解码

    一.简介 一维码ITF 25又称交插25条码,常用在序号,外箱编号等应用.交插25码是一种条和空都表示信息的条码,交插25码有两种单元宽度,每一个条码字符由五个单元组成,其中二个宽单元,三个窄单元.在 ...

  7. (zxing&period;net)一维码EAN 13的简介、实现与解码

    一维码EAN 13:属于国际标准条码, 由13个数字组成,为EAN的标准编码型式(EAN标准码). 依结构的不同,EAN条码可区分为: EAN 13码: 由13个数字组成,为EAN的标准编码型式(EA ...

  8. (zxing&period;net)一维码EAN 8的简介、实现与解码

    一.简介 一维码EAN 8:属于国际标准条码,由8个数字组成,属EAN的简易编码形式(EAN缩短码).当包装面积小于120平方公分以下无法使用标准码时,可以申请使用缩短码. 依结构的不同,EAN条码可 ...

  9. (zxing&period;net)一维码Code 128的简介、实现与解码

    一.简介 一维码Code 128:1981年推出,是一种长度可变.连续性的字母数字条码.与其他一维条码比较起来,相对较为复杂,支持的字元也相对较多,又有不同的编码方式可供交互运用,因此其应用弹性也较大 ...

随机推荐

  1. 10本Java经典书目推荐

    本文列出的10本书是我个人非常喜欢的Java书籍,当我有时间的时候,我就会将它们捧在手里阅读.甚至有些书我反复读过很多遍,每次重新读的时候总会有新的收获.因此这些书也是大部分Java程序员喜欢的书籍. ...

  2. 会员管理系统全部源代码&lpar;C&num;&plus;EF&plus;SQLite&plus;Winforms实现&rpar;

    会员管理系统全部源代码,VS2010开发,使用Ado.net实体框架EF,简化数据库访问层,并能方便的移植到其他数据库.利用数据绑定减少编码量,提高程序的可维护性和可读性.使用Winfoms方便快速界 ...

  3. UOJ Test Round 1

    第一题: 题目大意: 给出N个字符串,字符串的前面部分都是字母且都是一样的,后面部分是数字,按照后面的数字排序.N<=10000 解题过程: 1.第一题是真良心,一开始的做法是把后面的数字分离出 ...

  4. html的两种提交按钮submit和button

    转自:http://baiying.blog.51cto.com/1068039/1319784 html按钮有两种: <input type="button" value= ...

  5. &period;xlsx文件总是默认用2007 Microsoft Office component 打开,且无法更改用EXCEL打开的解决方法

    之前装了OFFICE2003,后来改装了 OFFICE2007,之后XLSX文件双击总是用2007 Microsoft Office component 打开,导致无法打开. 解决方法: 打开注册表R ...

  6. hibernate第一天

    首先介绍一下javaEE开发的三层架构 Web层    也被称为表现层    它是表现层的一个设计模型:也就是大家常用的MVC开发模式 Service层   它是和需求相关的 DAO层   它只和数据 ...

  7. 《On Writing Well 30th Anniversa》【PDF】下载

    <On Writing Well 30th Anniversa>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382210 内容简 ...

  8. VS2010 Extension实践(3)——实现自定义配置

    在之前的两篇曾提到通过VSSDK(MSDN也叫VSX)来拓宽思路,实现一些MEF Extension所不能做到的功能,比如获取IVsUIShell服务来执行Command等等,这里我给各位看官展示如何 ...

  9. CSS3 Flexbox轻巧实现元素的水平居中和垂直居中

    CSS3 Flexbox轻松实现元素的水平居中和垂直居中 网上有很多关于Flex的教程,对于Flex的叫法也不一,有的叫Flexbox,有的叫Flex,其实这两种叫法都没有错,只是Flexbox旧一点 ...

  10. DotNetCore学习-3&period;管道中间件

    中间件是用于组成应用程序管道来处理请求和响应的组件.管道内的每个组件都可以选择是否将请求交给下一个组件,并在管道中调用下一个组件之前和之后执行一些操作. 请求委托被用来建立请求管道,并处理每一个HTT ...