OpenCV图像处理专栏一 | 盘点常见颜色空间互转

C++ RGB格式转换为HSV格式,颜色提取操作 颜色的提取在HSV空间效果更好,然而我们的图像格式一般都是RGB格式的。因此我们首先需要用到将RGB图像格式转换为HSV格式。简单介绍一下HSV,H表示色彩、色调,S表示纯度,V表示色彩的明亮程度。值得一提的是,HSV各数据的取值范围根据各自的计算方法都没统一。 公式: 设 (r, g, b) 分别是一个颜色的红、绿和蓝坐标,它们的值是在 0 到 1 之间的实数 设 max 等于 r, g, b 中的最大者 设 min 等于 r, g, b 中的最小者 代码: 我们采用的方法,H取值范围[0,360], 阅读详情

前言

今天是OpenCV传统图像处理算法的第一篇,我们来盘点一下常见的6种颜色空间互转算法,并给出了一些简单的加速方案,希望可以帮助到学习OpenCV图像处理的同学。这6种算法分别是:

  • RGB和GRAY互转
  • RGB和YUV互转
  • RGB和HSV互转
  • RGB和HSI互转
  • RGB和YCbCr互转
  • RGB和YDbDr互转

算法原理和代码实现

一,RGBGG转GRAY

RGB是依据人眼识别的颜色定义出的空间,可表示大部分颜色。是图像处理中最基本、最常用、面向硬件的颜色空间,是一种光混合的体系。

RGB颜色空间最常用的用途就是显示器系统,彩色阴极射线管,彩色光栅图形的显示器都使用R、G、B数值来驱动R、G、B 电子枪发射电子,并分别激发荧光屏上的R、G、B三种颜色的荧光粉发出不同亮度的光线,并通过相加混合产生各种颜色。扫描仪也是通过吸收原稿经反射或透射而发送来的光线中的R、G、B成分,并用它来表示原稿的颜色。

首先是RGB2GRAY,也就是彩色图转灰度图的算法。RGB值和灰度的转换,实际上是人眼对于彩色的感觉到亮度感觉的转换,这是一个心理学问题,有一个公式:
Grey = 0.299×\times×R + 0.587×\times×G + 0.114×\times×B。直接计算复杂度较高,考虑优化可以将小数转为整数,除法变为移位,乘法也变为移位,但是这种方法也会带来一定的精度损失,我们可以根据实际情况选择需要保留的精度位数。下面给出不同精度(2-20位)的计算公式:

Grey = (R*1 + G*2 + B*1) >> 2

Grey= (R*2 + G*5 + B*1) >> 3

Grey= (R*4 + G*10 + B*2) >> 4

Grey = (R*9 + G*19 + B*4) >> 5

Grey = (R*19 + G*37 + B*8) >> 6

Grey= (R*38 + G*75 + B*15) >> 7

Grey= (R*76 + G*150 + B*30) >> 8

Grey = (R*153 + G*300 + B*59) >> 9

Grey = (R*306 + G*601 + B*117) >> 10

Grey = (R*612 + G*1202 + B*234) >> 11

Grey = (R*1224 + G*2405 + B*467) >> 12

Grey= (R*2449 + G*4809 + B*934) >> 13

Grey= (R*4898 + G*9618 + B*1868) >> 14

Grey = (R*9797 + G*19235 + B*3736) >> 15

Grey = (R*19595 + G*38469 + B*7472) >> 16

Grey = (R*39190 + G*76939 + B*14943) >> 17

Grey = (R*78381 + G*153878 + B*29885) >> 18

Grey =(R*156762 + G*307757 + B*59769) >> 19

Grey= (R*313524 + G*615514 + B*119538) >> 20

再给出保留20位精度的计算代码(使用了Openmp多线程优化):

//RGB2GRAY优化
Mat speed_rgb2gray(Mat src) {
	Mat dst(src.rows, src.cols, CV_8UC1);
#pragma omp parallel for num_threads(4)
	for (int i = 0; i < src.rows; i++) {
		for (int j = 0; j < src.cols; j++) {
			dst.at<uchar>(i, j) = ((src.at<Vec3b>(i, j)[0] << 18) + (src.at<Vec3b>(i, j)[0] << 15) + (src.at<Vec3b>(i, j)[0] << 14) +
				(src.at<Vec3b>(i, j)[0] << 11) + (src.at<Vec3b>(i, j)[0] << 7) + (src.at<Vec3b>(i, j)[0] << 7) + (src.at<Vec3b>(i, j)[0] << 5) +
				(src.at<Vec3b>(i, j)[0] << 4) + (src.at<Vec3b>(i, j)[0] << 2) +
				(src.at<Vec3b>(i, j)[1] << 19) + (src.at<Vec3b>(i, j)[1] << 16) + (src.at<Vec3b>(i, j)[1] << 14) + (src.at<Vec3b>(i, j)[1] << 13) +
				(src.at<Vec3b>(i, j)[1] << 10) + (src.at<Vec3b>(i, j)[1] << 8) + (src.at<Vec3b>(i, j)[1] << 4) + (src.at<Vec3b>(i, j)[1] << 3) + (src.at<Vec3b>(i, j)[1] << 1) +
				(src.at<Vec3b>(i, j)[2] << 16) + (src.at<Vec3b>(i, j)[2] << 15) + (src.at<Vec3b>(i, j)[2] << 14) + (src.at<Vec3b>(i, j)[2] << 12) +
				(src.at<Vec3b>(i, j)[2] << 9) + (src.at<Vec3b>(i, j)[2] << 7) + (src.at<Vec3b>(i, j)[2] << 6) + (src.at<Vec3b>(i, j)[2] << 5) + (src.at<Vec3b>(i, j)[2] << 4) + (src.at<Vec3b>(i, j)[2] << 1) >> 20);
		}
	}
	return dst;
}

