图象的色彩空间

YUV
   在现代彩色电视系统中,通常采用三管彩色 摄像机或彩色CCD(点耦合器件)摄像机,它把摄得的彩色图像 信号,经分色、分别放大校正得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y、B-Y,最后发送端将亮度和色差三个信号分别进行编码,用同一信道发送出去。这就是我们常用的YUV色彩空间。

  RGB

  RGB中的R G B 都代表什么 Red Green Blue

  YUV (YCrCb)和4:2:2, 4:1:1, 4:2:0 是指亮度信号Y和红/蓝色差信号的抽样格式. 在dv中, ntsc是4:1:1, pal采用4:2:0. 注意, 4:2:0并非蓝色差信号采样为0,而是和4:1:1相比,在水平方向上提高1倍色差采样频率,在垂直方向上以Cr/Cb间隔的方式减小一半色差采样.

   RGB TO YUV转换原理及代码示例

    转自:http://hi.baidu.com/study_then/blog/item/3f04b1c3c36f6954b319a8f7.html


http://www.ishowu.cn/blog/html/diary/showlog.vm?sid=1&cat_id=2&log_id=9中有对颜色空间的详细介绍,可以参考上述材料对RGBYUV颜色空间了解一下。

由于H.264等压缩算法都是在YUV的颜色空间上进行的,所有在进行压缩前,首先要进行颜色空间的转换。如果摄像头采集的资源是RGB的,那么首先要转换成YUV,如果是YUV的,那么要根据压缩器具体支持的YUV格式做数据的重排。本文以RGB24àYUV420(YV12)为例,讲解颜色空间转换的原理。

数据表述方式

320*240的一帧图像为例RGB24的排列方式如下图所示:

每个像素点有三个字节组成分别表示R,G,B分量上的颜色值。在数据中的表示方式为一个像素 一个像素表示。字节流可以表述如下:

BGRBGRBGRBGRBGR……

|---------------320*240*3-------|

每一个字母表示一个字节,也就是该颜色分量的数值,相邻的三个BGR字节表示一个像素点。在我们做计算时,通常一次取三个字节,也就是一个像素点。

相应的YV12的排列方式如下图所示:

每个像素点都有一个Y分量,每隔一列就有一个U或者V分量,UV交替出现。YV12的字节流表示方式和RGB24有很大区别,YV12并不是按照像素依次排列的,而是先放置Y空间,然后放置整个V空间,最后放置U空间,那么字节流如下所示:

YYYYYYY……VVVV……UUUU……

|-----320*240----|-320*240/4-|-320*240/4-|

320*240个字节的Y后,紧跟着320*240/4V320*240/4U

YV12RGB24同样都有320*240个像素点,但是在数据结构和字节流上有着很大区别。单纯从数据大小来看,RGB24的数据大小为320*240*3Bytes,YV12320*240*1.5Bytes,可见YV12的数据量为RGB24的一半。

转换公式

明白了数据表述方式之后,只要把对应像素点的RGB数值,分别计算成对应的YUV值,然后通过YUV的字节流样式把数据表述出来就可以了,这里,首先介绍一下RGBYUV转换公式。

     Y= 0.3*R + 0.59*G + 0.11*B

     U= (B-Y) * 0.493

V= (R-Y) * 0.877

同样反过来,YUV转换成RGB的公式如下:

R = Y + 1.14V

G = Y - 0.39U - 0.58V

B = Y + 2.03U

代码示例

下面给出了RGB24YV12YUV420)的转换代码示例(C++):

     uint_8_t * pSrc=;// this is RGB bit stream

     uint_8_t * YUV_Image=new uint_8[320*240*3/2];// YUV420 bit stream

