opencv 色彩空间


注:本文使用opencv3.4.2

灰度色彩空间

单通道,取值范围[0,255]

RGB色彩空间(opencv中习惯用BGR)

计算机色彩显示器和彩色电视机显示色彩的原理一样,都是采用R、G、B相加混色的原理,通过发射出三种不同强度的电子束,使屏幕内侧覆盖的红、绿、蓝磷光材料发光而产生色彩。这种色彩的表示方法称为RGB色彩空间表示。
在RGB颜色空间中,任意色光F都可以用R、G、B三色不同分量的相加混合而成:F=r[R]+r[G]+r[B] F=r[R]+r[G]+r[B]
RGB色彩空间还可以用一个三维的立方体来描述。当三基色分量都为0(最弱)时混合为黑色光;当三基色都为k(最大,值由存储空间决定)时混合为白色光。

opencv中R,G,B三通道取值范围均为[0,255]。

在这里插入图片描述

HSV/HSL色彩空间

HSV是一种将RGB色彩空间中的点在倒圆锥体中的表示方法。HSV即色相(Hue)、饱和度(Saturation)、明度(Value),又称HSB(B即Brightness)。色相是色彩的基本属性,就是平常说的颜色的名称,如红色、黄色等。饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。明度(V),取0-max(计算机中HSV取值范围和存储的长度有关)。HSV颜色空间可以用一个圆锥空间模型来描述。圆锥的顶点处,V=0,H和S无定义,代表黑色。圆锥的顶面中心处V=max,S=0,H无定义,代表白色。

还有一种圆柱形表示
在这里插入图片描述
HSL和HSV稍有区别,一般我们常用的是HSV模型:
https://blog.csdn.net/binglan520/article/details/56288135

Hue 定义在圆周上,取值范围[0,360][0,360]
S和V取值范围是[0,1][0,1]

opencv中:

  • 图片数据类型为uchar(CV_8U)和ushort(CV_16U)时,默认将Hue映射到[0,180][0,180],区间[180,255][180,255]和区间[0,75][0,75]重合,此时可以修改fullRangetrue将Hue映射到[0,256][0,256],但是我们一般不这样做。
  • 图片类型为float(CV_32F)时,将Hue映射到[0,360][0,360]
  • S和V取值范围是[0,255][0,255]

opencv源码:

int hrange = depth == CV_32F ? 360 : isFullRange ? 256 : 180;

CIE-Lab色彩空间

占坑

opencv中的存储

c++ 中,R,G和B通道值的常规范围是:

  • CV_8U图像为0到255
  • 对于CV_16U图像,为0到65535
  • CV_32F图像为0到1

色彩空间转换

使用void cvtColor( InputArray _src, OutputArray _dst, int code, int dcn )函数。

  • src,dst,code,dcn:原图片,目标图片,转换格式,目标图片通道数。