二,RGB和YUV互转

首先介绍一下YUV颜色空间,YUV(亦称YCrCb)是被欧洲电视系统所采用的一种颜色编码方法。在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄影机进行取像,然后把取得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),最后发送端将亮度和两个色差总共三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V信号分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。
YUV主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之同的差异。
1,RGB转YUV

Y = 0.299R + 0.587G + 0.114B
U = -0.147R - 0.289G + 0.436B
V = 0.615R - 0.515G - 0.100B

2,YUV转RGB

R = Y + 1.14V
G = Y - 0.39U - 0.58V
B = Y + 2.03U

优化1:去掉浮点运算

基于这一点,我们做如下操作:

Y * 256 = 0.299 * 256R + 0.587 * 256G + 0.114 * 256B

U * 256 = -0.147 * 256R - 0.289 * 256G + 0.436 * 256B
V * 256 = 0.615 * 256R - 0.515 * 256G - 0.100 * 256B

R * 256 = Y * 256 + 1.14 * 256V
G * 256 = Y * 256 - 0.39 * 256U - 0.58 * 256V
B * 256 = Y * 256 + 2.03 * 256U

简化上面的公式如下:

256Y = 76.544R + 150.272G + 29.184B

256U = -37.632R - 73.984G + 111.616B

256V = 157.44R - 131.84G - 25.6B

256R = 256Y + 291.84V

256G = 256Y - 99.84U - 148.48V

256B = 256Y + 519.68U

然后,我们就可以对上述公式进一步优化,彻底干掉小数,注意这里是有精度损失的。

256Y = 77R + 150G + 29B

256U = -38R - 74G + 112B

256V = 158R - 132G - 26B

256R = 256Y + 292V

256G = 256Y - 100U - 149V

256B = 256Y + 520U

实际上就是四舍五入,这是乘以256是为了缩小误差,当然乘数越大,误差越小。和RGB2GRAY一样的套路。

优化二:乘法和除法变为移位运算

先将除法变为移位运算:

Y = (77R + 150G + 29B) >> 8

U = (-38R - 74G + 112B) >> 8

V = (158R - 132G - 26B) >> 8

R = (256Y + 292V) >> 8

G = (256Y - 100U - 149V) >> 8

B = (256Y + 520U) >> 8

公式中还有很多乘法运算,乘法跟移位运算相比,还是效率太低了,因此,我们将把所有乘法都改成移位运算。如何将常数乘法改成移位运算?这里给个例子:Y=Y*9可以改为:Y=(Y<<3)+Y。因此,我们可以讲YUV的公式继续改为最简:
RGB转YUV:

Y = ((R << 6) + (R << 3) + (R << 2) + R + (G << 7) + (G << 4) + (G << 2) + (G << 1) + (B << 4) + (B << 3) + (B << 2) + B) >> 8;
U = (-((R << 5) + (R << 2) + (R << 1)) - ((G << 6) + (G << 3) + (G << 1)) + ((B << 6) + (B << 5) + (B << 4))) >> 8;
V = ((R << 7) + (R << 4) + (R << 3) + (R << 2) + (R << 1) - ((G << 7) + (G << 2)) - ((B << 4) + (B << 3) + (B << 1))) >> 8;

YUV转RGB:

R = ((Y << 8) + ((V << 8) + (V << 5) + (V << 2))) >> 8;
G = ((Y << 8) - ((U << 6) + (U << 5) + (U << 2)) - ((V << 7) + (V << 4) + (V << 2) + V)) >> 8;
B = ((Y << 8) + (U << 9) + (U << 3)) >> 8;

使用OpemMP和上诉优化的互转代码如下:注意一下,imread读取的图片通道顺序默认是BGR。

//RGB2YUV优化
Mat speed_rgb2yuv(Mat src) {
	Mat dst(src.rows, src.cols, CV_8UC3);
#pragma omp parallel for num_threads(4)
	for (int i = 0; i < src.rows; i++) {
		for (int j = 0; j < src.cols; j++) {
			dst.at<Vec3b>(i, j)[0] =
				((src.at<Vec3b>(i, j)[2] << 6) + (src.at<Vec3b>(i, j)[2] << 3) + (src.at<Vec3b>(i, j)[2] << 2) + src.at<Vec3b>(i, j)[2] +
				(src.at<Vec3b>(i, j)[1] << 7) + (src.at<Vec3b>(i, j)[1] << 4) + (src.at<Vec3b>(i, j)[1] << 2) + (src.at<Vec3b>(i, j)[1] << 1) +
					(src.at<Vec3b>(i, j)[0] << 4) + (src.at<Vec3b>(i, j)[0] << 3) + (src.at<Vec3b>(i, j)[0] << 2) + src.at<Vec3b>(i, j)[0]) >> 8;
			dst.at<Vec3b>(i, j)[1] = (-((src.at<Vec3b>(i, j)[2] << 5) + (src.at<Vec3b>(i, j)[2] << 2) + (src.at<Vec3b>(i, j)[2] << 1)) -
				((src.at<Vec3b>(i, j)[1] << 6) + (src.at<Vec3b>(i, j)[1] << 3) + (src.at<Vec3b>(i, j)[1] << 1)) +
				((src.at<Vec3b>(i, j)[0] << 6) + (src.at<Vec3b>(i, j)[0] << 5) + (src.at<Vec3b>(i, j)[0] << 4))) >> 8;
			dst.at<Vec3b>(i, j)[2] = ((src.at<Vec3b>(i, j)[2] << 7) + (src.at<Vec3b>(i, j)[2] << 4) + (src.at<Vec3b>(i, j)[2] << 3) + (src.at<Vec3b>(i, j)[2] << 2) + (src.at<Vec3b>(i, j)[2] << 1) -
				((src.at<Vec3b>(i, j)[1] << 7) + (src.at<Vec3b>(i, j)[1] << 2)) - ((src.at<Vec3b>(i, j)[0] << 4) + (src.at<Vec3b>(i, j)[0] << 3) + (src.at<Vec3b>(i, j)[0] << 1))) >> 8;
		}
	}
	return dst;
}


