编译环境
windows10下MinGW
编译语言
c++ && CImg库
运行命令
g++ -o test main.cpp jpegCompress.cpp jpegCompress.h -O2 -lgdi32
1. 8x8分块
因为之后的DCT变换是要对8x8的子块为一个单位进行处理,所以为了方便第一步我们就进行分块。所以源图的width和height必须要是8的倍数,所以我们对源图进行初始化处理,如果不是8的倍数的话我们要用0对其进行两侧的填充。
初始化处理:
//载入图片,并对图片进行初始化处理
//如果图片的宽高不是8的倍数,则将两边均匀填0
source.load(fileName);
int width = source._width;
int height = source._height;
int width1 = width % 8 == 0 ? width : (width / 8 + 1) * 8;
int height1 = height % 8 ==0 ? height : (height / 8 + 1) * 8;
CImg<unsigned char> source_a(width1, height1, 1, 3, 0);
cimg_forXY(source_a, x, y) {
if (x < width && y < height) {
source_a(x + (width1 - width) / 2, y + (height1 - height) / 2, 0) = source(x, y, 0);
source_a(x + (width1 - width) / 2, y + (height1 - height) / 2, 1) = source(x, y, 1);
source_a(x + (width1 - width) / 2, y + (height1 - height) / 2, 2) = source(x, y, 2);
}
}
source = source_a;
Width = source._width;
Height = source._height;
分块:
//每次取8x8的小块进行处理
for(int yPos=0; yPos<Height; yPos+=8)
for (int xPos=0; xPos<Width; xPos+=8)
2.颜色空间转换
JPEG采用的是YCrCb颜色空间,而正常的编程软件读入图片后都会转为rgb格式,这也是bmp文件存储的颜色空间。所以要进行颜色空间转换。。YCrCb颜色空间中,Y代表亮度,Cr,Cb则代表色度和饱和度(也有人将Cb,Cr两者统称为色度),三者通常以Y,U,V来表示,即用U代表Cb,用V代表Cr。RGB和YCrCb之间的转换关系如下:
Y
=
0.299
R
+
0.587
G
+
0.114
B
Y = 0.299R+0.587G+0.114B
Y=0.299R+0.587G+0.114B
C b = − 0.1687 R − 0.3313 G + 0.5 B + 128 Cb = -0.1687R-0.3313G+0.5B+128 Cb=−0.1687R−0.3313G+0.5B+128
C r = 0.5 R = 0.418 G − 0.0813 B + 128 Cr = 0.5R=0.418G-0.0813B+128 Cr=0.5R=0.418G−0.0813B+128
代码:
void JpegCompress::RGB2YCbCr(int xPos, int yPos, char* y_channel, char* cd_channel, char* cr_channel)
{
for (int y=0; y<8; y++)
{
for (int x=0; x<8; x++)
{
unsigned char R = source(xPos+x,yPos+y,0);
unsigned char G = source(xPos+x,yPos+y,1);
unsigned char B = source(xPos+x,yPos+y,2);
y_channel[y*8+x] = (unsigned char)(0.299f * R + 0.587f * G + 0.114f * B - 128);
cd_channel[y*8+x] = (unsigned char)(-0.1687f * R - 0.3313f * G + 0.5f * B );
cr_channel[y*8+x] = (unsigned char)(0.5f * R - 0.4187f * G - 0.0813f * B);
}
}
}
实现效果:
对应的分别是Y,Cb,Cr三个空间的图片:
3.离散余弦变换(DCT)
F ( u , v ) = a l p h a ( u ) ∗ a l p h a ( v ) ∗ ∑ x = 0 7 ∑ y = 0 7 f ( x , y ) cos ( 2 x + 1 16 u π ) c o s ( 2 y + 1 16 v π ) u , v = 0 , 1 , . . . , 7 F(u,v) = alpha(u)*alpha(v)*\sum_{x=0}^{7}\sum_{y=0}^{7} f(x,y)\cos(\frac{2x+1}{16}u\pi)cos(\frac{2y+1}{16}v\pi) \qquad u,v=0,1,...,7 F(u,v)=alpha(u)∗alpha(v)∗x=0∑7y=0∑7f(x,y)cos(162x+1uπ)cos(162y+1vπ)u,v=0,1,...,7
a l p h a ( u ) = { 1 / 8 w h e n    u = 0 1 / 2 w h e n    u ≠ 0 alpha(u) = \left\{ \begin{aligned} 1/ \sqrt{8} \qquad when \; u = 0 \\ 1 / \quad 2 \qquad when \; u \neq 0 \end{aligned} \right. alpha(u)={1/8whenu=01/2whenu̸=0
8x8的二维像素块经过DCT操作之后,就得到了8x8的变换系数矩阵。这些系数,都有具体的物理含义,例如,U=0,V=0时的F(0,0)是原来的64个数据的均值,相当于直流分量,也有人称之为DC系数或者直流系数。随着U,V的增加,相另外的63个系数则代表了水平空间频率和垂直空间频率分量(高频分量)的大小,多半是一些接近于0的正负浮点数,我们称之为交流系数AC。DCT变换后的8*8的系数矩阵中,低频分量集中在矩阵的左上角。高频成分则集中在右下角。
DCT变换代码:
void JpegCompress::DCT(const char* channel_data, float* fdc_data)
{
for(int v=0; v<8; v++)
{
for(int u=0; u<8; u++)
{
float alpha_u = (u==0) ? 1/sqrt(8.0f) : 0.5f;
float alpha_v = (v==0) ? 1/sqrt(8.0f) : 0.5f;
float f_xy = 0.0;
for(int x=0; x<8; x++)
{
for(int y=0; y<8; y++)
{
float data = channel_data[y*8+x];
data *= cos((2*x+1)*u*PI/16.0f);
data *= cos((2*y+1)*v*PI/16.0f);
f_xy += data;
}
}
fdc_data[v*8+u] = f_xy * alpha_u * alpha_v;
}
}
}
由于大多数图像的高频分量比较小,相应的图像高频分量的DCT系数经常接近于0,再加上高频分量中只包含了图像的细微的细节变化信息,而人眼对这种高频成分的失真不太敏感,所以,可以考虑将这一些高频成分予以抛弃,从而降低需要传输的数据量。这样一来,传送DCT变换系数的所需要的编码长度要远远小于传送图像像素的编码长度。到达接收端之后通过反离散余弦变换就可以得到原来的数据,虽然这么做存在一定的失真,但人眼是可接受的,而且对这种微小的变换是不敏感的。
4.量化
量化阶段需要两个8*8量化矩阵数据,一个是专门处理亮度的频率系数,另一个则是针对色度的频率系数,将频率系数除以量化矩阵的值之后取整,即完成了量化过程。当频率系数经过量化之后,将频率系数由浮点数转变为整数,这才便于执行最后的编码。不难发现,经过量化阶段之后,所有的数据只保留了整数近似值,也就再度损失了一些数据内容。在JPEG算法中,由于对亮度和色度的精度要求不同,分别对亮度和色度采用不同的量化表。前者细量化,后者粗量化。
//标准亮度量化表
const unsigned char Luminance_Quantization_Table[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 unsigned char Chrominance_Quantization_Table[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
};
- 量化表也是控制 JPEG 压缩比的关键。这个步骤除掉了一些高频量, 损失了很高细节。但事实上人眼对高空间频率远没有低频敏感.所以处理后的视觉损失很小。
- 另一个重要原因是所有的图片的点与点之间会有一个色彩过渡的过程. 大量的图象信息被包含在低空间频率中。经过量化处理后, 在高空间频率段, 将出现大量连续的零。
- 我的调整方式是设为10个等级,等级越高压缩比例也越大,得到的图片越小。
void JpegCompress::InitQualityTables(int quality)
{
for(int i=0; i<64; i++)
{
int quant = Luminance_Quantization_Table[i] * quality / 10;
quant == 0 ? Y_Table[ZigZag[i]] = 1:Y_Table[ZigZag[i]] = quant;
quant = Chrominance_Quantization_Table[i] * quality / 10;
quant == 0 ? CbCr_Table[ZigZag[i]] = 1:CbCr_Table[ZigZag[i]] = quant;
}
}
- 用得到的处理后的量化表进行量化处理:
void JpegCompress::Quality(float* fdc_data, short* Quant, unsigned char* Table) {
for(int v=0; v<8; v++)
{
for(int u=0; u<8; u++)
{
Quant[ZigZag[v*8+u]] = round(fdc_data[v*8+u] / Table[ZigZag[v*8+u]]);
}
}
}
- 矩阵的量化最后一步是把量化后的二维矩阵转变成一个一维数组,以方便后面的霍夫曼压缩,但在做这个顺序转换时,需要按照一个特定的取值顺序 。也就是我们说的ZigZag扫描
//用于对量化后的矩阵进行顺序调整
const char ZigZag[64] =
{
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
};
5.Huffman编码
a. DC系数的DPCM编码:
即取同一个图像分量中每个DC值与前一个DC值的差值来进行编码。这样对差值进行编码所需要的位数会比对原值进行编码所需要的位数少了很多 。
- 这是基于DC系数的两个特点:
- 系数的数值比较大;
- 相邻的8*8图像块的DC系数值变化不大;
代码:
//对DC分量进行DPCM编码
int dcDiff = (int)(Quant[0] - prev);
prev = Quant[0];
if (dcDiff == 0) {
outputBit[ptr] = DC_Table[0];
ptr++;
}
else
{
Bit B;
int v = abs(dcDiff);
int length = 0;
while (v) {
length++;
v /= 2;
}
B.value = dcDiff>0 ? dcDiff : (pow(2,length)+dcDiff-1);
B.length = length;
outputBit[ptr] = DC_Table[B.length];
ptr++;
outputBit[ptr] = B;
ptr++;
}
b. AC系数的游长编码:
量化之后的AC系数的特点是,63个系数中含有很多值为0的系数,尤其是在ZigZag扫描之后0大部分集中在数组的后面位置。因此,可以采用行程编码RLC(Run Length Coding)来更进一步降低数据的传输量。利用该编码方式,可以将一个字符串中重复出现的连续字符用两个字节来代替,其中,第一个字节代表重复的次数,第二个字节代表被重复的字符串。
//对AC分量进行游长编码
int end = 63;
//找到最后不为0的之前部分,后面为0的全部置为EOB
while((end > 0) && (Quant[end] == 0)) end--;
for(int i=1; i<=end; i++)
{
int start = i;
while((Quant[i] == 0) && (i <= end)) i++;
int count = i - start;
if (count >= 16)
{
for (int j=1; j<=count/16; j++) {
outputBit[ptr] = AC_Table[0xF0];
ptr++;
}
count = count%16;
}
Bit B;
int v = abs(Quant[i]);
int length = 0;
while (v) {
length++;
v /= 2;
}
B.value = Quant[i]>0 ? Quant[i] : (pow(2,length)+Quant[i]-1);
B.length = length;
outputBit[ptr] = AC_Table[(count * 16) | B.length];
ptr++;
outputBit[ptr] = B;
ptr++;
}
if (end != 63) {
outputBit[ptr] = AC_Table[0x00];
ptr++;
}
c. 熵编码(Huffman编码)
Huffman编码时DC系数与AC系数分别采用不同的Huffman编码表,对于亮度和色度也采用不同的Huffman编码表。因此,需要4张Huffman编码表才能完成熵编码的工作。具体的Huffman编码采用查表的方式来高效地完成。
//标准直流分量色度亮度哈夫曼表
const char Standard_DC_Luminance_NRCodes[] = { 0, 0, 7, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
const unsigned char Standard_DC_Luminance_Values[] = { 4, 5, 3, 2, 6, 1, 0, 7, 8, 9, 10, 11 };
const char Standard_DC_Chrominance_NRCodes[] = { 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 };
const unsigned char Standard_DC_Chrominance_Values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
//标准交流分量色度亮度哈夫曼表
const char Standard_AC_Luminance_NRCodes[] = { 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d };
const unsigned char Standard_AC_Luminance_Values[] =
{
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
};
const char Standard_AC_Chrominance_NRCodes[] = { 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 };
const unsigned char Standard_AC_Chrominance_Values[] =
{
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
};
参照jpeg官方的文件交换格式将标准霍夫曼表转换为图像中应用的包含(Huffman_size、Huffman_code)的相应四张查询表。
在《ISO/IEC10918-1, 1993(e), Annex C》中,用三个流程图,进行了详细的阐述,下面是其相应的官方转换Matlab代码:
% 根据DHT产生HUFFSIZE[]、HUFFCODE[]
% 参考"ISO/IEC10918-1, 1993(e), Annex C"
% dxq@2014-04-24
function[ehufco,ehufsi]=dedht(dht)
bits =dht(1:16);
huffval =dht(17:end);
% 参考"ISO/IEC10918-1, 1993(e), Annex C ,Figure C.1"
k = 0 ;
j = 1 ;
fori=1:16
for j=1:bits(i)
huffsize(k+1)=i;
k=k+1;
end
end
huffsize(k+1)=0;
lastk = k;
% 参考"ISO/IEC10918-1, 1993(e), Annex C ,Figure C.2"
code = 0;
k=1;
si =huffsize(1);
whilehuffsize(k)>0
huffcode(k)=code ;
code = code + 1 ;
k = k+ 1 ;
while huffsize(k)==si
huffcode(k)=code ;
code = code + 1 ;
k = k+ 1 ;
end
if huffsize(k)==0
break;
end
code = code * 2 ;
si = si + 1;
while huffsize(k)~= si
code = code * 2 ;
si = si + 1;
end
end
% 参考"ISO/IEC10918-1, 1993(e), Annex C ,Figure C.3"
for k=1:lastk
i=huffval(k);
ehufco(i+1)=uint32(huffcode(k));
ehufsi(i+1)=uint32(huffsize(k));
end
改写为c++代码:
void JpegCompress::InitHuffmanTables()
{
unsigned char pos = 0;
unsigned short code_value = 0;
for(int i = 1; i <= 16; i++)
{
for(int j = 1; j <= Standard_DC_Luminance_NRCodes[i-1]; j++)
{
Y_DC_Huffman[Standard_DC_Luminance_Values[pos]].value = code_value;
Y_DC_Huffman[Standard_DC_Luminance_Values[pos]].length = i;
pos++;
code_value++;
}
code_value *= 2;
}
pos = 0; code_value = 0;
for(int i = 1; i <= 16; i++)
{
for(int j = 1; j <= Standard_AC_Luminance_NRCodes[i-1]; j++)
{
Y_AC_Huffman[Standard_AC_Luminance_Values[pos]].value = code_value;
Y_AC_Huffman[Standard_AC_Luminance_Values[pos]].length = i;
pos++;
code_value++;
}
code_value *= 2;
}
pos = 0; code_value = 0;
for(int i = 1; i <= 16; i++)
{
for(int j = 1; j <= Standard_DC_Chrominance_NRCodes[i-1]; j++)
{
CbCr_DC_Huffman[Standard_DC_Chrominance_Values[pos]].value = code_value;
CbCr_DC_Huffman[Standard_DC_Chrominance_Values[pos]].length = i;
pos++;
code_value++;
}
code_value *= 2;
}
pos = 0; code_value = 0;
for(int i = 1; i <= 16; i++)
{
for(int j = 1; j <= Standard_AC_Chrominance_NRCodes[i-1]; j++)
{
CbCr_AC_Huffman[Standard_AC_Chrominance_Values[pos]].value = code_value;
CbCr_AC_Huffman[Standard_AC_Chrominance_Values[pos]].length = i;
pos++;
code_value++;
}
code_value *= 2;
}
}
d. 范式霍夫曼编码的格式:
为了提高储存效率, JPEG 里并不直接保存数值, 而是将数值按位数分成 16 组:
数值 组 实际保存值
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 00000,..,01111,10000,..,11111
-63,..,-32,32,..,63 6 .
-127,..,-64,64,..,127 7 .
-255,..,-128,128,..,255 8 .
-511,..,-256,256,..,511 9 .
-1023,..,-512,512,..,1023 10 .
-2047,..,-1024,1024,..,2047 11 .
-4095,..,-2048,2048,..,4095 12 .
-8191,..,-4096,4096,..,8191 13 .
-16383,..,-8192,8192,..,16383 14 .
-32767,..,-16384,16384,..,32767 15 .
//范式霍夫曼编码表数值范围
const short paradigm_table[] = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};
if ((value & paradigm_table[pos]) != 0)
{
newByte = newByte | paradigm_table[newBytePos];
}
至此,终于完成了霍夫曼编码。
6.jpeg文件的写入
查询官方的jpeg格式要求,附在txt文档中。
jpeg头文件:
void JpegCompress::JpegHeader(FILE* fp)
{
//SOI
WriteShort(0xFFD8, fp); // marker = 0xFFD8
//APPO
WriteShort(0xFFE0,fp); // marker = 0xFFE0
WriteShort(16, fp); // length = 16 for usual JPEG
fwrite("JFIF", 1, 5, fp); // 'JFIF\0'
WriteByte(1, fp); // version_hi
WriteByte(1, fp); // version_low
WriteByte(0, fp); // xyunits = 0 no units, normal density
WriteShort(1, fp); // xdensity
WriteShort(1, fp); // ydensity
WriteByte(0, fp); // thumbWidth
WriteByte(0, fp); // thumbHeight
//DQT
WriteShort(0xFFDB, fp); //marker = 0xFFDB
WriteShort(132, fp); //size=132
WriteByte(0, fp); //QTYinfo== 0: bit 0..3: number of QT = 0 (table for Y)
// bit 4..7: precision of QT
// bit 8 : 0
fwrite(Y_Table, 1, 64, fp); //YTable
WriteByte(1, fp); //QTCbinfo = 1 (quantization table for Cb,Cr)
fwrite(CbCr_Table, 1, 64, fp); //CbCrTable
//SOFO
WriteShort(0xFFC0, fp); //marker = 0xFFC0
WriteShort(17, fp); //length = 17 for a truecolor YCbCr JPG
WriteByte(8, fp); //precision = 8: 8 bits/sample
WriteShort(Height&0xFFFF, fp); //height
WriteShort(Width&0xFFFF, fp); //width
WriteByte(3, fp); //nrofcomponents = 3
WriteByte(1, fp); //IdY = 1
WriteByte(0x11, fp); //HVY sampling factors for Y (bit 0-3 vert., 4-7 hor.)(SuBamp 1x1)
WriteByte(0, fp); //QTY
WriteByte(2, fp); //IdCb = 2
WriteByte(0x11, fp); //HVCb = 0x11(SuBamp 1x1)
WriteByte(1, fp); //QTCb = 1
WriteByte(3, fp); //IdCr = 3
WriteByte(0x11, fp); //HVCr = 0x11 (SuBamp 1x1)
WriteByte(1, fp); //QTCr
//DHT
WriteShort(0xFFC4, fp); //marker = 0xFFC4
WriteShort(0x01A2, fp); //length = 0x01A2
WriteByte(0, fp); //HTYDCinfo bit 0..3
// bit 4
// bit 5..7
fwrite(Standard_DC_Luminance_NRCodes, 1, sizeof(Standard_DC_Luminance_NRCodes), fp); //DC_L_NRC
fwrite(Standard_DC_Luminance_Values, 1, sizeof(Standard_DC_Luminance_Values), fp); //DC_L_VALUE
WriteByte(0x10, fp); //HTYACinfo
fwrite(Standard_AC_Luminance_NRCodes, 1, sizeof(Standard_AC_Luminance_NRCodes), fp);
fwrite(Standard_AC_Luminance_Values, 1, sizeof(Standard_AC_Luminance_Values), fp);
WriteByte(0x01, fp); //HTCbDCinfo
fwrite(Standard_DC_Chrominance_NRCodes, 1, sizeof(Standard_DC_Chrominance_NRCodes), fp);
fwrite(Standard_DC_Chrominance_Values, 1, sizeof(Standard_DC_Chrominance_Values), fp);
WriteByte(0x11, fp); //HTCbACinfo
fwrite(Standard_AC_Chrominance_NRCodes, 1, sizeof(Standard_AC_Chrominance_NRCodes), fp);
fwrite(Standard_AC_Chrominance_Values, 1, sizeof(Standard_AC_Chrominance_Values), fp);
//SOS
WriteShort(0xFFDA, fp); //marker = 0xFFC4
WriteShort(12, fp); //length = 12
WriteByte(3, fp); //nrofcomponents, Should be 3: truecolor JPG
WriteByte(1, fp); //Idy=1
WriteByte(0, fp); //HTY bits 0..3: AC table (0..3)
// bits 4..7: DC table (0..3)
WriteByte(2, fp); //IdCb
WriteByte(0x11, fp); //HTCb
WriteByte(3, fp); //IdCr
WriteByte(0x11, fp); //HTCr
WriteByte(0, fp); //Ss
WriteByte(0x3F, fp); //Se
WriteByte(0, fp); //Bf
}
编码部分的写入:
void JpegCompress::WriteToJpeg(const Bit* B, int counts, int& newByte, int& newBytePos, FILE* fp)
{
for(int i=0; i<counts; i++)
{
int value = B[i].value;
int pos = B[i].length - 1;
while (pos >= 0)
{
if ((value & paradigm_table[pos]) != 0)
{
newByte = newByte | paradigm_table[newBytePos];
}
pos--;
newBytePos--;
if (newBytePos < 0)
{
// 写入
WriteByte((unsigned char)(newByte), fp);
if (newByte == 255)
{
// 特殊处理
WriteByte((unsigned char)(0x00), fp);
}
// 初始化更新
newBytePos = 7;
newByte = 0;
}
}
}
}
7.实现效果:
对不同等级的量化程度进行测试,均得到理想效果:
例如下面是量化等级为5时的结果图:
参考文献:
https://www.codingnow.com/2000/download/jpeg.txt
https://www.impulseadventure.com/photo/jpeg-huffman-coding.html
https://www.w3.org/Graphics/JPEG/jfif.txt
https://blog.csdn.net/carson2005/article/details/7753499
https://blog.csdn.net/fzh2712/article/details/28318715
附录:
附:JPEG 文件格式
~~~~~~~~~~~~~~~~
- 文件头 (2 bytes): $ff, $d8 (SOI) (JPEG 文件标识)
- 任意数量的段 , 见后面
- 文件结束 (2 bytes): $ff, $d9 (EOI)
段的格式:
~~~~~~~~~
- header (4 bytes):
$ff 段标识
n 段的类型 (1 byte)
sh, sl 该段长度, 包括这两个字节, 但是不包括前面的 $ff 和 n.
注意: 长度不是 intel 次序, 而是 Motorola 的, 高字节在前,
低字节在后!
- 该段的内容, 最多 65533 字节
注意:
- 有一些无参数的段 (下面那些前面注明星号的)
这些段没有长度描述 (而且没有内容), 只有 $ff 和类型字节.
- 段之间无论有多少 $ff 都是合法的, 必须被忽略掉.
段的类型:
~~~~~~~~~
*TEM = $01 可以忽略掉
SOF0 = $c0 帧开始 (baseline JPEG), 细节附后
SOF1 = $c1 dito
SOF2 = $c2 通常不支持
SOF3 = $c3 通常不支持
SOF5 = $c5 通常不支持
SOF6 = $c6 通常不支持
SOF7 = $c7 通常不支持
SOF9 = $c9 arithmetic 编码(Huffman 的一种扩展算法), 通常不支持
SOF10 = $ca 通常不支持
SOF11 = $cb 通常不支持
SOF13 = $cd 通常不支持
SOF14 = $ce 通常不支持
SOF14 = $ce 通常不支持
SOF15 = $cf 通常不支持
DHT = $c4 定义 Huffman Table, 细节附后
JPG = $c8 未定义/保留 (引起解码错误)
DAC = $cc 定义 Arithmetic Table, 通常不支持
*RST0 = $d0 RSTn 用于 resync, 通常被忽略
*RST1 = $d1
*RST2 = $d2
*RST3 = $d3
*RST4 = $d4
*RST5 = $d5
*RST6 = $d6
*RST7 = $d7
SOI = $d8 图片开始
EOI = $d9 图片结束
SOS = $da 扫描行开始, 细节附后
DQT = $db 定义 Quantization Table, 细节附后
DNL = $dc 通常不支持, 忽略
DRI = $dd 定义重新开始间隔, 细节附后
DHP = $de 忽略 (跳过)
EXP = $df 忽略 (跳过)
APP0 = $e0 JFIF APP0 segment marker (细节略)
APP15 = $ef 忽略
JPG0 = $f0 忽略 (跳过)
JPG13 = $fd 忽略 (跳过)
COM = $fe 注释, 细节附后
其它的段类型都保留必须跳过
SOF0: Start Of Frame 0:
~~~~~~~~~~~~~~~~~~~~~~~
- $ff, $c0 (SOF0)
- 长度 (高字节, 低字节), 8+components*3
- 数据精度 (1 byte) 每个样本位数, 通常是 8 (大多数软件不支持 12 和 16)
- 图片高度 (高字节, 低字节), 如果不支持 DNL 就必须 >0
- 图片宽度 (高字节, 低字节), 如果不支持 DNL 就必须 >0
- components 数量(1 byte), 灰度图是 1, YCbCr/YIQ 彩色图是 3, CMYK 彩色图
是 4
- 每个 component: 3 bytes
- component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q)
- 采样系数 (bit 0-3 vert., 4-7 hor.)
- quantization table 号
DRI: Define Restart Interval:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- $ff, $dd (DRI)
- 长度 (高字节, 低字节), 必须是 4
- MCU 块的单元中的重新开始间隔 (高字节, 低字节),
意思是说, 每 n 个 MCU 块就有一个 RSTn 标记.
第一个标记是 RST0, 然后是 RST1 等, RST7 后再从 RST0 重复
DQT: Define Quantization Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- $ff, $db (DQT)
- 长度 (高字节, 低字节)
- QT 信息 (1 byte):
bit 0..3: QT 号(0..3, 否则错误)
bit 4..7: QT 精度, 0 = 8 bit, 否则 16 bit
- n 字节的 QT, n = 64*(精度+1)
备注:
- 一个单独的 DQT 段可以包含多个 QT, 每个都有自己的信息字节
- 当精度=1 (16 bit), 每个字都是高位在前低位在后
DAC: Define Arithmetic Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
法律原因, 现在的软件不支持 arithmetic 编码.
不能生产使用 arithmetic 编码的 JPEG 文件
DHT: Define Huffman Table:
~~~~~~~~~~~~~~~~~~~~~~~~~~
- $ff, $c4 (DHT)
- 长度 (高字节, 低字节)
- HT 信息 (1 byte):
bit 0..3: HT 号 (0..3, 否则错误)
bit 4 : HT 类型, 0 = DC table, 1 = AC table
bit 5..7: 必须是 0
- 16 bytes: 长度是 1..16 代码的符号数. 这 16 个数的和应该 <=256
- n bytes: 一个包含了按递增次序代码长度排列的符号表
(n = 代码总数)
备注:
- 一个单独的 DHT 段可以包含多个 HT, 每个都有自己的信息字节
COM: 注释:
~~~~~~~~~~
- $ff, $fe (COM)
- 注释长度 (高字节, 低字节) = L+2
- 注释为长度为 L 的字符流
SOS: Start Of Scan:
~~~~~~~~~~~~~~~~~~~
- $ff, $da (SOS)
- 长度 (高字节, 低字节), 必须是 6+2*(扫描行内组件的数量)
- 扫描行内组件的数量 (1 byte), 必须 >= 1 , <=4 (否则是错的) 通常是 3
- 每个组件: 2 bytes
- component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q), 见 SOF0
- 使用的 Huffman 表:
- bit 0..3: AC table (0..3)
- bit 4..7: DC table (0..3)
- 忽略 3 bytes (???)
备注:
- 图片数据 (一个个扫描行) 紧接着 SOS 段.