2021SC@SDUSC-Zxing(十四):混合编码解码分析

2021SC@SDUSC

在之前的例子中,展示的都是针对单个码进行解码,但是在生活中会出现同时扫描两个二维码的情况,这种时候应该怎么解码呢?本节将对Zxing这一部分代码进行解读。

一、Zxing中的multi目录

为了方便介绍各类之间的关系,将Detector和Reader也加入到UML图中。可以大概了解到:对于多个码同时存在的情况,定位方法MultiDetector继承了Detector;但是解析方法是对Decode的调用;而解码器QRCodeMultiReader则是通过接口实现了多继承。我们解读这部分源码的重点是Zxing如何定位、存储多个二维码的。
在这里插入图片描述

  • QRCodeMultiReader:检测和解码图像中的多个QR码
  • GenericMultipleBarcodeReader:试图通过反复解码图像的一部分来定位图像中的多个条形码。找到一个条形码后,将递归扫描码ResultPoint的左、上、右和下区域。当试图在图像中查找多个二维条形码(如QR码)时,调用方还希望使用ByQuadrantReader。其中多个条形码的存在可能会阻止检测其中任何一个条形码。
  • ByQuadrantReader:此类尝试从图像中解析码,不是通过扫描整个图像,而是通过扫描图像的子集。当图像中可能存在多个码时,检测一个条形码时可能会发现多个条形码的部分,并且无法解码(例如QR码)。取而代之的是扫描图像的四个象限——以及中央的“象限”,以覆盖在中央发现码的情况。
  • MultipleBarcodeReader:此接口的实现尝试从一个图像读取多个条形码。
  • MultiDetector:这个类封装了可以探测图像中一个或多个QR码的逻辑,即使QR码旋转、倾斜或部分模糊也可以尝试探测出来。
  • MultiFinderPatternFinder:尝试在二维码中找到finder patterns(二维码个角上的方形标记),该类将返回图像中所有可能的QR码位置的数组。

二、扫码器QRCodeMultiReader

这个类继承了QRCodeReader(详见Zxing七),实现了MultipleBarcodeReader接口。类中只有3个方法。最主要的是decodeMultiple,可以类比于QRCodeReader中的decode。在具体解析方法上与QRCodeReader没有太大的区别,只是将result、DetectorResult、ResultPoint等信息变成了数组或链表进行存储。解析时,多个码彼此独立,单独解析,同图片中只要一个码的情况一致。通过下面的代码分析可以看出,该方法与QRCodeReader的decode方法是十分相似的。

 @Override
  public Result[] decodeMultiple(BinaryBitmap image, Map<DecodeHintType,?> hints) throws NotFoundException {
    //定义Result类型的链表存放结果
    List<Result> results = new ArrayList<>();
    //封装解码位矩阵的结果,它包含获得的原始字节,以及这些字节的字符串解释。
    DetectorResult[] detectorResults = new MultiDetector(image.getBlackMatrix()).detectMulti(hints);
    //对于每个DetectorResult进行分析
    for (DetectorResult detectorResult : detectorResults) {
      try {
        DecoderResult decoderResult = getDecoder().decode(detectorResult.getBits(), hints);
        ResultPoint[] points = detectorResult.getPoints();
        //如果代码被镜像:交换左下角点和右上角点
        if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
          ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
        }
        Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
                                   BarcodeFormat.QR_CODE);
        // 返回结果中的字节段列表。如果没有,返回null
        List<byte[]> byteSegments = decoderResult.getByteSegments();
        if (byteSegments != null) {
          result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
        }
        // 使用的错误更正级别的名称
        String ecLevel = decoderResult.getECLevel();
        if (ecLevel != null) {
          result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
        }
        // 如果代码格式支持结构化追加,并且当前扫描的代码是其中的一部分:
        if (decoderResult.hasStructuredAppend()) {
          //序列号随附
          result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
                             decoderResult.getStructuredAppendSequenceNumber());
          //码与码之间关系平等
          result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
                             decoderResult.getStructuredAppendParity());
        }
        results.add(result);
      } catch (ReaderException re) {
        // 忽视并继续
      }
    }
    //如果结果是空值
    if (results.isEmpty()) {
      return EMPTY_RESULT_ARRAY;
    } else {
      //对结果进行二次处理。
      results = processStructuredAppend(results);
      return results.toArray(EMPTY_RESULT_ARRAY);
    }
  }

但是在QRCodeMultiReader中,增加了对结果二次处理的操作。这是针对上述代码中提到的STRUCTURED_APPEND_SEQUENCE执行的操作。由于是多个码同时出现,可能有前后顺序的约束,因此processStructuredAppend方法中执行的就是如果有序列化要求,就将结果进行排序,再返回排序后的newResults。为了实现这个方法,QRCodeMultiReader还实现了一个内部类,以方便使用语句“Collections.sort(saResults, new SAComparator());”给result进行排序。

  private static final class SAComparator implements Comparator<Result>, Serializable {
    @Override
    public int compare(Result a, Result b) {
    //getResultMetadata实现将Map中ResultMetadataType的键映射到值。
      int aNumber = (int) a.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE);
      int bNumber = (int) b.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE);
      return Integer.compare(aNumber, bNumber);
    }
  }