//YUV2RGB优化
Mat speed_yuv2rgb(Mat src) {
	Mat dst(src.rows, src.cols, CV_8UC3);
	#pragma omp parallel for num_threads(4)
	for (int i = 0; i < src.rows; i++) {
		for (int j = 0; j < src.cols; j++) {
			dst.at<Vec3b>(i, j)[0] = ((src.at<Vec3b>(i, j)[0] << 8) + (src.at<Vec3b>(i, j)[1] << 9) + (src.at<Vec3b>(i, j)[1] << 3)) >> 8;
			dst.at<Vec3b>(i, j)[1] = ((src.at<Vec3b>(i, j)[0] << 8) - ((src.at<Vec3b>(i, j)[1] << 6) + (src.at<Vec3b>(i, j)[1] << 5) +
			(src.at<Vec3b>(i, j)[1] << 2)) - ((src.at<Vec3b>(i, j)[2] << 7) + (src.at<Vec3b>(i, j)[2] << 4) +
			(src.at<Vec3b>(i, j)[2] << 2) + src.at<Vec3b>(i, j)[2])) >> 8;
			dst.at<Vec3b>(i, j)[2] = ((src.at<Vec3b>(i, j)[0] << 8) + ((src.at<Vec3b>(i, j)[2] << 8) + (src.at<Vec3b>(i, j)[2] << 5) +
			(src.at<Vec3b>(i, j)[2] << 2))) >> 8;
		}
	}
	return dst;
}

三,RGB和HSV互转

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无定义,代表白色。
RGB颜色空间中,三种颜色分量的取值与所生成的颜色之间的联系并不直观。而HSV颜色空间,更类似于人类感觉颜色的方式,封装了关于颜色的信息:“这是什么颜色?深浅如何?明暗如何?”。
从RGB转到HSV的计算公式为:设max等于r、g和b中的最大者,min为最小者。对应的HSV空间中的(h,s,v)值为:
在这里插入图片描述h在0到360°之间,s在0到100%之间,v在0到max之间。
从HSV空间转回RGB空间的公式为:
在这里插入图片描述
代码实现,效果测试无误:

Mat RGB2HSV(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_32FC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			float b = src.at<Vec3b>(i, j)[0] / 255.0;
			float g = src.at<Vec3b>(i, j)[1] / 255.0;
			float r = src.at<Vec3b>(i, j)[2] / 255.0;
			float minn = min(r, min(g, b));
			float maxx = max(r, max(g, b));
			dst.at<Vec3f>(i, j)[2] = maxx; //V
			float delta = maxx - minn;
			float h, s;
			if (maxx != 0) {
				s = delta / maxx;
			}
			else {
				s = 0;
			}
			if (r == maxx) {
				h = (g - b) / delta;
			}
			else if (g == maxx) {
				h = 2 + (b - r) / delta;
			}
			else {
				h = 4 + (r - g) / delta;
			}
			h *= 60;
			if (h < 0)
				h += 360;
			dst.at<Vec3f>(i, j)[0] = h;
			dst.at<Vec3f>(i, j)[1] = s;
		}
	}
	return dst;
}

Mat HSV2RGB(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	float r, g, b, h, s, v;
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			h = src.at<Vec3f>(i, j)[0];
			s = src.at<Vec3f>(i, j)[1];
			v = src.at<Vec3f>(i, j)[2];
			if (s == 0) {
				r = g = b = v;
			}
			else {
				h /= 60;
				int offset = floor(h);
				float f = h - offset;
				float p = v * (1 - s);
				float q = v * (1 - s * f);
				float t = v * (1 - s * (1 - f));
				switch (offset)
				{
				case 0: r = v; g = t; b = p; break;
				case 1: r = q; g = v; b = p; break;
				case 2: r = p; g = v; b = t; break;
				case 3: r = p; g = q; b = v; break;
				case 4: r = t; g = p; b = v; break;
				case 5: r = v; g = p; b = q; break;
				default:
					break;
				}
			}
			dst.at<Vec3b>(i, j)[0] = int(b * 255);
			dst.at<Vec3b>(i, j)[1] = int(g * 255);
			dst.at<Vec3b>(i, j)[2] = int(r * 255);
		}
	}
	return dst;
}

四,RGB和HSI互转

