自己动手写二维码

自己动手写二维码

Project Imformation

  • Project name:Implementation of QR-codes decoder and encoder
  • Solution Description:
    The general QR code encoding procedures are as follows:
  1. Data Analysis.
    We divide the input information into several pieces and select the suitable mode for each one, in order to generate the shortest possible bit stream.
  2. Determine Encoding Information.
    Create the bit sequence by corresponding encoding rules, such as Byte Mode and Kanji Mode. Each mode has its own principles in Mode Indicator and Encoding Method.
  3. Error Correction Coding.
    Divide the data codeword into blocks according to the version. Then generate the Error Correction Code(abbreviated as ECC) codeword by using the polynomial division in GF( 2 8 2^8 28) for each block.
  4. Complete the final message.
    Rearrange all the blocks in the encoding sequence.
  5. Fulfill the QR-framework.
    Write all the codewords into the QR matrix in the zig-zag way.
  6. Data Masking.
    Mask the data according to its mask type.
  7. Fulfill the Reserved Areas.
    Write bits to the reserved areas including locator pattern, timing line, alignment locator, quiet zone and format&version area.

The general QR code decoding procedures are as follows:

  1. Read Format Information and Version.
    Read the format and version and do the error correction by using the Hamming Detection.

  2. Data Unmasking.
    Unmask the data according to its mask type.

  3. Rearrange the Placement of Data Blocks.
    Rearrange all the blocks in the input stream sequence.

  4. Error Correction.
    Correct the data codeword by using the Error Correction Code. First, compute the syndromes polynomial. Then do a Berlekamp-Massey algorithm to get the error locator polynomial. After that, it is necessary to compute the error evaluator polynomial. Finally, do a subtraction at the error position by an error magnitude and the message will be fixed.

  5. Decode the Data.
    Decode the data in different encoding modes and output the final payload.

Above contents are detailed described in my Project Application.

Project Progress

Finished Work:

  1. Complete QR decoder for all encoding modes, except for kanji mode and ECI mode. These two modes are involved with problems about character set converting.

  2. Complete QR encoder for all encoding modes, except for kanji mode and ECI mode.

  3. Complete regression tests for encoder and decoder.

  4. Suggest improvement on QR detector.

Information about my implementation:

1.Decoder

The whole process is implemented in bool QRDecode::decodingProcess(). And here is the flowchart of it.
在这里插入图片描述

1. Read and correct format information

This step is implemented in bool QRDecode::readAndCorrectFormat(uint16_t& format).
There are two sets of format bits in one QR image, distributing in different areas, which make it more reliable for storing the format information. When one can’t be corrected due to exceeded error correction limit, you can read another one as the additional attempt.
在这里插入图片描述
The exact position of format bits are fixed in QR image. So I just define them as below:

    const int xs[2][max_format_length] = {{8, 8, 8, 8, 8, 8, 8,
                                           my_size-8,my_size-7,my_size-6,my_size-5,my_size-4,my_size-3,my_size-2,my_size-1},
                                          {0, 1, 2, 3, 4, 5, 7,8,8     ,8     ,8     ,8     ,8     ,8     ,8}};
    const int ys[2][max_format_length] = {{my_size-1,my_size-2,my_size-3,my_size-4,my_size-5,my_size-6,my_size-7,
                                           8,8, 8, 8, 8, 8, 8, 8 },
                                          {8,   8, 8, 8, 8, 8, 8,     8,     7,     5,     4,     3,     2,     1,     0}} ;

After reading from the image, format bits are stored in uint16_t my_format. And the following function bool QRDecode:: correctFormat(uint16_t& format) makes a correction for it.

I prefer to use hamming distance to correct the format infomation. Because the data string is only 5 bits long (2 bits for ECC level and 3 bits for mask pattern) with a fixed ECC generator. So there are only 32 components in the look-up table, which can be quickly matched. We don’t need to do a “berlekamp_massey” method as it used to.

/**Here is the look-up table for hammming detection  */
static const uint16_t after_mask_format [32]={
         0x5412,0x5125,0x5e7c,0x5b4b,0x45f9,  0x40ce,0x4f97,0x4aa0,0x77c4,0x72f3,
         0x7daa,0x789d,0x662f,0x6318,0x6c41,  0x6976,0x1689,0x13be,0x1ce7,0x19d0,
         0x0762,0x0255,0x0d0c,0x083b,0x355f,  0x3068,0x3f31,0x3a06,0x24b4,0x2183,
         0x2eda,0x2bed
};
2. Read and correct version information

If the version level is above 6, then we need to read the version information from the image, so that the version level can be more reliable.
This step is same as Step 1. The only differences are that the position of fixed pixels are located on different positions and the length of version information is 18 bits but not 15 bits.
在这里插入图片描述
After doing the following two steps, you can get all the basic information about the QR code.

    /**EC level (1-2)+Mask(3-5) + EC for this string( 6-15)
     * get rid of the ecc_code*/
    uint8_t fdata = my_format >> 10;
    ecc_level = eccCodeToLevel(fdata >> 3);
    mask_type = fdata & 7;
    version_info =&version_info_database[version_level];
    cur_ecc_params = &version_info->ecc[ecc_level];
3.Unmask the data bits

The sampling original information is stored in Mat straight. We need to unmask its data and store the result in Mat unmasked_data.

3.1 Exclude reserved area

Data bits in the reserved area such as the finder pattern and timing pattern are invalid for the final bit stream, so we just exclude these area by assigning const int invalid_region_value = 110; at these position.

3.2 Unmask according to the mask type

We need to do a XOR operation to unmask the data bits at corresponding positions. The unmask/mask rules are showed as follows:

Mask NumberIf the formula below is true for a given row/column coordinate, switch the bit at that coordinate
0(row + column) mod 2 == 0
1(row) mod 2 == 0
2(column) mod 3 == 0
3(row + column) mod 3 == 0
4( floor(row / 2) + floor(column / 3) ) mod 2 == 0
5((row * column) mod 2) + ((row * column) mod 3) == 0
6( ((row * column) mod 2) + ((row * column) mod 3) ) mod 2 == 0
7( ((row + column) mod 2) + ((row * column) mod 3) ) mod 2 == 0

在这里插入图片描述
The left image is the straight and the right one is unmask_data. The gray area stands for invalid value.
在这里插入图片描述

3.3 Matters need attention

However, the alignment patterns must be put into the matrix after the finder patterns and separators have been placed, and the alignment patterns must not overlap the finder patterns or separators.
The following images show a version 2 code, which was described in the previous paragraph as having alignment patterns centered at (6, 6), (6, 18), (18, 6) and (18, 18).
However, as the image on the left shows, the alignment patterns highlighted in red must not be placed in the matrix because they overlap the finder patterns and separators. Alignment patterns that overlap the finder patterns or separators are simply omitted from the matrix.
在这里插入图片描述

4.Read data bits into a bit stream

Using a zig-zag way to read the valid data bit into our bit stream vector.
Detailed information are described at: https://www.thonky.com/qr-code-tutorial/module-placement-matrix
在这里插入图片描述
The pixel bits are read into a 8-bit codeword. When meeting borders, we can change the heading direction.

    while (x > 0) {
        if (x == 6)
            x--;
        /**read horizontally in a zig-zag way*/
        for(int i = 0 ; i <=1 ; i ++ ){
            /** i = 0 stands for x in the right postion */
            bit_value = readBit( x - i ,  y);
            ...
        }
        y += dir;
        /**change direction when meets border*/
        if (y < 0 || y >= version_size) {
            dir = -dir;
            x -= 2;
            y += dir;
        }
    }

And we use the variable codeword_value to write the bits into the corresponding codeword. When all 8 bits are loaded into the variable, we need to store it in orignal_data, and continue to read the following 8 bits as a new codeword.

            /**read from the straight mat*/
            if(bit_value != invalid_region_value){
                codeword_value *=2;
                codeword_value +=bit_value;
                count ++;
                /**update codeword*/
                if(count % 8 == 0){
                    orignal_data.push_back(codeword_value);
                    codeword_value = 0;
                }
            }

During the process, we always skip the pixels of INVALID_REGION. And remember the first bit is always put at the front of the codeword.

5.Rearrange bit stream to get blocks in correct sequence

As we know, the codewords will be arranged in a particular sequence during the encoding process. The basic idea is :

  • Divide the codewords into blocks according to the version .
  • Concatenate the data block with its ECC block.
  • Arrange the data stream in the vertical sequence .
    在这里插入图片描述
    So, we need to recover the blocks according to the version and rearrange the data stream in the horizontal sequence for decoding process.
    在这里插入图片描述
    The data codewords can be fetched by the following method:
int offset=num_blocks_in_G1+num_blocks_in_G2;
/*i for blocks , j for number of data codeword in certain Group*/
rearranged_data[index]=orignal_data[i+j*offset]

As the ECC codewords always follow the data codewords, the offset can be defined as follows:

    int offset_ecc= cur_ecc_params->data_codewords_in_G1*cur_ecc_params->num_blocks_in_G1
                    +
                    cur_ecc_params->data_codewords_in_G2*cur_ecc_params->num_blocks_in_G2;

Note, the blocks in Group 2 have either one more column than blocks in Group 1 or the same. So we need to have additional judgement to deal with the condition.

Here is a flowchart of my implementation:
在这里插入图片描述

6.Correct the possible mistakes in the blocks

This step is completed in Step 5 and is implemented in bool QRDecode::correctSingleBlock(int block_num , int block_head_index ,Mat & corrected). In other words, we do a correctSingleBlock when we get a new block in the correct sequence.
The function correctSingleBlock is for one arranged block at a time.
The process has four main steps:

  1. Compute the syndromes polynomial.
  2. Compute the error locator polynomial.
  3. Find the position of the possible errors.
  4. Confirm the magnitude of the error position and correct the error.

R ( x ) = C ( x ) + E ( x ) R(x) = C(x) + E(x) R(x)=C(x)+E(x)

This equation represents the task we are going to deal with .R(x) stands for the block we receive (it may has some error) ,C(x)stands for the real block and E(x) stands for the error involved.
Our task is to figure out the polynomial of the E(x) and subtract it from the C(x).

6.1 Syndromes

Syndromes can be represented as the following equation:
S ( x ) = α 1 x + α 2 x 2 + . . . + α n − 1 x n − 1 + α n x n S(x) = \alpha _1x+ \alpha _2x^2+...+\alpha _{n-1}x^{n-1}+ \alpha _{n}x^{n} S(x)=α1x+α2x2+...+αn1xn1+αnxn
We can replace the x with α i \alpha^i αi and get a vector like this (n = the number of ECC codewords) :
[ s 1 s 2 ⋮ s n ] = [ S ( 2 0 ) S ( 2 1 ) ⋮ S ( 2 n ) ] \left [ \begin{matrix} s_1 \\ s_2 \\ \vdots \\s_{n} \end{matrix} \right ] = \left [ \begin{matrix} S(2^0) \\ S(2^1) \\ \vdots \\S(2^n) \end{matrix} \right ] s1s2sn=S(20)S(21)S(2n)
If elements in vector s(x) are all zeros, then no error occurs. Else, we need to correct the error.

int calBlockSyndromes(const Mat & block, int synd_num,vector <uint8_t>& synd){
    int nonzero = 0;
    /*the original method*/
    for (int i = 0; i < synd_num; i++) {
        /*get the syndromes by repalcing the x with pow(2,i) and evaluating the results of the equations*/
        uint8_t tmp =gfPolyEvaluate(block, gfPow(2,i));
        /*print for debug*/
        if (tmp)
            nonzero = 1;
        synd.push_back(tmp);
    }
    return nonzero;
}
6.2 Error locator

We use Berlekamp Massey Algorithm to calculate the error locator from the syndromes.

  1. Calculate the discrepancy while looping the syndromes:
    d e l t a = S n + C 1 ∗ S n − 1 + . . . + C L ∗ S n − L delta= S_n+ C_1*S_{n-1} + ... + C_L*S_{n-L} delta=Sn+C1Sn1+...+CLSnL
    C(x) is initialized to 1, L is the current number of assumed errors, and initialized to zero.
        /**cal discrepancy =Sn+ C1*S(n-1) + ... + CL*S(n-L)*/
        for(size_t j = 1 ; j<= L; j++ ){
            delta ^= gfMul(C.ptr(0)[j], synd[i - j]);
        }
  1. If delta=zero, current C(x) and L are correct for the moment, increments m, and continues.
    If delta!=zero, C(x) will be adjust:
    C ( x ) = C ( x ) − ( d / b ) x m B ( x ) C(x)=C(x) - (d/b)x^mB(x) C(x)=C(x)(d/b)xmB(x)

