1. 程式人生 > >顏色空間系列2: RGB和CIELAB顏色空間的轉換及優化演算法

顏色空間系列2: RGB和CIELAB顏色空間的轉換及優化演算法

      在幾個常用的顏色空間中,LAB顏色空間是除了RGB外,最常用的一種之一,不同於RGB色彩空間,Lab 顏色被設計來接近人類視覺。它致力於感知均勻性,它的 L 分量密切匹配人類亮度感知。因此可以被用來通過修改 a 和 b 分量的輸色階來做精確的顏色平衡,或使用 L 分量來調整亮度對比。這些變換在 RGB 或 CMYK 中是困難或不可能的,它們建模物理裝置的輸出,而不是人類視覺感知。

     本文研究的重點是RGB和LAB之間的快速轉換過程。

     首先,RGB和LAB之間沒有直接的轉換公式,其必須用通道XYZ顏色空間作為中間層,關於RGB和XYZ顏色空間的轉換及優化,詳見

顏色空間系列1

     XYZ------>LAB轉換公式如下:一般情況下我們認為Yn,Xn,Zn都為1。

     \begin{align}  L^\star &= 116 f(Y/Y_n) - 16\\  a^\star &= 500 \left[f(X/X_n) - f(Y/Y_n)\right]\\  b^\star &= 200 \left[f(Y/Y_n) - f(Z/Z_n)\right]\end{align}

其中

     f(t) = \begin{cases}  t^{1/3} & \text{if } t > (\frac{6}{29})^3 \\  \frac13 \left( \frac{29}{6} \right)^2 t + \frac{4}{29} & \text{otherwise}\end{cases}

      在上述表示式中,X,Y,Z及t變數的取值範圍都是[0,1],對應的L分量的取值範圍為[0,100],A和B分量都為[-127,127],因此,如果把L拉昇至[0,255],把A,B位移至於[0,255],就可以同RGB顏色空間表達為同一個範圍了。即使這樣對映後,一般來說,LAB各分量的結果仍為浮點數,這個和RGB不同,但是在很多情況下,為了速度計效率,我們這需結果的取整部分,得到類似於RGB空間的佈局。因此,對這類結果的優化更有實際意義。

      關於這樣的優化,OpenCv已經做了非常好的工作,各位看客也可以先看看OpenCv的程式碼,本文未直接沿用其優化,但本文的演算法更簡單明瞭,在保證結果無明顯變化的同時,速度和效率都有30%以上的提升。

      第一步,我們來看看f(t)這個函式的優化,f(t)是個分段函式,如果直接在函式體中判斷,會多一些跳轉和比較語句,不利於CPU的流水線工作,因此,我考慮的第一步是是否能用查表法來做。

     在顏色空間系列1文章中,我們知道,轉換後的XYZ值得範圍是[0,255],而這裡的t值範圍為[0,1],把if t>(6/29)^3這個演算法對映到[0,255],則為 if t>2.26 ,因為XYZ都為整數,即此條件和if t>2等價,可見這裡會出現一些漏判點;考慮2.26這個數字的特點,如果我們在把這個結果放大4倍,即XYZ範圍為[0,1020],則判斷條件隨之升級為if t>9.04,取整if t>9,則漏判現象大為減少。這是提的第一點。

     接著上面,這樣的話我們就定義一個查詢表,查詢表大小應該和XYZ的域相同的,即上面的1020(我更喜歡1024),對於表中的元素值,為求速度,當然必須為int 型別,

也就是說,需要把計算出來的小數值放大一定倍數。這裡不多說,見下面的程式碼:

複製程式碼
    for (I = 0; I < 1024; I++)
    {
        if (I > Threshold)
            LabTab[I] = (int)(Math.Pow((float)I / 1020, 1.0F / 3) * (1 << Shift) + 0.5 );
        else
            LabTab[I] = (int)((29 * 29.0 * I / (6 * 6 * 3 * 1020) + 4.0 / 29) * (1 << Shift) + 0.5 );
    }
