jpeg的压缩分为四个步骤
首先明确jpeg一般是将bmp转为jpeg格式的,那么我们就需要清楚bmp和jpeg的文件架构
以下是bmp的文件架构
项目 |
---|
位图文件头 |
位图信息头 |
彩色表/调色板 |
位图数据 |
1.bmp文件头
2位图信息头
3彩色表/调色板
索引:(蓝,绿,红,Alpha)
0号:(fe,fa,fd,00)
1号:(fd,f3,fc,00)
2号:(f4,f3,fc,00)
3号:(fc,f2,f4,00)
4号:(f6,f2,f2,00)
5号:(fb,f9,f6,00) 等等。
4位图数据
以下是jpeg的文件架构
四个步骤
1将RGB转化为yCbCR
YCbCR中的y代表亮度,cb,cr代表绿色和红色的色差,用这种表示方法能够很好的将图的细节区分开来
转换公式为
这一部分的代码如下:
for(int i = height - 1; i >= 0; i--)
{
for(int j = 0; j < width; j++)
{
int blue = dis.read();
int green = dis.read();
int red = dis.read();
int y = (int) (0.299 * (double)red + 0.587 * (double)green + 0.114 * (double)blue) -128;
imageY[i][j] = y;
imagecb[i][j] = (int) (-0.1687 * (double)red - 0.3313 * (double)green + 0.5 * (double)blue);
imagecr[i][j] = (int) (0.5 * (double)red - 0.4187 * (double)green - 0.0813 * (double)blue);
}
}
经过j这一步我们能得到3张二维矩阵,分别是y(亮度)矩阵,cb(绿色)矩阵,cr(红色)矩阵
2.DCT(离散余弦变换变换)
为了能更好的处理信息,我们需要将yCbCR进行二维的离散余弦变换,公式如下:
转换为之后我们就会发现图像中的低频的信息集中在矩阵的左上角,高频信息集中在右下角,而我们人眼是对低频信息敏感的。
这是对lena左上角的Y矩阵dct转换过程
代码:
public static int[] Dct(int posy, int posx, int preData[][], int aux[])
{
int res[] = new int[70];
for(int v=0; v<8; v++)
{
for(int u=0; u<8; u++)
{
double alpha_u = (u==0) ? 1/Math.sqrt(8.0) : 0.5;
double alpha_v = (v==0) ? 1/Math.sqrt(8.0) : 0.5;
double sum = 0;
for(int x=0; x<8; x++)
{
for(int y=0; y<8; y++)
{
double temp = preData[posy + y][posx + x];
temp *= Math.cos((2*x+1)*u*PI/16.0);
temp *= Math.cos((2*y+1)*v*PI/16.0);
sum += temp;
}
}
sum *= alpha_u*alpha_v/aux[ZigZag[v][u]];
res[ZigZag[v][u]] = (int)sum;
}
}
return res;
}
3.数据量化
这一部就是进一步将数据进行简略,为了使得处理后的数据更真实jpeg给我们提供了两张量化表,一张是标准亮度量化表,一张是标准色差量化表
公式如下
这一步的代码我写在了dct里面.
4.哈夫曼
将数据进一步处理后按照不同信息的出现频率不同重新编码
我认为这一步是最难的,需要处理很多细节,比如根据jpeg提供的标准生成哈夫曼表
我们首先要将上一步的数据用zigzag扫描变成一维矩阵,用zigzag的好处是能使数据中的低频信息集中在一维矩阵的前面。
然后对一位矩阵进行分块。将矩阵中非零数据以及前面的0作为一个处理单元,如果某个单元的0的个数超过16,则是16为一个单元。接着用jpeg提供的一张标准的码表来对数据进行编码,这里主要是对非0数据进行编码
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 |
然后对0的个数和非0数据编码的长度进行编码,这里用到了jpeg提供的哈夫曼编码;
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 |
代码:
public BitPack[] HandleHuff(int data[], int preDc[],BitPack[] dataDc,BitPack[] dataAc)
{
BitPack EOBFlag = dataAc[0X00];
BitPack MoreZeroFlag = dataAc[0xF0];
BitPack outRes[] = new BitPack[256];
int index = 0,endPos = 63;
//------
int dcRes = data[0] - preDc[0];
preDc[0] = data[0];
if(dcRes == 0)
{
outRes[index] = new BitPack();
outRes[index] = dataDc[0];
index++;
}
else
{
BitPack bp = ValueToBitPack(dcRes);
outRes[index] = new BitPack();
outRes[index] = dataDc[bp.len]; index++;
outRes[index] = new BitPack();
outRes[index] = bp;index++;
}
while((endPos > 0) && (data[endPos] == 0)) endPos--;
for(int i=1; i<=endPos; )
{
int startPos = i;
while((data[i] == 0) && (i <= endPos)) i++;
int zeroCounts = i - startPos;
if (zeroCounts >= 16)
{
for (int j=1; j<=zeroCounts/16; j++)
outRes[index] = new BitPack();
outRes[index] = MoreZeroFlag;index++;
zeroCounts = zeroCounts%16;
}
BitPack bp = ValueToBitPack(data[i]);
outRes[index] = new BitPack();
outRes[index] = dataAc[(zeroCounts << 4) | bp.len];index++;
outRes[index] = new BitPack();
outRes[index] = bp;index++;
i++;
}
if (endPos != 63)
{
outRes[index] = EOBFlag;index++;
}
bitPackNums = index;
return outRes;
}
以上就是整个jpeg的压缩过程
源文件