refactor: 移除 AI 跟练中没有作用的代码
- 删除从未显示的 idle 手势帧(小程序包内 png 和视频用高清帧),训练分包减少 17KB - 删除骨架连线 BONES、镜像函数 mirrorPose(骨架已去掉)和未使用的 Bgm 类型 - 只给单测用的 sampleMotion 移到测试目录 - 删除训练菜单里未使用的 .menu-icon-text 样式 - 补全 history 单测数据缺少的 adviceKind 字段(严格类型检查报错)
This commit is contained in:
@@ -477,11 +477,6 @@ $brand-light: #34d399;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.menu-icon-text {
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 哑铃小 icon */
|
||||
.dumbbell-shape {
|
||||
width: 40rpx;
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
* mode:
|
||||
* - demo 按示范节奏循环(speed 调快慢,paused 时站立待机、轻微呼吸)
|
||||
* - mirror 跟随用户实时动作幅度 amount / side
|
||||
* - gesture 挥手 / 欢呼 / 点赞 / 待机
|
||||
* - gesture 挥手 / 欢呼 / 点赞
|
||||
* 示范模式下教练每做完一次发出 rep 事件(领练模式据此计数,报数和画面同步)。
|
||||
* 动作中不插入欢呼 / 弹跳:和动作节奏对不上的突变,看起来就是“闪一下”。
|
||||
*
|
||||
@@ -67,7 +67,7 @@ const props = withDefaults(
|
||||
{
|
||||
move: 'squat',
|
||||
mode: 'demo',
|
||||
gesture: 'idle',
|
||||
gesture: 'wave',
|
||||
amount: 0,
|
||||
side: 'L',
|
||||
speed: 1,
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { createCounter, type CoachEvent } from '../counters.ts'
|
||||
import { MISTAKE_POSES, MOTIONS, sampleMotion, type MoveId } from '../motions.ts'
|
||||
import { MISTAKE_POSES, MOTIONS, type MoveId } from '../motions.ts'
|
||||
import { sampleMotion } from './sampleMotion.ts'
|
||||
import type { Pose } from '../keypoints.ts'
|
||||
|
||||
const MOVES: MoveId[] = ['squat', 'march', 'jack', 'raise']
|
||||
|
||||
@@ -19,6 +19,7 @@ const summary = (reps: number): SessionSummary => ({
|
||||
stars: 3,
|
||||
scores: [],
|
||||
advice: '',
|
||||
adviceKind: 'praise',
|
||||
})
|
||||
|
||||
const DAY = 86400000
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/** 单测辅助:把示范动作按时间采样成 Pose 序列(文件名不带 .test,不会被当成用例执行) */
|
||||
import type { Pose } from '../keypoints.ts'
|
||||
import { MOTIONS, type MoveId } from '../motions.ts'
|
||||
|
||||
export function sampleMotion(id: MoveId, seconds: number, fps = 12, startMs = 0): Pose[] {
|
||||
const spec = MOTIONS[id]
|
||||
const frames: Pose[] = []
|
||||
const n = Math.round(seconds * fps)
|
||||
for (let i = 0; i < n; i++) {
|
||||
const tSec = i / fps
|
||||
const points = spec.poseAt(tSec / spec.cycleSeconds)
|
||||
frames.push({ points, scores: points.map(() => 0.9), t: startMs + tSec * 1000 })
|
||||
}
|
||||
return frames
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { TrainingSession, summarize, type SessionEvent } from '../session.ts'
|
||||
import { MOTIONS, sampleMotion } from '../motions.ts'
|
||||
import { MOTIONS } from '../motions.ts'
|
||||
import { sampleMotion } from './sampleMotion.ts'
|
||||
import type { Pose } from '../keypoints.ts'
|
||||
|
||||
const standing = (t: number): Pose => {
|
||||
|
||||
@@ -177,5 +177,3 @@ export function createBgm(opts: BgmOptions) {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export type Bgm = ReturnType<typeof createBgm>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import type { MoveId, Side } from './motions.ts'
|
||||
import { COACH_HD, COACH_SEQUENCES, type CoachSequence } from './coachSequences.ts'
|
||||
|
||||
export type Gesture = 'idle' | 'wave' | 'cheer' | 'thumbs'
|
||||
export type Gesture = 'wave' | 'cheer' | 'thumbs'
|
||||
|
||||
const MOVE_SEQUENCE: Record<MoveId, Record<Side, string>> = {
|
||||
squat: { L: 'squat', R: 'squat' },
|
||||
|
||||
@@ -57,28 +57,6 @@ export const KP = {
|
||||
|
||||
export const KEYPOINT_COUNT = 23
|
||||
|
||||
/** 画骨架用的连线(左右成对,便于左右分色) */
|
||||
export const BONES: Array<[number, number]> = [
|
||||
[KP.L_SHOULDER, KP.R_SHOULDER],
|
||||
[KP.L_HIP, KP.R_HIP],
|
||||
[KP.L_SHOULDER, KP.L_HIP],
|
||||
[KP.R_SHOULDER, KP.R_HIP],
|
||||
[KP.L_SHOULDER, KP.L_ELBOW],
|
||||
[KP.L_ELBOW, KP.L_WRIST],
|
||||
[KP.L_WRIST, KP.L_HAND],
|
||||
[KP.R_SHOULDER, KP.R_ELBOW],
|
||||
[KP.R_ELBOW, KP.R_WRIST],
|
||||
[KP.R_WRIST, KP.R_HAND],
|
||||
[KP.L_HIP, KP.L_KNEE],
|
||||
[KP.L_KNEE, KP.L_ANKLE],
|
||||
[KP.L_ANKLE, KP.L_FOOT_OUT],
|
||||
[KP.L_ANKLE, KP.L_FOOT_IN],
|
||||
[KP.R_HIP, KP.R_KNEE],
|
||||
[KP.R_KNEE, KP.R_ANKLE],
|
||||
[KP.R_ANKLE, KP.R_FOOT_OUT],
|
||||
[KP.R_ANKLE, KP.R_FOOT_IN],
|
||||
]
|
||||
|
||||
/** 判定“全身入镜”需要看得到的点 */
|
||||
export const FULL_BODY_POINTS = [
|
||||
KP.NOSE,
|
||||
@@ -179,11 +157,6 @@ export function smoothPose(prev: Pose | null, next: Pose, alpha = 0.55): Pose {
|
||||
return { points, scores, t: next.t, aspect: next.aspect }
|
||||
}
|
||||
|
||||
/** 水平镜像(前置摄像头预览是镜像的) */
|
||||
export function mirrorPose(pose: Pose): Pose {
|
||||
return { ...pose, points: pose.points.map((p) => ({ x: 1 - p.x, y: p.y })) }
|
||||
}
|
||||
|
||||
type RawPoint = { x: number; y: number } | [number, number]
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* 坐标系与 VisionKit 一致:x/y 归一化到 [0,1],y 向下;
|
||||
* 被拍摄者的“左侧”在画面右侧(未镜像的正面视角)。
|
||||
*/
|
||||
import { KEYPOINT_COUNT, type Point, type Pose } from './keypoints.ts'
|
||||
import { KEYPOINT_COUNT, type Point } from './keypoints.ts'
|
||||
|
||||
export type MoveId = 'squat' | 'march' | 'jack' | 'raise'
|
||||
|
||||
@@ -310,19 +310,6 @@ export const MISTAKE_POSES: Record<MoveId, (phase: number) => Point[]> = {
|
||||
},
|
||||
}
|
||||
|
||||
/** 把示范动作按时间采样成 Pose 序列(单测用) */
|
||||
export function sampleMotion(id: MoveId, seconds: number, fps = 12, startMs = 0): Pose[] {
|
||||
const spec = MOTIONS[id]
|
||||
const frames: Pose[] = []
|
||||
const n = Math.round(seconds * fps)
|
||||
for (let i = 0; i < n; i++) {
|
||||
const tSec = i / fps
|
||||
const points = spec.poseAt(tSec / spec.cycleSeconds)
|
||||
frames.push({ points, scores: points.map(() => 0.9), t: startMs + tSec * 1000 })
|
||||
}
|
||||
return frames
|
||||
}
|
||||
|
||||
/**
|
||||
* 示范动作“完成一次”的检测:幅度到过顶点、再回落到起始附近,算完成一次。
|
||||
* 领练模式按教练动画计数,报数和画面同步(按时钟计数会比教练做完晚半拍)。
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -36,7 +36,7 @@ AI 跟练的素材流水线:教练“小甄”动作帧(AI 生成连续动
|
||||
- Codex 只能出约 1.5MP 的图,人物 300~450px 高:切帧时先 4 倍超分再缩到主帧尺寸(站姿身高 720px)
|
||||
- 设定稿里手臂偶尔碰到隔壁或上一行的人物,切帧会按格子拆开;脚踝以下出现的肤色像素(别人的指尖)会被去掉
|
||||
- 生成结果有随机性:先看 `work/preview/contact_*.jpg`,不满意就重新生成那一张;`coach_frames.py` 的 `MOVES` 可指定取哪些人物
|
||||
- 手势帧(idle / wave / cheer / thumbs)和错误示范帧(wrong_*)不随动作帧重新生成,导出时沿用 `public/coach/hd` 里已有的
|
||||
- 手势帧(wave / cheer / thumbs)和错误示范帧(wrong_*)不随动作帧重新生成,导出时沿用 `public/coach/hd` 里已有的
|
||||
- 没有 `work/` 时,可用 `python3 scripts/coach_export.py --from-hd` 从高清帧重新导出小程序帧
|
||||
|
||||
## 高清帧(COS)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
@@ -32,8 +32,8 @@ CDN_MARGIN = 12
|
||||
# 每个动作的帧名前缀:同一个动作的所有帧共用一个裁切框,帧与帧之间位置不变
|
||||
MOVE_PREFIX = {"squat": ("sq_",), "raise": ("ra_",), "jack": ("jl_", "jr_"), "march": ("ml_", "mr_")}
|
||||
|
||||
STATIC_FRAMES = ["idle", "wave", "cheer", "thumbs", "wrong_squat", "wrong_march", "wrong_jack", "wrong_raise"]
|
||||
MINI_STATIC = ["idle", "wave", "cheer", "thumbs"]
|
||||
STATIC_FRAMES = ["wave", "cheer", "thumbs", "wrong_squat", "wrong_march", "wrong_jack", "wrong_raise"]
|
||||
MINI_STATIC = ["wave", "cheer", "thumbs"]
|
||||
SEQUENCE_ORDER = ["squat", "raise", "jack_l", "jack_r", "march_l", "march_r"]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user