Create image processing apps powered by Apple silicon
Media & Web 进阶 25m

创建基于 Apple 芯片的图像处理应用

Create image processing apps powered by Apple silicon

2021年6月9日

在 Apple 官方观看视频

一句话判断

在 Apple Silicon 上做图像处理,你应该把 Core Image、Metal Performance Shaders 和 vImage 视为一个连续的工具谱——从”零代码滤镜”到”极致性能自定义算子”,覆盖所有场景。

这场 Session 讲了什么

Session 讲解了如何在 Apple Silicon(M1、A14/A15)上构建高性能的图像处理应用。覆盖了三个层次的工具:Core Image(高层滤镜 API)、Metal Performance Shaders(中层 GPU 计算)、vImage(底层 SIMD 图像处理),以及它们之间如何协作。

Apple Silicon 的统一内存架构(Unified Memory Architecture)是性能基础——CPU 和 GPU 共享同一块物理内存,图像数据不需要在 CPU 和 GPU 之间拷贝。Core Image 在 iOS 15 / macOS 12 中的改进让它可以更高效地利用这个特性:新的 CIImage 创建方式可以直接从 Metal texture 或 IOSurface 构建图像,零拷贝传递给滤镜管线。Session 还展示了 vImage 的新功能——支持在 Swift 中直接调用(不再需要忍受 C API 的繁琐),新增了对多通道浮点图像的处理能力。

值得深挖的点

Core Image 与 Metal 的零拷贝集成

以前把 Core Image 的结果传给 Metal 需要先渲染到 CIContextCGImage,再上传到 Metal texture——两次数据拷贝。现在通过 CIContext.render(_, to: MTLTexture) 方法,Core Image 直接渲染到 Metal texture,不经过 CPU。反向也一样:Metal 渲染的结果可以通过 CIImage(mtlTexture:) 直接变成 Core Image 的输入。整个管线可以做到零拷贝:Metal 渲染 -> Core Image 滤镜 -> Metal 显示,数据始终在 GPU 内存中。

vImage 的 Swift 友好 API

vImage 在 Accelerate 框架中一直是 C 风格的函数 API,参数多且命名冗长。今年 Apple 为 vImage 新增了 Swift 风格的包装:vImage.PixelBuffer 类型提供了链式调用的图像处理方法。比如 buffer.convert(to: .gray8) 就能把 RGBA 图像转成灰度,比以前的 vImageMatrixMultiply_ARGB8888ToPlanar8 好读一百倍。性能没有损失——Swift 包装仍然是直接调用底层的 SIMD 实现。

代码片段

// Core Image 零拷贝滤镜管线
// 从 Metal texture 创建 CIImage,处理后直接渲染回 Metal texture
func applyFilters(
    inputTexture: MTLTexture,
    outputTexture: MTLTexture,
    ciContext: CIContext
) {
    // 从 Metal texture 创建 CIImage(零拷贝)
    let inputImage = CIImage(mtlTexture: inputTexture)!
    
    // 构建滤镜链
    let filtered = inputImage
        .applyingFilter("CIGaussianBlur", 
                        parameters: [kCIInputRadiusKey: 5.0])
        .applyingFilter("CIColorControls",
                        parameters: [kCIInputContrastKey: 1.2])
        .applyingFilter("CIVignette",
                        parameters: [kCIInputIntensityKey: 0.8])
    
    // 直接渲染到 Metal texture(零拷贝)
    ciContext.render(filtered,
                    to: outputTexture,
                    commandBuffer: nil,
                    bounds: filtered.extent,
                    colorSpace: CGColorSpaceCreateDeviceRGB())
}
// vImage 的 Swift API:批量处理图像
func processImages(pixelBuffer: vImage.PixelBuffer<vImage.Interleaved8x4>,
                   destination: vImage.PixelBuffer<vImage.Interleaved8x4>) {
    // 色彩空间转换:sRGB -> Display P3
    pixelBuffer.convert(to: destination, 
                        destinationColorSpace: CGColorSpace(name: CGColorSpace.displayP3)!)
    
    // 调整亮度和对比度
    destination.multiply(by: 1.1)          // 增加亮度 10%
    destination.addCrossoverClamping(10)    // 加一点偏移
    
    // 高斯模糊(vImage 内置实现)
    destination.gaussianBlur(radius: 3.0,
                             destination: destination)
}
// 完整的 Metal + Core Image + vImage 混合管线
func hybridProcessingPipeline(
    inputBuffer: CVPixelBuffer,
    device: MTLDevice
) -> MTLTexture {
    let ciContext = CIContext(mtlDevice: device)
    
    // 从相机缓冲区创建 CIImage
    let ciImage = CIImage(cvPixelBuffer: inputBuffer)
    
    // Core Image 做色彩校正和白平衡
    let colorCorrected = ciImage
        .applyingFilter("CIWhiteBalance",
                        parameters: [kCIInputTemperatureKey: 5500])
    
    // 转换为 vImage 做直方图均衡化(Core Image 不擅长的操作)
    var format = vImage_CGImageFormat(bitsPerComponent: 8,
                                      bitsPerPixel: 32,
                                      colorSpace: CGColorSpaceCreateDeviceRGB(),
                                      bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue))!
    var sourceBuffer = try! vImage.PixelBuffer(ciImage: colorCorrected, 
                                                cgImageFormat: format)
    sourceBuffer.equalizeHistogram()
    
    // 转回 CIImage 并最终渲染到 Metal texture
    let result = try! sourceBuffer.makeCIImage(cgImageFormat: format)
    let descriptor = MTLTextureDescriptor.texture2DDescriptor(
        pixelFormat: .rgba8Unorm, width: Int(result.extent.width),
        height: Int(result.extent.height), mipmapped: false)
    descriptor.usage = .shaderRead
    let outputTexture = device.makeTexture(descriptor: descriptor)!
    
    ciContext.render(result, to: outputTexture, commandBuffer: nil,
                    bounds: result.extent,
                    colorSpace: format.colorSpace!)
    
    return outputTexture
}

最佳实践

  • 图像处理管线的架构选择:如果 Core Image 内置的 200+ 滤镜能满足需求,不要用 Metal 自己实现。Core Image 的内部调度已经针对 Apple Silicon 做了极限优化。
  • 需要自定义算子时,用 Core Image 的 CIImageProcessorKernel 或直接写 Metal compute shader,避免用 CPU 做 pixel-by-pixel 的处理。
  • 对于视频帧处理(相机或视频流),确保 CVPixelBuffer 在整个管线中保持 IOSurface 支持,这样可以利用统一内存避免拷贝。

还有什么值得关注

  • CIImage 现在支持直接从 CMSampleBuffer 创建,视频处理管线可以更简洁。
  • vImage 新增了对 16-bit 浮点图像格式(vImage.Interleaved16Fx4)的支持,适合 HDR 图像处理。
  • Core Image 的 CIFilter 现在可以在 Xcode 预览中实时调试,调整参数时直接看到效果。
WWDC 2021