你的浏览器禁用了JavaScript, 请开启后刷新浏览器获得更好的体验!
首页
热门
推荐
精选
登录
|
注册
iOS CoreImage之滤镜简单使用
立即下载
用AI写一个
该例子支持:好用才打赏哦
现在下载学习
发布时间:2017-09-07
29人
|
浏览:6125次
|
收藏
|
分享
技术:ios
运行环境:ios8+
概述
Core Image是一个很强大的框架。它可以让你简单地应用各种滤镜来处理图像,比如修改鲜艳程度, 色泽, 或者曝光。
详细
>老骥伏枥,志在千里 #####前记 最近一直在研究图像处理方面,既上一篇[iOS Quart2D绘图之UIImage简单使用](http://www.demodashi.com/demo/11609.html)后,就一直在学习关于`CoreImage`图像滤镜处理。中间也看了不少文章,也得到了不少帮助,下面就结合这些知识和我自己的认识,记录一下,方便自己,方便他人 * 作用:对图像进行滤镜操作,比如模糊、颜色改变、锐化、人脸识别等。 * `Core Graphics`对比:基于`Quartz 2D`绘图引擎的绘图`API`,通过它可以进行绘图功能、常用的剪切裁剪合成等。 ######简介 `Core Image`是一个很强大的框架。它可以让你简单地应用各种滤镜来处理图像,比如修改鲜艳程度, 色泽, 或者曝光。 它利用`GPU`(或者`CPU`)来非常快速、甚至实时地处理图像数据和视频的帧。并且隐藏了底层图形处理的所有细节,通过提供的API就能简单的使用了,无须关心`OpenGL`或者`OpenGL ES`是如何充分利用`GPU`的能力的,也不需要你知道`GCD`在其中发挥了怎样的作用,`Core Image`处理了全部的细节 ![大概方式.png](/contentImages/image/jianshu/2525768-c7902222e7ac3d2d.png) ######实现方式 `Core Image`滤镜需要一副输入图像(生成图像的滤镜除外)以及一些定制滤镜行为的参数。被请求时,`Core Image`将滤镜应用于输入图像,并提供一副输出图像。在应用滤镜方面,`Core Image`的效率极高:仅当输出图像被请求时才应用滤镜,而不是在指定时就应用它们;另外,`Core Image`尽可能将滤镜合并,以最大限度地减少应用滤镜的计算量。 ######涉及API * `CIImage` :这是一个模型对象,它保存能构建图像的数据,可以是图像的`Data`,可以是一个文件,也可以是`CIFilter`输出的对象。 * `CIContext` :上下文,是框架真正工作的地方,它需要分配必要的内存,并编译和运行滤镜内核来执行图像处理。建立一个上下文是非常昂贵的,所以你会经常想创建一个反复使用的上下文。 * `CIFilter` :滤镜对象,主要是对图像进行处理的类。通过设置一些键值来控制滤镜的具体效果 ****** 注:`Core Image`和`Core Graphics`使用的是左下原点坐标 到此有一个疑问?就是苹果怎么会弄出这么多`image`,比如`CIImage `、`UIImage`、`CGImageRef`,有什么区别呢?为了弄清这个问题,我也特别搜寻了一番,下面也记录一下 ①`UIImage`:管理图片数据,主要用来展现,`Image`对象并没有提供直接访问相关的图片数据的操作, 因此你总是通过已经存在的图片数据来创建它 ②`CGImage`:是基于像素的矩阵,每个点都对应了图片中点的像素信息 ③`CIImage `:包含了创建图片的所有必要的数据,但其本身没有渲染成图片,它代表的是图像数据或者生成图像数据的流程(如滤镜)。拥有与之关联的图片数据, 但本质上并不是一张图片,你可以`CIImage`对象作为一个图片的"配方"。`CIImage`对象拥有生成一张图片所具备的所有信息,但`Core Image`并不会真正的去渲染一张图片, 除非被要求这么做。 ******* ######使用方式 - 1 . ` CIImage`创建,在使用滤镜之前,你必须要先有一个`CIImage`对象,在拥有该对象后结合`CIFilter `才能实现我们的滤镜效果。这里需要注意的是,如果直接使用`image.cIImage`,那么很遗憾的告诉你,你将得到一个`nil`,哈哈 如下: ![image.CIImage.png](/contentImages/image/jianshu/2525768-6e86864a2a7eed2d.png) 原因在`UIImage`的`API`中有介绍` // returns underlying CIImage or nil if CGImageRef based`,应该是说图片可能不是基于`CIImage`而创建的 正确的方式为 ``` //得到CIImage CIImage *inputCIImage = [[CIImage alloc] initWithImage:_image]; ``` - 2 . `CIFilter`创建方式大概有下面三种 ``` +(nullable CIFilter *) filterWithName:(NSString *) name +(nullable CIFilter *)filterWithName:(NSString *)name keysAndValues:key0, ... NS_REQUIRES_NIL_TERMINATION NS_SWIFT_UNAVAILABLE(""); +(nullable CIFilter *)filterWithName:(NSString *)name withInputParameters:(nullable NSDictionary
*)params NS_AVAILABLE(10_10, 8_0); ``` 方法上都差不多,只是后面两个在初始化的时候加入了一些键值,在`API`文档中,可以查到很多键值,这里需要说明下,键值`kCIInputImageKey`是我们必须要设置的,这是为我们的滤镜对象设置输入图像,图像的值为`CIImage`对象,方法如下 ``` [_filter setValue:inputCIImage forKey:kCIInputImageKey]; ``` 方法中的`name`就是我们需要用的滤镜效果,具体效果,可以在官网上面进行查询,如下 ![filter.png](/contentImages/image/jianshu/2525768-122207ff8e1f7a2f.png) 下面,我们以冲印效果为例,冲印属于`CICategoryColorEffect`中的`CIPhotoEffectProcess` ``` //创建滤镜对象 CIFilter *ciFilter = [CIFilter filterWithName:@"CIPhotoEffectProcess" keysAndValues:kCIInputImageKey,ciImage, nil]; ``` 大概效果如下 ![冲印.png](/contentImages/image/jianshu/2525768-67fa5eb8aaf2ac62.png) ******** 注意: 1、在设置键值的时候,我们需要有选择性的进行设置,具体怎么选择呢? 比如上面的冲印效果,在官方文档是这么展示的 ![冲印展示.png](/contentImages/image/jianshu/2525768-9a6508f9be3a1f28.png) 只有一个必须输入的`inputImage`,因此不需要其它参数就可以实现 又比如高斯模糊`CIGaussianBlur`,在官方文档中,是这么展示的 ![高斯模糊展示.png](/contentImages/image/jianshu/2525768-81315e96710d1a9c.png) 如果我们需要控制其模糊半径,可以这么设置 ``` [ciFilter setValue:@(20.f) forKey:@"inputRadius"]; ``` 2、`CIFilter` 并不是线程安全的,这意味着 一个 `CIFilter `对象不能在多个线程间共享。如果你的操作是多线程的,每个线程都必须创建自己的 `CIFilter` 对象,而`CIContext`和`CIImage`对象都是不可修改的, 意味着它们可以在线程之间安全的共享。多个线程可以使用同样的`GPU`或者`CPU`的`CIContext`对象来渲染`CIImage`对象 在`CIFilter`类中,还有一些其他函数,可能是我们需要用到的,这里也简单说明下 ``` //输入的键值信息 NSArray
*inputKeys; //输出的键值信息 NSArray
*outputKeys; //返回滤镜的属性描述信息 NSDictionary
*attributes; //将所有输入键值的值设为默认值(曾经乱用,导致我的滤镜效果完全没有任何反应,差点怀疑人生...) - (void)setDefaults; //根据滤镜的key查找其下面的所以子类效果 + (NSArray
*)filterNamesInCategory:(nullable NSString *)category ``` ******* - 3 . `CIContext` 在创建结果图片的时候需要用到,刚开始用的时候,出于好奇用了两种不同的方法来返回结果,本以为....我会有一个方式获取不到处理后的结果,然而大跌眼镜,居然有.... ``` CIImage *outPutImage = [ciFilter outputImage]; //获取上下文 CIContext *context = [CIContext contextWithOptions:nil]; CGImageRef cgImage = [context createCGImage:outPutImage fromRect:outPutImage.extent]; UIImage *filter_image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); // UIImage *filter_image = [UIImage imageWithCIImage:outPutImage]; ``` 就是上面屏蔽的代码部分`imageWithCIImage`,这就使我纳闷了,于是猜测并查阅资料,原来在调用该方法的时候,其实是隐式的声明了`CIContext `,这样看来,哇!好简单,省了我一堆代码,然而,这却引起另外的问题了,就是每次都会重新创建一个 `CIContext`,然而 `CIContext `的代价是非常高的。并且,`CIContext` 和 `CIImage` 对象是不可变的,在线程之间共享这些对象是安全的。所以多个线程可以使用同一个 `GPU` 或者 `CPU` `CIContext `对象来渲染 `CIImage` 对象。所以我们不应该使用 `imageWithCIImage` 来生成` UIImage`,而应该用上述另外一种方式来获取结果图像。 ######Core Image 效率 `Core Image`在处理图像的时候,可以有两种选择`GPU`、`CPU`,在`Context `中可以对其进行设置,通过设置键值,这里的键值为`kCIContextUseSoftwareRenderer`,默认情况下,是为`GPU`处理方式,如果将其设置为`YES`,则为`CPU`处理 如下 ``` //CPU处理 CIContext *context = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]]; ``` 如果通过`GPU`的话,速度就会更快,利用了`GPU`硬件的并行优势,可以使用 `OpenGLES` 或者` Metal` 来渲染图像,这种方式`CPU`完全没有负担,应用程序的运行循环不会受到图像渲染的影响。但是也有个问题,就是如果`APP`运行到后台的时候,`GPU`就会停止处理,等回到前台的时候又继续,而如果采取`CPU`来处理的话,就不会出现这么一种情况,在前面的图中,我们可以看到`CPU`是采用`GCD`的方式来对图像进行渲染。所以在使用的时候,还是需要分情况,如果是处理复杂的操作,比如高斯模糊这样的,建议还是用`GPU`来处理,可以节省`CPU`的开销,如果在后台还需要操作的话,可以使用`CPU`来操作。 ``` // CIImage *outPutImage = [ciFilter outputImage]; //获取上下文 CIContext *context = [CIContext contextWithOptions:nil]; CGImageRef cgImage = [context createCGImage:outPutImage fromRect:outPutImage.extent]; UIImage *filter_image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); ``` 上面的这段代码是通过`GPU`的方式来处理图像,然后得到结果`UIImage `,最后再赋值给`UIImageView`。 ![1.jpg](/contentImages/image/jianshu/2525768-bfce0c969605bc45.jpg) 分析下这个过程: 1、将图像上传到`GPU`,然后进行滤镜处理 2、得到`CGImageRef cgImage`的时候,又将图像复制到了`CPU`上 3、在赋值给`UIImageView`进行显示的时候,又需要通过`GPU`处理位图数据,进行渲染 这样的话,我们就在`GPU`-`CPU`-`GPU`上循环操作,在性能上肯定是有一定的损耗的,那么为了避免这种问题,我们该这怎么办呢? 查看`API`,我们可以看到有这么一个函数 ``` + (CIContext *)contextWithEAGLContext:(EAGLContext *)eaglContext ``` `EAGLContext`:是基于`OpenGL ES`的上下文 通过上面的函数,我们通过`OpenGL ES`的上下文创建的`Core Image`的上下文就可以实时渲染了,并且渲染图像的过程始终在 `GPU `上进行,但是要显示图像,又该怎么办呢?如果还是用`UIImageView`的话,那么势必会回到`CPU`上,这里,我们可以用`GLKView`,一个属于`GLKIT`中的类,通过`GLKView`和其属性`@property (nonatomic, retain) EAGLContext *context`来将图像绘制出来,这样的话,就能保证我们的滤镜,一直在`GPU`上进行,大大的提高效率。 针对该方案,我自定义了一个类似`UIImageView`的类`FilterImageView` ``` //FilterImageView.h #import
@interface FilterImageView : GLKView @property (nonatomic,strong) UIImage *image; @property (nonatomic,strong) CIFilter *filter; @end ``` `.m`文件核心代码 ``` //FilterImageView.m - (id)initWithFrame:(CGRect)frame { EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; self = [super initWithFrame:frame context:context]; if (self) { _ciContext = [CIContext contextWithEAGLContext:context options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:kCIContextUseSoftwareRenderer]]; //超出父视图 进行剪切 self.clipsToBounds = YES; } return self; } - (void)drawRect:(CGRect)rect { if (_ciContext && _image) { //得到CIImage CIImage *inputCIImage = [[CIImage alloc] initWithImage:_image]; CGRect inRect = [self imageBoundsForContentModeWithFromRect:inputCIImage.extent toRect:CGRectMake(0, 0, self.drawableWidth, self.drawableHeight)]; if (_filter) { [_filter setValue:inputCIImage forKey:kCIInputImageKey]; //根据filter得到输出图像 if (_filter.outputImage) { //渲染开始 [_ciContext drawImage:_filter.outputImage inRect:inRect fromRect:inputCIImage.extent]; } }else{ [_ciContext drawImage:inputCIImage inRect:inRect fromRect:inputCIImage.extent]; } } } ``` #####项目文件截图: ![](/contentImages/image/20170907/xsgT4KptWNtwoHK7aqA.jpg) 如此之后,我们就能提高滤镜的效率,特别是一些复杂的。 关于滤镜,能写的就只要这么多了,在学习中,也确实发现这是一个好东西,可以做很多炫酷的东西出来,为此,特意做了一个简单的[Demo],目前还未完善,希望各位勿喷。
本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
感谢
2
手机上随时阅读、收藏该文章 ?请扫下方二维码
相似例子推荐
评论
作者
敷衍丶尘世
16
例子数量
615
帮助
43
感谢
评分详细
可运行:
4.5
分
代码质量:
4.5
分
文章描述详细:
4.5
分
代码注释:
4.5
分
综合:
4.5
分
作者例子
iOS 仿支付宝密码支付
iOS 九宫格手势密码
iOS CAReplicatorLayer 简单动画
iOS 之UIBezierPath
iOS 核心动画 Core Animation浅谈
iOS CoreImage之滤镜简单使用
iOS UIButton文字和图片间距随意调整
iOS 简单引导界面
iOS 两种不同的图片无限轮播
iOSQuart2D绘图之UIImage简单使用
iOS 自定义键盘
iOS 自定义转场动画浅谈
iOS Core ML与Vision初识
iOS 11之Vision人脸检测
iOS之基于FreeStreamer的简单音乐播放器(模仿QQ音乐)
iOS 音频视频图像合成那点事