Core Image:iOS图像处理技术追踪

2020 年 8 月 09 日

Core Image:iOS图像处理技术追踪

Core Image 是苹果官方提供的图像处理框架,通过丰富的 built-in(内置)或自定义 Filter(过滤器)高效处理静态图片、动态图片或视频。开发者还可以通过构造 Filter 链或自定义 Core Image Kernel 来实现更丰富的效果。
在 WWDC20 中,苹果官方针对 Core Image 技术在以下三方面做了优化:Core Image 对视频 / 动图的支持、基于 Metal 构建 Core Image (CI) Kernel 以及 Core Image 的 Debug 支持。
这三方面会在下文逐一提到,文末笔者也会浅谈 Core Image 在手淘图片库中的应用可能以及对 Core Image 技术的展望。

优化 Core Image 对视频 / 动图的支持

创建 CIContext

创建 CIContext 时,需要遵循一个 view 一个 context 的原则。由于视频的每一帧都会发生变化,将 CIContext 的 cacheIntermediates 属性设置为 false 可以大大减少内存消耗。

如果在使用 Core Image 时将同时运用 Metal(作为输入或输出),通过设置 MTLCommandQueue 属性创建 CIContext 将会是较好选择。在不使用 MTLCommandQueue 的情况下,每一个 Metal 或 CoreImage 执行的任务都在不同队列中并以 wait 命令分隔开,导致任务执行效率低。通过设置 MTLCommandQueue 创建的 CIContext 和相应的 Metal 任务在同一队列中,能提高 app 的运行效率。

图一:不使用 MTLCommandQueue 的工作流程

图二:使用 MTLCommandQueue 的工作流程

编写 Core Image Kernel(在 Metal 中实现)

为了将效果处理得更丰富,通过 Metal 来实现自定义 CI Kernel 是个高效的选择。苹果官方提供了的许多方便部署的内置工具(都通过 Metal 实现),如内置 CI 滤镜。通过 Metal 实现自定义 CI Kernel,不仅 app 的 runtime 编译时间将会大大减少(这段工作会移至 app 构建完成后进行),开发者还能获得高性能语言特性(如 gather-reads、group-writes、半精度浮点数)、高效开发体验(如缩进检查、缩进高光)等功能。

选择合适的 View 类

如果要对视频 / 动图应用特效,静态内容 View 如 UIImageView 或 NSImageView 应当被避免。AVPlayerView 和 MetalKit View(MTKView)是个两个不错的选择。前者为简单选择,后者为进阶选择。

使用 AVPlayerView 时,需要创建 AVMutableVideoComposition 对象,CI 滤镜在 block 中执行图像处理任务。在进行断点 debug 时,通过点击 CIImage 对象地址右侧的眼睛图示可以浏览 CI 滤镜处理流程的详细信息。

官方提供的案例中,Core Image 还将 10 位的 HDR 视频帧数据自动从 HLG 转化成了 Core Image working space。

图三:CI Image 断点测试中展现的处理流程

使用 MTKView 时,开发者需要以 frame 和 device 作为参数重载 init 方法。VIew 对应的 CIContext 也将在 init 函数中被创建。如果我们在 macOS 中开发支持 HDR 的 view,color-Pixel-Format 属性需要被设定为 rgba16Float,wants-Extended-Dynamic-Range-Content 属性需要被设定为 true。设定完 init 方法后,开发者需要实现 draw-in view 方法。需要注意的是,此处并未直接将 Metal 材质传入 CIRenderDestination 函数,而是创建了一个会返回 texture 的 block。这使得 CIContext 能在前面的帧尚未完成时将 Metal 工作入队。之后该方法会执行渲染任务(至指定目的地)并创建 command buffer 将当前绘制结果渲染至 view。

本人也亲自尝试了通过 Core Image 处理视频的整个流程。以下案例使用 CIVortexDistortion 滤镜对视频进行逐帧处理并渲染,展示内容包含核心代码、原视频、CI 滤镜处理后视频以及断点测试的滤镜逐帧处理图示。

复制代码
let filepath: String? = Bundle.main.path(forResource: "test_video", ofType: "MOV")
let fileURL = URL.init(fileURLWithPath: filepath!)
let asset = AVAsset(url: fileURL)
let item = AVPlayerItem(asset: asset)
item.videoComposition = AVMutableVideoComposition(asset: asset) { request in
let filter = CIFilter(name: "CIVortexDistortion")
filter?.setValue(request.sourceImage, forKey: kCIInputImageKey)
filter?.setValue(NSNumber(400), forKey: "inputAngle")
filter?.setValue(NSNumber(1200), forKey: "inputRadius")
filter?.setValue(CIVector(x: 700, y: 400), forKey: "inputCenter")
let out = filter?.outputImage
request.finish(with: out ?? request.sourceImage, context: nil)
}
avPlayer = AVPlayer(playerItem: item)
测试核心代码

http://mpvideo.qpic.cn/0bf2pyaamaaapeahvnrdnbpva7wdaz7aabqa.f10002.mp4?dis_k=0f04353de60ce504c1ffc8be4dd1484b&dis_t=1596367310

原视频(笔者本人取材)

http://mpvideo.qpic.cn/0b78siaacaaa3mah22zdnrpvbewdagjaaaia.f10002.mp4?dis_k=fc45165fd0e34a1f15afa120e5501099&dis_t=1596367310

添加 CIVortexDistortion 滤镜后的视频

图四:断点调试时 Core Image 对每帧的处理流程

基于 Metal 构建 Core Image Kernel

使用 CI Kernel 有诸多优势,包括上文提及的缩短 runtime 编译时间、高性能语言特性(如 gather-reads、group-writes、半精度浮点数)、高效开发体验(如缩进检查、缩进高光)。基于 Metal 构建 CI Kernel 有 5 步流程,会在下文进行逐一介绍。

在项目中增加自定义构建规则

苹果官方推荐在项目 target 中增加两项自定义构建规则。第一个构建规则针对以“.ci.metal”为后缀名的文件。该构建规则会创建一个以“.ci.air”为后缀名的二进制输出文件。

图五:针对“*.ci.metal”文件的构建规则

第二个构建规则针对以“.ci.air”为后缀名的文件(上一个构建规则的输出结果)。该构建规则会在 app 的资源文件夹内创建以“.ci.metallib”为后缀名的输出文件。

图六:针对“*.ci.air”文件的构建规则

在项目中增加.ci.metal 资源

在 Xcode 提供的创建面板中选择 Metal File 即可。开发者对 Metal File 进行命名时需要以“.ci”作为后缀名,这样项目中新生成的文件会以“.ci.metal”作为后缀名。

编写 Metal Kernel

便携 Metal Kernel 需要 include CoreImage.h 头文件,用来使用 Metal 和 Core Image 提供的各种类。官方提供的范例编写了一个 CIColorKernel,输入参数为 coreimage::samle_t 对象(表示输入图片的一个像素)、time 和 coreimage::destination 对象,返回 float4 像素。

图七:苹果官方提供的代码范例:Metal Kernel 编写

苹果官方为开发者提供了描述 CI Kernel 中 Metal Shader 语言的文档,详情见「 Metal Shading Language for Core Image Kernels」① 。

加载 Kernel 并应用于新图像(基于 Swift)

Kernel 会被 CI 滤镜的子类使用。苹果官方推荐开发者在实例化滤镜的 CIKernel 对象时使用静态属性(static property),这种情况下加载 metallib 资源的工作仅会执行一次(在首次需要时)。CI 滤镜的子类也必须重载输出图片的属性,Kernel 将在 getter 中进行图像处理并创建新图像。

图八:苹果官方提供的代码范例:Kernel 加载与使用

Core Image 的 Debug 支持

苹果官方在 WWDC20 详细介绍了 Debug 特性:CI_PRINT_TREE。

什么是 CI_PRINT_TREE

CI_PRINT_TREE 的基础框架与 Xcode 提供的 Core Image Quick Look 支持相同。Core Image Quick Look 为开发者提供了快捷可视化的 Core Image 图片(详见上文图三),而 CI_PRINT_TREE 支持几种不同的模式和选项用来查看 Core Image 如何优化和渲染图像。

如何启用 CI_PRINT_TREE

苹果官方提供了 CI_PRINT_TREE 的两种启动方式。最常用的方法是编辑 Xcode target scheme,在 Arugments 窗体下的环境变量列表中加入 CI_PRINT_TREE 并设置值。另一种方法是在 Terminal.app 中通过命令行启动 CI_PRINT_TREE(需要在执行应用程序前设定)。

图九:启用 CI_PRINT_TREE 的两种方式

如何控制 CI_PRINT_TREE

CI_PRINT_TREE 的字符串格式为“<graph type> <output type> <options>”

  • graph type: 表示 Core Image render 的若干 stage,包括 type-1 初始图像(有助于查看被使用的色彩空间)、type-2 优化后的图像(有助于查看 core image 对 render 的优化效果)、type-4 级联图像(有助于查看各 stage 如何级联于 GPU 程序,以便了解 render 需要多少中间缓存)以及 type-7(输出图像 type1、2 和 4)。

图十:苹果官方对 graph type 四个 stage 的描述

  • output type: 输出格式可以是 pdf 或 png。在 macOS 上 trees 会被存储在临时项目文件夹,在 iOS 上 trees 会被存储在文档(Documents)目录下。如果 output type 没有确定,core image 会把 tree 以紧凑文本格式输出在标准输出(stdout)。通过设置 CI_LOG_FILE=“oslog”,文本也可以前往 Console.app(在 iOS 开发中更为方便)。
  • options: 对于 CI_PRINT_TREE,开发者可以设定额外的选项。如通过设定 context==name 来限制输出(仅输出名字相同的 context),或是通过设定 frame-n 来框定具体输出 context 的哪一帧。更多 option 及详情请见图十一。设定 option 对 debug 能提供很大帮助,但也需谨慎使用,因为生产这些文件需要额外的时间和内存。

图十一:苹果官方提供的 option

图十二:type 设定为 7 时 tmp 文件夹下的文件

如何获得 CI_PRINT_TREE 文件

在 macOS 中,开发者只需要进入“/tmp”文件夹就能找到生成的 CI_PRINT_TREE 文件。需要注意的是沙盒应用会使用特有的临时存储文件夹。

在 iOS 中,开发者需要将 Custom iOS Target Properties 中的“Application supports iTunes file sharing”项设为 YES(图十三)。这样生成的 CI_PRINT_TREE 文件可以在连接中的 iOS 设备上被找到并拖拽至 macOS 存储中。

图十三:Custom iOS Target Properties 中进行设置

如何解释 CI_PRINT_TREE 文件

读 CI_PRINT_TREE 时,需要遵循以下规则:

  • 输入在底层,输出在顶层
  • 绿色节点代表卷曲内核(warp kernel),红色节点代表颜色内核(color kernel)

图十四:绿色节点与红色节点示例

  • 在树的初始位置(initial tree)很容易找到颜色搭配节点(colormatch nodes),里面记录了搭配前后的色彩空间名称。苹果官方提供的案例为 ITUR_2100_HLG_to_workingspace,即 HLG 色彩空间转化为 Core Image 线性色彩空间。

图十五:苹果官方案例中 initial tree 对色彩空间的描述

  • 每个节点会显示 Region of Interest(ROI),表示该节点在 render 中被使用的范围。

如果开发者在 CI_PRINT_TREE 控制字符串中选择 type-4 并在 option 中设定 dump-intermediates,产生的级联图片会展示中间缓存的每一次 pass(除了 output pass)及其耗时、像素点数量和像素点格式(用来查找耗时大、占内存大的 pass)。这对 render 内追踪错误非常有帮助。如果树中没有展示中间图,那么说明这张图在先前渲染的时候已被缓存,因此 Core Image 没有渲染它的必要。

图十六:设定 dump-intermediates 的 debug 效果展示

Core Image 在手淘图片库中的应用可能

手淘图片库中的 CDN 图片适配处理库(TBCDNImage)的核心目的是为不同终端设备、网络环境下的图片展示提供最优解。目前考虑的维度主要是终端设备硬件和网络状态,考虑的参数则是图片尺寸、压缩比率、锐化等图片属性。随着苹果在 Core Image、端智能(CoreML)、硬件支持(自研芯片)等方面进行技术提升,手淘的 CDN 图片适配处理库可以考虑增加“图片内容”作为新的维度,增加亮度、对比度、滤镜、图片种类等新参数。以下为部分应用场景:

  • 识别亮度较暗的图片,提升亮度做 CDN 图片适配处理
  • 判断图片内容种类(如美食),根据不同内容种类的图片增加适合的滤镜做 CDN 图片适配处理
  • 根据移动终端设备屏幕亮度(或深 / 浅色模式)修改图片色调做 CDN 图片适配处理,达到护眼效果

对 Core Image 技术的展望

总结全文,WWDC20 对 Core Image 技术的提升主要在三方面:

  • 优化 CI 对视频 / 动图的支持,包括开发流程简化、逐帧处理性能提升等。
  • 允许开发者更自由的构建 Core Image Kernel,使 CI 的特效处理更加丰富
  • 针对 CI 开发流程提供更高效的 Debug 支持

随着苹果未来自研芯片的底层硬件支持将提供视频流流畅的逐帧处理与渲染。笔者认为 Core Image 技术将会在以下场景有较大应用价值:

  • 直播滤镜 / 特效功能原生化(摆脱自研或第三方 API),实现质量更高的实时滤镜渲染
  • 视频拍摄增加滤镜功能(如淘宝或咸鱼的商品视频录制)

参考:

https://developer.apple.com/metal/MetalCIKLReference6.pdf

https://github.com/duzhaoquan/ImagesVideoFilters

本文转载自公众号淘系技术(ID:AlibabaMTT)。

原文链接

https://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ==&mid=2650409336&idx=1&sn=f630ee519d7b9fe89807f06484e614b9&chksm=8396c160b4e14876c10c3fd7ac34935582b64f398cb1fd46522bc2ad7c44b06a1602a6f07eb5&scene=27#wechat_redirect

2020 年 8 月 09 日 10:00 812

评论

发布
暂无评论
发现更多内容

手把手教你从零开始使用python编写大型冒险类游戏01之游戏介绍

Geek_8dbdc1

Dubbo2.7试用

心平气和

dubbo 灰度 hessian

Git命令可视化展示,代码管理再也不愁了,建议收藏!

诸葛小猿

git git merge git rebase git fetch git reset

MySQL-技术专题-共享锁与排他锁

李博@Alex

从北京降雨的复盘中,我发现了企业SD-WAN网络的秘密

脑极体

微博基于 Flink 的机器学习实践

Apache Flink

flink

Python中的单下划线和双下划线使用场景

王坤祥

Python Python基础知识 Python基础

MySQL 架构与历史

多选参数

MySQL 数据库 MySQL优化

MySQL-技术专题-InnoDB存储引擎

李博@Alex

区块链一新基建 新动能 新发展大会将在南昌举办

CECBC区块链专委会

新基建 区块链技术

Django的Models更新时,不触发Signals解决办法

Young先生

django singals 信号机制 update 更新

芯片破壁者(十二.下):青瓦台魔咒与半导体“死亡谷”

脑极体

记一次腾讯云(西安)后台开发面试经历

z小赵

面试 分布式 高并发

用故事去理解「文件 I/O」

小林coding

操作系统 异步 文件系统 同步 非阻塞网络I/O

源码分析 | 咋嘞?你的IDEA过期了吧!加个Jar包就破解了,为什么?

小傅哥

javassist 字节码插桩 asm bytebuddy javaagent

非科班学习编程一定得知道这几个网站!

我是程序员小贱

Go: Goroutine, 系统线程和CPU管理

陈思敏捷

go golang mpg

中本聪原始比特币论文解读:点对点的电子现金系统

韩超

比特币 区块链

契约测试:解决微服务测试的问题

陈磊@Criss

Windows AD 保姆级配置NTP服务器教程

Young先生

时间 AD ntp Windows Server 2012 R2

图解23种设计模式——前方高能,前端切图仔请务必抓好方向盘

执鸢者

typescript 前端 设计模式

Flink 中的应用部署:当前状态与新应用模式

Apache Flink

flink

手撕二分查找及其变种,就是干!

我是程序员小贱

Git设置分支保护实现CodeReview卡点

陈磊@Criss

goalng写的IM服务器

Geek_9b1a66

golang IM

office 365激活,总是自动变成专业版2019

wood

Office Office 365

Serverless:为我们到底带来了什么

刘宇

Serverless 云原生

一文了解对称加密与非对称加密

我是程序员小贱

安全

为什么修改hosts不立即生效?--浏览器DNS缓存机制分析

陈磊@Criss

想要成功,你需要的是目标与动机,目标是你的助攻,动机是你的爱人。

叶小鍵

成功学 心理学 海蒂·格兰特·霍尔沃森

数据治理第一步,摆脱“手工作坊”

KAMI

大数据 数据治理 数据开发 数据平台

Core Image:iOS图像处理技术追踪-InfoQ