如何用CoreImage同时捕获iOS相机实时过滤与未过滤图像?
实现同时保存滤镜图与原图的核心思路
一、优先推荐:基于AVCaptureSession捕获原始帧 + CoreImage实时渲染方案
这个方案能精准保证两张图来自同一帧画面,是最可靠的实现方式:
- 配置
AVCaptureSession时,除了预览层,额外添加AVCaptureVideoDataOutput,用于获取相机输出的原始帧数据(CMSampleBuffer格式); - 点击保存按钮时,设置一个标记位(比如
shouldSaveCurrentFrame),在视频帧回调中触发处理:- 将原始
CMSampleBuffer转换为UIImage,直接存入相册; - 用你的CoreImage滤镜链对同一原始帧应用效果,生成带滤镜的
UIImage后存入相册; - 处理完成后重置标记位,避免重复处理。
- 将原始
关键代码片段:
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,避免丢帧;同时把回调队列设为串行队列,保证帧处理顺序。
二、截图方案的优化(适合快速复用现有逻辑)
如果已经基于预览层完成了滤镜渲染,想快速实现需求:
- 点击按钮时同时执行两个操作:
- 对当前滤镜预览层进行截图,得到带效果的图片;
- 立即从
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
相关产品推荐
相关产品推荐