HSI色彩空间是从人的视觉系统出发,用色调(Hue)、饱和度(Saturation或Chroma)和亮度 (Intensity或Brightness)来描述色彩。

  • H——表示颜色的相位角。红、绿、蓝分别相隔120度;互补色分别相差180度,即颜色的类别。
  • S——表示成所选颜色的纯度和该颜色最大的纯度之间的比率,范围:[0, 1],即颜色的深浅程度。
  • I——表示色彩的明亮程度,围:[0, 1],人眼对亮度很敏感!
    在这里插入图片描述
    可以看到HSI色彩空间和RGB色彩空间只是同一物理量的不同表示法,因而它们之间存在着转换关系:HSI颜色模式中的色调使用颜色类别表示,饱和度与颜色的白光光亮亮度刚好成反比,代表灰色与色调的比例,亮度是颜色的相对明暗程度。
    转自:https://blog.youkuaiyun.com/aoshilang2249/article/details/38070663
    RGB转换为HSI的公式:
    在这里插入图片描述
    HSI转RGB的公式为:给定 HSI空间中的 (h, s, l) 值定义的一个颜色,带有 h 在指示色相角度的值域 [0, 360)中,分别表示饱和度和亮度的s 和 l 在值域 [0, 1] 中,相应在 RGB 空间中的 (r, g, b) 三原色,带有分别对应于红色、绿色和蓝色的 r, g 和 b 也在值域 [0, 1] 中,它们可计算为:
    首先,如果 s = 0,则结果的颜色是非彩色的、或灰色的。在这个特殊情况,r, g 和 b 都等于 l。注意 h 的值在这种情况下是未定义的。当 s ≠ 0 的时候,可以使用下列过程:
    在这里插入图片描述
    源码实现,测试无误:
Mat RGB2HSI(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_32FC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			float b = src.at<Vec3b>(i, j)[0] / 255.0;
			float g = src.at<Vec3b>(i, j)[1] / 255.0;
			float r = src.at<Vec3b>(i, j)[2] / 255.0;
			float minn = min(b, min(g, r));
			float maxx = max(b, max(g, r));
			float H = 0;
			float S = 0;
			float I = (minn + maxx) / 2.0f;
			if (maxx == minn) {
				dst.at<Vec3f>(i, j)[0] = H;
				dst.at<Vec3f>(i, j)[1] = S;
				dst.at<Vec3f>(i, j)[2] = I;
			}
			else {
				float delta = maxx - minn;
				if (I < 0.5) {
					S = delta / (maxx + minn);
				}
				else {
					S = delta / (2.0 - maxx - minn);
				}
				if (r == maxx) {
					if (g > b) {
						H = (g - b) / delta;
					}
					else {
						H = 6.0 + (g - b) / delta;
					}
				}
				else if (g == maxx) {
					H = 2.0 + (b - r) / delta;
				}
				else {
					H = 4.0 + (r - g) / delta;
				}
				H /= 6.0; //除以6,表示在那个部分
				if (H < 0.0)
					H += 1.0;
				if (H > 1)
					H -= 1;
				H = (int)(H * 360); //转成[0, 360]
				dst.at<Vec3f>(i, j)[0] = H;
				dst.at<Vec3f>(i, j)[1] = S;
				dst.at<Vec3f>(i, j)[2] = I;
			}
		}
	}
	return dst;
}

float get_Ans(double p, double q, double Ht) {
	if (Ht < 0.0)
		Ht += 1.0;
	else if (Ht > 1.0)
		Ht -= 1.0;
	if ((6.0 * Ht) < 1.0)
		return (p + (q - p) * Ht * 6.0);
	else if ((2.0 * Ht) < 1.0)
		return q;
	else if ((3.0 * Ht) < 2.0)
		return (p + (q - p) * ((2.0F / 3.0F) - Ht) * 6.0);
	else
		return (p);
}

Mat HSI2RGB(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			float r, g, b, M1, M2;
			float H = src.at<Vec3f>(i, j)[0];
			float S = src.at<Vec3f>(i, j)[1];
			float I = src.at<Vec3f>(i, j)[2];
			float hue = H / 360;
			if (S == 0) {//灰色
				r = g = b = I;
			}
			else {
				if (I <= 0.5) {
					M2 = I * (1.0 + S);
				}
				else {
					M2 = I + S - I * S;
				}
				M1 = (2.0 * I - M2);
				r = get_Ans(M1, M2, hue + 1.0 / 3.0);
				g = get_Ans(M1, M2, hue);
				b = get_Ans(M1, M2, hue - 1.0 / 3.0);
			}
			dst.at<Vec3b>(i, j)[0] = (int)(b * 255);
			dst.at<Vec3b>(i, j)[1] = (int)(g * 255);
			dst.at<Vec3b>(i, j)[2] = (int)(r * 255);
		}
	}
	return dst;
}

五,RGB和YCbCr互转

在常用的几种颜色空间中,YCbCr颜色空间在学术论文中出现的频率是相当高的,常用于肤色检测等等。其和RGB空间之间的相互转换公式在网上也有多种,我们这里取http://en.wikipedia.org/wiki/YCbCr 描述的JPG转换时使用的计算公式:
在这里插入图片描述和RGB与CIEXYZ空间互转的优化套路一样,测试无误的代码如下:

const float YCbCrYRF = 0.299F;              // RGB转YCbCr的系数(浮点类型)
const float YCbCrYGF = 0.587F;
const float YCbCrYBF = 0.114F;
const float YCbCrCbRF = -0.168736F;
const float YCbCrCbGF = -0.331264F;
const float YCbCrCbBF = 0.500000F;
const float YCbCrCrRF = 0.500000F;
const float YCbCrCrGF = -0.418688F;
const float YCbCrCrBF = -0.081312F;

const float RGBRYF = 1.00000F;            // YCbCr转RGB的系数(浮点类型)
const float RGBRCbF = 0.0000F;
const float RGBRCrF = 1.40200F;
const float RGBGYF = 1.00000F;
const float RGBGCbF = -0.34414F;
const float RGBGCrF = -0.71414F;
const float RGBBYF = 1.00000F;
const float RGBBCbF = 1.77200F;
const float RGBBCrF = 0.00000F;

const int Shift = 20;
const int HalfShiftValue = 1 << (Shift - 1);