B(x) is a copy of the last C(x) since L was updated and initialized to 1.
b is a copy of the last delta d since L was updated and initialized to 1.
m is the number of iterations since L, B(x), and b were updated and initialized to 1.

/**shift = x^m*/
Mat shift(1,(int)synd_num,CV_8UC1,Scalar(0));
shift.ptr(0)[m]=1;
/**scale_coeffi = d/b */
Mat scale_coeffi = gfPolyScaling(shift,gfMul(delta,gfInverse(b)));
C=gfPolyAdd(C,gfPolyMul(B,scale_coeffi));
  1. Adjust other variables as well.
B = t.clone();
b=delta;
L = i + 1 - L;
m = 1;

Note that during the iteration process, the delta will become zero before i becomes greater than or equal to 2L (i is used as the main iterator and to index the syndromes from 0 to N−1) .
After transversing all the syndromes, the C(x) is the error locator polynomial.
Here is a flowchart of my implementation:
在这里插入图片描述

6.3 Error position

We use Chien’s search to calculate the roots of error locator poly and to calculate the index of the error in current block.
The normal way is to transverse all the element in the gf_table. If a n − i a^{n-i} ani is the error position, then a − ( n − i ) a^{-(n-i)} a(ni) is the root of the error locator poly.
Λ ( x ) = ( 1 + α i 1 ) ( 1 + α i 2 ) . . . ( 1 + α i v ) \Lambda(x)=(1+\alpha^{i_1})(1+\alpha^{i_2})...(1+\alpha^{i_v}) Λ(x)=(1+αi1)(1+αi2)...(1+αiv)
n is total length of the original message, v is number of syndromes, i is the iterator.
And here we optimize the function by using Chien’s search algorithm. We just take in 2 i 2^i 2i to find the root such that each evaluation only takes constant time.

 /*optimize to just check the interesting symbols*/
    for(int i = 0; i < msg_len ; i ++){
        int index=msg_len-i-1;
        /* if a^(n-i) is the error postion ,then a^-(n-i) is the root of the poly Sigma
             * use Chien's search to evaluate the polynomial such that each evaluation only takes constant time
             */
        if(gfPolyEvaluate(sigma,gfInverse(gfPow(2,index)))==0){
            error_index.push_back(index);
        }
    }
6.4 Error magnitude and correct the error

