iOS 针对 LUT 滤镜的实现对比三

在 app 内利用各种图形算法可以对图片进行一些变换,这样的效果也称为“滤镜”,滤镜效果大致可以分为以下几类:

  • 独立像素点变换,包括亮度、对比、饱和度、色调、灰色化、分离RGB通道等
  • 像素卷积变换,包括边缘检测、浮雕化、模糊、锐化
  • 仿射矩阵变换。包括缩放、旋转、倾斜、扭曲、液化等
  • 多图像合成

其中最简单的就是进行独立像素点变换,利用 LUT 技术还可以提供给设计师灵活的方式来自定义各种滤镜效果。

1.LUT 技术

1.1 LUT 技术简介

LUT 是 LookUpTable 的简称,也称作颜色查找表技术,它可以分为一维 LUT(1DLUT) 和 三维 LUT(3DLUT)。简单来说,LUT 就是一个 RGB 组合到 RGB 组合的映射,对于一维 LUT,假设映射关系为 LUT1,则

LUT(R1) = R2
LUT(G1) = G2
LUT(B1) = B2

其中 R1、G1、B1 为原像素值,R2、G2、B2 为映射像素值,可以看出 1DLUT 的映射颜色值的每一个分量仅与其原始像素值的分量有关,用图像表示如下

iOS 针对 LUT 滤镜的实现对比三_第1张图片

对于 3DLUT,假设其映射关系为 LUT3,则

LUT(R1, G1, B1) = (R2, G2, B2)

3DLUT 相比于 1DLUT 能够实现全立体的色彩空间控制,非常适合用于精确的颜色控制工作,它的示意图如下

iOS 针对 LUT 滤镜的实现对比三_第2张图片

可以简单做一个计算,如果 RGB 三个分量分别可以取 256 种值的话,那么 3DLUT 技术就可以包含 256X256X256 种情况,大约占 48MB 空间,这样一个 3DLUT 映射关系的数据量有些庞大,通常会采取采样方式来降低数据量,例如可以对每一个分量按照每 4 个变化值为间距,进行 64 次采样,获得一个 64X64X64 大小的映射关系表,对于不在表内的颜色值进行内插法获得其相似结果。

那么获得了 LUT 映射表以后,如何对任意一张图片进行滤镜变换呢。我们可以遍历图片的像素点,对于每一个像素点,获得其 RGB 组合,在 LUT 表格中查找此 RGB 组合及其对应的 RGB 映射值,然后用 RGB 映射值替换原图的像素点,就可以完成滤镜变换了。

1.2 3DLUT 数据存储方式

3DLUT 是一个三维颜色空间体,通过下面的方式可以将其数据压入一张二维图片中。这里以一张 64X64X64 数据量的 LUT 图为例,它的大小是 512X512

iOS 针对 LUT 滤镜的实现对比三_第3张图片

它在横竖方向上分成了 8X8 一共 64 个小方格,每一个小方格内的 B 分量为一个定值,总共就表示了 B 分量的 64 种可能值。同时对于每一个小方格,横竖方向又各自分为 64 个小格,横向小格的 R 分量依次增加,纵向小格的 G 分量依次增加,通过放大图片可以看到如下细节

iOS 针对 LUT 滤镜的实现对比三_第4张图片

 

这样就将所有数据都存储到一张 LUT 图中了,从图中也可以看出色值随着 RGB 分量变化而变化的情况。

上面所展示的 LUT 图是一张特殊的 LUT 图,因为它的映射关系最简单,原始 RGB 颜色是什么,映射 RGB 颜色就是什么,这样的 LUT 图我们可以将其作为 LUT 参照图,设计师将想实现的滤镜效果分别作用于 LUT 参照图上,可以生成 LUT 滤镜图,其可能情况如下图所示

iOS 针对 LUT 滤镜的实现对比三_第5张图片

通过对比 LUT 参照图和 LUT 滤镜图,就能获知任何原始 RGB 色值的映射颜色值是多少了。

2. LUT 滤镜变换过程实现

iOS 中与图像处理有关的框架大致有以下几个:CoreImage,Metal,OpenGL-ES,第三方框架 GPUImage 等,它们都可以实现 LUT 映射。下面分点阐述。

2.1 CoreImage

CoreImage 是 iOS5 新加入到 iOS 平台的一个图像处理框架,提供了强大高效的图像处理功能, 用来对基于像素的图像进行操作与分析, 内置了很多强大的滤镜(Filter) (目前数量超过了 180 种)。CoreImage 实现 LUT 有两种方式:

  • CIColorCube 过滤器
  • CIKernel