三、定位器MultiDetector

MultiDetector可以类比Detector,MultiFinderPatternFinder可以类比FinderPatternFinder (Detector、FinderPatternFinder详见Zxing十一)。此外,MultiFinderPatternFinder继承了FinderPatternFinder

  public DetectorResult[] detectMulti(Map<DecodeHintType,?> hints) throws NotFoundException {
    BitMatrix image = getImage();
    //当找到可能的ResultPoint时,需要通过回调通知调用方。
    ResultPointCallback resultPointCallback =
        hints == null ? null : (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
    // 尝试在二维码中找到查找器模式。Finder图案是二维码三个角上的方形标记
    MultiFinderPatternFinder finder = new MultiFinderPatternFinder(image, resultPointCallback);
    //定义数组存放查询结果
    FinderPatternInfo[] infos = finder.findMulti(hints);
    //如果查询结果是空
    if (infos.length == 0) {
      //抛异常:在图像中找不到条形码时引发。可能已部分检测到,但无法确认。
      throw NotFoundException.getNotFoundInstance();
    }
    //定义链表保存结果
    List<DetectorResult> result = new ArrayList<>();
    //对可能的定位点逐个操作
    for (FinderPatternInfo info : infos) {
      try {
        //调用的方法在Detector的detect中
        result.add(processFinderPatternInfo(info));
      } catch (ReaderException e) {
        // 忽视
      }
    }
    if (result.isEmpty()) {
      return EMPTY_DETECTOR_RESULTS;
    } else {
      return result.toArray(EMPTY_DETECTOR_RESULTS);
    }
  }

在Detector中,查找定位点的算法是FinderPatternFinder中的find(Map<DecodeHintType,?> hints)方法,而在这里,查定位点的算法是MultiFinderPatternFinder中的findMulti(Map<DecodeHintType,?> hints),基本思路完全一致,只是MultiFinderPatternFinder要多次操作,返回的是结果数组。

四、测试样例

我们通过Zxing的测试来展示对于多个二维码同时存在的扫码过程。这是Zxing官方给出的测试图片,我们要测试同时解析4个二维码的情况。在之前的扫码中,由于我们只针对一个码进行解析,其结果返回是一个result;但是现在有多个码,结果就是一个数组的result。
在这里插入图片描述

  @Test
  public void testMultiQRCodes() throws Exception {
    //读取图片
    Path testBase = AbstractBlackBoxTestCase.buildTestBase("src/test/resources/blackbox/multi-qrcode-1");

    Path testImage = testBase.resolve("1.png");
    //解码的套路操作,前面详细讲述了,这里不做赘述
    BufferedImage image = ImageIO.read(testImage.toFile());
    LuminanceSource source = new BufferedImageLuminanceSource(image);
    BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
    // 我们主要关注MultipleBarcodeReader
    MultipleBarcodeReader reader = new QRCodeMultiReader();
    // 在之前的解码中,result是单个的对象,但是在这里result是以数组的方式展示的
    Result[] results = reader.decodeMultiple(bitmap);
	//判断解析结果是否为空及是否长度为4
    assertNotNull(results);
    assertEquals(4, results.length);
    Collection<String> barcodeContents = new HashSet<>();
    for (Result result : results) {
    //输出测试结果
      System.out.println(result.getText());
      System.out.println(result.getResultPoints()[0]+" "+result.getResultPoints()[1]+" "+result.getResultPoints()[2]+" "+result.getResultPoints()[3]);
      //在HashSet中加入解析的文字结果
      barcodeContents.add(result.getText());
      assertEquals(BarcodeFormat.QR_CODE, result.getBarcodeFormat());
      assertNotNull(result.getResultMetadata());
    }
    //判断解析结果是否正确
    Collection<String> expectedContents = new HashSet<>();
    expectedContents.add("You earned the class a 5 MINUTE DANCE PARTY!!  Awesome!  Way to go!  Let's boogie!");
    expectedContents.add("You earned the class 5 EXTRA MINUTES OF RECESS!!  Fabulous!!  Way to go!!");
    expectedContents.add("You get to SIT AT MRS. SIGMON'S DESK FOR A DAY!!  Awesome!!  Way to go!! Guess I better clean up! :)");
    expectedContents.add("You get to CREATE OUR JOURNAL PROMPT FOR THE DAY!  Yay!  Way to go!  ");
    assertEquals(expectedContents, barcodeContents);
  }

打印一下文本结果和部分定位点信息,可以看到成功同时解析了4个二维码:
在这里插入图片描述
Zxing的测试代码和测试图片都很全面也很规范,包含了单元测试、集成测试等,对于理解源码很有帮助。

五、总结

本以为会很复杂的混合编码解码并不复杂,都是在之前已有的模块基础上进行编码的。通过这部分的代码分析,体会到了java代码复用的强大。在原有的模块基础上稍加改动就可以解决更加复杂的问题。

欢迎提出宝贵意见,感谢观看!
参考: ZxingAPI

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值