二維碼裡德所羅門演算法
這是一個相當古老的專案了。
前序
之前一直想寫一個有關二維碼生成器的教程,由於各種各樣的原因,當然主要原因是懶,一直沒有下手。最近感覺還是要做點什麼,不然有種要爛尾的衝動。
我之所以要寫這個教程,主要是前些年有一個專案,需要用到二維碼。當然網上的確已經有很多二維碼生成器的版本了,但是寫C和C++的還是少數,而且最坑爹的是這些人寫程式碼都不帶註釋的。對於我來說,我的工作不僅僅限於生成一個二維碼就完了,還需要利用二維碼的一些特性對二維碼進行處理,這樣的話前人的程式碼就相當不友好了,索性我就自己造了輪子。當然我也是寫了一部分註釋,基本能解釋每個函式是幹了什麼工作。但是這麼做還是很難對二維碼有一個整體性的認識,為了造福後人,我打算根據自己寫的程式碼,詳細的解析二維碼,造福後人,避免大家趟我的坑。
二維碼的理論基礎
想要比較深入理解二維碼,理解二維碼的糾錯演算法是很必要的。
二維碼中使用的Reed Solomom 糾錯演算法是BCH碼的一種,廣泛用於通訊,航空航天等領域。RS碼具有低冗餘,高糾錯的特點。為什麼二維碼一部分缺失還能正確解碼,二維碼的糾錯能力有多強,對於這些東西我們要知其然,也要知其所以然。
有限域理論
在正式開始講解RS碼之前,還是要補習一些數學知識 。
有限域又稱為伽羅瓦域(Galois Field),是僅含有限個元素的域,是由伽羅瓦於十八世紀30年代研究代數方程根式求解問題時引出。
有限域的特徵數必定是特徵為
在此歪個題,伽羅瓦可是個傳奇數學家,死的也很有傳奇性。想了解的筒子可以看死於決鬥的天才少年伽羅瓦。
在本教程裡,生成有限域依靠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之外,其餘所有的元素都是有本原多項式生成,本原多項式特徵滿足的餘數為0,這點可能比較難以理解,但是我們可以記住二維碼生成的有限域的本原多項式可以表示為,所以primitive=0x011D,直接輸入就行。
size 有限域中元素的個數
為了方便計算機計算通常,且二維碼的基本碼子大小為8bit,所以size=256,意味著二維碼生成有限域中有256個元素。
expTab 儲存對應冪次由本原多項式生成的元素
假設expTab[i]=,,logTab 儲存expTab中對應元素取對數的結果,這樣做的好處是可以將有限域中元素之間乘法轉為對數加法計算,減少計算量。
geneBase 對應裡的所羅門校驗碼生成式中的係數
可以為0或1,但在二維碼生成式中我們取通常取
一切有限域都有加法和乘法兩種運算,除此之外,GenericGF還定義了幾種常用運算
int add_or_sub(int a, int b)
有限域中兩個元素加法計算,減法可以看做元素補的加法
int exp(const int a)
返回expTab中第a個本原元素
int log(const int a)
返回expTab中第a個本原元素的對數
int multiply(const int a, const int b)
返回有限域中兩個元素的乘積
int inverse(const int a)
返回有限域中元素a的逆
裡的所羅門演算法主要是定義在有限域多項式函式上的操作,因此還在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)
多項式與多項式函式除法,返回商和餘數
裡的所羅門編碼演算法
介紹完有限域的概念後我們終於可以進入正題,開始介紹二維碼的編碼演算法了。
首先我們要定義一些符號在域中,定義為一次標準的裡的所羅門編碼操作。其中符號含義如下:
表示符號(本原元素)大小,如表示符號由8位二進位制陣列成
表示碼塊符號的長度
表示碼塊中資訊的符號長度
表示校驗碼的符號數
表示能夠校正的錯誤碼塊字元數
因此,如果我們在生成二維碼時,需要生成一個的裡的所羅門編碼,意味著這個編碼塊一共由36byte字元組成,其中前22byte為資訊資料,後14byte為校驗編碼,這個編碼塊最多可以糾正不超過7個分散或連續符號的錯誤。
裡的所羅門編碼演算法實際上是計算資訊多項式,除以校驗碼生成多項式之後的餘數多項式