Detect Body and Hand Pose with Vision
Machine Learning & AI 进阶 17m

使用 Vision 检测身体和手部姿态

Detect Body and Hand Pose with Vision

2020年6月24日

在 Apple 官方观看视频

一句话判断

Vision 框架在 iOS 14 中新增了人体姿态估计和手部关节检测——17 个身体关键点 + 21 个手部关节,全部在设备端实时运行,AR 和健身类 app 可以直接拿来用,不需要自己训练模型。

这场 Session 讲了什么

iOS 14 的 Vision 框架新增了三个关键的人体分析能力:Body Pose Estimation(身体姿态估计)、Hand Pose Estimation(手部关节检测)以及基于姿态的动作分类。

Body Pose 可以从图像或视频帧中检测 17 个人体关键点(头、肩、肘、腕、髋、膝、踝等),每个点包含位置坐标和置信度。Hand Pose 更精细,检测单只手的 21 个关节点,包括每根手指的各个关节弯曲角度。这些检测全部在 Neural Engine 上运行,帧率可达 30fps 以上。

Session 演示了如何用 VNDetectHumanBodyPoseRequestVNDetectHumanHandPoseRequest 获取关键点数据,以及如何基于连续帧的关键点序列做动作识别(比如识别「挥手」「点赞」等手势)。

值得深挖的点

关键点的置信度很重要

每个关键点都带有 confidence 值(0-1)。当人体被遮挡或画面模糊时,某些关键点的置信度会很低。Session 建议设置一个阈值(推荐 0.3 以上),过滤掉低置信度的点再做后续计算。直接用低置信度点算角度或距离,结果会很不稳定。

从关键点到动作识别的路径

Vision 本身不直接提供动作分类。但你可以用连续帧的关键点序列作为 Create MLMLActionClassifier 的输入。苹果提供了从 Vision 关键点到 MLActionClassifier 训练数据的转换工具链:先采集视频,用 Vision 提取每帧的关键点序列,标注动作类型,然后训练分类器。整个流程不需要 GPU 集群,在 Mac 上就能完成。

代码片段

import Vision

// 检测身体姿态
func detectBodyPose(in image: CIImage) {
    let request = VNDetectHumanBodyPoseRequest { request, error in
        guard let observations = request.results as? [VNHumanBodyPoseObservation],
              let bodyPose = observations.first else { return }
        
        // 获取所有关键点
        let recognizedPoints = try? bodyPose.recognizedPoints(.all)
        
        // 提取特定关键点
        if let leftWrist = recognizedPoints?[.leftWrist],
           leftWrist.confidence > 0.3 {
            print("左手腕位置: \(leftWrist.location)")
        }
        
        if let rightElbow = recognizedPoints?[.rightElbow],
           rightElbow.confidence > 0.3 {
            print("右手肘位置: \(rightElbow.location)")
        }
    }
    
    let handler = VNImageRequestHandler(ciImage: image)
    try? handler.perform([request])
}
import Vision

// 检测手部关节
func detectHandPose(in pixelBuffer: CVPixelBuffer) {
    let request = VNDetectHumanHandPoseRequest { request, error in
        guard let observations = request.results as? [VNHumanHandPoseObservation],
              let handPose = observations.first else { return }
        
        // 获取所有 21 个手部关节点
        let allPoints = try? handPose.recognizedPoints(.all)
        
        // 提取指尖关键点
        let fingertips: [VNHumanHandPoseObservation.JointName] = [
            .thumbTip, .indexTip, .middleTip, .ringTip, .littleTip
        ]
        
        for tip in fingertips {
            if let point = allPoints?[tip], point.confidence > 0.3 {
                print("指尖 \(tip.rawValue): \(point.location)")
            }
        }
    }
    
    // 限制最多检测 2 只手
    request.maximumHandCount = 2
    
    let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
    try? handler.perform([request])
}
// 计算两个关键点之间的角度(用于手势判断)
func angleBetween(_ point1: CGPoint, _ point2: CGPoint, _ point3: CGPoint) -> Float {
    let vector1 = CGPoint(x: point1.x - point2.x, y: point1.y - point2.y)
    let vector2 = CGPoint(x: point3.x - point2.x, y: point3.y - point2.y)
    
    let dot = vector1.x * vector2.x + vector1.y * vector2.y
    let cross = vector1.x * vector2.y - vector1.y * vector2.x
    return atan2(cross, dot)
}

// 判断手指是否弯曲(用关节角度)
func isFingerBent(tip: RecognizedPoint, dip: RecognizedPoint, pip: RecognizedPoint) -> Bool {
    let angle = angleBetween(tip.location, dip.location, pip.location)
    return abs(angle) < .pi / 3  // 弯曲角度小于 60 度
}

最佳实践

  1. 实时场景用 AVCaptureSession 配合 Vision。每帧用 VNImageRequestHandler 处理 CVPixelBuffer,在 captureOutput 回调中执行。注意不要在主线程跑 Vision 请求,用单独的 dispatch queue。
  2. 关键点要做平滑处理。原始关键点会有帧间抖动,用低通滤波(exponential smoothing)或卡尔曼滤波器平滑位置数据,否则基于关键点的 UI 会不停抖动。
  3. 身体和手部检测不要同时开。两个请求都跑在 Neural Engine 上,同时执行会争抢资源,帧率减半。根据场景切换——全身场景用 Body Pose,近景交互用 Hand Pose。

还有什么值得关注

  • Vision 的 Body Pose 和 Hand Pose 在 iPadOS 上支持通过摄像头实时检测,在 macOS 上只支持静态图片。
  • 关键点的坐标是基于归一化坐标系(0-1),需要乘以图片宽高才能得到像素坐标。
  • 苹果在 Create ML 中提供了 Action Classification 模板,可以直接用 Vision 提取的关键点序列训练动作分类器。
WWDC 2020