从零画一个二维码(QR-Code)

EXCEL表格在顶部资源下载

数据编码

题目要求编码为‘电子科技大学 信息感知与标识 嘎嘎嘎 2019010801003’

在综合考虑之后确定最终的编码格式为‘电子科技大学,信息感知与标识,嘎嘎嘎 2019010801003’,其中的符号为中文符号。

则考虑使用混合编码模式,分为两部分汉字和数字进行编码。

在汉字部分因为使用类型为‘1101’的GB2312编码并未正确识别,我们改用**’8-bit Byte Mode‘**模式;在数字部分,因为位数并不是纯数字中常见的3的倍数,我们添加了空格,使用’Alphanumeric Mode‘,即字母数字混合模式。

汉字编码

首先对汉字部分进行编码,使用GB2312进行编码,每个汉字对应的十六进制和十进制值如下所示:

a = '电子科技大学,信息感知与标识,嘎嘎嘎'
a_hex = a.encode('gb2312')
print(len(a))
print(a_hex)
for i in a_hex:
    print(i, end=' ')
    
    
"""
18
b'\xb5\xe7\xd7\xd3\xbf\xc6\xbc\xbc\xb4\xf3\xd1\xa7\xa3\xac\xd0\xc5\xcf\xa2\xb8\xd0\xd6\xaa\xd3\xeb\xb1\xea\xca\xb6\xa3\xac\xc2\xe6\xb3\xa9\xc8\xbb'
181 231 215 211 191 198 188 188 180 243 209 167 163 172 208 197 207 162 184 208 214 170 211 235 177 234 202 182 163 172 194 230 179 169 200 187 
"""

随后将这些转换为八位的二进制数,并添加模式符和字符数量,规则如下所示:

image-20230624161655405


name_lst = []
for i in range(len(a)):
    index1 = a_hex[i * 2]
    index2 = a_hex[i * 2 + 1]
    str_temp = ''
    for j in [index1, index2]:
        temp = bin(j)[2:]
        str_temp += '0' * (8 - len(temp)) + temp
    name_lst.append(str_temp)
# print(name_lst)
count = '0' * (8 - len(bin(len(a) * 2)[2:])) + bin(len(a) * 2)[2:]
name_str = '0100' + count
for i in name_lst:
    name_str += i
print(f'len:{len(name_str)} data:{name_str}')

"""
len:300 data:010000100100101101011110011111010111110100111011111111000110101111001011110010110100111100111101000110100111101000111010110011010000110001011100111110100010101110001101000011010110101010101101001111101011101100011110101011001010101101101010001110101100110000101110011010110011101010011100100010111011
"""

得到的二进制串长度为4(模式)+8(计数)+18(汉字数) * 2(字节) * 8(比特)=300

学号编码

在这部分我们使用数字字母混合模式进行编码,其规则如下所示:

输入数据字符每两个字符被分为一组,被编码为11位二进制代码。第一个字符的字符值乘以45加第二个数字的字符值,字符值如下所示:

image-20230624162258233


