简体   繁体   English

为什么ZXing解码这么差?

[英]Why is ZXing decoding so poorly?

The following program uses ZXing (and PDFBox) to encode a byte array (lengths N=1,2,3,...) as a QR code, which is then embedded into a PDF document, rendered, extracted as a BufferedImage, and decoded.以下程序使用 ZXing(和 PDFBox)将字节数组(长度 N=1,2,3,...)编码为二维码,然后将其嵌入到 PDF 文档中,渲染、提取为 BufferedImage,然后解码。 The decoded and encoded byte arrays are compared.比较解码和编码的字节数组。 Decoding is attempted first without the decoding option PURE_BARCODE, and if this fails, decoding is then attempted with the PURE_BARCODE option.首先在没有解码选项 PURE_BARCODE 的情况下尝试解码,如果失败,则尝试使用 PURE_BARCODE 选项进行解码。

Decoding starts failing around N=20 (N=28 for the example random seed).解码在 N=20 左右开始失败(对于示例随机种子,N=28)。 Note that until decoding fails, the byte arrays are correctly decoded .请注意,直到解码失败,字节数组才能正确解码

When the decoding fails, the failed QR code is displayed in a dialog box and the payload String printed to the console.当解码失败时,会在对话框中显示失败的二维码,并将payload String 打印到控制台。

If I point my phone at this dialog box, however, my phone has no problem decoding this QR Code image, and the decoded string matches that on the console.但是,如果我将手机指向此对话框,则我的手机在解码此 QR 码图像时没有问题,并且解码后的字符串与控制台上的字符串匹配。

What mistake am I making?我犯了什么错误?

package zxing;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Map;
import java.util.PrimitiveIterator;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;

public class Problem {

    public static void main(String[] args) throws IOException,
            WriterException,
            NotFoundException,
            ChecksumException,
            FormatException {
        Random r = new Random(12345);

        PrimitiveIterator.OfInt rb = r.ints(-128, 127).iterator();

        for (int N = 1; N < 100; N += 1) { // encode and decode random byte arrays of size N
            System.out.println(N);
            for (int numRuns = 0; numRuns < 10; numRuns++) { // number of tries at size N 

                byte[] payload = new byte[N]; // payload to be encoded

                for (int i = 0; i < N; i++) { // payload random initialization 
                    payload[i] = rb.next().byteValue();
                }
                final String payloadString = new String(payload, STRING_ENCODING); // encode as string 

                // encode using zxing
                final BufferedImage qr = MatrixToImageWriter.toBufferedImage(new QRCodeWriter().encode(payloadString,
                        BarcodeFormat.QR_CODE, 256, 256, ENCODING_HINTS));

                // insert into PDF
                PDDocument pdDocument = new PDDocument();
                PDPage page = new PDPage();
                pdDocument.addPage(page);
                PDPageContentStream pageContent = new PDPageContentStream(pdDocument, page);
                pageContent.drawImage(JPEGFactory.createFromImage(pdDocument, qr), 0, 0, 200, 200);
                pageContent.close();
                // render pdf and extract qr code image
                BufferedImage pageImage = new PDFRenderer(pdDocument).renderImage(0);
                BufferedImage qrcodeImage = pageImage.getSubimage(0, pageImage.getHeight() - 200, 200, 200);
                pdDocument.close();

                byte[] resultPayload;
                try { // try zxing decode qrcodeImage *not* in PURE_BARCODE mode
                    resultPayload = new QRCodeReader().decode(
                            new BinaryBitmap(new HybridBinarizer(new RGBLuminanceSource(qrcodeImage.getWidth(),
                                    qrcodeImage.getHeight(),
                                    qrcodeImage.getRGB(0, 0, qrcodeImage.getWidth(), qrcodeImage.getHeight(), null, 0,
                                            qrcodeImage.getWidth())))), DECODING_HINTS_IMPURE).getText().getBytes(
                                    STRING_ENCODING);
                } catch (Throwable ex) {
                    try { // failed so try zxing decode qrcodeImage in PURE_BARCODE mode
                        resultPayload = new QRCodeReader().decode(
                                new BinaryBitmap(new HybridBinarizer(new RGBLuminanceSource(qrcodeImage.getWidth(),
                                        qrcodeImage.getHeight(),
                                        qrcodeImage.getRGB(0, 0, qrcodeImage.getWidth(), qrcodeImage.getHeight(), null,
                                                0,
                                                qrcodeImage.getWidth())))), DECODING_HINTS_PURE).getText().getBytes(
                                        STRING_ENCODING);
                    } catch (Throwable ex2) { // both decodings failed, display image
                        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(qrcodeImage)),
                                "N=" + Integer.toString(N),
                                JOptionPane.PLAIN_MESSAGE, null);
                        System.out.println("Encoded=" + payloadString);
                        throw ex2;
                    }
                }

                if (!Arrays.equals(payload, resultPayload)) {
                    throw new RuntimeException("Payload mismatch.");
                }


            }
        }

    }
    private final static String STRING_ENCODING = "ISO-8859-1";
    final private static Map<EncodeHintType, Object> ENCODING_HINTS = new EnumMap<EncodeHintType, Object>(
            EncodeHintType.class);

    static {
        ENCODING_HINTS.put(EncodeHintType.CHARACTER_SET, STRING_ENCODING);
        ENCODING_HINTS.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    }

    final private static Map<DecodeHintType, Object> DECODING_HINTS_IMPURE = new EnumMap<DecodeHintType, Object>(
            DecodeHintType.class);

    static {
        DECODING_HINTS_IMPURE.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
        DECODING_HINTS_IMPURE.put(DecodeHintType.CHARACTER_SET, STRING_ENCODING);
    }

    final private static Map<DecodeHintType, Object> DECODING_HINTS_PURE = new EnumMap<DecodeHintType, Object>(
            DecodeHintType.class);

    static {
        DECODING_HINTS_PURE.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
        DECODING_HINTS_PURE.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
        DECODING_HINTS_PURE.put(DecodeHintType.CHARACTER_SET, STRING_ENCODING);
    }

}

[... ZXing throws a FormatException ...] [... ZXing 抛出 FormatException ...]

[... The ZXing phone application can read the dialog QR Code too ...] [... ZXing手机应用程序也可以读取对话框二维码...]

The following changes improves the accuracy to about N=64 for high quality error correction and N=100 with no error correction (which suffices for my needs).以下更改将准确度提高到大约 N=64(用于高质量纠错)和 N=100(没有纠错)(这足以满足我的需要)。

Three changes are needed.需要三个改变。

Firstly, as suggested by @Tilman Hausherr, use a lossless factory when inserting the QR Code image into the PDF.首先,正如@Tilman Hausherr 所建议的,在将二维码图像插入 PDF 时使用无损工厂 While this does not make any difference on the code as presented in the OP, it is a necessary co-condition for the other changes to be effective.虽然这对 OP 中提供的代码没有任何影响,但它是其他更改生效的必要条件。

Further, render the PDF in binary rather than rgb, and at a higher scale .此外,以二进制而不是 rgb 格式呈现 PDF,并以更高的比例呈现。 The default scale (1.0) is only 72dpi.默认比例 (1.0) 仅为 72dpi。

pageContent.drawImage(LosslessFactory.createFromImage(pdDocument, qr), 0, 0, 200, 200);
...
BufferedImage pageImage = new PDFRenderer(pdDocument)
    .renderImage(0,3.0f,ImageType.BINARY);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM