乐于分享
好东西不私藏

苹果照片 App 的抠图功能,现在你也能用代码调了

苹果照片 App 的抠图功能,现在你也能用代码调了

这里每天分享一个 iOS 的新知识,快来关注我吧

前言

iOS 16 的时候苹果在照片 App 里加了「抠图」功能——长按主体就能把前景从背景中分离出来,相信很多人都用过了。

iOS 17 开始,这个能力通过 Vision 框架暴露给了开发者。

今天来看看怎么用 VNGenerateForegroundInstanceMaskRequest 在 Swift 中实现图片去背景,纯本地推理,不需要服务器。

整体流程

去背景分三步:

  1. 生成前景遮罩:Vision 框架识别前景主体,输出像素级 mask
  2. 应用遮罩:用 Core Image 的 blend filter 把背景替换为透明
  3. 渲染输出:CIContext 转成 UIImage

第一步:生成前景遮罩

func makeForegroundMask(from ciImage: CIImagethrows -> CIImage {
    let handler = VNImageRequestHandler(ciImage: ciImage)
    let request = VNGenerateForegroundInstanceMaskRequest()
    try handler.perform([request])
    guard let result = request.results?.first else {
        throw BackgroundRemovalError.noResult
    }
    let maskPixelBuffer = try result.generateScaledMaskForImage(
        forInstances: result.allInstances,
        from: handler
    )
    return CIImage(cvPixelBuffer: maskPixelBuffer)
}

allInstances 会检测图片中所有前景主体。如果你只想保留特定物体,可以筛选 result.allInstances 中的子集。

第二步:应用遮罩

func applyMask(_ mask: CIImage, to image: CIImage) -> CIImage {
    let filter = CIFilter.blendWithMask()
    filter.inputImage = image
    filter.maskImage = mask
    filter.backgroundImage = CIImage.empty()
    return filter.outputImage ?? image
}

原理很简单:mask 中白色区域保留原图(前景),黑色区域变透明(背景)。CIImage.empty() 作为背景就是纯透明。

第三步:渲染

func render(_ ciImage: CIImage) -> UIImage? {
    let context = CIContext()
    guard let cgImage = context.createCGImage(
        ciImage, from: ciImage.extent
    ) else {
        return nil
    }
    return UIImage(cgImage: cgImage)
}

注意:CIContext 创建开销较大,实际使用中应该复用同一个实例,不要每次调用都新建。

SwiftUI 集成

@Observable 封装整个流程:

@Observable
class SubjectExtractor {
    var resultUIImage?
    private let context = CIContext()
    func extract(from image: UIImageasync {
        guard let ciInput = CIImage(image: image) else {
            return
        }
        do {
            let mask = try makeForegroundMask(from: ciInput)
            let masked = applyMask(mask, to: ciInput)
            result = render(masked)
        } catch {
            print("Extraction failed: \(error)")
        }
    }
}

在用户选择图片后调用 extract,处理完成后 result 就是去掉背景的图片。

注意事项

  • 需要 iOS 17+
  • 完全本地推理,不需要网络
  • 对人物、动物、物品的识别效果较好
  • 复杂背景或半透明物体(玻璃、烟雾)效果可能不理想
  • 处理高分辨率图片时建议放到后台线程

最后

几行代码就能实现系统级的抠图效果,你打算在什么场景用?评论区聊聊。

这里每天分享一个 iOS 的新知识,快来关注我吧