之前在工作中使用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,以下是官方文档中的示例:
上面的图表代表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分量,即:
0,0 | 0,1 | 0,2 | 0,3 | |
Y | Y00 | Y01 | Y02 | Y03 |
U | Cb00 | Cb00 | Cb01 | Cb01 |
V | Cr00 | Cr00 | Cr01 | Cr01 |
还原了每一个像素的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。