从之前的几篇文章介绍可以看出,JPEG编码最重要的一步就是DCT变换,将空域的图像信号转换到频域,达到良好的去空间相关性的性能,DCT变换本身是无损的。因此DCT变换在图像编码领域被广泛应用。

一、一维DCT变换

      在JPEG编码中使用了二维DCT变换,一维DCT是二维的基础,我们先看下一维DCT变换。一维DCT变换共有8种形式,其中最常用的是第二种形式,由于其运算简单、适用范围广。我们在这里只讨论这种形式,其表达式如下:

    其中,f(i)为原始的信号,F(u)是DCT变换后的系数,N为原始信号的点数,c(u)可以认为是一个补偿系数,可以使DCT变换矩阵为正交矩阵。对应的逆DCT变换公式为:

二、二维DCT变换

    二维DCT变换其实是在一维DCT变换的基础上再做了一次DCT变换,其公式如下:

     f(i, j)为原始像素的信号,F(u, v)是DCT变换后对应点位的系数,N为原始信号的点数,c(u)/c(v)可以认为是一个补偿系数。

    由公式我们可以看出,上面只讨论了二维图像数据为方阵的情况,在实际应用中,如果不是方阵的数据一般都是补齐之后再做变换的,重构之后可以去掉补齐的部分,得到原始的图像信息,这个尝试一下,应该比较容易理解。

    如果原始信号是图像等相关性较大的数据的时候,我们可以发现在变换之后,系数较大的集中在左上角,而右下角的几乎都是0,其中左上角的是低频分量,右下角的是高频分量,低频系数体现的是图像中目标的轮廓和灰度分布特性,高频系数体现的是目标形状的细节信息。DCT变换之后,能量主要集中在低频分量处,这也是DCT变换去相关性的一个体现。

      之后在量化和编码阶段,我们可以采用“Z”字形编码,这样就可以得到大量的连续的0,这大大简化了编码的过程。

 三、二维DCT反变换

     在图像的接收端,根据DCT变化的可逆性,我们可以通过二维DCT反变换恢复出原始的图像信息,其公式如下:

 四、分块DCT变换

    在实际的图像处理中,DCT变换的复杂度其实是比较高的,所以通常的做法是,将图像进行分块,然后在每一块中对图像进行DCT变换和反变换,在合并分块,从而提升变换的效率。具体的分块过程中,随着子块的变大,算法复杂度急速上升,但是采用较大的分块会明显减少图像分块效应,所以,这里面需要做一个折中,在JPEG编码中,采用8*8的分块。

 五、代码分析

     从《JPEG编码协议--代码实现》中,我们可以看到代码上对DCT实现的部分,我们再次来分析这块代码:

 1 void _forword_FDCT(const float* channel_data, float* fdct_data)
 2 {
 3     const float PI = 3.1415926f;
 4     for(int v=0; v<8; v++)
 5     {
 6         for(int u=0; u<8; u++)
 7         {
 8             float alpha_u = (u==0) ? 1/sqrt(8.0f) : 0.5f;
 9             float alpha_v = (v==0) ? 1/sqrt(8.0f) : 0.5f;
10 
11             float temp = 0.f;
12             for(int x=0; x<8; x++)
13             {
14                 for(int y=0; y<8; y++)
15                 {
16                     float data = channel_data[y*8+x];
17 
18                     data *= cos((2*x+1)*u*PI/16.0f);
19                     data *= cos((2*y+1)*v*PI/16.0f);
20 
21                     temp += data;
22                 }
23             }
24             temp = alpha_u*alpha_v*temp;
25             fdct_data[v*8+u] = temp;
26         }
27     }
28 }

     在上述的变换实现中,通过for循环实现了8x8的变换块。

     1、第4行和第6行变换后的系数,可以看到此处是8x8变换,会得到8x8个DCT系数;

     2、第8行和第9行求出公式中c(u)和c(v)的值;

     3、第12行和第14行的for循环,对8x8的像素数据做变换。对应公式中的累加部分;

     4、第16行对应为f(i, j)值;

     5、第18/19行,分别求值公式中的两个cos函数;

     6、第21行完成一次累加,完成12~23行循环,结束全部累加;

     7、第24行累加结果和c(u)、c(v)相乘,得到f(u, v)位置的DCT系数;

     8、重复2~7步骤,得到8x8变换块每个位置的系数。

     相应的,我们可以根据公式,得到反变换IDCT的代码实现:

 1 void _forword_IDCT(const float* fdct_data, float* channel_data)
 2 {
 3     const float PI = 3.1415926f;
 4     for(int i=0; i<8; i++)
 5     {
 6         for(int j=0; j<8; j++)
 7         {
 8             float cos_i = (2*i+1)*PI/16.0f;
 9             float cos_j = (2*j+1)*PI/16.0f;
10 
11             float temp = 0.f;
12             for(int u=0; u<8; u++)
13             {
14                 for(int v=0; v<8; v++)
15                 {
16                     float alpha_u = (u==0) ? 1/sqrt(8.0f) : 0.5f;
17                     float alpha_v = (v==0) ? 1/sqrt(8.0f) : 0.5f;                
18                     float data = alpha_u*alpha_v*fdct_data[u*8+v]*cos(cos_i*u)*cos(cos_j*v);
19      
20                     temp += data;
21                 }
22             }
23             channel_data[i*8+j] = temp;
24         }
25     }
26 }

     IDCT变换的代码和FDCT差异不大,结合第三节公式即可看懂;

