JPEG 編碼知識(shí)
JPEG 是 Joint Photographic Exports Group 的英文縮寫琅捏,中文稱之為聯(lián)合圖像專家小組教硫。該小組隸屬于 ISO 國(guó)際標(biāo)準(zhǔn)化組織杈绸,主要負(fù)責(zé)定制靜態(tài)數(shù)字圖像的編碼方法珊燎,即所謂的 JPEG 算法。
JPEG 專家組開發(fā)了兩種基本的壓縮算法姑廉、兩種熵編碼方法缺亮、四種編碼模式。如下所示:
- 壓縮算法:
有損的離散余弦變換 DCT(Discrete Cosine Transform)
無損的預(yù)測(cè)壓縮技術(shù)庄蹋;
- 熵編碼方法:
Huffman 編碼瞬内;
算術(shù)編碼;
- 編碼模式:
基于 DCT 的順序模式:編碼限书、解碼通過一次掃描完成虫蝶;
基于 DCT 的漸進(jìn)模式:編碼、解碼需要多次掃描完成倦西,掃描效果由粗到精能真,逐級(jí)遞增;
無損模式:基于 DPCM扰柠,保證解碼后完全精確恢復(fù)到原圖像采樣值粉铐;
層次模式:圖像在多個(gè)空間分辨率中進(jìn)行編碼,可以根據(jù)需要只對(duì)低分辨率數(shù)據(jù)做解碼卤档,放棄高分辨率信息蝙泼;
在實(shí)際應(yīng)用中,JPEG 圖像編碼算法使用的大多是
離散余弦變換 DCT
劝枣、Huffman 編碼
汤踏、順序編碼模式
。
這被稱為 JPEG 的基本系統(tǒng)舔腾。這里介紹的 JPEG 編碼算法的流程溪胶,也是針對(duì)基本系統(tǒng)而言∥瘸希基本系統(tǒng)的 JPEG 壓縮編碼算法一共分為 11 個(gè)步驟:
顏色模式轉(zhuǎn)換
采樣
分塊
離散余弦變換(DCT)
量化
Zigzag 掃描排序
DC 系數(shù)的差分脈沖調(diào)制編碼
DC 系數(shù)的中間格式計(jì)算
AC 系數(shù)的游程長(zhǎng)度編碼
AC 系數(shù)的中間格式計(jì)算
熵編碼
JPEG 編碼步驟
JPEG 編碼涉及到的知識(shí)還是很復(fù)雜的哗脖,每一個(gè)步驟如果展開的話都是非常大的篇幅。我們這里簡(jiǎn)單了解每一步是干什么的扳还、怎么做才避,達(dá)到每一步的目標(biāo)就可以了。
本文使用了 ffjpeg library a simple jpeg codec氨距,幾乎所有的編碼算法都是使用了這個(gè)庫(kù)里的桑逝。
1. 顏色模式轉(zhuǎn)換
JPEG 采用的是 YCrCb 顏色空間,YCrCb 顏色空間中衔蹲,Y 代表亮度,Cr,Cb 則代表色度和飽和度(也有人將 Cb,Cr 兩者統(tǒng)稱為色度),三者通常以 Y,U,V 來表示舆驶,即用 U 代表 Cb橱健,用 V 代表 Cr。RGB 和 YCrCb 之間的轉(zhuǎn)換關(guān)系如下所示:
Y = 0.299*R + 0.587*G + 0.114*B
Cb = -0.169*R - 0.331*G + 0.500*B + 128
Cr = 0.500*R - 0.419*G - 0.081*B + 128
取值范圍:
R/G/B [0 ~ 255]
Y/Cb/Cr[0 ~ 255]
示例代碼:
// Y = 0.299*R + 0.587*G + 0.114*B
// U = Cb = -0.169*R - 0.331*G + 0.500*B + 128
// V = Cr = 0.500*R - 0.419*G - 0.081*B + 128
// R/G/B [0 ~ 255]
// Y/Cb/Cr[0 ~ 255]
void rgb_to_yuv(uint8_t R, uint8_t G, uint8_t B, uint8_t *Y, uint8_t *U, uint8_t *V) {
int y_val = (int)round(0.299*R + 0.587*G + 0.114*B);
int u_val = (int)round(-0.169*R - 0.331*G + 0.500*B + 128);
int v_val = (int)round(0.500*R - 0.419*G - 0.081*B + 128);
*Y = bound(0, y_val, 255);
*U = bound(0, u_val, 255);
*V = bound(0, v_val, 255);
}
相關(guān)閱讀:
YUV - RGB Color Format Conversion
2. 采樣
為節(jié)省帶寬起見沙廉,大多數(shù) YUV 格式平均使用的每像素位數(shù)都少于 24 位拘荡。主要的采樣(subsample)格式有 YCbCr4:2:0、YCbCr4:2:2撬陵、YCbCr4:1:1 和 YCbCr4:4:4珊皿。YUV 的表示法稱為 A:B:C 表示法:
- 4:4:4 表示完全取樣。
- 4:2:2 表示 2:1 的水平取樣巨税,垂直完全采樣蟋定。
- 4:2:0 表示 2:1 的水平取樣,垂直 2:1 采樣草添。
- 4:1:1 表示 4:1 的水平取樣驶兜,垂直完全采樣。
表格中远寸,每一格代表一個(gè)像素
未采樣前
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 V0 | Y1 U1 V1 | Y2 U2 V2 | Y3 U3 V3 |
2 | Y4 U4 V4 | Y5 U5 V5 | Y6 U6 V6 | Y7 U7 V7 |
3 | Y8 U8 V8 | Y9 U9 V9 | Y10 U10 V10 | Y11 U11 V11 |
4 | Y12 U12 V12 | Y13 U13 V13 | Y14 U14 V14 | Y15 U15 V15 |
4:4:4 采樣
4:4:4 表示完全取樣
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 V0 | Y1 U1 V1 | Y2 U2 V2 | Y3 U3 V3 |
2 | Y4 U4 V4 | Y5 U5 V5 | Y6 U6 V6 | Y7 U7 V7 |
3 | Y8 U8 V8 | Y9 U9 V9 | Y10 U10 V10 | Y11 U11 V11 |
4 | Y12 U12 V12 | Y13 U13 V13 | Y14 U14 V14 | Y15 U15 V15 |
映射的像素:
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 V0 | Y1 U1 V1 | Y2 U2 V2 | Y3 U3 V3 |
2 | Y4 U4 V4 | Y5 U5 V5 | Y6 U6 V6 | Y7 U7 V7 |
3 | Y8 U8 V8 | Y9 U9 V9 | Y10 U10 V10 | Y11 U11 V11 |
4 | Y12 U12 V12 | Y13 U13 V13 | Y14 U14 V14 | Y15 U15 V15 |
4:2:2 采樣
4:2:2 表示 2:1 的水平取樣抄淑,垂直完全采樣
每采樣過一個(gè)像素點(diǎn),都會(huì)采樣其 Y 分量驰后,而 U肆资、V 分量就會(huì)間隔一個(gè)采集一個(gè)。
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 - | Y1 - V1 | Y2 U2 - | Y3 - V3 |
2 | Y4 U4 - | Y5 - V5 | Y6 U6 - | Y7 - V7 |
3 | Y8 U8 - | Y9 - V9 | Y10 U10 - | Y11 - V11 |
4 | Y12 U12 - | Y13 - V13 | Y14 U14 - | Y15 - V15 |
映射的像素:
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 V1 | Y1 U0 V1 | Y2 U2 V3 | Y3 U2 V3 |
2 | Y4 U4 V5 | Y5 U4 V5 | Y6 U6 V7 | Y7 U6 V7 |
3 | Y8 U8 V9 | Y9 U8 V9 | Y10 U10 V11 | Y11 U10 V11 |
4 | Y12 U12 V13 | Y13 U12 V13 | Y14 U14 V15 | Y15 U14 V15 |
4:2:0 采樣
4:2:0 表示 2:1 的水平取樣灶芝,垂直 2:1 采樣
每采樣過一個(gè)像素點(diǎn)郑原,都會(huì)采樣其 Y 分量,而 U监署、V 分量就會(huì)間隔一行按照 2 : 1 進(jìn)行采樣颤专。
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 - | Y1 - - | Y2 U2 - | Y3 - - |
2 | Y4 - V4 | Y5 - - | Y6 - V6 | Y7 - - |
3 | Y8 U8 - | Y9 - - | Y10 U10 - | Y11 - - |
4 | Y12 - V12 | Y13 - - | Y14 - V14 | Y15 - - |
映射的像素:
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 V4 | Y1 U0 V4 | Y2 U2 V6 | Y3 U2 V6 |
2 | Y4 U0 V4 | Y5 U0 V4 | Y6 U2 V6 | Y7 U2 V6 |
3 | Y8 U8 V12 | Y9 U8 V12 | Y10 U10 V14 | Y11 U10 V14 |
4 | Y12 U8 V12 | Y13 U8 V12 | Y14 U10 V14 | Y15 U10 V14 |
4:1:1 采樣
4:1:1 表示 4:1 的水平取樣,垂直完全采樣
每采樣過一個(gè)像素點(diǎn)钠乏,都會(huì)采樣其 Y 分量栖秕,而 U、V 分量就會(huì)間隔一行按照 2 : 1 進(jìn)行采樣晓避。
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 - | Y1 - - | Y2 - V2 | Y3 - - |
2 | Y4 U4 - | Y5 - - | Y6 - V6 | Y7 - - |
3 | Y8 U8 - | Y9 - - | Y10 - V10 | Y11 - - |
4 | Y12 U12 - | Y13 - - | Y14 - V14 | Y15 - - |
映射的像素:
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | Y0 U0 V2 | Y1 U0 V2 | Y2 U0 V2 | Y3 U0 V2 |
2 | Y4 U4 V6 | Y5 U4 V6 | Y6 U4 V6 | Y7 U4 V6 |
3 | Y8 U8 V10 | Y9 U8 V10 | Y10 U8 V10 | Y11 U8 V10 |
4 | Y12 U12 V14 | Y13 U12 V14 | Y14 U12 V14 | Y15 U12 V14 |
示例代碼:
void yuv444_to_yuv420(uint8_t *inbuf, uint8_t *outbuf, int w, int h) {
uint8_t *srcY = NULL, *srcU = NULL, *srcV = NULL;
uint8_t *desY = NULL, *desU = NULL, *desV = NULL;
srcY = inbuf;//Y
srcU = srcY + w * h;//U
srcV = srcU + w * h;//V
desY = outbuf;
desU = desY + w * h;
desV = desU + w * h / 4;
int half_width = w / 2;
int half_height = h / 2;
memcpy(desY, srcY, w * h * sizeof(uint8_t));//Y分量直接拷貝即可
//UV
for (int i = 0; i < half_height; i++) {
for (int j = 0; j < half_width; j++) {
*desU = *srcU;
*desV = *srcV;
desU++;
desV++;
srcU += 2;
srcV += 2;
}
srcU = srcU + w;
srcV = srcV + w;
}
}
相關(guān)閱讀:
3. 分塊
后面的 DCT 變換是是對(duì) 8x8 的子塊進(jìn)行處理的簇捍,因此,在進(jìn)行 DCT 變換之前必須把源圖象數(shù)據(jù)進(jìn)行分塊俏拱。源圖象經(jīng)過上面的顏色模式轉(zhuǎn)換
暑塑、采樣
變成了 YUV 數(shù)據(jù),所以需要分別對(duì) Y
U
V
三個(gè)分量進(jìn)行分塊锅必。由左及右事格,由上到下依次讀取 8x8 的子塊惕艳,存放在長(zhǎng)度為 64 的數(shù)組中,即可以進(jìn)行 DCT 變換驹愚。
圖像 YUV444 數(shù)據(jù)進(jìn)行分塊:
示例代碼:
// 計(jì)算分塊數(shù)量
int calc_block_size(int width, int height) {
int h_block_size = width/8 + (width%8==0?0:1);
int v_block_size = height/8 + (height%8==0?0:1);
return h_block_size*v_block_size;
}
// 獲取一個(gè)子塊
void get_8x8_block(const uint8_t *data, uint8_t *block_data, int h_block_pos, int v_block_pos, int width, int height) {
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 8; j++) {
int h_pos = h_block_pos*8 + i;
int v_pos = v_block_pos*8 + j;
if(h_pos >= width || v_pos >= height) {
block_data[8*i+j] = 0;
} else {
block_data[8*i+j] = data[h_pos*width + v_pos];
}
}
}
}
// 對(duì)數(shù)據(jù)進(jìn)行分塊
void block_data_8x8(uint8_t *data, uint8_t blocks[][64], int width, int height) {
int h_block_size = width/8 + (width%8==0?0:1);
int v_block_size = height/8 + (height%8==0?0:1);
for(int i = 0; i < h_block_size; i++) {
for(int j = 0; j < v_block_size; j++) {
uint8_t *tmp = blocks[h_block_size*i+j];
get_8x8_block(data, tmp, i, j, width, height);
}
}
}
相關(guān)閱讀:
JPEG壓縮原理與DCT離散余弦變換-以8x8的圖象塊為基本單位進(jìn)行編碼
4. 離散余弦變換(DCT)
DCT 變換的公式為:
將原始圖像數(shù)據(jù)分為 8x8 的數(shù)據(jù)單元矩陣之后远搪,還必須將每個(gè)數(shù)值減去 128,然后一一帶入 DCT 變換公式逢捺,即可達(dá)到 DCT 變換的目的谁鳍。圖像的數(shù)據(jù)值必須減去 128,是因?yàn)?DCT 公式所接受的數(shù)字范圍是 -128 到 127 之間劫瞳。
舉例來說明一下倘潜,例子來自【JPEG 原理詳細(xì)實(shí)例分析及其在嵌入式 Linux 中的應(yīng)用】
8x8 的原始圖像:
減去 128 后,使其范圍變?yōu)?-128~127:
使用離散余弦變換志于,并四舍五入取最接近的整數(shù):
矩陣最左上角的是直流系數(shù)(
DC
)
矩陣其余 63 個(gè)是交流系數(shù)(AC
)
DCT 輸出的頻率系數(shù)矩陣最左上角的直流(DC
)系數(shù)幅度最大涮因,圖中為-415
;以 DC
系數(shù)為出發(fā)點(diǎn)向下恨憎、向右的其它 DCT 系數(shù)蕊退,離 DC 分量越遠(yuǎn),頻率越高憔恳,幅度值越小瓤荔,圖中最右下角為2,即圖像信息的大部分集中于直流系數(shù)及其附近的低頻頻譜上钥组,離 DC 系數(shù)越來越遠(yuǎn)的高頻頻譜幾乎不含圖像信息输硝,甚至于只含雜波。
示例代碼:
void fdct2d8x8(int *data) {
int u, v;
int x, y, i;
float buf[64];
float temp;
for (u=0; u<8; u++) {
for (v=0; v<8; v++) {
temp = 0;
for (x=0; x<8; x++) {
for (y=0; y<8; y++) {
temp += data[y * 8 + x]
* (float)cos((2.0f * x + 1.0f) / 16.0f * u * M_PI)
* (float)cos((2.0f * y + 1.0f) / 16.0f * v * M_PI);
}
}
buf[v * 8 + u] = alpha(u) * alpha(v) * temp;
}
}
for (i=0; i<64; i++) data[i] = buf[i];
}
for(int y_index = 0; y_index < block_size; y_index++) {
uint8_t *y_block = y_blocks[y_index];
// DCT 之前減去 128
for(int i = 0; i < 64; i++) {y_blocks_dct[y_index][i] = y_block[i]-128;}
fdct2d8x8(y_blocks_dct[y_index]);
}
for(int u_index = 0; u_index < block_size; u_index++) {
uint8_t *u_block = u_blocks[u_index];
// DCT 之前減去 128
for(int i = 0; i < 64; i++) {u_blocks_dct[u_index][i] = u_block[i] - 128;}
fdct2d8x8(u_blocks_dct[u_index]);
}
for(int v_index = 0; v_index < block_size; v_index++) {
uint8_t *v_block = v_blocks[v_index];
// DCT 之前減去 128
for(int i = 0; i < 64; i++) {v_blocks_dct[v_index][i] = v_block[i] - 128;}
fdct2d8x8(v_blocks_dct[v_index]);
}
相關(guān)閱讀:
JPEG 原理詳細(xì)實(shí)例分析及其在嵌入式 Linux 中的應(yīng)用-3程梦、離散余弦變換 DCT
JPEG 簡(jiǎn)易文檔 - 2. DCT (離散余弦變換)
5. 量化
量化過程實(shí)際上就是對(duì) DCT 系數(shù)的一個(gè)優(yōu)化過程。它是利用了人眼對(duì)高頻部分不敏感的特性來實(shí)現(xiàn)數(shù)據(jù)的大幅簡(jiǎn)化屿附。
量化過程實(shí)際上是簡(jiǎn)單地把頻率領(lǐng)域上每個(gè)成份郎逃,除以一個(gè)對(duì)于該成份的常數(shù),且接著四舍五入取最接近的整數(shù)挺份。
這是整個(gè)過程中的主要有損運(yùn)算褒翰。
以這個(gè)結(jié)果來說,經(jīng)常會(huì)把很多高頻率的成份四舍五入而接近0匀泊,且剩下很多會(huì)變成小的正或負(fù)數(shù)优训。
整個(gè)量化的目的是減小非“0”系數(shù)的幅度以及增加“0”值系數(shù)的數(shù)目。
量化是圖像質(zhì)量下降的最主要原因各聘。
因?yàn)槿搜蹖?duì)亮度信號(hào)比對(duì)色差信號(hào)更敏感揣非,因此使用了兩種量化表:亮度量化值和色差量化值。
使用這個(gè)量化矩陣與前面所得到的 DCT 系數(shù)矩陣:
如躲因,使用?415(DC系數(shù))且四舍五入得到最接近的整數(shù)
總體上來說早敬,DCT 變換實(shí)際是空間域的低通濾波器忌傻。對(duì) Y 分量采用細(xì)量化,對(duì) UV 采用粗量化搞监。
量化表是控制 JPEG 壓縮比的關(guān)鍵芯勘,這個(gè)步驟除掉了一些高頻量;另一個(gè)重要原因是所有圖片的點(diǎn)與點(diǎn)之間會(huì)有一個(gè)色彩過渡的過程腺逛,大量的圖像信息被包含在低頻率中,經(jīng)過量化處理后衡怀,在高頻率段棍矛,將出現(xiàn)大量連續(xù)的零。
示例代碼:
/* 全局變量定義 */
const int STD_QUANT_TAB_LUMIN[64] = {
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109,103,77,
24, 35, 55, 64, 81, 104,113,92,
49, 64, 78, 87, 103,121,120,101,
72, 92, 95, 98, 112,100,103,99,
};
const int STD_QUANT_TAB_CHROM[64] = {
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
};
/* 函數(shù)實(shí)現(xiàn) */
void quant_encode(int du[64], int qtab[64]) {
int i; for (i=0; i<64; i++) du[i] /= qtab[i];
}
int *pqtab[2];
pqtab[0] = malloc(64*sizeof(int));
pqtab[1] = malloc(64*sizeof(int));
memcpy(pqtab[0], STD_QUANT_TAB_LUMIN, 64*sizeof(int));
memcpy(pqtab[1], STD_QUANT_TAB_CHROM, 64*sizeof(int));
for(int y_index = 0; y_index < block_size; y_index++) {
quant_encode(y_blocks_dct[y_index], pqtab[0]);
}
for(int u_index = 0; u_index < block_size; u_index++) {
quant_encode(u_blocks_dct[u_index], pqtab[1]);
}
for(int v_index = 0; v_index < block_size; v_index++) {
quant_encode(v_blocks_dct[v_index], pqtab[1]);
}
相關(guān)閱讀:
JPEG 原理詳細(xì)實(shí)例分析及其在嵌入式 Linux 中的應(yīng)用-4. 量化
6. Zigzag 掃描排序
量化后的數(shù)據(jù)抛杨,有一個(gè)很大的特點(diǎn)够委,就是直流分量相對(duì)于交流分量來說要大,而且交流分量中含有大量的 0怖现。這樣茁帽,對(duì)這個(gè)量化后的數(shù)據(jù)如何來進(jìn)行簡(jiǎn)化,從而再更大程度地進(jìn)行壓縮呢屈嗤。
這就出現(xiàn)了“Z”字形編排潘拨,如圖:
對(duì)于前面量化的系數(shù)所作的 “Z”字形編排結(jié)果就是:
底部 ?26,?3饶号,0铁追,?3,?3茫船,?6琅束,2,?4算谈,1 ?4涩禀,1祸穷,1愁溜,5雪位,1春叫,2谋减,?1图甜,1乎澄,?1壤短,2觉义,0雁社,0,0晒骇,0霉撵,0磺浙,?1,?1徒坡,0撕氧,0,0喇完,0伦泥,0,0锦溪,0不脯,0,0刻诊,0防楷,0,0则涯,0复局,0,0粟判,0亿昏,0,0档礁,0龙优,0,0事秀,0彤断,0,0易迹,0宰衙,0,0睹欲,0供炼,0,0窘疮,0袋哼,0,0闸衫,0涛贯,0,0蔚出,0弟翘,0 頂部
這樣做的特點(diǎn)就是會(huì)連續(xù)出現(xiàn)多個(gè)0虫腋,這樣很有利于使用簡(jiǎn)單而直觀的游程編碼(RLE:Run Length Coding)對(duì)它們進(jìn)行編碼。
示例代碼:
/* 內(nèi)部全局變量定義 */
const int ZIGZAG[64] =
{
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34,
27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36,
29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63,
};
/* 函數(shù)實(shí)現(xiàn) */
void zigzag_encode(int data[64]) {
int buf[64], i;
for (i=0; i<64; i++) buf [i] = data[ZIGZAG[i]];
for (i=0; i<64; i++) data[i] = buf[i];
}
for(int y_index = 0; y_index < block_size; y_index++) {
zigzag_encode(y_blocks_dct[y_index]);
}
for(int u_index = 0; u_index < block_size; u_index++) {
zigzag_encode(u_blocks_dct[u_index]);
}
for(int v_index = 0; v_index < block_size; v_index++) {
zigzag_encode(v_blocks_dct[v_index]);
}
相關(guān)閱讀:
7. DC 系數(shù)的差分脈沖調(diào)制編碼
8x8 圖像塊經(jīng)過 DCT 變換之后得到的 DC 直流系數(shù)有兩個(gè)特點(diǎn)悦冀,一是系數(shù)的數(shù)值比較大,二是相鄰 8x8 圖像塊的 DC 系數(shù)值變化不大睛琳。根據(jù)這個(gè)特點(diǎn)盒蟆,JPEG 算法使用了差分脈沖調(diào)制編碼 (DPCM) 技術(shù),對(duì)相鄰圖像塊之間量化DC系數(shù)的差值 (Delta) 進(jìn)行編碼师骗。
DC(0)=0
Delta = DC(i) - DC(i-1)
示例代碼:
void encode_du(HUFCODEC *hfcac, HUFCODEC *hfcdc, int du[64], int *dc) {
...
// 7. DC 系數(shù)的差分脈沖調(diào)制編碼
// dc
diff = du[0] - *dc;
*dc = du[0];
...
}
int dc[3]= {0};
for(int index = 0; index < block_size; index++) {
encode_du(phcac[0], phcdc[0], y_blocks_dct[index], &(dc[0]));
encode_du(phcac[1], phcdc[1], u_blocks_dct[index], &(dc[1]));
encode_du(phcac[1], phcdc[1], v_blocks_dct[index], &(dc[2]));
}
相關(guān)閱讀:
JPEG圖像壓縮算法流程詳解-(7)DC系數(shù)的差分脈沖調(diào)制編碼
JPEG格式與壓縮流程分析-7.DC系數(shù)的差分脈沖調(diào)制編碼
8. DC 系數(shù)的中間格式計(jì)算
JPEG 中為了更進(jìn)一步節(jié)約空間茁影,并不直接保存數(shù)據(jù)的具體數(shù)值,而是將數(shù)據(jù)按照位數(shù)分為 16 組丧凤,保存在表里面。這也就是所謂的變長(zhǎng)整數(shù)編碼 VLI步脓。即愿待,第 0 組中保存的編碼位數(shù)為 0,其編碼所代表的數(shù)字為 0靴患;第 1 組中保存的編碼位數(shù)為 1仍侥,編碼所代表的數(shù)字為 -1 或者 1 ......,如下面的表格所示鸳君,這里农渊,暫且稱其為 VLI 編碼表:
如果 DC 系數(shù)差值為 3,通過查找 VLI 可以發(fā)現(xiàn)或颊,整數(shù) 3 位于 VLI 表格的第 2 組砸紊,因此,可以寫成(2)(3)的形式囱挑,該形式醉顽,稱之為 DC 系數(shù)的中間格式。
示例代碼:
void category_encode(int *code, int *size) {
unsigned absc = abs(*code);
unsigned mask = (1 << 15);
int i = 15;
if (absc == 0) { *size = 0; return; }
while (i && !(absc & mask)) { mask >>= 1; i--; }
*size = i + 1;
if (*code < 0) *code = (1 << *size) - absc - 1;
}
// 7. DC 系數(shù)的差分脈沖調(diào)制編碼
diff = du[0] - *dc;
// 8. DC 系數(shù)的中間格式計(jì)算
code = diff;
category_encode(&code, &size);
相關(guān)閱讀:
JPEG圖像壓縮算法流程詳解-(8)DC系數(shù)的中間格式計(jì)算
JPEG格式與壓縮流程分析-8.DC系數(shù)的中間格式計(jì)算
9. AC 系數(shù)的游程長(zhǎng)度編碼
游程編碼 RLC(Run Length Coding)來更進(jìn)一步降低數(shù)據(jù)的傳輸量平挑。利用該編碼方式游添,可以將一個(gè)字符串中重復(fù)出現(xiàn)的連續(xù)字符用兩個(gè)字節(jié)來代替,其中通熄,第一個(gè)字節(jié)代表重復(fù)的次數(shù)唆涝,第二個(gè)字節(jié)代表被重復(fù)的字符串。
57, 45, 0, 0, 0, 0, 23, 0, -30, -8, 0, 0, 1, 000…
經(jīng)過 0 RLC 之后唇辨,將呈現(xiàn)出以下的形式:
(0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-8) ; (2,1) ; (0,0)
注意廊酣,如果 AC 系數(shù)之間連續(xù) 0 的個(gè)數(shù)超過 16,則用一個(gè)擴(kuò)展字節(jié) (15,0) 來表示 16 連續(xù)的 0赏枚。
示例代碼:
typedef struct {
unsigned runlen : 4;
unsigned codesize : 4;
unsigned codedata : 16;
} RLEITEM;
// 9. AC 系數(shù)的游程長(zhǎng)度編碼
// 10. AC 系數(shù)的中間格式計(jì)算
// rle encode for ac
for (i=1, j=0, n=0, eob=0; i<64 && j<63; i++) {
if (du[i] == 0 && n < 15) {
n++;
} else {
code = du[i]; size = 0;
// 10. AC 系數(shù)的中間格式計(jì)算
category_encode(&code, &size);
rlelist[j].runlen = n;
rlelist[j].codesize = size;
rlelist[j].codedata = code;
n = 0;
j++;
if (size != 0) eob = j;
}
}
// set eob
if (du[63] == 0) {
rlelist[eob].runlen = 0;
rlelist[eob].codesize = 0;
rlelist[eob].codedata = 0;
j = eob + 1;
}
相關(guān)閱讀:
JPEG圖像壓縮算法流程詳解-(9)AC系數(shù)的行程長(zhǎng)度編碼(RLC)
JPEG格式與壓縮流程分析-9. AC系數(shù)的行程長(zhǎng)度編碼(RLC)
10. AC 系數(shù)的中間格式計(jì)算
根據(jù)前面提到的 VLI 表格啰扛,對(duì)于前面的字符串:
(0,57) ; (0,45) ; (4,23) ; (1,-30) ; (0,-8) ; (2,1) ; (0,0)
只處理每對(duì)數(shù)右邊的那個(gè)數(shù)據(jù)嚎京,對(duì)其進(jìn)行 VLI 編碼: 查找上面的 VLI 編碼表格,可以發(fā)現(xiàn)隐解,57 在第 6 組當(dāng)中鞍帝,因此,可以將其寫成 (0,6),57 的形式煞茫,該形式帕涌,稱之為 AC 系數(shù)的中間格式。
同樣的 (0,45) 的中間格式為:(0,6),45续徽;
(1,-30) 的中間格式為:(1,5),-30蚓曼;
示例代碼:
void category_encode(int *code, int *size) {
unsigned absc = abs(*code);
unsigned mask = (1 << 15);
int i = 15;
if (absc == 0) { *size = 0; return; }
while (i && !(absc & mask)) { mask >>= 1; i--; }
*size = i + 1;
if (*code < 0) *code = (1 << *size) - absc - 1;
}
// 9. AC 系數(shù)的游程長(zhǎng)度編碼
// 10. AC 系數(shù)的中間格式計(jì)算
// rle encode for ac
for (i=1, j=0, n=0, eob=0; i<64 && j<63; i++) {
if (du[i] == 0 && n < 15) {
n++;
} else {
code = du[i]; size = 0;
// 10. AC 系數(shù)的中間格式計(jì)算
category_encode(&code, &size);
rlelist[j].runlen = n;
rlelist[j].codesize = size;
rlelist[j].codedata = code;
n = 0;
j++;
if (size != 0) eob = j;
}
}
// set eob
if (du[63] == 0) {
rlelist[eob].runlen = 0;
rlelist[eob].codesize = 0;
rlelist[eob].codedata = 0;
j = eob + 1;
}
相關(guān)閱讀:
JPEG圖像壓縮算法流程詳解-(10)AC系數(shù)的中間格式
JPEG格式與壓縮流程分析-10.AC系數(shù)的中間格式算
11. 熵編碼
在得到 DC 系數(shù)的中間格式和 AC 系數(shù)的中間格式之后,為進(jìn)一步壓縮圖像數(shù)據(jù)钦扭,有必要對(duì)兩者進(jìn)行熵編碼纫版,通過對(duì)出現(xiàn)概率較高的字符采用較小的 bit 數(shù)編碼達(dá)到壓縮的目的。JPEG 標(biāo)準(zhǔn)具體規(guī)定了兩種熵編碼方式:Huffman 編碼和算術(shù)編碼客情。JPEG 基本系統(tǒng)規(guī)定采用 Huffman 編碼其弊。
Huffman 編碼:對(duì)出現(xiàn)概率大的字符分配字符長(zhǎng)度較短的二進(jìn)制編碼,對(duì)出現(xiàn)概率小的字符分配字符長(zhǎng)度較長(zhǎng)的二進(jìn)制編碼膀斋,從而使得字符的平均編碼長(zhǎng)度最短梭伐。Huffman 編碼的原理請(qǐng)參考數(shù)據(jù)結(jié)構(gòu)中的 Huffman 樹或者最優(yōu)二叉樹。
Huffman 編碼時(shí) DC 系數(shù)與 AC 系數(shù)分別采用不同的 Huffman 編碼表仰担,對(duì)于亮度和色度也采用不同的 Huffman 編碼表糊识。因此,需要 4 張 Huffman 編碼表才能完成熵編碼的工作摔蓝。具體的 Huffman 編碼采用查表的方式來高效地完成赂苗。然而,在 JPEG 標(biāo)準(zhǔn)中沒有定義缺省的 Huffman 表贮尉,用戶可以根據(jù)實(shí)際應(yīng)用自由選擇哑梳,也可以使用J PEG 標(biāo)準(zhǔn)推薦的 Huffman 表』婷耍或者預(yù)先定義一個(gè)通用的 Huffman 表鸠真,也可以針對(duì)一副特定的圖像,在壓縮編碼前通過搜集其統(tǒng)計(jì)特征來計(jì)算 Huffman 表的值龄毡。
示例代碼:
// huffman codec ac
HUFCODEC *phcac[2];
phcac[0]= calloc(1, sizeof(HUFCODEC));
phcac[1]= calloc(1, sizeof(HUFCODEC));
// huffman codec dc
HUFCODEC *phcdc[2];
phcdc[0] = calloc(1, sizeof(HUFCODEC));
phcdc[1] = calloc(1, sizeof(HUFCODEC));
// init huffman codec
memcpy(phcac[0]->huftab, STD_HUFTAB_LUMIN_AC, MAX_HUFFMAN_CODE_LEN + 256);
memcpy(phcac[1]->huftab, STD_HUFTAB_CHROM_AC, MAX_HUFFMAN_CODE_LEN + 256);
memcpy(phcdc[0]->huftab, STD_HUFTAB_LUMIN_DC, MAX_HUFFMAN_CODE_LEN + 256);
memcpy(phcdc[1]->huftab, STD_HUFTAB_CHROM_DC, MAX_HUFFMAN_CODE_LEN + 256);
phcac[0]->output = bs; huffman_encode_init(phcac[0], 1);
phcac[1]->output = bs; huffman_encode_init(phcac[1], 1);
phcdc[0]->output = bs; huffman_encode_init(phcdc[0], 1);
phcdc[1]->output = bs; huffman_encode_init(phcdc[1], 1);
// 7. DC 系數(shù)的差分脈沖調(diào)制編碼
// 8. DC 系數(shù)的中間格式計(jì)算
// 9. AC 系數(shù)的游程長(zhǎng)度編碼
// 10. AC 系數(shù)的中間格式計(jì)算
// 11. 熵編碼
// 7吠卷、8、9沦零、10祭隔、11 在 encode_du 里完成
for(int index = 0; index < block_size; index++) {
encode_du(phcac[0], phcdc[0], y_blocks_dct[index], &(dc[0]));
encode_du(phcac[1], phcdc[1], u_blocks_dct[index], &(dc[1]));
encode_du(phcac[1], phcdc[1], v_blocks_dct[index], &(dc[2]));
}
void encode_du(HUFCODEC *hfcac, HUFCODEC *hfcdc, int du[64], int *dc) {
...
// 7. DC 系數(shù)的差分脈沖調(diào)制編碼
diff = du[0] - *dc;
*dc = du[0];
// 8. DC 系數(shù)的中間格式計(jì)算
code = diff;
category_encode(&code, &size);
// 11. 熵編碼 DC
huffman_encode_step(hfcdc, size);
bitstr_put_bits(bs, code, size);
// 9. AC 系數(shù)的游程長(zhǎng)度編碼
// 10. AC 系數(shù)的中間格式計(jì)算
// rle encode for ac
for (i=1, j=0, n=0, eob=0; i<64 && j<63; i++) {
...
}
...
// 11. 熵編碼 AC
for (i=0; i<j; i++) {
huffman_encode_step(hfcac, (rlelist[i].runlen << 4) | (rlelist[i].codesize << 0));
bitstr_put_bits(bs, rlelist[i].codedata, rlelist[i].codesize);
}
}
相關(guān)閱讀:
簡(jiǎn)述JPEG編碼實(shí)現(xiàn)的基本步驟
JPEG 文件寫入
根據(jù) 音視頻入門-14-JPEG文件格式詳解, JPEG 文件大體上可以分成兩個(gè)部分:標(biāo)記碼(Tag)和壓縮數(shù)據(jù)。
常用的標(biāo)記有 SOI疾渴、APP0千贯、APPn、DQT搞坝、SOF0搔谴、DHT、DRI桩撮、SOS敦第、EOI:
標(biāo)記 | 標(biāo)記代碼 | 描述 |
---|---|---|
SOI | 0xD8 | 圖像開始 |
APP0 | 0xE0 | JFIF應(yīng)用數(shù)據(jù)塊 |
APPn | 0xE1 - 0xEF | 其他的應(yīng)用數(shù)據(jù)塊(n, 1~15) |
DQT | 0xDB | 量化表 |
SOF0 | 0xC0 | 幀開始 |
DHT | 0xC4 | 霍夫曼(Huffman)表 |
DRI | 0xDD | 差分編碼累計(jì)復(fù)位的間隔 |
SOS | 0xDA | 掃描線開始 |
EOI | 0xD9 | 圖像結(jié)束 |
通過上面的 11 個(gè)編碼步驟,我們也得到了需要的數(shù)據(jù)店量,下一步只需將數(shù)據(jù)寫入文件即可芜果。
// 下面開始將數(shù)據(jù)寫入文件
//FILE *fp = fopen("C:\\Users\\Administrator\\Desktop\\rainbow-rgb-to-jpeg.jpg", "wb+");
FILE *fp = fopen("/Users/staff/Desktop/rainbow-rgb-to-jpeg.jpg", "wb");
// output SOI
fputc(0xff, fp);
fputc(0xd8, fp);
// output DQT
for (int i = 0; i < 2; i++) {
int len = 2 + 1 + 64;
fputc(0xff, fp);
fputc(0xdb, fp);
fputc(len >> 8, fp);
fputc(len >> 0, fp);
fputc(i , fp);
for (int j = 0; j < 64; j++) {
fputc(pqtab[i][ZIGZAG[j]], fp);
}
}
// output SOF0
int SOF0Len = 2 + 1 + 2 + 2 + 1 + 3 * 3;
fputc(0xff, fp);
fputc(0xc0, fp);
fputc(SOF0Len >> 8, fp);
fputc(SOF0Len >> 0, fp);
fputc(8 , fp); // precision 8bit
fputc(height >> 8, fp); // height
fputc(height >> 0, fp); // height
fputc(width >> 8, fp); // width
fputc(width >> 0, fp); // width
fputc(3, fp);
fputc(0x01, fp); fputc(0x11, fp); fputc(0x00, fp);
fputc(0x02, fp); fputc(0x11, fp); fputc(0x01, fp);
fputc(0x03, fp); fputc(0x11, fp); fputc(0x01, fp);
// output DHT AC
for (int i = 0; i < 2; i++) {
fputc(0xff, fp);
fputc(0xc4, fp);
int len = 2 + 1 + 16;
for (int j = 0; j < 16; j++) len += phcac[i]->huftab[j];
fputc(len >> 8, fp);
fputc(len >> 0, fp);
fputc(i + 0x10, fp);
fwrite(phcac[i]->huftab, len - 3, 1, fp);
}
// output DHT DC
for (int i = 0; i < 2; i++) {
fputc(0xff, fp);
fputc(0xc4, fp);
int len = 2 + 1 + 16;
for (int j = 0; j < 16; j++) len += phcdc[i]->huftab[j];
fputc(len >> 8, fp);
fputc(len >> 0, fp);
fputc(i + 0x00, fp);
fwrite(phcdc[i]->huftab, len - 3, 1, fp);
}
// output SOS
int SOSLen = 2 + 1 + 2 * 3 + 3;
fputc(0xff, fp);
fputc(0xda, fp);
fputc(SOSLen >> 8, fp);
fputc(SOSLen >> 0, fp);
fputc(3, fp);
fputc(0x01, fp); fputc(0x00, fp);
fputc(0x02, fp); fputc(0x11, fp);
fputc(0x03, fp); fputc(0x11, fp);
fputc(0x00, fp);
fputc(0x3F, fp);
fputc(0x00, fp);
// output data
if (buffer) {
fwrite(buffer, dataLength, 1, fp);
}
// output EOI
fputc(0xff, fp);
fputc(0xd9, fp);
// 釋放各種資源
......
JPEG 結(jié)果驗(yàn)收
以上完整代碼在 binglingziyu/audio-video-blog-demos 可以獲取。
運(yùn)行代碼融师,生成 JPEG 圖片:
代碼:
15-rgb-to-jpeg
參考資料:
某科學(xué)的JPEG編碼函數(shù) svjpeg()
簡(jiǎn)述JPEG編碼實(shí)現(xiàn)的基本步驟
YUV - RGB Color Format Conversion
本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!