在 GPUImage 中实现 ColorConversion

0x00 背景

常见的颜色空间有 RGB 和 YCbCr。YCbCr 能够提供比 RGB 更好的压缩比,因为只需要保证 Y 分量精度够高,Cb 和 Cr 进行适当压缩不影响最后的体感质量,所以在视频相关领域中被广泛使用。

在 OpenCV 中,有方便的 cvtColor 函数可以执行该转换,但在 iOS 中则没有相应的方法,只能手撸。 幸运的是转换公式是现成的,可以参考这里: http://www.equasys.de/colorconversion.html , 里面有很多套公式,通过对 GPUImage 代码的研读,可以推算出 iOS 中一般采用的是 HDTV 那一套(BT.709)。

在 GPUImage 中实现 ColorConversion_第1张图片
image.png

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 即可。

你可能感兴趣的:(在 GPUImage 中实现 ColorConversion)