iOS CoreImage专题(二) —— 进阶
来源:互联网 发布:c语言数据类型怎么用 编辑:程序博客网 时间:2024/05/16 13:43
- 前言
- 管理线程安全
- 滤镜链
- 使用转场效果
- 图像中的人脸检测
- 人脸检测
- 获取人脸和人脸的特征所在的矩形界限
- 自动图像增强
- 自动图像增强滤镜
- 使用自动增强滤镜
- 总结
前言
上一章节我们简单的介绍了CoreImage以及其滤镜的简单使用,包括对输出图像的渲染。这一部分我们将使用更高级一些的技巧:滤镜链、图像转场、人脸检测、自动图像增强等。
管理线程安全
在我们开始之前,先看一个比较重要的东西,那就是线程安全。
CIContext和CIImage对象都是不可变的,也就是说他们可以安全地共享给多个线程。多线程可以使用相同的GPU或者CPU下的CIContext对象来渲染CIImage对象。然而,这种情况并不能用于CIFilter对象,因为它是可变的。一个CIFilter对象不能安全地共享给多个线程。如果你的app是多线程的,每个线程必须创建它自己的CIFilter对象。否则你的app可能会产生一些意料之外的效果。
滤镜链
你可以创建一个滤镜链来获得一种amazing的效果。创建滤镜链就是把一个滤镜的的输出图像作为下一个滤镜的输入图像。我们来看看如何在一个图像上应用多个滤镜 – 变暗(CIGloom)以及凹凸变形(CIBumpDistortion)。
变暗的滤镜通过削弱一个图像的强光部分来让其变暗。下面的代码创建了一个滤镜然后把刚才调整色调的输出图像作为变暗滤镜的输入图像。这就是把滤镜连成一串,简单吧。
CIFilter *gloom = [CIFilter filterWithName:@"CIGloom"]; [gloom setDefaults]; // 1 [gloom setValue: result forKey: kCIInputImageKey]; [gloom setValue: @25.0f forKey: kCIInputRadiusKey]; // 2 [gloom setValue: @0.75f forKey: kCIInputIntensityKey]; // 3 result = [gloom valueForKey: kCIOutputImageKey]; // 4
1. 设置默认参数。在OS X中必须要手动调用才能设置默认参数,iOS则不需要,因为在iOS中在调用filterWithName的时候已经自动设置好了。
2. 设置输入半径为25。输入半径指定了效果的范围,可以从0变化到100,其默认值为10。之前提到了你能在代码中通过一个滤镜的属性字典来找到最大值、最小值和默认值。
3. 设置输入强度为0.75。输入强度是一个标量值,它指定了原图和输出图的线性混合。最小值为0,最大值为1,默认值为1。
4. 获取输出图像,而此时并有绘制图像。
代码到这里只是请求了一个输出图像而并没有绘制图像,下图展示了如果你在这时就绘制图像(在处理了色调和变暗后)的话图像将会显示成什么样子。
凹凸变形滤镜(CIBumpDistortion)在原图中一个指定的点创建一个凸起效果。下面的代码将展示如何创建、设置以及将其应用在之前的滤镜(变暗滤镜)的输出图像上。凹凸变形需要三个参数:指定效果的位置,效果的半径以及输入比例。
CIFilter *bumpDistortion = [CIFilter filterWithName:@"CIBumpDistortion"]; //1 [bumpDistortion setDefaults]; //2 [bumpDistortion setValue: result forKey: kCIInputImageKey]; [bumpDistortion setValue: [CIVector vectorWithX:200 Y:150] forKey: kCIInputCenterKey]; //3 [bumpDistortion setValue: @100.0f forKey: kCIInputRadiusKey]; //4 [bumpDistortion setValue: @3.0f forKey: kCIInputScaleKey]; //5 result = [bumpDistortion valueForKey: kCIOutputImageKey];
- 通过滤镜名称创建滤镜
- 同样的需要在OS X中设置为默认值(iOS中则不需要)
- 设置效果的中心点在图像中的位置
- 设置凹凸半径为100像素
- 设置输入比例为3。输入比例指定了效果的方向和数量。默认值为-0.5。取值范围是-10.0到10.0。设置为0则表示没有效果。负值创建一个向外凸起的效果,正值则创建向内凸起的效果。
最终的渲染图像:
使用转场效果
转场效果通常被用于幻灯片的切换或者在视频中从一个场景切换到另一个场景。这种效果是随着时间的推移而渲染的,所以你要设置一个计时器。接下来要讲的是如何设置计时器,你将会学会如何设置拷贝机(copy machine)转场滤镜(CICopyMachine)并将其应用于两张图片。拷贝机转场将创建一根光条,就像你在拷贝机或者图像扫描器上看到的那样。这根光条在源图像上从左扫到右,扫过的地方将变成目标图像。下图展示了从一个滑雪靴到滑雪者的转场中,这个滤镜在作用前、作用时和作用后看起来是怎样的。
你需要通过以下步骤来实现一个转场滤镜的效果:
1. 创建一个CIImage对象用来转场。
2. 设置并调度一个计时器。
3. 创建一个CIContext对象。
4. 创建一个CIFilter对象,将用来应用于图像。
5. 在OS X中,需要设置滤镜的默认值。
6. 设置滤镜参数。
7. 设置源图像和目标图像。
8. 计算时间。
9. 应用滤镜。
10. 绘制结果。
11. 重复8-10步直到转场完成。
你会发现这些步骤中的好几步都跟用一个普通滤镜处理图像一样。不同之处在于使用了计时器来在整个转场的过程中重复地绘制效果。
在下面的代码中,我们将封装一个UIView,在他的initWithFrame:方法里面获取两张图片(boots.png和skier.png)然后把它们设置为源图像和目标图像。使用了一个计时器,它每1/30秒重复一次。thumbnailWidth和thumbnailHeight两个变量用来限制被渲染的图像是如何显示到视图上的。
这个例子在官方例子的基础上作了修改,因为官方的例子是基于OS X的CoreImage方法来呈现的,一些方法在iOS中并不存在。
@interface CICopyMachineView (){ CGFloat thumbnailWidth; CGFloat thumbnailHeight; NSTimeInterval base; CIFilter * transition; CIContext * context;}@property (nonatomic, strong) CIImage * sourceImage;@property (nonatomic, strong) CIImage * targetImage;@end@implementation CICopyMachineView- (instancetype)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { NSTimer *timer; NSURL *url; thumbnailWidth = CGRectGetWidth(frame); thumbnailHeight = CGRectGetHeight(frame); url = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource: @"boots" ofType: @"png"]]; [self setSourceImage: [CIImage imageWithContentsOfURL: url]]; url = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource: @"skier" ofType: @"png"]]; [self setTargetImage: [CIImage imageWithContentsOfURL: url]]; timer = [NSTimer scheduledTimerWithTimeInterval: 1.0/30.0 target: self selector: @selector(timerFired:) userInfo: nil repeats: YES]; base = [NSDate timeIntervalSinceReferenceDate]; [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] addTimer: timer forMode: UITrackingRunLoopMode]; } return self;}
创建一个转场滤镜就和创建普通滤镜一样,使用filterWithName:方法来创建滤镜。然后它会自动调用setDefaults来对所有输入参数进行初始化。按例使用thumbnail变量来指定效果的中心。在这个例子中效果的中心被设置到了图像的中心上,而效果的中心被设置为图像的中心并不是必须的。
- (void)setupTransition{ CGFloat w = thumbnailWidth; CGFloat h = thumbnailHeight; CIVector *extent = [CIVector vectorWithX: 0 Y: 0 Z: w W: h]; transition = [CIFilter filterWithName: @"CICopyMachineTransition"]; // Set defaults on OS X; not necessary on iOS. [transition setDefaults]; [transition setValue: extent forKey: kCIInputExtentKey];}
timerFired:方法将在每次计时器触发时刻到来时被回调。在这个方法中我们将获取一个矩形,它的宽高和视图宽高相等。接下来我们要设置一个渲染时刻。如果我们的CIContext对象还没有被创建,我们就创建一个(懒加载)。同样的,如果我们的转场滤镜还没有被创建,我们就创建一个。最后我们获取渲染后的输出图像,它是一个CGImageRef(指向CGImage结构体的指针),我们可以用它来渲染视图图层的内容,这样就能加以显示了。imageForTransition:方法每当渲染时刻到来时会被调用,它将滤镜应用于图像,并返回作用后的图像。
- (void)timerFired:(NSTimer *)timer{ CGRect cg = self.bounds; CGFloat t = 0.4 * ([NSDate timeIntervalSinceReferenceDate] - base); if (context == nil) { context = [CIContext contextWithOptions:nil]; } if (transition == nil) { [self setupTransition]; } CIImage * image = [self imageForTransition: t + 0.1]; CGImageRef cgImage = [context createCGImage:image fromRect:cg]; self.layer.contents = (__bridge id)cgImage; CGImageRelease(cgImage);}
imageForTransition:方法将基于当前渲染时刻指出谁才是真正的源图像谁才是真正的目标图像,这样我们就能让拷贝机的扫描效果循环地往返出现。如果你不想要循环效果,那么就删掉if-else代码块。
- (CIImage *)imageForTransition: (float)t{ // Remove the if-else construct if you don't want the transition to loop if (fmodf(t, 2.0) < 1.0f) { [transition setValue: _sourceImage forKey: kCIInputImageKey]; [transition setValue: _targetImage forKey: kCIInputTargetImageKey]; } else { [transition setValue: _targetImage forKey: kCIInputImageKey]; [transition setValue: _sourceImage forKey: kCIInputTargetImageKey]; } [transition setValue: @( 0.5 * (1 - cos(fmodf(t, 1.0f) * M_PI)) ) forKey: kCIInputTimeKey]; CIFilter *crop = [CIFilter filterWithName: @"CICrop" keysAndValues: kCIInputImageKey, [transition valueForKey: kCIOutputImageKey], @"inputRectangle", [CIVector vectorWithX: 0 Y: 0 Z: thumbnailWidth W: thumbnailHeight], nil]; return [crop valueForKey: kCIOutputImageKey];}
最后我们在一个controller中调用这个view,就能看到炫酷的效果了。如果使用模拟器运行出现了一些卡帧,不用惊慌,那是因为我们的模拟器并没有被配置GPU,它只能使用CPU进行渲染,这会导致CPU占用率颇高,而我们的真机拥有GPU,可以相当高效地进行渲染。
#import "CICopyMachineViewController.h"#import "CICopyMachineView.h"@implementation CICopyMachineViewController- (void)viewDidLoad { [super viewDidLoad]; CICopyMachineView * view = [[CICopyMachineView alloc] initWithFrame:CGRectMake(100, 100, 276, 208)]; [self.view addSubview:view]; self.view.backgroundColor = [UIColor whiteColor];}@end
图像中的人脸检测
CoreImage能够分析并发现图像中的人脸。它只进行人脸检测,而不是人脸识别。人脸检测是识别含有人脸特征的矩形,而人脸识别则是特定人脸的识别(这张脸属于谢耳朵,莱昂纳德等)。在CoreImage检测到人脸后,它可以提供脸部特征的信息,比如眼睛和嘴巴的位置。它还可以在视频中追踪一张指定的脸的位置。
知道一张图像里面的脸在哪些位置能让你进行一些其他的操作,如剪裁或调整人脸图像的图像品质(色调平衡,红眼校正等)。你也可以进行其他有趣的操作,比如:
· 匿名人脸滤镜配方,它展现了如何将一个像素化滤镜仅作用于图像中的人脸。
· 白晕人脸配方,它展现了如何在人脸周围加点光晕。
人脸检测
在下面的代码中,我们将使用CIDetector类来找到图像中的人脸。
CIContext *context = [CIContext contextWithOptions:nil]; //1 NSDictionary *opts = @{ CIDetectorAccuracy : CIDetectorAccuracyHigh }; //2 CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace context:context options:opts]; //3 opts = @{ CIDetectorImageOrientation : [[myImage properties] valueForKey:CIDetectorImageOrientation] }; // 4NSArray *features = [detector featuresInImage:myImage options:opts]; //5
- 创建一个上下文。当你创建一个检测器的时候可以传一个nil的上下文。
- 创建一个选项字典,它用来指定检测器的精度。你可以指定低精度或者高精度。低精度(CIDetectorAccuracyLow)执行得更快,而这个例子中的高精度将会比较慢。
- 创建一个人脸检测器。你唯一能创建的识别器类型就是人脸检测。
- 找到人脸,我们需要设置一个选项字典。让CoreImage知道图像的转向是相当重要的,如此一来检测器就知道它能在哪找到摆正的脸。大多数情况你都需要从图像中获取图像的转向,然后将它作为选项字典的值。
- 使用检测器来发现图像中的特征,该图像必须是一个CIImage对象。CoreImage将会返回一个CIFeature对象的数组,每个元素都代表了图像中的一张脸。
在你获得人脸的数组后,你就可以从中找到这些脸的特征,比如它们的眼睛和嘴巴在哪里。接下来我们就来看看如何读取人脸的各种特征。
获取人脸和人脸的特征所在的矩形界限
人脸特征包括:
· 左眼和右眼的位置。
· 嘴巴的位置
· 追踪ID和追踪边框数,CoreImage使用它们来在视频段落中追踪一张人脸。
当你通过CIDetector对象获取一个人脸特征的数组后,你就可以遍历出脸及脸部特征所在的边框。
for (CIFaceFeature *f in features) { NSLog(@"%@",NSStringFromCGRect(f.bounds)); if (f.hasLeftEyePosition) NSLog(@"Left eye %g %g", f.leftEyePosition.x, f.leftEyePosition.y); if (f.hasRightEyePosition) NSLog(@"Right eye %g %g", f.rightEyePosition.x, f.rightEyePosition.y); if (f.hasMouthPosition) NSLog(@"Mouth %g %g", f.mouthPosition.x, f.mouthPosition.y);}
自动图像增强
CoreImage的自动增强特征分析了图像的直方图,人脸区域内容和元数据属性。接下来它将返回一个CIFilter对象的数组,每个CIFilter的输入参数已经被设置好了,这些设置能够自动去改善被分析的图像。
自动图像增强滤镜
下表列出了CoreImage用作自动图像增强的滤镜。这些滤镜将会解决在照片中被发现的那些常见问题。
使用自动增强滤镜
自动增强API只有两个方法:autoAdjustmentFilters以及autoAdjustmentFiltersWithOptions:。在大多数情况下,你会使用那个提供了选项字典的方法。
你可以设置这些选项:
· 图像转向,这对于CIRedEyeCorrection和CIFaceBalance滤镜来说相当关键,设置了图像转向就能让CoreImage正确的找到人脸。
· 是否只应用红眼校正。(设置kCIImageAutoAdjustEnhance为false)
· 是否应用除了红眼校正以外的所有滤镜。(设置kCIImageAutoAdjustRedEye为false)。
autoAdjustmentFiltersWithOptions:方法返回选项滤镜的数组,你将会把这些滤镜做成一个滤镜链,然后应用于被分析的图像。下面的代码首先创建了一个选项字典,然后获取图像的转向并将它设置为CIDetectorImageOrientation键的值。
CIImage * myImage = [CIImage imageWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"tbbt" ofType:@"png"]]]; id orientationProperty = [[myImage properties] valueForKey:(__bridge id)kCGImagePropertyOrientation]; NSDictionary *options = nil; if (orientationProperty) { options = @{CIDetectorImageOrientation : orientationProperty}; } NSArray *adjustments = [myImage autoAdjustmentFiltersWithOptions:options]; for (CIFilter *filter in adjustments) { [filter setValue:myImage forKey:kCIInputImageKey]; myImage = filter.outputImage;}
记住,输入参数的值已经被CoreImage设置好了,这样就能产生最佳结果。
你不必马上就去应用这些滤镜进行渲染,你可以把滤镜名称和参数值先存起来待会用。把这些参数保存起来稍后执行将会消除再次分析图像的成本。
总结
在这一章节中我们介绍了几种CoreImage的进阶技巧,我们整个专题的demo全部放到了这里:
github link for demo
在下一章节中,我们将基于上面的一些技巧来看看如何通过子类化一个CIFilter对象来封装我们的自定义滤镜。
- iOS CoreImage专题(二) —— 进阶
- iOS CoreImage专题(一)—— 概述
- iOS CoreImage专题(三)—— 自定义滤镜
- iOS开发框架篇—CoreImage
- iOS:CoreImage
- iOS CoreImage
- 【iOS】CoreImage原生二维码生成(二)一个方法生成带logo的二维码
- 二维码生成(利用iOS CoreImage框架)
- 【iOS】CoreImage原生二维码生成(一)
- IOS 中的CoreImage框架(framework)
- iOS 中的CoreImage框架(framework)
- CoreImage的初步学习—
- iOS CoreAnimation专题——原理篇(二) UIView block动画实现原理
- iOS中的CoreImage
- iOS CoreImage学习
- iOS CoreImage学习
- iOS中的CoreImage
- iOS 的CoreImage
- VC 用发音函数Beep()播放简谱音乐
- 开源控件ExpandableTextView的使用
- 【LeetCode】13Roman to Integer
- 使用android-async-http下载图片时出现org.apache.http.client.HttpResponseException: Content-Type not allowed的错误
- android 按原始数据读出资源
- iOS CoreImage专题(二) —— 进阶
- 简单的使用GitHub,代码管理
- MIT算法导论-第六讲-顺序统计问题
- initWithNibName、initWithCoder、awakeFromNib、initWithNibName、loadNibNamed
- 表单中Readonly和Disabled的区别
- 显示通知小红点
- 图像卷积的理解
- 安卓开发关于手柄外设控制程序
- duilib各种布局的作用,相对布局与绝对布局的的意义与用法