小鹿活码怎么注册_Hello World!_微信二维码

非凡微信社群营销服务平台www.haqsl.com我们提供【社群管理裂变】【自动建群】【多群转播】【活码系统】【小程序开发】【公众号开发】【各类商城SAAS】一站式服务,各类功能提供免费体验,满意付款,如您还有其他疑问请您添加企鹅/微信1003312430方便咨询哦。
非凡社群助手——微信活码系统演示(客户活码二维码累计扫描量一千万+)
活码系统介绍:
二维码图案不变,内容可随时变更, 极大提高营销效果,基于活码技术,二维码图案更简单,扫码更加容易宣传海报、二维码印刷、商品.群.软文。
让二维码图案更简单,扫码更加容易宣传。

,

2016.7.5 更新:长文多图代码预警,电脑食用效果更佳。

完整版代码已上传 GitHub,后续一些有的没的的代码更新也都在GitHub上()

给末端的几个被自动识别的QR码做了防自动识别。。顺便也检测一下我们这不怎么高的容错率(7%)。要是再被知乎自动识别了。。。_(:з」∠)_

======================================================================

作为一只程序猿,第一篇文章自然要写hello world,然则呐,我看你们今天这样热情,只写一句hello world就闷声你们又不喜悦。恰好最近实习事情在处置QR码,就来薛习一下QR码版本的hello world吧。

  • 前期准备

  • 靠山信息

要想实现一个QR码天生器,我们首先需要领会什么是QR码,QR码有哪些类型,以及QR码是若何事情的。

(Quick Response Code) 是二维码的一种,在正方形二位矩阵内通过是非标识编码二进制位从而编码数据,最早发现用于日本汽车制造业追踪零部件。QR码现有40个尺度版本,4个微型版本。QR码的数据编码方式有四种:

  1. 数字(Numeric):0-9
  2. 大写字母和数字(alphanumeric):0-9,A-Z,空格,$,%,*,+,-,.,/,:
  3. 二进制/字节:通过 尺度编码
  4. 日本汉字/假名:通过 尺度编码

QR码另有四种容错级别可以选择:

  1. L(Low):7%的字码可被修正
  2. M(Medium):15%的字码可被修正
  3. Q(Quartile):25%的字码可被修正
  4. H(High):30%的字码可被修正

(Wikipedia: QR code, )

(40+4)×4×4=…… ∑(っ °Д °;)っ


咳。。好,那我们为了读者着想 (←_←),只实现 Version 1-Byte mode-Low error control 的QR码天生就好了嗯。。

好我们继续。

现在QR码随处可见,人人阅码无数可能也发现了一些纪律:这些QR码有大有小、有红有绿,有些另有种种装饰,然则它们总有一些部门看起来十分相似,好比三个角落里总有“回”字形的图样。这就要谈到QR码的结构了。

  • 结构

除了存储编码的数据,QR码里还含有一些基本尺度里钦定的图样来辅助扫描软件快速识别和解码。


(图片泉源:Wikipedia:QR码, )

尺度(ISO/IEC 18004)里是这样说的


