V4L2 YUV/YCbCr格式数据 转 RGB格式数据 V4L2_PIX_FMT_NV12 转 RGB

  之前在工作中使用V4L2 + Qt显示摄像头画面的时候,遇到的一个问题就是:摄像头通过V4L2采集到的数据只支持YUV格式的,但是Qt显示图片的时候需要RGB格式,所以需要一个转换函数。但是网上找的YUV转RGB函数在我的代码里面一跑就段错误了,程序coredown。

  后来实在无奈,自己阅读了一下V4L2的官方文档,才发现自己太心急:只想找现成的代码去用,而没有理解底层的YUV数据的格式。其实,YUV数据的格式有很多种,不能一概而论,我自己的摄像头采集的数据格式和网上的代码对应的格式不一样就会导致转码出错。

  我们可以看一下linux下/usr/include/linux/videodev2.h中有关yuv格式的宏:

/* Luminance+Chrominance formats */
#define V4L2_PIX_FMT_YUYV    v4l2_fourcc('Y', 'U', 'Y', 'V') /* 16  YUV 4:2:2     */
#define V4L2_PIX_FMT_YYUV    v4l2_fourcc('Y', 'Y', 'U', 'V') /* 16  YUV 4:2:2     */
#define V4L2_PIX_FMT_YVYU    v4l2_fourcc('Y', 'V', 'Y', 'U') /* 16 YVU 4:2:2 */
#define V4L2_PIX_FMT_UYVY    v4l2_fourcc('U', 'Y', 'V', 'Y') /* 16  YUV 4:2:2     */
#define V4L2_PIX_FMT_VYUY    v4l2_fourcc('V', 'Y', 'U', 'Y') /* 16  YUV 4:2:2     */
#define V4L2_PIX_FMT_Y41P    v4l2_fourcc('Y', '4', '1', 'P') /* 12  YUV 4:1:1     */
#define V4L2_PIX_FMT_YUV444  v4l2_fourcc('Y', '4', '4', '4') /* 16  xxxxyyyy uuuuvvvv */
#define V4L2_PIX_FMT_YUV555  v4l2_fourcc('Y', 'U', 'V', 'O') /* 16  YUV-5-5-5     */
#define V4L2_PIX_FMT_YUV565  v4l2_fourcc('Y', 'U', 'V', 'P') /* 16  YUV-5-6-5     */
#define V4L2_PIX_FMT_YUV32   v4l2_fourcc('Y', 'U', 'V', '4') /* 32  YUV-8-8-8-8   */
#define V4L2_PIX_FMT_HI240   v4l2_fourcc('H', 'I', '2', '4') /*  8  8-bit color   */
#define V4L2_PIX_FMT_HM12    v4l2_fourcc('H', 'M', '1', '2') /*  8  YUV 4:2:0 16x16 macroblocks */
#define V4L2_PIX_FMT_M420    v4l2_fourcc('M', '4', '2', '0') /* 12  YUV 4:2:0 2 lines y, 1 line uv interleaved */

/* two planes -- one Y, one Cr + Cb interleaved  */
#define V4L2_PIX_FMT_NV12    v4l2_fourcc('N', 'V', '1', '2') /* 12  Y/CbCr 4:2:0  */
#define V4L2_PIX_FMT_NV21    v4l2_fourcc('N', 'V', '2', '1') /* 12  Y/CrCb 4:2:0  */
#define V4L2_PIX_FMT_NV16    v4l2_fourcc('N', 'V', '1', '6') /* 16  Y/CbCr 4:2:2  */
#define V4L2_PIX_FMT_NV61    v4l2_fourcc('N', 'V', '6', '1') /* 16  Y/CrCb 4:2:2  */
#define V4L2_PIX_FMT_NV24    v4l2_fourcc('N', 'V', '2', '4') /* 24  Y/CbCr 4:4:4  */
#define V4L2_PIX_FMT_NV42    v4l2_fourcc('N', 'V', '4', '2') /* 24  Y/CrCb 4:4:4  */

/* two non contiguous planes - one Y, one Cr + Cb interleaved  */
#define V4L2_PIX_FMT_NV12M   v4l2_fourcc('N', 'M', '1', '2') /* 12  Y/CbCr 4:2:0  */
#define V4L2_PIX_FMT_NV21M   v4l2_fourcc('N', 'M', '2', '1') /* 21  Y/CrCb 4:2:0  */
#define V4L2_PIX_FMT_NV16M   v4l2_fourcc('N', 'M', '1', '6') /* 16  Y/CbCr 4:2:2  */
#define V4L2_PIX_FMT_NV61M   v4l2_fourcc('N', 'M', '6', '1') /* 16  Y/CrCb 4:2:2  */
#define V4L2_PIX_FMT_NV12MT  v4l2_fourcc('T', 'M', '1', '2') /* 12  Y/CbCr 4:2:0 64x32 macroblocks */
#define V4L2_PIX_FMT_NV12MT_16X16 v4l2_fourcc('V', 'M', '1', '2') /* 12  Y/CbCr 4:2:0 16x16 macroblocks */

