二維碼生成之糾錯碼算法

原理參考 Error Correction Coding - QR Code Tutorial html

首先介紹伽羅瓦域,接着使用日誌和反日誌來簡化GF(256)中的乘法。
由於糾錯算法中須要使用多項式進行運算,須要處理多項式的四則運算。
生成糾錯編碼要進行多項式長除法,使用兩種多項式,一個是消息多項式 (message polynomial),另外一個是生成器多項式 (generator polynomial)。消息多項式將被一個生成器多項式除;生成器多項式是經過相乘獲得的多項式 \((x - α^0) ... (x - α^{n-1})\)java

接着生成糾錯碼,用消息多項式除以生成器多項式獲得餘數多項式,它的係數就是所要的糾錯碼 。算法

1. 伽羅瓦域 (Galois Field) 介紹

伽羅瓦域,該域本質上是一組受限的數字(也稱有限域),以及一些建立仍在該集合中的數字的數學運算。數組

二維碼標準要求使用模2位運算和模100011101位運算。這意味着使用Galois Field \(2^8\),或者說 Galois Field 256,有時寫成GF(256)。 GF(\(2^w\)) 表示含有 \(2^w\)個元素的有限域。測試

GF(256)中的數字都在0到255(包括)的範圍內。注意,這是能夠用8位字節表示的數字的相同範圍(最大的8位字節是11111111,它等於255)。編碼

伽羅瓦域運算spa

GF(256) 包含從0到255的全部數字。GF(256) 中的數學運算本質上是循環的,這意味着若是在 GF(256) 中執行的數學運算的結果大於255,那麼就須要使用模運算來得到仍然在 GF 中的數字 。日誌

在伽羅瓦域中,負數與正數具備相同的值,所以 -n = n。這意味着在 GF 中的加法和減法是同樣的。伽羅瓦域中的加法和減法是經過正常的加減運算,而後再進行模運算來實現的。因爲咱們使用的是位模2算法(如二維碼規範中所述),因此這與執行XOR(異或^)操做是同樣的。例如:code

1 + 1 = 2 % 2 = 0 等價於 1 ^ 1 = 0orm

0 + 1 = 1 % 2 = 1 等價於 0 ^ 1 = 1

爲了對二維碼進行編碼,GF(256)中的全部加減法都是經過將兩個數字進行異或操做來進行的。

使用字節模100011101生成2的冪

GF(256) 中的全部數字自己都必須在0到255的範圍內,所以\(2^8\) 對於 GF 來講彷佛太大了,由於它等於256。

二維碼規範要求使用字節模 100011101 算法(其中 100011101 是一個二進制數,至關於十進制數的 285)。這意味着當一個數字是256或更大時,它應該與285異或操做。即:\(2^8\) = 256 ^ 285 = 29 。 當\(2^9\)時,須要使用\(2^8\)的值來運算:\(2^9\) = \(2^8\) * 2 = 29 * 2 = 58

(使用此過程,GF(256)中的全部數字均可以用2n表示,其中n是0 <= n <= 255範圍內的數字。使用字節模100011101能夠確保全部的值都在0到255的範圍內。 )

下一小節將生成全部的 \(2^0\) ~ \(2^{255}\)的值來簡化 GF(256) 中的乘法。

2. 日誌和反日誌

在GF(256)中執行乘法只須要生成2的全部乘方, 以及相應的逆對數。

一個數的逆對數(antilogarithm),是指一個正數的對數即等於已知數。也就是說,在b=logaN對數運算中(以a爲底的對數),逆對數是已知對數b去尋求相應的真數N。

import java.util.Arrays;

/**
 * 冪和逆對數
 */
public class Power {
        /**
     * 生成2的n次方,GF(256) 範圍內的冪
     */
    public static int[] powersOf2ForGF(){
        int gf[] = new int[256]; // key 是指數, value 是冪值
        int t = 0;

        // 初始化第一個值 2**0=1
        gf[0] = 1;
        for(int exponent=1; exponent<=255; exponent++){
            gf[exponent] = exponent;
            t = gf[exponent-1]*2;
            
            if(t>255){
                t ^= 285;
            }
            gf[exponent] = t;
        }

        
        return gf;
    }

    /**
     * 逆對數 anti log
     * @param log
     * @return
     */
    public static int[] antiLog(int powers[]){
        int antiLog[] = new int[256]; // key 是值, value 是指數
        
        for(int exponent=0; exponent<=255; exponent++){
            antiLog[powers[exponent]] = exponent;
        }
        antiLog[0] = Integer.MIN_VALUE; // 2**x=0,指數x不存在,無窮小則趨於0
        antiLog[1] = 0; // 值爲1 對應的指數爲 0 和 255, 取0
        return antiLog;
    }

