OpenCV圖像處理專欄一 | 盤點常見顏色空間互轉

  • 2019 年 12 月 9 日
  • 筆記

前言

今天是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.299R + 0.587G + 0.114B。直接計算複雜度較高,考慮優化可以將小數轉為整數,除法變為移位,乘法也變為移位,但是這種方法也會帶來一定的精度損失,我們可以根據實際情況選擇需要保留的精度位數。下面給出不同精度(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.csdn.net/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種常見的顏色空間互轉算法,因為這六種算法是我在學習過程和圖像處理論文復現中有使用到的,所以如果還有其他我這裡沒提到的的轉換算法,歡迎留言和我交流哦