/* three planes - Y Cb, Cr */
#define V4L2_PIX_FMT_YUV410  v4l2_fourcc('Y', 'U', 'V', '9') /*  9  YUV 4:1:0     */
#define V4L2_PIX_FMT_YVU410  v4l2_fourcc('Y', 'V', 'U', '9') /*  9  YVU 4:1:0     */
#define V4L2_PIX_FMT_YUV411P v4l2_fourcc('4', '1', '1', 'P') /* 12  YVU411 planar */
#define V4L2_PIX_FMT_YUV420  v4l2_fourcc('Y', 'U', '1', '2') /* 12  YUV 4:2:0     */
#define V4L2_PIX_FMT_YVU420  v4l2_fourcc('Y', 'V', '1', '2') /* 12  YVU 4:2:0     */
#define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4', '2', '2', 'P') /* 16  YVU422 planar */

/* three non contiguous planes - Y, Cb, Cr */
#define V4L2_PIX_FMT_YUV420M v4l2_fourcc('Y', 'M', '1', '2') /* 12  YUV420 planar */
#define V4L2_PIX_FMT_YVU420M v4l2_fourcc('Y', 'M', '2', '1') /* 12  YVU420 planar */
#define V4L2_PIX_FMT_YUV422M v4l2_fourcc('Y', 'M', '1', '6') /* 16  YUV422 planar */
#define V4L2_PIX_FMT_YVU422M v4l2_fourcc('Y', 'M', '6', '1') /* 16  YVU422 planar */
#define V4L2_PIX_FMT_YUV444M v4l2_fourcc('Y', 'M', '2', '4') /* 24  YUV444 planar */
#define V4L2_PIX_FMT_YVU444M v4l2_fourcc('Y', 'M', '4', '2') /* 24  YVU444 planar */

大概有这么多的YUV格式,它们的数据存储格式都是不太一样的,具体的存放格式可以参考官方文档:http://v4l.videotechnology.com/dwg/v4l2.pdf

  此处,我就用我之前所用到个格式来举例:V4L2_PIX_FMT_NV12,以下是官方文档中的示例:

V4L2.pdf第35页

上面的图表代表YUV数据实际存放的方式,U对应Cb,V对应Cr。比如一张100*100像素的图片,那么Y数据就占前100*100个字节,UV数据占后面 (100*100)/2 个字节。

  下面图表代表YUV数据是如何从图像中取得的。比如左上角像素(0,0),(0,1),(1,0),(1,1)公用一个C数据,也就是UV数据。但是他们的Y分量都是各自的。通过上面的表,我们可以将内存中V4L2采集到的数据的YUV分量先分辨出来。然后再根据下面的表,还原出原始图片的每一个像素的YUV分量,即:


 

还原后像素YUV分量,以第一行为例
 0,00,10,20,3
YY00Y01Y02Y03
UCb00Cb00Cb01Cb01
VCr00Cr00Cr01Cr01

  还原了每一个像素的YUV分量后,接下来就一个像素一个像素的转换成RGB即可。转换公式网上很容易找到,不过这里还是贴一下,省去再搜索的时间:

R = Y + 1.4075 * (V-128);  
G = Y - 0.3455 * (U-128) - 0.7169*(V-128);  
B = Y + 1.779 * (U-128); 

有了此处的公式,转换RGB就很容易了,以下为C实现的YUV420(V4L2_PIX_FMT_NV12格式的)转RGB代码:

typedef unsigned char uchar;
typedef unsigned int uint;

static inline uchar rgb_overflow_guard(int rgb)
{
    if (rgb > 255)
    {
        return 255;
    }

    if (rgb < 0)
    {
        return 0;
    }

    return rgb;
}

// change yuv420(nv12) to rbg, return a pointer to rgb buffer
uchar *nv12_to_rgb(const uchar *yuvBuf, uint width, uint height)
{
    int row, col;
    int Y, U, V, R, G, B;
    const uchar *pY, *pU; // pointer to Y data, U data. V data follow the U data
    uchar *rgbBuf = NULL;
    uchar *pixel = NULL; // pointer to a rgb pixel

    // parameter check
    assert(yuvBuf);
    assert(width);
    assert(height);

    rgbBuf = (uchar *)malloc(width * height * 3);
    if (!rgbBuf)
    {
        return NULL;
    }

    pY = yuvBuf;
    pU = yuvBuf + width * height;

    for (row = 0; row < height; row++)
    {
        for (col = 0; col < width; col++)
        {
            Y = pY[row * width + col];
            U = pU[(row / 2) * (width / 2) + col / 2];
            V = pU[(row / 2) * (width / 2) + col / 2 + 1];

            R = rgb_overflow_guard(Y + 1.4075 * (V - 128));
            G = rgb_overflow_guard(Y - 0.3455 * (U - 128) - 0.7169 * (V - 128));
            B = rgb_overflow_guard(Y + 1.779 * (U - 128));

            pixel = rgbBuf + (row * width + col) * 3;
            pixel[0] = R;
            pixel[1] = G;
            pixel[2] = B;
        }
    }
    return rgbBuf;
}

  总之,要进行YUV转码RGB,首先得搞清楚你所拿到的YUV的格式是什么,这个可以结合官方文档去看。然后才能选择对应格式的转码代码,否则,轻则转码异常,重则SEGMENTFAULT,段错误,core down。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值