iOS - 苹果的Vision综合实践

iOS11引入的Vision Framework,这几年在不断的添加新能力。今年新增的能力比较多,苹果直接整了一个完整的App和文档,来综合的展示新的视觉能力,文章本身是对这个demo App和文档的解读和翻译,demo app的链接在文末。

丢沙包游戏

iOS - 苹果的Vision综合实践_第1张图片
丢沙包游戏

这个游戏貌似是老美很常见的一个游戏,主要元素是:

  1. 一个人,几个沙包
  2. 一个带有洞的木板(2*4英尺大小的木板,6英寸直径的洞)
  3. 游戏规则就是人站在25英尺外扔沙包
  4. 碰到木板或者进洞就算得分

Action & Vision

苹果以丢沙包这个游戏为例,写了一个名字叫Action & Vision的App,通过iOS的视觉能力来帮助用户分析游戏行为和统计分数。实际运行效果如下。

Action & Vision

业务流程和代码

  1. 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.1 首先需要识别木板的位置,苹果使用了Create ML自己创建了一个模型,来识别木板。
    1.2 然后通过VNDetectContoursRequest,来确定木板的边缘,确定像素和实际物体长宽的对应关系,来确定洞在木板和图片中的位置。因为在1.1中已经识别了木板,实际使用VNDetectContoursRequest的过程是,把1.1中识别的木板的bouding box拿到,在使用VNDetectContoursRequest时指定regionOfInterest,减少噪声提高效率。
        let contoursRequest = VNDetectContoursRequest()
        contoursRequest.regionOfInterest = boardBoundingBox.visionRect
  1. 确定画面的稳定性
    之前的普遍做法是,使用陀螺仪,隔一段时间计算陀螺仪的参数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)
    }
  1. 识别进入画面的玩家(人体识别)
    使用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跑模型,来进行姿势分类。

  1. 检测投掷沙包的抛物线
    使用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

你可能感兴趣的:(iOS - 苹果的Vision综合实践)