int i=0,j=0;

     int width=320; // width of the RGB image

     int height=240; // height of the RGB image

     int uPos=0, vPos=0;

     for( i=0;i< height;i++ ){

         bool isU=false;

         if( i%2==0 ) isU=true; // this is a U line

         for( j=0;j<width;j++ ){

              int pos = width * i + j; // pixel position

              uint_8_t B = pSrc[pos*3];

              uint_8_t G = pSrc[pos*3+1];

              uint_8_t R = pSrc[pos*3+2];

              uint8_t Y= (uint8_t)(0.3*R + 0.59*G + 0.11*B);

              uint8_t U= (uint8_t)((B-Y) * 0.493);

              uint8_t V= (uint8_t)((R-Y) * 0.877);

              YUV_Image[pos] = Y;

              bool isChr=false;  // is this a chroma point

              if( j%2==0 ) isChr=true;

              if( isChr && isU ){

                   YUV_Image[plane+(plane>>2)+uPos]=U;

              }

              if( isChr&& !isU ){

                   YUV_Image[plane+vPos]=V;

              }

         }

}



 附件是采样格式编码的具体描述,对于YUV 4:2:0编码方式和上面叙述的比较,根据附件末尾的采样图来看,对于象素320*240的一幅图片,因为在行上是交错的,所以,对于色差分量来说就320/2了,而在列上又是2个Y一个色差的关系,所以240/2了,故一个色差信号的信号大小就为320x240/4了.



文件:rgb2yuv.rar
大小:38KB
下载:下载

----------------------------------------------------
图象的色彩空间(yuv2rgb)
我在上一篇BLOG文章中介绍了RGB2YUV的转换,见 图象的色彩空间
本文章是一篇从YUV2RGB转换的文章,有理论基础和示例代码可提供参考。

RGB,YUV的来历及其相互转换
FROM:http://www.winbile.net/BBS/1027381/ShowPost.aspx
    在视频等相关的应用中, YUV 是一个经常出现的格式。本文主要以图解的资料的形式详细描述 YUV RGB 格式的来由,相互关系以及转换方式,并对 C 语言实现的 YUV 转为 RGB 程序进行介绍。

         人类眼睛的色觉,具有特殊的特性,早在上世纪初,Young1809)和Helmholtz1824)就提出了视觉的三原色学说,即:视网膜存在三种视锥细胞,分别含有对红、绿、蓝三种光线敏感的视色素,当一定波长的光线作用于视网膜时,以一定的比例使三种视锥细胞分别产生不同程度的兴奋,这样的信息传至中枢,就产生某一种颜色的感觉。

       70年代以来,由于实验技术的进步,关于视网膜中有三种对不同波长光线特别敏感的视锥细胞的假说,已经被许多出色的实验所证实 例如:①有人用不超过单个视锥直径的细小单色光束,逐个检查并绘制在体(最初实验是在金鱼和蝾螈等动物进行,以后是人)视锥细胞的光谱吸收曲线,发现所有绘制出来的曲线不外三种类型,分别代表了三类光谱吸收特性不同的视锥细胞,一类的吸收峰值在420nm处,一类在534nm处,一类在564nm处,差不多正好相当于蓝、绿、红三色光的波长。与上述视觉三原色学说的假设相符。②用微电极记录单个视锥细胞感受器电位的方法,也得到了类似的结果,即不同单色光所引起的不同视锥细胞的超极化型感受器电位的大小也不同,峰值出现的情况符合于三原色学说。


   
于是,在彩色显示器还没有发明的时候,人类已经懂得使用三原色光调配出所有颜色的光。并不是说三原色混合后产生了新的频率的光,而是给人眼睛的感觉是这样。

                     
         
在显示器发明之后,从黑白显示器发展到彩色显示器,人们开始使用发出不同颜色的光的荧光粉(
CRT,等离子体显示器),或者不同颜色的滤色片(LCD),或者不同颜色的半导体发光器件(OLEDLED大型全彩显示牌)来形成色彩,无一例外的选择了Red,Green,Blue3种颜色的发光体作为基本的发光单元。通过控制他们发光强度,组合出了人眼睛能够感受到的大多数的自然色彩。

         计算机显示彩色图像的时候也不例外,最终显示的时候,要控制一个像素中Red,Green,Blue的值,来确定这个像素的颜色。计算机中无法模拟连续的存储从最暗到最亮的量值,而只能以数字的方式表示。于是,结合人眼睛的敏感程度,使用3个字节(3*8位)来分别表示一个像素里面的Red,GreenBlue的发光强度数值,这就是常见的RGB格式。我们可以打开画图板,在自定义颜色工具框中,输入r,g,b值,得到不同的颜色。

        
        
 
