Core Image编程指南翻译六(子类化CIFilter自定义特效)

示例代码

子类化CIFilter:自定义特效

您可以使用一个图像滤镜的输出作为另一个图像滤镜的输入来创建自定义效果,并根据需要将尽可能多的滤镜链接在一起。当您以这种方式创建要多次使用CIFilter的效果时,请考虑子类化以将效果封装为滤镜。

本章介绍了Core Image如何子类化CIFilter创建CIColorInvert滤镜。然后它描述了将各种滤镜链接在一起以实现有趣效果的配方。通过遵循Subclassing CIFilter中的子类化过程来创建CIColorInvert滤镜,您应该能够从本章的方案中创建滤镜,或者冒险创建自己有趣的Core Image提供的内置滤镜组合。

对CIFilter进行子类化以创建CIColorInvert滤镜

子类化CIFilter时,可以通过使用预设值对其进行编码或将它们链接在一起来修改现有滤镜。Core Image使用这种技术实现了一些内置滤镜。

要对滤镜进行子类化,您需要执行以下任务:

  • 声明滤镜输入参数的属性。您必须为每个输入参数名称添加前缀input,例如inputImage。
  • 如有必要,重写setDefaults方法。(在此示例中没有必要,因为输入参数是设置值。)
  • 重写outputImage方法。

Core Image提供的CIColorInvert滤镜是CIColorMatrix滤镜的变体。顾名思义,CIColorInvert为CIColorMatrix提供矢量,可以反转输入图像的颜色。按照清单5-1和清单5-2中所示的简单示例构建自己的滤镜。

清单5-1 CIColorInvert滤镜的接口

@interface CIColorInvert: CIFilter {
    CIImage *inputImage;
}
@property (retain, nonatomic) CIImage *inputImage;
@end

清单5-2 CIColorInvert滤镜的outputImage方法

@implementation CIColorInvert
@synthesize inputImage;
- (CIImage *) outputImage
{
    CIFilter *filter = [CIFilter filterWithName:@"CIColorMatrix"
                            withInputParameters: @{
            kCIInputImageKey: inputImage,
        @"inputRVector": [CIVector vectorWithX:-1 Y:0 Z:0],
        @"inputGVector": [CIVector vectorWithX:0 Y:-1 Z:0],
        @"inputBVector": [CIVector vectorWithX:0 Y:0 Z:-1],
        @"inputBiasVector": [CIVector vectorWithX:1 Y:1 Z:1],
        }];
    return filter.outputImage;
}

色度键控滤镜

从源图像中删除颜色或颜色范围,然后将源图像与背景图像合成。

图5-1 色度键控滤镜处理链


image

创建色度键控滤镜:

  • 创建一个数据立方体贴图,用于映射要删除的颜色值,使其透明(Alpha值为0.0)。
  • 使用CIColorCube滤镜和立方体贴图从源图像中删除色度键颜色。
  • 使用CISourceOverCompositing滤镜将处理后的源图像混合在背景图像上

以下部分显示了如何执行每个步骤。

创建立方体贴图

颜色立方体是3D颜色查找表。Core Image的CIColorCube滤镜将颜色值作为输入,并将查找表应用于该值。CIColorCube的默认查找表是一个单位矩阵 - 意味着它对提供的数据没有任何作用。但是,要求您从图像中删除所有绿色。(如果您愿意,可以删除其他颜色。)

您需要通过将绿色设置为alpha = 0.0来移除图像中的所有绿色,这使其透明。“绿色”包含一系列颜色。最直接的方法是将图像中的颜色值从RGBA转换为HSV值。在HSV中,色调表示为围绕圆柱的中心轴的角度。在该表示中,您可以将颜色可视化为饼图切片,然后只需删除表示色度键控颜色的切片。

要删除绿色,您需要定义包含绿色色调的中央访问周围的最小和最大角度。然后,对于任何绿色的东西,将其alpha值设置为0.0。纯绿色的值相当于120º。最小和最大角度需要以该值为中心。

立方体贴图数据必须预乘alpha,因此创建立方体贴图的最后一步是将RGB值乘以刚刚计算的alpha值,对于绿色色调,该值为0.0,否则为1.0。清单5-3显示了如何创建此滤镜所需的颜色多维数据集。

