AirPods Proのモーションセンサーを使って体の動きを機械学習で判定する
iOS14からAirPods Proのモーションデータが取得できるようになったので機械学習を使って体の動きが判定(分類)ができるか試してみた。下記の動画は歩いているのか止まっているのか認識(分類)している様子、まあまあの精度で分類できている。
iOSは機械学習のフレームワークCore MLを提供しているので機械学習の機能をアプリに組み込むことができる。機械学習で使うモデルは目的に合うものが配布されていればそのまま使うことができるし、Xcodeに付属しているCreate ML Appを使って自分でモデル作ることもできる。
Building Activity Classification Models in Create ML - WWDC 2019 - Videos - Apple Developer
Step
- モーションデータの収集
- Create MLで機械学習のモデルを作成
- アプリにモデルを組み込んで推論
モーションデータの収集
Activity Classificationするためにモーションデータを集める必要がある。Create MLが要求するモーションデータはcsv形式なので、適当なアプリを作ってファイルに記録する。
csvファイルへの記録については以下の記事に書いた。
AirPods Proのモーションデータをcsvファイルに記録する | Zenn
csvのフォーマットはAppleDeveloperを調べてみたがそれらしき情報はなかったがネットなどを調べた結果
- ヘッダー(1行目)にパラメータ名を並べる
- 2行目からデータを書いていく
- 1列目のタイムスタンプは無くても良い(WWDC19のビデオでは1列目がタイムスタンプ)
で良かった。実際に記録したデータは以下のような形式。
acceleration_x,acceleration_y,acceleration_z,attitude_pitch,attitude_roll,attitude_yaw,gravity_x,gravity_y,gravity_z,quaternion_x,quaternion_y,quaternion_z,quaternion_w,rotation_x,rotation_y,rotation_z
-0.14635148644447327,0.026090431958436966,0.0238814577460289,-0.6984695898076727,0.12187640638061611,-0.730099692747087,0.09310534596443176,0.643046498298645,-0.7601463794708252,-0.29860926058236176,0.17538322005864287,-0.35428882508981197,0.8686498537001848,0.05012949928641319,0.1775907427072525,0.5151610374450684
-0.14463886618614197,0.027029864490032196,0.059267524629831314,-0.6917567467141371,0.14565635873613061,-0.7014744399598024,0.11177759617567062,0.6378911733627319,-0.7619715929031372,-0.2940190022798997,0.18046330251483386,-0.3455520574210765,0.8726853507344794,0.13838644325733185,0.1492508351802826,0.6365307569503784
-0.12410151958465576,0.04369588941335678,0.05246793106198311,-0.6815905538054416,0.17229856747404868,-0.6697355597521057,0.13314111530780792,0.6300290822982788,-0.7650730609893799,-0.2878492666197985,0.18602752457871397,-0.3357562097792261,0.8773849107929436,0.18613776564598083,0.19000323116779327,0.674749493598938
(以下続く...)
データを収集する際は以下の点を考慮しておくと良い。
- ヘッダーのカラム名がCreate ML/Core MLのパラメータ名になるので分かりやすい名前にする
- 学習に使うパラメータを後で変えられるようにできるだけ多くのパラメータを記録しておく
今回は歩いているの状態と止まっている状態を識別するために、2秒間のモーションデータを、それぞれ100ファイル程度記録した。
Create MLで機械学習のモデルを作成
以下のようにcsvをフォルダに放り込んでCreate ML AppでActivity Classificationを行う。
モデルを作成する際にどのセンサーをの値を利用するのかは手動で選択する必要がある。今回は試行錯誤した結果accelerationの値のみを使って学習した。
トレーニングの結果。
終了するとモデルがダウンロードできるので推論するアプリにコピーする。
アプリにモデルを組み込んで推論
ソースコード
yorifuji/airpods-motion-classifier
コードの要点
CMHeadphoneMotionManagerの初期化とデバイスモーションの取得
let hmm = CMHeadphoneMotionManager()
if !hmm.isDeviceMotionAvailable {
print("current device does not supports the headphone motion manager.")
return
}
hmm.startDeviceMotionUpdates(to: .main) { (motion, error) in
if let motion = motion {
self.classifier.process(deviceMotion: motion)
}
if let error = error {
print(error)
}
}
モデルのロード、HumanActivityClassifier_30は自分が作成したモデルのクラス名
static let configuration = MLModelConfiguration()
let model = try! HumanActivityClassifier_30(configuration: configuration)
センサー情報を格納する配列、配列のサイズはCreate MLで学習する際のウィンドウサイズに合わせる(以下の例ではpredictionWindowSize = 100としている)
static let predictionWindowSize = 100
let acceleration_x = try! MLMultiArray(shape: [predictionWindowSize] as [NSNumber], dataType: MLMultiArrayDataType.double)
let acceleration_y = try! MLMultiArray(shape: [predictionWindowSize] as [NSNumber], dataType: MLMultiArrayDataType.double)
let acceleration_z = try! MLMultiArray(shape: [predictionWindowSize] as [NSNumber], dataType: MLMultiArrayDataType.double)
センサー情報の格納
func process(deviceMotion: CMDeviceMotion) {
if predictionWindowIndex == HeadphoneMotionClassifier.predictionWindowSize {
return
}
acceleration_x[[predictionWindowIndex] as [NSNumber]] = deviceMotion.userAcceleration.x as NSNumber
acceleration_y[[predictionWindowIndex] as [NSNumber]] = deviceMotion.userAcceleration.y as NSNumber
acceleration_z[[predictionWindowIndex] as [NSNumber]] = deviceMotion.userAcceleration.z as NSNumber
predictionWindowIndex += 1
if predictionWindowIndex == HeadphoneMotionClassifier.predictionWindowSize {
DispatchQueue.global().async {
self.predict()
DispatchQueue.main.async {
self.predictionWindowIndex = 0
}
}
}
}
推論と結果の取得
private func predict() {
let input = HumanActivityClassifier_30Input(
acceleration_x: acceleration_x,
acceleration_y: acceleration_y,
acceleration_z: acceleration_z)
guard let result = try? model.prediction(input: input) else { return }
let sorted = result.labelProbability.sorted {
return $0.value > $1.value
}
delegate?.motionDidDetect(results: sorted)
}
参考情報
shu223/iOS-Experiments-Contents: Table of Contents of my repo "iOS-Experiments"
(ソースコードへのアクセスはGitHub Sponsorsのみ)
Discussion