但是对于视频捕获和编解码等应用来讲,这样的表示方式数据量太大了。需要想办法在不太影响感觉的情况下,对原始数据的表示方法进行更改,减少数据量。

         无论中间处理过程怎样,最终都是为了展示给人观看,这样的更改,也是从人眼睛的特性出发,和发明RGB三原色表示方法的出发点是一样的。

         于是我们使用Y,Cb,Cr模型来表示颜色。Iain的书中写道:The human visual system (HVS) is less sensitive to colour than to luminance (brightness).人类视觉系统(其实就是人的眼睛)对亮度的感觉比对颜色更加敏感。

         RGB色彩空间中,三个颜色的重要程度相同,所以需要使用相同的分辨率进行存储,最多使用RGB565这样的形式减少量化的精度,但是3个颜色需要按照相同的分辨率进行存储,数据量还是很大的。所以,利用人眼睛对亮度比对颜色更加敏感,将图像的亮度信息和颜色信息分离,并使用不同的分辨率进行存储,这样可以在对主观感觉影响很小的前提下,更加有效的存储图像数据。

         YCbCr色彩空间和它的变形(有时被称为YUV)是最常用的有效的表示彩色图像的方法。Y是图像的亮度(luminance/luma)分量,使用以下公式计算,为R,G,B分量的加权平均值:

         Y = kr R + kgG + kbB

        其中k是权重因数。

        上面的公式计算出了亮度信息,还有颜色信息,使用色差(color difference/chrominancechroma)来表示,其中每个色差分量为R,G,B值和亮度Y的差值:

  Cb = B Y

  Cr = R Y

Cg = G Y

其中,Cb+Cr+Cg是一个常数(其实是一个关于Y的表达式),所以,只需要其中两个数值结合Y值就能够计算出原来的RGB值。所以,我们仅保存亮度和蓝色、红色的色差值,这就是(Y,Cb,Cr)

相比RGB色彩空间,YCbCr色彩空间有一个显著的优点。Y的存储可以采用和原来画面一样的分辨率,但是Cb,Cr的存储可以使用更低的分辨率。这样可以占用更少的数据量,并且在图像质量上没有明显的下降。所以,将色彩信息以低于量度信息的分辨率来保存是一个简单有效的图像压缩方法。

COLOUR SPACES .17 ITU-R recommendation BT.601 中,建议在计算Y时,权重选择为kr=0.299,kg=0.587,kb=0.114。于是常用的转换公式如下:

Y = 0.299R + 0.587G + 0.114B

Cb = 0.564(B Y )

Cr = 0.713(R Y )

 

R = Y + 1.402Cr

G = Y - 0.344Cb - 0.714Cr

B = Y + 1.772Cb

有了这个公式,我们就能够将一幅RGB画面转换成为YUV画面了,反过来也可以。下面将画面数据究竟是以什么形式存储起来的。

RGB24格式中,对于宽度为w,高度为h的画面,需要w*h*3个字节来存储其每个像素的rgb信息,画面的像素数据是连续排列的。按照r(0,0),g(0,0),b(0,0);r(0,1),g(0,1),b(0,1);…;r(w-1,0),g(w-1,0),b(w-1,0);…;r(w-1,h-1),g(w-1,h-1),b(w-1,h-1)这样的顺序存放起来。

YUV格式中,以YUV420格式为例。宽度为w高度为h的画面,其亮度Y数据需要w*h个字节来表示(每个像素点一个亮度)。而CbCr数据则是画面中4个像素共享一个Cb,Cr值。这样Cbw*h/4个字节,Crw*h/4个字节。

