使用 Create ML 分类手势和动作
Classify hand poses and actions with Create ML
2021年6月11日
一句话判断
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参数,控制分析的时间窗口长度。