在 GPUImage 中實現 ColorConversion

weixin_34162695發表於2018-07-28

0x00 背景

常見的顏色空間有 RGB 和 YCbCr。YCbCr 能夠提供比 RGB 更好的壓縮比,因為只需要保證 Y 分量精度夠高,Cb 和 Cr 進行適當壓縮不影響最後的體感質量,所以在視訊相關領域中被廣泛使用。

在 OpenCV 中,有方便的 cvtColor 函式可以執行該轉換,但在 iOS 中則沒有相應的方法,只能手擼。 幸運的是轉換公式是現成的,可以參考這裡: http://www.equasys.de/colorconversion.html , 裡面有很多套公式,通過對 GPUImage 程式碼的研讀,可以推算出 iOS 中一般採用的是 HDTV 那一套(BT.709)。

0x01 CPU 方式

有了公式就能進行轉換了,最簡單的辦法就是進行 pixel-wise 的操作,但即便這個簡單的需求發現資料也挺少,這裡就簡單提一下。

首先需要將 Image 轉換為 Bitmap,Bitmap 有幾種不同的 Pixel format,最常用的是 kCGImageAlphaPremultipliedLast 代表 RGB 分量已經預乘了 alpha,並且 alpha 在最高位地址。當我們取出一個畫素點時(32bits integer),拿pixel 的資訊可以拆分為兩步:

  1. 拿到 pixel 的 value。首先可以通過 bitmapcreate+drawimage 的形式拿到 rawData 的 pointer。然後用 i 遍歷 height,j 遍歷 width,並通過:
uint *pixel_data =  rawData + i * width + j
複製程式碼

即可拿到單畫素的值,value 格式為 RGBA8888

  1. 然後結合 iOS big endian的特性,通過如下的巨集拿到各個 channel 的分量
#define Mask8(x) ( (x) & 0xFF )
#define R(x) ( Mask8(x) )
#define G(x) ( Mask8(x >> 8 ) )
#define B(x) ( Mask8(x >> 16) )
複製程式碼

能夠進行 pixel-wise operation 理論上來說就可以轉換了,但 CPU 方式在做 pixel-wise operation 往往不如 GPU,所以 CPU 方式在此不繼續展開。

0x02 GPU 方式

iOS 上進行 GPU 程式設計有幾個選擇,比如 OpenGL ES 和 metal。但 GPUImage 良好的架構,當仁不讓的成為第一選擇。

核心的思想就是將 pixel-wise 的轉換放到 shader 中實現,GPUImage 內部包含了一個 YUV-> RGB 的轉換流程,通過研究其實現結合0x01中介紹的公式可以得出轉換的 shader

PS:

  1. OpenGL 的 mat 時 column major,所以從公式中拿出矩陣來乘的時候需要進行一次 transpose
  2. iOS 的顏色空間 RGB 分別是[0,1]而不是[0,255] ,所以公式中的16 128 128 需要分別轉換為 16.0/255.0 0.5 0.5

RGB->YUV:

varying mediump vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
    mediump vec3 yuv;
    mediump vec3 rgb;
    rgb = texture2D(inputImageTexture, textureCoordinate).rgb;
    yuv = mat3(0.256, -.148, .439,
                .504, -.291, -.368,
                0.098, .439,-.071 ) * rgb + vec3(16.0 / 255.0, 0.5, 0.5);
    gl_FragColor = vec4(yuv.r,0,0, 1);
}
複製程式碼

YUV->RGB:

varying mediump vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
void main()
{
    mediump vec3 yuv;
    mediump vec3 rgb;
    yuv = texture2D(inputImageTexture, textureCoordinate).rgb - vec3(16.0/255.0, 0.5, 0.5);

    rgb = mat3(1.164, 1.164, 1.164,
                0, -.392, 2.017,
                1.596, -.813,0 ) * yuv;
    gl_FragColor = vec4(rgb, 1);
}
複製程式碼

最後分別為這兩個 shader 配套建立一個 custom filter 即可。

相關文章