matlab yuv转rgb_YUV系列之BGR2YUV

在OpenCV中imgproc模块下的cvtColor API。这个API的主要功能是对图片做色彩空间转换,使用起来很方便,但是背后的转换理论多少有些繁琐,但是也不难。因此今天在这篇文章中对色彩空间转换的理论进行梳理。
OpenCV支持的色彩非常丰富,我们会在以后的系列中逐步介绍,这个系列主要介绍YUV色彩空间与RGB或者BGR空间之间的转换,同时借此了解OpenCV中cvtColor这个函数的代码结构。

cvtColor API


opencv/modules/imgproc/src目录下grep cvtColor可以查找到这个API的函数原型,截取片段,如下:

void cvtColor( InputArray _src, OutputArray _dst, int code, int dcn )
{
   CV_INSTRUMENT_REGION();
   CV_Assert(!_src.empty());
   if(dcn <= 0)
           dcn = dstChannels(code);
   CV_OCL_RUN( _src.dims() <= 2 && _dst.isUMat() &&
               !(CV_MAT_DEPTH(_src.type()) == CV_8U && (code == COLOR_Luv2BGR || code == COLOR_Luv2RGB)),
               ocl_cvtColor(_src, _dst, code, dcn) )
   switch( code )
   {
       case COLOR_BGR2BGRA: case COLOR_RGB2BGRA: case COLOR_BGRA2BGR:
       case COLOR_RGBA2BGR: case COLOR_RGB2BGR:  case COLOR_BGRA2RGBA:
           cvtColorBGR2BGR(_src, _dst, dcn, swapBlue(code));
           break;
       case COLOR_BGR2BGR565:  case COLOR_BGR2BGR555: case COLOR_BGRA2BGR565: case COLOR_BGRA2BGR555:
       case COLOR_RGB2BGR565:  case COLOR_RGB2BGR555: case COLOR_RGBA2BGR565: case COLOR_RGBA2BGR555:
           cvtColorBGR25x5(_src, _dst, swapBlue(code), greenBits(code));
           break;
       ...

API参数说明:

  • _src: 输入图像

  • _dst: 输出图像

  • code: 色彩空间转换码

  • dcn: 输入图像的通道数,如果为0,则会自动根据_src和色彩空间转换码推断其值

关于色彩空间转换码,可以在opencv/modules/imgproc/include/opencv2目录的imgproc.hpp文件中查看,截取如下:

/** the color conversion code
@see @ref imgproc_color_conversions
@ingroup imgproc_misc
*/
enum ColorConversionCodes {
   COLOR_BGR2BGRA     = 0, //!< add alpha channel to RGB or BGR image
   COLOR_RGB2RGBA     = COLOR_BGR2BGRA,
   COLOR_BGRA2BGR     = 1, //!< remove alpha channel from RGB or BGR image
   COLOR_RGBA2RGB     = COLOR_BGRA2BGR,
   COLOR_BGR2RGBA     = 2, //!< convert between RGB and BGR color spaces (with or without alpha channel)
   COLOR_RGB2BGRA     = COLOR_BGR2RGBA,
   COLOR_RGBA2BGR     = 3,
   COLOR_BGRA2RGB     = COLOR_RGBA2BGR,
   ...

这里定义了各种色彩空间转换的编码。
继续查看cvtColor函数原型,能够看到本片文章的主角——RGB2NV21:

...
case COLOR_BGR2YCrCb: case COLOR_RGB2YCrCb:
case COLOR_BGR2YUV:   case COLOR_RGB2YUV:
   cvtColorBGR2YUV(_src, _dst, swapBlue(code), code == COLOR_BGR2YCrCb || code == COLOR_RGB2YCrCb);
   break;
...

可以看到RGB转NV21的色彩空间转换码是COLOR_BGR2YUV,使用的函数是:cvtColorBGR2YUV

cvtColorBGR2YUV 函数


这个函数的后两个参数,需要关注一下,最后一个参数用来区分是YCrCb还是YUV,接下来看swapBlue函数:

swapBlue函数

inline bool swapBlue(int code)
{
   switch (code)
   {
   case COLOR_BGR2BGRA: case COLOR_BGRA2BGR:
   case COLOR_BGR2BGR565: case COLOR_BGR2BGR555: case COLOR_BGRA2BGR565: case COLOR_BGRA2BGR555:
   case COLOR_BGR5652BGR: case COLOR_BGR5552BGR: case COLOR_BGR5652BGRA: case COLOR_BGR5552BGRA:
   case COLOR_BGR2GRAY: case COLOR_BGRA2GRAY:
   case COLOR_BGR2YCrCb: case COLOR_BGR2YUV:
   case COLOR_YCrCb2BGR: case COLOR_YUV2BGR:
   case COLOR_BGR2XYZ: case COLOR_XYZ2BGR:
   case COLOR_BGR2HSV: case COLOR_BGR2HLS: case COLOR_BGR2HSV_FULL: case COLOR_BGR2HLS_FULL:
   case COLOR_YUV2BGR_YV12: case COLOR_YUV2BGRA_YV12: case COLOR_YUV2BGR_IYUV: case COLOR_YUV2BGRA_IYUV:
   case COLOR_YUV2BGR_NV21: case COLOR_YUV2BGRA_NV21: case COLOR_YUV2BGR_NV12: case COLOR_YUV2BGRA_NV12:
   case COLOR_Lab2BGR: case COLOR_Luv2BGR: case COLOR_Lab2LBGR: case COLOR_Luv2LBGR:
   case COLOR_BGR2Lab: case COLOR_BGR2Luv: case COLOR_LBGR2Lab: case COLOR_LBGR2Luv:
   case COLOR_HSV2BGR: case COLOR_HLS2BGR: case COLOR_HSV2BGR_FULL: case COLOR_HLS2BGR_FULL:
   case COLOR_YUV2BGR_UYVY: case COLOR_YUV2BGRA_UYVY: case COLOR_YUV2BGR_YUY2:
   case COLOR_YUV2BGRA_YUY2:  case COLOR_YUV2BGR_YVYU: case COLOR_YUV2BGRA_YVYU:
   case COLOR_BGR2YUV_IYUV: case COLOR_BGRA2YUV_IYUV: case COLOR_BGR2YUV_YV12: case COLOR_BGRA2YUV_YV12:
       return false;
   default:
       return true;
   }
}

这个inline函数定义在opencv/modules/imgproc/src目录下的color.h文件中,可以发现所有涉及BGR格式的转换,swapBlue的返回值都是false,其他均是true,也就是或OpenCV的默认色彩排列是BGR的,如果输入图像是RGB相关的,或者输出图像是BGR相关的,就需要进行swapBlue,也就是在后续的处理中需要对颜色的通道顺序进行调整,所以相同大小的图,相同的处理,BGR会比RGB要快,因为BGR不需要调整通道顺序。

cvtColorBGR2YUV

路径:opencv/modules/imgproc/src/color_yuv.cpp +2614

void cvtColorBGR2YUV(InputArray _src, OutputArray _dst, bool swapb, bool crcb)
{
   CvtHelper< Set<3, 4>, Set<3>, Set > h(_src, _dst, 3);
   hal::cvtBGRtoYUV(h.src.data, h.src.step, h.dst.data, h.dst.step, h.src.cols, h.src.rows,
                    h.depth, h.scn, swapb, crcb);
}

可以看到该函数调用了hal::cvtBGRtoYUV

hal::cvtBGRtoYUV

路径:opencv/modules/imgproc/src/color_yuv.cpp +2266

void cvtBGRtoYUV(const uchar * src_data, size_t src_step,
                uchar * dst_data, size_t dst_step,
                int width, int height,
                int depth, int scn, bool swapBlue, bool isCbCr)
{
   CV_INSTRUMENT_REGION();
   CALL_HAL(cvtBGRtoYUV, cv_hal_cvtBGRtoYUV, src_data, src_step, dst_data, dst_step, width, height, depth, scn, swapBlue, isCbCr);
#if defined(HAVE_IPP)
#if !IPP_DISABLE_RGB_YUV
   CV_IPP_CHECK()
   {
       if (scn == 3 && depth == CV_8U && swapBlue && !isCbCr)
       {
           if (CvtColorIPPLoop(src_data, src_step, dst_data, dst_step, width, height,
                               IPPGeneralFunctor((ippiGeneralFunc)ippiRGBToYUV_8u_C3R)))
               return;
       }
       else if (scn == 3 && depth == CV_8U && !swapBlue && !isCbCr)
       {
           if (CvtColorIPPLoop(src_data, src_step, dst_data, dst_step, width, height,
                               IPPReorderGeneralFunctor(ippiSwapChannelsC3RTab[depth],
                                                        (ippiGeneralFunc)ippiRGBToYUV_8u_C3R, 2, 1, 0, depth)))
               return;
       }
       else if (scn == 4 && depth == CV_8U && swapBlue && !isCbCr)
       {
           if (CvtColorIPPLoop(src_data, src_step, dst_data, dst_step, width, height,
                               IPPReorderGeneralFunctor(ippiSwapChannelsC4C3RTab[depth],
                                                        (ippiGeneralFunc)ippiRGBToYUV_8u_C3R, 0, 1, 2, depth)))
               return;
       }
       else if (scn == 4 && depth == CV_8U && !swapBlue && !isCbCr)
       {
           if (CvtColorIPPLoop(src_data, src_step, dst_data, dst_step, width, height,
                               IPPReorderGeneralFunctor(ippiSwapChannelsC4C3RTab[depth],
                                                        (ippiGeneralFunc)ippiRGBToYUV_8u_C3R, 2, 1, 0, depth)))
               return;
       }
   }
#endif
#endif
   int blueIdx = swapBlue ? 2 : 0;
   if( depth == CV_8U )
       CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_i(scn, blueIdx, isCbCr));
   else if( depth == CV_16U )
       CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_i(scn, blueIdx, isCbCr));
   else
       CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_f(scn, blueIdx, isCbCr));
}

