HandPoseのkeypointsMultiArrayのデータを活用したい
iOS14以降ののCoreMLでは、画像情報から手や体の特徴点座標を取得することができます。この特徴点情報を活用して、さらにもう一段ポーズやアクションの機械学習・推論を行いたいと思い、CoreMLで得られるkeypointsMulitiArrayの構造について調べてみました。Vision frameworkおよびCoreMLを使ったポーズ推定の手順や、機械学習用データフォーマットであるMLMultiArrayについては、以下の記事が大変参考になりました。
Qiita MLMultiArrayとポインタ
WWDC2020 Detect Body and Hand Pose with Vision
ポーズ推定で得られる特徴点の情報は、
- 画像(CGImage)に対してVNImageRequestHandlerを設定
- handlerでVNDetectHumanHandPoseRequestを実行
- HandPoseRequest.resultsから、keypointsMultiArrayを取得
の手順で取得できます。エッセンスは以下のコードです。
let handPoseRequest = VNDetectHumanHandPoseRequest()
let handler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer, orientation: .up, options: [:])
do { try handler.perform([handPoseRequest])
guard let handPose = handPoseRequest.results else {return}
//keypointsMultiArrayで特徴点情報を取得できる
guard let keypointsMultiArray = try? handPose.first.keypointsMultiArray() else {return}
} catch {
//エラー処理
}
keypointsMultiArrayとは何者か?
このkeypointsMultiArrayのデータ構造については、公式サイトを見てもよく分かリませんでしたが、そんな時参考になったのが、CoreMLの姉妹パッケージ(?)CreateMLでした。
CreateMLは、iOS上でノンコードで機械学習できるアプリケーションで、その中の1つのHandPoseClassificationを使うと、さまざまな手の形を認識する推論モデルを作ることができるようになっています。生成された推論モデルの情報をみてみると以下の記載がありました。
Input
MultiArray (Float32 1 × 3 × 21)
A hand pose to classify. Its multiarray encoding uses the first dimension to index time over 1 frame. The second dimension indexes x, y, and confidence of hand pose keypoint locations. The last dimension indexes the keypoint type, ordered as wrist, thumbCMC, thumbMP, thumbIP, thumbTip, indexMCP, indexPIP, indexDIP, indexTip, middleMCP, middlePIP, middleDIP, middleTip, ringMCP, ringPIP, ringDIP, ringTip, littleMCP, littlePIP, littleDIP, littleTip.
つまり、HandPoseClassificationのinputデータは画像そのものではなく、特徴点の座標情報が格納された[1 x 3 x 21]のMultiArrayということになります。ちなみにkeypointsMultiArrayは
print(keypointsMultiArray.shape) //[1, 3, 21]
print(keypointsMultiArray.count) //63
print(keypointsMultiArray[0][0][0]) //MultiArrayといいつつ一次元配列で格納されているのでエラー
print(keypointsMultiArray[0]) //wristのx座標
print(keypointsMultiArray[21]) //wristのy座標
print(keypointsMultiArray[42]) //wristの信頼度
となっていて、上記のMultiArrayとkeypointsMultiArrayとは同一のものと考えて良さそうです。つまり、keypointsMultiArrayは1次元配列で、wrist, thumbCMC, thumbMP, thumbIP, thumbTip, indexMCP, indexPIP, indexDIP, indexTip, middleMCP, middlePIP, middleDIP, middleTip, ringMCP, ringPIP, ringDIP, ringTip, littleMCP, littlePIP, littleDIP, littleTipの順に最初の21点がx座標、次の21点がy座標、その次の21点が信頼度という形でデータが格納されていることがわかりました。ただし、
print(keypointsMultiArray[100]) //配列の要素数を超えるインデックスに関わらず、エラーにならず値を持っている。
要素数を超えた配列indexを指定してもエラーは起こらず、何らかの値を持っていることがわかります。
(普通ならOut of Range FatalErrorと怒られる)
不思議なことですが、最初の63データがHandPose推定の第一候補で、それ以降も第二候補以降のデータが格納されている可能性もあるのかもしれないと思いました。
ただいずれにせよ、更なる機械学習用inputとしては第一候補のみで十分ですので、最初の63データを活用していくことにします。これでHandPoseの座標情報を機械学習の入力データとして活用する準備ができました。