六、demo测试

    我们通过如下demo来测试公式,代码首先构造8x8数据块,首先做FDCT变换,得到变换系数,之后通过IDCT反变换得到原始数据块。

 1 #include <stdio.h>
 2 #include <math.h>
 3 
 4 void _forword_FDCT(const float* channel_data, float* fdct_data)
 5 {
 6     const float PI = 3.1415926f;
 7     for(int v=0; v<8; v++)
 8     {
 9         for(int u=0; u<8; u++)
10         {
11             float alpha_u = (u==0) ? 1/sqrt(8.0f) : 0.5f;
12             float alpha_v = (v==0) ? 1/sqrt(8.0f) : 0.5f;
13 
14             float temp = 0.f;
15             for(int x=0; x<8; x++)
16             {
17                 for(int y=0; y<8; y++)
18                 {
19                     float data = channel_data[y*8+x];
20 
21                     data *= cos((2*x+1)*u*PI/16.0f);
22                     data *= cos((2*y+1)*v*PI/16.0f);
23 
24                     temp += data;
25                 }
26             }
27             temp = alpha_u*alpha_v*temp;
28             fdct_data[v*8+u] = temp;
29         }
30     }
31 }
32 
33 void _forword_IDCT(const float* fdct_data, float* channel_data)
34 {
35     const float PI = 3.1415926f;
36     for(int i=0; i<8; i++)
37     {
38         for(int j=0; j<8; j++)
39         {
40             float cos_i = (2*i+1)*PI/16.0f;
41             float cos_j = (2*j+1)*PI/16.0f;
42 
43             float temp = 0.f;
44             for(int u=0; u<8; u++)
45             {
46                 for(int v=0; v<8; v++)
47                 {
48                     float alpha_u = (u==0) ? 1/sqrt(8.0f) : 0.5f;
49                     float alpha_v = (v==0) ? 1/sqrt(8.0f) : 0.5f;                
50                     float data = alpha_u*alpha_v*fdct_data[u*8+v]*cos(cos_i*u)*cos(cos_j*v);
51      
52                     temp += data;
53                 }
54             }
55             channel_data[i*8+j] = temp;
56         }
57     }
58 }
59 
60 #define PRINTF_BLOCK(p)    do{  \
61                                 for(int i=0; i<8; i++) \
62                                 {                        \
63                                     for(int j=0; j<8; j++) \
64                                         printf("%5.2f ", p[i*8+j]); \
65                                     printf("\n");  \
66                                 }  \
67                                 printf("\n");\
68                             }while(0);
69 int main(int argc, char *argv[])
70 {
71     float data[64] = {
72         1, 1, 1, 1, 1, 1, 1, 1,
73         1, 1, 1, 1, 1, 1, 1, 1,
74         1, 1, 2, 2, 2, 2, 1, 1,
75         1, 1, 2, 3, 3, 2, 1, 1,
76         1, 1, 2, 3, 3, 2, 1, 1,
77         1, 1, 2, 2, 2, 2, 1, 1,
78         1, 1, 1, 1, 1, 1, 1, 1,
79         1, 1, 1, 1, 1, 1, 1, 1
80     };
81     float fdct_data[64];
82     PRINTF_BLOCK(data);
83     _forword_FDCT(data, fdct_data);
84     PRINTF_BLOCK(fdct_data);
85     _forword_IDCT(fdct_data, data);
86     PRINTF_BLOCK(data);
87     return 0;
88 }
View Code

     运行结果如下,可以看到经过FDCT变换后,数据复杂度明显降低,且IDCT后可以完整的还原原始数据。

 

posted on 2023-06-13 23:19  沉默的思想  阅读(224)  评论(0编辑  收藏  举报