清单5-3 代码中的颜色立方体

    //分配内存
    unsigned int size = 64;
    float *cubeData = malloc(size*size*size*sizeof(float)*4);
    float rgb[3], hsv[3], *c = cubeData;
    //使用从0到1的简单渐变填充多维数据集
    for (int b = 0; b < size; b++) {
        rgb[2] = b/(double)size;//蓝色值
        for (int g = 0; g < size; g++) {
            rgb[1] = g/(double)size;//绿色值
            for (int r = 0; r < size; r++) {
                rgb[0] = r/(double)size;//红色值
                //将RGB转换为HSV
                //您可以在Internet上找到公开可用的rgbToHSV功能
                RGBtoHSV(rgb[0], rgb[1], rgb[2], hsv, hsv + 1, hsv + 2);
                //使用色调值确定要透明的内容
                //最小和最大色调角取决于
                //要删除的颜色
                float alpha = hsv[0] > self.minHueAngle && hsv[0] < self.maxHueAngle ? 0.0f : 1.0f;
                //计算多维数据集的预乘alpha值
                c[0] = rgb[0]*alpha;
                c[1] = rgb[1]*alpha;
                c[2] = rgb[2]*alpha;
                c[3] = alpha;
                c += 4;//将指针前进到内存中以获取下一个颜色值
            }
        }
    }
    
    //使用多维数据集数据创建内存
    NSData *data = [NSData dataWithBytesNoCopy:cubeData length:size*size*size*sizeof(float)*4 freeWhenDone:YES];
    CIFilter *colorCube = [CIFilter filterWithName:@"CIColorCube"];
    [colorCube setValue:@(size) forKey:@"inputCubeDimension"];
    //设置多维数据集的数据
    [colorCube setValue:data forKey:@"inputCubeData"];
    [colorCube setValue:self.inputImage forKey:kCIInputImageKey];

从源图像中删除绿色

现在您已经拥有了颜色贴图数据,请将前景图像(您希望将绿色从中删除的图像)提供给CIColorCube滤镜并获取输出图像。

[colorCube setValue:myInputImage forKey:kCIInputImageKey];
CIImage * result = [colorCube valueForKey:kCIOutputImageKey];

将处理后的源图像混合在背景图像上

设置CISourceOverCompositing滤镜的输入参数,如下所示:

  • 设置inputImage为从CIColorCube滤镜生成的图像。
  • 设置inputBackgroundImage为显示新背景的图像。此示例使用海滩图像。

现在,前景图像看起来好像在海滩上。

面部虚化滤镜

在图像中增加面部检测

图5-2 面部虚化滤镜处理链


image

创建滤镜:

  • 在图像中找到人脸。
  • 使用面向中心的CIRadialGradient创建基本阴影贴图。
  • 将基本阴影贴图与原始图像混合。

以下部分显示了如何执行每个步骤。

找到面孔

使用CIDetector该类在图像中定位面部。featuresInImage:options:返回的数组中的第一个项是滤镜要操作的面部对象。拥有面部后,根据探测器提供的边界计算面部中心。您需要中心值来创建阴影贴图。清单5-4显示了如何使用CIDetector定位面部。

清单5-4 使用CIDetector查找一个面部

CIDetector *detector = [CIDector detectorOfType:CIDetectorTypeFace
                                        context:nil
                                        options:nil];
NSArray *faceArray = [detector featuresInImage:image options:nil];
CIFeature *face = faceArray[0];
CGFloat xCenter = face.bounds.origin.x + face.bounds.size.width/2.0;
CGFloat yCenter = face.bounds.origin.y + face.bounds.size.height/2.0;
CIVector *center = [CIVector vectorWithX:xCenter Y:yCenter];

创建阴影贴图

使用CIRadialGradient滤镜创建以面为中心的阴影贴图。阴影贴图的中心应该是透明的,以便图像中的面部保持不变。图像的边缘应为不透明的白色。两者之间的区域应具有不同程度的透明度。

要实现此效果,请将输入参数设置为CIRadialGradient,如下所示:

  • 设置inputRadius0为大于图像最长尺寸的值。
  • 设置inputRadius1为大于脸部的值,例如face.bounds.size.height + 50。
  • 设置inputColor0为不透明的白色。
  • 设置inputColor1为透明白色。
  • 将清单5-4中计算的面边界的中心设置为inputCenter。

