GPU 實現 RGB -- YUV 轉換
前言
RGB --> YUV 轉換的公式是現成的,直接在 CPU 端轉換的話,只需要遍歷每個像素,得到新的 YUV 值,根據其內存分布規律,合理安排分布即可。然而在 CPU 端進行轉換,存在的問題運行效率太低,無法滿足高效轉換的需求。我們將目光投向擁有流水線體系的支持高速浮點數計算的硬件——GPU.
轉換公式如下:
GPU 上面的實現
考慮在 GPU 上執行 RGB --> YUV 轉換。GPU 的流水線操作:
vertices
----> Pipeline ----> Out color
texture
所以將 RGB 圖像作為紋理輸入,流水線輸出我們需要的 YUV 數據。前面一部分很好理解,圖像作為唯一的紋理輸入,沒有別的選項。后面一部分的話,需要在輸出的時候輸出我們需要的 YUV 數據即可,在 fragment shader 中的輸出按常理就是每一個 fragment 的顏色,為實現讀取像素是 YUV 的目標,要調整輸出的數據。
考慮 YUV 格式內存分布,以 NV12 為例,一張圖片占用內存大小為:width x height * 3 / 2
(我們認為圖像的寬為 width 高為 height). 如果是 RGBA 的格式存儲的話,占用的內存空間大小是:width x height x 4
(因為 RGBA 一共4個通道)。如果我們把 OpenGL renderbuffer 大小設置成等於圖像的大小,那輸出的大小就是 RGBA 那一種的大小,和 YUV 格式的是對不上的。考慮 YUV 的分布特點,設計輸出的寬高為 (width / 4, height * 3 / 2)
. 示意圖如下:
Memory of a frame (yuv format)
width / 4
|-------------|
| |
| | h
| chrominance |
| |
|-------------|
| |
| luminance | h / 2
|-------------|
因為每一個 out color 含有四個分量 RGBA 所以將寬度設為 width / 4, 那么正好每一行的像素就是原來 width 的數量。在 fragment shader 內部計算的時候,需要考慮當前處理的單個 fragment 是屬於 chrominance OR luminance, 可以用紋理坐標的 t 值的大小來判斷。
Chrominance
所謂的 RGBA 四個分量實際上代表四個不同的像素的 chrominance 值,也就是說需要做一定的 offset, 來獲取到當前像素附近的像素的值,我先假定 offset 為 1.0f / width. 故四個分量如下:
- (s, t)
- (s + off, t)
- (s + off x 2.0f, t)
- (s + off x 3.0f, t)
根據四個像素的 RGBA 值計算出四個 Y 通道的數據作為這個 fragment 的輸出顏色。
Luminance
仍然是一個像素四個分量,但是現在代表的是兩對 UV 分量。因為根據一個 RGBA 就可以算出 YUV 值,所以此處只需要做一個偏移。
- (s, t)
- (s + off x 2, t)
這里 offset 的設置可以乘 1 或 2 或 3,我覺得都可以,我只是取中道選擇了 2. 將上面兩個像素的 UV 分量作為這個 fragment 的輸出顏色。
readback pixel
最終用 glReadpixels()
函數,將我們輸出的顏色讀回來,就完成了。
補充
實際操作中遇到的一個問題是,如果設置了 GL_BLEND
, 最終輸出的顏色會是混合以后的顏色,記得一定要確認關閉了 blending.
Written with StackEdit.