const int YCbCrYRI = (int)(YCbCrYRF * (1 << Shift) + 0.5);         // RGB转YCbCr的系数(整数类型)
const int YCbCrYGI = (int)(YCbCrYGF * (1 << Shift) + 0.5);
const int YCbCrYBI = (int)(YCbCrYBF * (1 << Shift) + 0.5);
const int YCbCrCbRI = (int)(YCbCrCbRF * (1 << Shift) + 0.5);
const int YCbCrCbGI = (int)(YCbCrCbGF * (1 << Shift) + 0.5);
const int YCbCrCbBI = (int)(YCbCrCbBF * (1 << Shift) + 0.5);
const int YCbCrCrRI = (int)(YCbCrCrRF * (1 << Shift) + 0.5);
const int YCbCrCrGI = (int)(YCbCrCrGF * (1 << Shift) + 0.5);
const int YCbCrCrBI = (int)(YCbCrCrBF * (1 << Shift) + 0.5);

const int RGBRYI = (int)(RGBRYF * (1 << Shift) + 0.5);              // YCbCr转RGB的系数(整数类型)
const int RGBRCbI = (int)(RGBRCbF * (1 << Shift) + 0.5);
const int RGBRCrI = (int)(RGBRCrF * (1 << Shift) + 0.5);
const int RGBGYI = (int)(RGBGYF * (1 << Shift) + 0.5);
const int RGBGCbI = (int)(RGBGCbF * (1 << Shift) + 0.5);
const int RGBGCrI = (int)(RGBGCrF * (1 << Shift) + 0.5);
const int RGBBYI = (int)(RGBBYF * (1 << Shift) + 0.5);
const int RGBBCbI = (int)(RGBBCbF * (1 << Shift) + 0.5);
const int RGBBCrI = (int)(RGBBCrF * (1 << Shift) + 0.5);

Mat RGB2YCbCr(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			int Blue = src.at<Vec3b>(i, j)[0];
			int Green = src.at<Vec3b>(i, j)[1];
			int Red = src.at<Vec3b>(i, j)[2];
			dst.at<Vec3b>(i, j)[0] = (int)((YCbCrYRI * Red + YCbCrYGI * Green + YCbCrYBI * Blue + HalfShiftValue) >> Shift);
			dst.at<Vec3b>(i, j)[1] = (int)(128 + ((YCbCrCbRI * Red + YCbCrCbGI * Green + YCbCrCbBI * Blue + HalfShiftValue) >> Shift));
			dst.at<Vec3b>(i, j)[2] = (int)(128 + ((YCbCrCrRI * Red + YCbCrCrGI * Green + YCbCrCrBI * Blue + HalfShiftValue) >> Shift));
		}
	}
	return dst;
}

Mat YCbCr2RGB(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			int Y = src.at<Vec3b>(i, j)[0];
			int Cb = src.at<Vec3b>(i, j)[1] - 128;
			int Cr = src.at<Vec3b>(i, j)[2] - 128;
			int Red = Y + ((RGBRCrI * Cr + HalfShiftValue) >> Shift);
			int Green = Y + ((RGBGCbI * Cb + RGBGCrI * Cr + HalfShiftValue) >> Shift);
			int Blue = Y + ((RGBBCbI * Cb + HalfShiftValue) >> Shift);
			if (Red > 255) Red = 255; else if (Red < 0) Red = 0;
			if (Green > 255) Green = 255; else if (Green < 0) Green = 0;    // 编译后应该比三目运算符的效率高
			if (Blue > 255) Blue = 255; else if (Blue < 0) Blue = 0;
			dst.at<Vec3b>(i, j)[0] = Blue;
			dst.at<Vec3b>(i, j)[1] = Green;
			dst.at<Vec3b>(i, j)[2] = Red;
		}
	}
	return dst;
}

六,RGB和YDbDr互转

YDbDr颜色空间和YCbCr颜色空间类似,其和RGB空间之间的相互转换公式里取http://en.wikipedia.org/wiki/YDbDr 所描述的。
在这里插入图片描述代码:

const float YDbDrYRF = 0.299F;              // RGB转YDbDr的系数(浮点类型)
const float YDbDrYGF = 0.587F;
const float YDbDrYBF = 0.114F;
const float YDbDrDbRF = -0.1688F;
const float YDbDrDbGF = -0.3312F;
const float YDbDrDbBF = 0.5F;
const float YDbDrDrRF = -0.5F;
const float YDbDrDrGF = 0.4186F;
const float YDbDrDrBF = 0.0814F;

const float RGBRYF = 1.00000F;            // YDbDr转RGB的系数(浮点类型)
const float RGBRDbF = 0.0002460817072494899F;
const float RGBRDrF = -1.402083073344533F;
const float RGBGYF = 1.00000F;
const float RGBGDbF = -0.344268308442098F;
const float RGBGDrF = 0.714219609001458F;
const float RGBBYF = 1.00000F;
const float RGBBDbF = 1.772034373903893F;
const float RGBBDrF = 0.0002111539810593343F;

const int Shift = 20;
const int HalfShiftValue = 1 << (Shift - 1);

const int YDbDrYRI = (int)(YDbDrYRF * (1 << Shift) + 0.5);         // RGB转YDbDr的系数(整数类型)
const int YDbDrYGI = (int)(YDbDrYGF * (1 << Shift) + 0.5);
const int YDbDrYBI = (int)(YDbDrYBF * (1 << Shift) + 0.5);
const int YDbDrDbRI = (int)(YDbDrDbRF * (1 << Shift) + 0.5);
const int YDbDrDbGI = (int)(YDbDrDbGF * (1 << Shift) + 0.5);
const int YDbDrDbBI = (int)(YDbDrDbBF * (1 << Shift) + 0.5);
const int YDbDrDrRI = (int)(YDbDrDrRF * (1 << Shift) + 0.5);
const int YDbDrDrGI = (int)(YDbDrDrGF * (1 << Shift) + 0.5);
const int YDbDrDrBI = (int)(YDbDrDrBF * (1 << Shift) + 0.5);

