使用 Vision 检测身体和手部姿态
Detect Body and Hand Pose with Vision
2020年6月24日
一句话判断
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 演示了如何用 VNDetectHumanBodyPoseRequest 和 VNDetectHumanHandPoseRequest 获取关键点数据,以及如何基于连续帧的关键点序列做动作识别(比如识别「挥手」「点赞」等手势)。
值得深挖的点
关键点的置信度很重要
每个关键点都带有 confidence 值(0-1)。当人体被遮挡或画面模糊时,某些关键点的置信度会很低。Session 建议设置一个阈值(推荐 0.3 以上),过滤掉低置信度的点再做后续计算。直接用低置信度点算角度或距离,结果会很不稳定。
从关键点到动作识别的路径
Vision 本身不直接提供动作分类。但你可以用连续帧的关键点序列作为 Create ML 中 MLActionClassifier 的输入。苹果提供了从 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 度
}
最佳实践
- 实时场景用 AVCaptureSession 配合 Vision。每帧用
VNImageRequestHandler处理CVPixelBuffer,在captureOutput回调中执行。注意不要在主线程跑 Vision 请求,用单独的 dispatch queue。 - 关键点要做平滑处理。原始关键点会有帧间抖动,用低通滤波(exponential smoothing)或卡尔曼滤波器平滑位置数据,否则基于关键点的 UI 会不停抖动。
- 身体和手部检测不要同时开。两个请求都跑在 Neural Engine 上,同时执行会争抢资源,帧率减半。根据场景切换——全身场景用 Body Pose,近景交互用 Hand Pose。
还有什么值得关注
- Vision 的 Body Pose 和 Hand Pose 在 iPadOS 上支持通过摄像头实时检测,在 macOS 上只支持静态图片。
- 关键点的坐标是基于归一化坐标系(0-1),需要乘以图片宽高才能得到像素坐标。
- 苹果在
Create ML中提供了 Action Classification 模板,可以直接用 Vision 提取的关键点序列训练动作分类器。