二维码里德所罗门算法

这是一个相当古老的项目了。

前序

之前一直想写一个有关二维码生成器的教程,由于各种各样的原因,当然主要原因是懒,一直没有下手。最近感觉还是要做点什么,不然有种要烂尾的冲动。

我之所以要写这个教程,主要是前些年有一个项目,需要用到二维码。当然网上的确已经有很多二维码生成器的版本了,但是写C和C++的还是少数,而且最坑爹的是这些人写代码都不带注释的。对于我来说,我的工作不仅仅限于生成一个二维码就完了,还需要利用二维码的一些特性对二维码进行处理,这样的话前人的代码就相当不友好了,索性我就自己造了轮子。当然我也是写了一部分注释,基本能解释每个函数是干了什么工作。但是这么做还是很难对二维码有一个整体性的认识,为了造福后人,我打算根据自己写的代码,详细的解析二维码,造福后人,避免大家趟我的坑。
github项目 欢迎fork,star,可以结合代码理解实现。

二维码的理论基础

想要比较深入理解二维码,理解二维码的纠错算法是很必要的。

二维码中使用的Reed Solomom 纠错算法是BCH码的一种,广泛用于通信,航空航天等领域。RS码具有低冗余,高纠错的特点。为什么二维码一部分缺失还能正确解码,二维码的纠错能力有多强,对于这些东西我们要知其然,也要知其所以然。

有限域理论

在正式开始讲解RS码之前,还是要补习一些数学知识 。

有限域又称为伽罗瓦域(Galois Field),是仅含有限个元素的域,是由伽罗瓦于十八世纪30年代研究代数方程根式求解问题时引出。

有限域的特征数必定是特征为 p p 的有限域,则为某一素数 p p ,因此它含的素域同构于 Zp Z p 。若 F F 中元素个数为 pn p n 元素, n n 为某一正整数。元素个数相同的有限域是同构的,通常用 GF(pn) G F ( p n ) 表示 pn p n 元的有限域。

在此歪个题,伽罗瓦可是个传奇数学家,死的也很有传奇性。想了解的筒子可以看死于决斗的天才少年伽罗瓦。

在本教程里,生成有限域依靠GenericGF类。GenericGF是生成伽罗瓦域的标准操作。下面我们将参照GenericGF里的相关函数来具体学习如何生成二维码生成所需的伽罗瓦域。

class GenericGF final {

private:
     int size;
     int primitive;
     int geneBase;
     std::vector<int> expTab;
     std::vector<int> logTab;

public:
    GenericGF(int primitive, int size, int b);
... 
}

GeneicGF 类有5个成员函数,其中前三个为构造函数传入的参数,后两个用来存储计算结果。下面我们根据具体参数来讲解二维码有限域生成。

primitive 有限域的本原多项式。

在有限域中除了0,1之外,其余所有的元素都是有本原多项式生成,本原多项式特征满足 x2p1+1P(x) x 2 p − 1 + 1 P ( x ) 的余数为0,这点可能比较难以理解,但是我们可以记住二维码生成的有限域的本原多项式可以表示为 P(x)=x8+x4+x3+x2+1 P ( x ) = x 8 + x 4 + x 3 + x 2 + 1 ,所以primitive=0x011D,直接输入就行。

size 有限域中元素的个数 pn p n

为了方便计算机计算通常 p=2 p = 2 ,且二维码的基本码子大小为8bit,所以size=256,意味着二维码生成有限域中有256个元素。

expTab 储存对应幂次由本原多项式生成的元素

假设expTab[i]= ai a i ai=pimodP(x),i<2p a i = p i m o d P ( x ) , i < 2 p ,logTab 存储expTab中对应元素取对数的结果,这样做的好处是可以将有限域中元素之间乘法转为对数加法计算,减少计算量。

geneBase 对应里的所罗门校验码生成式 G(x)=Ki=0(xab+i) G ( x ) = ∏ i = 0 K ( x − a b + i ) 中的系数 b b

b b 可以为0或1,但在二维码生成式中我们取通常取 b=0 b = 0

一切有限域都有加法和乘法两种运算,除此之外,GenericGF还定义了几种常用运算

int add_or_sub(int a, int b)
有限域中两个元素加法计算,减法可以看做元素补的加法 a+b a + b

int exp(const int a)
返回expTab中第a个本原元素

int log(const int a)
返回expTab中第a个本原元素的对数 log(a) l o g ( a )