const int RGBRYI = (int)(RGBRYF * (1 << Shift) + 0.5);              // YDbDr转RGB的系数(整数类型)
const int RGBRDbI = (int)(RGBRDbF * (1 << Shift) + 0.5);
const int RGBRDrI = (int)(RGBRDrF * (1 << Shift) + 0.5);
const int RGBGYI = (int)(RGBGYF * (1 << Shift) + 0.5);
const int RGBGDbI = (int)(RGBGDbF * (1 << Shift) + 0.5);
const int RGBGDrI = (int)(RGBGDrF * (1 << Shift) + 0.5);
const int RGBBYI = (int)(RGBBYF * (1 << Shift) + 0.5);
const int RGBBDbI = (int)(RGBBDbF * (1 << Shift) + 0.5);
const int RGBBDrI = (int)(RGBBDrF * (1 << Shift) + 0.5);

Mat RGB2YDbDr(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			int Blue = src.at<Vec3b>(i, j)[0];
			int Green = src.at<Vec3b>(i, j)[1];
			int Red = src.at<Vec3b>(i, j)[2];
			dst.at<Vec3b>(i, j)[0] = (uchar)((YDbDrYRI * Red + YDbDrYGI * Green + YDbDrYBI * Blue + HalfShiftValue) >> Shift);
			dst.at<Vec3b>(i, j)[1] = (uchar)(128 + ((YDbDrDbRI * Red + YDbDrDbGI * Green + YDbDrDbBI * Blue + HalfShiftValue) >> Shift));
			dst.at<Vec3b>(i, j)[2] = (uchar)(128 + ((YDbDrDrRI * Red + YDbDrDrGI * Green + YDbDrDrBI * Blue + HalfShiftValue) >> Shift));
		}
	}
	return dst;
}

Mat YDbDr2RGB(Mat src) {
	int row = src.rows;
	int col = src.cols;
	Mat dst(row, col, CV_8UC3);
	for (int i = 0; i < row; i++) {
		for (int j = 0; j < col; j++) {
			int Y = src.at<Vec3b>(i, j)[0];
			int Db = src.at<Vec3b>(i, j)[1] - 128;
			int Dr = src.at<Vec3b>(i, j)[2] - 128;
			int Red = Y + ((RGBRDbI * Db + RGBRDrI * Dr + HalfShiftValue) >> Shift);
			int Green = Y + ((RGBGDbI * Db + RGBGDrI * Dr + HalfShiftValue) >> Shift);
			int Blue = Y + ((RGBBDbI * Db + RGBBDrI * Dr + HalfShiftValue) >> Shift);
			if (Red > 255) Red = 255;
			else if (Red < 0) Red = 0;
			if (Green > 255) Green = 255;
			else if (Green < 0) Green = 0;
			if (Blue > 255) Blue = 0;
			else if (Blue < 0) Blue = 0;
			dst.at<Vec3b>(i, j)[0] = Blue;
			dst.at<Vec3b>(i, j)[1] = Green;
			dst.at<Vec3b>(i, j)[2] = Red;
		}
	}
	return dst;
}

后记

本文盘点了6种常见的颜色空间互转算法,因为这六种算法是我在学习过程和图像处理论文复现中有使用到的,所以如果还有其他我这里没提到的的转换算法,欢迎留言和我交流哦。


欢迎关注我的微信公众号GiantPadaCV,期待和你一起交流机器学习,深度学习,图像算法,优化技术,比赛及日常生活等。
图片.png

数字图像处理(16):RGB与HSV互转 RGB与HSV互转 阅读详情

相关推荐

