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的壓縮過程
源文件