1、 QR 二维码的生成与识别原理、 简介二维码(2-dimensional bar code),是用某种特定的几何 图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的。二 维码 的种类包括:QR Code ,Data Matrix, Maxi Code, Aztec , Vericode, PDF417, Ultracode, Code 49, Code 16K 等。其中 QR Code 是被广泛使用的二维码,QR 全称 Quick Response,与其他编码方式相比,QR 二维码 具有存储容量大、 编码速度快的特点,并且它也能表示更多的数据类型:比如:字符,数字,日文,
2、中文等等。随着近几年智能手机的迅猛发展, QR 二维码得到了广泛的应用。关于 QR 二维码的标准,可参 见标准文档(QR Code Spec ):http:/ 应用现状随着智能机的普及和手机摄像头成像能力的提升, 为了提高向机器内 输入信息的速度,QR 二维码得到迅猛发 展,在 许多行业中得到应用。在一维码时代, “扫码”主要应用在超市或图书馆等场所,以获取商品价格或图书分类等有限的特定信息。二维码 可以存储大容量数据, 给人们 的生活带来巨大方便。从开始的扫描二维码提取文字或网址,到后来 “扫一扫”添加好友、关注个人或公司微信或微博,再到扫码支付,二维码的应用已经非常普遍。、 基础知识QR
3、码可分为不同的尺寸,或者叫版本 Version。Version 1 是 21 x 21 的矩阵,Version 2 是 25 x 25 的矩阵 ,Version 3 是 29 的尺寸,每增加一个 version,就会增加 4的尺寸,公式是:(V-1)*4 + 21(V 是版本号)最高 Version 40,(40-1)*4+21 = 177,所以最高是 177 x 177 的正方形。样例如下:定位图案Position Detection Pattern 是定位图案,用于 标记二维码的矩形大小。这三个定位图案有白边叫 Separators for Postion Detection Patter
4、ns。之所以三个而不是四个意思就是三个就可以标识一个矩形了。Timing Patterns 也是用于定位的。原因是二维码有 40 种尺寸,尺寸过大了后需要有根标准线,不然扫描的时候可能会 扫歪了。Alignment Patterns 只有 Version 2 以上(包括 Version2)的二维码需要这个东东,同样是为了定位用的。功能性数据Format Information 存在于所有的尺寸中,用于存放一些格式化数据的。Version Information 在 Version 7 以上,需要 预留两块 3 x 6 的区域存放一些版本信息。数据码和纠错码除了上述的那些地方,剩下的地方存放 D
5、ata Code 数据码 和 Error Correction Code 纠错码。、 数据编码QR 码支持如下的编码:Numeric mode:数字编码,从 0 到 9。如果需要编码的数字的个数不是 3 的倍数,那么,最后剩下的 1 或 2 位数会被 转成 4 或 7bits,则其它的每 3 位数字会被编成 10,12,14bits,编成多长还要看二维码的尺寸。Alphanumeric mode:字符编码,包括 0-9,大写的 A 到 Z(没有小写),以及符号$ % * + . / : 包括空格。 这些字符会映射成一个字符索引表。如下所示:(其中的 SP 是空格,Char 是字符,Value
6、是其索引 值)编码的过程是把字符两两分 组,然后转成下表的 45 进制,然后转成 11bits 的二进制,如果最后有一个落单的,那就转成 6bits 的二进制。而 编码模式和字符的个数需要根据不同的 Version 尺寸编成 9, 11 或 13 个二进制(如下表中Table 3)Byte mode:字节编码,可以是 0-255 的 ISO-8859-1 字符。有些二维码的扫描器可以自动检测是否是 UTF-8 的编码 。Kanji mode:日文编码,也是双字 节编码。同 样,也可以用于中文编码。Extended Channel Interpretation (ECI) mode:主要用于特殊
7、的字符集。并不是所有的扫描器都支持这种编码。Structured Append mode:用于混合编码,也就是说,这个二维码中包含了多种编码格式。FNC1 mode:这种编码方式主要是给一些特殊的工业或行业用的。比如 GS1 条形码之类的。这里我们主要介绍最常用的数字编码和字符编码。下面两张表中,Table 2 是各个编码格式的“编号”(注:中文是 1101),编号要写在Format Information 中。Table 3 表示了不同版本(尺寸)的二 维码,对于数字、字符、字节和 Kanji 模式下, 对于单个编码 的 2 进制的位数。 (编码规范表可参见二维码规格说明书)举例说明:示例一
8、:数字编码例如,在 Version 1 的尺寸下,纠错级别为 H 的情况下,编码: 01234567 的编码方式如下:1. 把上述数字分成三组: 012 345 672. 把他们转成二进制: 012 转成 0000001100; 345 转成 0101011001; 67 转成 1000011。3. 把这三个二进制串起来: 0000001100 0101011001 10000114. 把数字的个数转成二进制 (version 1-H 是 10 bits ): 8 个数字的二进制是 00000010005. 把数字编码的标志 0001 和第 4 步的编码加到前面: 0001 000000100
9、0 0000001100 0101011001 1000011示例二:字符编码同样,在 Version 1 的尺寸下, 纠错级别为 H 的情况下, 编码 : AC-42 的编码方式如下:1. 从字符索引表中找到 AC-42 这五个字条的索引 (10,12,41,4,2)2. 两两分组: (10,12) (41,4) (2)3.把每一组转成 11bits 的二进 制:(10,12) 10*45+12 等于 462 转成 00111001110(41,4) 41*45+4 等于 1849 转 成 11100111001(2) 等于 2 转成 0000104. 把这些二进制连接起来:00111001
10、110 11100111001 000010;5. 把字符的个数转成二进制 (Version 1-H 为 9 bits ): 5 个字符,5 转成 000000101;6. 在头上加上 编码标识 0010 和第 5 步的个数编码: 0010 000000101 00111001110 11100111001 000010;、 结束符和补齐符假如我们有个 HELLO WORLD 的字符串要编码,根据上面的示例二,我们可以得到下面的编码编码 字符数 HELLO WORLD 的编码0010 000001011 01100001011 01111000110 10001011100 101101110
11、00 10011010100 001101还要加上结束符:编码 字符数 HELLO WORLD 的编码 结束0010 000001011 01100001011 01111000110 10001011100 10110111000 10011010100 0011010000按 8bits 重排如果所有的编码加起来不是 8 个倍数我们还要在后面加上足够的 0,比如上面一共有 78 个 bits,所以 还要加上 2 个 0,然后按 8 个 bits 分好组:00100000 01011011 00001011 01111000 11010001 01110010 11011100 010011
12、01 01000011 01000000补齐码(Padding Bytes)最后,如果还没有达到最大的 bits 数的限制,还要加一些补齐码(Padding Bytes),Padding Bytes 就是重复下面的两个 bytes:11101100 00010001 (这两个二进制转成十进制是 236 和 17,关于每一个 Version 的每一种纠错级别的最大 Bits 限制,可以参看 QR Code Spec)假设我们需要编码的是 Version 1 的 Q 纠错级,那么,其最大需要 104 个 bits,而我们上面只有 80 个 bits,所以,还需要补 24 个 bits,也就是需要
13、3 个 Padding Bytes,我们就添加三个,于是得到下面的编码:00100000 01011011 00001011 01111000 11010001 01110010 11011100 01001101 01000011 01000000 11101100 00010001 11101100上面的编码就是数据码了,叫 Data Codewords,每一个 8bits 叫一个 codeword,我们还要对这些数据码加上纠错信息。、 纠错码上面我们说到了一些纠错级别,Error Correction Code Level,二 维码中有四种级别的纠错,这就是为什么二维码 有残缺还能扫出来
14、,也就是 为 什么有人在二维码的中心位置加入图标。错误修正容量L 水平7%的字 码可被修正M 水平15%的字 码可被修正Q 水平25%的字 码可被修正H 水平30%的字 码可被修正那么,QR 是怎么对数据码加上 纠错码的?首先需要对数据 码进行分组,也就是分成不同的 Block,然后对各个 Block 进行纠错编码,对于如何分组,可以查看 QR Code Spec 的定义表。 这里注意最后两列:Number of Error Code Correction Blocks :需要分多少个块 。Error Correction Code Per Blocks:每一个块中的 code 个数,所谓的
15、code 的个数,也就是有多少个 8bits 的字节。举例说明:上述的 Version 5 + Q 纠错级:需要 4 个 Blocks(2 个 Blocks 为一组,共两组),第一组的两个 Blocks 中各 15 个 bits 数据 + 各 9 个 bits 的纠错码(注:表中的codewords 就是一个 8bits 的 byte)(再注:最后一例中的(c, k, r )的公式为:c = k + 2 * r,因为后脚注解释了:纠错码的容量小于纠错码的一半)下图给一个 5-Q 的示例(因为 二进制写起来会让表格太大,这里使用十进制表示,可以看到每一块的纠错码有 18 个 codewords,
16、也就是 18 个 8bits 的二进制数)组 块 数据 对每个块的纠错码1 67 85 70 134 87 38 85 194 119 50 6 18 6 103 38213 199 11 45 115 247 241 223 229 248 154 117 154 111 86 161 111 3912 246 246 66 7 118 134 242 7 38 86 22 198 199 146 687 204 96 60 202 182 124 157 200 134 27 129 209 17 163 163 120 1331 182 230 247 119 50 7 118 134
17、87 38 82 6 134 151 50 7148 116 177 212 76 133 75 242 238 76 195 230 189 10 108 240 192 14122 70 247 118 86 194 6 151 50 16 236 17 236 17 236 17 236235 159 5 173 24 147 59 33 106 40 255 172 82 2 131 32 178 236(注:二维码的纠错码主要是通过 Reed-Solomon error correction(里德- 所罗门纠错算法)来实现的)。、 最终编码上述步骤完成之后,还要把数据 码和纠错码的各
18、个 codewords 交替放在一起。交替规则如下:对于数据码:把每个块的第一个 codewords 先拿出来按顺度排列好,然后再取第一块的第二个,如此类推。如上述示例中的 Data Codewords 如下:块 1 67 85 70 134 87 38 85 194 119 50 6 18 6 103 38块 2 246 246 66 7 118 134 242 7 38 86 22 198 199 146 6块 3 182 230 247 119 50 7 118 134 87 38 82 6 134 151 50 7块 4 70 247 118 86 194 6 151 50 16 23
19、6 17 236 17 236 17 236我们先取第一列的:67, 246, 182, 70然后再取第二列的:67, 246, 182, 70, 85,246,230 ,247如此类推:67, 246, 182, 70, 85,246,230 ,247 ,38,6,50,17,7,236对于纠错码,也是一样:块 1213 199 11 45 115 247 241 223 229 248 154 117 154 111 86 161 111 39块 287 204 96 60 202 182 124 157 200 134 27 129 209 17 163 163 120 133块 314
20、8 116 177 212 76 133 75 242 238 76 195 230 189 10 108 240 192 141块 4235 159 5 173 24 147 59 33 106 40 255 172 82 2 131 32 178 236和数据码取的一样,得到:213 ,87,148,235,199,204,116,159, 39,133,141,236然后,再把这两组放在一起(纠错码 放在数据码之后)得到:67, 246, 182, 70, 85, 246, 230, 247, 70, 66, 247, 118, 134, 7, 119, 86, 87, 118, 50,
21、 194, 38, 134, 7, 6, 85, 242, 118, 151, 194, 7, 134, 50, 119, 38, 87, 16, 50, 86, 38, 236, 6, 22, 82, 17, 18, 198, 6, 236, 6, 199, 134, 17, 103, 146, 151, 236, 38, 6, 50, 17, 7, 236, 213, 87, 148, 235, 199, 204, 116, 159, 11, 96, 177, 5, 45, 60, 212, 173, 115, 202, 76, 24, 247, 182, 133, 147, 241, 1
22、24, 75, 59, 223, 157, 242, 33, 229, 200, 238, 106, 248, 134, 76, 40, 154, 27, 195, 255, 117, 129, 230, 172, 154, 209, 189, 82, 111, 17, 10, 2, 86, 163, 108, 131, 161, 163, 240, 32, 111, 120, 192, 178, 39, 133, 141, 236这就是数据区。Remainder Bits最后再加上 Reminder Bits,对于某些 Version 的 QR,上面的还不够长度,还要加上 Remainder
23、 Bits,比如:上述的 5Q 版的二维码, 还要加上 7 个 bits,Remainder Bits 加零就好了。关于哪些 Version 需要多少个 Remainder bit,可以参看 QR Code Spec 的第15 页的 Table-1 的定义表。、 画二维码图Position Detection Pattern首先,先把 Position Detection 图案画在三个角上。 (无论 Version 如何,这个图案的尺寸不变)Alignment Pattern然后,再把 Alignment 图案画上(无 论 Version 如何, 这个图案的尺寸就是这么大)关于 Alignme
24、nt 的位置,可以 查看 QR Code Spec 的第 81 页的 Table-E.1 的定义表(下表是不完全表格)下图是根据上述表格中的 Version8 的一个例子(6,24, 42)Timing Pattern接下来是 Timing Pattern 的线ormat Information再接下来是 Formation Information,下 图中的蓝色部分。Format Information 是一个 15 个 bits 的信息,每一个 bit 的位置如下图所示:(注意图中的 Dark Module,是永远出现的)这 15 个 bits 中包括:5 个数据 bits:其中,2 个
25、bits 用于表示使用什么样的 Error Correction Level, 3 个bits 表示使用什么样的 Mask10 个纠错 bits。主要通过 BCH Code 来计算然后 15 个 bits 还要与 101010000010010 做 XOR 操作。 这样就保证不会因为我们选用了 00 的纠错级别和 000 的 Mask,从而造成全部为白色,这会增加我们的扫描器的图像识别的困难。下面是一个示例:关于 Error Correction Level 如下表所示:关于 Mask 图案如后面的 Table 23 所示。Version Information再接下来是 Version In
26、formation(版本 7 以后需要这个编码 ),下 图中的蓝色部分。Version Information 一共是 18 个 bits,其中包括 6 个 bits 的版本号以及 12 个 bits的纠错码,下面是一个示例:而其填充位置如下:数据和数据纠错码然后是填接我们的最终编码,最 终编码的填充方式如下:从左下角开始沿着 红线填我们的各个 bits,1 是黑色,0 是白色。如果遇到了上面的非数据区,则绕开或跳过。掩码图案这样下来,图基本填好了,但是,也许那些点并不均衡,如果出现大面积的空白或黑块, 扫描识别会变得困难。所以,最后还要做 Masking 操作。QR 的 Spec 中描述到,
27、QR 有 8 个 Mask 你可以使用,其中,各个 mask 的公式在各个图下面。所谓 mask,就是和上面生成的图做 XOR 操作。Mask 只会和数据区进行 XOR,不会影响功能区。 (注:选择一个合适的 Mask 也是有算法的)其 Mask 的标识码如下所示:(其中的 i,j 分别对应于上图的 x,y)下面是 Mask 后的一些样子,可以看到被某些 Mask XOR 了的数据变得比较零散了。Mask 过后的二维码就成最终 的图了。、 识别:既然二维码的生成搞懂了,那么 识别就会变得简单,基本上就是生成的逆过程。对于手机端扫描 QR 二维码的识别而言,重点在于 摄像头获 取数据后, 对数据
28、的最初处理,这里以 Android 手机识别 QR 二维码为例、以源码为主要依据进行简要说明。1、 获取摄像头原始数据。首先 Android 提供了 PreviewCallback 接口,只要在 Activity 里实现PreviewCallback 接口后,就会自 动重载这个函数:public void onPreviewFrame(byte data, Camera camera) 这个函数里的 data 就是实时预览帧视频,也就是摄像头返回的最原始的数据。这样,就解决了如何获取摄像头数据的问题。一旦程序调用 PreviewCallback 接口,就会自动调用 onPreviewFrame
29、 这个函数。 调用 PreviewCallback 的方法有三种。分别是:setPreviewCallback, setOneShotPreviewCallback, setPreviewCallbackWithBuffer,程序中使用第二种方式,示例如下:至于何时触发 onPreviewFrame()这个函数来获得摄像头数据,一般选择按一个按键触发一次或者每隔一段时间触发一次,无 论如何,只要在 该 触发的地方写上Camera.setOneShotPreviewCallback(RectPhoto.this);便会自 动触发一次。这样我们就可以得到手机摄像头实时预览帧视频数据 data。2、
30、 解码数据获取该数据后,要对二维码进 行解码,我 们在 DecodeHandler 类中定义了解码方法:private void decode(byte data, int width, int height)我们重点对该方法进行分析: 第 78-82 行代码,最初的数据应该被看成为一个矩阵数据,只是把它存放在一维数组 byte 中,这里是将其 转换成转置矩阵,即 aij与 aji交换,因此在 83-85 行代码中,对宽高也进行了调换; 第 88-95 行代码,这是解析数据最为关键的代码, 88 行我们定义了PlanarYUVLuminanceSource 类的对象,关于 YUV 数据格式以及
31、该类的介绍参见后面的附录一。 最后,需要将 PlanarYUVLuminanceSource 类处理的数据转化成 Bitmap 对象。代 码的第 91 行即为此操作。 至此,我们得到了二维码图 片的 Bitmap 对象,就可以按照二维码生成的逆过程运算对数据进行解析。 解码示例:例如,程序在进行解码分析时 ,首先要根据版本信息和 纠错级别进 行判断,即读取Version Information 和 Data and Error Correction Codewords 处数据,若取得版本 为1,接错级别为 H,按照 QR 码 的规范, 查下表:可知应读取数据区连续的 72 个 bits 作为一
32、个整体,比如我们读到如下数据:00100000 0010100111001110 11100111 0010000100000000 11101100 00010001 11101100对于该数据,按照 QR 码的规 范,前四位 为编码格式编号,即 0010 对应的是字符编号,说明后面的内容为字符;根据 QR 码编码规范,版本为 1、纠错级别为 H 的编码,数字个数占九位,即取编码格式编号的后九位:000000101,转换成十进制为 5,即得知该数据包含 5 个字符。再次按照 QR 码编码规范,字符 编码过程中,每一 组数据为 11 位,并且如果最后有一个落单的,那就转成 6bits 的二进制
33、。那么根据有 5 个字符,我们取随后的28 位数据,即为真正的数据编码 :00111001110 11100111001000010将上述三组数据转换成十进制即为(462,1849,2),根据字符编码的编码规范,编码的过程是把字符两两分组,然后 转成下表的 45 进制。那么,分别将 462、1849、2转化为 45 进制转换之前的数据,得到 (10,12)(41,4)(2),查询字符编码表,得出 对应的字符分别为:A,C,-,4,2,即原始数据为 AC-42,解码结束。3、 将获取数据与解码关联在实际的应用中,因为数据的 识别解析过程比较慢,并且二维码识别、解码一次不一定能成功,那么要如何科学
34、的把上述两步(获取数据与解 码)关联在一起呢?首先,由于数据识别和解析是一个耗 时操作,我 们要利用线 程机制,在一个线程中操作,这里我们定义了一个 继承 Thread 的 DecodeThread 类,该线程与DecodeHandler 配合,完成解析数据的功能,由于一次识别不一定成功,那么就要在每次识别失败之后,重新调用第一小节中提到的方法,重新获取摄像 头数据进行再一次的解析。如此反复直到成功。那么程序又是如何判定失败的呢?在上面贴出的第 95 行代码处,将最终的解析结果放置在 rawResult 变量中,后面对该变量就行判断,若不为空, 则表示解析成功,若为空表示解析失败,程序会向主线
35、程发送解析失败的消息,来进行再一次的解析过程。附录一:YUV 数据格式简介以及获得像素数据。YUV 是一种颜色编码方法。 “Y”表示明亮度(Luminance、 Luma),“U”和“V”则是色度、浓度。与我们熟知的 RGB 类似, YUV 也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有 UV 信息一样可以显示完整的图像,只不过是黑白的,这样的设计 很好地解决了彩色电视机与黑白 电视的兼容问题。并且,YUV不像 RGB 那样 要求三个独立的视频信号同时传输,所以用 YUV 方式传送占用极少的频宽。YUV 码流的存储格式其实与其采样的方式密
36、切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,用三个图来直观地表示采集的方式如下,其中黑点表示采样该像素点的 Y 分量,空心圆圈表示采用该像素点的 UV 分量。这样,可以根据其采样格式来从 码流中还原每个像素点的 YUV 值,因为只有正确地还原了每个像素点的 YUV 值,才能通 过 YUV 与 RGB 的转换公式提取出每个像素点的 RGB 值,然后显示出来。对于代码中的 PlanarYUVLuminanceSource 类,其主要作用是获得像素数据。PlanarYUVLuminanceSource 继承自 LuminanceSource 这个抽象类,需要实
37、现它的构造方法,并重载 getMatrix()和 getRow(int y, byte row)方法。其构造方法中需要传入宽高,这两个值指的就是图片的宽和高。 getMatrix()方法会返回一个 byte 数组,这个数组就是图片的像素数组。getRow(int y, byte row)如字面的意义 ,就是得到图片像素数组的一行。其中的 y 就是需要的哪一个行的像素数 组。其构造方法主要代码为:注:这里的 byte 数组是指图片的像素数组,而不是所谓 Bitmap 转换成 byte 数组。Bitmap 对象的 getPixels 方法可以取得的像素数组,但它得到是 int 型数组。根据其api
38、 文档解释,取得的是 color,也就是像素 颜色值。每个像素值包含透明度,红色,绿色,蓝色。所以白色就是 0xffffffff,黑色就是 0xff000000。直接由 int 型转成 byte 型。再来就是 getRow 方法:补充:getPixels 得到的像素数组是一维的,也就是按照 图片宽度逐行取像素颜色值录入。如果想得到单行的像素数 组内容,通 过 y*width 就可以找该行的第一个像素值,拷贝后面 width 个就可以得到 该行的像素内容。最后一个就是 getMatrix()方法,它用来返回我们的图像转换成的像素数组。您好,欢迎您阅读我的文章,本 WORD 文档可编辑修改,也可以直接打印。阅读过后,希望您提出保贵的意见或建议。阅读和学习是一种非常好的习惯,坚持下去,让我们共同进步。