WWDC Quick Look 💓 By SwiftGGTeam
Enhance RAW image processing with Core Image

Enhance RAW image processing with Core Image

观看原视频

Highlight

Apple 在 iOS/iPadOS/macOS/visionOS 27 中推出 RAW 9 解码管线,用基于 Apple Neural Engine 的 CoreML 模型同时处理去马赛克和降噪,第三方 App 只需将 CIRAWFilter 的 decoderVersion 设为 version9 即可获得大幅画质提升,并可通过 CIImageProcessor 的新 API 精确控制 tile 尺寸和临时缓冲区生命周期。

核心内容

老照片也能变清晰

RAW 文件的魅力在于:你今天拍的照片,五年后可以用更好的算法重新处理。Apple 从 2006 年的 21 款相机校准,一路扩展到如今的 784 款。每次管线升级,去马赛克、降噪、色彩都在进步。(02:48

但以前有个痛点:高 ISO 场景下,RAW 8 的降噪和色彩恢复总差一口气。ISO 51200 的照片里,蜡笔的颜色糊成一片,细小文字几乎不可读。富士 X-T5 的非传统传感器布局更是容易出色彩伪影。(04:27

RAW 9 的解法是把去马赛克和降噪合并成一个基于 tile 的 CoreML 模型,跑在 Apple Neural Engine 上。结果是:低噪照片的文字更清晰,高噪照片的色彩更准确,连蜡笔上的高光反射都能还原出来。(03:33

交互编辑与批量导出,两种场景两种策略

RAW 9 画质好了,代价是更吃性能。同一张照片反复渲染时,Core Image 会缓存中间结果,所以用户拖动曝光滑块的反馈依然很快。但导出场景完全不同:一次性全分辨率输出,缓存只会浪费内存。(08:40

Apple 明确给出了两套最佳实践:

  • 交互编辑:用 scaleFactor 降采样,每个视图一个 CIContext,开启 cacheIntermediates,直接渲染到 Metal-backed view。加上 Extended Virtual Addressing entitlement 让系统分配更多缓存内存。(09:39
  • 批量导出cacheIntermediates 设为 false,memoryLimit 从默认 256MB 提升到 512MB 或 1024MB,用 CIContextheifRepresentationjpegRepresentation 方法直接输出。(10:56

搞混这两套配置,App 要么卡顿,要么闪退。

详细内容

启用 RAW 9

RAW 9 不会自动开启,需要手动检查并设置。(05:50

let rawFilter = CIRAWFilter(imageURL: rawURL)!

// 检查当前设备是否支持 RAW 9
if rawFilter.supportedDecoderVersions.contains(.version9) {
    rawFilter.decoderVersion = .version9
}

// 查询某版本支持的相机型号列表
let models = CIRAWFilter.supportedCameraModels(for: .version9)

关键点:

  • supportedDecoderVersions 返回当前 RAW 文件可用的解码版本列表
  • decoderVersion 默认不是 version9,必须显式设置
  • supportedCameraModels(for:) 是类方法,返回支持指定版本的所有相机型号数组
  • 支持列表会通过系统 OTA 更新持续扩充
  • 原生拍摄 DNG 的设备(包括 iPhone)自动兼容 RAW 9

CIRAWFilter 的可调参数

RAW 9 保留了 20 个校准参数供用户微调,但废弃了三个旧参数。(07:31

保留的核心参数:

  • exposure — 控制整体明暗
  • luminanceNoiseReductionAmount — 控制亮度噪点
  • sharpnessAmount — 控制边缘锐化程度
  • contrastAmount — 控制边缘附近的局部对比度

RAW 9 中失效的参数:

  • colorNoiseReductionAmount — CoreML 模型自动处理色彩降噪
  • detailAmount — 不再需要
  • moireReductionAmount — 不再需要

调用 isSupported 可检查某个属性对当前 filter 实例是否有效。

批量导出的 CIContext 配置

11:08

let exportCtx = CIContext(options: [
    .cacheIntermediates: false,
    .memoryLimit: 512
])

关键点:

  • cacheIntermediates: false — 导出是一次性操作,缓存无意义
  • memoryLimit 单位是 MB,iOS 默认 256MB,提升到 512 或 1024 可显著加速
  • exportCtx.heifRepresentation(of:)jpegRepresentation(of:) 比直接调 Image IO 更省内存

CIImageProcessor 显式输出 Tile 尺寸

内存紧张时,Core Image 会自动把图像拆成小块处理。RAW 9 新增了让开发者自己控制 tile 大小的能力。(12:23

import CoreImage

class MyProcessor: CIImageProcessorKernel {
    override class func roi(forInput input: Int32,
                            arguments: [String : Any]?,
                            outputRect: CGRect) -> CGRect { return outputRect }

    override class func process(with inputs: [CIImageProcessorInput]?,
                                arguments: [String : Any]?,
                                output: CIImageProcessorOutput) throws {
        guard let input = inputs?.first,
              let iBuffer = input.pixelBuffer,
              let oBuffer = output.pixelBuffer else { return }

        let iRegion = input.region
        let oRegion = output.region

        // 在这里处理像素数据
    }
}

let extent = inImg.extent
let tileSize = 512.0
var tiles: [CIVector] = []
for y in stride(from: extent.minY, to: extent.maxY, by: tileSize) {
    for x in stride(from: extent.minX, to: extent.maxX, by: tileSize) {
        let tile = CGRect(x: x, y: y,
                          width: min(tileSize, extent.maxX - x),
                          height: min(tileSize, extent.maxY - y))
        tiles.append(CIVector(cgRect: tile))
    }
}

let result = try MyProcessor.apply(withTiledExtent: tiles, inputs: [inImg], arguments: [:])

关键点:

  • roi(forInput:arguments:outputRect:) 定义输入区域与输出区域的映射关系
  • process 回调中的 input.regionoutput.region 是 Core Image 分配的实际处理区域
  • 手动生成 tile 数组后,通过 apply(withTiledExtent:inputs:arguments:) 传入
  • tile 大小可根据 GPU 显存和算法需求灵活调整,不限于 512x512

CIImageProcessor 临时缓冲区

处理多 tile 时,反复创建和销毁临时缓冲区会影响性能。RAW 9 新增了由 Core Image 管理的临时 PixelBuffer。(14:24

import CoreImage

class MyProcessor: CIImageProcessorKernel {
    override class func process(with inputs: [CIImageProcessorInput]?,
                                arguments: [String: Any]?,
                                output: CIImageProcessorOutput) throws {
        guard let input = inputs?.first,
              let srcPixelBuffer = input.pixelBuffer,
              let dstPixelBuffer = output.pixelBuffer else { return }

        // 从 Core Image 缓存池申请临时缓冲区
        guard let scratch = output.temporaryPixelBuffer(
            identifier: "myScratch",
            format: kCVPixelFormatType_64RGBAHalf,
            width: Int(output.region.width),
            height: Int(output.region.height),
            pixelBufferAttributes: nil
        ) else { return }

        // Step 1: 把输入数据拷贝到 scratch
        // Step 2: 在 scratch 中处理像素
        // Step 3: 把结果拷贝到输出 buffer
    }
}

关键点:

  • temporaryPixelBuffer(identifier:format:width:height:pixelBufferAttributes:) 从 Core Image 内部缓存池获取缓冲区
  • identifier 用于区分多个临时缓冲区,同一处理器内必须唯一
  • Core Image 自动管理生命周期:当前 tile 处理完后回收,下一个 tile 复用
  • 典型用途是将 Core Image 的交错式图像数据转换为 CoreML 需要的平面式数据

核心启发

  • 做一款 RAW 浏览与轻量编辑 App:利用 RAW 9 的画质提升和 CIRAWFilter 的 20 个可调参数,做一个专注快速预览和参数微调的 App。入口是 CIRAWFilter(imageURL:),核心交互是拖动 exposure/sharpness 滑块实时预览。(07:15

  • 批量 RAW 转码工具:针对摄影师的工作流,做一个后台批量将 RAW 导出为 HEIF/JPEG 的工具。关键配置是 cacheIntermediates: false + memoryLimit: 1024,配合 heifRepresentation(of:colorSpace:) 直接输出。(10:56

  • 自定义 RAW 后期滤镜:用 CIImageProcessorKernel 接入自己的 Metal 或 CoreML 算法,在 RAW 9 解码后叠加自定义效果。用显式 tile 尺寸控制大图的内存占用,用临时缓冲区避免重复分配。(12:17

  • 老照片重处理功能:扫描用户相册中的旧 RAW 文件,用 RAW 9 重新解码后对比展示画质差异。用 supportedCameraModels(for:) 提前判断哪些照片可以受益。(06:27

关联 Session

评论

GitHub Issues · utterances