这里每天分享一个 iOS 的新知识,快来关注我吧
前言
iOS 16 的时候苹果在照片 App 里加了「抠图」功能——长按主体就能把前景从背景中分离出来,相信很多人都用过了。
iOS 17 开始,这个能力通过 Vision 框架暴露给了开发者。
今天来看看怎么用 VNGenerateForegroundInstanceMaskRequest 在 Swift 中实现图片去背景,纯本地推理,不需要服务器。
整体流程
去背景分三步:
生成前景遮罩:Vision 框架识别前景主体,输出像素级 mask 应用遮罩:用 Core Image 的 blend filter 把背景替换为透明 渲染输出:CIContext 转成 UIImage
第一步:生成前景遮罩
func makeForegroundMask(from ciImage: CIImage) throws -> 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 result: UIImage?
private let context = CIContext()
func extract(from image: UIImage) async {
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 的新知识,快来关注我吧
夜雨聆风