手动解码 QR 码 - 如何正确提取长度和编码?

Manually decoding a QR code - how to properly extract length and encoding?

考虑以下代码:

#!/usr/bin/env python

import sys


m = ['                       ',
     ' XXXXXXX  X  X XXXXXXX ',
     ' X     X   XX  X     X ',
     ' X XXX X X  XX X XXX X ',
     ' X XXX X XX    X XXX X ',
     ' X XXX X  XXX  X XXX X ',
     ' X     X   XXX X     X ',
     ' XXXXXXX X X X XXXXXXX ',
     '          XXXX         ',
     '    XX XX X X     XX   ',
     ' XX  XX  X XXX XXXX  X ',
     '  XXX XX X X    X X  X ',
     '  X  XX X     XXXX  X  ',
     ' X  XXXXXX  X XXXXXXXX ',
     '         XXXX XX   XX  ',
     ' XXXXXXX XXX      XX   ',
     ' X     X     X     XXX ',
     ' X XXX X X XX  X X X X ',
     ' X XXX X XX    XXX     ',
     ' X XXX X  XX    XXX XX ',
     ' X     X  XXX X X X XX ',
     ' XXXXXXX  X  X  XX X   ',
     '                       ']

m = map(lambda l: l.replace('X', '1').replace(' ', '0'), m)



def flatten_arr(arr):
    return (''.join(str(c) for c in arr))

def arr_to_b2(arr):
    return int(flatten_arr(arr), 2)


def main():
    if len(m) != len(m[0]):
        raise ValueError("QR code is not square")
    if len(m) != 23:
        raise NotImplementedError("This program can currently"
                                  " parse only 23x23 QR codes.")

    ECC_EXPLAIN = {0b00: 'H (30%)', 0b01: 'Q (25%)',
                   0b10: 'M (15%)', 0b11: 'L (7%)'}
    ecc_arr = m[9][1:3]
    ecc_lvl = arr_to_b2(ecc_arr)
    print("Error correction: %s" % ECC_EXPLAIN.get(ecc_lvl, ecc_lvl))
    # TODO: check if this is consistent with its horizontal equivalent

    mask_arr = m[9][3:6]
    mask = arr_to_b2(mask_arr)
    print("Mask: %s" % flatten_arr(mask_arr))

    ENCODING_EXPLAIN = {
        0b0000: 'End of message',
        0b0001: 'Numeric encoding (10 bits per 3 digits)',
        0b0010: 'Alphanumeric encoding (11 bits per 2 characters)',
        0b0011: 'Structured append (used to split a message across multiple QR symbols)',
        0b0100: 'Byte encoding (8 bits per character)',
        0b0101: 'FNC1 in first position (see Code 128 for more information)',
        0b0111: 'Extended Channel Interpretation (select alternate character set or encoding)',
        0b1000: 'Kanji encoding (13 bits per character)',
        0b1001: 'FNC1 in second position',
    }

    encoding_arr = [m[21][21], m[21][20], m[20][21], m[20][20]]
    print("Encoding: %s" % flatten_arr(encoding_arr))


if __name__ == '__main__':
    main()

我正在尝试根据维基百科文章解码示例 QR 码,但结果似乎没有意义 - 编码似乎是“0011”,而我希望是字母数字编码。我看错了图表吗?如何正确提取二维码的编码和长度?

看来我在阅读时没有屏蔽这些位。这是我解决它的方法:

#!/usr/bin/env python

import sys


m = [[1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1],
     [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
     [1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],
     [1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1],
     [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1],
     [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1],
     [1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1],
     [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
     [0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0],
     [1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1],
     [0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1],
     [0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0],
     [1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1],
     [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0],
     [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0],
     [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1],
     [1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1],
     [1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
     [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1],
     [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1],
     [1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0]]


def flatten_arr(arr):
    return (''.join(str(c) for c in arr))

def arr_to_b2(arr):
    return int(flatten_arr(arr), 2)

def mask(m, i, j):
    if ((i*j)%3 + i*j) % 2 == 0:
        return int(not(m[i][j]))
    return m[i][j]

def main(m):
    if len(m) != len(m[0]):
        raise ValueError("QR code is not square")
    if len(m) != 21:
        raise NotImplementedError("This program can currently"
                                  " parse only 21x21 QR codes.")

    ECC_EXPLAIN = {0b00: 'H (30%)', 0b01: 'Q (25%)',
                   0b10: 'M (15%)', 0b11: 'L (7%)'}
    ecc_arr = m[8][0:2]
    ecc_lvl = arr_to_b2(ecc_arr)
    print("Error correction: %s" % ECC_EXPLAIN.get(ecc_lvl, ecc_lvl))
    # TODO: check if this is consistent with its horizontal equivalent

    MASKS = {
        0b000: lambda i, j: (i * j) % 2 + (i * j) % 3 == 0,
        0b001: lambda i, j: (i / 2 + j / 3) % 2 == 0,
        0b010: lambda i, j: ((i * j) % 3 + i + j) % 2 == 0,
        0b011: lambda i, j: ((i * j) % 3 + i * j) % 2 == 0,
        0b100: lambda i, j: i % 2 == 0,
        0b101: lambda i, j: (i + j) % 2 == 0,
        0b110: lambda i, j: (i + j) % 3 == 0,
        0b111: lambda i, j: j % 3 == 0,
    }
    mask_arr = m[8][2:5]
    mask_id = arr_to_b2(mask_arr)
    print("Mask: %s" % flatten_arr(mask_arr))
    masked = MASKS[mask_id]
    mask = lambda m, i, j: int(not m[i][j]) if masked(i,j) else m[i][j]

    ENCODING_EXPLAIN = {
        0b0000: 'End of message',
        0b0001: 'Numeric encoding (10 bits per 3 digits)',
        0b0010: 'Alphanumeric encoding (11 bits per 2 characters)',
        0b0011: 'Structured append (used to split a message across multiple QR symbols)',
        0b0100: 'Byte encoding (8 bits per character)',
        0b0101: 'FNC1 in first position (see Code 128 for more information)',
        0b0111: 'Extended Channel Interpretation (select alternate character set or encoding)',
        0b1000: 'Kanji encoding (13 bits per character)',
        0b1001: 'FNC1 in second position',
    }

    encoding_arr = [mask(m, 20, 20), mask(m, 20, 19),
                    mask(m, 19, 20), mask(m, 19, 19)]
    encoding_id = arr_to_b2(encoding_arr)

    length_arr = [mask(m, 18, 20), mask(m, 18, 19),
                  mask(m, 17, 20), mask(m, 17, 19),
                  mask(m, 16, 20), mask(m, 16, 19),
                  mask(m, 15, 20), mask(m, 15, 19)]

    length = arr_to_b2(length_arr)
    print("Length: %s" % length)

    print("Encoding: %s (%s)" % (ENCODING_EXPLAIN[encoding_id],
                                 flatten_arr(encoding_arr)))

    if 'ipython' in sys.argv:
        import IPython
        IPython.embed()

if __name__ == '__main__':
    main(m)