1. 程式人生 > >二維碼裡德所羅門演算法

二維碼裡德所羅門演算法

這是一個相當古老的專案了。

前序

之前一直想寫一個有關二維碼生成器的教程,由於各種各樣的原因,當然主要原因是懶,一直沒有下手。最近感覺還是要做點什麼,不然有種要爛尾的衝動。

我之所以要寫這個教程,主要是前些年有一個專案,需要用到二維碼。當然網上的確已經有很多二維碼生成器的版本了,但是寫C和C++的還是少數,而且最坑爹的是這些人寫程式碼都不帶註釋的。對於我來說,我的工作不僅僅限於生成一個二維碼就完了,還需要利用二維碼的一些特性對二維碼進行處理,這樣的話前人的程式碼就相當不友好了,索性我就自己造了輪子。當然我也是寫了一部分註釋,基本能解釋每個函式是幹了什麼工作。但是這麼做還是很難對二維碼有一個整體性的認識,為了造福後人,我打算根據自己寫的程式碼,詳細的解析二維碼,造福後人,避免大家趟我的坑。

github專案 歡迎fork,star,可以結合程式碼理解實現。

二維碼的理論基礎

想要比較深入理解二維碼,理解二維碼的糾錯演算法是很必要的。

二維碼中使用的Reed Solomom 糾錯演算法是BCH碼的一種,廣泛用於通訊,航空航天等領域。RS碼具有低冗餘,高糾錯的特點。為什麼二維碼一部分缺失還能正確解碼,二維碼的糾錯能力有多強,對於這些東西我們要知其然,也要知其所以然。

有限域理論

在正式開始講解RS碼之前,還是要補習一些數學知識 。

有限域又稱為伽羅瓦域(Galois Field),是僅含有限個元素的域,是由伽羅瓦於十八世紀30年代研究代數方程根式求解問題時引出。

有限域的特徵數必定是特徵為

p的有限域,則為某一素數p,因此它含的素域同構於Zp。若F中元素個數為pn元素,n為某一正整數。元素個數相同的有限域是同構的,通常用GF(pn)表示pn元的有限域。

在此歪個題,伽羅瓦可是個傳奇數學家,死的也很有傳奇性。想了解的筒子可以看死於決鬥的天才少年伽羅瓦

在本教程裡,生成有限域依靠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)的餘數為0,這點可能比較難以理解,但是我們可以記住二維碼生成的有限域的本原多項式可以表示為P(x)=x8+x4+x3+x2+1,所以primitive=0x011D,直接輸入就行。

size 有限域中元素的個數pn

為了方便計算機計算通常p=2,且二維碼的基本碼子大小為8bit,所以size=256,意味著二維碼生成有限域中有256個元素。

expTab 儲存對應冪次由本原多項式生成的元素

假設expTab[i]=aiai=pimodP(x),i<2p,logTab 儲存expTab中對應元素取對數的結果,這樣做的好處是可以將有限域中元素之間乘法轉為對數加法計算,減少計算量。

geneBase 對應裡的所羅門校驗碼生成式G(x)=i=0K(xab+i)中的係數b

b可以為0或1,但在二維碼生成式中我們取通常取b=0

一切有限域都有加法和乘法兩種運算,除此之外,GenericGF還定義了幾種常用運算

int add_or_sub(int a, int b)
有限域中兩個元素加法計算,減法可以看做元素補的加法 a+b

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

int log(const int a)
返回expTab中第a個本原元素的對數 log(a)

int multiply(const int a, const int b)
返回有限域中兩個元素的乘積 ab

int inverse(const int a)
返回有限域中元素a的逆 aa1=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<GenericGFPoly*> divide(GenericGFPoly *other)
多項式與多項式函式除法,返回商和餘數

裡的所羅門編碼演算法

介紹完有限域的概念後我們終於可以進入正題,開始介紹二維碼的編碼演算法了。

首先我們要定義一些符號在GF(2m)域中,定義RS(n,k)為一次標準的裡的所羅門編碼操作。其中符號含義如下:  

m         表示符號(本原元素)大小,如m=8表示符號由8位二進位制陣列成
   n         表示碼塊符號的長度
   k         表示碼塊中資訊的符號長度
   K=nk2t  表示校驗碼的符號數
   t          表示能夠校正的錯誤碼塊字元數
  
   
因此,如果我們在生成二維碼時,需要生成一個RS(36,22)的裡的所羅門編碼,意味著這個編碼塊一共由36byte字元組成,其中前22byte為資訊資料,後14byte為校驗編碼,這個編碼塊最多可以糾正不超過7個分散或連續符號的錯誤。

裡的所羅門編碼演算法實際上是計算資訊多項式M(x),除以校驗碼生成多項式G(x)之後的餘數多項式R(x)