(图片泉源:ISO/IEC 18004:
Information – Automatic identification and data capture techniques – QR Code barcode symbology specification

以是说我们做QR码啊,照样要凭据QR尺度,凭据基本尺度来。我没有任何硬点这些图样的意思,它们都是有自己的作用的,我们一个一个说。

    • 功能性图样(function patterns):不介入编码数据的区域。

      • 闷声区(quite zone):尺度中划定尺度QR码(Ver1-40)周围应有宽4个单元、微型QR码周围应有宽2个单元的区域颜色等效于QR码中白色点(light module),其中不能有图样或符号,以保证QR码清晰可识别。
      • 定位标识(finder pattern):之前提到的“回”字形标识,位于QR码的左上,右上和左下角,用于协助扫描软件定位QR码并变换坐标系。定位标识可以让QR码在随便角度被扫描,这是一维条形码做不到的。 (图片泉源:ISO/IEC 18004: Information – Automatic identification and data capture techniques – QR Code barcode symbology specification
      • 分隔符(separator):一单元宽的白色点带,位于每个定位标识和编码区域之间用于区分。
      • 准时标识(timing pattern):一单元宽的是非交替点带,由玄色起始和竣事,用于指示标识密度和确定坐标系。
      • 校正标识(alignment pattern):只有 Version 2 及以上的QR码有校正标识。校正标识用于进一步校正坐标系。校正标识的数目取决于版本。
    • 编码区域(encoding region):编码数据的区域。
      • 花样信息(format information):存储容错级别和数据掩码,和分外的自身BCH容错码,讲到再睁开。
      • 版本信息(version information):存储版本信息。
      • 数据及容错字码(data and error correction codewords):存储编码方式,现实编码的数据和数据的RS容错码。

以上就是QR码的通用结构尺度了,再来看一看我们要实现的 Version 1 QR码的结构:


(图片泉源:ISO/IEC 18004: Information – Automatic identification and data capture techniques – QR Code barcode symbology specification

剖析完了QR码的结构,豁然开朗,这器械也不就这么回事嘛,简朴!最先做!(多年以后,当程序猿面临电脑屏幕的时刻,将会回想起不懂事的自己立起flag的谁人下昼)

  • 流程

利便的是,尺度也划定了将数据编码成QR码的流程:

  1. 数据剖析(data analysis):剖析输入数据,凭据数据决议要使用的QR码版本、容错级别和编码模式。低版本的QR码无法编码过长的数据,含有非数字字母字符的数据要使用扩展字符编码模式。由于我们只实现 V1-L byte mode QR码,此步略去。
  2. 编码数据(data encoding):凭据选择的编码模式,将输入的字符串转换成比特流,插入模式标识码(mode indicator)和终止标识符(terminator),把比特流切分成八比特的字节,加入填充字节来知足尺度的数据字码数要求。
  3. 盘算容错码(error correction coding):对步骤二发生的比特流盘算容错码,附在比特流之后。高版本的编码方式可能需要将数据流切分成块(block)再划分举行容错码盘算。
  4. 组织数据(structure final message):凭据结构图把步骤三获得的有容错的数据流切分,准备填充。
  5. 填充(module placement in matrix):把数据和功能性图样凭据尺度填充到矩阵中。
  6. 应用数据掩码(data masking):应用尺度中的八个数据掩码来变换编码区域的数据,选择最优的掩码应用。讲到再睁开。
  7. 填充花样和版本信息(format and version information):盘算花样和版本信息填入矩阵,完成QR码。

简朴! ( ̄ε(# ̄)╰╮( ̄▽ ̄///) ↓↓↓


  • 代码实战

为(yin)了(wei)可(wo)读(lan)和简朴,我们用 Python 来实现这个简化版QR码天生器。为了天生和操作图像,我们需要安装第三方图像处置库 。限于篇幅,本文纰谬PIL的使用做过多先容,入门可参见 ,。

由于程序需要处置矩阵,利便起见在这里先界说坐标系统。以矩阵的左上角为原点,原点坐标界说为(0,0),i 轴向右,坐标 i 对应列;j 轴向下,坐标 j 对应行。于是对于图像中的像素(i,j),有矩阵元素 mat [ j ] [ i ] 与之对应。


新建Python代码文件 qrcode.py,引入需要的库:

# qrcode.py
from PIL import Image, ImageDraw

为了头脑简捷,我们自顶向下地构建代码。首先,假设我们已经填充好了一个QR码的矩阵bitmap,我们需要把响应的图像天生出来。这里就有了图像巨细的问题:Version 1 的QR码示意为 21×21 的矩阵,直接把这个矩阵当做位图来输出的话,图像只有21像素宽。为了获得巨细合适的图像,我们先界说图像巨细,再把每一个像素映射到合适的矩阵元素上。

在 qrcode.py 中添加如下代码:

def _genImage(bitmap, width, filename):
    '''  Generate image corresponding to the input bitmap  with specified width and filename.  '''
    # New image in black-white mode initialized with white.
    img = Image.new('1', (width, width), 'white')
    drw = ImageDraw.Draw(img)
    # Normalized pixel width.
    pwidth = width / len(bitmap)
    for j in range(width):
        # Normalized j coordinate in bitmap
        normalj = j / pwidth
        for i in range(width):
            # Normalized i coordinate in bitmap
            normali = i / pwidth
            if normalj < len(bitmap) and normali < len(bitmap):
                # Draw pixel.
                drw.point((i, j), fill=bitmap[normalj][normali])
    img.save(filename)

这个函数吸收三个参数:QR码矩阵bitmap,图像宽度width,保留文件名filename。

    img = Image.new('1', (width, width), 'white')
    drw = ImageDraw.Draw(img)

这两行初始化了图像和绘图工具。初始化图像时的参数 ‘1’ 代表天生是非模式图像,’white’ 代表图像初始化填充白色。

    pwidth = width / len(bitmap)

用图像宽度除以矩阵维度获得尺度化后的像素宽度(QR码中一个单元对应的像素数)。

    normalj = j / pwidth
    normali = i / pwidth

遍历图像时,将像素坐标(i,j)尺度化为矩阵坐标 [ i ][ j ]。检查不越界之后,按坐标绘制像素,最后保留图像。

保留代码后,我们测试一下这个函数。在文件目录打开命令行/shell,输入python进入Python REPL。引入qrcode然后举行测试。


我们界说了一个矩阵 test,然后挪用 qrcode._genImage 来天生一个240×240,名为 test.jpg 的图像如下。


我们注意到,原点处对应 (0 + 0) % 2 的像素为黑,由于 0 值对应玄色,1 对应白色。为了明确,在 qrcode.py 中加入如下界说

_LIGHT = 1
_DARK = 0

可见我们的图像天生函数是乐成的,现在只需要填充出QR码矩阵就行了。

嗯,玩一会。。_(:з」∠)_




好我们继续。

_genImage 函数吸收QR码矩阵作为参数,自顶向下地,我们需要天生这个矩阵。思量到一个QR码中有许多稳定的图样(fixed pattern),我们可以预先填充好一个含有这些稳定图样的模板,天生QR码矩阵时直接把编码好的数据填充到这个模板里就行了。

在 qrcode.py 中加入模板的界说,待填充:

_ver1 = [[_LIGHT for i in range(21)] for j in range(21)]

假设 _ver1 是已经填充好的模板,我们天生QR码矩阵需要怎么做呢?凭据前期准备,我们需要编码数据,填充数据,应用掩码,再填充花样信息。于是我们界说这些函数:

def _fmtEncode(fmt):
    '''Encode format code.'''
    pass

def _encode(data):
    '''  Encode the input data stream.  Add mode prefix, encode data using ISO-8859-1,  group data, add padding suffix, and call RS encoding method.  '''
    pass

def _fillData(bitstream):
    '''Fill the encoded data into the template QR code matrix'''
    pass

def _mask(mat):
    '''  Mask the data QR code matrix with all 8 masks,  and select the best mask.  '''
    pass

def _fillInfo(arg):
    '''  Fill the encoded format code into the masked QR code matrix.  '''
    pass

def _genBitmap(bitstream):
    '''  Take in the encoded data stream and generate the  final QR code bitmap.  '''
    return _fillInfo(_mask(_fillData(bitstream)))

_encode 编码数据,_fillData 将这些数据填充到模板中,_mask 应用掩码,_fmtEncode 编码花样信息,_fillInfo 填充花样信息,最后 _genBitmap 把这些函数按尺度串联起来,返回准备好的QR码矩阵给 _genImage 来天生QR码。

接下来我们凭据流程顺序实现这些函数。

  • 编码数据

首先我们要检测输入的数据是否跨越V1-L byte mode的最大编码长度17,若是跨越就抛出异常。在 qrcode.py 最先界说异常:

class CapacityOverflowException(Exception):
    '''Exception for data larger than 17 characters in V1-L byte mode.'''
    def __init__(self, arg):
        self.arg = arg

    def __str__(self):
        return repr(self.arg)

在 _encode 中加入检测

def _encode(data):
    '''  Encode the input data stream.  Add mode prefix, encode data using ISO-8859-1,  group data, add padding suffix, and call RS encoding method.  '''
    if len(data) > 17:
        raise CapacityOverflowException(
            'Error: Version 1 QR code encodes no more than 17 characters.')

在编码数据之前,还要凭据尺度的划定加入编码模式前缀和数据字符计数,byte mode的前缀是 0100,接上八位二进制数代表的数据长度,组成数据前缀。再把数据用 ISO/IEC 8859-1 尺度编码,按八个二进制位分组,接上终止符和11101100和00010001交替的填充字节,按尺度修剪到19字节,完成数据编码。实现 _encode 如下:

def _encode(data):
    '''  Encode the input data stream.  Add mode prefix, encode data using ISO-8859-1,  group data, add padding suffix, and call RS encoding method.  '''
    if len(data) > 17:
        raise CapacityOverflowException(
            'Error: Version 1 QR code encodes no more than 17 characters.')
    # Byte mode prefix 0100.
    bitstring = '0100'
    # Character count in 8 binary bits.
    bitstring += '{:08b}'.format(len(data))
    # Encode every character in ISO-8859-1 in 8 binary bits.
    for c in data:
        bitstring += '{:08b}'.format(ord(c.encode('iso-8859-1')))
    # Terminator 0000.
    bitstring += '0000'
    res = list()
    # Convert string to byte numbers.
    while bitstring:
        res.append(int(bitstring[:8], 2))
        bitstring = bitstring[8:]
    # Add padding pattern.
    while len(res) < 19:
        res.append(int('11101100', 2))
        res.append(int('00010001', 2))
    # Slice to 19 bytes for V1-L.
    res = res[:19]

在L容错品级下,编码了数据我们还需要盘算出七位的(可简朴了,看我和善的眼神 )

里德-所罗门码是 。这意味着一个牢固长度输入的数据将被处置成一个牢固长度的输出数据。在最常用的(255,223)里所码中,223个里德-所罗门输入符号(每个符号有8个 )被编码成255个输出符号。

大多数里所错误校正编码流程是成系统的。这意味着输出的码字中有一部门包含着输入数据的原始形式。

符号巨细为8位元的里所码迫使码长( )最长为255个符号。

尺度的(255,223)里所码可以在每个码字中校正最多16个里所符号的错误。由于每个符号事实上是8个位元,这意味着这个码可以校正最多16个短爆发性错误。

里德-所罗门码,犹如 一样,是一种透明码。这代表若是信道符号在行列的某些地方被反转,解码器一样可以事情。解码效果将是原始数据的弥补。然则,里所码在缩短后会失去透明性。在缩短了的码中,“丢失”的比特需要被0或者1替换,这由数据是否需要补足而决议。(若是符号这时刻反转,替换的0需要酿成1)。于是乎,需要在里所解码前对数据举行强制性的侦测决议(“是”或者“补足”)。

(Wikipedia: 里德-所罗门码,)

………………………………..(⊙v⊙)……………………………….


这。。照样留给有兴趣的读者吧(微笑)

参考:

在 _encode 之前加入如下RS容错码盘算工具:

def _gfpMul(x, y, prim=0x11d, field_charac_full=256, carryless=True):
    '''Galois field GF(2^8) multiplication.'''
    r = 0
    while y:
        if y & 1:
            r = r ^ x if carryless else r + x
        y = y >> 1
        x = x << 1
        if prim > 0 and x & field_charac_full:
            x = x ^ prim
    return r

# Calculate alphas to simplify GF calculations.
_gfExp = [0] * 512
_gfLog = [0] * 256
_gfPrim = 0x11d

_x = 1

for i in range(255):
    _gfExp[i] = _x
    _gfLog[_x] = i
    _x = _gfpMul(_x, 2)

for i in range(255, 512):
    _gfExp[i] = _gfExp[i-255]

def _gfPow(x, pow):
    '''GF power.'''
    return _gfExp[(_gfLog[x] * pow) % 255]

def _gfMul(x, y):
    '''Simplified GF multiplication.'''
    if x == 0 or y == 0:
        return 0
    return _gfExp[_gfLog[x] + _gfLog[y]]

def _gfPolyMul(p, q):
    '''GF polynomial multiplication.'''
    r = [0] * (len(p) + len(q) - 1)
    for j in range(len(q)):
        for i in range(len(p)):
            r[i+j] ^= _gfMul(p[i], q[j])
    return r

def _gfPolyDiv(dividend, divisor):
    '''GF polynomial division.'''
    res = list(dividend)
    for i in range(len(dividend) - len(divisor) + 1):
        coef = res[i]
        if coef != 0:
            for j in range(1, len(divisor)):
                if divisor[j] != 0:
                    res[i+j] ^= _gfMul(divisor[j], coef)
    sep = -(len(divisor) - 1)
    return res[:sep], res[sep:]

def _rsGenPoly(nsym):
    '''Generate generator polynomial for RS algorithm.'''
    g = [1]
    for i in range(nsym):
        g = _gfPolyMul(g, [1, _gfPow(2, i)])
    return g

def _rsEncode(bitstring, nsym):
    '''Encode bitstring with nsym EC bits using RS algorithm.'''
    gen = _rsGenPoly(nsym)
    res = [0] * (len(bitstring) + len(gen) - 1)
    res[:len(bitstring)] = bitstring
    for i in range(len(bitstring)):
        coef = res[i]
        if coef != 0:
            for j in range(1, len(gen)):
                res[i+j] ^= _gfMul(gen[j], coef)
    res[:len(bitstring)] = bitstring
    return res

(Source: Wikiversity:)

在 _encode 末端直接挪用 _rsEncode 添加容错码,完成数据编码部门。

def _encode(data):
    '''  Encode the input data stream.  Add mode prefix, encode data using ISO-8859-1,  group data, add padding suffix, and call RS encoding method.  '''
    if len(data) > 17:
        raise CapacityOverflowException(
            'Error: Version 1 QR code encodes no more than 17 characters.')
    # Byte mode prefix 0100.
    bitstring = '0100'
    # Character count in 8 binary bits.
    bitstring += '{:08b}'.format(len(data))
    # Encode every character in ISO-8859-1 in 8 binary bits.
    for c in data:
        bitstring += '{:08b}'.format(ord(c.encode('iso-8859-1')))
    # Terminator 0000.
    bitstring += '0000'
    res = list()
    # Convert string to byte numbers.
    while bitstring:
        res.append(int(bitstring[:8], 2))
        bitstring = bitstring[8:]
    # Add padding pattern.
    while len(res) < 19:
        res.append(int('11101100', 2))
        res.append(int('00010001', 2))
    # Slice to 19 bytes for V1-L.
    res = res[:19]
    # Call _rsEncode to add 7 EC bits.
    return _rsEncode(res, 7)

  • 数据切分和填充

(在我完成这个项目之后,想了想数据填充有更优雅的方式,还可以通用在其他版本的QR码上。感兴趣或者是想到的读者可以自行实现优化的 _fillData)

QR码尺度将八个二进制位(一字节)划定为一个数据元组,先将编码后数据的每一个字节填充到 2×4 的矩阵(高版本QR码中会泛起不规则形状的字节元组,本文中不思量。)中,再将这些小的矩阵填入QR码矩阵。尺度也划定了字节填入小矩阵的方式:


(图片泉源:ISO/IEC 18004: Information – Automatic identification and data capture techniques – QR Code barcode symbology specification

其中,7代表字节最高位(most significant bit),0代表最低位(least significant bit)。

在 _fillData 前添加 _fillByte 来实现单个字节的填充:

def _fillByte(byte, downwards=False):
    '''  Fill a byte into a 2 by 4 matrix upwards,  unless specified downwards.  '''
    bytestr = '{:08b}'.format(byte)
    res = [[0, 0], [0, 0], [0, 0], [0, 0]]
    for i in range(8):
        res[i/2][i%2] = not int(bytestr[7-i])
    if downwards:
        res = res[::-1]
    return res

有了填充好的小矩阵,接下来就把它们填入大矩阵中。尺度划定的填充方式为:由大矩阵的右下最先向上填充,遇到编码区域的界限后向左,改为向下填充,云云蛇行将数据填入数据区域。


(图片泉源:Wikipedia:QR code,)


思量到将小矩阵填入大矩阵的操作会异常频仍,我们把它写成函数来实现复用。在 qrcode.py 最先添加函数

def _matCp(src, dst, top, left):
    '''
    Copy the content of matrix src into matrix dst.
    The top-left corner of src is positioned at (left, top)
    in dst.
    '''
    res = copy.deepcopy(dst)
    for j in range(len(src)):
        for i in range(len(src[0])):
            res[top+j][left+i] = src[j][i]
    return res

要实现 _fillData,我们就会用到之前说的模板矩阵,那我们先把模板矩阵填充出来吧。

我们的想法是在模板矩阵中填入在所有V1-L QR码中都牢固稳定的标识来简化天生历程,那么首先我们得找出所有这样牢固稳定的标识。之前提到的功能性标识包含了大部门牢固的图样,那么我们先填充出这些功能性标识。定位标识和校正标识可以界说为变量,然则准时标识会随版本转变有长度转变,为了代码的可扩展性,我们把准时标识界说为天生函数。

在 qrcode.py 最先添加界说:

def _transpose(mat):
    '''Transpose a matrix'''
    res = [[mat[j][i] for j in range(len(mat))] for i in range(len(mat[0]))]
    return res

def _timSeq(len, vertical=False):
    '''
    Generate a horizontal, unless specified vertical
    timing sequence with alternating dark and light
    pixels with length len.
    '''
    res = [[i % 2 for i in range(len)]]
    if vertical:
        res = _transpose(res)
    return res

# Finder pattern.
_finder = _matCp(_matCp([[_DARK for i in range(3)] for j in range(3)],
    [[_LIGHT for i in range(5)] for j in range(5)], 1, 1),
    [[_DARK for i in range(7)] for j in range(7)], 1, 1)

# Alignment pattern. Not used in version 1.
_align = _matCp(_matCp([[_DARK]],
    [[_LIGHT for i in range(3)] for j in range(3)], 1, 1),
    [[_DARK for i in range(5)] for j in range(5)], 1, 1)

有了这些功能性标识,先别急着往模板里填。仔细读尺度我们会发现,在花样信息区域也有一个牢固稳定的黑点。


(图片泉源:ISO/IEC 18004: Information – Automatic identification and data capture techniques – QR Code barcode symbology specification

现实上这张图里就是 Version 1 QR码里所有的稳定样式了。继续在 qrcode.py 中填充模板:

# Version 1 QR code template with fixed patterns.
_ver1 = [[_LIGHT for i in range(21)] for j in range(21)]
_ver1 = _matCp(_finder, _ver1, 0, 0)
_ver1 = _matCp(_finder, _ver1, 14, 0)
_ver1 = _matCp(_finder, _ver1, 0, 14)
_ver1 = _matCp(_timSeq(5), _ver1, 6, 8)
_ver1 = _matCp(_timSeq(5, vertical=True), _ver1, 8, 6)
_ver1 = _matCp([[_DARK]], _ver1, 13, 8)

我们的模板矩阵就完成了,效果如图:


为了制止填充历程修改模板而导致后续QR码天生失足,保险起见我们只通过deepcopy使用这个模板,在 qrcode.py 头部加入模块引入:

import copy

然后实现 _fillData 如下:

def _fillData(bitstream):
    '''Fill the encoded data into the template QR code matrix'''
    res = copy.deepcopy(_ver1)
    for i in range(15):
        res = _matCp(_fillByte(bitstream[i], (i/3)%2!=0),
            res,
            21-4*((i%3-1)*(-1)**((i/3)%2)+2),
            21-2*(i/3+1))
    tmp = _fillByte(bitstream[15])
    res = _matCp(tmp[2:], res, 7, 11)
    res = _matCp(tmp[:2], res, 4, 11)
    tmp = _fillByte(bitstream[16])
    res = _matCp(tmp, res, 0, 11)
    tmp = _fillByte(bitstream[17], True)
    res = _matCp(tmp, res, 0, 9)
    tmp = _fillByte(bitstream[18], True)
    res = _matCp(tmp[:2], res, 4, 9)
    res = _matCp(tmp[2:], res, 7, 9)
    for i in range(3):
        res = _matCp(_fillByte(bitstream[19+i], True),
            res, 9+4*i, 9)
    tmp = _fillByte(bitstream[22])
    res = _matCp(tmp, res, 9, 7)
    for i in range(3):
        res = _matCp(_fillByte(bitstream[23+i], i%2==0),
            res, 9, 4-2*i)
    return res

这是一个异常ad hoc的实现,代码长然则没有什么技术含量。

测试一下填入数据的效果:




已经有一些QR码的样子了! <( ̄︶ ̄)> (没有完成,这是无法扫描的)

  • 掩码和责罚

获得了填入数据的矩阵,下一步就是应用掩码来变换数据图样。那有人要问了,既然我们已经把数据编入了QR码,想编码的信息就已经在里面了,为什么不直接填入花样信息获得QR码,而要多举行这么一步操作呢?

掩码真的是画蛇添足吗?你们呐照样要提高自身的姿势水平。QR码是要拿来扫描的,而扫描怕的就是无法清晰地分辨出编码信息的每一位。要是QR码中是非点数目不均,或是空间漫衍不均都市导致大色块区域的泛起,而大色块区域的泛起会增添扫描时定位的难度,从而降低扫描的效率。更严重的情形下,若是数据填入后恰巧泛起了功能性标识,好比定位标识的图样,还会滋扰正常功能性标识的作用,导致QR码无法扫描。

举个栗子:




这样的数据发生的原始QR码显著含有大量大面积色块,扫描难度很高。

以是,掩码和之前提到的在数据后添加11101100和00010001交替的填充字节,都是为了制止这种情形发生,让图像更“平均”。

知道了掩码的重要性,我们来看看掩码到底是什么。在盘算机科学中,掩码就是一个二进制串,通过和数据举行异或运算来变换数据。在QR码中,掩码也是通过异或运算来变换数据矩阵。以是你可能已经猜到了,QR码的掩码就是预先界说好的矩阵。QR尺度通过天生规则界说了八个数据掩码:

  1. dark if (row + column) mod 2 == 0
  2. dark if (row) mod 2 == 0
  3. dark if (column) mod 3 == 0
  4. dark if (row + column) mod 3 == 0
  5. dark if ( floor(row / 2) + floor(column / 3) ) mod 2 == 0
  6. dark if ((row * column) mod 2) + ((row * column) mod 3) == 0
  7. dark if ( ((row * column) mod 2) + ((row * column) mod 3) ) mod 2 == 0
  8. dark if ( ((row + column) mod 2) + ((row * column) mod 3) ) mod 2 == 0

给定了规则我们很容易写出代码来天生这些掩码:




然则且慢,你看泛起在泛起了什么问题吗?

对,掩码的局限也覆盖了功能性区域,要是用这样的掩码的话,功能性标识也难以幸免。以是我们需要一个代表数据区域的“蒙版”来过滤掉功能性区域中的掩图案。这个“过滤”的历程可以通过矩阵间“与”运算来实现。

在 qrcode.py 最先添加矩阵间“与”运算函数和数据区域蒙版的填充:

def _matAnd(mat1, mat2):
    '''  Matrix-wise and.  Dark and dark -> dark  Light and light -> light  Dark and light -> light  Light and dark -> light  '''
    res = [[_LIGHT for i in range(len(mat1[0]))] for j in range(len(mat1))]
    for j in range(len(mat1)):
        for i in range(len(mat1[0])):
            res[j][i] = int(mat1[j][i] == _LIGHT or mat2[j][i] == _LIGHT)
    return res

# Data area mask to avoid applying masks to functional area.
_dataAreaMask = [[_DARK for i in range(21)] for j in range(21)]
_dataAreaMask = _matCp([[_LIGHT for i in range(9)] for j in range(9)],
    _dataAreaMask, 0, 0)
_dataAreaMask = _matCp([[_LIGHT for i in range(9)] for j in range(8)],
    _dataAreaMask, 13, 0)
_dataAreaMask = _matCp([[_LIGHT for i in range(8)] for j in range(9)],
    _dataAreaMask, 0, 13)
_dataAreaMask = _matCp([[_LIGHT for i in range(4)]], _dataAreaMask, 6, 9)
_dataAreaMask = _matCp([[_LIGHT] for i in range(4)], _dataAreaMask, 9, 6)

填充出的数据区域蒙版效果如图


我们在界说掩码时和蒙版举行“与”运算,就可以获得局限准确的掩码了。继续添加掩码界说

# Data masks defined in QR standard.
_dataMasks = []
_dataMasks.append(_matAnd(_dataAreaMask,
    [[_DARK if (i+j)%2==0 else _LIGHT for i in range(21)] for j in range(21)]))
_dataMasks.append(_matAnd(_dataAreaMask,
    [[_DARK if j%2==0 else _LIGHT for i in range(21)] for j in range(21)]))
_dataMasks.append(_matAnd(_dataAreaMask,
    [[_DARK if i%3==0 else _LIGHT for i in range(21)] for j in range(21)]))
_dataMasks.append(_matAnd(_dataAreaMask,
    [[_DARK if (i+j)%3==0 else _LIGHT for i in range(21)] for j in range(21)]))
_dataMasks.append(_matAnd(_dataAreaMask,
    [[_DARK if (j/2 + i/3)%2==0 else _LIGHT for i in range(21)] for j in range(21)]))
_dataMasks.append(_matAnd(_dataAreaMask,
    [[_DARK if (i*j)%2+(i*j)%3==0 else _LIGHT for i in range(21)] for j in range(21)]))
_dataMasks.append(_matAnd(_dataAreaMask,
    [[_DARK if ((i*j)%2+(i*j)%3)%2==0 else _LIGHT for i in range(21)] for j in range(21)]))
_dataMasks.append(_matAnd(_dataAreaMask,
    [[_DARK if ((i+j)%2+(i*j)%3)%2==0 else _LIGHT for i in range(21)] for j in range(21)]))

效果如图


现在我们就可以安心地使用这些掩码啦!

在 qrcode.py 最先添加矩阵间异或函数

def _matXor(mat1, mat2):
    '''  Matrix-wise xor.  Dark xor dark -> light  Light xor light -> light  Dark xor light -> dark  Light xor dark -> dark  '''
    res = [[_LIGHT for i in range(len(mat1[0]))] for j in range(len(mat1))]
    for j in range(len(mat1)):
        for i in range(len(mat1[0])):
            res[j][i] = int(mat1[j][i] == mat2[j][i])
    return res

由于我们用1来示意白色,0来示意玄色,以是异或和与的逻辑都是和正常逻辑相反的。

该实现 _mask 来给填了数据的QR码应用掩码了。可是纰谬啊,为什么要八个掩码啊?这是由于思量到数据的多样性,一种掩码难以达到预期的效果,以是QR尺度界说了八个掩码,要求在应用掩码时先划分应用所有的掩码发生八个效果,然后凭据责罚规则盘算出每个效果矩阵的责罚分,再选出责罚分最小,效果最好的掩码当做最终效果。这一历程发生的掩码ID也是花样信息的一部门,来告诉扫描软件应该用哪个掩码来还原数据。

QR尺度把责罚分分成了四项,划分对应行/列中的延续色条、大面积的色块、行/列中类似定位标识的部门、整个矩阵中颜色的不平衡做出加权责罚。


(图片泉源:ISO/IEC 18004: Information – Automatic identification and data capture techniques – QR Code barcode symbology specification

其中,N1=3,N2=3,N3=40,N4=10,i 是色条超出5的部门的长度。

在 _mask 之前添加 _penalty 的实现:

def _penalty(mat):
    '''  Calculate penalty score for a masked matrix.  N1: penalty for more than 5 consecutive pixels in row/column,  3 points for each occurrence of such pattern,  and extra 1 point for each pixel exceeding 5  consecutive pixels.  N2: penalty for blocks of pixels larger than 2x2.  3*(m-1)*(n-1) points for each block of mxn  (larger than 2x2).  N3: penalty for patterns similar to the finder pattern.  40 points for each occurrence of 1:1:3:1:1 ratio  (dark:light:dark:light:dark) pattern in row/column,  preceded of followed by 4 consecutive light pixels.  N4: penalty for unbalanced dark/light ratio.  10*k points where k is the rating of the deviation of  the proportion of dark pixels from 50% in steps of 5%.  '''
    # Initialize.
    n1 = n2 = n3 = n4 = 0
    # Calculate N1.
    for j in range(len(mat)):
        count = 1
        adj = False
        for i in range(1, len(mat)):
            if mat[j][i] == mat[j][i-1]:
                count += 1
            else:
                count = 1
                adj = False
            if count >= 5:
                if not adj:
                    adj = True
                    n1 += 3
                else:
                    n1 += 1
    for i in range(len(mat)):
        count = 1
        adj = False
        for j in range(1, len(mat)):
            if mat[j][i] == mat[j-1][i]:
                count += 1
            else:
                count = 1
                adj = False
            if count >= 5:
                if not adj:
                    adj = True
                    n1 += 3
                else:
                    n1 += 1
    # Calculate N2.
    m = n = 1
    for j in range(1, len(mat)):
        for i in range(1, len(mat)):
            if mat[j][i] == mat[j-1][i] and mat[j][i] == mat[j][i-1] and mat[j][i] == mat[j-1][i-1]:
                if mat[j][i] == mat[j-1][i]:
                    m += 1
                if mat[j][i] == mat[j][i-1]:
                    n += 1
            else:
                n2 += 3 * (m-1) * (n-1)
                m = n = 1
    # Calculate N3.
    count = 0
    for row in mat:
        rowstr = ''.join(str(e) for e in row)
        occurrences = []
        begin = 0
        while rowstr.find('0100010', begin) != -1:
            begin = rowstr.find('0100010', begin) + 7
            occurrences.append(begin)
        for begin in occurrences:
            if rowstr.count('00000100010', begin-4) != 0 or rowstr.count('01000100000', begin) != 0:
                count += 1
    transposedMat = _transpose(mat)
    for row in transposedMat:
        rowstr = ''.join(str(e) for e in row)
        occurrences = []
        begin = 0
        while rowstr.find('0100010', begin) != -1:
            begin = rowstr.find('0100010', begin) + 7
            occurrences.append(begin)
        for begin in occurrences:
            if rowstr.count('00000100010', begin-4) != 0 or rowstr.count('01000100000', begin) != 0:
                count += 1
    n3 += 40 * count
    # Calculate N4.
    dark = sum(row.count(_DARK) for row in mat)
    percent = int((float(dark) / float(len(mat)**2)) * 100)
    pre = percent - percent % 5
    nex = percent + 5 - percent % 5
    n4 = min(abs(pre-50)/5, abs(nex-50)/5) * 10
    return n1 + n2 + n3 + n4

(插一句,我实现的这个 _penalty 还没有测试准确性。。)

(也许仔细看完了辣么一大段代码然后看到上一句的人会想来打我吧。。)

实现 _mask :

def _mask(mat):
    '''  Mask the data QR code matrix with all 8 masks,  call _penalty to calculate penalty scores for each  and select the best mask.  Return tuple(selected masked matrix, number of selected mask).  '''
    maskeds = [_matXor(mat, dataMask) for dataMask in _dataMasks]
    penalty = [0] * 8
    for i, masked in enumerate(maskeds):
        penalty[i] = _penalty(masked)
    # Find the id of the best mask.
    index = penalty.index(min(penalty))
    return maskeds[index], index

这里思量到 _mask 是由 _fillInfo 挪用,而填写花样信息需要选择的掩码的ID,我们让 _mask 返回了效果矩阵和掩码ID组成的tuple。

用我们之前的栗子测试一下掩码效果:




效果不错!

  • 填充花样信息

只剩最后一步了!花样信息很简朴,由两位容错品级代码和三位QR掩码代码组成。

容错品级代码:


(图片泉源:ISO/IEC 18004: Information – Automatic identification and data capture techniques – QR Code barcode symbology specification

QR掩码代码:


固然花样信息也是要加容错码的。花样信息的容错算法接纳(15,5)。

编码

构建码字为

(c14, c13, …, c8)

这样多项式为

c14+c13+…+c8

我们将它称为 CI。

然后就要找出 CR 知足 CR=CI (mod m1,3(x))=c7+c6+…+c0

这样就获得待发的码字 C(x) = CI+CR (mod m1,3(x)) = 0

例如,若是我们要对 (1,1,0,0,1,1,0) 举行编码

CI=x14+x13+x10+x9

然后用 m1,3(x) 除以(这里的除法是多项式除法)CI ,获得效果为 CR(x),在Z2域中,我们可以算出 CR为

x3+1

这样,待发的码字为

(1,1,0,0,1,1,0, 0,0,0,0,1,0,0,1)

(Wikipedia: BCH码,)

……………………………………………….


咳咳。。去看,都讲得很清晰嘛,很容易就看懂了对纰谬?(和善的微笑)

盘算得出十位BCH容错码接在花样信息之后,还要与掩码101010000010010举行异或,作用同QR掩码。

在 _fillInfo 之前添加 _fmtEncode 实现容错码盘算和应用掩码:

def _fmtEncode(fmt):
    '''Encode the 15-bit format code using BCH code.'''
    g = 0x537
    code = fmt << 10
    for i in range(4,-1,-1):
        if code & (1 << (i+10)):
            code ^= g << i
    return ((fmt << 10) ^ code) ^ 0b101010000010010

(Source: Wikiversity: )

有了编码好的花样信息,就可以把它凭据尺度填入矩阵了。


(图片泉源:ISO/IEC 18004: Information – Automatic identification and data capture techniques – QR Code barcode symbology specification

其中14代表最高位(most significant bit),0代表最低位(least significant bit)。

继续实现 _fillInfo:

def _fillInfo(arg):
    '''  Fill the encoded format code into the masked QR code matrix.  arg: (masked QR code matrix, mask number).  '''
    mat, mask = arg
    # 01 is the format code for L error control level,
    # concatenated with mask id and passed into _fmtEncode
    # to get the 15 bits format code with EC bits.
    fmt = _fmtEncode(int('01'+'{:03b}'.format(mask), 2))
    fmtarr = [[not int(c)] for c in '{:015b}'.format(fmt)]
    mat = _matCp(_transpose(fmtarr[7:]), mat, 8, 13)
    mat = _matCp(fmtarr[9:][::-1], mat, 0, 8)
    mat = _matCp(fmtarr[7:9][::-1], mat, 7, 8)
    mat = _matCp(fmtarr[:7][::-1], mat, 14, 8)
    mat = _matCp(_transpose(fmtarr[:6]), mat, 8, 0)
    mat = _matCp([fmtarr[6]], mat, 8, 7)
    return mat

至此QR码所有完成(撒花花 ︿( ̄︶ ̄)︿)。

  • 接口

最后一步,为我们的QR码天生器提供挪用接口:

def qrcode(data, width=210, filename='qrcode.jpg'):
    '''Module public interface'''
    try:
        _genImage(_genBitmap(_encode(data)), width, filename)
    except Exception, e:
        print e
        raise e

哒哒哒哒!完成!(完整版代码已上传 GitHub: )

别忘了我们最初的目的:hello world!来试验一下吧!


Hello world! (二维码自动识别)

能!扫!描!了!

满满的成就感有没有!!!

可是突然想到!!!

我只是想说一句 hello world啊!!!!!

那何不多说几句啊!!






  • 写在后面

破晓2点14,终于完稿。我只有几点想说的

  1. 学习真有趣
  2. Python真好用
  3. 制订尺度真是凝结了工程师的无限智慧
  4. 熬夜伤身
  5. 熬夜会饿
  6. 午夜饿真难受
  7. 第一次写器械,啰啰嗦嗦拖了这么长的篇幅
  8. 看到这里的都是真爱



(END)

小鹿活码怎么注册_Hello World!_微信二维码

微信二维码登录原理

网页登陆是微信4.2以后版本提供的一种全新的登陆方式。用户只需要用手机扫一扫微信网页中的二维码,就能马上实现微信网页登陆。 这种登陆方式虽然炫酷,但是多少有些违背直觉: 网页端是怎么知道是哪个微信账号扫…

相关文章

联系我们

联系我们

17638350532

在线咨询: QQ交谈

邮箱: 1003312430@qq.com

工作时间:周一至周日,8:00-19:00,节假日休息
关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部