这部分代码位于opencv_imgproc项目的color.cpp中,需要源码编译才能查看(源码编译步骤:https://blog.csdn.net/qq_33485434/article/details/78488710):
在这里插入图片描述

下面是cvtColor的主要代码,篇幅过长,没有全放上来。

void cvtColor( InputArray _src, OutputArray _dst, int code, int dcn )
{
    CV_INSTRUMENT_REGION()
    if(dcn <= 0) //如果dcn错误,通过code获得正确的dcn
            dcn = dstChannels(code);
//这部分涉及UMat(需要opencl,能够使用gpu计算),调用的是ocl_cvtColor()函数
    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函数将不同的转换分成不同任务
    switch( code )
    {
        case COLOR_BGR2GRAY: case COLOR_BGRA2GRAY:
        case COLOR_RGB2GRAY: case COLOR_RGBA2GRAY:
            cvtColorBGR2Gray(_src, _dst, swapBlue(code));
            break;

        case COLOR_GRAY2BGR:
        case COLOR_GRAY2BGRA:
            cvtColorGray2BGR(_src, _dst, dcn);
            break;
        
        case COLOR_BGR2HSV: case COLOR_BGR2HSV_FULL:
        case COLOR_RGB2HSV: case COLOR_RGB2HSV_FULL:
            cvtColorBGR2HSV(_src, _dst, swapBlue(code), isFullRangeHSV(code));
            break;

        case COLOR_HSV2BGR: case COLOR_HSV2BGR_FULL:
        case COLOR_HSV2RGB: case COLOR_HSV2RGB_FULL:
            cvtColorHSV2BGR(_src, _dst, dcn, swapBlue(code), isFullRangeHSV(code));
            break;
			...
} 

BGR空间到灰度空间

理论公式:
RGB[A] to Gray:Y0.299R+0.587G+0.114B RGB[A]\ to\ Gray:Y←0.299⋅R+0.587⋅G+0.114⋅B
从公式上可以看出,绿色通道占主要部分。
opencv源码:

void cvtColorBGR2Gray( InputArray _src, OutputArray _dst, bool swapb)
{
	//CvtHelper是一个opencv自定义类型,定义在Color.hpp中,
	//CvtHelper(InputArray _src, OutputArray _dst, int dcn)
	//功能是:
	//1.验证一下src和dst的数据类型,通道数是否支持;
	//2.判断src和dst是否相同。如果src和dst相同,先对src进行copy,操作完再用copy的img覆盖src
	//3.涉及到yuv空间有一个尺寸上的变换
	//4.把处理后的image赋给自身的成员
    CvtHelper< Set<3, 4>, Set<1>, Set<CV_8U, CV_16U, CV_32F> > h(_src, _dst, 1);
	//调用cvtBGRtoGray
    hal::cvtBGRtoGray(h.src.data, h.src.step, h.dst.data, h.dst.step, h.src.cols, h.src.rows,
                      h.depth, h.scn, swapb);
}
// 支持三种数据类型:8u, 16u, 32f
void cvtBGRtoGray(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)
{
    CV_INSTRUMENT_REGION()
	//涉及 OpenCV Hardware Acceleration Layer (HAL),不去管它
    CALL_HAL(cvtBGRtoGray, cv_hal_cvtBGRtoGray, src_data, src_step, dst_data, dst_step, width, height, depth, scn, swapBlue);

	//检查是否支持intel的ipp库,就是那个讨厌的ippicv,这部分我去掉了
	...
	
    int blueIdx = swapBlue ? 2 : 0;
    if( depth == CV_8U )
    	//核心代码用CvtColorLoop调用RGB2Gray
    	//CvtColorLoop里边:
    	//1.并行处理RGB2Gray每次传入一个pixel,共调用n=width*height次
    	//2.对像素归一化,像素映射到[0,1]
        CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2Gray<uchar>(scn, blueIdx, 0));
    else if( depth == CV_16U )
        CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2Gray<ushort>(scn, blueIdx, 0));
    else
        CvtColorLoop(src_data, src_step, dst_data, dst_step, width, height, RGB2Gray<float>(scn, blueIdx, 0));
}

    	/*
    	color.hpp中R2Y, G2Y, B2Y等定义
    	//constants for conversion from/to RGB and Gray, YUV, YCrCb according to BT.601
    	const float B2YF = 0.114f;
    	const float G2YF = 0.587f;
    	const float R2YF = 0.299f;

    	enum
    	{
        	yuv_shift = 14,
        	xyz_shift = 12,
        	R2Y = 4899, // == R2YF*16384
        	G2Y = 9617, // == G2YF*16384
        	B2Y = 1868, // == B2YF*16384
        	BLOCK_SIZE = 256
    	};
		*/

template<typename _Tp> struct RGB2Gray
{
    typedef _Tp channel_type;
	//RGB2Gray参数
	//_srccn:原图片通道数
	//蓝色通道index,决定是否对通道转换,BGR=0,RGB=2,BGR和RGB就差在这了
	//coeffs:各通道权重
    RGB2Gray(int _srccn, int blueIdx, const float* _coeffs) : srccn(_srccn)
    {
    	//0.299f, 0.587f, 0.114f
        static const float coeffs0[] = { R2YF, G2YF, B2YF };
        memcpy( coeffs, _coeffs ? _coeffs : coeffs0, 3*sizeof(coeffs[0]) );
        if(blueIdx == 0)
            std::swap(coeffs[0], coeffs[2]);
    }

    void operator()(const _Tp* src, _Tp* dst, int n) const
    {
        int scn = srccn;
        float cb = coeffs[0], cg = coeffs[1], cr = coeffs[2];
        for(int i = 0; i < n; i++, src += scn)
        	//核心代码!!! 
        	//// 0.299f*src[0]+0.587f*src[1]+0.114f*src[2]
            dst[i] = saturate_cast<_Tp>(src[0]*cb + src[1]*cg + src[2]*cr);
    }
    int srccn;
    float coeffs[3];
};

其它转换方法:
https://www.cnblogs.com/zhangjiansheng/p/6925722.html

灰度空间到BGR空间

