目录

  • 色彩空间简介
  • BGR颜色空间
  • HSV颜色空间
  • BGR和HSV之间的转换
  • BGR转HSV
  • HSV转BGR
  • OpenCV中的实现
  • 相关函数说明
  • BGR2HSV
  • HSV2BGR
  • 实例说明
  • 代码
  • 参考资料

色彩空间简介

色彩是人的眼睛对于不同频率的光线的不同感受,色彩既是客观存在的(不同频率的光)又是主观感知的,有认识差异。所以人类对于色彩的认识经历了极为漫长的过程,直到近代才逐步完善起来,但至今,人类仍不能说对色彩完全了解并准确表述了,许多概念不是那么容易理解。“色彩空间”一词源于西方的“Color Space”,又称作“色域”,色彩学中,人们建立了多种色彩模型,以一维、二维、三维甚至四维空间坐标来表示某一色彩,这种坐标系统所能定义的色彩范围即色彩空间。

需要说明的是,没有一种颜色模型能解释所有的颜色问题,在实际使用中,我们需要根据自己的需求选择合适的色彩空间。

BGR颜色空间

RGB颜色空间以R(Red:红)、G(Green:绿)、B(Blue:蓝)三种基本色为基础,进行不同程度的叠加,产生丰富而广泛的颜色,所以俗称三基色模式。

RGB颜色空间是用一个单位长度的立方体来表示颜色的,黑蓝绿青红紫黄白8种常见颜色分别位居立方体的8个顶点,通常将黑色置于三维直角坐标系的原点,红绿蓝分别置于3根坐标轴土,整个立方体放在第1卦限内。如下图所示。而其中的青色与红色、紫色(或称品红色)与绿色、黄色与蓝色是互补色。各参数的取值范围是:R:0-255;G:0-255;B:0-255。参数值也称为三色系数或基色系数或颜色值,除以255后归一到0-1之间,但不是无穷多个而是有限多个值。由于每个灰度级都定为256,所以,红绿蓝分量全部组合起来共可表示256=2=16777216种不同的颜色。它比人眼能分辨的颜色种数多得多。因此,虽然自然界中的颜色非常多,但用RGB颜色空间来近似表达自然界中的颜色是完全够用了。

需要注意的是,三原色RGB混合能形成其他的颜色,并不是说物理上其他颜色的光是由三原色的光混合形成的,每种单色光都有自己独特的光谱,如黄光是一种单色光,但红色与绿色混合能形成黄色,原因是人的感官系统所致,与人的生理系统有关。只能说“将三原色光以不同的比例复合后,对人的眼睛可以形成与各种频率的可见光等效的色觉。”

opencv中默认的色彩空间是RGB颜色空间,但是顺序是BGR,所以此文中均已BGR颜色空间来说明。仅表示BGR的顺序,并没有其他含义。

HSV颜色空间

HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。色调H用角度度量,取值范围为0°~360°,S表示饱和度,也就是色彩的深浅度(0-100%) ,V表示色彩的亮度(0-100%) 。

RGB颜色模型都是面向硬件的,而HSV(Hue Saturation Value)颜色模型是面向用户的。

HSV模型的三维表示从RGB立方体演化而来。设想从RGB沿立方体对角线的白色顶点向黑色顶点观察,就可以看到立方体的六边形外形。六边形边界表示色彩,水平轴表示纯度,明度沿垂直轴测量。

BGR和HSV之间的转换

BGR转HSV

HSV转BGR

OpenCV中的实现

相关函数说明

CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );函数声明如下:

//! @} imgproc_misc//! @addtogroup imgproc_color_conversions
//! @{/** @brief Converts an image from one color space to another.The function converts an input image from one color space to another. In case of a transformation
to-from RGB color space, the order of the channels should be specified explicitly (RGB or BGR). Note
that the default color format in OpenCV is often referred to as RGB but it is actually BGR (the
bytes are reversed). So the first byte in a standard (24-bit) color image will be an 8-bit Blue
component, the second byte will be Green, and the third byte will be Red. The fourth, fifth, and
sixth bytes would then be the second pixel (Blue, then Green, then Red), and so on.The conventional ranges for R, G, and B channel values are:
-   0 to 255 for CV_8U images
-   0 to 65535 for CV_16U images
-   0 to 1 for CV_32F imagesIn case of linear transformations, the range does not matter. But in case of a non-linear
transformation, an input RGB image should be normalized to the proper value range to get the correct
results, for example, for RGB \f$\rightarrow\f$ L\*u\*v\* transformation. For example, if you have a
32-bit floating-point image directly converted from an 8-bit image without any scaling, then it will
have the 0..255 value range instead of 0..1 assumed by the function. So, before calling #cvtColor ,
you need first to scale the image down:
@codeimg *= 1./255;cvtColor(img, img, COLOR_BGR2Luv);
@endcode
If you use #cvtColor with 8-bit images, the conversion will have some information lost. For many
applications, this will not be noticeable but it is recommended to use 32-bit images in applications
that need the full range of colors or that convert an image before an operation and then convert
back.If conversion adds the alpha channel, its value will set to the maximum of corresponding channel
range: 255 for CV_8U, 65535 for CV_16U, 1 for CV_32F.@param src input image: 8-bit unsigned, 16-bit unsigned ( CV_16UC... ), or single-precision
floating-point.
@param dst output image of the same size and depth as src.
@param code color space conversion code (see #ColorConversionCodes).
@param dstCn number of channels in the destination image; if the parameter is 0, the number of the
channels is derived automatically from src and code.@see @ref imgproc_color_conversions*/
CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );

