Core Image(翻译by王云鹏)
一 处理图像
CIFilter 是一个可变对象,代表一种效果。一个滤镜对象至少要有一个输入参数,并产生一个输出图片。
CIImage 是一个不变对象,代表一个图片。你可以通过图像数据,或者通过文件,或者另一个CIFilter对象的输出得到一个CIImage。
CIContext 是一个对象,通过它Core Image可以绘制一个CIFilter产生的结果。一个Core Image Context可以基于CPU或GPU。
1 概览
源码:
CIContext *context = [CIContext contextWithOptions:nil]; // 1
CIImage *image = [CIImage imageWithContentsOfURL:myURL]; // 2
CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone"]; // 3
[filter setValue:image forKey:kCIInputImgeKey];
[filter setValue:[NSNumber numberWithFloat:0.8f] forKey:@"InputIntensity"];
CIImage *result = [filter valueForKey:kCIOutputImageKey]; // 4
CGImageRef cgImage = [context createCGImage:result fromRect:[result extent]; // 5
说明:
1. 创建一个CIContext对象。这个方法你可以在iOS上使用。
2. 创建一个CIImage对象. 你可以从多种来源创建一个CIImage对象,不日URL。
3. 创建滤镜,并设置其输入参数。
4. 获得输出图像,输出图像是一个描述如何生成图像的配方(这里的配方是描述如何制作一道菜的配方,不是点菜的菜谱)。图像还没有被渲染出来。
5. 渲染CIImage,得到CGImageRef,CGImageRef是可以直接展示或者保存到文件中的。
2 内置滤镜(说明:原文是filter,这里采用滤镜更直观,因此不翻译为过滤器)
Core Image提供了大量内置的滤镜,供你的应用做图像处理。不同iOS版本提供的滤镜是不同的,所以Core Image 提供了方法,让你可以查询系统提供了哪些滤镜。
一个滤镜类别定义了各种效果的类型,或者其可以作用的目标:
images, video, nonsquare pixels, 等等.
一个滤镜可以属于多个类别。滤镜有一个显示名称(给用户看的)和一个滤镜名称(在代码中要使用的)。
滤镜属性是以键-值对的形式保存的。键是一个常量,标识属性及其对应的值。
Core Image使用键-值编码,你可以通过KVC获得属性的值。
3 创建一个Core Image Context
要渲染图像,你需要创建Core Image Context,并使用这个上下文绘制输出图像。
一个Core Image context标识一个绘制的目标。目标决定了使用CPU还是GPU去渲染。
创建的函数:
contextWithOptions: CPU或GPU都支持。
以下仅支持GPU:
contextWithCGLContext: pixelFormat:options: GPU OS X
contextWithCGLContext: pixelFormat:
colorSpace:options:
[1] 在iOS上创建一个Core Image Context,When You Don’t Need Real-Time
Performance
源码:
CIContext *context = [CIContext contextWithOptions:nil];
这个方法可以使用CPU或者GPU进行渲染。要定义使用哪个,需要设置一个选项字典,增加键kCIContextUserSoftwareRenderer,并设置相应的布尔值。CPU渲染要比GPU要慢。但是对于GPU渲染,在渲染结果被拷贝回CPU内存并转化为UIImage之前,不会被显示出来。
[2] 在iOS上创建一个Core Image Context,When You Need Real-Time Performance
如果你的应用要支持实时的图像处理,你需要从一个EAGL context创建一个CIContext对象,不能使用contextWithOptions: 方法。优点是渲染的图像保存在GPU上,并不需要拷贝回CPU内存。你首先需要创建一个EAGL context:
myEAGLContext = [EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
之后使用方法contextWithEAGLContext: 创建一个CIContext对象。
你应该将working color space设置为null,来关闭颜色管理功能。颜色管理会降低性能。在需要颜色保真的情况下,你才需要颜色管理。但是对于一个real-time app,颜色保真通常并不是必要的。
代码:
NSMutableDictionary *options = [NSMutableDictionary alloc] init];
[options setObject: [NSNull null] forKey: kCIContextWorkingColorSpace];
myContext = [CIContext contextWithEAGLContext:myEAGLContext options:options];
4 创建一个CIImage对象
Core Image filters 处理Core Image images (CIImage objects).
记住,一个CIImage对象实际上是一个配方;在被调用,在目标上渲染结果之前,Core Image实际上不产生一个像素。创建的方法:
5 创建一个CIFilter并设置属性值
filterWithName: 方法创建了一个过滤器,通过滤镜名称参数来设定滤镜的类型。
名称参数是一个字符串,其值必须与内置滤镜严格匹配。
代码:
hueAdjust = [CIFilter filterWithName:@"CIHueAdjust"];
[hueAdjust setValue: myCIImage forKey: @"inputImage"];
[hueAdjust setValue: [NSNumber numberWithFloat: 2.094]
forKey: @"inputAngle"];
另一种方式:
hueAdjust = [CIFilter filterWithName:@"CIHueAdjust"] keysAndValues:
@"inputImage",myCIImage,
@"inputAngle, [NSNumber numberWithFloat: 2.094],
nil]; // 你可以增加更多参数,但是必须以nil结尾。
6 获取输出图像
你通过outputImage键,获得输出图像。
CIImage *result = [hueAdjust valueForKey: @"outputImage"];
在你调用某个方法实际执行渲染之前,Core Image不会执行任何的图像处理操作。
当你请求去输出图像时,Core Image组装并保存要生成一个输出图像的运算步骤(保存到一个CIImage对象中)。只有在你显示调用一个图像绘制方法时,实际的图像才会被渲染。
将处理延迟到渲染的时候,使得Core Image 快速而高效。在渲染的时候,Core Image会判断是否有超过一个滤镜需要作用到一个图像上。如果是这样,它自动把多个配方连接成一个操作(好像就是做一系列的矩阵运算,运算结果仍是一个矩阵,所以可以合成一步对结果矩阵的操作,说明by王云鹏),即每个像素只会被处理一次,而不是多次。
下图列出了一个multiple-operations workflow ,Core Image 可以更高效。最终图像是原始图像的一个缩小版本。
对于一个大图像,在大小调整之前,应用颜色调整,比先做大小调整,之后再应用颜色调整,需要更多的处理资源。
通过将滤镜处理操作延迟到渲染的时候,Core Image可以按照最高效的处理顺序来执行操作。
7 渲染输出图像
渲染输出图像会触发CPU或者GPU(依赖于你设置的context)的密集的处理器操作。下面的函数可以用来渲染:
代码:
[myContext drawImage:result inRect:destinationRect fromRect:contextRect];
效果对比:
8 保证线程安全
CIContext 和CIImage对象是不可变的, 在线程之间共享这些对象是安全的.
多个线程可以使用同一个GPU或者CPU CIContext对象来渲染CIImage对象。
但是对于可变的CIFilter确不行。一个CIFilter对象不能在多个线程间共享。如果你的应用是多线程的,每个线程都必须创建自己的CIFilter对象。否则,你的应用将产生不可预期的结果。
9 滤镜链
你可以通过滤镜链创造出惊人的效果,即将一个滤镜的输出作为另一个滤镜的输入。
让我们看看在一个图像上如何应用两个滤镜—幽暗(?gloom)(CIGloom) 和爆沸畸变 (?bump distortion)(CIBumpDistortion).
gloom滤镜:通过淡化亮度来使一张图片变暗。
gloom = [CIFilter filterWithName:@"CIGloom"]; // 1
[gloom setValue: result forKey: @"inputImage"];
[gloom setValue: [NSNumber numberWithFloat: 25]
forKey: @"inputRadius"]; // 2
[gloom setValue: [NSNumber numberWithFloat: 0.75]
forKey: @"inputIntensity"]; // 3
result = [gloom valueForKey: @"outputImage"]; // 4
1. 创建一个CIGloom类型的滤镜。
2. 设置输入半径为25。半径定义了效果的外延,可以设置0到100之间的值,缺省值是10。(你可以通过滤镜的属性字典获得滤镜的最小、最大、缺省值)。
3. 将输入强度设置为0.75。输入强度是一个标量值,它定义了原图与滤镜输出之间的一个线性的混合。最小值是0.0,最大值是1.0,缺省值是1.0。
4. 请求输出图像,但并不真正绘制。
处理结果:
bump distortion 滤镜 (CIBumpDistortion)在一个图像上的某个点创建一个突起。
源码:
bumpDistortion = [CIFilter filterWithName:@"CIBumpDistortion"]; // 1
[bumpDistortion setValue: result forKey: @"inputImage"];
[bumpDistortion setValue: [CIVector vectorWithX:200 Y:150 ]
forKey: @"inputCenter"]; // 3
[bumpDistortion setValue: [NSNumber numberWithFloat: 100]
forKey: @"inputRadius"]; // 4
[bumpDistortion setValue: [NSNumber numberWithFloat: 3.0]
forKey: @"inputScale"]; // 5
result = [bumpDistortion valueForKey: @"outputImage"];
说明:
1. 通过滤镜名称创建滤镜
2. OSX特有的处理,这里删掉了。
3. 将效果的中心点设置在图像的中心位置。
4. 设置扭曲的半径为100。
5. 将输入标量设置为3。输入标量定义了效果的方向和数量。
(原文:The input scale specifies the direction and the amount of the effect. )
缺省值是-0.5。范围是-10.0到10.0。0是没有效果。负值会创建一个向外扭曲的效果。正值向内扭曲。
效果:
10 使用过渡(transition)效果
过渡通常用于图像间的滑动,或者在视频中从一个场景切换到另个场景。
这些效果需要经过一段时间进行渲染,你需要为此设置一个定时器。本断的目的是展示如何设置一个定时器。copy machine transition(CICopyMachine)创建了一个发光条,类似于复印机或者图片扫描仪。发光条从左向右扫过原始图像,并揭示目标图像。
过渡滤镜需要完成一下任务:
1. 创建用于过渡的Core Image images (CIImage objects) 。
2. 设置一个定时器。
3. 创建一个CIContext 对象。
4. 创建一个CIFilter对象来处理图像。
6. 设置滤镜参数。
7. 设置要处理的源与目标图像。
8. 计算时间。
9. 应用滤镜。
10. 绘制结果。
11. 重复8-10步,直到完成过渡。
效果:
代码比较长,直接看原文吧。
11 在视频上使用滤镜
Core Image 和 Core Video一起工作,获得丰富的效果。比如,在水下拍摄视频时,
你可以使用一个颜色校正滤镜来修正(水吸收红光要比蓝光更快)。有很多种方式让你可以使用这些技术。
例子是OS X的,不贴出来了。
二 人脸探测
Core Image可以对图像进行分析,并找到人脸。它进行的是人脸探测,但不是识别(识别的意思是能认出你是谁,探测的意思是是能找到人脸在图像中的位置)。Core Image检测出人脸后,可以提供面部特性,比如眼睛和嘴的位置。它还可以跟踪视频中人脸的位置。
知道脸在图像中的位置让你可以执行一些其他的操作,比如:对头像做剪裁或者调整图像质量的操作 (tone balance, red-eye correction and so on)。你还可以在脸上做其他有趣的操作:
● 仅在人脸上应用像素滤镜。
● 在人脸边上加小插图。
1 探测人脸
CIContext *context = [CIContext contextWithOptions:nil]; // 1
NSDictionary *opts = [NSDictionary dictionaryWithObject:CIDetectorAccuracyHigh
forKey:CIDetectorAccuracy]; // 2
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace
context:context
options:opts]; // 3
opts = [NSDictionary dictionaryWithObject:
[myImage properties] valueForKey:kCGImagePropertyOrientation;
forKey:CIDetectorImageOrientation];// 4
NSArray *features = [detector featuresInImage:myImage
options:opts]; // 5
1. 创建一个CIContext对象。
2. 创建一个选项字典来定义detector的精度。你可以定义低精度或高精度。
低精度 (CIDetectorAccuracyLow) 速度快; 高精度,更彻底,但是比较慢。
3. 创建一个用于人脸检测的detector。你唯一能创建的就是用于人脸检测的detector。
4. 设定一个用于检测人脸的选项字典。有一点是非常重要的,必须让Core Image 知道图像的方向。这样detector才能知道怎样查找人脸。通常你可以从图像本身获得方向的信息,并传入字典。
5. 使用detector去查找人脸的面部特征。你提供的图像必须是一个CIImage对象。Core Image返回一个CIFeature对象的数组,你可能想找到他们的特性,比如眼睛,嘴的位置。
2 获得脸和脸部特征界限
脸部特征包括:
- 左右眼睛的位置
- 嘴巴的位置
- 跟踪ID和跟踪帧数(Core Image 以此来跟踪视频断中的脸)(available in iOS v6.0 and later and in OS X v10.8 and later)
在你通过CIDetector对象获得一个脸部特征数据的array后,你可以循环这个array来检测每张脸的边界及其特性。
for (CIFaceFeature *f in features)
{
NSLog(NSStringFromRect(f.bounds));
if (f.hasLeftEyePosition)
printf("Left eye %g %g\n", f.leftEyePosition.x, f.leftEyePosition.y);
if (f.hasRightEyePosition)
printf("Right eye %g %g\n", f.rightEyePosition.x, f.rightEyePosition.y);
if (f.hasmouthPosition)
printf("Mouth %g %g\n", f.mouthPosition.x, f.mouthPosition.y);
}
三 自动增强图像
Core Image的自动增强特性分析图像的直方图,脸部区域内容,和元数据属性。之后返回一个CIFilter对象的array,这些对象的参数已经被设置好,用来增强被分析的图像。
(Auto enhancement is available in iOS v5.0 and later and in OS X v10.8 and later)
1 自动增强滤镜
下面这些滤镜可以修正照片中的大部分问题:
2 使用自动增强滤镜
自动增强API仅有2个方法: autoAdjustmentFilters和
autoAdjustmentFiltersWithOptions:. 在大多数情况下,你会使用带有选项字典参数的方法。
你可以做以下设置:
- 对于CIRedEyeCorrection和CIFaceBalance 滤镜,图像的方向是很重要的,所以Core Image 可以精确的定位到脸。
- 是否仅应用红眼校正。(把kCIImageAutoAdjustEnhance设置为false)
- 是否使用红眼以外的全部其他滤镜。(把kCIImageAutoAdjustRedEye设置为false)
autoAdjustmentFiltersWithOptions: 返回一个备选滤镜的array,你可以把他们链接起来并应用到被分析的图像。下面的代码先创建一个选项字典。之后获取图像的方向并将其设置为键CIDetectorImageOrientation的值。
NSDictionary *options = [NSDictionary dictionaryWithObject:
[image properties] valueForKey:kCGImagePropertyOrientation]
forKey:CIDetectorImageOrientation];
NSArray *adjustments = [myImage autoAdjustmentFiltersWithOptions:options];
for (CIFilter *filter in adjustments){
[filter setValue:myImage forKey:kCIInputImageKey];
myImage = filter.outputImage;
}
回想一下,这些输入参数值都已经被Core Image 设定好,并产生最佳结果。
你不必立即应用自动调整滤镜。你可以保存滤镜名称及参数,后续使用。保存这些使你的app可以在后续执行增强图像操作时,不必再重新分析图像。
四 向系统查询滤镜
Core Image提供了一些方法,你可以向系统查询内置的滤镜,以及每个滤镜的相关信息-显示名称,输入参数,参数类型,缺省值,等等。向系统查询使你可以得到最新的滤镜信息。
如果你的应用允许用户选择和设置滤镜,那么当你创建一个设置滤镜的用户界面时,你可以使用这些信息。
1 获取滤镜列表和属性
使用filterNamesInCategory:和filterNamesInCategories:方法来得到系统支持哪些滤镜。滤镜被分了类别,这样更易于管理。如果你知道一个滤镜的类别,你可以通过filterNamesInCategory:查到这个类别支持哪些滤镜。如果你想查一个类别列表支持的所有滤镜,你可以调用filterNamesInCategories:方法。这个方法返回一个NSArray对象,里面每个类别的滤镜名称。你可以把类别列表参数设置为nil,来获得所有类别的滤镜的列表。
类别常量:
在你获得一个滤镜名称列表后,你可以通过创建一个滤镜对象,并调用属性方法来获得滤镜的属性。
CIFilter *myFilter;
NSDictionary *myFilterAttributes;
myFilter = [CIFilter filterWithName:@"<Filter Name Here>"];
myFilterAttributes = [myFilter attributes];
在真正编码时,你要将"<Filter Name Here>"替换为你感兴趣的滤镜名称。
属性包括这些内容:名称,类别,类,最小、最大值。
2 构建一个滤镜字典
如果你的应用提供了一个用户界面,(你的应用)可以根据滤镜字典创建和更新界面。
比如,滤镜属性是个布尔值,需要一个checkbox或者类似的界面元素,对于在一个范围内持续变化的属性,使用一个slider。你可以使用text label设置最大最小值。属性的缺省值,就是用户界面的初值。
滤镜名称和属性提供充足的信息,使你可以构建一个用户界面,并允许用户选择滤镜,控制其输入参数。
categories = [NSMutableDictionary alloc] init];
NSMutableArray *array;
array = [NSMutableArray arrayWithArray:
[CIFilter filterNamesInCategory:
kCICategoryGeometryAdjustment];
[array addObjectsFromArray:
[CIFilter filterNamesInCategory:
kCICategoryDistortionEffect];
[categories setObject: [self buildFilterDictionary: array]
forKey: @"Distortion"];
array = [NSMutableArray arrayWithArray:
[CIFilter filterNamesInCategory: kCICategorySharpen];
[array addObjectsFromArray:
[CIFilter filterNamesInCategory: kCICategoryBlur];
[categories setObject: [self buildFilterDictionary: array]
forKey:@"Focus"];
// 根据滤镜名称构造字典
- (NSMutableDictionary *)buildFilterDictionary: (NSArray *)names // 1
{
NSMutableDictionary *td, *catfilters;
NSDictionary *attr;
NSString *classname;
CIFilter *filter;
int i;
catfilters = [NSMutableDictionary dictionary];
for(i=0 ; i<[names count] ; i++) // 2
{
classname = [names objectAtIndex: i]; // 3
filter = [CIFilter filterWithName: classname]; // 4 构造滤镜
if(filter)
{
attr = [filter attributes]; // 5 滤镜属性
td = [NSMutableDictionary dictionary];
[td setObject: classname forKey: @"class"]; // 6
[catfilters setObject: td
forKey:[attr objectForKey:@"name"]; // 7
}
else
NSLog(@" could not create '%@' filter", classname);
}
return catfilters;
}
五 继承CIFilter:定制效果(Effect)的配方
你可以将一个滤镜的输入作为另一个滤镜的输入,把多个滤镜链接到一起,从而定制出特定的效果。
当你创建一种效果,并且后续想要复用,可以考虑继承CIFilter来把这种效果封装到一个滤镜中。
本章介绍如何继承CIFilter,创建一个CIColorInvert滤镜。
之后描述了将多种滤镜链接到一起,达到有趣效果的方式。
1 继承CIFilter创建CIColorInvert滤镜
当你继承CIFilter时,你可以对现有的滤镜进行设置或者将现有滤镜链接起来。Core Image通过这种方式实现了一些内置的滤镜。
要继承一个滤镜,你需要完成以下工作:
- 声明滤镜的输入参数属性。你必须预先设定好每个以input开头的输入参数,比如inputImage
- 如果有必要的话,覆写setDefaults方法。
- 覆写outputImage方法。
Core Image提供的CIColorInvert滤镜,就是CIColorMatrix滤镜的变异。见名知意,CIColorInvert向CIColorInvert提供向量,来反转输入图像的颜色。
代码:
@interface CIColorInvert: CIFilter{
CIImage *inputImage;
}
@property (retain, nonatomic) CIImage *inputImage;
@end
@implementation CIColorInvert
@synthesize inputmage;
- (CIImage *) outputImage {
return [CIFilter filterWithName:@"CIColorMatrix"
keysAndValues:
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],
nil, outputImage];
}
2 Chroma Key Filter Recipe(浓度键滤镜配方)
从原图中删除一种颜色,或者某个范围内的颜色,之后将原图和背景组合起来。
创建一个Chroma Key滤镜需要以下操作:
- Create a cube map of data that maps the color values you want to remove so they are transparent (alpha value is 0.0). 不会翻译了-_-|||,大概意思是会把要过滤掉的颜色,设置为透明色。
- Use the CIColorCube filter and the cube map to remove chroma-key color from the source image.使用CIColorCube滤镜和cube map从原图上删除浓度-键颜色。
3 创建一个Cube Map
一个color cube是一个3D颜色查找表(lookup table)。Core Image 滤镜 CIColorCube 使用色值作为输入,并应用一个查找表到这些色值。
CIColorCube的缺省查找表是一个特性矩阵-矩阵的含义是不会对输入数据做任何操作。然而,这个配方需要你从图像中删除所有的绿色。你要把图中的把绿色的alpha值设置为0.0(透明),来删除绿色。
“绿色”包括一定范围内的颜色。最直接的处理方式是把图像的色值从RGBA转为HSV。
HSV格式,颜色是使用围绕柱面中轴的角度来表示的。在这种表示方式下,你可以把颜色想象成饼图上的一片(pie slice),之后,简单的删掉chroma-key color。
要删除绿色,你需要定义围绕中心点的最小和最大的角度。之后,对于任何的绿色,将其alpha值设置为0.0。纯绿的相对角度是120º。最小值和最大值要以这个值为中心。
Cube map数据必须预乘alpha,所以创建cube map的最后一步是把RGB值乘以你刚刚计算出的alpha值(如果是绿色,就是0,如果不是就是1.0)。
代码:
// Allocate memory
const unsigned int size = 64;
float *cubeData = (float *)malloc (size * size * size * sizeof (float) * 4);
float rgb[3], hsv[3], *c = cubeData;
// Populate cube with a simple gradient going from 0 to 1
for (int z = 0; z < size; z++){
rgb[2] = ((double)z)/(size-1); // Blue value
for (int y = 0; y < size; y++){
rgb[1] = ((double)y)/(size-1); // Green value
for (int x = 0; x < size; x ++){
rgb[0] = ((double)x)/(size-1); // Red value
// Convert RGB to HSV
// You can find publicly available rgbToHSV functions on the Internet
rgbToHSV(rgb, hsv);
// Use the hue value to determine which to make transparent
// The minimum and maximum hue angle depends on
// the color you want to remove
float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f:
1.0f;
// Calculate premultiplied alpha values for the cube
c[0] = rgb[0] * alpha;
c[1] = rgb[1] * alpha;
c[2] = rgb[2] * alpha;
c[3] = alpha; // c的指针不移动???
}
}
}
// Create memory with the cube data
NSData *data = [NSData dataWithBytesNoCopy:cubeData
length:cubeDataSize
freeWhenDone:YES];
CIColorCube *colorCube = [CIFilter filterWithName:@"CIColorCube"];
[colorCube setValue:[NSNumber numberWithInt:size] forKey:@"inputCubeDimension"];
// Set data for cube
[colorCube setValue:data forKey:@"inputCubeData"];
4 从原图中删除绿色
现在你有了color map data,给CIColorCube滤镜提供背景图(从这张图上把绿色删掉),并得到输出图像。
[colorCube setValue:myInputImage forKey:@"inputImage"];
CIImage *result = [colorCube valueForKey:kCIOutputImageKey];
5 混合处理过的图像与背景图
设置CISourceOverCompositing滤镜的参数:
● 把CIColorCube滤镜处理后的图像作为 inputImage参数。
● 把新的背景图作为inputBackgroundImage参数。
前面的图现在看起来就像在海滩上。
6 White Vignette for Faces Filter Recipe (iOS only) 脸部滤镜配方的白晕影
创建一个白晕影滤镜:
● 找到图像中的人脸
● 在CIRadialGradient上以人脸为中心,创建一个base shade map
● 将base shade map与人脸混合
7 找到人脸
使用CIDetector获得人脸在图像中的位置。featuresInImage:options:函数返回的array的第一项就是滤镜要操作的对象。在你有脸后(-_-),根据探测器提供的边界计算脸部中心。你需要中心值来创建create the shade map。
CIDetector *detector = [CIDector detectorOfType:CIDetectorTypeFace
context:nil
options:nill];
NSArray *faceArray = [detector featuresInImage:image options:nil];
CIFeature *face = (CIFeature *) [faceArray objectAtIndex:0]; // 也不怕crash...
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];
8 创建一个Shade Map
使用CIRadialGradient滤镜创建一个以人脸为中心的shade map。shade map的中心必须是透明的,这样图像中的人脸才能保持未变化。map的边缘应该是不透明的白色。这两个区域之间是不同程度的透明。
为了达到这个效果,如下设置CIRadialGradient的参数:
● 设置 inputRadius0 成一个大于图像最长维度的值
● 设置 inputRadius1 成一个比人脸大的值,比如face.bounds.size.height + 50.
● 设置 inputColor0 为不透明的白.
● 设置 inputColor1 为透明的白.
● 设置 inputCenter 为你计算出的人脸的中心.
9 混合Gradient(梯度)与人脸
设置CISourceOverCompositing滤镜的输入参数:
● 设置 inputImage 为原图.
● 设置 inputBackgroundImage为上步生成的shade map.
10 移轴滤镜配方(Tilt-Shift Filter Recipe)
效果:
创建一个移轴滤镜要完成的工作:
- 创建一个模糊的图像
- 创建两个线性gradients
- 通过混合线性gradients来创建一个遮掩
- 将模糊图像,遮掩,原图混合
下面的段落介绍如何执行每一步:
11 创建图像的模糊版本
如下设置CIGaussianBlur的输入参数:
- 把inputImage参数设置为你要处理的图像
- 把inputRadius设置为10.0
12 创建线性梯度
从一个单色(如绿色或灰色)创建一个线性梯度,从上倒下变化。
如下设置CILinearGradient的输入参数:
● Set inputPoint0 to (0, 0.75 * h)
● Set inputColor0 to (0,1,0,1)
● Set inputPoint1 to (0, 0.5*h)
● Set inputColor1 to (0,1,0,0)
创建一个绿色线性梯度,从上到下变化。
如下设置CILinearGradient的输入参数:
● Set inputPoint0 to (0, 0.25 * h)
● Set inputColor0 to (0,1,0,1)
● Set inputPoint1 to (0, 0.5*h)
● Set inputColor1 to (0,1,0,0)
13 通过线性梯度,创建遮掩
如下设置CIAdditionCompositing滤镜的参数:
- 给你创建的第一个线性梯度设置输入图像(inputImage)
- 给你创建的第二个线性梯度设置输入背景图像(inputBackgroundImage)
14 混合模糊图像,原图和梯度
最后一步使用CIBlendWithMask滤镜,如下设置输入参数:
- 设置inputImage参数为图像的模糊版本
- 设置inputBackgroundImage参数为未处理的图像
- 设置inputMaskImage为混合的梯度
遮掩只会影响图像中有东西的部分,透明的部分会被略过,显示原始图像。不透明的部分允许显示模糊图像。
其他滤镜不翻译了,需要时直接查文档吧。
六 获得最佳性能
Core Image提供了很多可选项来创建图像,上下文和渲染内容。
你如何完成一项任务,取决于:
- 你的app需要执行一项任务的频率
- 你的app是使用静态还是视频图像
- 你是否需要支持实时的处理和分析
- 颜色的保真度对你的用户是否重要
你应该通读性能最佳实践 (performance best practices),以确保您的app能够高效地运行。
1 性能最佳实践
如下来实践最佳性能:
- 不要每次渲染都创建一个CIContext对象
上下文中保存了大量的状态信息;重用他们会更加高效。
- 评估一下你的app是否需要颜色管理。如果你不需要就不要使用它。
- 使用GPU上下文渲染CIImage对象时,避免Core Animation的动画。
- 保证图像不超过CPU和GPU的限制。
Core Image 使用CPU或者GPU时CIContext对图像大小的限制是不同的。
通过inputImageMaximumSize和outputImageMaximumSize来获得限制的实际值。
- 尽可能使用小图像。
性能会随着输出的像素数增加。你可以让Core Image渲染到一个小的视图,质地,或者帧缓冲区。
Allow Core Animation to upscale to display size.
使用Core Graphics或Image I/O函数来剪切或者采样,比如
CGImageCreateWithImageInRect 或者 CGImageSourceCreateThumbnailAtIndex。
● UIImageView类最适合处理静态图像。
如果你的app要获得最佳性能,请使用底层API。
● 避免CPU和GPU之间不必要的质地转换。
●
Render to a rectangle that is the same size as the source image before applying a contents scale factor.
● 考虑使用能达到近似于算法滤镜效果的简单滤镜。
比如,CIColorCube能产生与CISepiaTone近似的输出,并且更高效。
● Take advantage of the support for YUV image in iOS v6.0 and later.
Camera pixel buffers are natively YUV but most image processing algorithms expect RBGA data. There is a cost to converting between the two. Core Image supports reading YUB from CVPixelBuffer objects and applying the appropriate color transform.
YUV, YUB是神码?
options = @{ (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber
numberWithInt:kCVPixelFormatType_420YpCvCr88iPlanarFullRange]};
2 你的APP需要颜色管理吗?
默认情况下, Core Image 应用所有的滤镜于轻-线性颜色空间。
这提供了最精准和一致的结果。
转变到sRGB,和向sRGB转变,增加了滤镜的复杂程度,需要Core Image去应用这些方程:
rgb = mix(rgb.0.0774, pow(rgb*0.9479 + 0.05213, 2.4), step(0.04045, rgb))
rgb = mix(rgb12.92, pow(rgb*0.4167) * 1.055 - 0.055, step(0.00313, rgb))
以下场景,要考虑禁止颜色管理:
- 你的app需要绝对的高性能。
- 夸张的操作(exaggerated manipulations)后用户不会注意到质量差别。
要禁用颜色管理,将kCIImageColorSpace键设置为null。
如果你使用一个EAGL上下文,也要在创建EAGL上下文时,将上下文颜色空间设置为null。
七 Using Feedback to Process Images
iOS 不支持,略过。
八 在实现一个自定义滤镜前,你需要知道的几件事
OS X provides support for writing custom filters.
iOS不支持,略过。
九 创建自定义滤镜
On OS X, if the filters provided by Core Image don’t provide the functionality you need, you can write your own filter. (Custom filters are not available on iOS.)
iOS不支持,略过。
十 打包和加载图像单元
An image unit represents the plug-in architecture for Core Image filters. Image units use the NSBundle class as the packaging mechanism to allow you to make the filters that you create available to other apps. An image unit can contain filters that are executable or nonexecutable. (See “Executable and Nonexecutable Filters” (page
69) for details.)
CIFilter 是一个可变对象,代表一种效果。一个滤镜对象至少要有一个输入参数,并产生一个输出图片。
CIImage 是一个不变对象,代表一个图片。你可以通过图像数据,或者通过文件,或者另一个CIFilter对象的输出得到一个CIImage。
CIContext 是一个对象,通过它Core Image可以绘制一个CIFilter产生的结果。一个Core Image Context可以基于CPU或GPU。
1 概览
源码:
CIContext *context = [CIContext contextWithOptions:nil]; // 1
CIImage *image = [CIImage imageWithContentsOfURL:myURL]; // 2
CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone"]; // 3
[filter setValue:image forKey:kCIInputImgeKey];
[filter setValue:[NSNumber numberWithFloat:0.8f] forKey:@"InputIntensity"];
CIImage *result = [filter valueForKey:kCIOutputImageKey]; // 4
CGImageRef cgImage = [context createCGImage:result fromRect:[result extent]; // 5
说明:
1. 创建一个CIContext对象。这个方法你可以在iOS上使用。
2. 创建一个CIImage对象. 你可以从多种来源创建一个CIImage对象,不日URL。
3. 创建滤镜,并设置其输入参数。
4. 获得输出图像,输出图像是一个描述如何生成图像的配方(这里的配方是描述如何制作一道菜的配方,不是点菜的菜谱)。图像还没有被渲染出来。
5. 渲染CIImage,得到CGImageRef,CGImageRef是可以直接展示或者保存到文件中的。
2 内置滤镜(说明:原文是filter,这里采用滤镜更直观,因此不翻译为过滤器)
Core Image提供了大量内置的滤镜,供你的应用做图像处理。不同iOS版本提供的滤镜是不同的,所以Core Image 提供了方法,让你可以查询系统提供了哪些滤镜。
一个滤镜类别定义了各种效果的类型,或者其可以作用的目标:
images, video, nonsquare pixels, 等等.
一个滤镜可以属于多个类别。滤镜有一个显示名称(给用户看的)和一个滤镜名称(在代码中要使用的)。
滤镜属性是以键-值对的形式保存的。键是一个常量,标识属性及其对应的值。
Core Image使用键-值编码,你可以通过KVC获得属性的值。
3 创建一个Core Image Context
要渲染图像,你需要创建Core Image Context,并使用这个上下文绘制输出图像。
一个Core Image context标识一个绘制的目标。目标决定了使用CPU还是GPU去渲染。
创建的函数:
contextWithOptions: CPU或GPU都支持。
以下仅支持GPU:
contextWithCGLContext: pixelFormat:options: GPU OS X
contextWithCGLContext: pixelFormat:
colorSpace:options:
[1] 在iOS上创建一个Core Image Context,When You Don’t Need Real-Time
Performance
源码:
CIContext *context = [CIContext contextWithOptions:nil];
这个方法可以使用CPU或者GPU进行渲染。要定义使用哪个,需要设置一个选项字典,增加键kCIContextUserSoftwareRenderer,并设置相应的布尔值。CPU渲染要比GPU要慢。但是对于GPU渲染,在渲染结果被拷贝回CPU内存并转化为UIImage之前,不会被显示出来。
[2] 在iOS上创建一个Core Image Context,When You Need Real-Time Performance
如果你的应用要支持实时的图像处理,你需要从一个EAGL context创建一个CIContext对象,不能使用contextWithOptions: 方法。优点是渲染的图像保存在GPU上,并不需要拷贝回CPU内存。你首先需要创建一个EAGL context:
myEAGLContext = [EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
之后使用方法contextWithEAGLContext: 创建一个CIContext对象。
你应该将working color space设置为null,来关闭颜色管理功能。颜色管理会降低性能。在需要颜色保真的情况下,你才需要颜色管理。但是对于一个real-time app,颜色保真通常并不是必要的。
代码:
NSMutableDictionary *options = [NSMutableDictionary alloc] init];
[options setObject: [NSNull null] forKey: kCIContextWorkingColorSpace];
myContext = [CIContext contextWithEAGLContext:myEAGLContext options:options];
4 创建一个CIImage对象
Core Image filters 处理Core Image images (CIImage objects).
记住,一个CIImage对象实际上是一个配方;在被调用,在目标上渲染结果之前,Core Image实际上不产生一个像素。创建的方法:
![]() |
![]() |
5 创建一个CIFilter并设置属性值
filterWithName: 方法创建了一个过滤器,通过滤镜名称参数来设定滤镜的类型。
名称参数是一个字符串,其值必须与内置滤镜严格匹配。
代码:
hueAdjust = [CIFilter filterWithName:@"CIHueAdjust"];
[hueAdjust setValue: myCIImage forKey: @"inputImage"];
[hueAdjust setValue: [NSNumber numberWithFloat: 2.094]
forKey: @"inputAngle"];
另一种方式:
hueAdjust = [CIFilter filterWithName:@"CIHueAdjust"] keysAndValues:
@"inputImage",myCIImage,
@"inputAngle, [NSNumber numberWithFloat: 2.094],
nil]; // 你可以增加更多参数,但是必须以nil结尾。
6 获取输出图像
你通过outputImage键,获得输出图像。
CIImage *result = [hueAdjust valueForKey: @"outputImage"];
在你调用某个方法实际执行渲染之前,Core Image不会执行任何的图像处理操作。
当你请求去输出图像时,Core Image组装并保存要生成一个输出图像的运算步骤(保存到一个CIImage对象中)。只有在你显示调用一个图像绘制方法时,实际的图像才会被渲染。
将处理延迟到渲染的时候,使得Core Image 快速而高效。在渲染的时候,Core Image会判断是否有超过一个滤镜需要作用到一个图像上。如果是这样,它自动把多个配方连接成一个操作(好像就是做一系列的矩阵运算,运算结果仍是一个矩阵,所以可以合成一步对结果矩阵的操作,说明by王云鹏),即每个像素只会被处理一次,而不是多次。
下图列出了一个multiple-operations workflow ,Core Image 可以更高效。最终图像是原始图像的一个缩小版本。
对于一个大图像,在大小调整之前,应用颜色调整,比先做大小调整,之后再应用颜色调整,需要更多的处理资源。
通过将滤镜处理操作延迟到渲染的时候,Core Image可以按照最高效的处理顺序来执行操作。
![]() |
7 渲染输出图像
渲染输出图像会触发CPU或者GPU(依赖于你设置的context)的密集的处理器操作。下面的函数可以用来渲染:
![]() |
![]() |
代码:
[myContext drawImage:result inRect:destinationRect fromRect:contextRect];
效果对比:
![]() |
![]() |
8 保证线程安全
CIContext 和CIImage对象是不可变的, 在线程之间共享这些对象是安全的.
多个线程可以使用同一个GPU或者CPU CIContext对象来渲染CIImage对象。
但是对于可变的CIFilter确不行。一个CIFilter对象不能在多个线程间共享。如果你的应用是多线程的,每个线程都必须创建自己的CIFilter对象。否则,你的应用将产生不可预期的结果。
9 滤镜链
你可以通过滤镜链创造出惊人的效果,即将一个滤镜的输出作为另一个滤镜的输入。
让我们看看在一个图像上如何应用两个滤镜—幽暗(?gloom)(CIGloom) 和爆沸畸变 (?bump distortion)(CIBumpDistortion).
gloom滤镜:通过淡化亮度来使一张图片变暗。
gloom = [CIFilter filterWithName:@"CIGloom"]; // 1
[gloom setValue: result forKey: @"inputImage"];
[gloom setValue: [NSNumber numberWithFloat: 25]
forKey: @"inputRadius"]; // 2
[gloom setValue: [NSNumber numberWithFloat: 0.75]
forKey: @"inputIntensity"]; // 3
result = [gloom valueForKey: @"outputImage"]; // 4
1. 创建一个CIGloom类型的滤镜。
2. 设置输入半径为25。半径定义了效果的外延,可以设置0到100之间的值,缺省值是10。(你可以通过滤镜的属性字典获得滤镜的最小、最大、缺省值)。
3. 将输入强度设置为0.75。输入强度是一个标量值,它定义了原图与滤镜输出之间的一个线性的混合。最小值是0.0,最大值是1.0,缺省值是1.0。
4. 请求输出图像,但并不真正绘制。
处理结果:
![]() |
bump distortion 滤镜 (CIBumpDistortion)在一个图像上的某个点创建一个突起。
源码:
bumpDistortion = [CIFilter filterWithName:@"CIBumpDistortion"]; // 1
[bumpDistortion setValue: result forKey: @"inputImage"];
[bumpDistortion setValue: [CIVector vectorWithX:200 Y:150 ]
forKey: @"inputCenter"]; // 3
[bumpDistortion setValue: [NSNumber numberWithFloat: 100]
forKey: @"inputRadius"]; // 4
[bumpDistortion setValue: [NSNumber numberWithFloat: 3.0]
forKey: @"inputScale"]; // 5
result = [bumpDistortion valueForKey: @"outputImage"];
说明:
1. 通过滤镜名称创建滤镜
2. OSX特有的处理,这里删掉了。
3. 将效果的中心点设置在图像的中心位置。
4. 设置扭曲的半径为100。
5. 将输入标量设置为3。输入标量定义了效果的方向和数量。
(原文:The input scale specifies the direction and the amount of the effect. )
缺省值是-0.5。范围是-10.0到10.0。0是没有效果。负值会创建一个向外扭曲的效果。正值向内扭曲。
效果:
![]() |
10 使用过渡(transition)效果
过渡通常用于图像间的滑动,或者在视频中从一个场景切换到另个场景。
这些效果需要经过一段时间进行渲染,你需要为此设置一个定时器。本断的目的是展示如何设置一个定时器。copy machine transition(CICopyMachine)创建了一个发光条,类似于复印机或者图片扫描仪。发光条从左向右扫过原始图像,并揭示目标图像。
过渡滤镜需要完成一下任务:
1. 创建用于过渡的Core Image images (CIImage objects) 。
2. 设置一个定时器。
3. 创建一个CIContext 对象。
4. 创建一个CIFilter对象来处理图像。
6. 设置滤镜参数。
7. 设置要处理的源与目标图像。
8. 计算时间。
9. 应用滤镜。
10. 绘制结果。
11. 重复8-10步,直到完成过渡。
效果:
![]() |
代码比较长,直接看原文吧。
11 在视频上使用滤镜
Core Image 和 Core Video一起工作,获得丰富的效果。比如,在水下拍摄视频时,
你可以使用一个颜色校正滤镜来修正(水吸收红光要比蓝光更快)。有很多种方式让你可以使用这些技术。
例子是OS X的,不贴出来了。
二 人脸探测
Core Image可以对图像进行分析,并找到人脸。它进行的是人脸探测,但不是识别(识别的意思是能认出你是谁,探测的意思是是能找到人脸在图像中的位置)。Core Image检测出人脸后,可以提供面部特性,比如眼睛和嘴的位置。它还可以跟踪视频中人脸的位置。
![]() |
知道脸在图像中的位置让你可以执行一些其他的操作,比如:对头像做剪裁或者调整图像质量的操作 (tone balance, red-eye correction and so on)。你还可以在脸上做其他有趣的操作:
● 仅在人脸上应用像素滤镜。
● 在人脸边上加小插图。
1 探测人脸
CIContext *context = [CIContext contextWithOptions:nil]; // 1
NSDictionary *opts = [NSDictionary dictionaryWithObject:CIDetectorAccuracyHigh
forKey:CIDetectorAccuracy]; // 2
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace
context:context
options:opts]; // 3
opts = [NSDictionary dictionaryWithObject:
[myImage properties] valueForKey:kCGImagePropertyOrientation;
forKey:CIDetectorImageOrientation];// 4
NSArray *features = [detector featuresInImage:myImage
options:opts]; // 5
1. 创建一个CIContext对象。
2. 创建一个选项字典来定义detector的精度。你可以定义低精度或高精度。
低精度 (CIDetectorAccuracyLow) 速度快; 高精度,更彻底,但是比较慢。
3. 创建一个用于人脸检测的detector。你唯一能创建的就是用于人脸检测的detector。
4. 设定一个用于检测人脸的选项字典。有一点是非常重要的,必须让Core Image 知道图像的方向。这样detector才能知道怎样查找人脸。通常你可以从图像本身获得方向的信息,并传入字典。
5. 使用detector去查找人脸的面部特征。你提供的图像必须是一个CIImage对象。Core Image返回一个CIFeature对象的数组,你可能想找到他们的特性,比如眼睛,嘴的位置。
2 获得脸和脸部特征界限
脸部特征包括:
- 左右眼睛的位置
- 嘴巴的位置
- 跟踪ID和跟踪帧数(Core Image 以此来跟踪视频断中的脸)(available in iOS v6.0 and later and in OS X v10.8 and later)
在你通过CIDetector对象获得一个脸部特征数据的array后,你可以循环这个array来检测每张脸的边界及其特性。
for (CIFaceFeature *f in features)
{
NSLog(NSStringFromRect(f.bounds));
if (f.hasLeftEyePosition)
printf("Left eye %g %g\n", f.leftEyePosition.x, f.leftEyePosition.y);
if (f.hasRightEyePosition)
printf("Right eye %g %g\n", f.rightEyePosition.x, f.rightEyePosition.y);
if (f.hasmouthPosition)
printf("Mouth %g %g\n", f.mouthPosition.x, f.mouthPosition.y);
}
三 自动增强图像
Core Image的自动增强特性分析图像的直方图,脸部区域内容,和元数据属性。之后返回一个CIFilter对象的array,这些对象的参数已经被设置好,用来增强被分析的图像。
(Auto enhancement is available in iOS v5.0 and later and in OS X v10.8 and later)
1 自动增强滤镜
下面这些滤镜可以修正照片中的大部分问题:
![]() |
2 使用自动增强滤镜
自动增强API仅有2个方法: autoAdjustmentFilters和
autoAdjustmentFiltersWithOptions:. 在大多数情况下,你会使用带有选项字典参数的方法。
你可以做以下设置:
- 对于CIRedEyeCorrection和CIFaceBalance 滤镜,图像的方向是很重要的,所以Core Image 可以精确的定位到脸。
- 是否仅应用红眼校正。(把kCIImageAutoAdjustEnhance设置为false)
- 是否使用红眼以外的全部其他滤镜。(把kCIImageAutoAdjustRedEye设置为false)
autoAdjustmentFiltersWithOptions: 返回一个备选滤镜的array,你可以把他们链接起来并应用到被分析的图像。下面的代码先创建一个选项字典。之后获取图像的方向并将其设置为键CIDetectorImageOrientation的值。
NSDictionary *options = [NSDictionary dictionaryWithObject:
[image properties] valueForKey:kCGImagePropertyOrientation]
forKey:CIDetectorImageOrientation];
NSArray *adjustments = [myImage autoAdjustmentFiltersWithOptions:options];
for (CIFilter *filter in adjustments){
[filter setValue:myImage forKey:kCIInputImageKey];
myImage = filter.outputImage;
}
回想一下,这些输入参数值都已经被Core Image 设定好,并产生最佳结果。
你不必立即应用自动调整滤镜。你可以保存滤镜名称及参数,后续使用。保存这些使你的app可以在后续执行增强图像操作时,不必再重新分析图像。
四 向系统查询滤镜
Core Image提供了一些方法,你可以向系统查询内置的滤镜,以及每个滤镜的相关信息-显示名称,输入参数,参数类型,缺省值,等等。向系统查询使你可以得到最新的滤镜信息。
如果你的应用允许用户选择和设置滤镜,那么当你创建一个设置滤镜的用户界面时,你可以使用这些信息。
1 获取滤镜列表和属性
使用filterNamesInCategory:和filterNamesInCategories:方法来得到系统支持哪些滤镜。滤镜被分了类别,这样更易于管理。如果你知道一个滤镜的类别,你可以通过filterNamesInCategory:查到这个类别支持哪些滤镜。如果你想查一个类别列表支持的所有滤镜,你可以调用filterNamesInCategories:方法。这个方法返回一个NSArray对象,里面每个类别的滤镜名称。你可以把类别列表参数设置为nil,来获得所有类别的滤镜的列表。
类别常量:
![]() |
![]() |
![]() |
在你获得一个滤镜名称列表后,你可以通过创建一个滤镜对象,并调用属性方法来获得滤镜的属性。
CIFilter *myFilter;
NSDictionary *myFilterAttributes;
myFilter = [CIFilter filterWithName:@"<Filter Name Here>"];
myFilterAttributes = [myFilter attributes];
在真正编码时,你要将"<Filter Name Here>"替换为你感兴趣的滤镜名称。
属性包括这些内容:名称,类别,类,最小、最大值。
2 构建一个滤镜字典
如果你的应用提供了一个用户界面,(你的应用)可以根据滤镜字典创建和更新界面。
比如,滤镜属性是个布尔值,需要一个checkbox或者类似的界面元素,对于在一个范围内持续变化的属性,使用一个slider。你可以使用text label设置最大最小值。属性的缺省值,就是用户界面的初值。
滤镜名称和属性提供充足的信息,使你可以构建一个用户界面,并允许用户选择滤镜,控制其输入参数。
categories = [NSMutableDictionary alloc] init];
NSMutableArray *array;
array = [NSMutableArray arrayWithArray:
[CIFilter filterNamesInCategory:
kCICategoryGeometryAdjustment];
[array addObjectsFromArray:
[CIFilter filterNamesInCategory:
kCICategoryDistortionEffect];
[categories setObject: [self buildFilterDictionary: array]
forKey: @"Distortion"];
array = [NSMutableArray arrayWithArray:
[CIFilter filterNamesInCategory: kCICategorySharpen];
[array addObjectsFromArray:
[CIFilter filterNamesInCategory: kCICategoryBlur];
[categories setObject: [self buildFilterDictionary: array]
forKey:@"Focus"];
// 根据滤镜名称构造字典
- (NSMutableDictionary *)buildFilterDictionary: (NSArray *)names // 1
{
NSMutableDictionary *td, *catfilters;
NSDictionary *attr;
NSString *classname;
CIFilter *filter;
int i;
catfilters = [NSMutableDictionary dictionary];
for(i=0 ; i<[names count] ; i++) // 2
{
classname = [names objectAtIndex: i]; // 3
filter = [CIFilter filterWithName: classname]; // 4 构造滤镜
if(filter)
{
attr = [filter attributes]; // 5 滤镜属性
td = [NSMutableDictionary dictionary];
[td setObject: classname forKey: @"class"]; // 6
[catfilters setObject: td
forKey:[attr objectForKey:@"name"]; // 7
}
else
NSLog(@" could not create '%@' filter", classname);
}
return catfilters;
}
五 继承CIFilter:定制效果(Effect)的配方
你可以将一个滤镜的输入作为另一个滤镜的输入,把多个滤镜链接到一起,从而定制出特定的效果。
当你创建一种效果,并且后续想要复用,可以考虑继承CIFilter来把这种效果封装到一个滤镜中。
本章介绍如何继承CIFilter,创建一个CIColorInvert滤镜。
之后描述了将多种滤镜链接到一起,达到有趣效果的方式。
1 继承CIFilter创建CIColorInvert滤镜
当你继承CIFilter时,你可以对现有的滤镜进行设置或者将现有滤镜链接起来。Core Image通过这种方式实现了一些内置的滤镜。
要继承一个滤镜,你需要完成以下工作:
- 声明滤镜的输入参数属性。你必须预先设定好每个以input开头的输入参数,比如inputImage
- 如果有必要的话,覆写setDefaults方法。
- 覆写outputImage方法。
Core Image提供的CIColorInvert滤镜,就是CIColorMatrix滤镜的变异。见名知意,CIColorInvert向CIColorInvert提供向量,来反转输入图像的颜色。
代码:
@interface CIColorInvert: CIFilter{
CIImage *inputImage;
}
@property (retain, nonatomic) CIImage *inputImage;
@end
@implementation CIColorInvert
@synthesize inputmage;
- (CIImage *) outputImage {
return [CIFilter filterWithName:@"CIColorMatrix"
keysAndValues:
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],
nil, outputImage];
}
2 Chroma Key Filter Recipe(浓度键滤镜配方)
从原图中删除一种颜色,或者某个范围内的颜色,之后将原图和背景组合起来。
![]() |
创建一个Chroma Key滤镜需要以下操作:
- Create a cube map of data that maps the color values you want to remove so they are transparent (alpha value is 0.0). 不会翻译了-_-|||,大概意思是会把要过滤掉的颜色,设置为透明色。
- Use the CIColorCube filter and the cube map to remove chroma-key color from the source image.使用CIColorCube滤镜和cube map从原图上删除浓度-键颜色。
3 创建一个Cube Map
一个color cube是一个3D颜色查找表(lookup table)。Core Image 滤镜 CIColorCube 使用色值作为输入,并应用一个查找表到这些色值。
CIColorCube的缺省查找表是一个特性矩阵-矩阵的含义是不会对输入数据做任何操作。然而,这个配方需要你从图像中删除所有的绿色。你要把图中的把绿色的alpha值设置为0.0(透明),来删除绿色。
“绿色”包括一定范围内的颜色。最直接的处理方式是把图像的色值从RGBA转为HSV。
HSV格式,颜色是使用围绕柱面中轴的角度来表示的。在这种表示方式下,你可以把颜色想象成饼图上的一片(pie slice),之后,简单的删掉chroma-key color。
要删除绿色,你需要定义围绕中心点的最小和最大的角度。之后,对于任何的绿色,将其alpha值设置为0.0。纯绿的相对角度是120º。最小值和最大值要以这个值为中心。
Cube map数据必须预乘alpha,所以创建cube map的最后一步是把RGB值乘以你刚刚计算出的alpha值(如果是绿色,就是0,如果不是就是1.0)。
代码:
// Allocate memory
const unsigned int size = 64;
float *cubeData = (float *)malloc (size * size * size * sizeof (float) * 4);
float rgb[3], hsv[3], *c = cubeData;
// Populate cube with a simple gradient going from 0 to 1
for (int z = 0; z < size; z++){
rgb[2] = ((double)z)/(size-1); // Blue value
for (int y = 0; y < size; y++){
rgb[1] = ((double)y)/(size-1); // Green value
for (int x = 0; x < size; x ++){
rgb[0] = ((double)x)/(size-1); // Red value
// Convert RGB to HSV
// You can find publicly available rgbToHSV functions on the Internet
rgbToHSV(rgb, hsv);
// Use the hue value to determine which to make transparent
// The minimum and maximum hue angle depends on
// the color you want to remove
float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f:
1.0f;
// Calculate premultiplied alpha values for the cube
c[0] = rgb[0] * alpha;
c[1] = rgb[1] * alpha;
c[2] = rgb[2] * alpha;
c[3] = alpha; // c的指针不移动???
}
}
}
// Create memory with the cube data
NSData *data = [NSData dataWithBytesNoCopy:cubeData
length:cubeDataSize
freeWhenDone:YES];
CIColorCube *colorCube = [CIFilter filterWithName:@"CIColorCube"];
[colorCube setValue:[NSNumber numberWithInt:size] forKey:@"inputCubeDimension"];
// Set data for cube
[colorCube setValue:data forKey:@"inputCubeData"];
4 从原图中删除绿色
现在你有了color map data,给CIColorCube滤镜提供背景图(从这张图上把绿色删掉),并得到输出图像。
[colorCube setValue:myInputImage forKey:@"inputImage"];
CIImage *result = [colorCube valueForKey:kCIOutputImageKey];
5 混合处理过的图像与背景图
设置CISourceOverCompositing滤镜的参数:
● 把CIColorCube滤镜处理后的图像作为 inputImage参数。
● 把新的背景图作为inputBackgroundImage参数。
前面的图现在看起来就像在海滩上。
6 White Vignette for Faces Filter Recipe (iOS only) 脸部滤镜配方的白晕影
![]() |
创建一个白晕影滤镜:
● 找到图像中的人脸
● 在CIRadialGradient上以人脸为中心,创建一个base shade map
● 将base shade map与人脸混合
7 找到人脸
使用CIDetector获得人脸在图像中的位置。featuresInImage:options:函数返回的array的第一项就是滤镜要操作的对象。在你有脸后(-_-),根据探测器提供的边界计算脸部中心。你需要中心值来创建create the shade map。
CIDetector *detector = [CIDector detectorOfType:CIDetectorTypeFace
context:nil
options:nill];
NSArray *faceArray = [detector featuresInImage:image options:nil];
CIFeature *face = (CIFeature *) [faceArray objectAtIndex:0]; // 也不怕crash...
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];
8 创建一个Shade Map
使用CIRadialGradient滤镜创建一个以人脸为中心的shade map。shade map的中心必须是透明的,这样图像中的人脸才能保持未变化。map的边缘应该是不透明的白色。这两个区域之间是不同程度的透明。
为了达到这个效果,如下设置CIRadialGradient的参数:
● 设置 inputRadius0 成一个大于图像最长维度的值
● 设置 inputRadius1 成一个比人脸大的值,比如face.bounds.size.height + 50.
● 设置 inputColor0 为不透明的白.
● 设置 inputColor1 为透明的白.
● 设置 inputCenter 为你计算出的人脸的中心.
9 混合Gradient(梯度)与人脸
设置CISourceOverCompositing滤镜的输入参数:
● 设置 inputImage 为原图.
● 设置 inputBackgroundImage为上步生成的shade map.
10 移轴滤镜配方(Tilt-Shift Filter Recipe)
效果:
![]() |
创建一个移轴滤镜要完成的工作:
- 创建一个模糊的图像
- 创建两个线性gradients
- 通过混合线性gradients来创建一个遮掩
- 将模糊图像,遮掩,原图混合
下面的段落介绍如何执行每一步:
11 创建图像的模糊版本
如下设置CIGaussianBlur的输入参数:
- 把inputImage参数设置为你要处理的图像
- 把inputRadius设置为10.0
12 创建线性梯度
从一个单色(如绿色或灰色)创建一个线性梯度,从上倒下变化。
如下设置CILinearGradient的输入参数:
● Set inputPoint0 to (0, 0.75 * h)
● Set inputColor0 to (0,1,0,1)
● Set inputPoint1 to (0, 0.5*h)
● Set inputColor1 to (0,1,0,0)
创建一个绿色线性梯度,从上到下变化。
如下设置CILinearGradient的输入参数:
● Set inputPoint0 to (0, 0.25 * h)
● Set inputColor0 to (0,1,0,1)
● Set inputPoint1 to (0, 0.5*h)
● Set inputColor1 to (0,1,0,0)
13 通过线性梯度,创建遮掩
如下设置CIAdditionCompositing滤镜的参数:
- 给你创建的第一个线性梯度设置输入图像(inputImage)
- 给你创建的第二个线性梯度设置输入背景图像(inputBackgroundImage)
14 混合模糊图像,原图和梯度
最后一步使用CIBlendWithMask滤镜,如下设置输入参数:
- 设置inputImage参数为图像的模糊版本
- 设置inputBackgroundImage参数为未处理的图像
- 设置inputMaskImage为混合的梯度
遮掩只会影响图像中有东西的部分,透明的部分会被略过,显示原始图像。不透明的部分允许显示模糊图像。
其他滤镜不翻译了,需要时直接查文档吧。
六 获得最佳性能
Core Image提供了很多可选项来创建图像,上下文和渲染内容。
你如何完成一项任务,取决于:
- 你的app需要执行一项任务的频率
- 你的app是使用静态还是视频图像
- 你是否需要支持实时的处理和分析
- 颜色的保真度对你的用户是否重要
你应该通读性能最佳实践 (performance best practices),以确保您的app能够高效地运行。
1 性能最佳实践
如下来实践最佳性能:
- 不要每次渲染都创建一个CIContext对象
上下文中保存了大量的状态信息;重用他们会更加高效。
- 评估一下你的app是否需要颜色管理。如果你不需要就不要使用它。
- 使用GPU上下文渲染CIImage对象时,避免Core Animation的动画。
- 保证图像不超过CPU和GPU的限制。
Core Image 使用CPU或者GPU时CIContext对图像大小的限制是不同的。
通过inputImageMaximumSize和outputImageMaximumSize来获得限制的实际值。
- 尽可能使用小图像。
性能会随着输出的像素数增加。你可以让Core Image渲染到一个小的视图,质地,或者帧缓冲区。
Allow Core Animation to upscale to display size.
使用Core Graphics或Image I/O函数来剪切或者采样,比如
CGImageCreateWithImageInRect 或者 CGImageSourceCreateThumbnailAtIndex。
● UIImageView类最适合处理静态图像。
如果你的app要获得最佳性能,请使用底层API。
● 避免CPU和GPU之间不必要的质地转换。
●
Render to a rectangle that is the same size as the source image before applying a contents scale factor.
● 考虑使用能达到近似于算法滤镜效果的简单滤镜。
比如,CIColorCube能产生与CISepiaTone近似的输出,并且更高效。
● Take advantage of the support for YUV image in iOS v6.0 and later.
Camera pixel buffers are natively YUV but most image processing algorithms expect RBGA data. There is a cost to converting between the two. Core Image supports reading YUB from CVPixelBuffer objects and applying the appropriate color transform.
YUV, YUB是神码?
options = @{ (id)kCVPixelBufferPixelFormatTypeKey: [NSNumber
numberWithInt:kCVPixelFormatType_420YpCvCr88iPlanarFullRange]};
2 你的APP需要颜色管理吗?
默认情况下, Core Image 应用所有的滤镜于轻-线性颜色空间。
这提供了最精准和一致的结果。
转变到sRGB,和向sRGB转变,增加了滤镜的复杂程度,需要Core Image去应用这些方程:
rgb = mix(rgb.0.0774, pow(rgb*0.9479 + 0.05213, 2.4), step(0.04045, rgb))
rgb = mix(rgb12.92, pow(rgb*0.4167) * 1.055 - 0.055, step(0.00313, rgb))
以下场景,要考虑禁止颜色管理:
- 你的app需要绝对的高性能。
- 夸张的操作(exaggerated manipulations)后用户不会注意到质量差别。
要禁用颜色管理,将kCIImageColorSpace键设置为null。
如果你使用一个EAGL上下文,也要在创建EAGL上下文时,将上下文颜色空间设置为null。
七 Using Feedback to Process Images
iOS 不支持,略过。
八 在实现一个自定义滤镜前,你需要知道的几件事
OS X provides support for writing custom filters.
iOS不支持,略过。
九 创建自定义滤镜
On OS X, if the filters provided by Core Image don’t provide the functionality you need, you can write your own filter. (Custom filters are not available on iOS.)
iOS不支持,略过。
十 打包和加载图像单元
An image unit represents the plug-in architecture for Core Image filters. Image units use the NSBundle class as the packaging mechanism to allow you to make the filters that you create available to other apps. An image unit can contain filters that are executable or nonexecutable. (See “Executable and Nonexecutable Filters” (page
69) for details.)
好棒
写的不错,帮我实现业务了
> 我来回应