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種常見的顏色空間互轉算法,因為這六種算法是我在學習過程和圖像處理論文復現中有使用到的,所以如果還有其他我這裡沒提到的的轉換算法,歡迎留言和我交流哦