二维码
二维码在生活中随处可见。添加好友,微信扫一扫;吃饭娱乐停车费,扫码支付;活动宣传海报,扫一扫了解更多…各种各样形式的二维码布满我们的生活,而我们早已习惯了二维码带来的便利。接下来从研发的角度出发,更深入了解一下二维码的世界。
1、定义
二维码是一种由黑白像素组成,可以用于存储各种类型的信息(例如文本、URL、图像、视频等)的矩形矩阵式二维条码。
2、二维码的数据容量
抽象成一个盒子 里面放东西
- 版本:是指二维码中模块(Module)数的大小及其容量大小的定义。版本号越高,模块数和数据容量越大——型号
- 纠错等级:Error Correction Level,是指二维码中纠错码的长度和能力,通常用L、M、Q、H四个等级表示。纠错等级越高,纠错码越长,相应冗余数据增加,数据容量随之降低——隔板
- 编码方式:二维码可以使用多种编码方式来存储数据。通常情况下采用数字、字母、特殊字符混合编码方式来获取最大的数据承载量。对于特定类型,例如URL地址、电话号码等采用相应编码方式来获得更大数据承载量——摆放方式
- 码制:Symbol Type,常见的有QR Code,Data Matrix,PDF417等。不同码制具有不同的编码方式和数据结构,从而影响数据承载量——材质
- 像素密度:Pixel Density,二维码像素密度越高,存储数据越多,但是二维码尺寸、复杂度也随之增加,会导致扫描、解码的速度和准确性降低——存放物品的形状
选择不同型号的盒子, 大小不一, 容量不等。
选择不同隔板,横放、竖放,占用容量不等,数据容量不等
选择不同摆放方式,平铺、叠放,占用容量不等,数据容量不等
选择不同材质的盒子,纸糊、铁铸承受不等,数据容量不等
选择不同的物品,三角形、矩形、圆形形状不等,数据容量不等
于实时生成的二维码应用:
二维码不会被破坏(扫码时生成)——> 选择L纠错级别增加数据容量
笔记本浏览器显示限制——> 选择800*800大小
考虑手机扫码、拍照场景、光线等因素——> 字符固定约2000个(手机扫码上限约2600)
3、纠错能力
- L级——纠正7%错误
- M级——纠正15%错误
- Q级——纠正25%错误
- H级——纠正30%错误
版本10的二维码,纠错等级为H时,总共包含346码字,其中224个纠错码字,可以纠正112个替代错误(如彩色颠倒)或者224个据读错误(无法读到或无法译码),112/346=32.4%
常用的是里德-所罗门码(Reed-solomon codes, RS码)。是一种前向错误更正的信道编码,对由校正过采样数据所产生的有效多项式。编码过程首先在多个点上对这些多项式求冗余,然后将其传输或存储。对多项式的这种超出必要值的采样使得多项式超过限定。当接收器正确地收到足够的点后,它就可以恢复原来的多项式,即使接收到的多项式上有很多点被噪声干扰失真。
——原理基于“任意k个确定点可表示一个阶数至少为k-1的多项式”,发送超过k个点,二维码损坏时也可以通过数学原理反推最初的多项式,获取信息
并不是所有位置都可以损坏,三个方框则会直接影响初始定位;
为什么是三个?
- 一个方框,会产生无数个固定一点、大小不同的正方形
- 两个方框,无法区分正反
- 三个方框,三点共面,确定一个正方形,可以以任意方向扫码
- 四个方框,无法区分正反
GF(256)有限域
是一个有限域(finite field),也称为伽罗华域(Galois field),由256个元素组成,其中每个元素都可以表示为一个8位二进制数。
加减法规则: 模2的异或运算(XOR),即两个二进制数的每一位进行异或运算,得到的结果作为和。
eg: 在GF(256)中,83+202 ——> 01010011 XOR 11001010 (十进制转二进制——模2取余倒排) 结果为10011001
乘法规则: 将不可约多项式(irreducible polynomial)不能分解为两个次数更低的多项式的多项式
作为模数,将两数相乘的结果对不可约多项式取模,得到的结果作为积。
常见的不可约多项式:
- x8 + x4 + x3 + x + 1 —— 100011011 ——283
- x8 + x4 + x3 + x2 + 1 —— 100011101 ——285
eg:在GF(256)中, 83 * 202 ——> (x6 + x4 + x + 1)* (x7 + x6 + x3 + x)% (x8 + x4 + x3 + x2 + 1) 结果为00000001, 即82与202互为乘法逆元(乘积为1)
除法规则:
- 选择合适的项、系数去乘除数,使得被除数与除数的第一项次数相同
- 采用XOR运算
- 重复步骤1、2
总结GF(256):
-
加法乘法满足交换律、结合律、分配律
-
加法单位元为0, 乘法单位元为1
-
每个非零元素都有一个乘法逆元素,即对于∀ a≠0,∃ b ∈ GF(256), 使得 a * b = 1
log以及反log乘法运算
两个数字p和q相乘 可以表示为: 2log2p+log2q
这也就意味着,所有GF(256)的值均可以被表示为2的次幂,那么域中的所有乘法都可以表示为2的指数相加
283 * 2202 = 283+202 = 2285%255 = 230
在 GF(256) 进行乘法是为了获得所有的 2 的次幂。所有值都被预先计算好,可以在 log 和 反 log 表中 查到。表中 alpha = 2,0 到 255 的数字都可以对应到 2 的 0 到 255 次幂,同时 2 的 0 到 255 次幂也都可以对应到 0到 255 的数字。
log和反log表 https://www.thonky.com/qr-code-tutorial/log-antilog-table
生成多项式
生成多项式是由 (x-α0 )…(x-α(n-1)) 相乘得到的,其中 n 为校错表中的纠错码中字节个数,alpha 值为 2。
n=2, alpha=2的生成多项式为:【α25 = 3, 可从上表查询】
(x - 20)(x - 21) = x2 + 3x1 + 2x0 = α0x2 + α25x1 + α1x0
生成多项式小工具: https://www.thonky.com/qr-code-tutorial/generator-polynomial-tool
生成纠错码
信息多项式
采用1-M二维码, 以HELLO WORLD为例
HELLO WORLD选择1-M二维码,得到的十进制数字为:32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17
将其作为信息多项式的系数,得到信息多项式。
32x15 + 91x14 + …… + 17x0
生成多项式
纠错表 https://www.thonky.com/qr-code-tutorial/error-correction-table
采用1-M二维码 查询纠错表需要生成10个纠错码,通过生成多项式小工具得:
α0x10 + α251x9 + α67x8 + α46x7 + α61x6 + α118x5 + α70x4 + α64x3 + α94x2 + α32x+ α45
消息多项式XOR生成多项式
生成多项式做消息多项式的指数补齐、系数补齐
α0x10 + α251x9 + α67x8 + α46x7 + α61x6 + α118x5 + α70x4 + α64x3 + α94x2 + α32x+ α45
——> α5x25 + α256x24 + α72x23 + α51x22 + α66x21 + α123x20 + α75x19 + α69x18 + α99x17 + α37x16+ α50x15
(指数>255 取余)——>α5x25 + α1x24 + α72x23 + α51x22 + α66x21 + α123x20 + α75x19 + α69x18 + α99x17 + α37x16+ α50x15
进行除法操作
α5 = 32, α1 = 2, α72 = 101,…
(32 XOR 32)x25 + (91 XOR 2)x24 + (11 XOR 101)x24 + ……+ (17 XOR 0)x10
——> 89x24 + 110x23 + 114x22 + 176x21 + ……+ 17x10
重复【指数补齐、系数补齐, 除法操作】,最后得到:
196x9 + 35x8 + 39x7 + 119x6 + 235x5 + 215x4 + 231x3 + 226x2 + 93x1 + 23
余数多项式的系数,即10个纠错码: 196 35 39 119 235 215 231 226 93 23
package main
import (
"fmt"
"math"
"testing"
)
// GetPowersOf2ForGF 获取GF(256)中指定2的num次方
func GetPowersOf2ForGF(num int) int {
list := make([]int, num+1)
tmp := 0
list[0] = 1
for i := 1; i <= num; i++ {
tmp = list[i-1] * 2
if tmp > 255 {
tmp ^= 285
}
list[i] = tmp
}
return list[num]
}
// GetAllPowersOf2ForGF 获取GF(256)中2的幂
func GetAllPowersOf2ForGF() []int {
list := make([]int, 256)
tmp := 0
list[0] = 1
for i := 1; i <= 255; i++ {
tmp = list[i-1] * 2
if tmp > 255 {
tmp ^= 285
}
list[i] = tmp
}
for i, v := range list {
fmt.Println(fmt.Sprintf("%d--------%d", i, v))
}
return list
}
// GetAntiLog 获取反日志的逆对数(antilogarithm
func GetAntiLog() []int {
list := GetAllPowersOf2ForGF()
antiLogs := make([]int, 256)
for i := 0; i <= 255; i++ {
antiLogs[list[i]] = i
}
antiLogs[0] = math.MinInt32
antiLogs[1] = 0
return antiLogs
}
func TestGetPowersOf2ForGF(t *testing.T) {
fmt.Println(GetPowersOf2ForGF(72)) // 获取2^72
}