我的第一个demo!

序言

来到实验室差不多快三周了,真的有很多不同的感受了,现在开始记录完成的第一个测试吧。

测试要求

视频帧空域恢复
实验数据:测试序列foreman中两帧组成的YUV序列
实验平台:visual studio
编程语言:C/C++
实验步骤:1)将每帧中的Y通道按指定方式进行丢块操作;2)采用恢复算法进行恢复;3)计算每帧恢复后的PSNR
实验说明:
1、 块的基本尺寸与H.264一致为16x16,每帧的分辨率为704*576,总共两帧,存储方式为YUV420
2、 丢失方式:水平或垂直方向每隔一个宏块丢失一个,如下图所示(每个单元格表示一个宏块)。其中1表示不丢失,0表示丢失,默认左上角第一个宏块不丢失。
1 0 1 0 1 0
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1
3、 恢复算法不限制,可自行调研选择合适的实现,以PSNR为标准,PSNR越高则表示该算法恢复效果越好。(不同算法恢复后的PSNR值不同)
4、 要求保存中间结果,如丢块后的YUV,恢复后的YUV,计算的PSNR值

需要理解的几个地方

1,什么是YUV420?

  YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

  与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。
  YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,关于其详细原理,可以通过网上其它文章了解,这里我想强调的是如何根据其采样格式来从码流中还原每个像素点的YUV值,因为只有正确地还原了每个像素点的YUV值,才能通过YUV与RGB的转换公式提取出每个像素点的RGB值,然后显示出来。
  在YUV420中,一个像素点对应一个Y,一个2X2的小方块对应一个U和V。对于所有YUV420图像,它们的Y值排列是完全相同的,因为只有Y的图像就是灰度图像。YUV420sp与YUV420p的数据格式它们的UV排列在原理上是完全不同的。420p它是先把U存放完后,再存放V,也就是说UV它们是连续的。而420sp它是UV、UV这样交替存放的。(见下图)
  有了上面的理论,我就可以准确的计算出一个YUV420在内存中存放的大小。
  width * hight =Y(总和)
  U = Y / 4  
  V = Y / 4

所以YUV420 数据在内存中的长度是 width * hight * 3 / 2,
  YUV420的存储格式,以一帧704*576大小图像为例;
其存储格式是: 共大小为(704×576×3/2)字节,
分为三个部分:Y,U和V
Y分量:    (704×576)个字节  
U(Cb)分量:(704×576×1/4个字节
V(Cr)分量:(704×576×1/4)个字节
三个部分内部均是行优先存储,三个部分之间是Y,U,V 顺序存储。
即YUV数据的0--704×576字节是Y分量值,         
          704×576--704×576×5/4字节是U分量值,    
          704×576×5/4 --704×576×3/2字节是V分量值。

这里写图片描述

代码如下
#define IMAGE_WIDTH 704  //分辨率
#define IMAGE_HEIGHT 576

#define FRAMECOUNT 2   //帧数
#define YUV_INPUT "foreman.yuv" //输入序列
#define YUV_MASK "mask.yuv" //丢块序列
#define YUV_OUTPUT "output.yuv" //恢复序列

2,如何确定某一像素的位置?

  确定某一像素的位置,实则就是一个二维数组的问题,我们还需要知道像素位置的排列是一行一行,从左到右,从上到下排列的。
int i, j, k, m, n;
        int leftp = 0, rightp = 0, belowp = 0, upperp = 0, current_position = 0; //周围像素值及当前像素的位置
        int leftd = 0, rightd = 0, belowd = 0, upperd = 0; //周围像素与当前像素的距离
        int rows = IMAGE_HEIGHT / 16;
        int cloumns = IMAGE_WIDTH / 16;
        double mse = 0, psnr = 0;
        for (j = 0; j<rows; j++)
        {
            for (i = 0; i<cloumns; i++)
            {
                for (m = 0; m < 16; m++)
                {
                    for (n = 0; n < 16; n++)
                    {
                        current_position = (j * 16 + m)*IMAGE_WIDTH + i * 16 + n; //当前像素的位置

3,丢块操作

   找到某一像素的位置后,只需要把这一位置置0,即完成丢失操作,按照要求就是丢块的情况只有两种:1)奇数行,偶数列;2)偶数行,奇数列;
if (j % 2 == 0 && i % 2 == 1)
{
  mask_yuv_buf[current_position] = 0;
}
else if (j % 2 == 1 && i % 2 == 0)
{
  mask_yuv_buf[current_position] = 0;
}
else
{
  mask_yuv_buf[current_position]=input_yuv_buf[current_position];
}

4,恢复算法

在恢复部分我们采用的是JM8.6里面的错误掩盖的算法,在这里核心函数就是:
static void pixMeanInterpolateBlock( byte *src[], byte *block, int blockSize, int frameWidth )
{
  int row, column, k, tmp, srcCounter = 0, weight = 0, bmax = blockSize - 1;
  k = 0;
  for ( row = 0; row < blockSize; row++ ) 
  {
    for ( column = 0; column < blockSize; column++ ) 
    {
      tmp = 0;
      srcCounter = 0;
      /* above */
      if ( src[4] != NULL )   
      {
        weight = blockSize-row;
        tmp += weight * (*(src[4]+bmax*frameWidth+column));
        srcCounter += weight;
      }
      /* left */
      if ( src[5] != NULL )   
      {
        weight = blockSize-column;
        tmp += weight * (*(src[5]+row*frameWidth+bmax));
        srcCounter += weight;
      }
      /* below */
      if ( src[6] != NULL )   
      {
        weight = row+1;
        tmp += weight * (*(src[6]+column));
        srcCounter += weight;
      }
      /* right */
      if ( src[7] != NULL )   
      {
        weight = column+1;
        tmp += weight * (*(src[7]+row*frameWidth));
        srcCounter += weight;
      }

      if ( srcCounter > 0 )
        block[ k + column ] = (byte)(tmp/srcCounter);
      else 
        block[ k + column ] = 127;
    }
    k += frameWidth;
  }  
}

关于这个函数的详细解析参照http://blog.csdn.net/zhangji1983/article/details/1504824

 理解这个之后就可以完成恢复操作了,代码如下:
//判断上下左右像素是否可用,若超过边界则像素值和距离都设为0,否则采用相应位置的像素值
if (i * 16 - 1 < 0)
{
    leftp = 0;
    leftd = 0;
}
else
{
    leftp = input_yuv_buf[(j * 16 + m)*IMAGE_WIDTH + i * 16 - 1];
    leftd = 16 - n;
}
if (i * 16 + 16 + 1 > IMAGE_WIDTH)
{
    rightp = 0;
    rightd = 0;
}
else
{
    rightp = input_yuv_buf[(j * 16 + m)*IMAGE_WIDTH + i * 16 + 16 + 1];
    rightd = n + 1;
}
if (j * 16 - 1 < 0)
{
    upperp = 0;
    upperd = 0;
}
else
{
    upperp = input_yuv_buf[(j * 16 - 1)*IMAGE_WIDTH + i * 16 + n];
    upperd = 16 - m;
}
if (j * 16 + 16 + 1 > IMAGE_HEIGHT)
{
    belowp = 0;
    belowd = 0;
}
else
{
    belowp = input_yuv_buf[(j * 16 + 16 + 1)*IMAGE_WIDTH + i * 16 + n];
    belowd = m + 1;
}

//丢块的情况只有两种:1)奇数行,偶数列;2)偶数行,奇数列;下面两个if语句可以合并在一起。
//两种操作,一种是置为0,一种是通过上下左右的像素值进行恢复
//如果不在丢失宏块内,则复制输入文件的值
if (j % 2 == 0 && i % 2 == 1)
{
    mask_yuv_buf[current_position] = 0;
    output_yuv_buf[current_position] = (leftp * leftd + rightp * rightd + upperp * upperd + belowp * belowd) / (leftd + rightd + upperd + belowd);
}
else if (j % 2 == 1 && i % 2 == 0)
{
    mask_yuv_buf[current_position] = 0;
    output_yuv_buf[current_position] = (leftp * leftd + rightp * rightd + upperp * upperd + belowp * belowd) / (leftd + rightd + upperd + belowd);
}
else
{
    mask_yuv_buf[current_position]=input_yuv_buf[current_position];
    output_yuv_buf[current_position]=input_yuv_buf[current_position];
}

我把完整代码上传了,里面也有yuv的测试序列,有兴趣的朋友也可以自己下载下来看一下。链接:http://download.csdn.net/detail/rookiemonkey/9914179

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值