将渐变与脸部混合

设置CISourceOverCompositing滤镜的输入参数,如下所示:

  • 设置inputImage 为原始图像。
  • 设置inputBackgroundImage为最后一步生成的阴影贴图。

线性聚焦滤镜

选择性地聚焦图像以模拟微型场景。

图5-3 线性聚焦滤镜处理链


image

要创建线性聚焦滤镜:

  • 创建图像的模糊版本。
  • 创建两个线性渐变。
  • 通过合成线性渐变创建蒙版。
  • 合成模糊图像,蒙版和原始图像。

以下部分显示了如何执行每个步骤。

创建图像的模糊版本

设置CIGaussianBlur滤镜的输入参数,如下所示:

  • 设置inputImage为要处理的图像。
  • 设置inputRadius为10.0(这是默认值)。

创建两个线性渐变

从单一颜色(例如绿色或灰色)创建一个从上到下变化的线性渐变。设置CILinearGradient的输入参数如下:

  • 设inputPoint0为(0,0.75 * h)
  • 设inputColor0为(0,1,0,1)
  • 设inputPoint1为(0,0.5 * h)
  • 设inputColor1为(0,1,0,0)

创建一个从下到上变化的绿色线性渐变。设置CILinearGradient的输入参数如下:

  • 设inputPoint0为(0,0.25 * h)
  • 设inputColor0为(0,1,0,1)
  • 设inputPoint1为(0,0.5 * h)
  • 设inputColor1为(0,1,0,0)

从线性渐变创建蒙版

要创建掩码,请按如下方式设置CIAdditionCompositing滤镜的输入参数:

  • 设置inputImage为您创建的第一个线性渐变。
  • 设置inputBackgroundImage为您创建的第二个线性渐变。

结合模糊图像,源图像和渐变

最后一步是使用CIBlendWithMask滤镜,设置输入参数如下:

  • 设置inputImage为图像的模糊版本。
  • 设置inputBackgroundImage为原始的未处理图像。
  • 设置inputMaskImage为蒙版,即组合渐变。

遮罩仅影响图像的外部。遮罩的透明部分将显示原始的未处理图像。遮罩的不透明部分允许显示模糊图像。

面部马赛克滤镜

查找图像中的面部并对其进行像素化,以便无法识别它们。

图5-4 面部马赛克滤镜处理链

image

要创建面部马赛克滤镜:

  • 创建图像的像素化版本。
  • 使用图像中检测到的面构建蒙版。
  • 使用蒙版将像素化图像与原始图像混合。

以下部分显示了如何执行每个步骤。

创建图像的像素化版本

设置CIPixellate滤的输入参数如下:

  • 设置inputImage为包含面的图像。
  • 设置inputScale为max(width, height)/60或者另一个看起来令人愉悦的值,在哪里width并height参考图像的宽度和高度。

从图像中检测到的面部构建蒙版

使用CIDetector该类查找图像中的面。对于每张脸:

  • 使用CIRadialGradient滤镜创建围绕脸部的圆圈。
  • 使用CISourceOverCompositing滤镜将渐变添加到蒙版。

清单5-5 为图像中检测到的面构建掩码

CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace
                                          context:nil
                                          options:nil];
NSArray *faceArray = [detector featuresInImage:image options:nil];
 
// 创建一个绿色圆圈以覆盖返回的rects
 
CIImage *maskImage = nil;
 
for (CIFeature *f in faceArray) {
    CGFloat centerX = f.bounds.origin.x + f.bounds.size.width / 2.0;
    CGFloat centerY = f.bounds.origin.y + f.bounds.size.height / 2.0;
    CGFloat radius = MIN(f.bounds.size.width, f.bounds.size.height) / 1.5);
    CIFilter *radialGradient = [CIFilter filterWithName:@"CIRadialGradient" withInputParameters:@{
        @"inputRadius0": @(radius),
        @"inputRadius1": @(radius + 1.0f),
        @"inputColor0": [CIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0],
        @"inputColor1": [CIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.0],
        kCIInputCenterKey: [CIVector vectorWithX:centerX Y:centerY],
        }];
    CIImage *circleImage = [radialGradient valueForKey:kCIOutputImageKey];
    if (nil == maskImage)
        maskImage = circleImage;
    else
        maskImage = [[CIFilter filterWithName:@"CISourceOverCompositing" withInputParameters:@{
            kCIInputImageKey: circleImage,
            kCIInputBackgroundImageKey: maskImage,
            }] valueForKey:kCIOutputImageKey];
}

