JPG学习笔记5(附完整代码)

  JPG压缩的第4步是哈夫曼编码。下面主要介绍JPEG是如果进行哈夫曼编码的。

图片引用自"Compressed Image File Formats JPEG, PNG, GIF, XBM, BMP - John Miano"[1]

1.AC数据的哈夫曼Symbol.

对于AC数据而言,需要编码的前4位代表这个数据之前有多少个0,后4位代表当前值的Magnitude Value。

AC数据的编码是以ZigZag的顺序进行的。

如下图为例,从1开始前面有0个0, 数值大小为1, magnitude value为1,需要编码的symbol为0x01;

接着走到3处,前面有5个0,数值大小为3, magnitude value 为2,需要编码的symbol为0x52;

以此类推:

唯一有的2个额外的情况

0x00代表后面的数据都为0

0xF0代表16个0

总共的symbol数量 = (为0的个数)16 * 10(不同的maginitude) + 2 (特殊情况) = 162。

2.DC数据的哈夫曼Symbol

DC数据存的是difference,即当前Block的DC值减去上一个Block的DC值。

如下可知DC symbol总共有12个

3.JPEG默认哈夫曼编码

JPEG提供了默认的huffmanTable(emprically good)[2],如下

引用"https://www.impulseadventure.com/photo/optimized-jpeg.html"

也可以自己根据图片生成huffmanCode,代码如下