    public static void main(String[] args){
        // 2 的冪
        int powers[] = Power.powersOf2ForGF();
        System.out.println(Arrays.toString(powers));
        // 生成逆對數
        int antiLog[] = Power.antiLog(powers);
        System.out.println(Arrays.toString(antiLog));
    }
}
[1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, ... 216, 173, 71, 142, 1]
[-2147483648, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, ... 244, 234, 168, 80, 88, 175]

由於這些值不會變,因此能夠用數組保存起來,直接使用便可。

/**
 * 獲取一個key的冪
 * @param k
 * @return
 */
public static int getPower(int k){
    int powers[] = {1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1};
    return powers[k];
}

/**
 * 獲取key的逆log
 * @param k
 * @return
 */
public static int getAntilog(int k){
    // antiLog[0] 不存在,負無窮
    int antiLog[] = {-2147483648, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175};
    return antiLog[k];
}

3. 多項式加減乘

3.1 普通多項式的四則運算

加法部分思路參考 多項式加法,用單鏈表實現

爲何說是普通多項式呢?由於GF(256) 的四則運算和算術上的四則運算是不一樣的。先來看看普通的多項式四則運算,具體實現略去(以後專門寫一篇多項式的文章),這裏看看運算的測試結果:

public static void main(String[] args){
        Polynomial a = new Polynomial();
        a.addItem(-1,2).addItem(3,2).addItem(0,1).addItem(90.5,3);
        a.addItem(10,2);
        a.addItem(7,0);
        System.out.println(a); // +90.5x^{3}+12x^{2}+7

        Polynomial b = new Polynomial();
        b.addItem(0,3).addItem(3,2).addItem(5,1).addItem(3,-3);
        System.out.println(b); // +3x^{2}+5x+3x^{-3}

        // 加法操做
        Polynomial c = a.add(b);
        System.out.println(c); // +90.5x^{3}+15x^{2}+5x+7+3x^{-3}

        // 減法操做
        Polynomial c2 = a.sub(b);
        System.out.println(c2); // +90.5x^{3}+9.0x^{2}-5.0x+7-3.0x^{-3}

        // 乘法操做
        Polynomial d = a.mul(b);
        System.out.println(d); // +271.5x^{5.0}+488.5x^{4.0}+60.0x^{3.0}+21.0x^{2.0}+35.0x+271.5+36.0x^{-1.0}+21.0x^{-3.0}
        
        // 除法操做
        Polynomial d1 = new Polynomial().addItem(1,3).addItem(5,2).addItem(6,1).addItem(3,0);
        Polynomial e = new Polynomial(new Node(1,1)).addItem(1,0);

        // 商 和 餘數
        Polynomial quotient = new Polynomial();
        Polynomial remainder = new Polynomial();
        d1.div(e, quotient, remainder);
        
        System.out.println(d1); // x^{3}+5x^{2}+6x+3
        System.out.println(e); // x+1
        System.out.println(String.format("%s ... (%s) ", quotient, remainder)); // +x^{2.0}+4.0x+2.0 ... (+1.0)
        System.out.println(String.format("%s + [(%s) / (%s)]", quotient, remainder, e)); // +x^{2.0}+4.0x+2.0 + [(+1.0) / (+x+1)]
    }

兩個數的和差積分別爲:

a = \(+90.5x^{3}+12.0x^{2}+7\)
b = \(+3x^{2}+5x+3x^{-3}\)
a+b = \(+90.5x^{3}+15.0x^{2}+5x+7+3x^{-3}\)
a-b = \(+90.5x^{3}+9.0x^{2}-5.0x+7-3.0x^{-3}\)
a*b = \(+271.5x^{5.0}+488.5x^{4.0}+60.0x^{3.0}+21.0x^{2.0}+35.0x+271.5+36.0x^{-1.0}+21.0x^{-3.0}\)
除法用簡單的多項式測試:
被除數:a = \(+x^{3}+5x^{2}+6x+3\)
除數:b = \(+x+1\)
商爲:a/b = \(+x^{2.0}+4.0x+2.0\) 餘數是 \(1.0\)
表示爲:
$ +x^{2.0}+4.0x+2.0 ... (+1.0)\( 或者 \)+x^{2.0}+4.0x+2.0 + [(+1.0) / (+x+1)]$

3.2 GF(256) 中多項式的三則運算

爲何又是三則運算呢,由於除法比加減乘難一點,在下一節多項式生成好以後,下下一節再測試除法。(加減其實同樣,由於 -n = n, 因此減就是加,都是執行異或操做。)