可以看到函数中的宏HAVE_IPP,我们不使用IPP库加速,所以直接看后面的代码就可以。BGR转YUV,所以blueIdx=0;depth = CV_8U。调用CvtColorLoop函数,cvtColorLoop中调用了RGB2YCrCb_i函数,这个函计算的主体部分。注意blueIdx的取值是0或者2

RGB2YCrCb_i

路径:opencv/modules/imgproc/src/color_yuv.cpp +251

template struct RGB2YCrCb_i
{
   typedef _Tp channel_type;
   RGB2YCrCb_i(int _srccn, int _blueIdx, bool _isCrCb)
       : srccn(_srccn), blueIdx(_blueIdx), isCrCb(_isCrCb)
   {
       //设置系数
       static const int coeffs_crb[] = { R2Y, G2Y, B2Y, YCRI, YCBI };
       static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI };
       //yuv和YCrCb的系数不同
       memcpy(coeffs, isCrCb ? coeffs_crb : coeffs_yuv, 5*sizeof(coeffs[0]));
       //RGB和BGR的区别,需要交换B分量和R分量的位置
       if(blueIdx==0) std::swap(coeffs[0], coeffs[2]);
   }
   void operator()(const _Tp* src, _Tp* dst, int n) const
   {
       int scn = srccn, bidx = blueIdx;
       int yuvOrder = !isCrCb; //1 if YUV, 0 if YCrCb
       int C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4];
       //color.hpp +26 : yuv_shift = 14
       int delta = ColorChannel<_tp>::half()*(1 << yuv_shift);
       n *= 3;
       for(int i = 0; i < n; i += 3, src += scn)
       {
           int Y = CV_DESCALE(src[0]*C0 + src[1]*C1 + src[2]*C2, yuv_shift);
           int Cr = CV_DESCALE((src[bidx^2] - Y)*C3 + delta, yuv_shift);
           int Cb = CV_DESCALE((src[bidx] - Y)*C4 + delta, yuv_shift);
           dst[i] = saturate_cast<_tp>(Y);
           dst[i+1+yuvOrder] = saturate_cast<_tp>(Cr);
           dst[i+2-yuvOrder] = saturate_cast<_tp>(Cb);
       }
   }
   int srccn, blueIdx;
   bool isCrCb;
   int coeffs[5];
};

程序细节在代码中已经做了注释,这里主要介绍一些BGR转YUV的理论公式,首先系数C0-C4分别为:
static const int coeffs_yuv[] = { R2Y, G2Y, B2Y, R2VI, B2UI };
R2Y = 4899, // == R2YF16384
G2Y = 9617, // == G2YF
16384
B2Y = 1868, // == B2YF*16384

B2UI = 8061; // == B2UF16384
R2VI = 14369; // == R2VF
16384

bidx ^ 2 是按位异或(请注意bidx的取值是0或者2) :
当bidx == 0的时候,其值为 000 ^ 010 = 010 = 2
当bidx == 2的时候,其值为 010 ^ 010 = 011 = 0
可以得到YUV的计算公式:
Y = (4899 * R + 9617 * G + 1868 * B) >> 14;//注意代码中的注释blueIdx == 0 B和R的系数交换了,默认的系数排列顺序是先RGB,默认的通道排列顺序是BGR
V = ((R - Y) * 14369 + delta) >> 14;
U = ((B - Y) * 8061 + delta) >> 14;

以上是整数的计算公式,根据计算精度不同,左移的位数不同,整数系数也会调整;

delta的计算方式:
int delta = ColorChannel<_tp>::half()*(1 << yuv_shift);

ColorChannel<_tp>::half()在color.hpp中定义的:

template struct ColorChannel
{
   typedef float worktype_f;
   static _Tp max() { return std::numeric_limits<_tp>::max(); }
   static _Tp half() { return (_Tp)(max()/2 + 1); }
};

我们计算的是uchar,所以,
delta = (255 / 2 + 1) * (1 << 14)

到此位置完整介绍了RGB转YUV的计算方式,如果是浮点数转换,仅仅是公式不同,可以以相同的方式查看代码,查看计算的系数。这里的YUV就是YUV 444,后续的文章中会介绍YUV422,YUV420等计算方式,同时介绍这几种格式的在OpenCV中的采样方法。

最后也祝大家双十一购物快乐!生活充满色彩,就可以日常变换色彩空间了O(∩_∩)O哈!

10245a10d51403aac7d6e01d79604a94.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值