2.1.1 CIColorCube 过滤器

CIColorCube 接受一个 LUT 映射颜色矩阵作为输入参数,对于输入图片进行色值映射,具体实现如下

  • 获取 LUT 图的 bitmap


+ (unsigned char *)createRGBABitmapFromImage:(CGImageRef)image
{
    CGContextRef context = NULL;
    CGColorSpaceRef colorSpace;
    unsigned char *bitmap;
    NSInteger bitmapSize;
    NSInteger bytesPerRow;
    
    size_t width = CGImageGetWidth(image);
    size_t height = CGImageGetHeight(image);
    
    bytesPerRow = width * 4;
    bitmapSize = bytesPerRow * height;
    
    bitmap = malloc(bitmapSize);
    if (bitmap == NULL) {
        return NULL;
    }
    
    colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL) {
        free(bitmap);
        return NULL;
    }
    
    context = CGBitmapContextCreate (bitmap,
                                     width,
                                     height,
                                     8,
                                     bytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    if (context == NULL) {
        free(bitmap);
    }
    
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
    CGContextRelease(context);
    return bitmap;
}

  • 生成 CIColorCube 所需的 inputCubeData

+ (CIFilter *)filterWithLUTImage:(UIImage *)image dimension:(NSInteger)n
{
    NSInteger width = CGImageGetWidth(image.CGImage);
    NSInteger height = CGImageGetHeight(image.CGImage);
    NSInteger row = height/n;
    NSInteger column = width/n;
    
    if ((width % n != 0) || (height % n != 0) || (row * column != n)) {return nil;}
    
    unsigned char *bitmap = [self createRGBABitmapFromImage:image.CGImage];
    
    if (!bitmap) {return nil;}
    
    NSInteger z = 0;
    NSUInteger size = n * n * n * sizeof(float) * 4; // 所有像素点的 rgba 值个数 64 * 64 * 64 * 4
    float *data = malloc(size); // 存储空间
    NSInteger bitmapOffest = 0;
    
    for (NSInteger rowIndex = 0; rowIndex < row; rowIndex++) {
        for (NSInteger y = 0; y < n; y++) {
            NSInteger originalZ = z;
            for (NSInteger columnIndex = 0; columnIndex < column; columnIndex++) {
                for (NSInteger x = 0; x < n; x++) {
                    double r = (unsigned int)bitmap[bitmapOffest];
                    double g = (unsigned int)bitmap[bitmapOffest + 1];
                    double b = (unsigned int)bitmap[bitmapOffest + 2];
                    double a = (unsigned int)bitmap[bitmapOffest + 3];
                    NSInteger dataOffset = (z * n * n + y * n + x) * 4; // 在大存储空间中的偏移,z 从 0 开始,z 偏移一个,总共偏移 64 * 64 个点,y 偏移一个,总共偏移 64 个点,加上 x 个点,乘以 rgba 的 4 个点
                    // 存储值
                    data[dataOffset] = r / 255.0;
                    data[dataOffset + 1] = g / 255.0;
                    data[dataOffset + 2] = b / 255.0;
                    data[dataOffset + 3] = a / 255.0;
                    
                    bitmapOffest += 4; // 偏移 4 个点
                }
                z++;
            }
            z = originalZ; // 每一行遍历完,z 恢复到行头所属块的 index
        }
        z += column;
    }
    
    free(bitmap); // 释放位图
    
    CIFilter *filter = [CIFilter filterWithName:@"CIColorCube"];
    [filter setValue:[NSData dataWithBytesNoCopy:data length:size freeWhenDone:YES] forKey:@"inputCubeData"];
    [filter setValue:[NSNumber numberWithInteger:n] forKey:@"inputCubeDimension"];
    
    return filter;
}

这里读取的时候就是按照 3DLUT 存储方式来读取到一个存储空间里的。

  • 封装并使用

将上述过程封装为一个 Category,传入原图,得到处理后的图片

 CIImage *image = [[CIImage alloc] initWithImage:self.mediaAsset.assetImage];
    CIFilter *filter = [CIFilter filterWithLUTImage:[UIImage imageNamed:@"lookup_yth002"] dimension:64];
    [filter setValue:image forKey:@"inputImage"];
    CIImage *outputImage = [filter outputImage];
    CIContext *context = [CIContext contextWithOptions:nil];
    CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]];
    UIImage *result = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);

 

转载于:https://my.oschina.net/HeroOneHY/blog/3096939

你可能感兴趣的:(人工智能)