Classify hand poses and actions with Create ML
Machine Learning & AI 进阶 24m

使用 Create ML 分类手势和动作

Classify hand poses and actions with Create ML

2021年6月11日

在 Apple 官方观看视频

一句话判断

Create ML 的手势分类器(Hand Pose Classifier)让不擅长 ML 的 iOS 开发者也能做出手势识别功能——你只需要准备标注好的手势图片,剩下的交给 Create ML。

这场 Session 讲了什么

Vision 框架在 iOS 14 引入了 VNDetectHumanHandPoseRequest,可以检测手部 21 个关键点的位置。iOS 15 更进一步:Create ML 新增了 Hand Pose Classification 模板,允许你训练一个自定义的手势分类器,配合 Vision 的关键点检测实现端到端的手势识别。

工作流程分两步。Vision 先检测手部关键点,输出 21 个 (x, y) 坐标。然后把这些关键点数据输入到 Create ML 训练的手势分类器中,分类器判断这是什么手势(比如「竖大拇指」「比 V」「握拳」)。

Session 还介绍了 Hand Action Classification——不仅识别静态手势,还能识别连续动作(比如「挥手」「拍手」)。这需要一段视频序列作为输入,分类器会分析关键点随时间的变化模式。

值得深挖的点

为什么基于关键点的分类比直接图像分类更可靠

直接用图像做手势分类容易受光照、背景、肤色干扰。基于关键点的方法先提取手部骨骼结构,在第二维空间做分类,完全不受视觉外观影响。这意味着用深肤色手训练的模型同样适用于浅肤色手,用白天照片训练的模型晚上也能用。

训练数据的收集策略

收集手势训练数据不需要几千张图片。因为分类器是基于关键点的,每种手势只需要 100-200 个样本就够了。Session 建议用 app 内置的数据收集模式:提示用户做出特定手势,用 Vision 检测关键点,保存为 JSON 格式。这样训练数据直接反映用户的使用场景。

代码片段

// 使用 Vision 检测手部关键点
import Vision

class HandPoseDetector {
    let request = VNDetectHumanHandPoseRequest()
    
    func detect(image: CGImage) async throws -> [VNHumanHandPoseObservation] {
        request.maximumHandCount = 2
        
        let handler = VNImageRequestHandler(cgImage: image, options: [:])
        try handler.perform([request])
        
        return request.results ?? []
    }
    
    func getKeypoints(from observation: VNHumanHandPoseObservation) -> [CGPoint] {
        var points: [CGPoint] = []
        
        // 提取所有 21 个关键点
        do {
            let allPoints = try observation.recognizedPoints(.all)
            for (_, point) in allPoints.sorted(by: { $0.key.rawValue < $1.key.rawValue }) {
                if point.confidence > 0.3 {
                    points.append(point.location)
                }
            }
        } catch {
            print("提取关键点失败: \(error)")
        }
        
        return points
    }
}
// 使用训练好的手势分类器
import CoreML

class HandGestureClassifier {
    let model: HandPoseClassifier  // Create ML 训练的模型
    
    init() {
        self.model = try! HandPoseClassifier(configuration: MLModelConfiguration())
    }
    
    func classify(keypoints: [CGPoint]) -> String? {
        // 将关键点转换为模型输入格式
        guard keypoints.count == 21 else { return nil }
        
        let pointsArray = keypoints.flatMap { [$0.x, $0.y] }
        let mlMultiArray = try! MLMultiArray(
            shape: [1, 21, 2],
            dataType: .float32
        )
        
        for (i, value) in pointsArray.enumerated() {
            mlMultiArray[i] = NSNumber(value: Float(value))
        }
        
        let input = HandPoseClassifierInput(poses: mlMultiArray)
        guard let prediction = try? model.prediction(input: input) else {
            return nil
        }
        
        return prediction.classLabel
    }
}
// 使用 CreateMLComponents 在设备上训练手势分类器
import CreateMLComponents

func trainCustomGestureModel(
    trainingData: [(keypoints: MLMultiArray, label: String)]
) async throws -> SomeTabularClassifier {
    // 构建数据表
    var dataframe = DataFrame()
    dataframe.append(column: DataProviderColumn(
        name: "features",
        contents: trainingData.map { $0.keypoints }
    ))
    dataframe.append(column: DataProviderColumn(
        name: "label",
        contents: trainingData.map { $0.label }
    ))
    
    // 训练分类器
    let classifier = HandPoseClassifier()
    let model = try await classifier.fitted(to: dataframe)
    
    return model
}

最佳实践

手势识别的精度受手部关键点检测质量的影响很大。在低光或者手部被遮挡的场景下,Vision 返回的关键点置信度会很低。建议在分类之前先过滤掉置信度低于 0.3 的关键点,如果有效关键点少于 14 个,直接拒绝分类。

对于连续动作识别,建议收集 2-3 秒的视频片段作为训练样本。太短的动作模式不明显,太长会增加计算量。帧率 15-30 fps 就够了,不需要 60 fps。

还有什么值得关注

  • VNDetectHumanHandPoseRequest 现在支持 handChirality 属性,可以区分左手和右手。
  • 手势识别可以配合 ARKit 使用,在 AR 场景中做手部交互。
  • Create ML 的 Hand Action Classifier 支持 snippetLength 参数,控制分析的时间窗口长度。
WWDC 2021