一、视频编码题目
给定视频序列“foremam.qcif”,利用C语言完成视频编码。
这里我们只编第一帧和第二帧。
二、 程序设计步骤
1、初始化参数设置。初始化编码2帧图像需要的参数,比如视频分辨率,图像宏块划分,DCT变化矩阵,量化参数QP、Qstep等设置。
2、读取视频序列帧。读取原始视频序列“foremam_qcif.yuv”的第一帧和第二帧,设置第一帧为I帧,第二帧为P帧,存入分配好内存的数组,数组指针指向第一个像素。
2、I帧编码。对I帧的所有像素减去128得到残差,即用128的像素值对I帧进行预测,可以将0-255的像素值平移到-128到127,方便后续进行变换和量化操作。将I帧所有像素划分为4×4宏块,循环对每一个宏块依次进行DCT变换、量化,然后进行反量化和反变换,将结果写入“re_foremam_qcif.yuv”。
3、P帧编码。将P帧减去第二步重建I帧得到残差像素,即P帧预测结果。将残差图像划分为4×4宏块,循环对每一个宏块依次进行DCT变换、量化,然后进行反量化和反变换,将结果接着写入“re_foremam_qcif.yuv”。
4、编码结束,释放内存和指针。计算PSNR。
三、代码
//author-JackTuo
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<malloc.h>
#include<math.h>
//矩阵求积函数
void matrixMultiply(int a[4], int b[4], int c[4])
{
int i, j;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
c[i*4+j] = a[i*4] * b[j] + a[i * 4 +1] * b[4+j] + a[i * 4 +2] * b[8 + j] + a[i * 4 +3] * b[12 + j];
}
}
}
//量化函数
void quant_4x4(int dct[16], int mf[16], int bias)
{
for (int i = 0; i < 16; i++)
{
if (dct[i] < 0)
dct[i] = -((abs(dct[i]) * mf[i] + bias) >> 20);
else
dct[i] = (abs(dct[i]) * mf[i] + bias) >> 20;
}
}
//反量化函数
void dequant_4x4(int dct[16], int dequant_mf[16], int i_qp)
{
int t = i_qp / 6;
int i_mf = (int)(pow(2 ,t));
//const int i_qbits = i_qp / 6 - 4;
for (int i = 0; i < 16; i++)
dct[i] = dct[i] * dequant_mf[i] * i_mf;
}
//PSNR计算
int PSNR(int mse)
{
int psnr;
psnr = 10 * log10(pow(255 ,2)/ mse);
return psnr;
}
void main()
{
//初始化参数设置
int width = 176, height = 144;
int mbw = width / 4, mbh = height*3 / 8;
int mf[16];
mf[0] = mf[2] = mf[8] = mf[10] = 11916;
mf[5] = mf[7] = mf[13] = mf[15] = 4660;
mf[1] = mf[3] = mf[4] = mf[6] = mf[9] = mf[11] = mf[12] = mf[14] = 7490;
int qp = 31, qstep = 22;
int qbits = 15 + qp / 6;
int f1 = pow(2, qbits) / 3;
int f2 = pow(2, qbits) / 6;
int dequantmf[16];
dequantmf[0] = dequantmf[2] = dequantmf[8] = dequantmf[10] = 11;
dequantmf[5] = dequantmf[7] = dequantmf[13] = dequantmf[15] = 18;
dequantmf[1] = dequantmf[3] = dequantmf[4] = dequantmf[6] = dequantmf[9] = dequantmf[11] = dequantmf[12] = dequantmf[14] = 14;
//DCT matrix
int cf[16], ci[16], cfc[16], cic[16];
cf[0] = cf[1] = cf[2] = cf[3] = cf[5] = cf[8] = cf[11] = cf[12] = 1;
cf[6] = cf[9] = cf[10] = cf[15] = -1;
cf[4] = cf[14] = 2;
cf[7] = cf[13] = -2;
cfc[0] = cfc[2] = cfc[3] = cfc[4] = cfc[5] = cfc[8] = cfc[12] = cfc[14] = 1;
cfc[6] = cfc[9] = cfc[10] = cfc[15] = -1;
cfc[1] = cfc[11] = 2;
cfc[7] = cfc[13] = -2;
ci[0] = ci[1] = ci[2] = ci[4] = ci[8] = ci[11] = ci[12] = ci[14] = 2;
ci[6] = ci[7] = ci[10] = ci[13] = -2;
ci[3] = ci[5] = 1;
ci[9] = ci[15] = -1;
cic[0] = cic[1] = cic[2] = cic[3] = cic[4] = cic[8] = cic[11] = cic[14] = 2;
cic[7] = cic[9] = cic[10] = cic[13] = -2;
cic[5] = cic[12] = 1;
cic[6] = cic[15] = -1;
//读取视频序列第1、2帧
FILE* fp = fopen("E:\\foreman_qcif.yuv", "rb");
if (fp == NULL)
{
printf("打开视频文件失败! \n");
return -1;
}
unsigned char* frame = (unsigned char*)malloc(width * height * 3);
unsigned char* reframe = (unsigned char*)malloc(width * height * 3);
fread(frame, 1, width * height * 3, fp); //将读取的视频帧存入数组
//code第1帧
for (int i = 0; i < mbh; i++)
{
for (int j = 0; j < mbw; j++)
{
int dct[16],d[16],d1[16];
//宏块残差传递
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
d[x + y * 4] = frame[i * mbw * 16 + j * 4 + x + y * 176]-128;
}
}
//DCT变换
matrixMultiply(cf, d, d1);
matrixMultiply(d1, cfc, dct);
//量化
quant_4x4(dct, mf, f1);
//反量化
dequant_4x4(dct, dequantmf,qp);
//反变换
matrixMultiply(ci,dct,d1);
matrixMultiply(d1,cic,dct);
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
reframe[i * mbw * 16 + j * 4 + x + y * 176] = dct[x + y * 4]/256 +128;
}
}
}
}
//code第2帧
for (int i = 0; i < mbh; i++)
{
for (int j = 0; j < mbw; j++)
{
int dct[16], d[16], d1[16];
//宏块残差传递
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
d[x + y * 4] = frame[width*height*3/2 + i * mbw * 16 + j * 4 + x + y * 176]- reframe[i * mbw * 16 + j * 4 + x + y * 176];
}
}
//变换
matrixMultiply(cf, d, d1);
matrixMultiply(d1, cfc, dct);
//量化
quant_4x4(dct, mf, f2);
//反量化
dequant_4x4(dct, dequantmf, qp);
//反变换
matrixMultiply(ci, dct, d1);
matrixMultiply(d1, cic, dct);
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
reframe[width*height*3/2 +i*mbw*16 + j * 4 + x + y * 176] = dct[x + y * 4] / 256 + reframe[i * mbw * 16 + j * 4 + x + y * 176];
}
}
}
}
//Writefile
FILE* fp1 = fopen("E:\\re_foreman_qcif.yuv", "wb+"); //保存编码后的视频帧
fwrite(reframe, 1, width * height * 3, fp1);
//计算PSNR
int ssd = 0;
int mse , psnr;
for (int i = 0; i < width * height*3; i++)
{
ssd += (reframe[i] - frame[i]) * (reframe[i] - frame[i]);
}
mse =(ssd / (width * height*3));
psnr = PSNR(mse);
printf("视频编码结束 \n PSNR=%d \n", psnr);
//程序结束关闭文件,释放内存
fclose(fp);
fclose(fp1);
free(frame);
free(reframe);
}
四、结果分析
1、宏块对比分析
如上图所示,其中“foremam_qcif.yuv”为原视频文件,“re_foremam_qcif.yuv”为编码后再解码的视频文件。随机抽选第一帧和第二帧的宏块,对比编码前后视频帧宏块的数据差异,可知编码前后视频帧宏块像素大致一样,误差较小。
2、主观质量分析
如上图所示,使用YUVPlayer播放器来播放测试的YUV视频文件。其中“foremam_qcif.yuv”为原视频文件,“re_foremam_qcif.yuv”为编码后再解码的视频文件。通过人眼主观对比,视频帧差距不大,但细节不够清晰。可以通过设置QP值来提高编码质量,丰富细节。QP值越小,细节越清晰,但码率会变大。需要通过率失真优化和码率控制来权衡整体编码参数选择。
ps:视频编码交流学习,多多指正。