YUV图像用的比较多,并且YUV图像的格式众多(YUV格式能够参考YUV pixel formats),如何用OpenCV的Mat类型来存储YUV图像也是常常遇到的问题。php
对于YUV444图像来讲,就很简单。YUV的三个份量的采样方法一致,所以YUV三个份量的大小一致,能够用Mat的三个channel分别表示YUV便可。假设src是OpenCV默认的BGR三通道图像,和YUV444的转换以下,图像大小不变。ide
// If src is CV_8UC3, dest is CV_8UC3
cvtColor(src, dest, COLOR_BGR2YUV); cvtColor(dest, src, COLOR_YUV2BGR);
YUV422用的很少(其实我没用过),先说YUV420。YUV420图像的U/V份量在水平和垂直方向上downsample,在水平和垂直方向上的数据都只有Y份量的一半。所以整体来讲,U/V份量的数据量分别只有Y份量的1/4,不能做为Mat类型的一个channel。因此一般YUV420图像的所有数据存储在Mat的一个channel,好比CV_8UC1,这样对于Mat来讲,图像的大小就有变化。对于MxN(rows x cols,M行N列)的BGR图像(CV_8UC3),其对应的YUV420图像大小是(3M/2)xN(CV_8UC1)。前MxN个数据是Y份量,后(M/2)xN个数据是U/V份量,UV数据各占一半。函数
U/V份量如何存储,和YUV420的格式有关。YUV420有所谓的420p(420planar/420面)和420sp(420 semi-planar/420半面)格式。所谓420面格式,YUV三个份量按顺序存储完一个份量全部图像数据,称为一个面,再存储下一个份量的面,所以有三个面数据。420半面格式下,只有Y份量是做为一个单独的面存储,U/V份量按照像素排列顺序交错存储,算做一个面,所以称为半面。ui
420pspa |
420sp指针 |
||
YUV顺序code |
YVU顺序orm |
UVUV交错blog |
VUVU交错ip |
I420/IYUV |
YV12 |
NV12 |
NV21 |
420p或者420sp都是先存储Y份量的面,而后根据UV份量的存储顺序,又各分为两种格式。420p按照YUV的顺序存储三个面,是I420格式,或者叫IYUV格式。按照YVU的顺序存储三个面,叫YV12格式。420sp的U/V交错面,若是按照UVUV的顺序交错存储,称为NV12格式。反之,按照VUVU的顺序交错存储,称为NV21格式。
OpenCV如今从BGR到YUV420的颜色空间变化仅支持转换到420p的两种格式,不支持转换到420sp。但能够支持420p或者420sp转换到BGR。假设src是OpenCV默认的BGR三通道图像,和420p的转换以下。
// If src is BGR CV_8UC3 with size 640x960, dest is CV_8UC1 with 960x960
cvtColor(src, dest, COLOR_BGR2YUV_I420); // dest is I420
cvtColor(dest, src, COLOR_YUV2BGR_I420); cvtColor(src, dest, COLOR_BGR2YUV_YV12); // dest is YV12
cvtColor(dest, src, COLOR_YUV2BGR_YV12);
假设src是YUV420的420sp图像数据,到BGR的转换以下。
// If src is NV12 CV_8UC1 with size 960x960, dest is BGR CV_8UC3 with 640x960
cvtColor(src, dest, COLOR_YUV2BGR_NV12);
// If src is NV21 CV_8UC1 with size 960x960, dest is BGR CV8UC3 with 640x960
cvtColor(src, dest, COLOR_YUV2BGR_NV21);
OpenCV还提供了一个cvtColorTwoPlane函数,当前仅支持从420sp转换到BGR,可是Y面和U/V交错面存储在两个Mat结构中。
下面的代码片断把height x width的YUV图像数据顺时针旋转90°存储到Mat,格式是NV12。yPixel, uPixel, vPixel分别是指向YUV数据的指针,yStride,uvStride分别是Y和UV的行stride,uvPixelStride是UV数据像素stride。代码分别把YUV数据存储到一个临时Mat中,而后调用OpenCV的transpose()和flip()函数把图像顺时针旋转90°。较新版本的OpenCV提供了函数rotate()能够作90°,180°和270°的旋转,可使用。最后分别把旋转后的YUV数据写到Mat中,最后的格式是NV12,注意height和width交换了,UV数据是交错存储的。若是不使用OpenCV的函数,本身写一段代码来作旋转也是能够的。不过我试过了,确定没有OpenCV的函数快。OpenCV的函数至少要比咱们用循环写出来的代码快25%。因此有现成的库函数尽可能使用他们。
// Original image with size height x width // int32_t width, height; original image width and height // uint8_t *yPixel, *uPixel, *vPixel; pointers to YUV data // int32_t yStride, uvStride, uvPixelStride; line stride and uv pixel stride
cv::Mat yuv_nv12(width * 3 / 2, height, CV_8UC1) int i, j; int height2 = height / 2, width2 = width / 2; cv::Mat y_temp(height, width, CV_8UC1); cv::Mat u_temp(height2, width2, CV_8UC1); cv::Mat v_temp(height2, width2, CV_8UC1); // Get Y data and rotate
line_src = yPixel; for (i = 0; i < height; i++) { line_dest = y_temp.ptr(i); memcpy(line_dest, line_src, width); line_src += yStride; } cv::transpose(y_temp, y_temp); cv::flip(y_temp, y_temp, 1); // Get U data and rotate
line_src = uPixel; for (i = 0; i < height2; i++) { line_dest = u_temp.ptr(i); uchar *ptr = line_src; for (j = 0; j < width2; j++) { *line_dest++ = *ptr; ptr += uvPixelStride; } line_src += uvStride; } cv::transpose(u_temp, u_temp); cv::flip(u_temp, u_temp, 1); // Get V data and rotate
line_src = vPixel; for (i = 0; i < height2; i++) { line_dest = v_temp.ptr(i); uchar *ptr = line_src; for (j = 0; j < width2; j++) { *line_dest++ = *ptr; ptr += uvPixelStride; } line_src += uvStride; } cv::transpose(v_temp, v_temp); cv::flip(v_temp, v_temp, 1); // Write Y data to yuv_nv12
for (i = 0; i < width; i++) { line_dest = yuv_nv12.ptr(i); line_src = y_temp.ptr(i); memcpy(line_dest, line_src, height); } // Write UV data to yuv_nv12
cv::MatIterator_<uchar> it((cv::Mat_<uchar>*)&yuv_nv12, width); cv::MatIterator_<uchar> u_src_it = u_temp.begin<uchar>(); cv::MatIterator_<uchar> v_src_it = v_temp.begin<uchar>(); int wh2 = width2 * height2; for (i = 0; i < wh2; i++) { *it++ = *u_src_it++; *it++ = *v_src_it++; }
至于YUV422图像,我没有试过。OpenCV不支持从BGR转到YUV422,可是能够从YUV422转会BGR。大概看了下,YUV422图像用Mat类型存储应该也是用一个channel来存储全部YUV数据,并且应该是用所谓的紧凑格式(packed format),而不是前面提到的面格式(planar format)。所谓紧凑格式,就是对每一个像素的YUV三个份量按照必定的顺序交错存储,每4个数据组成一个所谓的宏像素。由于YUV422垂直方向没有downsample,只有水平方向有,因此每两个Y对应一个U和一个V,组成一个宏像素。好比UYVY格式(按照UYVY交错存储),YUY2格式(按照YUYV交错存储),YVYU格式等等。它们都有对应的转BGR的code,好比COLOR_YUV2BGR_UYVY,不一一列举了。