import{_ as r,c as s,a as i,b as t,d,e,f as u,r as a,o}from"./app-CdF2Bty1.js";const b="/images/2014/08/hufman7.gif",p={},g={class:"gridtable",style:{width:"600px","text-align":"center"}},x={style:{width:"500px"}},h={class:"gridtable",align:"center",style:{width:"600px","text-align":"center"}},y={style:{width:"500px"},colspan:"8"},m={class:"gridtable",align:"center",style:{width:"600px","text-align":"center"}},f={style:{width:"500px"},colspan:"8"},w={class:"gridtable",align:"center",style:{width:"800px","text-align":"center"}},B={style:{width:"700px"},colspan:"8"},E={class:"gridtable",align:"center",style:{width:"800px","text-align":"center"}},D={style:{width:"700px"},colspan:"8"},O={class:"gridtable",style:{width:"1000px","text-align":"center"}},A={style:{width:"900px"},colspan:"14"},C={colspan:"14"},J={colspan:"14"};function P(G,l){const n=a("center");return o(),s("div",null,[l[34]||(l[34]=i(`
JPEG压缩的最后一步是对数据进行哈弗曼编码(Huffman coding),哈弗曼几乎是所有压缩算法的基础,它的基本原理是根据数据中元素的使用频率,调整元素的编码长度,以得到更高的压缩比。 举个例子,比如下面这段数据
AABCBABBCDBBDDBAABDBBDABBBBDDEDBD
这段数据里面包含了33个字符,每种字符出现的次数统计如下
如果我们用我们常见的定长编码,每个字符都是3个bit。 那么这段文字共需要3*33=99个bit来保存,但如果我们根据字符出现的概率来编码,也就是出现频率较高的字符,使用较短的编码,如下:
那么这段文字共需要3*6+1*15+4*2+2*9+4*1=63个bit来保存,压缩比为63%,哈弗曼编码一般都是使用二叉树来生成的,这样得到的编码符合前缀规则,也就是较短的编码不能够是较长编码的前缀,比如字符'B'使用的编码是'0',那么其他字符的编码的第一个字符都不能是‘0’。 上面这个编码实例,就是由下面的这颗二叉树生成的。
我们回到JPEG压缩上,回顾上一节的内容,经过数据量化,我们现在要处理的数据是一串一维数组,举例如下:
',14)),t("table",g,[t("tbody",null,[t("tr",null,[l[1]||(l[1]=t("th",{style:{width:"100px","text-align":"left"}},"①原始数据",-1)),t("td",x,[e(n,null,{default:u(()=>l[0]||(l[0]=[d("35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0")])),_:1})])])])]),l[35]||(l[35]=d(" 在实际的压缩过程中,数据中的0出现的概率非常高,所以首先要做的事情,是使用RLE编码对其中的0进行处理,把数据中的非零的数据,以及数据前面0的个数作为一个处理单元。 ")),t("table",h,[t("tbody",null,[t("tr",null,[l[3]||(l[3]=t("th",{style:{width:"100px","text-align":"left"}},"①原始数据",-1)),t("td",y,[e(n,null,{default:u(()=>[l[2]||(l[2]=d("35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0")),e(n)]),_:1})])]),l[4]||(l[4]=t("tr",null,[t("th",{style:{"text-align":"left"}},[t("strong",null,"②RLE编码")]),t("td",null,"35"),t("td",null,"7"),t("td",null,"0,0,0,-6"),t("td",null,"-2"),t("td",null,"0,0,-9"),t("td",null,"0,0,…,0,8"),t("td",null,"0,0,…,0")],-1))])]),l[36]||(l[36]=d(" 如果其中某个单元的0的个数超过16,则需要分成每16个一组,如果最后一个单元全都是0,则使用特殊字符“EOB”表示,EOB意思就是“后面的数据全都是0”, ")),t("table",m,[t("tbody",null,[t("tr",null,[l[6]||(l[6]=t("th",{style:{width:"100px","text-align":"left"}},"①原始数据",-1)),t("td",f,[e(n,null,{default:u(()=>[l[5]||(l[5]=d("35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0")),e(n)]),_:1})])]),l[7]||(l[7]=t("tr",null,[t("th",{style:{"text-align":"left"},rowspan:"3"},[t("strong",null,"②RLE编码")]),t("td",null,"35"),t("td",null,"7"),t("td",null,"0,0,0,-6"),t("td",null,"-2"),t("td",null,"0,0,-9"),t("td",{colspan:"2"},"0,0,…,0,8"),t("td",null,"0,0,…,0")],-1)),l[8]||(l[8]=t("tr",null,[t("td",null,"35"),t("td",null,"7"),t("td",null,"0,0,0,-6"),t("td",null,"-2"),t("td",null,"0,0,-9"),t("td",null,"0,0,…,0"),t("td",null,"0,0,8"),t("td",null,"0,0,…,0")],-1)),l[9]||(l[9]=t("tr",null,[t("td",null,"(0,35)"),t("td",null,"(0,7)"),t("td",null,"(3,-6)"),t("td",null,"(0,-2)"),t("td",null,"(2,-9)"),t("td",null,"(15,0)"),t("td",null,"(2,8)"),t("td",null,"EOB")],-1))])]),l[37]||(l[37]=i(' 其中(15,0)表示15+1也就是16个0,接下来我们要处理的是括号里右面的数字,这个数字的取值范围在-2047~2047之间,JPEG提供了一张标准的码表用于对这些数字编码: Value Size Bits 0 0 – -1 1 1 0 1 -3,-2 2,3 2 00,01 10,11 -7,-6,-5,-4 4,5,6,7 3 000,001,010,011 100,101,110,111 -15,…,-8 8,…,15 4 0000,…,0111 1000,…,1111 -31,…,-16 16,…,31 5 0 0000,…,0 1111 1 0000,…,1 1111 -63,…,-32 32,…,63 6 00 0000,… …,11 1111 -127,…,-64 64,…,127 7 000 0000,… …,111 1111 -255,…,-128 128,…,255 8 0000 0000,… …,1111 1111 -511,…,-256 256,…,511 9 0 0000 0000,… …,1 1111 1111 -1023,…,-512 512,…,1023 10 00 0000 0000,… …,11 1111 1111 -2047,…,-1024 1024,…,2047 11 000 0000 0000,… …,111 1111 1111
举例来说,第一个单元中的“35”这个数字,在表中的位置是长度为6的那组,所对应的bit码是“100011”,而“-6”的编码是”001″,由于这种编码附带长度信息,所以我们的数据变成了如下的格式。 ',3)),t("table",w,[t("tbody",null,[t("tr",null,[l[11]||(l[11]=t("th",{style:{width:"100px","text-align":"left"}},"①原始数据",-1)),t("td",B,[e(n,null,{default:u(()=>[l[10]||(l[10]=d("35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0")),e(n)]),_:1})])]),l[12]||(l[12]=t("tr",null,[t("th",{style:{"text-align":"left"},rowspan:"3"},[t("strong",null,"②RLE编码")]),t("td",null,"35"),t("td",null,"7"),t("td",null,"0,0,0,-6"),t("td",null,"-2"),t("td",null,"0,0,-9"),t("td",{colspan:"2"},"0,0,…,0,8"),t("td",null,"0,0,…,0")],-1)),l[13]||(l[13]=t("tr",{style:{padding:"3px 3px"}},[t("td",null,"35"),t("td",null,"7"),t("td",null,"0,0,0,-6"),t("td",null,"-2"),t("td",null,"0,0,-9"),t("td",null,"0,0,…,0"),t("td",null,"0,0,8"),t("td",null,"0,0,…,0")],-1)),l[14]||(l[14]=t("tr",null,[t("td",null,"(0,35)"),t("td",null,"(0,7)"),t("td",null,"(3,-6)"),t("td",null,"(0,-2)"),t("td",null,"(2,-9)"),t("td",null,"(15,0)"),t("td",null,"(2,8)"),t("td",null,"EOB")],-1)),l[15]||(l[15]=t("tr",null,[t("th",{style:{"text-align":"left"}},[t("strong",null,"③BIT编码")]),t("td",null,[d("(0,6, "),t("em",null,[t("b",null,"100011")]),d(")")]),t("td",null,[d("(0,3, "),t("em",null,[t("b",null,"111")]),d(")")]),t("td",null,[d("(3,3, "),t("em",null,[t("b",null,"001")]),d(")")]),t("td",null,[d("(0,2, "),t("em",null,[t("b",null,"01")]),d(")")]),t("td",null,[d("(2,4, "),t("em",null,[t("b",null,"0110")]),d(")")]),t("td",null,"(15,-)"),t("td",null,[d("(2,4, "),t("em",null,[t("b",null,"1000")]),d(")")]),t("td",null,"EOB")],-1))])]),l[38]||(l[38]=d(" 括号中前两个数字分都在0~15之间,所以这两个数可以合并成一个byte,高四位是前面0的个数,后四位是后面数字的位数。 ")),t("table",E,[t("tbody",null,[t("tr",null,[l[17]||(l[17]=t("th",{style:{width:"100px","text-align":"left"}},"①原始数据",-1)),t("td",D,[e(n,null,{default:u(()=>[l[16]||(l[16]=d("35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0")),e(n)]),_:1})])]),l[18]||(l[18]=t("tr",null,[t("th",{style:{"text-align":"left"},rowspan:"3"},[t("strong",null,"②RLE编码")]),t("td",null,"35"),t("td",null,"7"),t("td",null,"0,0,0,-6"),t("td",null,"-2"),t("td",null,"0,0,-9"),t("td",{colspan:"2"},"0,0,…,0,8"),t("td",null,"0,0,…,0")],-1)),l[19]||(l[19]=t("tr",{style:{padding:"3px 3px"}},[t("td",null,"35"),t("td",null,"7"),t("td",null,"0,0,0,-6"),t("td",null,"-2"),t("td",null,"0,0,-9"),t("td",null,"0,0,…,0"),t("td",null,"0,0,8"),t("td",null,"0,0,…,0")],-1)),l[20]||(l[20]=t("tr",null,[t("td",null,"(0,35)"),t("td",null,"(0,7)"),t("td",null,"(3,-6)"),t("td",null,"(0,-2)"),t("td",null,"(2,-9)"),t("td",null,"(15,0)"),t("td",null,"(2,8)"),t("td",null,"EOB")],-1)),l[21]||(l[21]=t("tr",null,[t("th",{style:{"text-align":"left"},rowspan:"2"},[t("strong",null,"③BIT编码")]),t("td",null,[d("(0,6, "),t("em",null,[t("b",null,"100011")]),d(")")]),t("td",null,[d("(0,3, "),t("em",null,[t("b",null,"111")]),d(")")]),t("td",null,[d("(3,3, "),t("em",null,[t("b",null,"001")]),d(")")]),t("td",null,[d("(0,2, "),t("em",null,[t("b",null,"01")]),d(")")]),t("td",null,[d("(2,4, "),t("em",null,[t("b",null,"0110")]),d(")")]),t("td",null,"(15,-)"),t("td",null,[d("(2,4, "),t("em",null,[t("b",null,"1000")]),d(")")]),t("td",null,"EOB")],-1)),l[22]||(l[22]=t("tr",null,[t("td",null,[d("(0x6,"),t("em",null,[t("b",null,"100011")]),d(")")]),t("td",null,[d("(0x3,"),t("em",null,[t("b",null,"111")]),d(")")]),t("td",null,[d("(0x33,"),t("em",null,[t("b",null,"001")]),d(")")]),t("td",null,[d("(0x2,"),t("em",null,[t("b",null,"01")]),d(")")]),t("td",null,[d("(0x24,"),t("em",null,[t("b",null,"0110")]),d(")")]),t("td",null,"(0xF0,-)"),t("td",null,[d("(0x24,"),t("em",null,[t("b",null,"1000")]),d(")")]),t("td",null,"EOB")],-1))])]),l[39]||(l[39]=i(' 对于括号前面的数字的编码,就要使用到我们提到的哈弗曼编码了,比如下面这张表,就是一张针对数据中的第一个单元,也就是直流(DC)部分的哈弗曼表,由于直流部分没有前置的0,所以取值范围在0~15之间。 Length Value Bits 3 bits 04 05 03 02 06 01 00 (EOB) 000 001 010 011 100 101 110 4 bits 07 1110 5 bits 08 1111 0 6 bits 09 1111 10 7 bits 0A 1111 110 8 bits 0B 1111 1110
举例来说,示例中的DC部分的数据是0x06,对应的二进制编码是“100”,而对于后面的交流部分,取值范围在0~255之间,所以对应的哈弗曼表会更大一些 Length Value Bits 2 bits 01 02 00 01 3 bits 03 100 4 bits 00 (EOB) 04 11 1010 1011 1100 5 bits 05 12 21 1101 0 1101 1 1110 0 6 bits 31 41 1110 10 1110 11 … … … 12 bits 24 33 62 72 1111 1111 0100 1111 1111 0101 1111 1111 0110 1111 1111 0111 15 bits 82 1111 1111 1000 000 16 bits 09 … FA 1111 1111 1000 0010 … 1111 1111 1111 1110
这样经过哈弗曼编码,并且序列化后,最终数据成为如下形式 ',5)),t("table",O,[t("tbody",null,[t("tr",null,[l[24]||(l[24]=t("th",{style:{width:"100px","text-align":"left"}},"①原始数据",-1)),t("td",A,[e(n,null,{default:u(()=>[l[23]||(l[23]=d("35,7,0,0,0,-6,-2,0,0,-9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,…,0")),e(n)]),_:1})])]),l[28]||(l[28]=t("tr",null,[t("th",{rowspan:"3",style:{"text-align":"left"}},[t("strong",null,"②RLE编码")]),t("td",{colspan:"2"},"35"),t("td",{colspan:"2"},"7"),t("td",{colspan:"2"},"0,0,0,-6"),t("td",{colspan:"2"},"-2"),t("td",{colspan:"2"},"0,0,-9"),t("td",{colspan:"3"},"0,0,…,0,8"),t("td",null,"0,0,…,0")],-1)),l[29]||(l[29]=t("tr",{style:{padding:"3px 3px"}},[t("td",{colspan:"2"},"35"),t("td",{colspan:"2"},"7"),t("td",{colspan:"2"},"0,0,0,-6"),t("td",{colspan:"2"},"-2"),t("td",{colspan:"2"},"0,0,-9"),t("td",null,"0,0,…,0"),t("td",{colspan:"2"},"0,0,8"),t("td",null,"0,0,…,0")],-1)),l[30]||(l[30]=t("tr",null,[t("td",{colspan:"2"},"(0,35)"),t("td",{colspan:"2"},"(0,7)"),t("td",{colspan:"2"},"(3,-6)"),t("td",{colspan:"2"},"(0,-2)"),t("td",{colspan:"2"},"(2,-9)"),t("td",null,"(15,0)"),t("td",{colspan:"2"},"(2,8)"),t("td",null,"EOB")],-1)),l[31]||(l[31]=t("tr",null,[t("th",{style:{"text-align":"left"},rowspan:"2"},[t("strong",null,"③BIT编码")]),t("td",{colspan:"2"},[d("(0,6, "),t("em",null,[t("b",null,"100011")]),d(")")]),t("td",{colspan:"2"},[d("(0,3, "),t("em",null,[t("b",null,"111")]),d(")")]),t("td",{colspan:"2"},[d("(3,3, "),t("em",null,[t("b",null,"001")]),d(")")]),t("td",{colspan:"2"},[d("(0,2, "),t("em",null,[t("b",null,"01")]),d(")")]),t("td",{colspan:"2"},[d("(2,4, "),t("em",null,[t("b",null,"0110")]),d(")")]),t("td",null,"(15,-)"),t("td",{colspan:"2"},[d("(2,4, "),t("em",null,[t("b",null,"1000")]),d(")")]),t("td",null,"EOB")],-1)),l[32]||(l[32]=t("tr",null,[t("td",{colspan:"2"},[d("(0x6,"),t("em",null,[t("b",null,"100011")]),d(")")]),t("td",{colspan:"2"},[d("(0x3,"),t("em",null,[t("b",null,"111")]),d(")")]),t("td",{colspan:"2"},[d("(0x33,"),t("em",null,[t("b",null,"001")]),d(")")]),t("td",{colspan:"2"},[d("(0x2,"),t("em",null,[t("b",null,"01")]),d(")")]),t("td",{colspan:"2"},[d("(0x24,"),t("em",null,[t("b",null,"0110")]),d(")")]),t("td",null,"0xF0"),t("td",{colspan:"2"},[d("(0x24,"),t("em",null,[t("b",null,"1000")]),d(")")]),t("td",null,"EOB")],-1)),l[33]||(l[33]=t("tr",null,[t("th",{style:{"text-align":"left"}},[t("strong",null,"④哈弗曼编码")]),t("td",null,[t("em",null,[t("b",null,"100")])]),t("td",null,[t("em",null,[t("b",null,"100011")])]),t("td",null,[t("em",null,[t("b",null,"100")])]),t("td",null,[t("em",null,[t("b",null,"111")])]),t("td",null,[t("em",null,[t("b",null,"1111 1111 0101")])]),t("td",null,[t("em",null,[t("b",null,"001")])]),t("td",null,[t("em",null,[t("b",null,"01")])]),t("td",null,[t("em",null,[t("b",null,"01")])]),t("td",null,[t("em",null,[t("b",null,"1111 1111 0100")])]),t("td",null,[t("em",null,[t("b",null,"0110")])]),t("td",null,[t("em",null,[t("b",null,"1111 1111 001")])]),t("td",null,[t("em",null,[t("b",null,"1111 1111 0100")])]),t("td",null,[t("em",null,[t("b",null,"1000")])]),t("td",null,[t("em",null,[t("b",null,"1010")])])],-1)),t("tr",null,[l[26]||(l[26]=t("th",{style:{"text-align":"left"},rowspan:"2"},"⑤序列化",-1)),t("td",C,[e(n,null,{default:u(()=>[l[25]||(l[25]=d("100100011100111111111110101001010111111111010001101111111100111111111010010001010")),e(n)]),_:1})])]),t("tr",null,[t("td",J,[e(n,null,{default:u(()=>[l[27]||(l[27]=d("91 CF FE A5 7F D1 BF CF FA 45")),e(n)]),_:1})])])])])])}const L=r(p,[["render",P],["__file","JPEG004.html.vue"]]),j=JSON.parse('{"path":"/blog/2025/02/JPEG004.html","title":"JPEG算法解密(四)","lang":"zh-CN","frontmatter":{"title":"JPEG算法解密(四)","tags":"压缩 图像 程序 算法"},"headers":[{"level":3,"title":"步骤五:哈弗曼编码","slug":"步骤五-哈弗曼编码","link":"#步骤五-哈弗曼编码","children":[]}],"git":{"contributors":[{"name":"thejinchao","username":"thejinchao","email":"thejinchao@gmail.com","commits":1,"url":"https://github.com/thejinchao"}]},"filePathRelative":"blog/2025/02/JPEG004.md"}');export{L as comp,j as data};