We use Forney algorithm to correct the errors.This algorithm can calculate the error magnitude .The equation is as follows:
e k = − Ω ( X j − 1 ) Λ ′ ( X j − 1 ) e_k =- \frac{\Omega(X_j^{-1})}{\Lambda'(X_j^{-1})} ek=Λ(Xj1)Ω(Xj1)

  • We need to calculate the Ω ( x ) \Omega(x) Ω(x) as the numerator.
    Ω ( x ) = ( S ( x ) ∗ Λ ( x ) ) m o d ( x 2 t ) \Omega(x) = (S(x)* \Lambda(x) )mod ( x^{2t}) Ω(x)=(S(x)Λ(x))mod(x2t)
/**First calculate the error evaluator polynomial*/
Mat Omega= gfPolyMul(syndrome,e_loc_poly);
  • We need to calculate the Λ ( x ) \Lambda(x) Λ(x) as the denominator.
    Λ ( x ) = ( 1 + α i 1 ) ( 1 + α i 2 ) . . . ( 1 + α i v ) = 1 + ∑ i = 1 v λ i x i \Lambda(x)=(1+\alpha^{i_1})(1+\alpha^{i_2})...(1+\alpha^{i_v}) = 1 + \sum_{i=1}^v\lambda_ix^i Λ(x)=(1+αi1)(1+αi2)...(1+αiv)=1+i=1vλixi
    Λ ′ ( x ) = ∑ i = 1 v i ⋅ λ i x i \Lambda'(x)= \sum_{i=1}^vi · \lambda_ix^i Λ(x)=i=1viλixi
    In the above expression, note that i is an integer. The operator · represents ordinary multiplication (repeated addition in the finite field) and not the finite field’s multiplication operator.
    That’s why quirc implement the derivative like as follows, because after the XOR operation, the even items will be zero all the time .
	/* Compute derivative of sigma */
	memset(sigma_deriv, 0, MAX_POLY);
	for (i = 0; i + 1 < MAX_POLY; i += 2)
		sigma_deriv[i] = sigma[i + 1];

To make my code more readable, I write this part in a more mathematical way to show the whole derivation process.

    /**The operator · represents ordinary multiplication (repeated addition in the finite field)!!!
         * that's why the even items are always zero!*/
    for(size_t i = 1;i <= err_len; i++){
        uint8_t tmp = e_loc_poly.ptr(0)[i];
        err_location_poly_derivative.ptr(0)[i-1]=tmp;
        for(size_t j = 1; j < i ; j++)
            err_location_poly_derivative.ptr(0)[i-1]^=tmp;
    }
  • We can substitute the root we find in Step 2 to calculate the error magnitude in the corresponding position.
    for (size_t i = 0; i < err_len; i++) {
        uint8_t xinv = gfInverse(gfPow(2, error_index[i]));
        uint8_t denominator = gfPolyEvaluate(err_location_poly_derivative,xinv);
        uint8_t numerator = gfPolyEvaluate(Omega, xinv);
        /**divded them to get the magnitude*/
        uint8_t error_magnitude = gfDiv(numerator,denominator);
        msg_out.ptr(0)[error_index[i]]^=error_magnitude;
    }
7. Decode current bit stream according to different mode type

This step is implemented in the function bool QRDecode::decodeCurrentStream(). We will iterate all the stream bits until all bits are used.

  1. we judge whether the qr-code is mixed mode or other standard mode, which is easy to complete. If we can get two different modes in one qr-code, then it is defined as the mixed mode, so we set the mode type equal to -1:
 if(mode_type == 0){
            /**get the first mode head*/
            mode_type = mode;
        }
        else{
            /**won't change in these mode */
            if(mode_type == QR_MODE_STRUCTURE || mode_type == QR_MODE_ECI ||
               mode_type == QR_MODE_FNC1FIRST || mode_type == QR_MODE_FNC1SECOND || mode == QR_MODE_NUL)
                ;
            else if(mode != mode_type){
                /**appear more than once*/
                mode_type = -1;
            }
        }
  1. The bits of the character counter differs according to its mode and version.
    The binary data is also divided into groups and is transformed to other coding standard.
    The terminator always consists of zeros, so it is more efficient to create a QR_MODE_NUL pattern in the Decoding Mode.

I improve this part by using the Mat to store the bit instead of u_int8_t, which simplifies the process of getting bits. We are able to fetch the bits directly and avoid doing the additional transformation from u_int8_t to 8-bits.

This flowchart depicts the whole process of decoding :

在这里插入图片描述

  • 3.The data information is encoded in the following format:
    D a t a = M o d e + C h a r a c t e r C o u n t I n d i c a t o r + B i n a r y D a t a + T e r m i n a t o r Data = Mode + Character Count Indicator + Binary Data + Terminator Data=Mode+CharacterCountIndicator+BinaryData+Terminator
    The mode is always a 4-bit information, and the total decoding mode is defined as follows:
    typedef enum {
        QR_MODE_AUTO       = -1    , ///< Terminator (NUL character). Internal use only
        QR_MODE_NUL        = 0b0000,   ///< Terminator (NUL character). Internal use only
        QR_MODE_ECI        = 0b0111,        ///< ECI mode
        QR_MODE_NUM        = 0b0001,    ///< Numeric mode
        QR_MODE_ALPHA      = 0b0010,         ///< Alphabet-numeric mode
        QR_MODE_BYTE       = 0b0100,          ///< 8-bit data mode
        QR_MODE_KANJI      = 0b1000,      ///< Kanji (shift-jis) mode
        QR_MODE_STRUCTURE  = 0b0011,  ///< Internal use only
        QR_MODE_FNC1FIRST  = 0b0101, ///< FNC1, first position
        QR_MODE_FNC1SECOND = 0b1001, ///< FNC1, second position
    } QRencodeMode;
7.1 Numerical Mode

The main concept is to divide 3 numerical char into a 10-bit group.
在这里插入图片描述

bool QRDecode::numericDecoding(int &index){
    int count = 0;
    /*check version_level to update the bit counter*/
	int bits...get bits according to the level
	/**get correspoing bits as the character counter*/
    std::string cur_buffer = "";
    count = getBits(bits,final_data,index);

    /*divided 3 numerical char into a 10bit group*/
    while (count >= 3) {
        int num = getBits(10, final_data,index);
        cur_buffer += char(num / 100 + '0');
        cur_buffer += char((num % 100) / 10 + '0');
        cur_buffer += char(num % 10 + '0');
        count -= 3;
    }
    /*the final group*/
    if(count == 2){
        /*7 bit group*/
        int num = getBits(7,final_data,index);
        cur_buffer += char((num % 100) / 10 + '0');
        cur_buffer += char(num % 10 + '0');
    } else if (count == 1) {
        /*4 bit group*/
        int num = getBits(4, final_data,index);
        cur_buffer += char(num % 10 + '0');
    }
	/**translate into the final data payload*/
    return true;

}
7.2 Byte Mode

The main concept is to use 8-bit to represent a character.
在这里插入图片描述

bool QRDecode::byteDecoding(int &index){
    int bits = 8;
    int count = 0;
    /**check version_level to update the bit counter*/
    if(version_level>9)
        bits=16;
    std::string cur_buffer = "";
    count = getBits(bits,final_data,index);
    /**decode data bits*/
    for (int i = 0; i < count; i++){
        int tmp =getBits(8,final_data,index);
        cur_buffer+=char(tmp);
    }
    for (size_t i = 0; i < cur_buffer.length(); i++) {
        cur_str.push_back(uint8_t(cur_buffer[i]));
    }
    return true;
}
7.3 Kanji Mode

(The decoding method works properly only when the current coding set is SHIFT-JIS. Otherwise, you must do a UTF-8 to SHIFT-JIS conversion)
We need to divide 13-bit binary string to two byte in this mode.
Here are the steps:
1.Get a 13-bit string.
2.The converting process can be explained in the following equations:
P = X − C o n s t P = X - Const P=XConst
X = Shift JIS value, Const =0x8140 or 0xC140
L = P % 1 6 2          H = P / 1 6 2 L = P \% 16^2 \ \ \ \ \ \ \ \ H = P / 16^2 L=P%162        H=P/162
L is the lower byte and the H is the higher byte.
Y = H ∗ 0 x C 0 + L < = > { Y / 0 x C 0 = H + L / 0 x C 0 = H + ( L > = 0 ) Y % 0 x C 0 = L % 0 x C 0 Y=H*0xC0 + L <=> \begin{cases} Y/0xC0 = H + L/0xC0 = H + (L>=0) \\ Y \%0xC0 = L \%0xC0 \end{cases} Y=H0xC0+L<=>{Y/0xC0=H+L/0xC0=H+(L>=0)Y%0xC0=L%0xC0
Y = 13-bit value
{ H = Y / 0 x C 0 − ( L > = 0 ) L = Y % 0 x C 0 + { 0 , 0 x C 0 } \begin{cases} H = Y/0xC0 - (L>=0) \\ L = Y \%0xC0 + \{ 0,0xC0 \} \end{cases} {H=Y/0xC0(L>=0)L=Y%0xC0+{0,0xC0}
Y = H ∗ 0 x C 0 + L Y = H*0xC0 + L Y=H0xC0+L

bool QRDecode::kanjiDecoding(int &index){
    const int per_char_len = 13;
    /*initialize the count indicator*/
    int counter ....
    /*initialize the count length*/
    int count = 0;
    count = getBits(counter,final_data,index);
    /*correction for L_mod_C0*/
    const int addition[2] = {0b00000000 , 0b11000000};
    for (int i = 0; i < count; i++){
        /*Get My bits*/
        int Y =getBits(per_char_len,final_data,index);
        int L_mod_C0 = Y % 0xc0; /*the real L is L_mod_C0 + addition*/
        int H_around = Y / 0xc0; /*the real H is H_around + (L>=C0)*/
        /**the real L and H */
        int L = 0; int H = 0 ;
        bool is_err = true ;
        /**correction for L_mod */
        for(int j = 0 ; j < 2 ; j++){
            L = addition[j] + L_mod_C0 ;
            H = H_around - (L>=0xc0);
            /**check if is equal to the original bits*/
            if(Y == H*0xc0+L){
                is_err = false;
                break;
            }
        }
        if(is_err){
            return false;
        }
        /**get the subtract value */
        uint16_t subtract =uint16_t((H<<8) + L);
        uint16_t result = 0;
        if (0xe040-0xc140 <= subtract && subtract <= 0xebbf - 0xc140) {
            result = subtract + 0xc140;
        } else {
            result = subtract + 0x8140;
        }
        cur_str_len++;
        cur_str.push_back(result >> 8);
        cur_str_len++;
        cur_str.push_back(result & 0xff);
    }
    return true;
}
7.4 Alphanumeric Mode

This mode is pretty simple.
We just need to divide the 11-bit binary string into two byte by dividing 45 and modding 45. Then use a fixed character map to decode.

bool QRDecode::alphaDecoding(int &index){
    /*alpha table*/
    static const char *alpha_map =
            "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";

    std::string cur_buffer = "";
    int count = 0;
    /*initialize the count indicator*/
    int counter...
    /*string length*/
    count = getBits(counter,final_data,index);
    
    /*11bits at a time */
    while (count >= 2) {
        if(remainingBitsCount(index)<11){
            return false;
        }
        int num = getBits(11,final_data,index);
        /*divided into to parts*/
        int H = num/45;
        int L = num%45;
        cur_buffer+=alpha_map[H];
        cur_buffer+=alpha_map[L];
        count -= 2;
    }
    /*remaining 6 bits*/
    if (count!=0){
        if(remainingBitsCount(index)<6){
            return false;
        }
        int num = getBits(6,final_data,index);
        cur_buffer+=alpha_map[num];
    }
	/**update the final result*/
    return true;
}
7.5 ECI Mode

There are two main tasks in this mode.
1.Get the ECI Assignment Number and the corresponding encoding character set, which was stored in the variable eci.
2.The ECI Mode will influence other decoding modes, such as Byte Mode. Before writing to the result variable payload, we need to do a character set conversion.
Because the conversion process is implemented by <iconv.h>, so I only use UTF-8 for this mode.

bool QRDecode::eciDecoding(int &index){
    /*ECI Assignment Number is at least 8bits*/
    if (remainingBitsCount(index) < 8){
        return false;
    /*get ECI Assignment Number*/
    eci = (uint32_t)getBits(8,final_data,index);
    /*check the highest two bits*/
    int codeword_value = eci >> 6;
    /**judging the bits for eci mode*/
    while(codeword_value > 0){
        if (remainingBitsCount(index) < 8){
            return false;
        }
        eci = (eci << 8) | getBits(8,final_data,index);
        codeword_value /=2;
    }
    return true;
}
7.6 FNC1 in the first position

bool QRDecode::fnc1_first is the symbol of FNC1 mode. And FNC1 mode is reflected in other decoding processes of this mode.

Here is the flowchart of how it works:
在这里插入图片描述

7.7 FNC1 in the second position

bool QRDecode::fnc1_second is the symbol of FNC1 mode.
uint32_t QRDecode::fnc1_second_AI is the 8-bit codeword of Application Indicator.

Here is the flowchart of how it works:
在这里插入图片描述
The main process is implemented in bool QRDecode::fncDecoding()

bool QRDecode::fncDecoding(){
    if(fnc1_first && cur_str_len == 0){
        loadString("]Q3",cur_str);
    }
    else if(fnc1_second && cur_str_len == 0){//
        loadString("]Q5",cur_str);
    }
    return true;
}

The only difference is that FNC1SECOND has a 8-bit Application Identification follows the mode indicator.

			case QR_MODE_FNC1FIRST:
                fnc1_first = true;
                fncDecoding();
                break;
            case QR_MODE_FNC1SECOND:
                fnc1_second_AI = getBits(8,final_data,index);
                fnc1_second = true;
                fncDecoding();
                cur_str.push_back(char((fnc1_second_AI % 100) / 10 + '0'));
                cur_str.push_back(char((fnc1_second_AI % 10)+ '0'));
                break;
7.8 Structure Append Mode

In this mode , we just need to get the additional two codeword of Structure Append Mode.
The first codeword is symbol sequence indicator.
The second one is parity data, which is identical in all append messages in order to enable all readable symbols be part of the same Structured Append message.

bool QRDecode::structureAppendDecoding(int &index){
    if(remainingBitsCount(index)<16){
        return false;
    }
    getBits(4,final_data,index);//int current_postion =
    getBits(4,final_data,index);//int total_number =
    getBits(8,final_data,index);//int parity_data =
    return true;
}
2.Encoder

The whole process is implemented in QREncoder(const std::string& input ,int mode,int v ,int ecc ,int mask ,int eci_mode ,int structure_num ). And here is the flowchart of it.
在这里插入图片描述

2.1 Get basic information about QR code

The API of QR-code generation is as follows:

bool generate(cv::String input,cv::OutputArray  output,
             int version = 0, int correction_level = CORRECT_LEVEL_L,int mode = QR_MODE_AUTO,
             int structure_number = 1 )

We use the default parameters.v = 0 means use the Version Estimate function, ecc = 0 means use the least ECC level for error correction, eci_mode is fixed as UTF-8 and the input information will not be divided under the default condition.
And the final results are stored in vector<Mat> my_qrcodes

2.2 Calculate parity for structure append mode

The value of parity is the XOR result of all characters in the string.

        parity = 0;
        for(size_t i = 0 ; i < input.length() ; i++ ){
            parity ^= input[i];
        }
2.3 Divided into several QR codes

The Structure Append Mode need to divide a input string to several sub-str for several QR codes.So here we use the .substr() of std::stringfor the division.
And each iteration of the loop will generate a QR-image which is stored in the vector<Mat> my_qrcodes

 int segment_len = (int)ceil((int)input.length()/struct_num);
    for(int i = 0; i < struct_num ; i ++){
        sequence_num = (uint8_t)i;
        /**get the sub string for structure mode*/
        int segment_begin = i * segment_len;
        int segemnt_end   = min( (i+1)*segment_len ,(int)input.length()) -1 ;
        input_info = input.substr(segment_begin,segemnt_end-segment_begin + 1 );
        ....
        Mat qrcode = QRcodeGenerate();
        /**store all the qrcodes into to a vector*/
        my_qrcodes.push_back(qrcode);

    }
2.4 Estimate version and auto determination

If the version is 0 ,then we need to estimate length of current input string and find the most appropriate version level, which is the lowest.

2.4.1 Version estimate

First, we estimate the version roughly in 3 section.

numberversion segment
11 - 9
210 - 26
327 - 40

And we divided the length of input string into 6 Area. V(9).kanji stands for the data capacity of version level 9 with kanji mode.
If the length is smaller than V(9).kanji ,then version level must be less than 9 ,so it must be in the Section 1.
Here is chart for the rough estimate:

Area numberpossible version section number
1Section 1
2Section 1 or Section 2
3Section 2
4Section 2 or Section 3
5Section 3
6The string length is beyond the largest capacity.

在这里插入图片描述

2.4.2 Length estimate

After determining the rough version region, we can iterate over all version levels to find the lowest one that can contain the all input characters.
When we get the version level, the bits for character counter are fixed, so we can use bool QREncoder::encodeAuto(const std::string& input,vector<uint8_t>& output) to estimate the exact bits length of current input string.
Class encodingMethods such as strategy[i] records how the string from 0 to i is encoded. And Class autoEncodePerBlock records characters that are encoded in the same method .And block_load records the exact characters that are in this method. For example, blocks[i].encoding_mode == NUM, so this characters in blocks[i] are preferred to using the NUM_MODE to take up the smallest space.
在这里插入图片描述
The main concept for encodeAuto is to use the Dynamic Programming Strategy.
The result of each iteration is store in vector<encodingMethods> strategy, and the final result is stored in strategy[strategy.size()-1].
Here we have 3 ways to encode the data : QR_MODE_NUM ,QR_MODE_ALPHA and QR_MODE_BYTE. And the NUM_MODE will take the least memory and BYTE_MODE will take the most.
s t r a t e g y [ i ] = m i n 1 ⩽ k ⩽ i ( s t r a t e g y [ i − k ] + m i n ( N U M ( k + 1 , i ) , A L P H A ( k + 1 , i ) , B Y T E ( k + 1 , i ) ) ) strategy[i] =min_{ 1⩽k⩽i }(strategy[i-k] + min(NUM(k+1,i),ALPHA(k+1,i),BYTE(k+1,i)) ) strategy[i]=min1ki(strategy[ik]+min(NUM(k+1,i),ALPHA(k+1,i),BYTE(k+1,i)))

2.4.3 Find the most appropriate version

By iterating over all version in the section ,the least level will be selected as the final result.

for( i = version_begin ; i < version_end ; i++ ){
        const BlockParams* tmp_ecc_params =  ( &(version_info_database[i].ecc[ecc]));
        //std::shared_ptr<BlockParams> tmp_ecc_params =  static_cast<std::shared_ptr<BlockParams>>( &(version_info_database[i].ecc[ecc_level]));
        /**calcutlate the total data codewords of current version level*/
        data_codewords = version_info_database[i].total_codewords
                             -
                             tmp_ecc_params->ecc_codewords*(tmp_ecc_params->num_blocks_in_G1+tmp_ecc_params->num_blocks_in_G2);
        /**find the minimum version which can hold all the bit stream */
        if(data_codewords*byte_len > input_length ){
            version_index = i;
            break;
        }
    }
2.5 Translate string to bit stream

Using corresponding encoding mode to translate the input string to bit stream, which is implemented in bool QREncoder::stringToBits().
And the concepts of encoding methods are similar to the decoding ones, which are detailed explained above in Decode Step 7.

2.6 Pad bit stream

In this step, we need to fullfill the bit stream in order to make use of the most data capacity in the corresponding version level.
First, we need to calculate the remaining bits in this level.

    int total_data = version_info->total_codewords - cur_ecc_params->ecc_codewords*(cur_ecc_params->num_blocks_in_G1+cur_ecc_params->num_blocks_in_G2);
    total_data *=8;

Then if the bit string is shorter than the total number of required bits, a terminator of up to four 0s must be added to the right side of the string. If the bit string is more than four bits shorter than the required number of bits, add four 0s to the end. If the bit string is fewer than four bits shorter, add only the number of 0s that are needed to reach the required number of bits.

 if(pad_num <= 0)
        return;
    else if (pad_num <= 4){
        /** Add a Terminator of 0s (for padding)*/
        std::string pad = decToBin(0,(int)payload.size());
        loadString(pad,payload, true);
    }
    else{
        /** Add a Terminator of 0s*/
        loadString("0000",payload, true);
        ...
    }

After adding the terminator, if the number of bits in the string is not a multiple of 8, first pad the string on the right with 0s to make the string’s length a multiple of 8.

        int i = payload.size()%8;
        if(i!=0){
            /**Add More 0s to Make the Length a Multiple of 8*/
            std::string pad = decToBin(0,8-i);
            loadString(pad,payload, true);
        }

If the string is still not long enough to fill the maximum capacity, add the following bytes to the end of the string, repeating until the string has reached the maximum length:
11101100 00010001

        pad_num = total_data - (int)payload.size();
        CV_Assert(pad_num>=0);
        if( pad_num > 0 ){
            /**Add Pad Bytes if the String is Still too Short*/
            std::string pad_pattern[2] = {"11101100","00010001"};
            int num = pad_num/8;
            for(int j = 0 ; j < num ; j++){
                /**switch between two padding mode*/
                loadString(pad_pattern[j%2],payload, true);
            }
        }
2.7 Generate ECC code

ECC codewords allow QR code readers to detect and correct errors in QR codes. In this step, we create error correction codewords after encoding the data.
First, we need to break data codewords into 8-bits blocks

        /**get the data codeword*/
        Block_i = Mat(Size(block_len,1),CV_8UC1,Scalar(0));
        for(int j = 0 ;j < block_len; j++){
            Block_i.ptr(0)[block_len-1-j] = (uchar)getBits(8,payload,pay_index);
        }

Then find the corresponding polynomial generator.

    /**generator for ecc code */
    Mat G_x = polyGenerator(EC_codewords);

At last, we do a polynomial division to get the ECC code.
The polynomial generator is the divisor and the block is the dividend.

        /**get the ecc block by division*/
        Mat dividend ;
        Mat shift = Mat(Size(EC_codewords,1),CV_8UC1,Scalar(0));
        hconcat(shift,Block_i,dividend);
        ecc_i = gfPolyDiv(dividend,G_x,EC_codewords);

After these steps, the data blocks are stored in vector<Mat> data_blocks and ECC blocks are stored in vector<Mat> ecc_blocks.

2.8 Rearrange blocks

In this step, we need to interleave the data and ECC blocks, so that the qr code can be more reliable. The detailed procedure are showed as follows:

  • take the first data codeword from the first block
  • followed by the first data codeword from the second block
  • followed by the first data codeword from the third block
  • followed by the first data codeword from the fourth block
  • followed by the second data codeword from the first block
  • and so on
    This pattern is repeated, going across the blocks, until all of the data codewords have been interleaved.

After that, do the following:

  • take the first error correction codeword from the first block
  • followed by the first error correction codeword from the second block
  • followed by the first error correction codeword from the third block
  • followed by the first error correction codeword from the fourth block
  • followed by the second error correction codeword from the first block
  • and so on
    Do this until all error correction codewords have been used up.
    /**rearrange process*/
    for(int i = 0 ; i < total_codeword_num; i++ ){
        int cur_col = i / blocks ;
        int cur_row = i % blocks ;

        std::string bits;
        uint8_t tmp = 0;
        /**for data codeword */
        if(cur_col < col_border){
            /**read from data codeword*/
            if(is_not_equal && cur_col==cur_ecc_params->data_codewords_in_G2-1 && cur_row < cur_ecc_params->num_blocks_in_G1){
                /**G2 is longer than G1 , we need to ignore codeword padded before*/
                continue;
            }
            else{
                /**load in the final data array*/
                bits = decToBin(data_blocks[cur_row].ptr(0)[data_col-cur_col],8);
                tmp = data_blocks[cur_row].ptr(0)[data_col-cur_col];
            }
        }
        else{
            /**for ecc codeword */
            int index = ecc_col-(cur_col-col_border);
            /**read from ecc codeword*/
            bits = decToBin(ecc_blocks[cur_row].ptr(0)[index],8);
            tmp = ecc_blocks[cur_row].ptr(0)[index];
        }
        rearranged_data.push_back(tmp);
    }

At last we need to add remainder to fullfill all information capacity for corresponding version level.

 const int remainder_len []= {0,
                                 0,7,7,7,7,7,0,0,0,0,//!1-10
                                 0,0,0,3,3,3,3,3,3,3,//!11-20
                                 4,4,4,4,4,4,4,3,3,3,//!
                                 3,3,3,3,0,0,0,0,0,0};
    int cur_remainder_len = remainder_len[version_level];
    if(cur_remainder_len!=0)
        rearranged_data.push_back(0);
2.9 Fulfill bits to the image

The last step is to transmit the encoded bits to the pixels in QR image.

2.9.1 Write data bits into the image

First, we need to write some basic information into the reserved area. This process is similar to Decode Step 1.
Second, we need to write data bits into the pixels in a zig-zag way, which is also the same as Decode Step 1.
After doing so, the current data mat is stored in masked_data.

2.9.2 Find the best mask type

This step is used to determine the best mask.
We can iterate all 8 mask type and evaluate our mask according to the following penalty rules:

  • The first rule gives the QR code a penalty for each group of five or more same-colored modules in a row (or column) .
        /**Evaluation Condition #1*/
        for(int direction = 0 ; direction < 2 ; direction ++ ){
            /**tranverse for the vertical direction*/
            if(direction != 0)
                test_result = test_result.t();
            for(int i= 0;i<version_size;i++){
                int per_row = 0;
                for(int j= 0;j<version_size;j++) {
                    /**initial first position */
                    if(j == 0){
                        current_color = test_result.ptr(i)[j];
                        continued_num = 1;
                        continue;
                    }
                    if(current_color == test_result.ptr(i)[j]){
                        /**the same color*/
                        continued_num += 1;
                    }
                    if (current_color != test_result.ptr(i)[j] || j+1 == version_size){
                        /**the different color or into the different row */
                        current_color = test_result.ptr(i)[j];
                        /**update penalty*/
                        if(continued_num >= 5 ){
                            per_row += 3 + continued_num-5;
                        }
                        continued_num = 1;
                    }
                }
                penalty_one += per_row;
            }
        }

  • The second rule gives the QR code a penalty for each 2x2 area of same-colored modules in the matrix.
        /**Evaluation Condition #2*/
        for(int i= 0;i<version_size-1;i++){
            int per_row = 0;
            for(int j= 0;j<version_size-1;j++) {
                int color = test_result.ptr(i)[j];
                if(color == test_result.ptr(i)[j+1] &&
                   color == test_result.ptr(i+1)[j+1] &&
                   color == test_result.ptr(i+1)[j]){
                    penalty_two += 3;
                }
            }
            penalty_two += per_row;
        }
  • The third rule gives the QR code a large penalty if there are patterns that look similar to the finder patterns.
        /**Evaluation Condition #3*/
        Mat penalty_pattern[2];
        penalty_pattern[0] = (Mat_<uint8_t >(1,11)<<255,255,255,255,0,255,0,0,0,255,0);
        penalty_pattern[1] = (Mat_<uint8_t >(1,11)<<0,255,0,0,0,255,0,255,255,255,255) ;

        for(int direction = 0 ; direction < 2 ; direction ++ ){
            /**tranverse for the vertical direction*/
            if(direction != 0)
                test_result = test_result.t();
            for(int i= 0;i<version_size ;i++){
                int per_row = 0;
                for(int j= 0;j<version_size-10;j++) {
                    Mat cur_test = test_result(Range(i,i+1),Range(j,j+11));
                    for(int pattern_index = 0 ; pattern_index < 2 ; pattern_index++ ){
                        /**all zero == equal == countNonZero=0 => is_not_equal = 0*/
                        Mat diff = (penalty_pattern[pattern_index] != cur_test );
                        bool equal = ( countNonZero(diff) == 0 );
                        if(equal){
                            per_row += 40 ;
                        }
                    }
                }
                penalty_three += per_row;
            }
        }
  • The fourth rule gives the QR code a penalty if more than half of the modules are dark or light, with a larger penalty for a larger difference.
        /**Evaluation Condition #4*/
        int dark_modules = 0;
        int total_modules = 0;

        for(int i= 0;i<version_size ;i++) {
            for (int j = 0; j < version_size; j++) {
                if (test_result.ptr(i)[j] == 0)
                    dark_modules += 1;
                total_modules += 1;
            }
        }
        int modules_percent = dark_modules*100/total_modules;
        int base_num = modules_percent/5;
        penalty_four = min(abs(base_num*5-50),abs((base_num+1)*5-50))*10;
        penalty_total = penalty_one + penalty_two + penalty_three + penalty_four;
        /**update the index */
        if(penalty_total < lowest_penalty){
            best_index = cur_type;
            lowest_penalty = penalty_total;
        }
2.9.3 Mask data

This step is simple and can be done as Decoder Step 3.

2.9.4 Generate format and version information and fill the reserved area

This step is also simple, which is detailed explained in ISO IEC 18004 2015 Standard Annex C and Annex D.
Let’s take the version information generation as an example:
First, we need to get the polynomial generator.

    /**poly of format (small index stands for lower items)*/
    Mat Polynomial=Mat(Size(1,max_version_length),CV_8UC1,Scalar(0));

Second, we need to transmit the version into a 6-bit string, also get a 12-bit shift for the later division operation.

   /** Version for this level(0-5) + EC for this string( 6-17) */
    std::string version_bits = decToBin(version_level_num,6);

    /** get shift*/
    Mat binary_bit = (Mat_<uint8_t >(1,6)<<
                                         version_bits[5]-'0',version_bits[4]-'0',version_bits[3]-'0',
                                         version_bits[2]-'0',version_bits[1]-'0',version_bits[0]-'0');//!low-->high
    Mat shift = Mat(Size(12,1),CV_8UC1,Scalar(0));
    hconcat(shift,binary_bit,Polynomial);

Third, use the fixed version generator to divide the polynomial generator. And the result is ECC code which can be the low 12 bits as the final version info bit stream.

 /**length of format_generator is 13 not max_version_length*/
    Mat format_generator  = (Mat_<uint8_t >(1,13)<<1,0,1,0,0, 1,0,0,1,1, 1,1,1);//!low-->high

    /**get ecc by division*/
    Mat ecc_code = gfPolyDiv(Polynomial,format_generator,12);
    hconcat(ecc_code,binary_bit,version_array);
3.Regression Test

There are three regression tests for the code changes, including decoding test, encoding test and encoder-decoder pipeline.
All the testdata should be the original size with 2-pixels-width light borders.
PS: for 3.1 and 3.2:
The test image are stored in opencv_extra/testdata/cv/qrcode/encode. And you can add new images into this folder and do not forget to update the testdata name stored in std::string qrcode_images_name[].
You can uncomment the macro #define UPDATE_TEST_DATA to update the .json file.

3.1 Decoding Regression Test

The concept for Objdetect_QRCode_Decode is explained as follows:
1.Get the information in the .json file for current image, and store the encoded string in original_info
2.Decode current image using this decoder and store the decoded result in decoded_info.
3.Compare original_info and decoded_info .

3.2 Encoder-decoder pipeline

All testdata in this file must be in the auto version ,auto mode and auto mask generated by this encoder.
The concept for Objdetect_QRCode_Encode is explained as follows:
1.Get the information in the .json file for current image. The encoded string is stored in original_info and the current image is stored in Mat src.
2.Generate the QR image using original_info as input string, which is stored in Mat result.
3.Compare Mat src.and Mat result pixel by pixel.

3.3 Decoding&Encoding Shuffle Regression Test

The test image are stored in opencv_extra/testdata/cv/qrcode/decode_encode.There are two .json files.
The symbols_sets.json file contains symbols sets for each mode. Example:
{ “mode”: “numeric”, “symbols_set”: “0123456789” }
The capacity.json file contains information about capacity for each version and ECC level.

The concept for Objdetect_QRCode_Encode_Decode is explained as follows:
1.Create variables for mode (std::string cur_mode) , version (int j) and ECC level (int m) .
2.Iterate over these variables and get combination of mode, version and level.

                for (size_t index = 0; index < mode_count; index++){
                    /**loop each mode*/
                    FileNode config = mode_list[(int)index];
                    /**find corresponding symbol set*/
                    std::string cur_mode = config["mode"];
                    std::string symbol_set = config["symbols_set"];
                    for(int j = min_version ; j <= test_max_version ; j ++ ){
                        /**Loop each version level (1:7)*/
                        FileNode capa_config = capacity_list[(int)(j-1)];
                        for(int m = 0 ; m <= max_ecc ; m ++ ){
                        ...
                        }
                            /**loop each ecc level*/

3.Find in capacity.json file max capacity(const int cur_capacity = capa_config["ecc_level"][m];) for combination of version and level.

4.Find in symbols_sets.json set for current mode and get random number of symbols (in range from 1 to max capacity) in random order as the input string for encoder.

                            /**generate the input string **/
                            std::string input_info = symbol_set;
                            std::random_shuffle(input_info.begin(),input_info.end());
                            if((int)input_info.length() > cur_capacity){
                                input_info = input_info.substr(0,cur_capacity-1);
                            }

5.Generate qrcodes with all these parameters, which are stored in vector<Mat> qrcodes; and then use decoder to
decode this qrcode.
6.Compare the decoder-output string and encoder-input string.

Link to my PR : https://github.com/opencv/opencv/pull/17889
Written by Zheng Qiushi

2020.0926

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值