OpenCV之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 imageCOLOR_RGB2RGBA     = COLOR_BGR2BGRA,COLOR_BGRA2BGR     = 1, //!< remove alpha channel from RGB or BGR imageCOLOR_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函数原型,能够看到本片文章的主角——RGB2YUV:

...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转YUV的色彩空间转换码是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<CV_8U, CV_16U, CV_32F> > 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_YUVCV_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
#endifint blueIdx = swapBlue ? 2 : 0;if( depth == CV_8U )CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_i<uchar>(scn, blueIdx, isCbCr));else if( depth == CV_16U )CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_i<ushort>(scn, blueIdx, isCbCr));elseCvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2YCrCb_f<float>(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<typename _Tp> 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 YCrCbint C0 = coeffs[0], C1 = coeffs[1], C2 = coeffs[2], C3 = coeffs[3], C4 = coeffs[4];//color.hpp +26 : yuv_shift = 14int 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<typename _Tp> 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中的采样方法。

欢迎关注公众号:计算机视觉与高性能计算(to_2know)

OpenCV 之BGR2YUV相关推荐

  1. OpenCV计算机视觉学习(1)——图像基本操作(图像视频读取,ROI区域截取,常用cv函数解释)

    人工智能学习离不开实践的验证,推荐大家可以多在FlyAI-AI竞赛服务平台多参加训练和竞赛,以此来提升自己的能力.FlyAI是为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台.每周免费提供 ...

  2. OpenCV 笔记(09)— 常用的数据结构和函数(Vec、Point、Scalar、Size、Rect、cvtColor)

    1. Vec 对象类型 Vec 是一个主要用于数值向量的模板类.我们可以定义向量的类型和组件的数量: Vec<double, 19> myVector 我们还可以使用任何的预定义类型: t ...

  3. OpenCV 笔记(08)— 二维点、三维点、基于 Mat 的 std::vector 等常用数据结构的定义和输出

    1. 定义和输出二维点 Point2f p2(3, 4);cout << "[二维点] is "<< endl << p2 << e ...

  4. OpenCV 笔记(07)— Mat 对象输出格式设置(Python 格式、CSV 格式、NumPy 格式、C 语言格式)

    首先是下面代码中将要使用的 r 矩阵的定义.需要注意,我们可以通过用 randu 函数产生的随机值来填充矩阵, 需要给定一个上限和下限来确保随机值在期望的范围内. Mat r = Mat(2, 3, ...

  5. OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算

    数字图像中的每个点都称为像素(对于图像元素),并且每个像素可以存储一个或多个值,这取决于它是否是仅存储一个值的黑白图像(也称为二进制图像,比如只存储0或1),还是存储两个值的灰度图像,或者是存储三个值 ...

  6. OpenCV 笔记(05)— opencv.hpp 头文件作用(是其它所有头文件的全集)

    在编辑器中通过点击 #include "opencv2/opencv.hpp" 头文件就可以看到该头文件的定义如下 #ifndef OPENCV_ALL_HPP #define O ...

  7. OpenCV 笔记(03)— 读取视频、通过摄像头采集视频、采集视频 canny 边缘检测

    我们本节学习如何利用 OpenCV 中的 VideoCapture 类,来对视频进行读取显示,以及调用摄像头. VideoCapture 它提供了从摄像机或视频文件捕获视频的 C++ 接口, 作用是从 ...

  8. OpenCV 笔记(02)— 图像显示、保存、腐蚀、模糊、canny 边缘检测(imread、imshow、namedWindow、imwrite)

    OpenCV 提供两种用户界面选项: 基于原生用户界面的基本界面,适用于 Mac OS X 的 cocoa 或 carbon,以及适用于 Linux 或 Windows 用户界面的 GTK ,这些界面 ...

  9. OpenCV 笔记(01)— OpenCV 概念、整体架构、各模块主要功能

    1. OpenCV 概念 图像处理( Image Processing )是用计算机对图像进行分析, 以达到所需结果的技术, 又称影像处理. 图像处理技术一般包括图像压缩, 增强和复原, 匹配.描述和 ...

最新文章

  1. 排查一般MySQL性能问题
  2. Shell脚本实现简单分割字符串
  3. mysql dump 导入导出_使用mysqldump导入导出数据
  4. XGBoost算法概述
  5. PWN-PRACTICE-BUUCTF-20
  6. Gblfy 专栏设立服务大家,共享资源
  7. Android WebView内核版本的探究
  8. 学习笔记Android弹框material-dialogs
  9. 火山引擎多场景下的云原生技术实践
  10. w7计算机不显示cdf盘,微软解决Win7 SP1新黑屏问题(0xc0000034)
  11. yii mysql gii_Yii 框架使用Gii生成代码操作示例
  12. 7.中文句法依存分析
  13. React 多页签方案
  14. PHP去掉二维数组中某个元素重复的一维数组
  15. 【每日一库】ppcp - 带进度条的拷贝工具
  16. MMC / eMMC / SD
  17. 伦茨科技-智能语音遥控器
  18. 家用NAS上安装Domino
  19. 设计模式六大设计原则 详细整理版
  20. 解决container_linux.go:262: starting container process caused:

热门文章

  1. Intellij IDEA创建Android项目异常
  2. 缺氧 超级计算机 科学家,缺氧全复制人+资源+生态群落+物品属性详解 缺氧系统讲解 复制人-技能特性-游侠网...
  3. 2017年鸡年春联节选
  4. R语言实用案例分析-1
  5. 泰克示波器面板旋钮的秘密你知道吗
  6. 【区块链DAPP】智能合约概述
  7. 今天搞清楚了自然常熟e的现实意义
  8. recycleView使用之2 :横向现实图片之图片是正方形
  9. MFC下的汉字拼音首字母(适用于多字节字符集)
  10. Android使用低功耗蓝牙BLE进行简单通信