由于灰度空间到BGR空间的转换进行了升维,我们并不知道B、G、R三个通道所占的概率,所以认为是等概率的,此外,如果有alpha通道,直接设为最大值。公式如下:
Gray to RGB[A]:RY,GY,BY,Amax(ChannelRange) Gray\ to\ RGB[A]:R←Y,G←Y,B←Y,A←max(ChannelRange)

前面的通道数、尺寸、数据类型检查、封装、并行加速等和上面类似,直接放核心代码:

template<typename _Tp>
struct Gray2RGB
{
    typedef _Tp channel_type;

    Gray2RGB(int _dstcn) : dstcn(_dstcn) {}
    void operator()(const _Tp* src, _Tp* dst, int n) const
    {
        if( dstcn == 3 )
            for( int i = 0; i < n; i++, dst += 3 )
            {
                dst[0] = dst[1] = dst[2] = src[i];
            }
        else
        {
            _Tp alpha = ColorChannel<_Tp>::max();
            for( int i = 0; i < n; i++, dst += 4 )
            {
                dst[0] = dst[1] = dst[2] = src[i];
                dst[3] = alpha;
            }
        }
    }

    int dstcn;
};

实验:将彩色图转为灰度图再转回彩色图

int main()
{

	Mat img = imread("dog.jpg");
	resize(img, img, Size(img.cols / 2,img.rows / 2));//长、宽缩小一倍
	Mat grayimg,newimg;
	cvtColor(img, grayimg, COLOR_BGR2GRAY); 
	cvtColor(grayimg, newimg, COLOR_GRAY2BGR);
	
	imshow("origin", img);
	imshow("gray img", grayimg);
	imshow("new img", newimg);

	waitKey(0);
	destroyAllWindows();
}

在这里插入图片描述
可以看出灰度图转无法真正的转为彩色图。因为并不知道RGB与灰度的比例关系,只能简单地设每个像素的 R=G=B=灰度。转换之后,图片看上去还是灰色。

BGR空间与HSV空间相互转换

直接上官网的截图了:
在这里插入图片描述
在这里插入图片描述
opencv源码:
rgb转hsv,uchar或ushort型的类RGB2HSV_b
float型的RGB2HSV_f和它很相似,就不放了。

struct RGB2HSV_b
{
    typedef uchar channel_type;
	//构造函数,初始化列表为:
	//source image 通道数
	//蓝色分量所在通道的index  BGR是0,RGB是2
    //h通道范围 180或256
    RGB2HSV_b(int _srccn, int _blueIdx, int _hrange)
    : srccn(_srccn), blueIdx(_blueIdx), hrange(_hrange)
    {
    //断言,判断h通道范围是否正确
        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;
        //根据h的取值范围决定hdiv_table指向哪个数组
        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;

			/* 	
				注释中的代码位于precomp.hpp中
				
				 
				 extern const uchar icvSaturate8u_cv[]; //数组icvSaturate8u_cv[] 位于tables.cpp中
				 #define CV_FAST_CAST_8U(t)  ( (-256 <= (t) && (t) <= 512) ?icvSaturate8u_cv[(t)+256] : 0 )
				 #define CV_CALC_MIN_8U(a,b) (a) -= CV_FAST_CAST_8U((a) - (b))
				 #define CV_CALC_MAX_8U(a,b) (a) += CV_FAST_CAST_8U((b) - (a))

			*/
			/*获得v(vmax)和vmin*/
			//CV_CALC_MAX_8U(a,b) 利用宏命令和查表,将a,b中较大的数赋给a
			//CV_CALC_MIN_8U(a,b) 利用宏命令和查表,将a,b中较小的数赋给a
            CV_CALC_MAX_8U( v, g );
            CV_CALC_MAX_8U( v, r );
            CV_CALC_MIN_8U( vmin, g );
            CV_CALC_MIN_8U( vmin, r );
            
            /*计算s和h*/
            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;
};

hsv转rgb uchar或ushort型的类HSV2RGB_b

struct HSV2RGB_b
{
    typedef uchar channel_type;

    HSV2RGB_b(int _dstcn, int _blueIdx, int _hrange)
    : dstcn(_dstcn), blueIdx(_blueIdx), hscale(6.0f / _hrange)
    {
    }

    void operator()(const uchar* src, uchar* dst, int n) const
    {
        int j = 0, dcn = dstcn;
        uchar alpha = ColorChannel<uchar>::max();

        for( ; 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
            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;
};

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;
}

通过对opencv代码的简单阅读,发现大量使用了查找表来简化计算:
https://blog.csdn.net/qq_23968185/article/details/51282049

发布了83 篇原创文章 · 获赞 31 · 访问量 2万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览