public static void main(String[] args){
    PolynomialGF a = new PolynomialGF();
    a.addItem(3,2).addItem(0,1).addItem(90,3);
    a.addItem(10,2);
    a.addItem(7,0);
    System.out.println(a); // +2^{90}x^{3}+2^{115}x^{2}+x+2^{7}

    PolynomialGF b = new PolynomialGF();
    b.addItem(0,3).addItem(3,2).addItem(5,1).addItem(3,-3);
    System.out.println(b); // +x^{3}+2^{3}x^{2}+2^{5}x

    // 加法操做
    PolynomialGF c = a.add(b);
    System.out.println(c); // +2^{62}x^{3}+2^{10}x^{2}+2^{138}x+2^{7}

    // 乘法操做
    PolynomialGF d = a.mul(b);
    System.out.println(d); // +2^{90}x^{6}+2^{73}x^{5}+2^{225}x^{4}+2^{171}x^{3}+2^{143}x^{2}+2^{12}x
}

4. 消息多項式

消息多項式使用來自數據編碼步驟的數據碼字做爲其係數。例如,若是轉換爲整數的數據碼字是2五、218和35,則消息多項式將是\(25x^2 + 218x + 35\)

生成消息多項式代碼:

public class xxx{
    public static void main(String[] args){
        // 消息多項式
        int[] code = {2, 25, 218, 35};
        Polynomial mP = new ErrorCorrectionCoding().messagePolynomial(code);
        System.out.println(mP); // 2x^{3}+25x^{2}+218x+35
    }


    /**
     * 消息多項式
     * @param code
     * @return
     */
    public Polynomial messagePolynomial(int[] code){
        Polynomial m = new Polynomial();
        for(int i=0; i < code.length; i++){
            m.addItem(code[i], code.length-i-1);
        }

        return m;
    }
}

使用數字碼 2, 25, 218, 35 生成消息多項式爲 \(2x^{3}+25x^{2}+218x+35\)

4. 生成器多項式

生成器多項式是經過相乘獲得的多項式: \((x - α^0) ... (x - α^{n-1})\)

public class xxx{
    public static void main(String[] args){
        // 生成器多項式 (2個糾錯碼)
        PolynomialGF gp = new ErrorCorrectionCoding().generatorPolynomial(2);
        System.out.println(gp); // +x^{2}+α^{25}x+α^{1}
    }

    /**
     * (n個錯誤糾正碼的)生成器多項式 (x - α^0) ... (x - α^{n-1}) 
     * 遞歸實現,上一版本是普通實現
     * @param ECC_Number 糾錯碼字數(number of error correction codewords)
     * @return
     */
    public PolynomialGF generatorPolynomial(int ECC_Number){
        if(ECC_Number<1){
            return null;
        }

        PolynomialGF t = new PolynomialGF();

        // n=0
        if(ECC_Number==1){
            t.addItem(0, 1).addItem(0,0);
            return t;
        }else{
            t = new PolynomialGF().addItem(0, 1).addItem(ECC_Number-1,0);
            return t.mul(generatorPolynomial(ECC_Number-1));
        }
    }
}

5. 多項式的除法(用生成器多項式除消息多項式示例)

public static void main(String[] args){

    // 糾錯碼數
    int e_n = 10;

    // 消息多項式
    int[] code = {32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17};
    PolynomialGF mp = new ErrorCorrectionCoding().messagePolynomial(code);
    System.out.println(mp); // +α^{5}x^{15}+α^{92}x^{14}+α^{238}x^{13}+α^{78}x^{12}+α^{161}x^{11}+α^{155}x^{10}+α^{187}x^{9}+α^{145}x^{8}+α^{98}x^{7}+α^{6}x^{6}+α^{122}x^{5}+α^{100}x^{4}+α^{122}x^{3}+α^{100}x^{2}+α^{122}x+α^{100}
    
    // 生成器多項式 (10個糾錯碼)
    PolynomialGF gp = new ErrorCorrectionCoding().generatorPolynomial(e_n);
    System.out.println(gp); // +x^{10}+α^{251}x^{9}+α^{67}x^{8}+α^{46}x^{7}+α^{61}x^{6}+α^{118}x^{5}+α^{70}x^{4}+α^{64}x^{3}+α^{94}x^{2}+α^{32}x+α^{45}

    // 消息多項式 * x^n
    PolynomialGF xn = new PolynomialGF(new Node(0, e_n));
    mp = mp.mul(xn);
    System.out.println(mp); // +α^{5}x^{25}+α^{92}x^{24}+α^{238}x^{23}+α^{78}x^{22}+α^{161}x^{21}+α^{155}x^{20}+α^{187}x^{19}+α^{145}x^{18}+α^{98}x^{17}+α^{6}x^{16}+α^{122}x^{15}+α^{100}x^{14}+α^{122}x^{13}+α^{100}x^{12}+α^{122}x^{11}+α^{100}x^{10}

    System.out.println("多項式除法,商和餘數分別爲:");
    PolynomialGF quotient = new PolynomialGF(), remainder = new PolynomialGF();
    mp.div(gp, quotient, remainder);
    Coef.outputFormatValue = true; // 設置係數的輸出格式
    System.out.println(quotient); // +32x^{15}+89x^{14}+61x^{13}+138x^{12}+243x^{11}+149x^{10}+135x^{9}+183x^{8}+59x^{7}+184x^{6}+51x^{5}+41x^{4}+179x^{3}+70x^{2}+84x+107
    System.out.println(remainder); // +196x^{9}+35x^{8}+39x^{7}+119x^{6}+235x^{5}+215x^{4}+231x^{3}+226x^{2}+93x+23

    System.out.println("糾錯碼爲:");
    int[] e = remainder.getCoefs();
    System.out.println(Arrays.toString(e)); // [196, 35, 39, 119, 235, 215, 231, 226, 93, 23]
}

