图文并茂带你走进Core Image

Core Image框架是适合图片的苹果滤镜框架,主要用处可以给图片添加滤镜效果和图像识别功能(人脸、条形码等等)。本文将会介绍逐一介绍 Core Image相关基础概念、使用方式、注意点以及和其他图像处理方案的对比。

本文干货很多,基本介绍全了Core Image的重要内容,阅读完后请点赞支持我的辛苦。

Core Image概述

Core Image is an image processing and analysis technology designed to provide near real-time processing for still and video images. It operates on image data types from the Core Graphics, Core Video, and Image I/O frameworks, using either a GPU or CPU rendering path. Core Image hides the details of low-level graphics processing by providing an easy-to-use application programming interface (API). You don’t need to know the details of OpenGL or OpenGL ES to leverage the power of the GPU, nor do you need to know anything about Grand Central Dispatch (GCD) to get the benefit of multicore processing. Core Image handles the details for you.

这是苹果官方文档对于 Core Image 的介绍,大致意思是:Core Image 是一种为静态图像和 Video 提供处理和分析的技术,它可以使用 GPU/CPU 的方式对图像进行处理。Core Image 提供了简洁的 API 给用户,隐藏了图像处理中复杂的底层内容。你可以在不了解 OpenGL、OpenGL ES 甚至是 GCD 的基础上对其进行使用,他已经帮你对这些复杂的内容进行处理了。废话这么多,苹果就想告诉我们一件事: 所有的底层细节他都帮你做好了,你只需要放心调用API就行了。

Core Image的知识点我以图的形式归纳,如下:


Core Image.png

Core Image的工作原理:


工作原理.png

CIFilter滤镜效果使用

这一部分会结合代码,开发环境swift5.0。

CIFilter是通过操纵一个或多个输入图像或生成新的图像数据来合成图像的图像处理器。简单来说,CIFilter接收一个或多个图像作为输入源,通过键值对设置和检索CIFilter对象的参数,生成一个CIImage对象作为输出源。

CIFilter父类是NSObject,是使用CoreImage进行滤镜效果的核心类。当然,你也可以子类化CIFilter来实现自定义过滤效果,具体方案为:

  • 链接两个或多个内置核心图像过滤器,实现滤镜链
  • 自己编写的映像处理内核
    参考官方文档自定义滤镜
    无论你的子类是通过上面的那种方法,你都应该:
  • 将任何输入参数声明为属性,其名称前缀为input,例如inputImage。
  • 覆盖setDefaults()方法,为已声明的任何输入参数提供默认值。
  • 实现一个outputImage方法来创建一个具有过滤器效果的新CIImage。

CIFilter重要属性

/*
输出
*/
@available(iOS 5.0, *)
    open var outputImage: CIImage? { get }

    
    /* The name of the filter. On OSX and iOS 10, this property is read-write.
     * This can be useful when using CIFilters with CoreAnimation or SceneKit.
     * For example, to set an attribute of a filter attached to a layer,
     * a unique path such as "filters.myExposureFilter.inputEV" could be used.
     * CALayer animations may also access filter attributes via key-paths. */
/*
滤镜名
*/
    open var name: String

/*
滤镜输入参数
*/ 
    /** Returns an array containing the names of all inputs in the filter. */
    open var inputKeys: [String] { get }

 /*
滤镜输出参数
*/  
    /** Returns an array containing the names of all outputs in the filter. */
    open var outputKeys: [String] { get }

 /*
*/  
    /** Sets all inputs to their default values (where default values are defined, other inputs are left as-is). */
    open func setDefaults()

/*
滤镜支持的关键字
*/   
    /** Returns a dictionary containing key/value pairs describing the filter. (see description of keys below) */
    open var attributes: [String : Any] { get }

CIFilter滤镜效果

目前系统API提供的滤镜效果分类分为21种,每种滤镜效果对照苹果官方文档,大概做了些翻译:

/* Categories */
/*失真效果,改变几何形状创建3D效果
比如bump、旋转、hole
*/
public let kCICategoryDistortionEffect: String
/*
扭曲图片和纠正源图像问题,例如仿射变换来校正相对于地平线旋转的图像
比如仿射变换、平切、透视转换
*/
public let kCICategoryGeometryAdjustment: String
/*
合成滤镜,操作两个图像源
比如源覆盖(source over)、最小化、源在顶(source atop)、色彩混合模式
*/
public let kCICategoryCompositeOperation: String
/*
半色调效果
比如screen、line screen、hatched
*/
public let kCICategoryHalftoneEffect: String
/*
色彩调整,用于消除色彩偏移、校正亮度和对比度
比如伽马调整、白点调整、曝光
*/
public let kCICategoryColorAdjustment: String
/*
色彩效果,我们一般用的比较多,类似美图工具的滤镜效果
比如色调调整、posterize
*/
public let kCICategoryColorEffect: String
/*
图像间转换
比如dissolve、disintegrate with mask、swipe
*/
public let kCICategoryTransition: String
/*
瓦片效果 平铺图片
比如parallelogram、triangle
*/
public let kCICategoryTileEffect: String
/*
图案的过滤器,如纯色、棋盘或星星的光泽。生成的输出通常用作对另一个过滤器的输入。
比如stripes、constant color、checkerboard
*/
public let kCICategoryGenerator: String
@available(iOS 5.0, *)
/*
减少图像数据 解决图像分析问题
*/
public let kCICategoryReduction: String
/*
渐变效果
比如轴向渐变、仿射渐变、高斯渐变
*/
public let kCICategoryGradient: String
/*
风格化
比如像素化、水晶化
*/
public let kCICategoryStylize: String
/*
锐化图像 锐化掩模和提高亮度
*/
public let kCICategorySharpen: String
/*
柔滑图像,主要用于模糊图像
比如高斯模糊、焦点模糊、运动模糊
*/
public let kCICategoryBlur: String
/*
处理视频图像
*/
public let kCICategoryVideo: String
/*
处理静态图像
*/
public let kCICategoryStillImage: String
/*
处理交错图像
*/
public let kCICategoryInterlaced: String
/*
处理非方形图像
*/
public let kCICategoryNonSquarePixels: String
/*
处理高动态图像
*/
public let kCICategoryHighDynamicRange: String
/*
用于区分built-in filters  plug-in filters.
*/
public let kCICategoryBuiltIn: String
@available(iOS 9.0, *)
/*
链接几个过滤器
*/
public let kCICategoryFilterGenerator: String

每个效果分类又有很多具体的子分类,苹果号称提供180种滤镜效果,你可以通过代码获取子分类名字,以及每个分类对应支持的键值,以kCICategoryDistortionEffect为例:

//获取kCICategoryDistortionEffect类型所有滤镜的名字和属性设置
        let filterArr = CIFilter.filterNames(inCategory: kCICategoryDistortionEffect)
        filterArr.forEach { (filterName) in
            let filter = CIFilter(name: filterName)
            let attributes = filter?.attributes
            
            print("------\nfilter name is:\n\(filterName) \nfilter attributes is :\n\(String(describing: attributes))\n\n")
        }

获取到的结果为:

------
filter name is:
CIBumpDistortion 
filter attributes is :
Optional(["CIAttributeFilterAvailable_iOS": 6, "CIAttributeFilterCategories": <__NSArrayI 0x1c44457f0>(
CICategoryDistortionEffect,
CICategoryVideo,
CICategoryStillImage,
CICategoryBuiltIn
)
, "CIAttributeFilterDisplayName": Bump Distortion, "inputImage": {
    CIAttributeClass = CIImage;
    CIAttributeDescription = "The image to use as an input image. For filters that also use a background image, this is the foreground image.";
    CIAttributeDisplayName = Image;
    CIAttributeType = CIAttributeTypeImage;
}, "inputScale": {
    CIAttributeClass = NSNumber;
    CIAttributeDefault = "0.5";
    CIAttributeDescription = "The scale of the effect determines the curvature of the bump. A value of 0.0 has no effect. Positive values create an outward bump; negative values create an inward bump.";
    CIAttributeDisplayName = Scale;
    CIAttributeIdentity = 0;
    CIAttributeSliderMax = 1;
    CIAttributeSliderMin = "-1";
    CIAttributeType = CIAttributeTypeScalar;
}, "CIAttributeFilterName": CIBumpDistortion, "CIAttributeFilterAvailable_Mac": 10.4, "inputRadius": {
    CIAttributeClass = NSNumber;
    CIAttributeDefault = 300;
    CIAttributeDescription = "The radius determines how many pixels are used to create the distortion. The larger the radius, the wider the extent of the distortion.";
    CIAttributeDisplayName = Radius;
    CIAttributeMin = 0;
    CIAttributeSliderMax = 600;
    CIAttributeSliderMin = 0;
    CIAttributeType = CIAttributeTypeDistance;
}, "CIAttributeReferenceDocumentation": http://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBumpDistortion, "inputCenter": {
    CIAttributeClass = CIVector;
    CIAttributeDefault = "[150 150]";
    CIAttributeDescription = "The center of the effect as x and y coordinates.";
    CIAttributeDisplayName = Center;
    CIAttributeType = CIAttributeTypePosition;
}])

