iOS11引入的Vision Framework,这几年在不断的添加新能力。今年新增的能力比较多,苹果直接整了一个完整的App和文档,来综合的展示新的视觉能力,文章本身是对这个demo App和文档的解读和翻译,demo app的链接在文末。
丢沙包游戏
这个游戏貌似是老美很常见的一个游戏,主要元素是:
- 一个人,几个沙包
- 一个带有洞的木板(2*4英尺大小的木板,6英寸直径的洞)
- 游戏规则就是人站在25英尺外扔沙包
- 碰到木板或者进洞就算得分
Action & Vision
苹果以丢沙包这个游戏为例,写了一个名字叫Action & Vision的App,通过iOS的视觉能力来帮助用户分析游戏行为和统计分数。实际运行效果如下。
业务流程和代码
- Vision基础
所有Vision能力,命名都是一个VNXXXRequest,比如VNDetectHumanBodyPoseRequest。每当我们需要使用一个能力,我们需要创建一个request,并且使用一个VNImageRequestHandler,来输入图片,以及需要使用的[request]能力数组,最后,我们调用handle.perform(request),来执行实际的预测,并冲request中获取结果,即一个observation.
// 创建request
private let detectPlayerRequest = VNDetectHumanBodyPoseRequest()
// 输入图片并执行request
let visionHandler = VNImageRequestHandler(cmSampleBuffer: buffer, orientation: orientation, options: [:])
// 获取结果
let results = self.detectTrajectoryRequest.results as? [VNTrajectoryObservation]
- 游戏前的定位和分析。
1.1 首先需要识别木板的位置,苹果使用了Create ML自己创建了一个模型,来识别木板。
1.2 然后通过VNDetectContoursRequest,来确定木板的边缘,确定像素和实际物体长宽的对应关系,来确定洞在木板和图片中的位置。因为在1.1中已经识别了木板,实际使用VNDetectContoursRequest的过程是,把1.1中识别的木板的bouding box拿到,在使用VNDetectContoursRequest时指定regionOfInterest,减少噪声提高效率。
let contoursRequest = VNDetectContoursRequest()
contoursRequest.regionOfInterest = boardBoundingBox.visionRect
- 确定画面的稳定性
之前的普遍做法是,使用陀螺仪,隔一段时间计算陀螺仪的参数diff是否大于一个自己的阈值,来判断用户或者画面是否稳定。现在可以使用苹果提供的新API - VNTranslationalImageRegistrationRequest,用判断两张图仿射变换xy的距离来判断画面的稳定性。
利用VNTranslationalImageRegistrationRequest获取反射变换transform
private func checkSceneStability(_ controller: CameraViewController, _ buffer: CMSampleBuffer, _ orientation: CGImagePropertyOrientation) throws {
guard let previousBuffer = self.previousSampleBuffer else {
self.previousSampleBuffer = buffer
return
}
let registrationRequest = VNTranslationalImageRegistrationRequest(targetedCMSampleBuffer: buffer)
try sceneStabilityRequestHandler.perform([registrationRequest], on: previousBuffer, orientation: orientation)
self.previousSampleBuffer = buffer
if let alignmentObservation = registrationRequest.results?.first as? VNImageTranslationAlignmentObservation {
let transform = alignmentObservation.alignmentTransform
sceneStabilityHistoryPoints.append(CGPoint(x: transform.tx, y: transform.ty))
}
}
判断transform x,y,确定画面是否稳定
var sceneStability: SceneStabilityResult {
// Determine if we have enough evidence of stability.
guard sceneStabilityHistoryPoints.count > sceneStabilityRequiredHistoryLength else {
return .unknown
}
// Calculate the moving average by adding up values of stored points
// returned by VNTranslationalImageRegistrationRequest for both axis
var movingAverage = CGPoint.zero
movingAverage.x = sceneStabilityHistoryPoints.map { $0.x }.reduce(.zero, +)
movingAverage.y = sceneStabilityHistoryPoints.map { $0.y }.reduce(.zero, +)
// Get the moving distance by adding absolute moving average values of individual axis
let distance = abs(movingAverage.x) + abs(movingAverage.y)
// If the distance is not significant enough to affect the game analysis (less that 10 points),
// we declare the scene being stable
return (distance < 10 ? .stable : .unstable)
}
-
识别进入画面的玩家(人体识别)
使用VNDetectHumanBodyPoseRequest来获取人体和姿势数据。
3.1 根据observation.confidence结果,判断是否有人,根据observation.recognizedPoints,把points的结果合并,返回人的CGRect。
3.2 转换一下recognizedPoints的格式,根据部分点位,画出人的姿态。
这个recognizedPoints实际的数据是这样的
left_hand_joint
[0.201963; 0.587417]
然后你需要赛选几个你想要绘制的点,使用bezier path将他们链接,绘制,然后更新他们的位置。
3.3 姿势检测,基本原理是通过create ML训练一个模型来分来。步骤是:获取一张图,获取图中人的姿势结果,也就是通过VNDetectHumanBodyPoseRequest获取到observation,observation中有姿势相关的point,将他们构造一个MLMultiArray作为输入,使用core ML跑模型,来进行姿势分类。
-
检测投掷沙包的抛物线
使用VNDetectTrajectoriesRequest来检测抛物线轨迹。因为抛物线是多个点组成的,所以一个VNDetectTrajectoriesRequest实例需要一系列的视频帧来多次执行,获取到足够多的点,然后执行完成回调。Demo中把这些抛物线的点连成一条曲线,绘制出来。
private lazy var detectTrajectoryRequest: VNDetectTrajectoriesRequest! =
VNDetectTrajectoriesRequest(frameAnalysisSpacing: .zero, trajectoryLength: GameConstants.trajectoryLength)
try visionHandler.perform([self.detectTrajectoryRequest])
let results = self.detectTrajectoryRequest.results as? [VNTrajectoryObservation]
let trajectory = UIBezierPath()
for point in points.dropFirst() {
trajectory.addLine(to: point.location)
}
// 然后绘制 draw path
Building a Feature-Rich App for Sports Analysis
丢沙包 - Cornhole