首先準備消息多項式和生成器多項式

消息多項式應該是:
\(+32x^{15}+91x^{14}+11x^{13}+120x^{12}+209x^{11}+114x^{10}+220x^{9}+77x^{8}+67x^{7}+64x^{6}+236x^{5}+17x^{4}+236x^{3}+17x^{2}+236x+17\)

爲了統一格式,將每一項的係數是數值轉成了 \(α^n\) (α=2) 的格式:

\(+α^{5}x^{15}+α^{92}x^{14}+α^{238}x^{13}+α^{78}x^{12}+α^{161}x^{11}+α^{155}x^{10}+α^{187}x^{9}+α^{145}x^{8}+α^{98}x^{7}+α^{6}x^{6}+α^{122}x^{5}+α^{100}x^{4}+α^{122}x^{3}+α^{100}x^{2}+α^{122}x+α^{100}\)

爲了確保前導項的指數在除法期間不會變得過小,將消息多項式乘以 \(x^n\),其中n是所需的錯誤糾正碼的數量。在這種狀況下,n是10,對於10個糾錯碼,將消息多項式乘以x10,獲得

\(+α^{5}x^{25}+α^{92}x^{24}+α^{238}x^{23}+α^{78}x^{22}+α^{161}x^{21}+α^{155}x^{20}+α^{187}x^{19}+α^{145}x^{18}+α^{98}x^{17}+α^{6}x^{16}+α^{122}x^{15}+α^{100}x^{14}+α^{122}x^{13}+α^{100}x^{12}+α^{122}x^{11}+α^{100}x^{10}\)

生成器多項式(糾錯碼數爲10)是:
\(+x^{10}+α^{251}x^{9}+α^{216}x^{8}+α^{78}x^{7}+α^{69}x^{6}+α^{76}x^{5}+α^{114}x^{4}+α^{64}x^{3}+α^{94}x^{2}+α^{32}x+α^{45}\)

使用多項式除法計算出餘數

\(+196x^{9}+35x^{8}+39x^{7}+119x^{6}+235x^{5}+215x^{4}+231x^{3}+226x^{2}+93x+23\)

餘數的係數就是要生成的糾錯碼:

[196, 35, 39, 119, 235, 215, 231, 226, 93, 23]

6. 生成糾錯碼方法封裝

/**
 * 生成糾錯碼
 * @param dataCode data codewords 數據碼
 * @param eNum  error correction codewords 糾錯碼個數
 * @return
 */
public static int[] getCode(int dataCode[], int eNum){
    if(eNum<1 || dataCode.length<1){
        return null;
    }

    // 消息多項式
    PolynomialGF mp = new ErrorCorrectionCoding().messagePolynomial(dataCode);

    // 生成器多項式 (nNum個糾錯碼)
    PolynomialGF gp = new ErrorCorrectionCoding().generatorPolynomial(eNum);

    // 消息多項式 * x^n
    PolynomialGF xn = new PolynomialGF(new Node(0, eNum));
    mp = mp.mul(xn);

    // 多項式除法,求餘數");
    PolynomialGF quotient = new PolynomialGF();
    PolynomialGF remainder = new PolynomialGF();
    mp.div(gp, quotient, remainder);

    // 糾錯碼
    int[] e = remainder.getCoefs();
    return e;
}

---------------------
封裝後調用:
// 數據碼 和 糾錯碼數
int [] codeWords = {32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17};
int e_n = 10;
getCode(codeWords, e_n))

bingo!

相關文章
相關標籤/搜索