先取yuv 文件中一个16×8的块,跑通全流程
理解与思路:
1.块分割
YUV 文件分为:YUV444 YUV 422 YUV420。444:就是:12个char 有4个Y,4个U,4个 U,422:8个char 中有4个Y ,U,V各两个,420:意思就是8char里有6个Y,1个U,1个V。444与422 中的三分量多是交错存储的,420则是先存储Y,再存储U,V。
YUV存储也是线型存储的,不是平面块存储的。
对应到jpeg,也要按YUV的三种格式分别分块。jpeg协议中有一概念MCU:最小编码块。jpeg就是按MCU 为单位循环存储的。MCU中又有若干8×8 的block。这些block 就是Y ,U ,V 分量的8×8 块。
我理解:对应YUV444, 一行内取24字节(这是指水平行,程序处理同时要取8个水平行,意思MCU=8×24字节)。含有Y,U,V各8个字节。也就是说:程序一次读取8行24个char ,处理成3张8×8的表,直到读完整个yuv文件
YUV422:一行 8个char中有4个Y,UV各2个。必须把Y凑成8那就要乘2。那就是一行为16个字节,MCU=16×8,取8行生成一张8×8的Y,UV 各半张,UV要补0成为8×8。
YUV420:一行8个字节中有6个Y,UV各一个,Y×8成为8的倍数水平要取64字节,所以,MCU=8×8×8,意思就是有6张Y表,UV 各一张。
离散余弦变换
找了一组数据来验证,发现余弦转换除了直流分量外都正确,DC不正确,也就是转换后第一个数不正确,才想到第一个数的取值应该超出了char的取值范围-128
2.量化
Jpeg几乎对Y分量不压缩处理,只压缩UV彩色分量。把几个Y拼在一起余弦处理因为余弦转化是没有损失的,也不会让数据失真。真正让数据失真是量化这个环节,如果量化表全为1,则是无损量化。
下面的程序为借用成品图片的量化表。
3. Z形排序
采用查表法,先按Z顺序生成一个表,读数时按表的数值作为读取位置。
4. 去0
64个数中按位读取每一个数有4种情况:
a) 本为是0,下一位也是0,但下一位不是数据结尾
b)本位是0,下一位是0,但下一位是结尾
c)本位是0,下一位不是0
d)本位不是0
判断生成的结果存储到2位数组中,第一位存储非0数字,第二位存储0的个数
5. 规范RLC 格式:去0后的数如中间有超过15个0,必须拆分; 结尾如有多个0改写为(0,0)
Z 型排序这理还有一个细节错误,应该把直流DC 分出来,只排63个交流系数。
等霍夫曼编码时再修改过来
6. 范式霍夫曼编码
准备借用成品jpeg图片的霍夫曼表来编码。
这里还有几个问题没有搞明白,编码是变长度的比特流不是字节,而c操作的最小单位是字节char, 是怎样把比特流写入内存甚至文件的,难道是以比特流按8个比特为一个单位组成一个字节再写入?
从网上下载了一数据表验证本程序,从余弦转换开始到霍夫曼编码环节止数据只有4舍5入造成的微小差别。
#include
#include
#include
#include
#include
#include
#include
#include //v4l2 头文件
#include
#include
#include
#include
#define PI 3.1415926
#define pic_width 1280 //1280*720 640*360 960*540 320*240 424*240 848*480 640*480
#define pic_heigth 720
#define filename "/home/wjs/Pictures/1.yuv"
#define file1 "/home/wjs/Pictures/1.jpg" //借用成品图片的量化表
//int fdct(char (*i)[8], char (*o)[8] );
int main(void) {
//-----------FDCT()函数------------------------------------
int fdct(char (*i)[8], int(*o)[8] ) { //i 为输入 o 为参数传入的输出转换后的数据
double s;
double au;
double av;
for (int u = 0; u < 8; u++) {
for (int v = 0; v < 8; v++) {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
s = s + (1.0 / 4) * i[y][x] * cos((2 * y + 1) * u * PI / 16) * cos((2 * x + 1) * v * PI / 16);
}
}
if (u == 0) {
au = 1.0 / sqrt(2);
} else {
au = 1.0;
}
if (v == 0) {
av = 1.0 / sqrt(2);
} else {
av = 1.0;
}
s = s * au * av; //-30.1856
int s1 = round(s * 100); //-3019
s = s1 / 100.0; //-30.19
o[u][v] = s; //double 转为char 类型
s = 0;
}
}
return 0;
}
//-----------规范RLC格式---------------------
int zl(int len,int (*i)[2],int (*o)[2]){
int t=0; //如果中间有一次超过15个0,o的下标要加一,因为增加了(15,0)
for(int a=0;a=16)&&(i[a][0]!=0)){
o[a+t][0]=0;
o[a+t][1]=15;
o[a+1+t][0]=i[a][0];
o[a+1+t][1]=i[a][1]-15;
t++;
}
if((a0)&&(i[a] != 0)&&(i[a-1]!=0)) { //防止第3种重复读取,这种是读取连续的非0
o[t][0] = i[a];
o[t][1] = 0;
t++;
}
}
return t+1;
}
//--------Z 排序--------------------------------
int zz(int (*i)[8], int o[64]) {
int zb[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
};
int *p = (int *)i;
for (int a = 0; a < 64; a++) {
int c = zb[a];
o[a] = p[c];
}
return 0;
}
// -----量化函数---------------
int lh(int (*i)[8], char (*lhb)[8], int (*o)[8]) {
for (int a = 0; a < 8; a++) {
for (int b = 0; b < 8; b++) {
o[a][b] = round((i[a][b]) / (lhb[a][b]));
}
}
return 0;
}
FILE *f1 = fopen(file1, "rb"); //如用mmap 必须以读写方式打开文件
if (f1 == NULL) {
puts("filename error");
exit(-1);
}
fseek(f1, 0, SEEK_END);
int len1 = ftell(f1);
fseek(f1, 0, SEEK_SET);
int fd1 = fileno(f1);
unsigned char *mp1 = mmap(NULL, len1, PROT_READ, MAP_SHARED, fd1, 0); //必须要读,写
//---------读量化表-------------------------------
char lh00[64] = {}; //提取量化表
char lh10[64] = {};
for (int a = 0; a < len1; a++) {
if ((mp1[a] == 0xff) && (mp1[a + 1] == 0xdb) && (mp1[a + 2] == 0)) {
for (int b = 0; b < 65; b++) {
if (mp1[a + b + 4] == 0) {
memcpy(lh00, &(mp1[a + b + 5]), 64);
}
if (mp1[a + b + 4] == 1) {
memcpy(lh10, &(mp1[a + b + 5]), 64);
}
}
// printf("\n");
}
}
//---------------------------------------------------------------------------
FILE *f = fopen(filename, "rb"); //如用mmap 必须以读写方式打开文件
if (f == NULL) {
puts("filename error");
exit(-1);
}
fseek(f, 0, SEEK_END);
int len_file = ftell(f);
fseek(f, 0, SEEK_SET);
int fd = fileno(f);
unsigned char *mp = mmap(NULL, len_file, PROT_READ, MAP_SHARED, fd, 0); //必须要读,写
int width_pic = 1280;
int heigth_pic = 720;
//out:1.文件总长度 len_file
// 2.图片宽度 1280 width_pic
// 3.图片高度 720 heigth_pic
// 4.图片数据 *mp
//--------------------------------------------------------
//因为yuv422数据是4个字节生成2个像素点,所以图片的width*heigth*2=len_file
//摄像头输出的分辨率图片宽度×2都是8的倍数,高度X2也是8的倍数。所以yuv数据都不用补列。最多补文件末尾的空字节
//摄像头的分辨率末尾数X2=0,8,6,4,2,就是8的倍数
// 1.----------检查yuv文件长度末尾是否有空字节,有,补0-------------------
if ((width_pic * heigth_pic * 2) != len_file) {
memset(&mp[len_file], 0, (width_pic * heigth_pic * 2 - len_file));
}
// 2.----------取16*8--------------------------------------
//YUV422 字节排列:[0]=Y0 [1]=U0 [2]=Y1 [3]=V0 [4]=Y2 [5]=U1 [6]=Y3 [7]=V1 4个Y 2个U 2个V
//所以取16个字节的yuv 数据,含8个y ,4个u,4个v
unsigned char (*i_mp)[width_pic] = (unsigned char (*)[width_pic])mp;
int m = 0; // 行 取1个(0,0)开始的16×8 块
int n = 0; //列
int heigth = heigth_pic; //行
int width = width_pic; //列
int fheigth = 8;
int fwidth = 16;
unsigned char o[8][16] = {}; //取出16×8的块
if ((m + fheigth) > heigth) {
puts("fheigth error");
exit(-1);
}
if ((n + fwidth) > width) {
puts("fwidth error");
exit(-1);
}
for (int a = 0; a < fheigth; a++) {
for (int b = 0; b < fwidth; b++) {
o[a][b] = i_mp[a + m][b + n];
}
}
munmap(mp, len_file);
//----------分离Y---------------------
char y[8][8] = {};
for (int a = 0; a < 8; a++) {
for (int b = 0; b < 8; b++) {
y[a][b] = o[a][2*b]-128;
}
}
/* char y1[8][8]={{140,144,147,140,140,155,179,179},//验证程序数据
{144,152,140,147,140,148,167,179},
{152,155,136,167,163,162,152,172},
{168,145,156,160,152,155,136,160},
{162,148,156,148,140,136,147,162},
{147,167,140,155,155,140,136,162},
{136,156,123,167,162,144,140,147},
{148,155,136,155,152,147,147,136}
};
char y[8][8]={};
for(int a=0;a<8;a++){
for(int b=0;b<8;b++){
y[b][a]=y1[b][a]-128;
}
}*/
//----------分离U-----------------
char u[8][8] = {};
memset(&u, 0, 64); //补0
for (int a = 0; a < 8; a++) {
for (int b = 0; b < 8; b++) {
if ((4 * b + 1) <= 16) {
u[a][b] = o[a][4 * b + 1] - 128;
}
}
}
//-----------分离v-----------------
char v[8][8] = {};
memset(&v, 0, 64); //补0
for (int a = 0; a < 8; a++) {
for (int b = 0; b < 8; b++) {
if ((4 * b + 3) <= 16) {
v[a][b] = o[a][4 * b + 3] - 128;
}
}
}
//--------------FDCY Y- U V----------------------------
int y_fdct[8][8] = {};
fdct(y,y_fdct);
int u_fdct[8][8] = {};
fdct(u, u_fdct);
int v_fdct[8][8] = {};
fdct(v, v_fdct);
//----------------量化 Y U V -----------------------------------
//借用成品jpg图片量化表,lh0,lh1
char (*lh0)[8] = (char (*)[8])lh00;
char (*lh1)[8] = (char (*)[8])lh10;
/* char lh0[8][8]={ // 验证数据用量化表
{3,5,7,9,11,13,15,17},
{5,7,9,11,13,15,17,19},
{7,9,11,13,15,17,19,21},
{9,11,13,15,17,19,21,23},
{11,13,15,17,19,21,23,25},
{13,15,17,19,21,23,25,27},
{15,17,19,21,23,25,27,29},
{17,19,21,23,25,27,29,31}
};*/
int y_lh[8][8] = {};
int u_lh[8][8] = {};
int v_lh[8][8] = {};
lh(y_fdct, lh0, y_lh);
lh(u_fdct, lh1, u_lh);
lh(v_fdct, lh1, v_lh);
//---------Z排序--------------------------
int y_z[64] = {};
int u_z[64] = {};
int v_z[64] = {};
zz(y_lh, y_z);
zz(u_lh, u_z);
zz(v_lh, v_z);
//--------去0-------------------------
int y_0[64][2] = {};
int u_0[64][2] = {};
int v_0[64][2] = {};
int len_y_0 = q0(y_z, y_0);
int len_u_0 = q0(u_z, u_0);
int len_v_0 = q0(v_z, v_0);
//-----整理规范------------------------
int y_zl[64][2]={}; //定义是必须指定数组内存大小,下标不能用变量,64是此数组的最大值
int u_zl[64][2]={};
int v_zl[64][2]={};
int len_y_zl=zl(len_y_0,y_0,y_zl);
int len_u_zl=zl(len_u_0,u_0,u_zl);
int len_v_zl=zl(len_v_0,v_0,v_zl);
//---------------------------------------------------------------------
for (int a = 0; a