value = {'1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '0': 0, ' ': 36}
id_ori = ' 2019010801003'
id_str = '0010'	# 模式符
id_str += '000001110'	#	计数
for i in range(7):
    i1 = id_ori[i * 2]
    i2 = id_ori[i * 2 + 1]
    plus = bin(value[i1] * 45 + value[i2])[2:]
    temp = '0' * (11 - len(plus)) + plus
    id_str += temp
print(f'len:{len(id_str)}:{id_str}')

"""
len:90:001000000111011001010110000000000010011001010100000101101001011010000000010110100000000011
"""

得到的二进制串长度为4(模式)+9(计数)+14/2*11=90

选择版本

我们使用的是混合模式(‘Mixing modes’),混合模式的数据排布规则如下所示:

image-20230624161903854

此时不考虑终止符纠错码等,我们的二进制串长度为90+300=390,则考虑使用Version3,简单考虑我们使用最简单的纠错格式L,版本3的各纠错格式的数据容量如下所示:

image-20230624161909446

3-L的数据容量是440位,符合我们要求

补全数据

结束符和8整倍数约束

首先我们对得到的390位字符串添加0000终止符,如果添加后的二进制串长度不是8的倍数,则继续补0:

data = name_str + id_str + '0000'
print(f'len:{len(data)}:{data}')
last = len(data) % 8
data += '0' * (8 - last)
print(f'len:{len(data)}:{data}')

"""
len:394:0100001001001011010111100111110101111101001110111111110001101011110010111100101101001111001111010001101001111010001110101100110100001100010111001111101000101011100011010000110101101010101011010011111010111011000111101010110010101011011010100011101011001100001011100110101100111010100111001000101110110010000001110110010101100000000000100110010101000001011010010110100000000101101000000000110000
len:400:0100001001001011010111100111110101111101001110111111110001101011110010111100101101001111001111010001101001111010001110101100110100001100010111001111101000101011100011010000110101101010101011010011111010111011000111101010110010101011011010100011101011001100001011100110101100111010100111001000101110110010000001110110010101100000000000100110010101000001011010010110100000000101101000000000110000000000
"""

此时,我们得到400位的数据,3-L的数据位数是440位,我们在后序分别补十进制的236和17,直至其满足440位,得到的十进制数据如下所示,应为440/8=55个十进数字:

message = []
for i in range(int(len(data) / 8)):
    temp = data[8 * i:8 * i + 8]
    message.append(int(temp, 2))

# 填补空缺数
# 236 17
total = int(440 / 8)
for i in range(len(message), total):
    message.append(236) if message[i - 1] != 236 else message.append(17)
print(f'len:{len(message)} data:{message}')

"""
len:55 data:[66, 75, 94, 125, 125, 59, 252, 107, 203, 203, 79, 61, 26, 122, 58, 205, 12, 92, 250, 43, 141, 13, 106, 173, 62, 187, 30, 172, 171, 106, 58, 204, 46, 107, 58, 156, 139, 178, 7, 101, 96, 2, 101, 65, 105, 104, 5, 160, 12, 0, 236, 17, 236, 17, 236]
"""

计算纠错码

各版本要求的纠错码数量如下所示,3-L的要求为15位纠错码

image-20230624161923361

在https://www.thonky.com/qr-code-tutorial/generator-polynomial-tool处得到多项式系数为:

ploy = [0,8,183,61,91,202,37,51,58,58,237,140,124,5,99,105]

随后进行纠错码生成,其过程参考了https://www.thonky.com/qr-code-tutorial/error-correction-coding,使用提供的https://www.thonky.com/qr-code-tutorial/log-antilog-tableLog Antilog Table for Galois Field 256表可以自行设计程序计算纠错码:

def check(mydata, mypoly):
    df = pd.read_excel('alpha&int.xlsx')

    alpha2int = dict()
    int2alpha = dict()
    for i in range(256):
        row = df.iloc[i]
        alpha2int.update({int(row['alpha2int']): int(row['value1'])})
        int2alpha.update({int(row['int2alpha']): int(row['value2'])})
    for _ in range(len(mydata)):
        lead = mydata[0]
        lead2alpha = int2alpha[lead]
        temp = [(i + lead2alpha) % 255 for i in mypoly]
        temp = [alpha2int[i] for i in temp]
        for i in range(len(mypoly)):
            mydata[i] = mydata[i] ^ temp[i]
        del mydata[0]
        if len(mydata) < len(mypoly):
            for i in range(len(mypoly) - len(mydata)):
                mydata.append(0)
        print(mydata)
    mydata.pop()
    return mydata

当然更加方便的是使用官方提供的:https://www.thonky.com/qr-code-tutorial/show-division-steps可以直接得到,规避此处的繁复计算:

计算得到纠错码如下所示:

check_15 = check(message_copy, ploy)
print(f'check_15:{check_15}')

"""
check_15:[47, 151, 159, 251, 91, 160, 21, 244, 72, 231, 92, 238, 194, 185, 194]
"""

将十进制数据和十进制纠错码合并得到完整数据,将其每个十进制转换为8位的二进制串,得到的数据总位数:*(55+15)8=560

放置数据

格式标准和填写顺序

整体的二维码格式如下所示,该格式适用于2-7版本的二维码

image-20230624161933088

对于每个八位二进制,有填写顺序如下所示

image-20230624161940932

我们使用excel和python来进行填写,初始化如下所示:

image-20230624161949097

在数据填写的区域,标号就是要填写的二进制串的位置,蓝色位置的abcd是版本信息,后续说明,使用xlrd,xlwt,xlutils对表格进行操作

填写01数据

import xlrd
import xlwt
import xlutils.copy
import numpy as np


def write_black(worksheet, i, j):
    pattern = xlwt.Pattern()
    pattern.pattern = xlwt.Pattern.SOLID_PATTERN
    pattern.pattern_fore_colour = 0
    style = xlwt.XFStyle()
    style.pattern = pattern
    worksheet.write(i, j, '', style)


def write_01(worksheet, i, j, data):
    worksheet.write(i, j, data)

s = message_bin_lst
readbook = xlrd.open_workbook('QR_EXCEL.xls', formatting_info=True)
readsheet = readbook.sheet_by_index(0)
writebook = xlutils.copy.copy(readbook)
writesheet = writebook.get_sheet(0)

# 写01
for i in range(29):
    for j in range(29):
        try:
            value = readsheet.cell_value(i, j)
            data = s[int(value)]
            write_01(writesheet, i, j, data)
        except:
            pass
writebook.save('Only01.xls')

得到结果如下所示:

image-20230624162001308

设置掩码

共有七种掩码作为选择,我们使用掩码1:

image-20230624162010281

在这部分中,我们使用lambda函数对位置进行判断,如果满足式子就进行反转:

# 掩码

mask0 = lambda x, y: (x + y) % 2 == 0
mask1 = lambda x, y: x % 2 == 0
mask2 = lambda x, y: y % 3 == 0
mask3 = lambda x, y: (x + y) % 3 == 0
mask4 = lambda x, y: (np.floor(x / 2) + np.floor(y / 3)) % 3 == 0
mask5 = lambda row, column: ((row * column) % 2) + ((row * column) % 3) == 0
mask6 = lambda row, column: (((row * column) % 2) + ((row * column) % 3)) % 2 == 0
mask7 = lambda row, column: (((row + column) % 2) + ((row * column) % 3)) % 2 == 0


def make_mask(mask):
    readbook = xlrd.open_workbook('Only01.xls', formatting_info=True)
    readsheet = readbook.sheet_by_index(0)
    writebook = xlutils.copy.copy(readbook)
    writesheet = writebook.get_sheet(0)
    for i in range(29):
        for j in range(29):
            try:
                value = int(readsheet.cell_value(i, j))
                if mask(i, j):
                    value = 1 if value == 0 else 0
                writesheet.write(i, j, value)
            except:
                pass
    writebook.save('Mask01.xls')


make_mask(mask1)

得到的结果如下所示,可以观察0,3,……3n列的值的变化:

image-20230624162019852

改变色值

完成掩码我们进行染色,0不写,1填充黑色

# 改变黑白
readbook = xlrd.open_workbook('Mask01.xls', formatting_info=True)
readsheet = readbook.sheet_by_index(0)
writebook = xlutils.copy.copy(readbook)
writesheet = writebook.get_sheet(0)
for i in range(29):
    for j in range(29):
        try:
            data = readsheet.cell_value(i, j)
            data = int(data)
            if data == 0:
                write_01(writesheet, i, j, '')
            if data == 1:
                write_01(writesheet, i, j, '')
                write_black(writesheet, i, j)
        except:
            pass
writebook.save('Black.xls')

image-20230624162029844

写版本信息

蓝色的保留区域被称为’Format and Version Information‘,其填写规则如下所示,参考:https://www.thonky.com/qr-code-tutorial/format-version-tables:

image-20230624162036764

# 写版本信息
format_info_mask0 = '111011111000100'
format_info_mask1 = '111001011110011'
format_info_mask2 = '111110110101010'
format_info_mask3 = '111100010011101'
format_info_mask4 = '110011000101111'
format_info_mask5 = '110001100011000'
format_info_mask6 = '110110001000001'
format_info_mask7 = '110100101110110'


def write_format(format: str):
    mydict = dict()
    for i in range(len(format)):
        mydict.update({chr(i + 97): int(format[i])})
    readbook = xlrd.open_workbook('Black.xls', formatting_info=True)
    readsheet = readbook.sheet_by_index(0)
    writebook = xlutils.copy.copy(readbook)
    writesheet = writebook.get_sheet(0)
    for i in range(29):
        for j in range(29):
            try:
                data = str(readsheet.cell_value(i, j))
                data = mydict[data]
                if data == 0:
                    write_01(writesheet, i, j, '')
                if data == 1:
                    write_01(writesheet, i, j, '')
                    write_black(writesheet, i, j)
            except:
                pass
    writebook.save('Final.xls')


write_format(format_info_mask1)

最终结果如下所示:

image-20230624162043325

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值