WWDC 2018:Core Image - 更强的性能并支持基于 Python 快速开发

Session 719: Core Image: Performance, Prototyping, and Pythonhtml

相信绝大多数 iOS 开发者对 Core Image 都不陌生,做为系统标配的、异常强大的图像处理库,在绝大多数场景下都能知足 App 的图像处理需求。并且,目前 Core Image 已经支持在 iOS 上作自定义 filter,很有赶超 GPUImage 的态势(GPUImage 是目前 iOS 作图像处理事实上的标杆)。加上 iOS12 苹果打算 deprecate OpenGL 和 OpenGL ES, 推广 Metal。那和 Metal 联系紧密的 Core Image 无疑更有胜算。算法

这个 Session 讲的内容主要包括三个部分:swift

  1. Core Image 新的性能 API;
  2. 在 Core Image 体系上快速搭建滤镜原型;
  3. 在 Core Image 体系上应用机器学习;

1. Core Image 新的性能 API

中间缓存

在讲中间缓存以前,须要先复习一下 Core Image。在 Core Image 中,咱们可以对图像链式的执行 Filter,以下图所示:缓存

经过 Filter 的组合,咱们能够实现一些复杂的图像处理效果。创建 Filter 链的方法能够参考以下代码( 节选自 Core Image Programming Guide):markdown

func applyFilterChain(to image: CIImage) -> CIImage {
    // The CIPhotoEffectInstant filter takes only an input image
    let colorFilter = CIFilter(name: "CIPhotoEffectProcess", withInputParameters:
        [kCIInputImageKey: image])!
    
    // Pass the result of the color filter into the Bloom filter
    // and set its parameters for a glowy effect.
    let bloomImage = colorFilter.outputImage!.applyingFilter("CIBloom",
                                                             withInputParameters: [
                                                                kCIInputRadiusKey: 10.0,
                                                                kCIInputIntensityKey: 1.0
        ])
    
    // imageByCroppingToRect is a convenience method for
    // creating the CICrop filter and accessing its outputImage.
    let cropRect = CGRect(x: 350, y: 350, width: 150, height: 150)
    let croppedImage = bloomImage.cropping(to: cropRect)
    
    return croppedImage
}
复制代码

整个过程很直观,咱们将图片喂到第一个 Filter,而后获得第一个 Filter 的 outputImage,而后再把该对象喂到第二个 Filter……以此类推创建 Filter 链。架构

Core Image 的 Lazyapp

值得注意的一点是,当上述代码执行时,图像处理并无发生,只是 Core Image 内部进行了一些关系的创建,只有当图像须要被渲染的时候,才会实际去执行各个 Filter 的图像处理过程。框架

由于有 Lazy 的特性,因此 Core Image 上最重要的一个优化就是 "自动链接(Filter Concatenation)", 由于最终图像处理的过程都发生在全部 Filter 成链以后。因此 Core Image 能够将链式的多个 Filter 合并 成一个来执行,省去没必要要的开销。以下图所示:机器学习

中间缓存ide

如今回过头来看这样一个场景:

三个 Filter,第一个计算很耗时,而第三个的参数可让用户手动调节。这意味着每次用户调节后都须要从新计算这三个 Filter。但其实前两份 Filter 的参数是不变的,也就是说前两个 Filter 的运算过程和结果都是不随着用户调整第三个 Filter 的参数改变而改变的。这里重复的计算是否有可能进行优化呢?

咱们很容易就想到,咱们只须要把前两次运算的结果 cache 下来就能够了,以下图所示:

可是上文提到,Core Image 会把 Filter 链自动合并为一个 Filter,咱们如何访问中间结果呢?

苹果在 iOS12的 Core Image 中,给 CIImage 新增了一个中间缓存的属性(insertingIntermediate), 来解决这个问题,以下图所示:

咱们但愿保存第二个 Filter 的结果,只须要在第二个 Filter 的 outputImage 调用 insertingIntermediate() 来生成一个新的 CIImage 传到后面的流程便可。这样第三个 Filter 的参数调整就不会致使前两个 Filter 的重复计算。

怎么作的呢? 其实就是自动合并的逻辑会根据 insertingIntermediate 进行调整。以下图所示:

Core Image 的 CIContext 能够设置是否要打开 cacheIntermediate, 但此次新增的 insertingIntermediate 有更高的优先级。具体一些使用上的建议能够参考下图:

Kernal 语言的新特性

两种模式

iOS 上支持自定义 Filter,自定义 Filter 使用 Kernal 语言进行开发(一种相似 GLSL 的脚本语言)。目前一共有两种开发 CIKernel 程序的模式:

第一种是传统的基于 CIKernal 开发语言进行编写,而后编译成 Metal 或者 GLSL 的方式,第二种是直接使用 Metal Shading 语言进行开发,而后在 build 期间就生成二进制的库,执行阶段 load 以后直接转换为 GPU 的指令。

目前由于苹果主推 MPS(Metal Performance Shader), 因此方式一已经被标记为 deprecated

按组读写

使用 Metal 来开发 CIKernel 的优点:

  1. 支持半精度浮点数;
  2. 支持按组读写(Group read & Group write);

半精度浮点是纯运算性质方面的优化,在 A11 芯片上运算更快,并且由于用到的寄存器小因此也有较大的优化空间。

接下来重点介绍一下按组读写。

假设咱们对左图红框像素作一个3x3的卷积运算,结果为存入右边的绿色框。显而易见,对于每一个新的像素,都须要读取输入图像9次像素值。

但若是是按组读写,以下图所示。咱们一次性读取16个像素来计算并写入右边的四个像素,那咱们整个过程当中写了4次,读取了16次。每一个新像素平均需读取的数量为4,比上述的单像素须要9次显著下降。

按组读写的原理是很简单的,接下来介绍一下若是咱们有一个以前使用 CIKernal Language 开发的 kernal,若是修改使其可以使用按组读写这样高速的优化。

假设咱们的 kernal 以下图所示:

第一步,转换为 metal:

第二步, 改造为按组读写的模式。核心就是使用了 s.gatherX 来实现。

在使用了按组读写和半浮点经典的优化后,基本均可以获得2倍的性能提高。

2. 在 Core Image 体系上快速搭建滤镜原型

通常来说,一个滤镜典型的研发流程是首先在电脑上进行快速原型的测试,以后再移植到生产环境,电脑上有大量的工具(OpenCV、SciPy、Numpy、Tensorflow 等等)来进行快速原型开发,而生产环境的技术栈倒是 Core Image, vImage,Metal等彻底不一样的技术架构栈,这每每会致使一个问题:在电脑上原型测得好好的,结果到手机上效果却扑街了。

苹果为了解决这个问题,发布了一个神器 —— PyCoreImage。

初次看到这个名词是否是感到很是穿越? 但其实很明显,就是能够在 Python 中调用 Core Image 的接口

咱们在 prototype 的时候使用 Python + PyCoreImage 这样的方式,那就最大程度的模拟了真实的运行环境,基本上移植到手机上效果也不会打折。并且最关键的是,只要学一个框架就行了啊,多的学不完啊!!!!

在使用 PyCoreImage 时,最关键是要了解 PyObjc 的用法,PyObjc 在 OS X 10.5 发布,实现了在 Python 能够调用 Objective-C 的代码,其中最主要的转换规则就是冒号变下划线,具体能够参考图中的例子。

说回 PyCoreImage,其中的原理其实大概也能够想到,以下图所示,PyCoreImage 经过 PyObjC 和 macOS 的 Core Image 进行交互,并将结果输出回 NumPy。

下图中的代码首先导入了一个图片,而后对其应用高斯模糊的 Filter,而后将结果输出到变量 bar 中。

剩下的更多关于 PyCoreImage 的用法能够参考 Session 的 ppt,这里再也不赘述。

3. 在 Core Image 体系上应用机器学习

图像处理和计算机发展至今,已经大量经过使用机器学习和深度学习来提高算法的效果。Core Image 也对机器学习提供了很是有好的支持。

CoreML Filter

Core Image 如今能够直接将图片 apply 到一个 CoreML 的模型里,相对于给 Core Image 的 Filter 连接上了深度学习的能力。

iOS12 中的 Core Image 提供了 CICoreMLModelFilter 类来将 CoreML 的 model 封装成 Core Image 可以识别的 Filter 格式。

下图是一个 ML 领域的经典应用的例子(风格迁移)

不过如今在网上还彻底搜不到 CICoreMLModelFilter ,(大雾

数据填补

对于机器学习而言,训练集的完整性、覆盖度可以很大程度上决定最后模型的精确程度。可是现实状况是,咱们每每没有那么多训练集,在这样的状况下,学术界通常都采用对现有训练集的图片进行相应的变化来起到扩充数据集的做用。这类技术统称数据填补(Data Augmentation

Core Image 对于这类任务天生支持的很好,支持包括如下几种类型的变化:

  1. 图像外观;
  2. 图像噪声;
  3. 几何变换;

如下是几种使用 Core Image 的不一样 Filter 来将一张图变多多张训练图片的例子:

小结

这个 Session 带来的内容整体来讲仍是激动人心的,虽然有的同窗可能以为比较小,没有那种颠覆式的创新,但对于从事图像领域工做的同窗而言,毫无疑问这几个工做都给人一种 mind opener 的感受,切实的反应了苹果对于多媒体、用户体验这两个领域很是超前的思考。