------------------------
太多,没有完全列举,请自行打印
------------------------

CIFilter设置关键字

@available(iOS 5.0, *)
public let kCIOutputImageKey: String
@available(iOS 5.0, *)
public let kCIInputBackgroundImageKey: String
@available(iOS 5.0, *)
public let kCIInputImageKey: String
@available(iOS 11.0, *)
public let kCIInputDepthImageKey: String
@available(iOS 11.0, *)
public let kCIInputDisparityImageKey: String
@available(iOS 12.0, *)
public let kCIInputAmountKey: String
@available(iOS 7.0, *)
public let kCIInputTimeKey: String
@available(iOS 7.0, *)
public let kCIInputTransformKey: String
@available(iOS 7.0, *)
public let kCIInputScaleKey: String
@available(iOS 7.0, *)
public let kCIInputAspectRatioKey: String
@available(iOS 7.0, *)
public let kCIInputCenterKey: String
@available(iOS 7.0, *)
public let kCIInputRadiusKey: String
@available(iOS 7.0, *)
public let kCIInputAngleKey: String
@available(iOS 9.0, *)
public let kCIInputRefractionKey: String
@available(iOS 7.0, *)
public let kCIInputWidthKey: String
@available(iOS 7.0, *)
public let kCIInputSharpnessKey: String
@available(iOS 7.0, *)
public let kCIInputIntensityKey: String
@available(iOS 7.0, *)
public let kCIInputEVKey: String
@available(iOS 7.0, *)
public let kCIInputSaturationKey: String
@available(iOS 7.0, *)
public let kCIInputColorKey: String
@available(iOS 7.0, *)
public let kCIInputBrightnessKey: String
@available(iOS 7.0, *)
public let kCIInputContrastKey: String
@available(iOS 9.0, *)
public let kCIInputBiasKey: String
@available(iOS 9.0, *)
public let kCIInputWeightsKey: String
@available(iOS 9.0, *)
public let kCIInputGradientImageKey: String
@available(iOS 7.0, *)
public let kCIInputMaskImageKey: String
@available(iOS 12.0, *)
public let kCIInputMatteImageKey: String
@available(iOS 9.0, *)
public let kCIInputShadingImageKey: String
@available(iOS 7.0, *)
public let kCIInputTargetImageKey: String
@available(iOS 7.0, *)
public let kCIInputExtentKey: String
@available(iOS 6.0, *)
public let kCIInputVersionKey: String

CIFilter使用方法

  • 创建CIImage,处理前的图片
  • 通过名字创建CIFilter滤镜
  • 用KVC给CIFilter设置滤镜参数
  • 渲染并输出CIImage,处理后的图片
  • 创建CIContext上下文
  • 初始化CGImageRef对象,创建输出CGImage,赋给UIImage对象
func imageFilter(with image: UIImage, filterName: String, inputValue: [String: Any]) -> UIImage? {
    //将UIImage转换成CIImage,处理前的图片
    guard let input = CIImage(image:image) else {
        return nil
    }
    
    //通过名字创建CIFilter滤镜
    let filter = CIFilter(name: filterName, parameters: [kCIInputImageKey: input])
    
    // 用KVC给CIFilter设置滤镜参数,可直接在上面的初始化方法中加到parameters
    filter?.setValue(input, forKey: kCIInputImageKey)
    
    //渲染并输出CIImage,处理后的图片
    guard let output = filter?.outputImage else {
        return nil
    }
    
    //创建CIContext上下文
    let context = CIContext(options: nil)
    
    //初始化CGImageRef对象,创建输出CGImage,赋给UIImage对象
    //注意,我们使用输入图像大小的原因是,输出图像通常和输入图像具有不同的尺寸比。例如,一个模糊图像由于采样超出了输入图像的边缘,围绕在其边界外还会有一些额外的像素。
    guard let cgImage = context.createCGImage(output, from: input.extent) else {
        return nil
    }

    let uiImage = UIImage(cgImage: cgImage)
    
    
    return uiImage
}

当然,封装的方法你需要将KVC的关键字和值都通过外面传入,例如:

if let image = UIImage(named: "girl") {
        let inputValue = ["inputRadius": 10.0]
        let output = imageFilter(with: image, filterName: "CIColorMonochrome", inputValue: inputValue)
}

注意:
Context 创建的时候,我们可以给它设置为是基于 GPU 还是 CPU。
基于 GPU 的话,处理速度更快,因为利用了 GPU 硬件的并行优势。可以使用 OpenGLES 或者 Metal 来渲染图像,这种方式CPU完全没有负担,应用程序的运行循环不会受到图像渲染的影响。
但是 GPU 受限于硬件纹理尺寸,而且如果你的程序在后台继续处理和保存图片的话,那么需要使用 CPU,因为当 App 切换到后台状态时 GPU 处理会被打断。使用 CPU 渲染的 iOS 会采用 GCD 来对图像进行渲染,这保证了 CPU 渲染在大部分情况下更可靠,比 GPU 渲染更容易使用,可以在后台实现渲染过程。综上,对于复杂的图像滤镜使用 GPU 更好,但是如果在处理视频并保存文件,或保存照片到照片库中时,为避免程序进入后台对图片保存造成影响,这时应该使用 CPU 进行渲染。
用 Apple 官方的一句话来描述再合适不过了:
CPU is still what will give you the best fidelity where as the GPU will give you the best performance.

具体的设置方式,可以参考下面的例子:

// 创建基于 CPU 的 CIContext 对象 (默认是基于 GPU,CPU 需要额外设置参数)
context = [CIContext contextWithOptions: [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]];

// 创建基于 GPU 的 CIContext 对象
context = [CIContext contextWithOptions: nil];

// 创建基于 GPU 的 CIContext 对象
EAGLContext *eaglctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
context = [CIContext contextWithEAGLContext:eaglctx];

同样是基于 GPU 的,它们之间也是有区别的。
contextWithOptions 创建的 context 并没有实时性能, 虽然渲染是在 GPU 上执行,但是其输出的 image 是不能显示的,只有当其被复制回 CPU 存储器上时,才会被转成一个可被显示的 image 类型,比如 UIImage。当使用 Core Image 在 GPU 上渲染图片的时候,先是把图像传递到 GPU 上,然后执行滤镜相关操作。但是当需要生成 CGImage 对象的时候,图像又被复制回 CPU 上。最后要在视图上显示的时候,又返回 GPU 进行渲染。这样在 GPU 和 CPU 之前来回切换,会造成很严重的性能损耗。创建 context,那么它内部的渲染器会根据设备最优选择。依次为 Metal,OpenGLES,CoreGraphics。
它的渲染过程大致如下:


contextWithOptions.png

contextWithEAGLContext 创建的 context 支持实时渲染,渲染图像的过程始终在 GPU 上进行,并且永远不会复制回 CPU 存储器上,这就保证了更快的渲染速度和更好的性能。当然,这个前提是利用实时渲染的特效,而不是每次操作都产生一个 UIImage,然后再设置到视图上。
它的渲染过程大致如下:


contextWithEAGLContext.png

用 OpenGL 来提高性能

用 CPU 来绘制一个 CGImage 是非常耗时和浪费的,它只将结果回传给 UIKit 来做合成。我们更希望能够在屏幕上绘制应用滤镜后的图像,而不必去 Core Graphics 里绕一圈。
幸运的是,由于 OpenGL、和Metal等GPU渲染框架与Core Image 的可互操作性,我们可以这么做。
等后面介绍OpenGL、和Metal再详细讲解。

CIFilter滤镜链

是一个链接在一起的滤镜网络,使得一个滤镜的输出可以是另一个滤镜的输入。以这种方式,可以实现精心制作的效果。

自定义 CIFilter, Core Image 的可扩展性

iOS8 之后更是支持自定义 CIFilter,可以定制满足业务需求的复杂效果。
我会单独拿一章出来介绍自定义 CIFilter

滤镜图表

(本篇不是重点)
你可以构建一个滤镜图表原型查看效果:实例化我们需要的滤镜,设置它们的参数,把它们连接起来以便该图像数据按顺序传过每个滤镜。
一旦达到了我们满意的效果,我们可以重新在代码里还原滤镜图表效果。

两篇很好的文章,介绍了Quartz Composer构建滤镜图表原型

CIDetector

Core Image框架中提供的一个识别类,包括对人脸、形状、条码、文本的识别。

  • 人脸识别功能
    对人脸进行获取,可以获取眼睛和嘴等面部特征信息。但是CIDetector不包括面纹编码提取,也就是说CIDetector只能判断是不是人脸,而不能判断这张人脸是谁的,比如说面部打卡这种功能是实现不了的。

Demo

**Demo地址:后期会整理到一起发布,期待中

鸣谢

以下是参考文献

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

推荐阅读更多精彩内容