You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用CoreImage同时捕获iOS相机实时过滤与未过滤图像?

实现同时保存滤镜图与原图的核心思路

一、优先推荐:基于AVCaptureSession捕获原始帧 + CoreImage实时渲染方案

这个方案能精准保证两张图来自同一帧画面,是最可靠的实现方式:

  • 配置AVCaptureSession时,除了预览层,额外添加AVCaptureVideoDataOutput,用于获取相机输出的原始帧数据(CMSampleBuffer格式);
  • 点击保存按钮时,设置一个标记位(比如shouldSaveCurrentFrame),在视频帧回调中触发处理:
    1. 将原始CMSampleBuffer转换为UIImage,直接存入相册;
    2. 用你的CoreImage滤镜链对同一原始帧应用效果,生成带滤镜的UIImage后存入相册;
    3. 处理完成后重置标记位,避免重复处理。

关键代码片段:

var shouldSaveCurrentFrame = false

// 视频帧捕获回调
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    guard shouldSaveCurrentFrame else { return }
    
    // 转换原始帧为UIImage
    guard let originalImage = sampleBuffer.convertToUIImage() else { 
        shouldSaveCurrentFrame = false
        return 
    }
    
    // 应用滤镜生成处理后的图片
    let filteredImage = applyCoreImageFilter(to: originalImage)
    
    // 切回主线程保存相册
    DispatchQueue.main.async {
        UIImageWriteToSavedPhotosAlbum(originalImage, self, #selector(self.handleSaveResult(_:error:context:)), nil)
        UIImageWriteToSavedPhotosAlbum(filteredImage, self, #selector(self.handleSaveResult(_:error:context:)), nil)
        self.shouldSaveCurrentFrame = false
    }
}

// CMSampleBuffer转UIImage的扩展方法
extension CMSampleBuffer {
    func convertToUIImage() -> UIImage? {
        guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { return nil }
        CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
        defer { CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) }
        
        let baseAddr = CVPixelBufferGetBaseAddress(imageBuffer)
        let width = CVPixelBufferGetWidth(imageBuffer)
        let height = CVPixelBufferGetHeight(imageBuffer)
        let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
        
        guard let context = CGContext(data: baseAddr, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else { return nil }
        guard let cgImage = context.makeImage() else { return nil }
        return UIImage(cgImage: cgImage)
    }
}

// 相册保存结果回调
@objc func handleSaveResult(_ image: UIImage, error: Error?, context: UnsafeMutableRawPointer?) {
    // 可添加保存成功/失败的提示逻辑
}

注意:需要将AVCaptureVideoDataOutput的alwaysDiscardsLateVideoFrames设为false,避免丢帧;同时把回调队列设为串行队列,保证帧处理顺序。

二、截图方案的优化(适合快速复用现有逻辑)

如果已经基于预览层完成了滤镜渲染,想快速实现需求:

  • 点击按钮时同时执行两个操作:
    1. 对当前滤镜预览层进行截图,得到带效果的图片;
    2. 立即从AVCaptureVideoDataOutput获取最新的原始帧,转换为原图保存。

截图代码示例:

func captureFilteredPreview() -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(previewLayer.bounds.size, false, UIScreen.main.scale)
    previewLayer.render(in: UIGraphicsGetCurrentContext()!)
    let screenshot = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return screenshot
}

缺点:截图和原始帧可能存在极微小的时间差(几毫秒级别),适合对帧同步要求不高的场景。

三、核心注意事项

  • 权限:提前申请相机访问权限和相册写入权限,否则保存操作会直接失败;
  • 线程安全:AVCapture的帧回调在后台线程,所有UI操作(包括相册保存回调的提示)必须切回主线程;
  • 性能控制:仅在点击按钮时触发帧处理,避免持续对每一帧做滤镜计算和保存,防止卡顿。

内容的提问来源于stack exchange,提问作者pathman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:00:40