2009年11月30日星期一

如何将BGR转成YUV420

今天心情非常之好,花了一个下午时间搞定了困扰了我一个月的事情。写了一个程序可以把openCV的BGR图像格式转换成YUV4:2:0,然后通过FFmpeg的API把YUV4:2:0的图像编码压缩,最后利用live555把压缩后的buffer打包成RTP包,最后用Unicast将它们发送出去。简单的说就是 BGR—>YUV4:2:0—>encode to buffer—>RTP—>Unicast这样的过程。

把预想的程序完成真是一大快事,随便记录一部分比较特别的东西,因为openCV没有BGR转YUV420的函数,所以需要自己写一个,我写的这个函数读入一个三通道的图像数据,然后先使用cvCvtColor函数,将BGR转成YCrCb,这个YCrCb是4:4:4也就是全抽样,然后对Cb和Cr分量进行抽样,最后我们可以得到三个数组,分别存Y, U, V三个分量。这个转换不算复杂,就是使用了以下的概念。

YUV4:2:0

 yuv420 4:2:0并不意味着只有Y,Cb而没有Cr分量。它指得是对每行扫描线来说,只有一种色度分量以2:1的抽样率存储。相邻的扫描行存储不同的色度分量,也就是说,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0...以此类推。对每个色度分量来说,水平方向和竖直方向的抽样率都是2:1,所以可以说色度的抽样率是4:1。对非压缩的8比特量化的视频来说,每个由2x2个2行2列相邻的像素组成的宏像素需要占用6字节内存。
八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]
存放的码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8
射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7][Y5 U0 V5] [Y6 U0 V5] [Y7U2 V7] [Y8 U2 V7](来自百度百科)我们可以对每八个像素:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]进行一次抽样,抽样: Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7 给Y分量,U0 U2 给U分量,V5 V7给V分量。

现在提供另一个简单的转换函数:

void convertBGR2YUV420(IplImage *in, unsigned char* out_y, unsigned char* out_u, unsigned char* out_v)  
{  
    // first, convert the input image into YCbCr 
    IplImage *tmp = cvCreateImage(cvSize(in->width, in->height), 8, 3); 
    cvCvtColor(in, tmp, CV_RGB2YCrCb); 
    /*  
    * widthStep = channel number * width  
    * if width%4 == 0  
    * for example, width = 352, width%4 == 0, widthStep = 3 * 352 = 1056  
    */ 
    int idx_in = 0;  
    int idx_out = 0;  
    int idx_out_y = 0;  
    int idx_out_u = 0;  
    int idx_out_v = 0; 
    
    for(int j = 0; j < in->height; j+=1) { 
        idx_in = j * in->widthStep; 
    
        for(int i = 0; i < in->widthStep; i+=12) { 
        // We use the chroma sample here, and put it into the out buffer 
        // take the luminance sample 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 0]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 3]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 6]; // Y 
        idx_out_y++; 
        out_y[idx_out_y] = tmp->imageData[idx_in + i + 9]; // Y 
        idx_out_y++; 

        if((j % 2) == 0) { 
              // take the blue-difference and red-difference chroma components sample  
              out_u[idx_out_u++] = tmp->imageData[idx_in + i + 1]; // Cr U  
              out_u[idx_out_u++] = tmp->imageData[idx_in + i + 7]; // Cr U  
              out_v[idx_out_v++] = tmp->imageData[idx_in + i + 2]; // Cb V  
              out_v[idx_out_v++] = tmp->imageData[idx_in + i + 8]; // Cb V 
        }  

        }    
    }  
    cvReleaseImage(&tmp); 
}

没有评论: