OpenCV 之BGR2YUV
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, // == 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<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相关推荐
- OpenCV计算机视觉学习(1)——图像基本操作(图像视频读取,ROI区域截取,常用cv函数解释)
人工智能学习离不开实践的验证,推荐大家可以多在FlyAI-AI竞赛服务平台多参加训练和竞赛,以此来提升自己的能力.FlyAI是为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台.每周免费提供 ...
- OpenCV 笔记(09)— 常用的数据结构和函数(Vec、Point、Scalar、Size、Rect、cvtColor)
1. Vec 对象类型 Vec 是一个主要用于数值向量的模板类.我们可以定义向量的类型和组件的数量: Vec<double, 19> myVector 我们还可以使用任何的预定义类型: t ...
- OpenCV 笔记(08)— 二维点、三维点、基于 Mat 的 std::vector 等常用数据结构的定义和输出
1. 定义和输出二维点 Point2f p2(3, 4);cout << "[二维点] is "<< endl << p2 << e ...
- OpenCV 笔记(07)— Mat 对象输出格式设置(Python 格式、CSV 格式、NumPy 格式、C 语言格式)
首先是下面代码中将要使用的 r 矩阵的定义.需要注意,我们可以通过用 randu 函数产生的随机值来填充矩阵, 需要给定一个上限和下限来确保随机值在期望的范围内. Mat r = Mat(2, 3, ...
- OpenCV 笔记(06)— Mat 结构、像素值存储方法、创建 Mat 对象各种方法、Mat 对象的运算
数字图像中的每个点都称为像素(对于图像元素),并且每个像素可以存储一个或多个值,这取决于它是否是仅存储一个值的黑白图像(也称为二进制图像,比如只存储0或1),还是存储两个值的灰度图像,或者是存储三个值 ...
- OpenCV 笔记(05)— opencv.hpp 头文件作用(是其它所有头文件的全集)
在编辑器中通过点击 #include "opencv2/opencv.hpp" 头文件就可以看到该头文件的定义如下 #ifndef OPENCV_ALL_HPP #define O ...
- OpenCV 笔记(03)— 读取视频、通过摄像头采集视频、采集视频 canny 边缘检测
我们本节学习如何利用 OpenCV 中的 VideoCapture 类,来对视频进行读取显示,以及调用摄像头. VideoCapture 它提供了从摄像机或视频文件捕获视频的 C++ 接口, 作用是从 ...
- OpenCV 笔记(02)— 图像显示、保存、腐蚀、模糊、canny 边缘检测(imread、imshow、namedWindow、imwrite)
OpenCV 提供两种用户界面选项: 基于原生用户界面的基本界面,适用于 Mac OS X 的 cocoa 或 carbon,以及适用于 Linux 或 Windows 用户界面的 GTK ,这些界面 ...
- OpenCV 笔记(01)— OpenCV 概念、整体架构、各模块主要功能
1. OpenCV 概念 图像处理( Image Processing )是用计算机对图像进行分析, 以达到所需结果的技术, 又称影像处理. 图像处理技术一般包括图像压缩, 增强和复原, 匹配.描述和 ...
最新文章
- 排查一般MySQL性能问题
- Shell脚本实现简单分割字符串
- mysql dump 导入导出_使用mysqldump导入导出数据
- XGBoost算法概述
- PWN-PRACTICE-BUUCTF-20
- Gblfy 专栏设立服务大家,共享资源
- Android WebView内核版本的探究
- 学习笔记Android弹框material-dialogs
- 火山引擎多场景下的云原生技术实践
- w7计算机不显示cdf盘,微软解决Win7 SP1新黑屏问题(0xc0000034)
- yii mysql gii_Yii 框架使用Gii生成代码操作示例
- 7.中文句法依存分析
- React 多页签方案
- PHP去掉二维数组中某个元素重复的一维数组
- 【每日一库】ppcp - 带进度条的拷贝工具
- MMC / eMMC / SD
- 伦茨科技-智能语音遥控器
- 家用NAS上安装Domino
- 设计模式六大设计原则 详细整理版
- 解决container_linux.go:262: starting container process caused: