-
使用 Accelerate 和 simd
了解如何利用先进的信号和图像处理技术来提升 app 的性能并降低耗电量。通过交互式演示,查看具有吸引力的 Accelerate 框架用例。探索如何使用 simd 这一实用附加项在您的 app 中轻松实现矢量编程。
资源
- Improving the quality of quantized images with dithering
- Rotating a cube by transforming its vertices
- Halftone descreening with 2D fast Fourier transform
- Signal extraction from noise
- vImage
- simd
- vDSP
- Accelerate
- 演示幻灯片 (PDF)
相关视频
WWDC19
-
搜索此视频…
(使用Accelerate 和simd 演讲701)
早上好 大家好吗?
很好
我叫Matthew Badin 是Apple的工程师 欢迎来到“使用Accelerate 和simd”演讲
我和同事Luke Chang 今天很兴奋 我们将与你讨论 Accelerate 及其关联框架中提供的优秀API 我们先对Accelerate 及其中包含的一些高性能库进行概述
然后我们将深入讨论一些库 我们首先从vDSP开始
我们有两个例子 首先 我们会告诉你 如何从噪音中提取信号 然后我们会向你展示 如何从图像中移除某些类型的痕迹
接着我们将看看simd 我们将向你展示如何使用四元数 来表示三维旋转
之后我将把话筒 交给同事Luke Chang 他将向你展示 你可以使用vImage做的 一些有趣的事情 所以 让我们开始吧
你可能会问自己 Accelerate到底是什么
Accelerate的主要目的 是提供数以千计的低级数学原语 我们在所有Apple平台上 提供这些原语 所以这不仅包括iOS和MacOS 同时包括watchOS和tvOS
这些原语中的大部分都是 经过对处理器的微架构手动调优的 这意味着我们将得到出色的性能
这种性能直接转化为节省的能量
因此若你是app开发者 并使用Accelerate框架 你的app将不仅运行更快 而且还会更省电 这意味着你的用户 将会在总体上有更好的体验
由于我们提供了如此多的原语 我们发现将它们分类为 领域特定库会非常有用
例如 我们将所有 信号处理原语集中到vDSP 因此这里面有你的FFT 或DFT和DCT 即你的快速傅里叶变换 以及离散余弦变换
VImage包含图像处理原语 如果你正在进行色彩空间转换 这就是为你准备的库
VForce 包含超越函数的矢量版本 例如正弦和余弦
我们也支持密集线性代数
以及稀疏线性代数
我们有一个叫做BNNS的 专用神经网络库 它代表“基础神经网络子程序” 有些库不是 Accelerate框架的一部分 但与其密切相关 我们发现这些库非常有用 包括像simd这样的库 它是CPU的矢量编程辅助工具 以及Compression 它包含几种不同的无损数据压缩算法
我们来看看我们的第一个库 我们先看看vDSP
vDSP是最先进的信号处理库 它包含了种类广泛的信号处理原语 其包括数组上的基本算术运算 比如加法和减法 以及更复杂的操作 如卷积和FFT
如果你是成功的app开发者 也许你在过去避开了FFT 我想向你展示我们如何通过 Accelerate简化其使用 只需几行代码就可以完成这项工作 我这有一个例子 我将向你展示如何从噪声中提取信号
我们这里有一个音频信号 这是基带信号 我们还没有对其添加任何噪音 你会注意到底部有两个滑块 在左下角有一个滑块 可以让我添加噪音
你可以看到它的效果
这是第二个滑块 它可以让我消除这种噪音
它被称为阈值
你还会注意到右下角有一个切换开关
这让我可以在不同的域下观察它 目前我们在时域下看这个信号 我们要做一些分析 现在在频域下看这个信号
你可以看到左侧的所有尖峰 这些是信号的频率分量
你还会注意到蓝色条 蓝色条是阈值滑块 你可以看到我能够移动它
我现在要给信号增加一些噪音
我们多添加一些噪音
你可以看到我感兴趣的信号 由左边的峰值表示 或者以另一种方式来看待它 是那些高度最高的尖峰 我们添加的噪音 其类型为背景噪音 所以它是均匀分布到各处的尖峰 但它们是低级尖峰
现在我要消除这些噪音 我的方法是 将此阈值略微调高一点
当我这样做时 幕后发生的情况是 我们正在识别任何 低于此阈值的频率分量 并将其删除 或者说 任何高度低于这个蓝色条的尖峰 我们将视其为噪音并将其删除
所以如果我继续调高它
最终我会消除所有的噪音
如果我回到时域视图 你可以看到我已经删除了噪音 如果你不信我 我们可以删除阈值 这就是所有噪声仍然存在的情况
告诉你我们如何做到
从高层角度来说 我们要做的是先对信号进行分析 这就是开关的作用
然后我们将识别代表噪音的频率分量 并将其移除
完成这步之后 我们要重建音频信号 我们来看一些代码
这里我们要用到的是 离散余弦变换或者说DCT
你可在此看到 DCT_CreateSetup
这个上下文将描述 我们要做的工作类型 以及为我们分配空间来完成这项工作
在这个例子中 我们将使用一个二类DCT
然后我们将把这个上下文 传递给一个执行函数
这将实际执行此工作
当我们执行了分析后 我们现在想要消除噪音 这就是魔术发生的地方
这个例程将会识别 任何小于该阈值的频率分量 并使其变为零 这将把它清除出去
完成这步之后 我们要重建音频信号
我们同样需要使用 CreateSetup创建上下文
在这个例子中 我们将使用三类DCT 来重建信号
然后我们要将这个上下文传给 DCT_Execute 这实际上将执行重建音频信号的工作
我们向你展示的是
你如何使用vDSP从音频信号中 移除某些类型噪音的例子 我还想向你展示如何通过vDSP 移除图像中的某些类型的噪声的例子
在这个例子中 我们想要恢复旧报纸的照片
我们所做的是拍摄了这张图片
并对其使用双色屏处理
这可以代表一张旧报纸的照片 我们要做的就是移除这种效果 我们将尝试移除你看到的痕迹 目前我们处于关闭状态 所以我们没有做任何事情
我们要做的是对这个屏幕取样 然后从该样本中创建一个滤罩 然后将其应用于图像 以尝试将噪音删除 让我向你展示第一次尝试
我们所做的是我们识别了 某个阈值的频率分量 任何高于该阈值的频率分量
我们都将会删除
你看 如果我们设置的阈值太低
我们不仅移除了痕迹 也移除了图片的一部分
如果我们设置得太高
你可以看到这没有任何效果
中等程度看起来不错 中等程度似乎能够正确识别 图像中的痕迹 且不至于损坏太多的图像 告诉你我们如何做到
从更高层面来说 我们要做的是 我们要对图像和样本执行FFT运算
然后我们将从该样本创建一个滤罩 并将其应用于图像
一旦完成这个步骤 我们就可以重建原始图像 我们来看一些代码
我们要做一个FFT
这意味着它必须是二次幂 这就是你看到log2调用的原因
1024x1024是图片的大小
然后我们将它传递给 fft2d_zrop函数 这里的参数可不少 这里的重点是 op代表空间不足 所以我们将不得不创建 一些临时空间来存储结果
我们将把它存储在这个复数结构中 这实际上是说 我们将把复数存储在两个数组中 实数部分存储在一个数组中 虚数部分存在第二个数组
我们还需要指定一个方向 这例子中 我们要做一个正向FFT
(频率移除) 现在 痕迹移除技术更先进一点了 所以我只会在较高的层面讨论它 我建议你下载示例app 它现在可以在线下载 而且其中每个例程 都被收录在vDSP文档中 我们拥有出色的在线文档
从高层角度来说 我们要做的是识别频率分量的大小 在这个例子中即样本
然后我们将确定要删除的分量
接着我们将从中创建一个滤罩
一旦完成这些步骤 我们就将这个滤罩 应用到图片上
实际上我们做的是 我们将想要删除的部分乘以0 而将想要保留的分量乘以1
我们再次使用zrop来重建图像 由于这是一个FFT 我们可以重用这个上下文
在这个例子中有一个关键细节 即我们要将图像存储在两个数组中 因为这是一个复数结构 其中偶数像素将保存在实数数组中 而奇数像素保存在虚数数组中
我们还要指定一个方向 这次我们将使用逆FFT
现在我想稍微换个挡 刚才我们向你展示了两个例子
我们有两个vDSP的工作示例 然后我们退一步 向你展示了我们如何创建这些示例 我们后退一步向你展示了 它的每个构件
对于下一个库 我想要做的是从基本组件开始 我想逐步达到我们的效果 我们来看看simd 我们将从基本的低级原语开始 我们将要逐步实现旋转效果 在这个例子中是对3D对象的旋转
从高层角度来说
simd是矢量处理单元的抽象 它可以让你声明向量和矩阵对象 然后你可以对这些对象执行操作 这将直接映射到处理器的矢量硬件
我们来看一个代码例子
我们这里要做的是取两个数组 计算其元素的平均值 所以我们将遍历其中每个标量 将它们加在一起再除以二
这将会非常缓慢
另一种方法 你可将这些数组声明为 simd_float4矢量类型
然后我们可以 对这些对象进行基本的算术运算
因此你不仅可以更自然地表达计算 而且它也会尽可能快地运行
这将适用于所有Apple平台
simd具有大量的功能 除了矢量和矩阵对象之外
它还允许你对这些对象执行算术运算
它还具有扩展功能 例如 点积和clamp函数
它也支持超越函数 例如正弦和余弦
以及四元数 四元数在表示三维旋转时 是非常有用的 我想多谈一点这个话题
我们来看一段代码示例
这里有很多东西要展开讨论 所以我们从右边开始 这里有一个单位球体 就是这个灰色的球体 你会注意到这个红点
这实际上是这个向量的尖端
我们声明了 simd_float3向量 我们将x和y分量设置为零 并将z分量设置为1 所以它指向我们
那个红色点代表该向量的尖端
我们现在要使用四元数 对这个向量进行旋转
从技术上来说 我们正在旋转整个场景 但出于说明的目的 我们说我们正在旋转这个向量
当使用四元数进行旋转时 你需要指定一个轴和旋转角度
或者换一种说法 你在旋转什么以及旋转了多少 我们要围绕X轴旋转 我们将向上旋转三个pi的弧度
你可通过调用simd_act 函数来应用此旋转
这将在该向量上执行四元数的动作 并返回一个 rotatedVector
现在我们来看看
通常情况下 你不会对沿单轴旋转感兴趣 你通常要沿多个轴旋转
如果你已经熟悉旋转矩阵 这将看起来很自然
就像旋转矩阵一样 你可以使用乘法来结合旋转 并且也像旋转矩阵一样 乘法是非可交换的 所以这意味着 如果你改变操作数的顺序 你将改变旋转的顺序
这里我们要做的事情是 向上旋转三个pi的弧度 然后向右旋转三个pi的弧度 我们将它们组合成一个单一的旋转
你可用四元数和simd 来做的一些更有趣的事情是插值 我们支持两种类型的插值
第一种是Slerp
它代表球面线性插值
它实际上有两种变体
我们有一个 simd_slerp函数 它能找到这两个点之间的最短弧 这个例子中 是蓝色和绿色之间的弧
我们还有simd_slerp_longest函数 它会找到最长的圆弧 所以你会发现它位于单位球体后面
第二种变体是Spline
当你有两个以上的旋转时 Spline更有用 例如 在这里我们要 在一个旋转数组中进行插值 这里有很多样板代码 我希望你只关注 Spline函数的调用
我们实际上正在做的只是 遍历每个旋转 并对其调用Spline
你不仅要用Spline指定 你希望插入的两个旋转 也应该指定上一个和下一个旋转
像这个样子
如果你是一个游戏开发者 你可能对旋转单独的矢量不感兴趣 你可能对旋转物体感兴趣 我们为你准备了这个 这是一个立方体 它由多个向量表示 它将经历一连串的八次旋转 在左边 我们将使用Slerp来 跟踪这些旋转 右边 我们将用Spline 我们来看看Slerp的效果
你可以看到 由于这是一个线性插值 它每改变一次方向 你都会得到这些尖角 而如果我们看Spline
由于它还知道前一轮和下一轮旋转 你最终会得到这些圆角 所以让我们再看一遍
我非常快速地讨论了所有这些话题
我们来回顾一下
我们首先看了下vDSP 并向你展示了两个例子 第一个是如何从噪声中提取信号 第二个是如何从图像中 移除某些类型的痕迹
然后我们讨论了simd 我向你展示了如何使用四元数 来表示三维旋转
我现在要将话筒交给 我的同事Luke Chang 他将向你展示一些 你可以用vImage做的 有趣的事情
谢谢你 Matthew
大家好 我叫Luke Chang 我是矢量和数值团队的工程师 今天我要谈论vImage vImage提供了什么 以及在你的app 使用vImage是多么容易 只需几行代码 你就可以在你的app中 创建引人入胜的视频效果 让我们开始吧
VImage是我们的图像处理库
它有几个组件 第一个组件 是转换函数 转换函数可帮助你 在不同的图像格式之间转换图像 不同的图像格式有不同的优势 例如 RGB格式与显示器上的像素相匹配 所以它最适合显示 另一方面 我们有YCbCr图像 这与人类如何感知图像相似 人眼识别亮度 这就是亮度信道 还有颜色 即色度信道
而且相机使用 YCbCr格式来捕捉图像
因此转换函数 可帮助你轻松地在这些格式之间 转换图像
我们还有几何函数
几何函数能改变图像的大小或方向 我们有vImageScale 它可以放大或缩小图像 我们使用Lanczos算法 因此在操作之后会有高质量的输出
我们也有vImageRotate 可以顺时针或逆时针旋转图像
接下来我们还有卷积函数 卷积函数最显着的效果 是模糊效果 你到处都能见到模糊效果 在用户界面中或者摄影中 如果你想将某些东西渐淡到背景中 你就可以使用模糊函数 模糊效果
接下来是变换函数 变换函数基本上是一个矩阵乘法 它可以让你在每个像素的 数据通道上进行操作 假设你想增强红色或增强绿色 你可以用变换函数来做到这一点
形态函数 形态函数改变图像中 物体的大小或形状 而不改变图片本身 我们有vImageErode 和vImageDilate 来使对象变得更小或更大
如果你感觉这很冒险 你实际上可以为这些函数 提供一个自定义的内核形状 vImage侵蚀和扩张函数 会根据你提供的内核 来使物体变小或变大
这些是vImage中的五种工具 现在我想向你展示我们 基于vImage编写的演示app 来演示你可以使用vImage 获得什么样的效果
这里有一个使用后置摄像头 捕捉图像的实验 我们将图像投影到屏幕上 我们现在是实时操作 这是一个直播视频流 你可以看到那只喝水鸟 正在玫瑰旁边做动作 好 我想向你展示的 第一个效果是颜色饱和效果 你可以在很多照片编辑软件中 看到这种效果 现在我想突出颜色效果 我可以将此滑块移到右侧
使红色更红 绿色更绿
在另一边有一朵白玫瑰 我觉得白玫瑰的颜色 对我来说并不是那么有趣 我想引导我的观众关注其构图 以及这张图片的对比度 我可以向左滑动来使图像去饱和
直到它变成黑白图像 现在色彩在这张图片中不再让人分心 现在观众可以专注于构图和对比
好 所以… 我们如何做到这一点
我们需要采取几个步骤 首先 我们当然必须从相机获取图像
然后我们要使用 vImage来应用效果 因此我们必须为vImage的 输入和输出准备缓冲区
然后我们实际调用vImage函数 来应用这些效果
并将输出显示在屏幕上
我们先来讨论 如何使用vImage函数 来应用效果
我向你展示的是一种色彩饱和效果 这是计算色彩饱和效果的公式 我们想要消除像素的偏差 并使用乘法来应用饱和效果 然后我们再将偏差放回像素
VImage具有完成此操作的函数 即vImageMatrixMultiply
vImageMatrixMultiply 需要一个preBias参数 在这个例子中为-128 以消除偏差
由于饱和度是浮点数 而图像是一个整数 我们希望先将此饱和度值 转换为定点格式 我们选择Q12作为定点格式 因此令divisor值为0x1000
再是postBias参数 其值为128乘以divisor 以此将偏差放回像素
矩阵本身非常简单 我们所要做的 只是对CbCr信道进行缩放 所以矩阵本身就是一个缩放器 将CbCr信道与该缩放器相乘
有了所需的所有信息 调用vImageMatrixMultiply 只需一行代码 一个函数调用 你就可以实现这个饱和效果
现在让我们回到需要采取的其他步骤
我们需要从相机拍摄图像 我们如何做到这一点?
我们需要写一个委托方法 相机给我们的是 一个CVImageBuffer 因此我们获取这个缓冲区 我们必须确保CPU可以访问 这个缓冲区 这即vImage所在之处 在我们应用这些效果之后 无论是哪种效果 我们必须解锁 这个像素缓冲区的基地址 以便相机可以重新使用这块内存
第二步 我们必须准备 vImage输入和输出缓冲区
我们已经在CVImageBuffer中 保存了这个图像 我们需要做的 只是获取如高度和宽度等信息
然后我们可以将它打包成 一个vImageBuffer对象 以便它可以被vImage库使用 我们为亮度和色度信道进行这项操作
现在我们需要准备一个输出缓冲区 请记住 我们还没有为输出图像 分配一块内存 因此我们需要这样做 vImage有一个很方便的函数 即vImageBuffer_Init 可以做到这一点
给定其高度 宽度和每像素位数 vImageBuffer_Init 将为你分配一个足够容纳此图像的内存 然后也会创建一个 vImageBuffer对象 从而可以被vImage库使用
最后一步 是将处理后的图像输出到屏幕上
正如我之前所说的 RGB是最佳显示格式 所以让我们使用转换函数 将YCbCr图像转换为RGB图像 然后 因为用户界面 期望的是CGImage对象 所以我们必须创建一个
在vImage中有一个方便的函数 vImageCreateCGImageFromBuffer
它能帮助你 基于vImage已有缓冲区 来创建CGImage 需要注意的一点是 我们实际上并没有将 图像中的大块数据缓冲区 从一个地方复制到另一个地方 我们只是简单的创建一个 CGImage对象 其向该图像缓冲区添加一个容器 因此我们只是向其中 填充CGImage需要的信息 创建一个CGImage对象 而不是到处复制数据
一旦有了该对象 我们就可以将CGImage对象 发送给imageView 它就会显示在屏幕上 就这么简单 只需四个步骤 你就可以创建自己的效果 我们向你展示了饱和效果 现在 我们还可以用 vImage做其他效果
我们可以做一个旋转 就像我之前说过的那样 顺时针或逆时针旋转图像 我们可以模糊化 将某些东西渐淡到背景中 你感觉…
如果你觉得你想为图片 添加一些复古感 你可以对黑白图像进行抖动处理 或对彩色图像进行色彩量化 我们来看看它们在app中的样子
再一次 这里有个滑块来控制旋转 我可以逆时针旋转
或顺时针旋转
现在我想尝试模糊效果 让我点击这里的一个按钮 然后… 我可以加深模糊
或…向左滑动 将玫瑰带回前景
对于黑白相片 我可以使用抖动
现在 这张黑白图片 其灰度现在由点的密度表示 这就是抖动效果 我们使用了重音和抖动算法 我稍后会告诉你该如何做
对于色彩量化 我们使用了查找表 我可以移动这个滑块来增加量化级别 当我将滑块向右移动时 此图片中的颜色越来越少 这很有创意…
这就是电脑屏幕在 90年代或80年代看起来的样子
好的 所以… 我们来看看这是如何做到的
对于旋转效果 你可以调用 vImageRotate 并给定旋转角度 它就会逆时针或顺时针旋转你的图像
对于模糊效果 我们使用 TentConvolve函数 模糊效果由内核大小控制 内核越大 就会越模糊
抖动效果基本上就是将8位图像 转换为1位图像 同时你可以指定抖动算法 在这个例子中 我们使用阿特金森抖动算法
对于色彩量化 我们使用量化级别 来为RGB信道创建一个查找表 然后我们调用 vImageTableLookUp 来将此查找表应用于RGB信道 以限制屏幕上的颜色数量
这些是我想向你展示的四个额外效果 我认为现在是 进入下一个主题的好时机
LINPACK Benchmark
我们谈到了 Accelerate的功能 我们也谈论了在你的app中 使用Accelerate有多容易 但我们还没有谈到 Accelerate的速度有多快 LINPACK Benchmark 是做这件事的完美工具
什么是LINPACK Benchmark? 它实际上会试图衡量 你可以多快地解决 机器上的线性系统问题
实际上有三种不同的 LINPACK Benchmark 第一个解决的是 100乘100的线性系统 第二个解决的是1000乘1000 最后一个… 这是最有趣的一个 也是我们今天要使用的这个 即“不受限” 你可以解决你想要的任意大的系统 来充分利用你的机器上的 所有计算能力
我们看看使用Accelerate 的iPhone X的性能
性能以吉拍来衡量
以双精度运算追赶 iPhone 5S iPhone 6
iPhone 6S iPhone 7 iPhone X 大约能达到28.7吉拍 这是双精度运算 我们来看看单精度
再一次…
我们的空间用完了 我们不得不缩小以使它们更靠近
iPhone X 大约能达到68吉拍
现在你可能会想 这并不令人意外 然而 随着时间的推移 它的性能也会随之提高 但事实上 这并不是全部
当微架构发生变化 从而为机器提供额外的计算能力时 你需要相应的软件 来充分利用这些额外的计算能力 这就是与我们相关的地方 请记住 这些是用同样的 LINPACK Benchmark可执行文件 运行在所有五代iPhone上 他们都使用Accelerate 获得了最佳性能
而无需做出任何改变
对于你的app也是如此 如果你在app中 使用Accelerate 你将自动获得最佳性能 在我们支持的所有架构上
此外… Accelerate还支持多平台 Accelerate能在macOS、iOS tvOS、watchOS上工作
假设明天 Apple推出了新架构或新平台 你也不必担心 你需要做的 顶多是重新构建你的app 并与Accelerate链接 你将自动在最新版本的平台或架构上 获得最佳性能
总而言之 我们谈到Accelerate 支持多种功能 有极大可能 你会在Accelerate中 找到你要的东西 如果你需要一些我们还没有的功能 请随时提交功能请求 我们经常查看这个功能请求 对它们进行评估 然后投入构建 实际上 我们的一些最佳功能 就是来自功能请求的
Accelerate很容易使用 大部分时间 只用一个函数调用就可以完成工作
它快速且节能 因此你的app更具响应能力 电池续航时间也更长
Accelerate可跨平台 和体系结构移植 你可以在我们支持的 所有平台和所有架构上 均获得最佳性能 而其中最棒的是你不必改变你的代码
欲了解更多信息 你可以参阅developer.apple.com上的 在线文档 我们所有的演示app 简单代码和演讲资料 都会在线提供 明天下午两点 我们有一个实验室 我期待在那里见到你们 如果你有任何问题 或你想了解更多 关于Accelerate的信息 我很乐意在那里见到你
以上就是我们今天的演讲 谢谢大家的光临 祝你有美好的一天
-