ユーザーの手を識別してじゃんけんをします。
今後は手の認識の速度を向上させます。
現在、グーとパーの識別率はほぼ100%ですが、チョキの識別率が約90%なので、チョキの識別率を100%に近づけます。
ファイル名 |
概要・解説 |
StageView |
キャラクターと難易度をScrolleViewで表示するViewです。 |
HandGestureView |
手を検知してじゃんけんをするViewです。 |
ResultView |
じゃんけんの結果を表示するViewです。 |
BackgroundView |
StageViewの背景のViewです。GeometryReaderを用いてスクロールに応じて背景が動きます。 |
ファイル名 |
概要・解説 |
StageViewModel |
StageModelのインスタンス、ボタンを押した時の効果音メソッドを記述したクラス |
HandGestureViewModel |
勝率からゲーム結果を算出するメソッドや動画フレームから手のジェスチャーを検出するメソッドなどを記述したクラス |
ファイル名 |
概要・解説 |
StageModel |
キャラクターのレベルや初期勝率、逆転の有無などの情報を格納した構造体 |
SoundPlayer |
効果音を再生するメソッドなどを記述したクラス |
JankenTextModel |
じゃんけんの掛け声とタイミングのメソッドを記述したクラス |
HandGestureModel |
ゲーム終了を判定するメソッド、HPを計算するメソッドなどを記述したクラス |
HandGestureDetector |
動画フレームからVisionを用いてジェスチャーを判別するメソッドなどを記述したクラス |
開発初期段階では、じゃんけんの手を3000枚撮影して、CreateMLを用いてモデルを作成して、CoreMLを用いて手を判別しました。しかし3度モデルを作り直しても判別の制度が良くならなかったのでVisionのみで手の判別をすることにしました。
Visionでは、手の骨格の座標情報が取得できます。そこで、以下のような手順で手の判別を行うことで精度を向上させました。
・手順1:Visionを用いて指先、第二関節、手首の座標を取得
|
// 指先 |
|
let indexTip = points[VNHumanHandPoseObservation.JointName.indexTip.rawValue]?.location ?? .zero |
|
let middleTip = points[VNHumanHandPoseObservation.JointName.middleTip.rawValue]?.location ?? .zero |
|
let ringTip = points[VNHumanHandPoseObservation.JointName.ringTip.rawValue]?.location ?? .zero |
|
let littleTip = points[VNHumanHandPoseObservation.JointName.littleTip.rawValue]?.location ?? .zero |
|
// 近位指節間(PIP)関節 = 第二関節のこと |
|
let indexPIP = points[VNHumanHandPoseObservation.JointName.indexPIP.rawValue]?.location ?? .zero |
|
let middlePIP = points[VNHumanHandPoseObservation.JointName.middlePIP.rawValue]?.location ?? .zero |
|
let ringPIP = points[VNHumanHandPoseObservation.JointName.ringPIP.rawValue]?.location ?? .zero |
|
let littlePIP = points[VNHumanHandPoseObservation.JointName.littlePIP.rawValue]?.location ?? .zero |
|
// 手首 |
|
let wrist = points[VNHumanHandPoseObservation.JointName.wrist.rawValue]?.location ?? .zero |
・手順2:三平方の定理より2点の距離を求めるメソッド作成
|
// 画面上の2点間の距離を三平方の定理より求める |
|
private func distance(from: CGPoint, to: CGPoint) -> CGFloat { |
|
return sqrt(pow(from.x - to.x, 2) + pow(from.y - to.y, 2)) |
|
} |
・手順3:手首から親指以外の指までの距離を求める
|
// 手首から指先の長さ |
|
let wristToIndexTip = distance(from: wrist, to: indexTip) |
|
let wristToMiddleTip = distance(from: wrist, to: middleTip) |
|
let wristToRingTip = distance(from: wrist, to: ringTip) |
|
let wristToLittleTip = distance(from: wrist, to: littleTip) |
|
|
|
// 手首から近位指節間(PIP)関節の長さ |
|
let wristToIndexPIP = distance(from: wrist, to: indexPIP) |
|
let wristToMiddlePIP = distance(from: wrist, to: middlePIP) |
|
let wristToRingPIP = distance(from: wrist, to: ringPIP) |
|
let wristToLittlePIP = distance(from: wrist, to: littlePIP) |
・手順4:どの指が曲がっているかによってグーチョキパーを判別
|
// HandPoseの判定(どの指が曲がっているかでグーチョキパーを判定する) |
|
if |
|
wristToIndexTip > wristToIndexPIP && |
|
wristToMiddleTip > wristToMiddlePIP && |
|
wristToRingTip > wristToRingPIP && |
|
wristToLittleTip > wristToLittlePIP { |
|
// 4本の指が曲がっていないのでぱー |
|
currentGesture = .paper |
|
} else if |
|
wristToIndexTip < wristToIndexPIP && |
|
wristToMiddleTip < wristToMiddlePIP && |
|
wristToRingTip < wristToRingPIP && |
|
wristToLittleTip < wristToLittlePIP { |
|
// 4本の指が曲がっているのでぐー |
|
currentGesture = .rock |
|
} else if |
|
wristToIndexTip > wristToIndexPIP && |
|
wristToMiddleTip > wristToMiddlePIP { |
|
// IndexとMiddleが曲がっていないのでちょき |
|
currentGesture = .scissors |
|
} else { |
|
currentGesture = .unknown |
|
} |
アプリのホームページ
制作者Twitter