早安!來欣賞一部好作品吧!
THE MOON RUNNERS | ICY | FIVE TUTS
Day 28 我們討論了『練習』是所有舞蹈的核心!
今天要往前一步思考,如何將『練習』轉化為可數位化執行的工具,
因此把先前探討的 MediaPipe Pose + Tutting 模擬器 整合成一個能輔助舞者訓練的系統。
[攝影機]
↓
[MediaPipe Pose 偵測]
↓
[關節 Mapping → 火柴人骨架]
↓
[訓練模組]
├─ 角度比對 (90°/180°)
├─ 對稱比對 (左右手誤差)
├─ 框架檢查 (矩形完整度)
├─ 流暢度分析 (角度變化平滑度)
↓
[回饋系統]
├─ 即時顯示分數
├─ 標記誤差部位 (紅/綠線)
├─ 給予修正提示 (ex: 「左手肘太高」)
用「向量餘弦定理」計算關節角度,例如手肘:
function getAngle(a, b, c) {
const ab = {x: a.x - b.x, y: a.y - b.y};
const cb = {x: c.x - b.x, y: c.y - b.y};
const dot = ab.x * cb.x + ab.y * cb.y;
const magAB = Math.sqrt(ab.x**2 + ab.y**2);
const magCB = Math.sqrt(cb.x**2 + cb.y**2);
return Math.acos(dot / (magAB * magCB)) * (180 / Math.PI);
}
if (Math.abs(leftAngle - 90) < 10) { ✅ } else { ❌ }
if (Math.abs(lm[15].y - lm[16].y) < 0.05) { ✅ }
function isRectangle(p1, p2, p3, p4) {
// 確認 (p1→p2) 和 (p3→p4) 平行
// 確認 (p2→p3) 和 (p4→p1) 平行
// 並檢查內角接近 90°
}
getSmoothness() {
if (this.history.length < 3) return 100; // 初始狀態滿分
let diffs = [];
for (let i = 1; i < this.history.length; i++) {
diffs.push(Math.abs(this.history[i] - this.history[i-1]));
}
// 平均變化量 (每幀角度變化)
let avgDiff = diffs.reduce((a,b)=>a+b,0) / diffs.length;
// 分數轉換:變化越小 → 越流暢
let score = Math.max(0, 100 - avgDiff * 5);
return Math.round(score);
}
整合後的 Tutting 訓練工具,可以做到:
這讓 Tutting 練習不再是單純的「照鏡子」,而是變成一個有數據、有目標、有回饋的系統。