2024年最全OpenCV4机器学习(三):颜色空间(RGB、HSI,原理+实战+视频+源码

大家如果对 OpenCV-Python 感兴趣,可以前往优质博主的。

2301_82243318的博客 1000

从 RGB 到 HSV 的转换详细介绍

从RGB 到 HSV 的转换详细介绍1.RGB       RGB是从颜色发光的原理来设计定的,通俗点说它的颜色混合方式就好像有红、绿、蓝三盏灯,当它们的光相互叠合的时候,色彩相混,而亮度却等于两者亮度之总和,越混合亮度越高,即加法混合。        红、绿、蓝三个颜色通道每种色各分为256阶亮度,在0时“灯”最弱——是关掉的,而在255时“灯”最亮。当三色灰度数值相同时,产生不同灰度值的灰色调...

hanshanbuleng的博客 8万+

全面列举OpenCV 各模块中常见的算子

OpenCVSharp 和 OpenCV 提供了广泛的图像处理功能和算法,涵盖了从基本的图像操作到复杂的计算机视觉任务的所有内容。从图像的读取、处理、显示,到特征提取、物体检测、机器学习、深度学习等高级应用,都有丰富的实现。这些功能帮助开发者在图像和视频分析、增强现实、自动驾驶、医学图像处理、安防监控等领域中进行高效的开发。每个模块和算子都可以根据具体的应用需求进行组合和优化,从而实现各种强大的功能。希望这个详细的介绍对你理解 OpenCVSharp 和 OpenCV 的模块及功能有所帮助!

weixin_45590420的博客 2099

opencv的并行处理cv::ParallelLoopBody

因此,cv::ParallelLoopBody 的作用就是实现并行处理算法,以提高图像或视频处理的速度。cv::Range 表示要处理的数据范围,这里是 cv::Range(0, image.rows),表示从第 0 行到最后一行,这是对整个图像进行处理。函数接受两个参数:一个 cv::Range 对象和一个 cv::ParallelLoopBody 对象。类的自定义类,它的 operator() 函数将在多个线程上并行执行。函数,从而实现图像的并行处理。类的实例,也就是继承了。

fb_941219的博客 1363

opencv 中的双边滤波用法总结(10)

(一)知识点: (二)API说明: 【双边滤波】结合空间临近度和像素值相似度的一种折中处理 原型:void bilateralFilter( InputArray src,  OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT ); 参数1:输入图像...

久久乐 6034

OpenCV 入门教程:颜色空间转换

在图像处理和计算机视觉领域,颜色空间转换是一项重要的任务。不同的颜色空间具有不同的表示方式,可以用于不同的图像处理和分析任务。 OpenCV 提供了丰富的功能来进行颜色空间的转换和处理。本文将以颜色空间转换为中心,为你介绍使用 OpenCV 进行颜色空间转换的基本步骤和实例。

小蓝枣的博客 2220

数字图像处理——RGB与HSV图像互相转换原理

01—RGB与HSV介绍讲RGB图与HSV图的互相转换之前,我们先分别介绍一下这两种图像。首先是RGB图像RGB图像是一种三通道图像,通常用于表示彩色图,它由相同行、列的红(Red)、绿(...

shandianfengfan的博客 4万+

《OpenCV-Python初学者疑难问题集》专栏目录

本专栏为笔者《OpenCV-Python图形图像处理》的伴生专栏,是笔者对OpenCV-Python图形图像处理学习中遇到的一些问题个人感悟的整合,相关资料基本上都是老猿反复研究的成果,有助于OpenCV-Python初学者比较深入地理解OpenCV。 ...

老猿Python 3965

OpenCV-Python图形图像处理专栏文章目录

OpenCV-Python setMouseCallback回调函数中图像变量img的传递方法解析 OpenCV-Python图像乘法运算cv2.multiply函数详解及像素值溢出归一化处理 OpenCV-Python图像位与运算bitwise_and函数详解 OpenCV-Python图像的减法运算cv2.subtract函数详解以及和矩阵减法的差异对比 OpenCV-Python图像的加法运算cv2.add函数详解 OpenCV-Python图像融合cv2.addWeighted权重加法函数详解 Op

老猿Python 6717

OpenCV开发笔记(六):OpenCV基础数据结构、颜色转换函数和颜色空间

若该文为原创文章,未经允许不得转载 原博主博客地址:https://blog.youkuaiyun.com/qq21497936 本文章博客地址:https://blog.youkuaiyun.com/qq21497936/article/details/100976153 目录 前言 基础数据结构 基础图像矩阵容器:cv::Mat 点:cv::Point 颜色:cv::Scalar 大小:cv::Size...

长沙红胖子 Qt・西安|省级科研团队从业背景,深耕双一流体系高精尖技术配套研发|剥离高精尖机理,沉淀高端特种行业领域 Qt 工程化落地经验|博文仅分享通用技术总结,输出公开普适工程思路。 4万+

Python图像处理:OpenCV HSV和标准表示法的区别以及转换

本文介绍了HSV(也称为HSB)的OpenCV表示与标准表示的差别以及转换关系,澄清了网上其他一些资料有可能导致误解和困惑的问题,有助于大家对HSV颜色空间的概念理解和应用。

老猿Python 1926

数字图像处理专栏目录

注:转载请标明原文出处链接:https://xiongyiming.blog.youkuaiyun.com/article/details/103137690 为了方便查阅复习,将自己原来撰写的博文进行整理,数字图像处理专栏目录如下: 数字图像处理(1): 数字图像处理领域应用——电磁波谱 可见光 数字图像处理(2): 颜色空间/模型—— RGB, CMY/CMYK, HSI, HSV, YUV 数字...

TechArtisan6的博客 7740

Jetson编译安装pycuda

可使用nvcc --version查看是否有信息输出,若没有,且安装cuda的情况下,需要手动将CUDA 的路径添加到环境变量中。编译生成_pvt_struct.cpython-38-aarch64-linux-gnu.so文件。基于jetson orin nano jetpack512模块上安装pycuda。4、获取pycuda源码(版本根据实际情况获取)2、进入conda环境并安装cuda工具包。9、若未检测出来需要清除并强制编译。7、编译成功后安装pycuda。5、配置编译并设置环境变量。

qq_48175141的博客 972

OpenCV 图像处理入门:从读取、显示到颜色空间转换

本教程介绍了使用 OpenCV 进行基础图像处理的关键操作,包括如何读取和显示图像、访问像素值以及在不同颜色空间之间转换。通过实例讲解灰度图和 RGB/BGR 图像的构成及操作方法,帮助读者掌握数字图像的空间坐标与强度值概念,以及在 Python 中使用 OpenCV 和 Matplotlib 进行图像显示和颜色转换的基本技巧。

等风来 1138

Token验证绕过 靶场实战

实验介绍 1.漏洞简介 在Web领域基于Token的身份验证随处可见。在大多数使用Web API的互联网公司中,tokens 是多用户下处理认证的最佳方式。 具有以下几点特性: 1)无状态、可扩展 2)支持移动设备 3)跨程序调用 4)安全 2.原理基于Token的验证原理 基于Token的身份验证是无状态的,我们不将用户信息存在服务器或Session中。 NoSession意味着你的程序可以根据需要去增减机器,而不用去担心用户是...

