在应用中从图片提取主体
Lift subjects from images in your app
2023年6月5日
一句话判断
iOS 16 引入的”一键抠图”功能现在向第三方 App 开放了——VisionKit 提供开箱即用的交互体验,Vision 框架提供更底层的像素级控制,两个 API 覆盖了从简单到复杂的使用场景。
这场 Session 讲了什么
这场 Session 介绍如何将系统级的”从图片中提取主体”能力集成到自己的 App 中。分两个层次:
VisionKit 层(上手简单):
ImageAnalysisInteraction(iOS)/ImageAnalysisOverlayView(macOS)——添加到图片视图上就能获得和系统相册一样的长按抠图交互。- 交互类型可通过
preferredInteractionTypes配置:.automatic包含抠图、实况文本和数据检测器,.imageSubject只包含抠图。 - 通过
ImageAnalyzer可以编程式获取图片主体信息:subjects属性返回所有主体列表,subject(at:)按坐标查询单个主体,highlightedSubjects管理选中状态。 - 主体图像通过
Subject.image获取单主体,image(for:)获取多主体合成图像。 - 处理在进程外执行,性能好但图片分辨率有限。
Vision 层(更灵活):
VNGenerateForegroundInstanceMaskRequest——生成前景实例的分割掩码。- 类别无关(class agnostic):任何前景物体都能被分割,不限于人或宠物。
- 输出包括:soft segmentation mask(与原图同分辨率)、instance mask(像素级实例标签)。
- 处理在进程内执行,不受分辨率限制,可集成到 CoreImage 等图像处理管线中。
- 支持从同一请求生成多个不同质量的输出。
Session 中的 Demo 展示了一个拼图 App,用 VisionKit 的主体提取实现拼图块的选取和拖拽,还加了悬浮阴影效果增强立体感。
值得深挖的点
两个框架的分工边界设计得很好。VisionKit 适合”我只想让用户能抠图”的场景,三行代码就能接入;Vision 适合”我需要拿到分割掩码做后续处理”的场景。前者是高层封装,后者是底层能力,不冲突。
Vision 的 instance mask 概念值得细看。它给每个前景物体分配一个唯一 ID(从 1 开始,0 是背景),输出是一个与原图等大的整数矩阵。你可以基于这个矩阵做非常细粒度的操作——比如只对特定物体做特效、统计画面中的物体数量、或者做语义分割的前处理。
“主体”的定义比想象的更灵活。它不一定是单个物体——人和他的宠物可以被视为一个组合主体,三杯咖啡可以是三个独立主体。底层算法会根据视觉焦点来判断分割粒度。
Vision 和 CoreImage 管线的配合是个被低估的亮点。分割掩码可以作为 CoreImage filter 的输入,实现背景虚化、前景高亮、局部调色等效果,全程在 GPU 上完成。
代码片段
VisionKit 快速接入主体提取:
// iOS:添加到图片视图即可启用系统抠图交互
let interaction = ImageAnalysisInteraction()
interaction.preferredInteractionTypes = .imageSubject // 只启用抠图,不启用实况文本
imageView.addInteraction(interaction)
// 用户现在可以长按图片中的物体进行抠图
编程式获取主体信息:
// 创建分析器并分析图片
let analyzer = ImageAnalyzer()
let analysis = try await analyzer.analyze(image, configuration: .imageSubject)
// 获取所有主体
let subjects = analysis.subjects
// 根据坐标查找主体
if let subject = try? await analysis.subject(at: touchPoint) {
// 获取主体图像
let subjectImage = subject.image
// 获取主体在原图中的边界
let bounds = subject.bounds
}
// 获取多个主体的合成图像
let combinedImage = try await analysis.image(for: selectedSubjects)
Vision 框架生成分割掩码:
import Vision
// 创建前景实例掩码请求
let request = VNGenerateForegroundInstanceMaskRequest()
// 执行请求
let handler = VNImageRequestHandler(cgImage: sourceImage)
try handler.perform([request])
if let result = request.results?.first {
// 生成与原图同分辨率的分割掩码
let mask = try result.generateScaledMaskForImage(
forInstances: result.allInstances,
from: handler
)
// mask 可用于 CoreImage 管线做后续处理
}
最佳实践
- 先评估用 VisionKit 还是 Vision。如果你的需求是”让用户能在 App 内抠图”,VisionKit 就够了。如果你需要拿到分割数据做图像处理,用 Vision。
- VisionKit 的
subject(at:)在没有主体时返回 nil,记得做 nil 检查。 - Vision 的分割掩码是 soft mask,边缘有半透明过渡。如果你需要硬边缘的二值掩码,需要自己做阈值处理。
- 主体提取的输入图片质量直接影响结果。模糊、低分辨率的图片分割效果会差很多。
- 如果需要提取多个主体合成一张图,用
image(for:)而不是逐个提取再拼接——前者在框架层做了优化。 - Vision 的 instance mask 中 ID 顺序不保证,不要依赖 ID 值来判断物体类型或位置。
还有什么值得关注
- Vision 框架还提供了 saliency request(显著性分析)和 person segmentation(人物分割)两种相关的分割 API,各有适用场景。
- 新的 person instance segmentation API 可以给画面中的每个人单独的掩码,这在合影编辑、视频会议背景替换等场景很有用。
- 主体提取在 macOS 上同样可用,macOS App 可以用
ImageAnalysisOverlayView接入。 - 分割结果缓存策略值得关注——对同一张图片重复分析是浪费,建议在 App 层做好缓存。
- 搭配 CoreImage 的 CIBlendWithMask 等 filter,可以实现丰富的图像编辑功能。