2021SC@SDUSC | 您所在的位置:网站首页 › 一维条形码原理图 › 2021SC@SDUSC |
2021SC@SDUSC 文章目录 一、Zxing中的一维码二、OneDReader三、一维码万能解码类——MultiFormatOneDReader四、CodaBarReader一、认识CodaBar二、CodaBar解码方法——decodeRow三、一维码定位方法直线拟合膨胀腐蚀 四、验证 五、一维码和二维码对比 一、Zxing中的一维码在Zxing的目录结构中,所有的一维码有关代码被放到了同一个文件夹oned中 所有一维码的解码器都要继承OneDReader类。OneDReader是Reader的子类。在二维码中,解码方法都是decode,但是在一维码中,解码方法都命名为decodeRow。 各方法介绍如下: 方法作用decode(BinaryBitmap image):Result重写方法。decode(BinaryBitmap image,Map hints) :Result重写方法,外部调用的解码方法reset():void重写方法,方法里面是空的,也就是OneDreader不具体描述这个方法doDecode(BinaryBitmap image, Map hints):Result内部实现的解码方法recordPattern(BitArray row, int start,int[] counters):void记录从给定点开始的一行中连续运行的白色和黑色像素的数量recordPatternInReverse(BitArray row, int start, int[] counters):void上一个方法是从前向后记录,这个方法是从后向前记录,只在rss中用到patternMatchVariance(int[] counters,int[] pattern,float maxIndividualVariance) :float确定一组观察到的黑白值运行计数与给定目标模式的匹配程度。是所有模式中,预期模式比例的总方差与模式长度的比率。decodeRow(int rowNumber, BitArray row, Map hints): Result抽象方法,尝试对给定一行图像的一维条形码格式进行解码。需要每个码根据自己的特点实现。docode doDecode decode交付给doDecode进行后续解码操作,而doDecode只是负责扫描定位,具体的解码方法在每个码的decodeRow中实现。 private Result doDecode(BinaryBitmap image, Map hints) throws NotFoundException { int width = image.getWidth();//宽 int height = image.getHeight();//高 BitArray row = new BitArray(width);//定义一个大小为宽度的BitArray来存放结果 boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); // 如果不知道扫码类型并且希望更加重视扫码的精度,扫描行的步长为(height>>8和1)中最大的一个,反之为(height>>5和1)中最大的一个 int rowStep = Math.max(1, height >> (tryHarder ? 8 : 5)); int maxLines; // 如果不知道扫码类型并且希望更加重视扫码的精度 if (tryHarder) { maxLines = height; //看看整个图像,而不仅仅是中心 } else { // 如果不知道扫码类型并且希望更加重视扫码的速度 maxLines = 15; // 间隔1/32的15行大致是图像的中间部分 } int middle = height / 2; for (int x = 0; x // 如果超出顶部或底部得范围,stop break; } //估计此行的黑点并加载它 try { //getBlackRow将一行亮度数据转换为1位数据,用于解码一维条形码,可以选择应用锐化。 row = image.getBlackRow(rowNumber, row); } catch (NotFoundException ignored) { continue; } // 虽然我们将图像数据放在一个位数组中,但将其反转以处理颠倒条形码的解码是相当方便的。 for (int attempt = 0; attempt row.reverse(); // 将行反转并继续 //这意味着在该方法的生命周期中,我们将只绘制一次结果点,因为我们希望避免在翻转行后绘制错误的点,并且不希望在每一行扫描中都出现噪音,只绘制从中心线开始的扫描。 if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) { Map newHints = new EnumMap(DecodeHintType.class); newHints.putAll(hints); newHints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK); hints = newHints; } } try { // 寻找条形码,decodeRow是解码的关键方法 Result result = decodeRow(rowNumber, row, hints); // 找到了条形码 if (attempt == 1) { // 但它是颠倒的,所以请注意 result.putMetadata(ResultMetadataType.ORIENTATION, 180); // 并记住水平翻转结果点 ResultPoint[] points = result.getResultPoints(); if (points != null) { points[0] = new ResultPoint(width - points[0].getX() - 1, points[0].getY()); points[1] = new ResultPoint(width - points[1].getX() - 1, points[1].getY()); } } return result; } catch (ReaderException re) { // 继续,直到无法解码这一行 } } } 三、一维码万能解码类——MultiFormatOneDReader还记得在Reader中我们介绍的MultiFormatReader吗?我们称它是万能解码类,MultiFormatOneDReader和Reader、OneDReader、MultiFormatReader有着密切的关系,如下:所有一/二维码解码器都要实现Reader,所有一维码解码器都要继承OneDReader。为了调用方便,Zxing写了两个工厂类:MultiFormatOneDReade、MultiFormatReader;MultiFormatReader是面向一/二维码的,MultiFormatOneDReade是面向一维码的,在MultiFormatReader中对一维码的解析调用的就是MultiFormatOneDReader类。 if (addOneDReader && !tryHarder) { readers.add(new MultiFormatOneDReader(hints)); }四者关系如图: 一维码和二维码不同,Reader不需要将定位和解码的任务交给其他类别完成,所有的算法都在各自的Reader中(rss除外)。 一、认识CodaBar Codabar构成:Codabar具有4个条和3个空(共7个单元),每个窄或宽的宽度代表一个字符(字母)。 Codabar的基本构成如下:![]() ![]() 规定这些表示字符的编码如下,表示宽条和窄条的模式。每个int的7个最低有效位对应于宽和窄的模式,1表示“宽”,0表示“窄”。 static final int[] CHARACTER_ENCODINGS = { 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD };以“0”为例,0的编码为0x003,低7位为0000011,即,窄窄窄窄窄宽宽,与上图中0的条式图案一致。 Codabar的特征:Codabar的遗漏读取比ITF的要少。同CODE 39相比,条码尺寸也较小。但这并不总意味着Codabar就不存在遗漏读取。如果条码的打印质量不好,往往在以下情形中会出现遗漏读取。![]() 总的来说,所有一维码的解码思路都基本一致: 第一步:定位。条形码Reader扫描图像的整个宽度,试图识别是否有条形码候选对象——黑/白图。 第二步:解码。定位出条形码后开始解码。在这一步中,Reader它对图像像素进行计数和比较,以匹配开始和结束标识符。然后,它根据该代码类型的规范来解析开始标识符和结束标识符之间的模式,以解开编码的数据。 @Override public Result decodeRow(int rowNumber, BitArray row, Map hints) throws NotFoundException { Arrays.fill(counters, 0); // 记录所有白色和黑色像素的大小,从白色开始。这与recordPattern类似,只是它记录所有计数器,并使用内置的“counters”成员进行存储。在这个方法中初始化counterLength大小。 setCounters(row); // 设置起始偏移量。在开始模式之前查找空白(>=开始模式宽度的50%,如上面的图所示,ABCD的起始位置都是窄框)。 int startOffset = findStartPattern(); int nextStart = startOffset; decodeRowResult.setLength(0); do { int charOffset = toNarrowWidePattern(nextStart); if (charOffset == -1) { throw NotFoundException.getNotFoundInstance(); } //我们将字母表中的位置存储到StringBuilder中,以便在validatePattern中访问解码的模式。我们稍后将转换为实际字符。 decodeRowResult.append((char) charOffset); nextStart += 8; // 我们一看到结束字符就停止。arrayContains方法判断元素是否在数组中 if (decodeRowResult.length() > 1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { break; } } while (nextStart throw NotFoundException.getNotFoundInstance(); } // 验证startOffset validatePattern(startOffset); // 将字符表偏移量转换为当前字符。 for (int i = 0; i throw NotFoundException.getNotFoundInstance(); } char endchar = decodeRowResult.charAt(decodeRowResult.length() - 1); if (!arrayContains(STARTEND_ENCODING, endchar)) { throw NotFoundException.getNotFoundInstance(); } // 删除停止/开始字符并检查是否包含足够长的字符串 if (decodeRowResult.length() decodeRowResult.deleteCharAt(decodeRowResult.length() - 1); decodeRowResult.deleteCharAt(0); } int runningCount = 0; for (int i = 0; i runningCount += counters[i]; } float right = runningCount; Result result = new Result( decodeRowResult.toString(), null, new ResultPoint[]{ new ResultPoint(left, rowNumber), new ResultPoint(right, rowNumber)}, BarcodeFormat.CODABAR); //SYMBOLOGY_IDENTIFIER条形码符号标识符。注:根据GS1规范,在条形码内容前加前缀时,标识符可能必须替换前导FNC1/GS字符。 result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]F0"); return result; } 三、一维码定位方法 直线拟合在CodaBar中使用的就是这种定位方式。在OneDReader的decode方法中,有一步是将图像旋转了90度,这样对于一张二值化后的一维码,我们可以得到如下图片: 如上对行进行处理,以定位条形码的位置。 膨胀腐蚀处理流程 对一张一维码的图片,在经过灰度处理二值化后,图像如下图(在Zxing中我们用“X”表示黑,用“ ”表示白) 腐蚀: 开操作:先腐蚀再膨胀,从下图中可以看出,一些小的噪声点都被去除了。 闭操作:先膨胀再腐蚀 四、验证由于条形码的定位标志没有QRcode那么明显,为了确保提取到的区域是条码,对于我们定位到的startOffset,我们还需要做一些验证,避免出错。 ①基于形状,判断区域宽高比例,如果区域太扁或者近似正方形,则该区域不是条码区域。 ②基于颜色,条码区域,黑白相间,黑色区域的面积和白色区域的面积差不多大小。 ③基于纹理,中间画测量线求正负跳变次数,少于阈值说明不是条码区域。 // 验证模式 private void validatePattern(int start) throws NotFoundException { // 首先,总结出四类条纹尺寸的总尺寸; int[] sizes = {0, 0, 0, 0}; int[] counts = {0, 0, 0, 0}; int end = decodeRowResult.length() - 1; // 我们在中间中断这个循环,以便正确处理字符间空格。 int pos = start; for (int i = 0; i //偶数j=条形,而奇数j=空间。类别2和3用于长条纹,而0和1用于短条纹。 int category = (j & 1) + (pattern & 1) * 2; sizes[category] += counters[pos + j]; counts[category]++; pattern >>= 1; } // 忽略字符间的空间,它可以是任意大小的 pos += 8; } // 使用定点数学计算允许的大小阈值。 float[] maxes = new float[4]; float[] mins = new float[4]; // 将可接受性阈值定义为平均小条纹和平均大条纹之间的中点。 // 条纹长度不应位于该线的“错误”一侧。 for (int i = 0; i int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(i)]; for (int j = 6; j >= 0; j--) { // 偶数j=条形,而奇数j=空间。类别2和3用于长条纹,而0和1用于短条纹。 int category = (j & 1) + (pattern & 1) * 2; int size = counters[pos + j]; if (size maxes[category]) { throw NotFoundException.getNotFoundInstance(); } pattern >>= 1; } pos += 8; } } 五、一维码和二维码对比 可以看到一维码没有纠错机制,如果一维码有破损,就不能被读取;对于二维码来说,即使有破损,也有可能可以正常读取。一维码只能在水平方向单向的表达商品信息,而在垂直方向则不表达任何信息,它的一定高度通常是为了便于条码设备的对准,读取。而二维码在水平和垂直方向都可表达信息,也就是说它在二维空间内存储信息。由于只有水平方向有信息,一维码扫码逻辑很简单。欢迎提出宝贵意见,感谢观看! 参考: ZxingAPI Codabar介绍 |
CopyRight 2018-2019 实验室设备网 版权所有 |