🖐️

[CreateML]手のポーズを分類して、非接触でTwitterする

2021/12/02に公開約6,500字

AdventCalendar2021🎄

ポテトチップスはサワークリームオニオンが一番好きです。
サワークリームオニオン味のポテチを食べながら、MacBookAir(M1)でTwitterしようかな😊

...ん?こんなサワークリームオニオンでべっとり味ついた手でおニューのPCのトラックパッドさわりたくない。

でもTwitterは何がなんでもしたい。トラックパッドにさわらないでTLスクロールしたい。

そうだ、なんか手のポーズでTLスクロールできるようにすれば良さそう💡

ということで、今年のWWDC2021で紹介された「CreateMLによる手のポーズや動作の識別」を使って、非接触Twitterビューワーを作ってみました。

ブログ概要

このブログでは、CreateMLで手のポーズを識別するための機械学習モデルを作成し、それをアプリに組み込むまでを紹介します。作成したアプリはカメラやTwitterAPIを使ったりしてますが、その辺の実装は今回省略します🙏

実装はWWDC2021のセッション「CreateMLによる手のポーズや動作の識別」に沿って行なっています。実装したアプリの要件は違うので、全く同じではありませんが、基本的な実装方法は同じです。26分ほどの動画でちょっと長いですが、とても分かりやすいので、ご興味がある方は是非そちらもご覧ください。

アプリについて

手のポーズを識別する機械学習モデルを作成し、そのモデルを使って、特定のポーズをするとTwitterのタイムラインが下にスクロール(進む)され、また別のポーズをすると上にスクロール(戻る)するMacOSアプリ。

CreateML

CreateMLはAppleが用意しているデベロッパーツールの一つで、Apple系のアプリケーションで使用することができる機械学習モデルを作成することができます。

私は機会学習の実装は未経験で、知識としては、ゼロから作るDeep Learningを読んだことがあるぐらいです。それぐらいの知識でも、CreateMLを使えば、簡素な機械学習モデルを作成することができました。

テンプレート

CreateMLには、私の様な機械学習初心者でもモデルが作れる様に、様々なテンプレートが用意されています。

今回は手のポーズの識別がしたいので、この中の「Hand pose classification」を使いました。

ツール

CreateMLはXcodeのDeveloperToolsの中に含まれます。

起動して新しいプロジェクトを作成し、学習するデータを追加していきます。

学習データ集め

学習データ集めが今回一番時間がかかりました😩

まず、オープンデータとして人物の画像がないか探しました。顔やファッションのデータセットは存在しているようでしたが、今回目的としている何らかのポーズをとっている人物画像というのは見つけることができませんでした。なので、結局Google検索で使えそうな画像を拾い集めました。あたりまえの話ですが、この過程で目的と合致した良い画像を沢山集めることができれば、学習モデルの判定精度も上がってくると思います。

また、モデルを作成するにあたって、どんな手のポーズにするのか決めないといけませんが、学習データを集めるのが大変だと予想していたので、どんな手のポーズにするのかは決めず、一番学習データが集めやすいポーズを採用することにしていました。で、採用したのがこちら。

下にスクロール(進む)ポーズ 上にスクロール(戻る)ポーズ

画像は分類したいカテゴリでフォルダを分けて格納します。
今回作成したフォルダは「Next」「Back」「Background」の三つです。

「Next」には下にスクロール(進む)ポーズをしている人物画像を格納し、「Back」には上にスクロール(戻る)ポーズをしている人物の画像を格納します。「Background」には今回分類させたくないポーズが入っていて、これによって分類したいポーズとは違うポーズをしているということが認識できます。

「Background」のフォルダには、分類させたくないポーズの画像をランダムにいれます。これらの画像には多様な肌色や年齢、性別、照明条件が含まれている必要があるそうです。

また、こちらをご覧ください。

下にスクロール(進む)ポーズをするまえに、まず腕を上げていると思います。

アプリでポーズを認識する際はカメラで撮影している動画を使用します。動画はパラパラ漫画最強版みたいな感じで、何枚もの画像が次々に表示されているだけです。ポーズの判定は、そのパラパラ漫画の画像を何枚かごとに一枚一枚判定します。つまり、ポーズを取り終わった後にシャッターを押して今そのポーズなのかを判定するのではなくて、腕を上げる過程の動作も判定していくので、その過程の動作も「Background」として追加し、分類させたくないポーズであるということを教えてあげる必要があります。