简单的翻译一下:

函数功能:将一张图像由一个指定的颜色空间转换到另一个指定的颜色空间。

RGB通道的约定范围:

-   0 to 255 for CV_8U images
       -   0 to 65535 for CV_16U images
       -   0 to 1 for CV_32F images

这个约定的范围对于线性变换来说无关紧要,但是对于非线性变换来说,必须先将RBG归一化到约定的范围,才能得到准确的转换结果。例如:从RGB到LUV色彩空间的转换,就是一个非线性变换的过程。如果你直接将一个8U类型的图像转换为32F类型的图像,不带任何尺度(也就是scale=1),得到的LUV空间的结果的范围将是0~255,而不是0~1.因此,你在调用cvtColor前,需要先让图像的尺度降下来:

img *= 1./255;
           cvtColor(img, img, COLOR_BGR2Luv);

如果使用cvtColor对 CV_8U图像直接进行转换,转换的过程将会损失一些精度。对于很多应用来说,这一点没有引起注意。

声明中这个地方,很明确的指出了,我们在进行非线性色彩空间的转换时,要想得到准确的转换,必须先将图像的类型和值的范围进行调整。对于BGR转换到HSV之前,我们如何操作,才能得到最准确的结果呢?且看接下来的分析。

函数cvtColor的定义可以看到通过第三个参数code来选择实际调用的函数:

BGR2HSV

简单绘制了一下BGR转HSV的调用关系流程图:

关于HSV转换后的范围,我们可以在函数hal::cvtBGRtoHSV看到有一个变量hrange进行控制:

    int hrange = depth == CV_32F ? 360 : isFullRange ? 256 : 180;int blueIdx = swapBlue ? 2 : 0;if(isHSV){if(depth == CV_8U)CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2HSV_b(scn, blueIdx, hrange));elseCvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2HSV_f(scn, blueIdx, static_cast<float>(hrange)));}

如果图像深度为CV_32F , hrange:0~360

如果图像深度不为CV_32F并且isFullRange为ture,hrange:0~256

如果图像深度不为CV_32F并且isFullRange为false,hrange:0~180

此处的图像深度指的是输入图像,isFullRange的判断如下:

inline bool isFullRangeHSV(int code)
{switch (code){case COLOR_BGR2HSV_FULL: case COLOR_RGB2HSV_FULL: case COLOR_BGR2HLS_FULL: case COLOR_RGB2HLS_FULL:case COLOR_HSV2BGR_FULL: case COLOR_HSV2RGB_FULL: case COLOR_HLS2BGR_FULL: case COLOR_HLS2RGB_FULL:return true;default:return false;}
}

也就是说,在BGR转HSV的过程中,如果code是COLOR_BGR2HSV_FULL,isFullRange为true,如果code是COLOR_BGR2HSV,isFullRange为false.

下面看一下转换的真正实现过程:

// RGB <-> HSV ///struct RGB2HSV_b
{typedef uchar channel_type;RGB2HSV_b(int _srccn, int _blueIdx, int _hrange): srccn(_srccn), blueIdx(_blueIdx), hrange(_hrange){CV_Assert( hrange == 180 || hrange == 256 );}void operator()(const uchar* src, uchar* dst, int n) const{int i, bidx = blueIdx, scn = srccn;const int hsv_shift = 12;static int sdiv_table[256];static int hdiv_table180[256];static int hdiv_table256[256];static volatile bool initialized = false;int hr = hrange;const int* hdiv_table = hr == 180 ? hdiv_table180 : hdiv_table256;n *= 3;if( !initialized ){sdiv_table[0] = hdiv_table180[0] = hdiv_table256[0] = 0;for( i = 1; i < 256; i++ ){sdiv_table[i] = saturate_cast<int>((255 << hsv_shift)/(1.*i));hdiv_table180[i] = saturate_cast<int>((180 << hsv_shift)/(6.*i));hdiv_table256[i] = saturate_cast<int>((256 << hsv_shift)/(6.*i));}initialized = true;}for( i = 0; i < n; i += 3, src += scn ){int b = src[bidx], g = src[1], r = src[bidx^2];int h, s, v = b;int vmin = b;int vr, vg;CV_CALC_MAX_8U( v, g );CV_CALC_MAX_8U( v, r );CV_CALC_MIN_8U( vmin, g );CV_CALC_MIN_8U( vmin, r );uchar diff = saturate_cast<uchar>(v - vmin);vr = v == r ? -1 : 0;vg = v == g ? -1 : 0;s = (diff * sdiv_table[v] + (1 << (hsv_shift-1))) >> hsv_shift;h = (vr & (g - b)) +(~vr & ((vg & (b - r + 2 * diff)) + ((~vg) & (r - g + 4 * diff))));h = (h * hdiv_table[diff] + (1 << (hsv_shift-1))) >> hsv_shift;h += h < 0 ? hr : 0;dst[i] = saturate_cast<uchar>(h);dst[i+1] = (uchar)s;dst[i+2] = (uchar)v;}}int srccn, blueIdx, hrange;
};struct RGB2HSV_f
{typedef float channel_type;RGB2HSV_f(int _srccn, int _blueIdx, float _hrange): srccn(_srccn), blueIdx(_blueIdx), hrange(_hrange) {#if CV_SIMD128hasSIMD = hasSIMD128();#endif}#if CV_SIMD128inline void process(v_float32x4& v_r, v_float32x4& v_g,v_float32x4& v_b, float hscale) const{v_float32x4 v_min_rgb = v_min(v_min(v_r, v_g), v_b);v_float32x4 v_max_rgb = v_max(v_max(v_r, v_g), v_b);v_float32x4 v_eps = v_setall_f32(FLT_EPSILON);v_float32x4 v_diff = v_max_rgb - v_min_rgb;v_float32x4 v_s = v_diff / (v_abs(v_max_rgb) + v_eps);v_float32x4 v_r_eq_max = v_r == v_max_rgb;v_float32x4 v_g_eq_max = v_g == v_max_rgb;v_float32x4 v_h = v_select(v_r_eq_max, v_g - v_b,v_select(v_g_eq_max, v_b - v_r, v_r - v_g));v_float32x4 v_res = v_select(v_r_eq_max, (v_g < v_b) & v_setall_f32(360.0f),v_select(v_g_eq_max, v_setall_f32(120.0f), v_setall_f32(240.0f)));v_float32x4 v_rev_diff = v_setall_f32(60.0f) / (v_diff + v_eps);v_r = v_muladd(v_h, v_rev_diff, v_res) * v_setall_f32(hscale);v_g = v_s;v_b = v_max_rgb;}#endifvoid operator()(const float* src, float* dst, int n) const{int i = 0, bidx = blueIdx, scn = srccn;float hscale = hrange*(1.f/360.f);n *= 3;#if CV_SIMD128if (hasSIMD){if (scn == 3) {if (bidx) {for ( ; i <= n - 12; i += 12, src += scn * 4){v_float32x4 v_r;v_float32x4 v_g;v_float32x4 v_b;v_load_deinterleave(src, v_r, v_g, v_b);process(v_r, v_g, v_b, hscale);v_store_interleave(dst + i, v_r, v_g, v_b);}} else {for ( ; i <= n - 12; i += 12, src += scn * 4){v_float32x4 v_r;v_float32x4 v_g;v_float32x4 v_b;v_load_deinterleave(src, v_r, v_g, v_b);process(v_b, v_g, v_r, hscale);v_store_interleave(dst + i, v_b, v_g, v_r);}}} else { // scn == 4if (bidx) {for ( ; i <= n - 12; i += 12, src += scn * 4){v_float32x4 v_r;v_float32x4 v_g;v_float32x4 v_b;v_float32x4 v_a;v_load_deinterleave(src, v_r, v_g, v_b, v_a);process(v_r, v_g, v_b, hscale);v_store_interleave(dst + i, v_r, v_g, v_b);}} else {for ( ; i <= n - 12; i += 12, src += scn * 4){v_float32x4 v_r;v_float32x4 v_g;v_float32x4 v_b;v_float32x4 v_a;v_load_deinterleave(src, v_r, v_g, v_b, v_a);process(v_b, v_g, v_r, hscale);v_store_interleave(dst + i, v_b, v_g, v_r);}}}}#endiffor( ; i < n; i += 3, src += scn ){float b = src[bidx], g = src[1], r = src[bidx^2];float h, s, v;float vmin, diff;v = vmin = r;if( v < g ) v = g;if( v < b ) v = b;if( vmin > g ) vmin = g;if( vmin > b ) vmin = b;diff = v - vmin;s = diff/(float)(fabs(v) + FLT_EPSILON);diff = (float)(60./(diff + FLT_EPSILON));if( v == r )h = (g - b)*diff;else if( v == g )h = (b - r)*diff + 120.f;elseh = (r - g)*diff + 240.f;if( h < 0 ) h += 360.f;dst[i] = h*hscale;dst[i+1] = s;dst[i+2] = v;}}int srccn, blueIdx;float hrange;#if CV_SIMD128bool hasSIMD;#endif
};

先理解一下输入图像是CV_32F时,调用RGB2HSV_f转换过程,具体和公式相关的转换细节就不解释了, 主要看一下最后的dst[i] = h*hscale;这句话,本身h的范围是0~360,乘以的hscale是由什么控制的呢?

float hscale = hrange*(1.f/360.f);

原来hscale是由上面提到的 hrange计算得到的。对于CV_32F,此时的hscale就等于1.0.因此结果的范围也就是0~360.那么s和v的范围又是多少呢?

先来看v,v取的是b,g,r中的最大值,也就是范围同输入图像的bgr。

再来看s,s = (v - vmin)/(float)(fabs(v) + FLT_EPSILON);vmin指的是bgr中最小值,可以看出s的范围就是0~1.当b==g==r时,s取最小值0;当min(b,g,r)==0并且max(b,g,r)!=0时,s取最大值1。

因此,可以得出一下结论:

如果输入图像是CV_32F,并且BGR的范围 为0.0~255.0,时,转换之后H:0~360,S:0~1,V:0~255

如果输入图像是CV_32F,并且BGR的范围 为0.0~1.0,时,转换之后H:0~360,S:0~1,V:0~1

一般我们用的输入图像都是CV_8U的,我们要想得到H的范围为0~360,必须先将图像的类型由CV_8U转为CV_32F。一般由下面的函数完成:

    /** @brief Converts an array to another data type with optional scaling.The method converts source pixel values to the target data type. saturate_cast\<\> is applied atthe end to avoid possible overflows:\f[m(x,y) = saturate \_ cast<rType>( \alpha (*this)(x,y) +  \beta )\f]@param m output matrix; if it does not have a proper size or type before the operation, it isreallocated.@param rtype desired output matrix type or, rather, the depth since the number of channels are thesame as the input has; if rtype is negative, the output matrix will have the same type as the input.@param alpha optional scale factor.@param beta optional delta added to the scaled values.*/void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;

然后转换时如果设置尺度因子alpha为1.0,那么转换之后的V就是0~255,如果尺度因子为1.0/255,那么转换之后的V就是0~1.

根据RGB2HSV_f,可以自己去分析一下RGB2HSV_b的实现过程,此处仅给出结论:

如果输入图像是CV_8U,code为COLOR_BGR2HSV_FULL,转换之后H:0~255,S:0~255,V:0~255

如果输入图像是CV_8U,code为COLOR_BGR2HSV,转换之后H:0~180,S:0~255,V:0~255

注意,输入图像是CV_8U时,这种转换的过程中是有精度损失的。bgr转hsv,再由hsv图像转回到bgr时肯定会与原图有差异,后续的例子会说明这个问题。

HSV2BGR

主要的实现过程在函数HSV2RGB_native中。

inline void HSV2RGB_native(const float* src, float* dst, const float hscale, const int bidx)
{float h = src[0], s = src[1], v = src[2];float b, g, r;if( s == 0 )b = g = r = v;else{static const int sector_data[][3]={{1,3,0}, {1,0,2}, {3,0,1}, {0,2,1}, {0,1,3}, {2,1,0}};float tab[4];int sector;h *= hscale;if( h < 0 )do h += 6; while( h < 0 );else if( h >= 6 )do h -= 6; while( h >= 6 );sector = cvFloor(h);h -= sector;if( (unsigned)sector >= 6u ){sector = 0;h = 0.f;}tab[0] = v;tab[1] = v*(1.f - s);tab[2] = v*(1.f - s*h);tab[3] = v*(1.f - s*(1.f - h));b = tab[sector_data[sector][0]];g = tab[sector_data[sector][1]];r = tab[sector_data[sector][2]];}dst[bidx] = b;dst[1] = g;dst[bidx^2] = r;
}struct HSV2RGB_f
{typedef float channel_type;HSV2RGB_f(int _dstcn, int _blueIdx, float _hrange): dstcn(_dstcn), blueIdx(_blueIdx), hscale(6.f/_hrange) {#if CV_SIMD128hasSIMD = hasSIMD128();#endif}void operator()(const float* src, float* dst, int n) const{int i = 0, bidx = blueIdx, dcn = dstcn;n *= 3;if (dcn == 3){#if CV_SIMD128if (hasSIMD){for (; i <= n - 12; i += 12, dst += dcn * 4){v_float32x4 v_src[3];v_load_deinterleave(src + i, v_src[0], v_src[1], v_src[2]);HSV2RGB_simd(v_src[0], v_src[1], v_src[2], hscale);v_store_interleave(dst, v_src[bidx], v_src[1], v_src[bidx^2]);}}#endiffor( ; i < n; i += 3, dst += dcn ){HSV2RGB_native(src + i, dst, hscale, bidx);}} else { // dcn == 4float alpha = ColorChannel<float>::max();#if CV_SIMD128if (hasSIMD){for (; i <= n - 12; i += 12, dst += dcn * 4){v_float32x4 v_src[3];v_load_deinterleave(src + i, v_src[0], v_src[1], v_src[2]);HSV2RGB_simd(v_src[0], v_src[1], v_src[2], hscale);v_float32x4 v_a = v_setall_f32(alpha);v_store_interleave(dst, v_src[bidx], v_src[1], v_src[bidx^2], v_a);}}#endiffor( ; i < n; i += 3, dst += dcn ){HSV2RGB_native(src + i, dst, hscale, bidx);dst[3] = alpha;}}}int dstcn, blueIdx;float hscale;#if CV_SIMD128bool hasSIMD;#endif
};struct HSV2RGB_b
{typedef uchar channel_type;HSV2RGB_b(int _dstcn, int _blueIdx, int _hrange): dstcn(_dstcn), blueIdx(_blueIdx), hscale(6.0f / _hrange){#if CV_SIMD128hasSIMD = hasSIMD128();#endif}void operator()(const uchar* src, uchar* dst, int n) const{int j = 0, dcn = dstcn;uchar alpha = ColorChannel<uchar>::max();#if CV_SIMD128if (hasSIMD){for (j = 0; j <= (n - 16) * 3; j += 48, dst += dcn * 16){v_uint8x16 h_b, s_b, v_b;v_uint16x8 h_w[2], s_w[2], v_w[2];v_uint32x4 h_u[4], s_u[4], v_u[4];v_load_deinterleave(src + j, h_b, s_b, v_b);v_expand(h_b, h_w[0], h_w[1]);v_expand(s_b, s_w[0], s_w[1]);v_expand(v_b, v_w[0], v_w[1]);v_expand(h_w[0], h_u[0], h_u[1]);v_expand(h_w[1], h_u[2], h_u[3]);v_expand(s_w[0], s_u[0], s_u[1]);v_expand(s_w[1], s_u[2], s_u[3]);v_expand(v_w[0], v_u[0], v_u[1]);v_expand(v_w[1], v_u[2], v_u[3]);v_int32x4 b_i[4], g_i[4], r_i[4];v_float32x4 v_coeff0 = v_setall_f32(1.0f / 255.0f);v_float32x4 v_coeff1 = v_setall_f32(255.0f);for( int k = 0; k < 4; k++ ){v_float32x4 v_src[3];v_src[0] = v_cvt_f32(v_reinterpret_as_s32(h_u[k]));v_src[1] = v_cvt_f32(v_reinterpret_as_s32(s_u[k]));v_src[2] = v_cvt_f32(v_reinterpret_as_s32(v_u[k]));v_src[1] *= v_coeff0;v_src[2] *= v_coeff0;HSV2RGB_simd(v_src[0], v_src[1], v_src[2], hscale);v_src[0] *= v_coeff1;v_src[1] *= v_coeff1;v_src[2] *= v_coeff1;b_i[k] = v_trunc(v_src[0]);g_i[k] = v_trunc(v_src[1]);r_i[k] = v_trunc(v_src[2]);}v_uint16x8 r_w[2], g_w[2], b_w[2];v_uint8x16 r_b, g_b, b_b;r_w[0] = v_pack_u(r_i[0], r_i[1]);r_w[1] = v_pack_u(r_i[2], r_i[3]);r_b = v_pack(r_w[0], r_w[1]);g_w[0] = v_pack_u(g_i[0], g_i[1]);g_w[1] = v_pack_u(g_i[2], g_i[3]);g_b = v_pack(g_w[0], g_w[1]);b_w[0] = v_pack_u(b_i[0], b_i[1]);b_w[1] = v_pack_u(b_i[2], b_i[3]);b_b = v_pack(b_w[0], b_w[1]);if( dcn == 3 ){if( blueIdx == 0 )v_store_interleave(dst, b_b, g_b, r_b);elsev_store_interleave(dst, r_b, g_b, b_b);}else{v_uint8x16 alpha_b = v_setall_u8(alpha);if( blueIdx == 0 )v_store_interleave(dst, b_b, g_b, r_b, alpha_b);elsev_store_interleave(dst, r_b, g_b, b_b, alpha_b);}}}#endiffor( ; j < n * 3; j += 3, dst += dcn ){float buf[6];buf[0] = src[j];buf[1] = src[j+1] * (1.0f / 255.0f);buf[2] = src[j+2] * (1.0f / 255.0f);HSV2RGB_native(buf, buf + 3, hscale, blueIdx);dst[0] = saturate_cast<uchar>(buf[3] * 255.0f);dst[1] = saturate_cast<uchar>(buf[4] * 255.0f);dst[2] = saturate_cast<uchar>(buf[5] * 255.0f);if( dcn == 4 )dst[3] = alpha;}}int dstcn;int blueIdx;float hscale;#if CV_SIMD128bool hasSIMD;#endif
};

可以看到,结构体HSV2RGB_f中是直接将转换后结果放置到bgr中,结构体HSV2RGB_b在转换前,先将hsv转换为float类型,并且将s,v归一化为0-1,转换之后乘以255再利用saturate_cast<uchar>控制结果的范围在uchar内。说明输入图像是CV_8U时,hsv转bgr的过程中也是有精度损失的。

实例说明

代码

public.h

#ifndef PUBLIC_H
#define PUBLIC_H#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;#define IN
#define OUTenum ConvertType {BGR2HSV_H360 = 0,BGR2HSV_H180,BGR2HSV_H255,HSV2BGR_H360,HSV2BGR_H180,HSV2BGR_H255,
};namespace Public {void ConvertBGR2HSV(IN const Mat &src, IN  OUT Mat &dst, IN  const ConvertType type);void ConvertHSV2BGR(IN const Mat &src, IN  OUT Mat &dst, IN  const ConvertType type);/**************************Draw function*********************************/void DrawColorHist(IN const string &name, IN const Mat &src, OUT Mat &dst);};#endif // !PUBLIC_H

public.cpp

#include "public.h"void Public::ConvertBGR2HSV(IN const Mat &src, IN Mat &dst, IN  const ConvertType type)
{CV_Assert((!src.empty()) && (src.channels() == 3));switch(type)  {case BGR2HSV_H360: {Mat src_float;src.convertTo(src_float, CV_32FC3, 1.0 / 255, 0);cvtColor(src_float, dst, COLOR_BGR2HSV);break;}case BGR2HSV_H180: {cvtColor(src, dst, COLOR_BGR2HSV);break;}        case BGR2HSV_H255: {cvtColor(src, dst, COLOR_BGR2HSV_FULL);break;}default:;}
}void Public::ConvertHSV2BGR(IN const Mat &src, IN Mat &dst, IN  const ConvertType type)
{CV_Assert((!src.empty()) && (src.channels() == 3));switch (type) {case HSV2BGR_H360: {Mat dst_float;cvtColor(src, dst_float, COLOR_HSV2BGR);dst_float.convertTo(dst, CV_8UC3,  255.0, 0);break;}case HSV2BGR_H180: {cvtColor(src, dst, COLOR_HSV2BGR);break;}case HSV2BGR_H255: {cvtColor(src, dst, COLOR_HSV2BGR_FULL);break;}default:;}
}void Public::DrawColorHist(IN const string &name, IN const Mat &src, OUT Mat &dst)
{CV_Assert((!src.empty()) && (src.channels() == 3));//! [Separate the image in 3 places ( B, G and R )]vector<Mat> bgr_planes;split(src, bgr_planes);//! [Separate the image in 3 places ( B, G and R )]//! [Establish the number of bins]int histSize = 256;//! [Establish the number of bins]//! [Set the ranges ( for B,G,R) )]float range[] = { 0, 256 }; //the upper boundary is exclusiveconst float* histRange = { range };//! [Set the ranges ( for B,G,R) )]//! [Set histogram param]bool uniform = true, accumulate = false;//! [Set histogram param]//! [Compute the histograms]Mat b_hist, g_hist, r_hist;calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);//! [Compute the histograms]//! [Draw the histograms for B, G and R]int hist_w = 512, hist_h = 400;int bin_w = cvRound((double)hist_w / histSize);Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));//! [Draw the histograms for B, G and R]//! [Normalize the result to ( 0, histImage.rows )]normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());//! [Normalize the result to ( 0, histImage.rows )]//! [Draw for each channel]for (int i = 1; i < histSize; i++){line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))),Scalar(255, 0, 0), 2, 8, 0);line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))),Scalar(0, 255, 0), 2, 8, 0);line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))),Scalar(0, 0, 255), 2, 8, 0);}//! [Draw for each channel]imshow(name, histImage);dst = histImage;waitKey();//! [Display]
}

test.cpp

#include "public.h"int main()
{Mat src = imread("./4_normal.png", IMREAD_COLOR);Mat hsv_360;Public::ConvertBGR2HSV(src, hsv_360, BGR2HSV_H360);Mat hsv_hist_360;Public::DrawColorHist("hsv hist 360", hsv_360, hsv_hist_360);Mat src_360;Public::ConvertHSV2BGR(hsv_360, src_360, HSV2BGR_H360);Mat hsv_180;Public::ConvertBGR2HSV(src, hsv_180, BGR2HSV_H180);Mat hsv_hist_180;Public::DrawColorHist("hsv hist 180", hsv_180, hsv_hist_180);Mat src_180;Public::ConvertHSV2BGR(hsv_180, src_180, HSV2BGR_H180);Mat hsv_255;Public::ConvertBGR2HSV(src, hsv_255, BGR2HSV_H255);Mat hsv_hist_255;Public::DrawColorHist("hsv hist 255", hsv_255, hsv_hist_255);Mat src_255;Public::ConvertHSV2BGR(hsv_255, src_255, HSV2BGR_H255);Mat src_diff360;absdiff(src, src_360, src_diff360);double diff_mean360 = mean(src_diff360).val[0];double diff_min360, diff_max360;minMaxIdx(src_diff360, &diff_min360, &diff_max360);cout << "convert bgr to hsv360(h:0~360,s:0~1,v:0~1):" << endl;cout << "the difference between src bgr and hsv convert back bgr:" << endl;cout << "mean:" << diff_mean360 << " max:" << diff_max360 << " min:" << diff_min360 << endl;Mat src_diff180;absdiff(src, src_180, src_diff180);double diff_mean180 = mean(src_diff180).val[0];double diff_min180, diff_max180;minMaxIdx(src_diff180, &diff_min180, &diff_max180);cout << "convert bgr to hsv180(h:0~180,s:0~255,v:0~255):" << endl;cout << "the difference between src bgr and hsv convert back bgr:" << endl;cout << "mean:" << diff_mean180 << "  max:" << diff_max180 << " min:" << diff_min180 << endl;Mat src_diff255;absdiff(src, src_255, src_diff255);double diff_mean255 = mean(src_diff255).val[0];double diff_min255, diff_max255;minMaxIdx(src_diff255, &diff_min255, &diff_max255);cout << "convert bgr to hsv255(h:0~255,s:0~255,v:0~255):" << endl;cout << "the difference between src bgr and hsv convert back bgr:" << endl;cout << "mean:" << diff_mean255 << "  max:" << diff_max255 << " min:" << diff_min255 << endl;return 0;
}

程序运行结果

从程序的运行结果来看,在进行BGR转换到HSV时,使用 CV_32FC3并且尺度因子为1.0 / 255时,是损失精度最小的。在这样的转换之后,进行回转时可以得到与原图一样的图像。有一点需要注意的是,bgr转hsv时尺度因子与hsv转bgr时尺度因子必须相乘为1.也就是如果bgr转hsv时尺度因子取1.0 / 255,回转到bgr时尺度因子取 255.0;如果bgr转hsv时尺度因子取1.0,回转到bgr时尺度因子取1.0.

至此,hsv和bgr之间的转换过程基本分析完了,但是实际在使用的时候,更难的是如何选取合适的阈值去识别出我们所希望的颜色,这个就涉及和具体应用相关,也是我们真正比较关心的。现在我还没有很好的方法,只是参考网上给出的hsv的颜色表来识别颜色目标,但是效果不是很好,最好是能够和机器学习结合起来,让程序自己去学习到一个比较好的阈值,而不是自己设定好的。最近工作很多时候都在调阈值,这让我很郁闷,下一步希望可以找到合适的方法得到自适应的阈值。

参考资料

  1. https://baike.baidu.com/item/%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4/4615427?fr=aladdin
  2. https://baike.baidu.com/item/RGB%E9%A2%9C%E8%89%B2%E7%A9%BA%E9%97%B4/20868274?fr=aladdin
  3. https://baike.baidu.com/item/HSV/547122?fromtitle=HSV%E9%A2%9C%E8%89%B2%E7%A9%BA%E9%97%B4&fromid=12630604&fr=aladdin
  4. https://blog.csdn.net/coldwindha/article/details/82080176
  5. https://www.cnblogs.com/justkong/p/6588704.html
  6. https://blog.csdn.net/viewcode/article/details/8203728

Opencv颜色空间转换---BGR和HSV详解相关推荐

  1. OpenCV颜色空间转换函数:cv::cvtColor介绍

    OpenCV颜色空间转换函数:cv::cvtColor介绍 Color Conversion Code(颜色转换代码) Enumerator COLOR_BGR2BGRA add alpha chan ...

  2. 干货 | OpenCV中KLT光流跟踪原理详解与代码演示

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:opencv学堂 稀疏光流跟踪(KLT)详解 在视频移动 ...

  3. 编码字典类特征、使用sklearn的DictVectorizer方法将字典组成的列表转换成向量、详解及实战

    编码字典类特征.使用sklearn的DictVectorizer方法将字典组成的列表转换成向量.详解及实战 sklearn.feature_extraction.DictVectorizer() 把字 ...

  4. OpenCV参考手册之Mat类详解

    OpenCV参考手册之Mat类详解(一) OpenCV参考手册之Mat类详解(二) OpenCV参考手册之Mat类详解(三)

  5. listen函数的第二个参数_【图像处理】OpenCV系列十七 --- 几何图像变换函数详解(一)...

    上一篇我们学习了仿射变换的warpAffine函数,知道了如何用这个函数对图像进行旋转.平移等操作,那么本节我们一起来学习一下与仿射变换相关的其他函数以及相关的几何图像变换. 一.convertMap ...

  6. opencv: 颜色空间转换(cv2.cvtColor) 探究(图示+源码)

    API Definition 我们从 OpenCV官网 的Miscellaneous Image Transformations 上,可查到 cv2.cvtColor 这个api的定义如下: cvtC ...

  7. OpenCV参考手册之Mat类详解(三)

    Mat::eye 返回一个恒等指定大小和类型矩阵. C++: static MatExpr Mat::eye(int rows, int cols, inttype) C++: static MatE ...

  8. 转换cdm为mysql_详解PowerDesigner之CDM、PDM、SQL之间转换

    详解PowerDesigner之CDM.PDM.SQL之间转换 前段时间用了下PowerDesigner做了一些辅助工作.在此特地小结一下: 有关CDM.PDM.SQL之间转换以及不同数据库之间库表S ...

  9. OpenCV学习三:Mat类详解

    目标 我们有多种方法可以获得从现实世界的数字图像:数码相机.扫描仪.计算机体层摄影或磁共振成像就是其中的几种.在每种情况下我们(人类)看到了什么是图像.但是,转换图像到我们的数字设备时我们的记录是图像 ...

最新文章

  1. 【数据安全案例】交警计算机系统再遭***,交通违法记录随意删除
  2. jasonrpcbridge
  3. iOS App上架流程(2016详细版)
  4. HTML5 progress元素的样式控制、兼容与实例
  5. oracle12154错误 Linux,关于“EXP-00056: ORACLE error 12154 encountered”的解决方法
  6. [Linux Mysql] Linux下Mysql的基本操作
  7. 【MVC模式】Front前端控制器模式、Jsp Model1 和 Jsp Model2、MVC思想
  8. 自动点击器一秒200_做PPT还需要找模板?用这招3分钟就能自动排好PPT!
  9. 【Flink】TaskSubmissionException: No task slot allocated for job ID xx and allocation ID xx.
  10. linux 版本号 加号,如何去除Linux Kernel版本号后面的加号?
  11. AngularJS-源码阅读(八.二)
  12. mysql导入超大sql文件时mysql服务重启
  13. android 蓝牙 发送字符串,Android向TLSR8266蓝牙mesh发送指令
  14. java tic tac toe_确定Tic Tac Toe游戏的算法
  15. 公司文案编辑常用迅捷PDF转换成Word转换器
  16. Pycharm环境下调用Qt desinger 常见问题以及解决方法
  17. php页眉,自定义页眉
  18. CString 和 LPCTSTR 之间的转换 及 LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR的区分与转化
  19. python as f是什么意思_Python中 with open(file_abs,'r') as f: 的用法以及意义
  20. 计算机丢失libjcc dll,libjcc.dll 64位

热门文章

  1. 刷题第45, 46天 | 70. 爬楼梯 (进阶)、322. 零钱兑换、279.完全平方数、139.单词拆分
  2. Spring Cloud Ribbon负载均衡策略详解
  3. 【100%通过率】华为OD机试真题 C 实现【探索地块建立】【2022.11 Q4 新题】
  4. mondb的and和or组合查询,pymongo的and和or组合查询
  5. 树链剖分(重链剖分法)
  6. css文字大小自适应
  7. python生成随机数方法_Python随机数生成方法
  8. Robot Framework简介和性能;安装RIDE工具进行Robot Framework测试
  9. c语言管理系统开发,日记管理系统的开发(C语言版)
  10. 直播如何适配医疗场景?有哪些靠谱的直播平台吗?