在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, // == G2YF16384
B2Y = 1868, // == B2YF*16384
B2UI = 8061; // == B2UF16384
R2VI = 14369; // == R2VF16384
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哈!