複製程式碼

     C#語言是強型別語言,一定要注意運算式中各變數的型別,比如上式中的1.0F/3,我常常寫成1/3(這個的運算結果為0),結果往往是總覺得程式寫得沒問題,但執行效果就是不對,找半天BUG也找不到。

     I / 1020的目的還是把值對映到[0,1]範圍的。 表示式最後的+0.5是因為(int)強制型別轉換時向下取整的,+0.5則為四捨五入的效果。顯然,這是我們需要的。

     OK,有了這個查詢表,下面的過程就簡單了,對於A,B分量,就是進行簡單的乘法、移位及加法,而對於L分量,必須有一個放大的過程,而這個過程我們應該直接從其係數入手,如下所示:

   const int ScaleLC = (int)(16 * 2.55 * (1 << Shift) + 0.5);
   const int ScaleLT = (int)(116 * 2.55 + 0.5);

     2.55即為放大倍數,注意116這個數字,由於,其後的 f(x)已經進行了放大,該數字就不能再放大了。

     通過以上分析,一個簡單的而有高效轉換演算法就有了:

複製程式碼
    public static unsafe void ToLAB(byte* From, byte* To, int Length = 1)
    {
        if (Length < 1) return;
        byte* End = From + Length * 3;
        int X, Y, Z, L, A, B;
        byte Red, Green, Blue;
        while (From != End)
        {
            Blue = *From; Green = *(From + 1); Red = *(From + 2);
            X = (Blue * LABXBI + Green * LABXGI + Red * LABXRI + HalfShiftValue) >> (Shift - 2);  //RGB->XYZ放大四倍後的結果
            Y = (Blue * LABYBI + Green * LABYGI + Red * LABYRI + HalfShiftValue) >> (Shift - 2);
            Z = (Blue * LABZBI + Green * LABZGI + Red * LABZRI + HalfShiftValue) >> (Shift - 2);
            X = LabTab[X];          // 進行查表
            Y = LabTab[Y];
            Z = LabTab[Z];
            L = ((ScaleLT * Y - ScaleLC + HalfShiftValue) >>Shift);
            A = ((500 * (X - Y) + HalfShiftValue) >> Shift) + 128;
            B = ((200 * (Y - Z) + HalfShiftValue) >> Shift) + 128;
            *To = (byte)L;          // 不要把直接計算的程式碼放在這裡,會降低速度
            *(To + 1) = (byte)A;    // 無需判斷是否存在溢位,因為測試過整個RGB空間的所有顏色值,無顏色存在溢位
            *(To + 2) = (byte)B;
            From += 3;
            To += 3;
        }
    }
複製程式碼

    再來看看反轉的過程,即LAB-XYZ的演算法,理論公式如下:

     \begin{align}  Y &= Y_n f^{-1}\left(\tfrac{1}{116}\left(L^*+16\right)\right)\\  X &=  X_n f^{-1}\left(\tfrac{1}{116}\left(L^*+16\right) + \tfrac{1}{500}a^*\right)\\  Z &=  Z_n f^{-1}\left(\tfrac{1}{116}\left(L^*+16\right) - \tfrac{1}{200}b^*\right)\\\end{align}

其中:

      f^{-1}(t) = \begin{cases}  t^3 & \text{if } t > \tfrac{6}{29} \\3\left(\tfrac{6}{29}\right)^2\left(t - \tfrac{4}{29}\right) & \text{otherwise}\end{cases}

注意,我這裡說的轉換有個前期條件,即LAB的資料是用類似於RGB空間的佈局表達的,也就是說LAB各元素為byte型別。

      我曾自己的研究過這些演算法,如果完全像上面那樣靠整數乘法及移位來實現,主要的難度是t^3這個表示式的計算結果會超出int型別的表達範圍,而如果用64位的long型別,在目前32位機器依舊佔主流配置的情況下,速度會下降很多。因此,我最後的研究還是以空間換時間的方法來實現。具體分析如下:

      觀察上式分析,Y的值只於L有關,而L由於我們的限定,只能取[0,255]這256個值,因此建立一個256個元素的查詢表即可,而X及Z的值分別於L及A/B有關,需要建立256*256個元素的查詢表即可,大約佔用0.25MB的記憶體。查詢表的建立如下:

