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 的資訊可以拆分為兩步:
- 拿到 pixel 的 value。首先可以通過 bitmapcreate+drawimage 的形式拿到 rawData 的 pointer。然後用 i 遍歷 height,j 遍歷 width,並通過:
uint *pixel_data = rawData + i * width + j
複製程式碼
即可拿到單畫素的值,value 格式為 RGBA8888
- 然後結合 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:
- OpenGL 的 mat 時 column major,所以從公式中拿出矩陣來乘的時候需要進行一次 transpose
- 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 即可。