試しに、「Background」無しのデータでモデルを作ってみました。
そもそも、ポーズの判定を行なったときに「判定できません」という結果はなく、必ず何かに分類するようになっています。今回だと必ず「Next」か「Back」のいずれかに分類するので、例えば、何もポーズをしていない状態の時は常に「Back」と判定をしてしまい、本当の「Back」のポーズがいつ行われているかわからなかったです😤

いざ、トレーニング💪

理想としては合計1500枚程度あると良さそうなんですが、本当に大変だったので今回は100枚程度しか集められませんでした。ただ、100枚でもそこそこの結果を得ることができたので、今回はとりあえずヨシとしました。データセットの集め方も、知見が欲しいですね...

その100枚のデータを学習させていきます。データをセットしたら、「Train」ボタンをポチるだけです。イテレーション(反復学習)はデフォルトの80回で、学習時間は1分以内で完了しました。

CreateMLが超便利だなと思ったのが、Previewという機能で、すぐにそのモデルの精度を確認できるところです。分類されている結果と、その確率が表示されます↓

https://twitter.com/k191k/status/1465879434603339779

私はこれで何度も確認して、学習データを精査して、判定精度を高める様にがんばりました😤

また、1つ気づいたことがありました。今回使用したテンプレートの「Hand pose classification」は手の関節位置を分類してどのポーズかを推論しているので、以下の二つのポーズをうまく分類することができず、同じポーズだと認識してしまいました。

ポーズ1 ポーズ2

私たちがみると、数字の「1」や、方角を指し示めすサインなど、色んな意味で解釈できますよね。人間ってすごい😆

最後に、Output機能でアプリに取り込めるフォーマットのモデルを出力して、Xcodeで使用していきます。

Xcodeで実装する

撮影から判定までの実装のパイプラインはこんな感じ。

判定処理の実装(上記画像の青枠部分)が以下になります。やってることは大きく二つ。

1. 撮影して取得したCVPixelBufferから、VisionのVNDetectHumanHandPoseRequestを使って手のポーズを検出する

    func session(pixelBuffer: CVPixelBuffer) -> HandPose? {
        
        let handPoseRequest = VNDetectHumanHandPoseRequest()
        handPoseRequest.maximumHandCount = 1
        handPoseRequest.revision = VNDetectHumanHandPoseRequestRevision1
        
        let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
        do {
            try handler.perform([handPoseRequest])
            guard let handObservation = handPoseRequest.results?.first else { return nil }
  1. 検出されたポーズを先ほど作ったモデルを基に分類して、結果を返却する
            if frameCounter % handPosePredictionInterval == 0 {
                guard let keypointsMultiArray = try? handObservation.keypointsMultiArray()
                else { fatalError() }
                                
                let model = try NextAndBackClassifier(configuration: MLModelConfiguration())
                let handPosePrediction = try model.prediction(poses: keypointsMultiArray)
                
                if handPosePrediction.labelProbabilities["back"] ?? 0 > 0.8 {
                    return .Back
                }
                
                if handPosePrediction.labelProbabilities["next"] ?? 0 > 0.8 {
                    return .Next
                }
            }
        } catch {
            assertionFailure("Request failed: \(error)")
        }
        return nil
    }

handPosePrediction.labelProbabilities にはこんな感じで値が入ってきます。

["next": 0.990234375, 
"back": 0.00864410400390625, 
"background": 0.0012683868408203125]

以上が、CreateMLで作った学習モデルを使った手のポーズ分類処理の実装です。シンプル。

あとは、分類結果を画面側に通知してあげて、結果によってリストをスクロールさせると、こんな感じになりました。

https://twitter.com/k191k/status/1465998354505031685

※タイムラインにしちゃうと他の人のツイートみれちゃうので、↑は一応私のツイートだけを流すようにしてます

まとめ

機械学習がより身近になったと感じました。もちろん、プロが作るものには程遠いですが、手軽に取り組めることで、知識を得る良いきっかけになったり、新しい発想に繋がるのではないかと思います。

また、WWDCでのセッションの冒頭で以下の話をされています。

手には20以上の骨や関節、筋肉があり、工学的にも驚くべき存在です。
この複雑さにもかかわらず、手は乳幼児が周りの世界に触れるための、最初に使う道具の一つなのです

手、すごくないですか?まじですごい✋
普段何も考えずに使っている手ですが、ちょっと違う角度で見てみると本当にすごいツールです。そんな無限の可能性を秘めた人間の手、それを活用したアプリケーションを作る手段の一つが、CreateMLだと思います。超ワクワクするので是非何か作ってみてください🧙‍♀️

今年もありがとうございました。
良いお年を〜😊

美味しかったです。背景に写ってるタバコと酒は私のじゃないです

Discussion

ログインするとコメントできます