複製程式碼
for (I = 0; I < 256; I++)
    {
        T = I * Div116 + Add16;
        if (T > ThresoldF)
            Y = T * T * T;
        else
            Y = MulT * (T - Sub4Div29);
        TabY[I] = (int)(Y * 255 + 0.5);      // 對映到[0,255]
        for (J = 0; J < 256; J++)
        {
            X = T + Div500 * (J - 128);
            if (X > ThresoldF)
                X = X * X * X;
            else
                X = MulT * (X - Sub4Div29);
            TabX[Index] = (int)(X * 255 + 0.5);

            Z = T - Div200 * (J - 128);
            if (Z > ThresoldF)
                Z = Z * Z * Z;
            else
                Z = MulT * (Z - Sub4Div29);
            TabZ[Index] = (int)(Z * 255 + 0.5);
            Index++;
        }
    }
複製程式碼

      最終的LAB-RGB轉換演算法如下:

複製程式碼
 public static unsafe void ToRGB(byte* From, byte* To, int Length = 1)
    {
        if (Length < 1) return;
        byte* End = From + Length * 3;
        int L, A, B, X, Y, Z;
        int Blue, Green, Red;
        while (From != End)
        {
            L = *(From); A = *(From + 1); B = *(From + 2);
            X = TabX[L * 256 + A];      // *256編譯後會自動優化為移位的
            Y = TabY[L];
            Z = TabZ[L * 256 + B];
            Blue = (X * LABBXI + Y * LABBYI + Z * LABBZI + HalfShiftValue) >> Shift;  
            Green = (X * LABGXI + Y * LABGYI + Z * LABGZI + HalfShiftValue) >> Shift;
            Red = (X * LABRXI + Y * LABRYI + Z * LABRZI + 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;
            *(To) = (byte)Blue;
            *(To + 1) = (byte)Green;
            *(To + 2) = (byte)Red;
            From += 3;
            To += 3;
        }
    }
複製程式碼

      通過以上的分析,可以看出,這個轉換的過程程式碼很簡單,清晰,而且效率不菲,對一副4000*3000的數碼照片進行RGB->LAB,然後再LAB->RGB演算法本體的時間只有250ms。

     還有幾個優化的地方就是我的所有的查詢表都不是用的C#的陣列,而是直接分配記憶體,這是因為C#的陣列在很多情況下會有一個判斷是否越界的彙編碼,而用非託管記憶體則不會。

     比如,以下是用非託管記憶體的陣列訪問的反彙編:

  static int* TabX = (int*)Marshal.AllocHGlobal(256 * 256 * 4);    // 這是原始的定義
複製程式碼
                X = TabX[L * 256 + A];      // *256編譯後會自動優化為移位的
00000037  mov         eax,edi 
00000039  shl         eax,8          // 看到這裡的移位沒有
0000003c  add         eax,edx 
0000003e  mov         edx,dword ptr ds:[005A1F0Ch] 
00000044  mov         eax,dword ptr [edx+eax*4] 
00000047  mov         dword ptr [ebp-14h],eax 
複製程式碼

      而用C#的陣列方式生產的彙編如下:

static int[] TabX = new int[256 * 256];     // 這是原始的定義
複製程式碼
                X = TabX[L * 256 + A];      // *256編譯後會自動優化為移位的
0000003c  mov         eax,edi 
0000003e  shl         eax,8 
00000041  add         eax,edx 
00000043  mov         edx,dword ptr ds:[02A27C68h]   
00000049  cmp         eax,dword ptr [edx+4]   // 多出這兩句程式碼
0000004c  jae         00000133 
00000052  mov         eax,dword ptr [edx+eax*4+8] 
00000056  mov         dword ptr [ebp-14h],eax 
複製程式碼

      其實還有很多細節上的優化的東西,比如語句的順序的講究,有的時候就是調換下不同行的語句,程式的執行效率就有很多的不同,這主要是編譯器的優化不同造成的,比如適當的順序會讓編譯器選擇某個常用變數為暫存器變數。 還比如有人喜歡用下面的程式碼

  *To++ = (byte)L;
  *To++ = (byte)A;
  *To++ = (byte)B;

     來代替:

 *To = (byte)L;          
 *(To + 1) = (byte)A;    
 *(To + 2) = (byte)B;
 To += 3;

     雖然程式碼看上去簡潔了,可你執行後就知道速度反而慢了,為什麼,我想我會在適當時候寫一些關於C#優化方面的粗淺文章在對此進行解釋吧。

     最後附上一些處理的效果,還是拿系列1文章中那些崇洋的新貴門來做實驗吧:

     原圖:

      

     轉換後的綜合影象:

     

    L通道:

     

     A通道: 

     

     B通道:

     

     同樣的道理,上述快速演算法如果進行多次轉換,必然也存在精度上的損失。

     LAB空間在以後的膚色檢測文章中還會有提到。

'*********************************************************************

  轉載請保留以下資訊:

  作者: laviewpbt

  時間:2013.2.2   11點於家中

  QQ:33184777

  E-Mail : [email protected]

相關推薦

顏色空間系列2: RGBCIELAB顏色空間轉換優化演算法

      在幾個常用的顏色空間中,LAB顏色空間是除了RGB外,最常用的一種之一,不同於RGB色彩空間,Lab 顏色被設計來接近人類視覺。它致力於感知均勻性,它的 L 分量密切匹配人類亮度感知。因此可以被用來通過修改 a 和 b 分量的輸色階來做精確的顏色平衡,或使用

RGBCIELAB顏色空間轉換偏色檢測

RGB轉為CIELAB 首先RGB是不可以直接轉為CIELAB顏色空間的,RGB需要先轉為CIEXYZ顏色空間,然後再由CIEXYZ顏色空間轉為CIELAB顏色空間。關於這2個顏色空間的互轉,主要參考了http://www.cnblogs.com/Imageshop/archive/

Python實現RGBLab顏色空間互轉

在網上找了一圈,只找到C++版本的,有個python版本的只有RGB轉Lab,只好自己寫了。C++版本傳送門,這裡把原理已經寫的很清楚了,我只是比葫蘆畫瓢的寫個python版本,沒做任何優化。只有一點需要小心,opencv讀取的影象格式是[b,g,r],剩下的就

OpenCV---如何在RGBHSV色彩空間之間轉換(8)

程式碼如下: import cv2 as cv import numpy as np def color(): src = cv.imread("D:/matplotlib/0.jpg") cv.imshow("rgbimage",src) hsv = cv.cvtCol

【C++】名稱空間、標準庫std名稱空間

namespace Li { int a = 10; int Sum(int a,int b) { std::cout<<"Li::Sum: "<<std::endl; return a + b; } } namespace Wang { int Sum(

iOS-AFNetworking 2.0 AFNetworking 3.0 區別具體用法

在AFNetworking 3.0之前,底層是通過封裝NSURLConnection來實現的。 在AFNetworking 3.0之後,也就是在iOS 9.0 之後,NSURLConnection被棄用,蘋果推薦使用NSURLSession來管理網路請求,所

JVM 關於JVM怎麼樣調整堆空間的初始大小最大空間

JVM的堆記憶體初始預設是系統實體記憶體的1/64,而預設最大記憶體是實體記憶體的1/4. 獲取堆記憶體的初始值和最大值的程式碼為: // 獲取堆記憶體的初始值和最大值 // 實體記憶體的1/64 long l = Runtime.getRuntime().t

Oracle高併發系列1:DML引起的常見問題優化思路

引言 Oracle資料庫是設計為一個高度共享的資料庫,這裡所說的“共享”,可以從資料庫共享記憶體、後臺程序、cursor、執行計劃、latch等方面去理解。Oracle如此設計的目的是以最小的系統開銷、最大化地支援更多的併發會話。也是基於這個設計思想,所以Oracle單個例項的垂直擴充套件能力一直

【C++ / Java】char陣列string的相互轉換自動轉換

一般的轉換: #include<cstdio> #include<cstring> #include<string> using namespace std; char str[100]; string s; int main()

QStringstring的相互轉換亂碼處理

String和QString之間的轉化 資料結構課程設計中,用Qt玩圖形化,經常因為QString和string的轉化出現問題,而出現一些莫名其妙的錯誤。 垃圾百度找了半天沒有解決方案,多虧了成都-賤賤O_o噠大神的相助。(適用於QT5) QStrin

Android進階——效能優化之記憶體洩漏記憶體抖動的檢測優化措施總結(七)

上一篇Android進階——效能優化之記憶體管理機制和垃圾回收機制(六)簡述了Java記憶體管理模型、記憶體分配、記憶體回收的機制

顏色空間探究:RGB、HSVHSL

從RGB空間說起 所謂RGB空間就是red,green和blue顏色3個向量張成的空間,正好類似於3維歐氏空間。 如圖所示,3個向量均歸一化了,其中(0,0,0)處為黑色,(1,1,1)處為白色。這種對映關係和光學稜鏡色散和疊加相對應。實際應用常用的在[0, 255]區間編碼。 簡單

RGB、YUVHSV顏色空間模型(轉載)

一、概述 顏色通常用三個獨立的屬性來描述,三個獨立變數綜合作用,自然就構成一個空間座標,這就是顏色空間。但被描述的顏色物件本身是客觀的,不同顏色空間只是從不同的角度去衡量同一個物件。顏色空間按照基本機構可以分為兩大類:基色顏色空間和色、亮分離顏色空間。前者典型的

【Python】matplotlib畫圖設定顏色、標記線型(系列2

摘要 上一節講了如何設定標題、軸標籤、刻度、刻度標籤。 本節講解設定顏色、標記和線型。 1.畫基礎圖 import matplotlib.pyplot as plt from numpy.random import randn plt.plot(randn(30).cumsum(

HSV HLS顏色空間

HA con 數字圖像 cto 最大值 mark href hsl width 顏色空間 顏色空間是特定的顏色組織;它提供了將顏色分類,並以數字圖像表示的方法。 RGB 是紅綠藍顏色空間。你可以將其視為 3D 空間,在這種情況下是立方體,其中任何顏色都可以用 R

顏色空間RGB與HSV(HSL)的轉換

一般的3D程式設計只需要使用RGB顏色空間就好了,但其實美術人員更多的是使用HSV(HSL),因為可以方便的調整飽和度和亮度。 有時候美術需要程式幫助調整飽和度來達到特定風格的渲染效果,這時候就需要轉換顏色空間了。 HSL 和 HSV(也叫做 HSB)是對RGB 色彩空

js顏色 RGB 16進位制轉換

function HSBToRGB (hsb) { var rgb = {};

RGB與HSV之間的轉換公式顏色

bsp 公式 blog log b- size 分享 ont idt RGB & HSV 英文全稱  RGB - Red, Green, Blue  HSV - Hue, Saturation, Value HSV --> RGB 轉換公式 HSV --&g

改變對話框控件的背景文本顏色

文件夾 png desire urn idc line 行程 msg odt WM_CTLCOLOR,響應函數:CWnd::OnCtlColor   afx_msg HBRUSH OnCtlColor(     CDC* pDC, //當前要繪制的控件的設備上下文的指針

用樹莓派實現RGB LED的顏色控制——C語言版本號

個數 hang clu 代碼 stdio.h 標準 tro color sage 用樹莓派實現RGB LED的顏色控制 RGB色彩模式是工業界的一種顏色標準。是通過對紅(R)、綠(G)、藍(B)三個顏色通道的變化