YUV文件中,把多个帧的画面连续存放。就是YUV YUV YUV…..这样的不断连续的形式,而其中每个YUV,就是一幅画面。

在这单个YUV中,前w*h个字节是Y数据,接着的w*h/4个字节是Cb数据,再接着的w*h/4个字节为Cr数据。

在由这样降低了分辨率的数据还原出RGB数据的时候,就要依据像素的位置找到它对应的Y,Cb,Cr值,其中Y值最好找到,像素位置为x,y的话,Y数据中第y*width+x个数值就是它的Y值。CbCr由于是每2x2像素的画面块拥有一个,这样CbCr数据相当于两个分辨率为w/2 * h/2的画面,那么原来画面中的位置为x,y的像素,在这样的低分辨率画面中的位置是x/2,y/2,属于它的Cb,Cr值就在这个地方:(y/2)*(width/2)+(x/2)

为了直观起见,再下面的图中,分别将Y画面(Cb,Cr=0)Cb,Cr画面(Y=128)显示出来,可见Cb,Cr画面的分辨率是Y画面的1/4。但是合成一个画面之后,我们的眼睛丝毫感觉不到4个像素是共用一个Cb,Cr的。


        Y
画面


        Cb,Cr
画面

Cb,Cr画面放大观察,里面颜色相同的块都是2x2大小的。

附件为Windows Mobile上使用公式进行YUVRGB转换的程序。其中需要注意的是Cb,Cr在计算过程中是会出现负数的,但是从-128127这些数值都用一个字节表示,读取的时候就映射0255这个区间,成为了无符号的值,所以要减去128,才能参与公式计算。这样的运算有浮点运算,效率是比较低的,所以要提高效率的话,一般在实用程序中使用整数计算或者查表法来代替。还有,运算后的r,g,b可能会超过0-255的区间,作一个判断进行调整就可以了。



下面这个函数是对上面的理论的非常好的实现:

// convert yuv12 or yuv420 into rgb mode for further dispaly operations
void CXvidDecTestDlg::convert_yuv420_rgb(unsigned char * src, unsigned char * dst,
                                         int width, int height, int flipUV, int ColSpace)
{
    unsigned char *Y, *U, *V ;
    int y1, y2, u, v ;
    int v1, v2, u1, u2 ;
    unsigned char *pty1, *pty2 ;
    int i, j ;
    unsigned char *RGB1, *RGB2 ;
    int r, g, b ;

    //Initialization
    Y = src;
    V = Y + width * height;
    U = Y + width * height + width * height / 4;

    pty1 = Y;
    pty2 = pty1 + width;
    RGB1 = dst;
    RGB2 = RGB1 + 3 * width;
    for (j = 0; j < height; j += 2) {
      for (i = 0; i < width; i += 2) {
          if (flipUV) {
              u = (*V++) - 128;
              v = (*U++) - 128;
          } else {
              v = (*V++) - 128;
              u = (*U++) - 128;
          }
         
          switch (ColSpace) {
          case 0:
              {
                  // M$ color space
                  v1 = ((v << 10) + (v << 9) + (v << 6) + (v << 5)) >> 10;    // 1.593
                  u1 = ((u << 8) + (u << 7) + (u << 4)) >> 10;    //         0.390
                  v2 = ((v << 9) + (v << 4)) >> 10;    //                0.515
                  u2 = ((u << 11) + (u << 4)) >> 10;    //               2.015
              }
          break;
          // PAL specific
          case 1:
            {
                  v1 = ((v << 10) + (v << 7) + (v << 4)) >> 10;    //      1.1406
                u1 = ((u << 8) + (u << 7) + (u << 4) + (u << 3)) >> 10;    // 0.3984
                v2 = ((v << 9) + (v << 6) + (v << 4) + (v << 1)) >> 10;    // 0.5800
                u2 = ((u << 11) + (u << 5)) >> 10;    //              2.0312
            }
          break;
          // V4l2
          case 2:
            {
                v1 = ((v << 10) + (v << 8) + (v << 7) + (v << 5)) >> 10;    //       1.406
                u1 = ((u << 8) + (u << 6) + (u << 5)) >> 10;    //                0.343
                v2 = ((v << 9) + (v << 7) + (v << 6) + (v << 5)) >> 10;    //        0.718
                u2 = ((u << 10) + (u << 9) + (u << 8) + (u << 4) + (u << 3)) >> 10;    // 1.773
            }
          break;
        case 3:
            {
                v1 = u1 = v2 = u2 = 0;
            }
          break;
        default:
          break;
        } // end switch
      //up-left

      y1 = (*pty1++);
      if (y1 > 0) {
          r = y1 + (v1);
          g = y1 - (u1) - (v2);
          b = y1 + (u2);

          r = CLIP (r);
          g = CLIP (g);
          b = CLIP (b);
      } else {
          r = g = b = 0;
      }
/*       *RGB1++ = r; */
/*       *RGB1++ = g; */
/*       *RGB1++ = b; */
        *RGB1++ = b;
      *RGB1++ = g;
      *RGB1++ = r;

      //down-left
      y2 = (*pty2++);
      if (y2 > 0) {
          r = y2 + (v1);
          g = y2 - (u1) - (v2);
          b = y2 + (u2);

          r = CLIP (r);
          g = CLIP (g);
          b = CLIP (b);


      } else {
          r = b = g = 0;
      }
/*       *RGB2++ = r; */
/*       *RGB2++ = g; */
/*       *RGB2++ = b; */
      *RGB2++ = b;
      *RGB2++ = g;
      *RGB2++ = r;

      //up-right
      y1 = (*pty1++);
      if (y1 > 0) {
          r = y1 + (v1);
          g = y1 - (u1) - (v2);
          b = y1 + (u2);

          r = CLIP (r);
          g = CLIP (g);
          b = CLIP (b);
      } else {
          r = g = b = 0;
      }
        *RGB1++ = b;
      *RGB1++ = g;
      *RGB1++ = r;

/*       *RGB1++ = r; */
/*       *RGB1++ = g; */
/*       *RGB1++ = b; */
      //down-right
      y2 = (*pty2++);
      if (y2 > 0) {
          r = y2 + (v1);
          g = y2 - (u1) - (v2);
          b = y2 + (u2);

          r = CLIP (r);
          g = CLIP (g);
          b = CLIP (b);
      } else {
          r = b = g = 0;
      }

/*       *RGB2++ = r; */
/*       *RGB2++ = g; */
/*       *RGB2++ = b; */

      *RGB2++ = b;
      *RGB2++ = g;
      *RGB2++ = r;

    }
      RGB1 += 3 * width;
      RGB2 += 3 * width;
      pty1 += width;
      pty2 += width;
    }
}

附件是一个VC6的工程,我提供了XVID的STATIC库,因为它比较小,还可以使程序编译通过,如果想要执行程序的话,就要下载XVIDCORE自己编译,生成动态连接库文件了,我的BLOG有一个BAKUP。
这个程序是一个测试程序,测试视频显示,解码和色彩空间转化。
使用的测试序列为
1) news.qcif或者foreman.qcif文件,这是I420的YUV数据文件,播放的时候请选择“直接播放”,然后输入文件名,CLICK THE OK BUTTON,然后就可以播放了。http://www.cipr.rpi.edu/resource/sequences/index.html
2)使用 XVID提供的小工具xvid_encraw -w 176 -h 144 -type 0 -i news.qcif -o test.m4v
   把它压缩成一个MPEG4的BITSTREAM文件test.m4v 播放的时候请选择“解码播放”,然后输入文件名test.m4v,CLICK THE OK BUTTON,然后就可以播放了。
3)如果使用YV12的数据,则在做YUV->RGB的时候,请把FLIPUV设置为0。注意这点区别。要不,图象可能没有显示颜色。

文件:xvidyuvTst.rar
大小:31KB
下载:下载



【来源】
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值