Search code examples
pythonqr-codezbar

Reading binary qr codes with pyzbar


I am trying to encode some binary data into a QR code using the qrcode library then decode it using the pyzbar library, but it is not working properly. When it is decoding, the result is slightly changed and some bytes are added. I made this test script to demonstrate the problem:

import qrcode
from pyzbar.pyzbar import decode, Decoded, ZBarSymbol


def test(data):
    code = qrcode.QRCode(
        version=6,
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        border=2,
    )

    code.add_data(data)
    code.make(fit=False)

    img = code.make_image().get_image()
    img.save("out.png")
    detections: list[Decoded] = decode(img, symbols=[ZBarSymbol.QRCODE])
    if detections:
        decoded = detections[0].data
        print(data)
        print(data == decoded)
        if data != decoded:
            print(decoded)
            print(data.hex())
            print(decoded.hex())
        print()


# Plain text
test(b"Hello World!")
test(b"\x8bwhat is happening\x00\x34")
test(b"\x00huh??\x65")
test(b"r@mM-{\x8be\xcdTh\xf4G6W\xe8\x10\x81\x89W\xe7\x89\r\xa6)z~2]45pa.\xbak([%\x94\xd2\x80\x1fy\xc5bJ\x00")

Output:

b'Hello World!'
True

b'\x8bwhat is happening\x004'
False
b'\xe4\xbb\x87hat is happening\x004'
8b776861742069732068617070656e696e670034
e4bb876861742069732068617070656e696e670034

b'\x00huh??e'
True

b'r@mM-{\x8be\xcdTh\xf4G6W\xe8\x10\x81\x89W\xe7\x89\r\xa6)z~2]45pa.\xbak([%\x94\xd2\x80\x1fy\xc5bJ\x00'
False
b'r@mM-{\xc2\x8be\xc3\x8dTh\xc3\xb4G6W\xc3\xa8\x10\xc2\x81\xc2\x89W\xc3\xa7\xc2\x89\r\xc2\xa6)z~2]45pa.\xc2\xbak([%\xc2\x94\xc3\x92\xc2\x80\x1fy\xc3\x85bJ\x00'
72406d4d2d7b8b65cd5468f4473657e810818957e7890da6297a7e325d343570612eba6b285b2594d2801f79c5624a00
72406d4d2d7bc28b65c38d5468c3b4473657c3a810c281c28957c3a7c2890dc2a6297a7e325d343570612ec2ba6b285b25c294c392c2801f79c385624a00

How could I fix this so that I can decode the QR code and get the same binary data that I used to make the QR code?


Solution

  • It turns out the issue was in the decoding by pyzbar. It was interpreting the binary data as text and trying to coerce it to some guessed encoding. As of version 0.23.1 of zbar, there is an undocumented config option to tell zbar to just output the raw binary of the QR code (as from this question). Pyzbar, however, does not support this config option. There is a pull request to add it, but I decided to instead just encode the data in base45 using this library since the slight increase in size is acceptable for my use case and then my program is not dependent on a specific zbar version.