2021SC@SDUSC

您所在的位置:网站首页 一维码的原理 2021SC@SDUSC

2021SC@SDUSC

2024-02-13 22:07| 来源: 网络整理| 查看: 265

2021SC@SDUSC

文章目录 一、Zxing中的一维码二、OneDReader三、一维码万能解码类——MultiFormatOneDReader四、CodaBarReader一、认识CodaBar二、CodaBar解码方法——decodeRow三、一维码定位方法直线拟合膨胀腐蚀 四、验证 五、一维码和二维码对比

一、Zxing中的一维码

在Zxing的目录结构中,所有的一维码有关代码被放到了同一个文件夹oned中 在这里插入图片描述 这是由于不同的一维码的编码解码逻辑比较简单并且关联性较大。这篇博客主要以CodaBar为例分析解码过程。

二、OneDReader

所有一维码的解码器都要继承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 在这里插入图片描述

//注:即使图片支持旋转,在没有TRY_HARDER标志的情况下也不会尝试旋转 @Override public Result decode(BinaryBitmap image, Map hints) throws NotFoundException, FormatException { try { // 直接调用解码算法 return doDecode(image, hints); } catch (NotFoundException nfe) { // 传输的条形码可能发生形变 boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); //如果解码的目标是花更多的时间去寻找条形码;优化精度,而不是速度,并且此子类支持逆时针旋转。 if (tryHarder && image.isRotateSupported()) { // 返回图像数据逆时针旋转90度的新对象,二值图像 BinaryBitmap rotatedImage = image.rotateCounterClockwise(); // 调用解码算法 Result result = doDecode(rotatedImage, hints); //记录我们发现它逆时针旋转90度/270度 Map metadata = result.getResultMetadata(); int orientation = 270; if (metadata != null && metadata.containsKey(ResultMetadataType.ORIENTATION)) { //但如果我们在doDecode()中发现了相反的结果,在此处添加该结果 //ORIENTATION表示图像中条形码的可能近似方向。该值以从正常垂直方向顺时针旋转的角度给出,其值在[0360]范围内。例如,通过自上而下读取找到的1D条形码将被称为方向为“90”。 orientation = (orientation + (Integer) metadata.get(ResultMetadataType.ORIENTATION)) % 360; } result.putMetadata(ResultMetadataType.ORIENTATION, orientation); //更新结果点 ResultPoint[] points = result.getResultPoints(); if (points != null) { int height = rotatedImage.getHeight(); for (int i = 0; i >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 尝试所有可能类型匹配的一维码解码器。 public MultiFormatOneDReader(Map hints) { @SuppressWarnings("unchecked") Collection possibleFormats = hints == null ? null : (Collection) hints.get(DecodeHintType.POSSIBLE_FORMATS);//POSSIBLE_FORMATS表示图像是几种可能的格式之一。 //ASSUME_CODE_39_CHECK_DIGIT表示不管它实际是什么,都假设使用Code39的校验位。 boolean useCode39CheckDigit = hints != null && hints.get(DecodeHintType.ASSUME_CODE_39_CHECK_DIGIT) != null; Collection readers = new ArrayList(); if (possibleFormats != null) { if (possibleFormats.contains(BarcodeFormat.EAN_13) || possibleFormats.contains(BarcodeFormat.UPC_A) || possibleFormats.contains(BarcodeFormat.EAN_8) || possibleFormats.contains(BarcodeFormat.UPC_E)) { // 可以推测MultiFormatUPCEANReader也是一个工厂类,针对的是EAN_13、UPC_A、EAN_8、UPC_E四种条形码 readers.add(new MultiFormatUPCEANReader(hints)); } if (possibleFormats.contains(BarcodeFormat.CODE_39)) { readers.add(new Code39Reader(useCode39CheckDigit)); } if (possibleFormats.contains(BarcodeFormat.CODE_93)) { readers.add(new Code93Reader()); } if (possibleFormats.contains(BarcodeFormat.CODE_128)) { readers.add(new Code128Reader()); } if (possibleFormats.contains(BarcodeFormat.ITF)) { readers.add(new ITFReader()); } if (possibleFormats.contains(BarcodeFormat.CODABAR)) { readers.add(new CodaBarReader()); } if (possibleFormats.contains(BarcodeFormat.RSS_14)) { readers.add(new RSS14Reader()); } if (possibleFormats.contains(BarcodeFormat.RSS_EXPANDED)) { readers.add(new RSSExpandedReader()); } } if (readers.isEmpty()) { // 可以推测MultiFormatUPCEANReader也是一个工厂类,针对的是EAN_13、UPC_A、EAN_8、UPC_E四种条形码 readers.add(new MultiFormatUPCEANReader(hints)); readers.add(new Code39Reader()); readers.add(new CodaBarReader()); readers.add(new Code93Reader()); readers.add(new Code128Reader()); readers.add(new ITFReader()); readers.add(new RSS14Reader()); readers.add(new RSSExpandedReader()); } this.readers = readers.toArray(EMPTY_ONED_ARRAY); } 四、CodaBarReader

一维码和二维码不同,Reader不需要将定位和解码的任务交给其他类别完成,所有的算法都在各自的Reader中(rss除外)。

一、认识CodaBar Codabar构成:Codabar具有4个条和3个空(共7个单元),每个窄或宽的宽度代表一个字符(字母)。 Codabar的基本构成如下: 在这里插入图片描述 Zxing对起始终止字符的定义如下: private static final char[] STARTEND_ENCODING = {'A', 'B', 'C', 'D'}; Codabar字符的构成:Codabar可以用数字(0至9)、字母(A、B、C、D)以及符号(-、$、/、. 、+)来表示字符。 在这里插入图片描述 在Zxing中,定义了字符字母串,并用一个数组存储他们 private static final String ALPHABET_STRING = "0123456789-$:/.+ABCD"; static final char[] ALPHABET = ALPHABET_STRING.toCharArray();

规定这些表示字符的编码如下,表示宽条和窄条的模式。每个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就不存在遗漏读取。如果条码的打印质量不好,往往在以下情形中会出现遗漏读取。在这里插入图片描述 为了避免遗漏读取,推荐采用和ITF一样的办法,把条码读取仪设置在"数位指定"功能上,只读取规定位数的数字。例如,A––––A用于罗列价格,A––––C用于特别折扣价格而C––––C为大减价。Codabar的应用:应用于验血(标本)的试管上,以确定各个身份。 二、CodaBar解码方法——decodeRow

总的来说,所有一维码的解码思路都基本一致: 第一步:定位。条形码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


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3