int multiply(const int a, const int b)
返回有限域中两个元素的乘积 ab a ∗ b

int inverse(const int a)
返回有限域中元素a的逆 aa1=1 a ∗ a − 1 = 1

里的所罗门算法主要是定义在有限域多项式函数上的操作,因此还在GenericGFPoly类中定义了一些有限域多项式函数方法:

class  GenericGFPoly final {
public:
    GenericGF *field;
    std::vector<int> coeff;
    ...
}

GenericGFPoly类中定义了两个成员函数,其中field代表有限域中的未知数,coeff是其系数。

GenericGFPoly * getZero()
定义一个系数为0的单项式函数

GenericGFPoly * getOne()
定义一个系数为1的单项式函数

GenericGFPoly* add_or_sub(GenericGFPoly* other)
返回多项式和

GenericGFPoly* multiply(const int scalar)
返回多项式函数与元素乘积

GenericGFPoly* multiply(GenericGFPoly *other)
返回多项式函数与多项式函数乘积

std::vector divide(GenericGFPoly *other)
多项式与多项式函数除法,返回商和余数

里的所罗门编码算法

介绍完有限域的概念后我们终于可以进入正题,开始介绍二维码的编码算法了。

首先我们要定义一些符号在 GF(2m) G F ( 2 m ) 域中,定义 RS(n,k) R S ( n , k ) 为一次标准的里的所罗门编码操作。其中符号含义如下:  

m m          表示符号(本原元素)大小,如 m=8 m = 8 表示符号由8位二进制数组成
    n n          表示码块符号的长度
    k k          表示码块中信息的符号长度
    K=nk2t K = n − k ≥ 2 t   表示校验码的符号数
    t t           表示能够校正的错误码块字符数
  
   
因此,如果我们在生成二维码时,需要生成一个 RS(36,22) R S ( 36 , 22 ) 的里的所罗门编码,意味着这个编码块一共由36byte字符组成,其中前22byte为信息数据,后14byte为校验编码,这个编码块最多可以纠正不超过7个分散或连续符号的错误。

里的所罗门编码算法实际上是计算信息多项式 M(x) M ( x ) ,除以校验码生成多项式 G(x) G ( x ) 之后的余数多项式 R(x) R ( x )

G(x)=M(x)xKG(x)(1) G ( x ) = M ( x ) x K G ( x ) ( 1 )

信息码符多项式的一般形式为:

M(x)=ikmixi(2) M ( x ) = ∑ i k m i x i ( 2 )

校验码生成多项式的一般形式为:

G(x)=i=0K(xab+i)(3) G ( x ) = ∏ i = 0 K ( x − a b + i ) ( 3 )

由信息码符多项式和校验码生成多项式相除得到的余数多项式一般形式为:

R(x)=i=0K1Qix(4) R ( x ) = ∑ i = 0 K − 1 Q i x ( 4 )

R(x) R ( x ) 的阶次比 G(x) G ( x )

xKi=0kmix+i=0K1Qix=xi=1K(xab+i)Q(x)(5) x K ∑ i = 0 k m i x + ∑ i = 0 K − 1 Q i x = x ∏ i = 1 K ( x − a b + i ) Q ( x ) ( 5 )

x=a,,ak x = a , ⋅ ⋅ ⋅ , a k 带入(5)即可获得里的所罗门码的校验方程,求解校验方程即可获得校验码符号 Qi Q i

用于生成里的所罗门编码的方法位于Reed_Solomon_encoder类中,具体方法如下:

RSencoder(GenericGF *field)
构造一个Reed Solomen 编码器,使用filed中定义的有限域进行实例化

void encode(std::vector &encode, int num_ecbytes)
校验码生成方法,输入encode串和校验符号数目,输出完整的rs编码串。

为此我们可以构造一个测试用例来检测算法的正确性

data={1,1,2,3,5,8,13,21}
输入 encode={1,1,2,3,5,8,13,21,0,0 },num_ecbytes=2
输出 encode={1,1,2,3,5,8,13,21,165,177}

测试代码如下:

    #include

    qrgen::RSencoder rs(new  qrgen::GenericGF(0x011D,256,0));
    std::vector<int> test={1,1,2,3,5,8,13,21,0,0};
    rs.encode(test,2);
    for(int i:test) std::cout<std::endl;

当然,这是一个通用的里的所罗门编码生成器,除了二维码编码生成器可使用外,也可以生成其他形式的里的所罗门编码。

你可能感兴趣的:(二维码)