void JPG::huffmanCoding() {    /*****************************************创建yDC_Table*********************************************/    int lastYDC = 0;    uint componentID = 1;    //创建YDC_Table    for (uint i = 0; i < mcuHeight; i++) {        for (uint j = 0; j < mcuWidth; j++) {            MCU& currentMCU = data[i * mcuWidth + j];            //iterate over 每一个component Y, cb cr            //遍历block            for(uint ii = 0; ii < getVerticalSamplingFrequency(componentID); ii++) {                for(uint jj = 0; jj < getHorizontalSamplingFrequency(componentID); jj++) {                    Block& currentBlock = currentMCU[componentID][ii * getHorizontalSamplingFrequency(componentID) + jj];                    int difference = currentBlock[0] - lastYDC; //DC分量是encode difference                    lastYDC = currentBlock[0];                    byte symbol = getBinaryLengthByValue(difference); //Y的2进制的长度就是symbol的值                    yDC.countOfSymbol[symbol]++;                }            }        }    }    yDC.generateHuffmanCode();      /*****************************************创建 yAC_Table*********************************************/    for (uint i = 0; i < mcuHeight; i++) {        for (uint j = 0; j < mcuWidth; j++) {            MCU& currentMCU = data[i * mcuWidth + j];            //遍历block            for(uint ii = 0; ii < getVerticalSamplingFrequency(componentID); ii++) {                for(uint jj = 0; jj < getHorizontalSamplingFrequency(componentID); jj++) {                    Block& currentBlock = currentMCU[componentID][ii * getHorizontalSamplingFrequency(componentID) + jj];                    uint numZero = 0;                    for(uint k = 1; k < 64; k++) {                        if(currentBlock[ZIG_ZAG[k]] == 0) {                            numZero++;                            if(numZero == 16) {                                if(isRemainingAllZero(currentBlock, k + 1)) {                                    yAC.countOfSymbol[0x00]++;                                    break;                                } else {                                    yAC.countOfSymbol[0xF0]++;//16个0                                    numZero = 0;                                }                            }                        } else {                            byte lengthOfCoefficient = getBinaryLengthByValue(currentBlock[ZIG_ZAG[k]]);                            byte symbol = (numZero << 4) + lengthOfCoefficient;                            yAC.countOfSymbol[symbol]++;                            numZero = 0;                        }                    }                }            }        }    }    yAC.generateHuffmanCode();    /*****************************************创建chromaDC_Table*********************************************/    int lastChromaDC = 0;    for(uint componentID = 2; componentID <=3; componentID++) {        for (uint i = 0; i < mcuHeight; i++) {            for (uint j = 0; j < mcuWidth; j++) {                MCU& currentMCU = data[i * mcuWidth + j];                //iterate over 每一个component Y, cb cr                //遍历block                for(uint ii = 0; ii < getVerticalSamplingFrequency(componentID); ii++) {                    for(uint jj = 0; jj < getHorizontalSamplingFrequency(componentID); jj++) {                        Block& currentBlock = currentMCU[componentID][ii * getHorizontalSamplingFrequency(componentID) + jj];                        int difference = currentBlock[0] - lastChromaDC; //DC分量是encode difference                        lastChromaDC = currentBlock[0];                        byte symbol = getBinaryLengthByValue(difference); //Y的2进制的长度就是symbol的值                        chromaDC.countOfSymbol[symbol]++;                    }                }            }        }    }    chromaDC.generateHuffmanCode();    /*****************************************创建chromaAC_Table*********************************************/    for(uint componentID = 2; componentID <=3; componentID++) {        for (uint i = 0; i < mcuHeight; i++) {            for (uint j = 0; j < mcuWidth; j++) {                MCU& currentMCU = data[i * mcuWidth + j];                //遍历block                for(uint ii = 0; ii < getVerticalSamplingFrequency(componentID); ii++) {                    for(uint jj = 0; jj < getHorizontalSamplingFrequency(componentID); jj++) {                        Block& currentBlock = currentMCU[componentID][ii * getHorizontalSamplingFrequency(componentID) + jj];                        uint numZero = 0;                        for(uint k = 1; k < 64; k++) {                            if(currentBlock[ZIG_ZAG[k]] == 0) {                                numZero++;                                if(numZero == 16) {                                    if(isRemainingAllZero(currentBlock, k + 1)) {                                        chromaAC.countOfSymbol[0x00]++;                                        break;                                    } else {                                        chromaAC.countOfSymbol[0xF0]++;//16个0                                        numZero = 0;                                    }                                }                            } else {                                byte lengthOfCoefficient = getBinaryLengthByValue(currentBlock[ZIG_ZAG[k]]);                                byte symbol = (numZero << 4) + lengthOfCoefficient;                                chromaAC.countOfSymbol[symbol]++;                                numZero = 0;                            }                        }                    }                }            }        }    }    chromaAC.generateHuffmanCode();}
void generateHuffmanCode() {        std::vector<LinkedSymbol> symbols;        //遍历每个出现的symbol, add to vectors        for(uint symbol = 0; symbol < 256; symbol++) {            if(countOfSymbol[symbol] == 0)                 continue;            Symbol* s = new Symbol(symbol, countOfSymbol[symbol], 0, nullptr);            LinkedSymbol linkedSymbol;            linkedSymbol.symbol = s;            linkedSymbol.weight = s->weight;            symbols.push_back(linkedSymbol);        }                        // FF是一个不会出现的symbol,作为我们的dummy symbol 防止one bit stream 的出现  比如11111, 这样就可以防止compressdata中出现FF的可能        Symbol* dummySymbol = new Symbol(0xFF, 1, 0, nullptr);         LinkedSymbol dymmyLinkedSymbol;        dymmyLinkedSymbol.symbol = dummySymbol;        dymmyLinkedSymbol.weight = dummySymbol->weight;        symbols.push_back(dymmyLinkedSymbol);                        //合并的过程        while(symbols.size() != 1) {                        //leastWeight            LinkedSymbol least = getLeastWeightLinkedSymbol(symbols);            //second Least Weight            LinkedSymbol second = getLeastWeightLinkedSymbol(symbols);            //add two weights            least.weight = least.weight + second.weight;            //linked two linkedsymbols;            Symbol* temp = second.symbol;            while(temp->nextSymbol != nullptr)                temp = temp->nextSymbol;            temp->nextSymbol = least.symbol;            least.symbol = second.symbol;            //把每个symbol加1 codeLength,并且加入到             for(auto i = least.symbol; i != nullptr; i = i->nextSymbol) {                i->codeLength++;            }            symbols.push_back(least);        }        //放入sortedSymbols        for(Symbol* i = symbols[0].symbol; i != nullptr; i = i->nextSymbol) {            sortedSymbol.push_back(*i);        }        //排序,并且把dummy symbol 放在最后面;        std::sort(sortedSymbol.begin(), sortedSymbol.end(), comp);        //释放内存        Symbol* temp = symbols[0].symbol;        while(temp != nullptr) {            auto t = temp->nextSymbol;            delete temp;            temp = t;        }        //长度为n的code的个数        //生成codeLengthCount for each codeLength;        for (auto it = sortedSymbol.cbegin(); it != sortedSymbol.cend(); it++) {            codeCountOfLength[it->codeLength]++;        }        //规定codeLength不能大于16, 套用书上的方法实现了一下        for(uint ii = 32; ii >= 17; ii--) {            while(codeCountOfLength[ii] != 0) {                uint jj = ii - 2;                while(codeCountOfLength[jj] == 0)                    jj--;                codeCountOfLength[ii] = codeCountOfLength[ii] - 2;                codeCountOfLength[ii - 1] = codeCountOfLength[ii - 1] + 1;                codeCountOfLength[jj + 1] = codeCountOfLength[jj + 1] + 2;                codeCountOfLength[jj] = codeCountOfLength[jj] - 1;            }        }        uint index = 1; //codeLength赋值回去        for (auto it = sortedSymbol.begin(); it != sortedSymbol.end(); it++) {            if(codeCountOfLength[index] != 0) {                it->codeLength = index;                codeCountOfLength[index]--;            } else {                index++;                it--;            }        }                //生成huffmanCode for each symbol        uint huffmanCode = 0;        uint currentLength = 1;        for (auto it = sortedSymbol.begin(); it != sortedSymbol.end(); it++) {            if(currentLength == it->codeLength) {                it->code = huffmanCode++;                codeOfSymbol[it->symbol] = it->code;                codeLengthOfSymbol[it->symbol] = it->codeLength;            } else {                huffmanCode = huffmanCode << 1;                currentLength++;                it--;            }        }    }

全部代码在https://github.com/Cheemion/JPEG_COMPRESS/tree/main/Day5

完结

Thanks for reading.

>>>> (JPG学习笔记6)待续


参考资料

[1]https://github.com/Cheemion/JPEG_COMPRESS/blob/main/resource/Compressed%20Image%20File%20Formats%20JPEG%2C%20PNG%2C%20GIF%2C%20XBM%2C%20BMP%20-%20John%20Miano.pdf

[2]https://www.impulseadventure.com/photo/optimized-jpeg.html

(0)

相关推荐