YUV <——> RGB 轉換演算法

ShenYuanLuo發表於2018-07-18

本文首發在我的個人部落格: blog.shenyuanluo.com/,喜歡的朋友歡迎訂閱。

YUV

簡述

YUV: 是一種顏色空間,基於 YUV 的顏色編碼是流媒體的常用編碼方式,這種表達方式起初是為了彩色電視與黑白電視之間的訊號相容;其中

  • Y: 表示明亮度(Luminance 或 Luma),也稱灰度圖。
  • U、V: 表示色度(Chrominance 或 Chroma),作用是描述影像的色彩及飽和度,用於指定畫素的顏色。

Y’CbCr:(也稱為 YUV),是 YUV 的壓縮版本,不同之處在於 Y’CbCr 用於 數字影象 領域,YUV 用於 模擬訊號 領域;MPEGDVD、攝像機中常說的 YUV 其實是 Y'CbCr,二者轉換為 RGBA 的轉換矩陣是不同的。

  • Cr:(色度紅)反應了 RGB 輸入訊號 紅色 部分與 RGB 訊號亮度值之間的差異(即,當前顏色對 紅色 的偏移程度)。
  • Cb:(色度紅)反應了 RGB 輸入訊號 藍色 部分與 RGB 訊號亮度值之間的差異(即,當前顏色對 藍色 的偏移程度)。

YUV <——> RGB 轉換演算法

注意: 如無特殊說明,本文討論的 YUV 均指 Y'CbCr

格式

YUV儲存格式:

  • planar: 先儲存 Y,然後 U,然後 V
  • packed: yuv 交叉儲存。

常見格式

  1. yuv444: packet 取樣(yuv yuv yuv)和 planar 取樣(yyyy uuuu vvvv
  2. yuv422: packet 取樣
    • yuvy: YUYV YUYV
    • uyvy: UYVY UYVY
  3. yuv422p: planar取樣:YYYY UU VV
  4. yuv420: packet取樣: YUV Y YUV Y
  5. yuv420p: planar取樣
    • I420:

      YUV <——> RGB 轉換演算法

    • YV12:

      YUV <——> RGB 轉換演算法

  6. yuv420sp:Y 是planar取樣,UV 是packet取樣
    • NV12:

      YUV <——> RGB 轉換演算法

    • NV21:

      YUV <——> RGB 轉換演算法

RGB

簡介

RGB: 是一種加色模型,將紅(Red)、綠(Green)、藍(Blue)三原色的色光以不同的比例相加,以產生多種多樣的色光;且三原色的紅綠藍不可能用其他單色光合成。

  1. 浮點表示方式: 取值範圍為 0.0 ~ 1.0(如在 OpenGL 中對每個子畫素點的表示就是使用這個表示方式)。
  2. 整數表示: 取值範圍為 0 ~ 255 或者 00 ~ FF(如 RGBA_8888RGB_565)。

格式

索引形式

  1. RGB1: 每個畫素用 1 個 bit 表示 01 兩種值,可表示的顏色範圍為雙色,即最傳統的黑和白;需要調色盤,不過調色盤只包含兩種顏色。
  2. RGB4: 每個畫素用 4 個 bit 表示,4 個 bit 所能夠表示的索引範圍是 0~15,共 16 個。也就是可以表示 16 種顏色。即調色盤中包含 16 中顏色。
  3. RGB8: 每個畫素用 8 個 bit 表示。8 個 bit 所能夠表示的索引範圍是 0~255,共 256 個。也就是可以表示 256 種顏色。即調色盤中包含 256 種顏色。

畫素形式

  1. RGB555:
    • 概述: 每一個畫素用 16 個 bit(2個位元組)來表示,但最高位不用R 用 5 個 bitG 用 5 個 bitB 用 5 個 bit 表示。

    • 記憶體示意圖:

      YUV <——> RGB 轉換演算法

    • 獲取具體畫素值方法:(假設 color 為儲存某一個畫素點的變數)

      • R = color & 0x7C00 // 獲取高位元組 5 個 bit
      • G = color & 0x03E0 // 獲取中間的 5 個 bit
      • B = color & 0x001F // 獲取低位元組 5 個 bit
  2. RGB565:
    • 概述: 每一個畫素用 16 個 bit(2 個位元組)來表示,R 用 5 個 bitG 用 6 個 bitB 用 5 個 bit 表示。

    • 記憶體示意圖:

      YUV <——> RGB 轉換演算法

    • 獲取具體畫素值方法:(假設 color 為儲存某一個畫素點的變數)

      • R = color & 0xF800 // 獲取高位元組 5 個 bit
      • G = color & 0x07E0 // 獲取中間的 6 個 bit
      • B = color & 0x001F // 獲取低位元組 5 個 bit
  3. RGB24:
    • 概述: 每一個畫素用 24 個 bit(3個位元組)來表示,RGB 均用 8 bit 表示。

    • 記憶體示意圖:

      YUV <——> RGB 轉換演算法

    • 獲取具體畫素值方法:(假設 color 為儲存某一個畫素點的變數)

      • R = color & 0x0000FF
      • G = color & 0x00FF00
      • B = color & 0xFF0000
  4. RGB32:
    • 概述: 每一個畫素用 32 個 bit(4個位元組)來表示,RGB 均用 8 bit 表示,最後 1 個位元組保留

    • 記憶體示意圖:

      YUV <——> RGB 轉換演算法

    • 獲取具體畫素值方法:(假設 color 為儲存某一個畫素點的變數)

      • R = color & 0x0000FF00
      • G = color & 0x00FF0000
      • B = color & 0xFF000000

轉換

轉換矩陣

注意: 這裡的轉換矩陣中,當轉換為 RGB 讀取 YUV 時,需要將 U(Cb)V(Cr) 的取值範圍整數表示時,轉換為:[-128, 127];浮點數表示時,轉換為:[-0.5, 0.5]

(這是因為:U(Cb)V(Cr) 取值範圍是 [﹣128, 127],對應的浮點數表示為 [﹣0.5, 0.5];而在儲存時,為了方便儲存,跟 Y 資料一樣,統一用一個(無符號)位元組表示,即取值範圍是 [0, 255],對應的浮點數表示為:[0, 1]。)

特別注意:OpenGL 內建的矩陣(如 mat2mat3mat4 )是 列主序,即需要將下列轉換矩陣轉換成 轉置矩陣

YUV ——> RGB

  1. 常規轉換標準:

    YUV <——> RGB 轉換演算法

  2. BT.601 標準:(SD TV)

    YUV <——> RGB 轉換演算法

  3. BT.709 標準:(HD TV)

    YUV <——> RGB 轉換演算法

RGB ——> YUV

  1. 常規轉換標準:

    YUV <——> RGB 轉換演算法

  2. BT.601 標準:(SD TV)

    YUV <——> RGB 轉換演算法

  3. BT.709 標準:(HD TV)

    YUV <——> RGB 轉換演算法

演算法優化

舉例:YUV ——> RGB 常規轉換矩陣。

常規轉換:(浮點運算)

r = y                  + (1.370705 * v);
g = y - (0.337633 * u) - (0.698001 * v);
b = y + (1.732446 * u);
複製程式碼

優化1:避免浮點運算

從上述演算法,可以看到存在許多的浮點運算,而在演算法優化中,最好能避免浮點運算(比較耗時)來提高效率。

因此,同時對錶達式中所有子項乘以 256 來對結果進行 四捨五入昨晚新的 整數係數,最後再對計算結果再右移 8 位(除以 256);即,(注意:這裡的轉換是有損的,精度會有所降低

256 * r = 256 * y                        + (256 * 1.370705 * v);
256 * g = 256 * y - (256 * 0.337633 * u) - (256 * 0.698001 * v);
256 * b = 256 * y + (256 * 1.732446 * u);
複製程式碼

===》

r = ((256 * y             + (351 * v))>>8);
g = ((256 * y - (86  * u) - (179 * v))>>8);
b = ((256 * y + (444 * u))            >>8);
複製程式碼

優化2:避免乘法運算

從上述演算法,可以看到存在許多的乘法運算,而乘法同樣也很好使,最好能避免乘法運算(使用位移運算代替)來提供效率。

因此,將所有表示式中的子項係數,拆解成整數(該整數必需是 2 的次冪,這樣可以使用位移運算)相加的形式。

例如:

351 = 256 + 64 + 16 + 8 + 4 + 2 + 1 = 2^8 + 2^6 + 2^4 + 2^3 + 2^2 + 2^1 + 2^0
複製程式碼

===》

r = (((y<<8) + (v<<8) + (v<<6) + (v<<4) + (v<<3) + (v<<2) + (v<<1) + v)                   >> 8);
g = (((y<<8) - (u<<6) - (u<<4) - (u<<2) - (u<<1) - (v<<7) - (v<<5) - (v<<4) - (v<<1) - v) >> 8);
b = (((y<<8) + (u<<8) + (u<<7) + (u<<5) + (u<<4) + (u<<3) + (u<<2))                       >> 8);
複製程式碼

優化3:查表

在常規轉換表示式中,變數的取值範圍是已經確定,Y:[0, 255],U:[-128, 127],V:[-128, 127];那麼就可以使用一維陣列儲存結果來提高效率。

因此,將表示式中相關的變數計算結果分別儲存在 4 個一位陣列中,在使用計算時,直接通過陣列查詢即可獲得表示式相乘結果。

例如:對於表示式 256 * 1.370705 * v

int rv = 0;     // 計算 R 值 V 係數

rv = 256 * 1.370705 = 351;
            
for (int i = 0; i < 256; i++)
{
    m_rv[i] = ((i - 128) * rv)>>8;
}
複製程式碼

===》

r = y + m_rv[v];
g = y - m_gu[u] - m_gv[v];
b = y + m_bu[u];
複製程式碼

演算法效能比較

測試說明

  • 系統:macOS 10.13.5
  • 環境:Xcode 9.4
  • 測試檔案:480 x 360,454 幀(檔案大小:117,676,800 Byte)
  • 轉換矩陣:常規標準
  • 流程描述:從原始檔讀取資料,對資料進行轉換(I420 ——> RGB24),但不寫入檔案

測試結果

轉換方式 迴圈次數 平均耗時(μs/次) 平均每幀耗時(μs)
浮點運算 50 2837788.36 6250.64
避免浮點運算 50 2650935.74 5839.07
避免乘法運算 50 3031586.02 6677.50
查表法 50 2674598.74 5891.19

Demo


參考

相關文章