weixin_54771278的博客 2251

react +ant desiagn pro实现按钮操作权限控制

在ant desiagn pro中提供了一个access用于对权限进行控制,需引入useAccess和Access

Sylvasylva的博客 2212

OpenCV4机器学习(三):颜色空间(RGB、HSI、HSV、Lab、Gray)之间的转换

本专栏主要结合OpenCV4(C++版本),来实现一些基本的图像处理操作、经典的机器学习算法(比如K-Means、KNN、SVM、决策树、贝叶斯分类器等),以及常用的深度学习算法。

不积跬步,无以至千里! 1万+

【U-Net 数据集制作】如何制作自己的图像分割数据集?(标注与格式转换),图像分割数据集制作与转换神器

在前面的课程中,我们像搭积木一样手写了 U-Net 的网络架构。很多同学迫不及待地想把自己的图片喂进网络开始“炼丹”。但是,深度学习界有一句名言:“数据决定了模型的上限,而网络结构只是在逼近这个上限。用标注工具标出来的是 JSON 文件,怎么变成 U-Net 需要的黑白掩码(Mask)图?为什么我的 Mask 肉眼看是黑白的,一放进 PyTorch 训练就报?如何快速按 8:2 的比例把几千张图片和标签划分成训练集和验证集?

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 195

《OpenCv视觉之眼》Python图像处理四 :Opencv图像灰度处理的四种方法及原理

本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的、不同方法的处理,以达到对图像进行去噪、锐化等一系列的操作。同时,希望观看本专栏的小伙伴可以理解到OpenCv进行图像处理的强大哦,如有转载,请注明出处(原文链接和作者署名),感谢各位小伙伴啦! 前文参考: 《OpenCv视觉之眼》Python图像处理一 :Opencv-python的简介及Python环境搭建 《OpenCv视觉之眼》Python图像

qq_42451251的博客 1万+

基于Simulink的自适应控制(Adaptive Control)电励磁同步电动机

目录基于Simulink的自适应控制(Adaptive Control)电励磁同步电动机项目实例1. 项目背景2. 系统架构2.1 电励磁同步电动机模型2.2 三相逆变器模型2.3 自适应控制器(Adaptive Controller)2.4 系统框图3. 模型设计3.1 创建Simulink模型3.2 自适应控制(Adaptive Control)设计3.3 仿真环境搭建4. 参数优化4.1 自动调优5. 实际应用案例5.1 工业驱动系统5.2 风力发电系统6. 结论进一步优化建议需求分析电励磁同步电动机

MHD0815的博客 266

YOLOv11半监督学习深度解析:基于FixMatch改进的先进目标检测框架

本文探讨了半监督学习与YOLOv11结合的创新方法。针对目标检测中半监督学习面临的伪标签质量不稳定、特征一致性难保证等挑战,研究提出改进版FixMatch框架。通过分析YOLOv11架构特性(如多尺度特征提取、双标签分配策略等)与半监督学习的适配性,设计了三项关键技术改进:空间感知阈值策略、多任务解耦处理和记忆增强机制。该方法在少量标注数据下可实现接近全监督的性能,显著降低工业检测等场景的数据标注成本。

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 2085

opencv+图像处理(Image Processing in OpenCV) 4-0改变颜色空间

彩色空间原理+opencv变化色彩空间代码案例

cvxiayixiao的博客 1706

用 ADT 连接 SAP S/4HANA Public Cloud 开发租户的完整落地指南

摘要:在SAP S/4HANA公有云中进行ABAP开发时,必须使用Development Tenant(客户端080)而非Customizing Tenant(客户端100)连接ADT工具。开发用户需具备SAP_BR_DEVELOPER角色且写权限不受限,开发对象应创建在结构包的子包中。连接前需确认正确的ABAP服务URL(通常为Launchpad URL去掉"/ui"),并通过运行控制台程序验证全链路功能。典型开发场景如通过BAdI实现业务校验时,需遵循Clean Core原则在指定包

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 339

《数字图像处理-OpenCV/Python》第3章:彩色图像处理

本章介绍常用的颜色空间,学习彩色图像的转换和处理方法;- 通过调节色彩平衡,理解图像的亮度、饱和度与对比度;介绍伪彩色图像,学习将灰度图像、多模态数据扩展为彩色图像的方法。

youcans的博客 1292

【Dify】知识库智能检索应用

本文介绍了一个基于大语言模型的知识库检索系统,实现从自然语言提问到智能回答的自动化流程。该系统通过知识检索节点定位相关信息,利用重排序模型优化结果,最后由大语言模型生成自然语言答案。核心模型包括InternLM2-7B和BCE重排序模型。工作流程包含输入问题、检索重排序、智能生成和答案输出四个阶段,适用于企业知识管理、客户支持、教育培训等多种场景,能显著提升信息检索效率和准确性。文章还提供了相关开发文档链接,涵盖AIGC工具平台搭建、常见问题解决等内容。

Mr数据杨 1001

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

基于HSV颜色空间用OpenCV-Python给照片换底

本文介绍了基于HSV空间的给照片换底的实现思路及程序示例,HSV由于对颜色的识别更准确,因此得到的图像效果比RGB颜色空间更好。同时HSV的蓝色的H值非常精确,因此不需要象BGR空间一样需要结合照片去确认BGR三通道的范围,因此上述代码对于所有蓝底照片都适用。

老猿Python 3297

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

上一篇: Tensorflow 实战8:Tensoflow实现Google Inception Net
下一篇: ENet论文阅读及Keras实现
just_sort
博客等级 码龄11年 2357粉丝 1667原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值