混合像素化图像,蒙版和原始图像

将CIBlendWithMask滤镜的输入参数设置为以下内容:

  • 设置inputImage为图像的像素化版本。
  • 设置inputBackgroundImage为原始图像。
  • 设置inputMaskImage为合成的绿色圆圈。

马赛克过渡滤镜

通过像素化每个图像从一个图像转换到另一个图像。

图5-5 马赛克过渡滤镜处理链


image

要创建马赛克过渡滤镜:

  • 使用CIDissolveTransition在源图像和目标图像之间进行转换。
  • Pixellate过渡滤镜的结果。

以下部分显示了如何执行每个步骤。

创建一个溶解过渡滤镜

  • 设置CIDissolveTransition滤镜的输入参数,如下所示:
  • 设置inputImage为要转换的图像。
  • 设置inputTagetImage为要转换的图像。
  • 设置inputTime为类似的值min(max(2*(time - 0.25), 0), 1),这是一个夹在两个值之间的斜坡函数。

Pixellate过渡的结果

设置CIPixellate滤镜以通过将其输入参数设置为以下来改变像素的比例:

  • 设置inputImage为CIDissolveTransition滤镜的输出图像。
  • 设置inputScale为通过提供三角函数的值来随时间变化:90(1 - 2abs(time - 0.5))
  • 使用默认值inputCenter。

旧照片滤镜

降低视频图像的质量,使其看起来像一个旧的,粗糙的模拟电影。

图5-6 旧照片滤镜处理链


image

要创建一个旧照片滤镜:

  • 将CISepiaTone滤镜应用于原始视频图像。
  • 创建随机变化的白色斑点。
  • 创建随机变化的深色划痕。
  • 将斑点和划痕复合到棕褐色图像上。

以下部分显示了如何执行每个步骤。

将棕褐色应用于视频图像

设置CISepiaTone的输入参数如下:

  • 设inputImage为视频图像以应用效果。
  • 设置inputIntensity为1.0。

创建随机变化的白色斑点

使用CIRandomGenerator滤镜,它会产生彩色噪声。它没有任何输入参数。

要处理噪声以便只获得白色斑点,请使用CIColorMatrix滤镜,输入参数设置如下:

  • 设置inputImage为随机生成器生成的输出。
  • 设置inputRVector,inputGVector和inputBVector(0,1,0,0)。
  • 设为inputBiasVector(0,0,0,0)。

使用CISourceOverCompositing滤镜通过设置滤镜的输入参数将斑点与视频图像混合,如下所示:

  • 设置inputImage为CIColorMatrix滤镜生成的白色斑点图像。
  • 设置inputBackgroundImage为CISepiaTone滤镜生成的图像。

创建随机变化的黑暗划痕

再次使用CIRandomGenerator滤镜生成有色噪声。然后使用带有这些输入参数的CIAffineTransform滤镜处理其输出:

  • 设置inputImage为CIRandomGenerator滤镜生成的噪声。
  • 设置inputTransform为将x缩放1.5并将y缩放25.这会使像素变粗且变长,但它们仍将被着色。

使用CIAffineTransform的另一种方法是使用该imageByApplyingTransform:方法转换噪声。

要使像素变暗,请按如下方式设置CIColorMatrix滤镜的输入参数:

  • 设置inputImage为转换后的视频图像。
  • 设为inputRVector(4,0,0,0)。
  • 设置inputGVector,inputBVector和inputAVector(0,0,0,0)。
  • 设为inputBiasVector(0,1,1,1)。

这导致青色划痕。
要使划痕变暗,请将CIMinimumComponent滤镜应用于青色划痕。此滤镜使用r,g,b值的最小值来生成灰度图像。

将斑点和划痕复合到棕褐色视频图像

设置CIMultiplyCompositing滤镜的输入参数,如下所示:

  • 设置inputBackgroundImage为已处理的视频图像(棕褐色调,白色斑点)。
  • 设置inputImage为深色划痕,即CIMinimumComponent滤镜的输出。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容