diff --git a/game/贪吃蛇/snake_game.html b/game/贪吃蛇/snake_game.html
new file mode 100644
index 0000000..18e7bc1
--- /dev/null
+++ b/game/贪吃蛇/snake_game.html
@@ -0,0 +1,828 @@
+
+
+
+
+
+ AI 姿态控制贪吃蛇
+
+
+
+
+
+
+
+
+
+ AI 姿态控制贪吃蛇
+
+
+
+
+
+
+
+
+
+
+
+
+
得分: 0
+
姿态识别: 未识别
+
控制指令: 静止
+
等待模型导入...
+
+
+
+
+
+
+
+
姿态控制说明
+
+ - 向上: 双手举过头顶
+ - 向下: 双手放在身体两侧或下垂
+ - 向左: 左手平举
+ - 向右: 右手平举
+ - 静止: 保持站立姿势 (无特定动作)
+
+
(确保在摄像头画面中能清晰识别全身)
+
+
+
+
+
+
+
游戏结束!
+
最终得分: 0
+
+
+
+
+
+
diff --git a/game/钢琴/index.html b/game/钢琴/index.html
new file mode 100644
index 0000000..542518f
--- /dev/null
+++ b/game/钢琴/index.html
@@ -0,0 +1,792 @@
+
+
+
+
+
+ AI 空气钢琴 - 手势控制
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AI 空气钢琴
+ 通过手势弹奏虚拟钢琴!
+
+
+
+
+
+
+
+
+
模型管理
+
+
+
+
+
+
演奏控制
+
+
+ 实时手势:
+ 未识别
+
+
+ 置信度:
+ 0%
+
+
+ 当前演奏音符:
+ 无
+
+
+
+
+
音符映射
+
请确保您的手势分类与音符对应:
+
+
+ - ID 0: 中央C (C4) → 音段 1
+ - ID 1: D4 → 音段 2
+ - ID 2: E4 → 音段 3
+ - ID 3: F4 → 音段 4
+ - ID 4: G4 → 音段 5
+ - ID 5: A4 → 音段 6
+ - ID 6: B4 → 音段 7
+ - ID 7: 高音C (C5) → 音段 8
+
+
+
+
+
+
+
+
diff --git a/game/钢琴/sounds/A2.mp3 b/game/钢琴/sounds/A2.mp3
new file mode 100644
index 0000000..415de14
Binary files /dev/null and b/game/钢琴/sounds/A2.mp3 differ
diff --git a/game/钢琴/sounds/B2.mp3 b/game/钢琴/sounds/B2.mp3
new file mode 100644
index 0000000..6f3fce2
Binary files /dev/null and b/game/钢琴/sounds/B2.mp3 differ
diff --git a/game/钢琴/sounds/C2.mp3 b/game/钢琴/sounds/C2.mp3
new file mode 100644
index 0000000..8f7d778
Binary files /dev/null and b/game/钢琴/sounds/C2.mp3 differ
diff --git a/game/钢琴/sounds/C3.mp3 b/game/钢琴/sounds/C3.mp3
new file mode 100644
index 0000000..f0aa866
Binary files /dev/null and b/game/钢琴/sounds/C3.mp3 differ
diff --git a/game/钢琴/sounds/D2.mp3 b/game/钢琴/sounds/D2.mp3
new file mode 100644
index 0000000..d4e57d3
Binary files /dev/null and b/game/钢琴/sounds/D2.mp3 differ
diff --git a/game/钢琴/sounds/E2.mp3 b/game/钢琴/sounds/E2.mp3
new file mode 100644
index 0000000..4d9a607
Binary files /dev/null and b/game/钢琴/sounds/E2.mp3 differ
diff --git a/game/钢琴/sounds/F2.mp3 b/game/钢琴/sounds/F2.mp3
new file mode 100644
index 0000000..83d5eaa
Binary files /dev/null and b/game/钢琴/sounds/F2.mp3 differ
diff --git a/game/钢琴/sounds/G2.mp3 b/game/钢琴/sounds/G2.mp3
new file mode 100644
index 0000000..5b36960
Binary files /dev/null and b/game/钢琴/sounds/G2.mp3 differ
diff --git a/姿态分类/script.js b/姿态分类/script.js
index e5b942b..1355ded 100644
--- a/姿态分类/script.js
+++ b/姿态分类/script.js
@@ -1,15 +1,17 @@
/**
* =============================================================================
- * 动态版 - 姿态识别与模型管理脚本 (v2.0)
+ * 动态版 - 姿态识别与模型管理脚本 (v2.1)
+ * - 新增自动采集样本功能
* =============================================================================
* 功能列表:
* - 实时姿态检测 (MoveNet)
* - KNN 分类器训练
* - 实时姿态预测
* - 坐标完美对齐 (Canvas与Video重叠)
- * - ✅ 动态添加/删除/重命名姿态类别
- * - ✅ 模型导出为包含类别信息的 JSON 文件
- * - ✅ 从 JSON 文件导入模型并恢复类别状态
+ * - 动态添加/删除/重命名姿态类别
+ * - 模型导出为包含类别信息的 JSON 文件
+ * - 从 JSON 文件导入模型并恢复类别状态
+ * - ✅ 新增:自动采集10次样本,间隔0.3秒
* =============================================================================
*/
@@ -32,6 +34,7 @@ const fileImporter = document.getElementById('file-importer');
let detector, classifier, animationFrameId;
let isPredicting = false;
+let isAutoCollecting = false; // 新增:标记是否正在进行自动采集
// 📌 核心状态管理: 使用一个对象来管理所有动态状态
const appState = {
@@ -105,12 +108,14 @@ function createClassUI(classId, className) {
poseClassDiv.className = 'pose-class';
poseClassDiv.dataset.classId = classId;
+ // 📌 修改这里:添加 btn-auto-sample 按钮
poseClassDiv.innerHTML = `
(0 样本)
+
@@ -124,10 +129,17 @@ function createClassUI(classId, className) {
appState.classMap[classId] = e.target.value;
});
+ const autoSampleButton = poseClassDiv.querySelector('.btn-auto-sample'); // 新增
+ autoSampleButton.addEventListener('click', () => toggleAutoCollection(classId, autoSampleButton)); // 新增
+
const sampleButton = poseClassDiv.querySelector('.btn-sample');
sampleButton.addEventListener('click', () => addExample(classId));
- if (isPredicting) sampleButton.disabled = true; // 如果在预测中,禁用新按钮
+ // 初始化时根据预测状态禁用按钮
+ if (isPredicting) {
+ sampleButton.disabled = true;
+ autoSampleButton.disabled = true; // 新增
+ }
const deleteButton = poseClassDiv.querySelector('.btn-delete-class');
deleteButton.addEventListener('click', () => deleteClass(classId));
@@ -177,11 +189,81 @@ async function addExample(classId) {
updateSampleCounts();
checkExportAbility();
+ console.log(`为类别 ${appState.classMap[classId]} 采集1个样本。`);
+ return true; // 表示采集成功
} else {
console.warn(`为类别 ${appState.classMap[classId]} 采集样本失败,未检测到姿态。`);
+ return false; // 表示采集失败
}
}
+// --- 新增:自动采集逻辑 ---
+let autoCollectionIntervalId = null; // 用于存储 setInterval ID
+let autoCollectionCount = 0; // 计数器
+const AUTO_COLLECTION_TOTAL = 10; // 总共采集次数
+const AUTO_COLLECTION_INTERVAL = 300; // 间隔时间 0.3 秒
+
+async function toggleAutoCollection(classId, buttonElement) {
+ if (isAutoCollecting) {
+ // 如果正在自动采集,则停止
+ stopAutoCollection(buttonElement);
+ } else {
+ // 否则,开始自动采集
+ startAutoCollection(classId, buttonElement);
+ }
+}
+
+async function startAutoCollection(classId, buttonElement) {
+ isAutoCollecting = true;
+ autoCollectionCount = 0;
+
+ // 禁用其他采集和预测按钮
+ predictButton.disabled = true;
+ exportButton.disabled = true;
+ importButton.disabled = true;
+ addClassButton.disabled = true;
+ document.querySelectorAll('.btn-sample, .btn-auto-sample, .btn-delete-class, .class-name-input').forEach(btn => {
+ if (btn !== buttonElement) { // 不禁用当前自动采集按钮
+ btn.disabled = true;
+ }
+ if (btn.classList.contains('class-name-input')) btn.disabled = true;
+ });
+
+ buttonElement.innerText = `停止采集 (0/${AUTO_COLLECTION_TOTAL})`;
+ buttonElement.classList.add('stop'); // 添加停止样式
+
+ const performCollection = async () => {
+ if (autoCollectionCount < AUTO_COLLECTION_TOTAL) {
+ const success = await addExample(classId); // 调用手动采集功能
+ if (success) {
+ autoCollectionCount++;
+ }
+ buttonElement.innerText = `停止采集 (${autoCollectionCount}/${AUTO_COLLECTION_TOTAL})`;
+ } else {
+ stopAutoCollection(buttonElement);
+ alert(`类别 "${appState.classMap[classId]}" 自动采集完成!`);
+ }
+ };
+
+ // 立即执行一次,然后设置定时器
+ await performCollection();
+ if (autoCollectionCount < AUTO_COLLECTION_TOTAL) {
+ autoCollectionIntervalId = setInterval(performCollection, AUTO_COLLECTION_INTERVAL);
+ }
+}
+
+function stopAutoCollection(buttonElement) {
+ clearInterval(autoCollectionIntervalId);
+ autoCollectionIntervalId = null;
+ isAutoCollecting = false;
+ buttonElement.innerText = '自动采集';
+ buttonElement.classList.remove('stop'); // 移除停止样式
+
+ // 重新启用按钮(根据应用状态)
+ updatePredictionUI(); // 根据预测状态重新启用/禁用相关按钮
+ enableControls(); // 重新启用添加类别、导出、导入按钮
+}
+
// --- 模型与预测逻辑 ---
/**
@@ -205,16 +287,20 @@ async function mainLoop() {
if (poses && poses.length > 0) {
drawPose(poses[0]);
- if (isPredicting && classifier.getNumClasses() > 0) {
+ // 只有当不在自动采集状态时才进行预测
+ if (isPredicting && classifier.getNumClasses() > 0 && !isAutoCollecting) {
const poseTensor = flattenPose(poses[0]);
const result = await classifier.predictClass(poseTensor, 3);
poseTensor.dispose();
const confidence = Math.round(result.confidences[result.label] * 100);
- // 📌 动态获取类别名称
const predictedClassName = appState.classMap[result.label] || '未知类别';
resultElement.innerText = `姿态: ${predictedClassName} (${confidence}%)`;
+ } else if (isAutoCollecting) {
+ resultElement.innerText = "自动采集中...";
}
+ } else {
+ resultElement.innerText = "未检测到姿态";
}
animationFrameId = requestAnimationFrame(mainLoop);
}
@@ -237,7 +323,6 @@ function exportModel() {
datasetObj[key] = data.arraySync();
});
- // 📌 导出格式大更新: 同时保存 classMap 和 dataset
const modelData = {
classMap: appState.classMap,
dataset: datasetObj
@@ -268,7 +353,6 @@ function importModel(event) {
try {
const modelData = JSON.parse(e.target.result);
- // 📌 导入格式验证
if (!modelData.classMap || !modelData.dataset) {
throw new Error("无效的模型文件格式。");
}
@@ -364,40 +448,53 @@ function updateSampleCounts() {
* 根据状态更新UI
*/
function updatePredictionUI() {
- const allActionButtons = document.querySelectorAll('.btn-sample, .btn-delete-class, .btn-add-class, #btn-import');
+ // 禁用所有采集按钮(包括手动和自动)和删除按钮
+ document.querySelectorAll('.btn-sample, .btn-auto-sample, .btn-delete-class').forEach(btn => btn.disabled = isPredicting || isAutoCollecting);
+
+ // 禁用添加类别和导入模型的按钮
+ addClassButton.disabled = isPredicting || isAutoCollecting;
+ importButton.disabled = isPredicting || isAutoCollecting;
+
+ // 禁用类别名称输入框
+ document.querySelectorAll('.class-name-input').forEach(input => input.disabled = isPredicting || isAutoCollecting);
+
if (isPredicting) {
predictButton.innerText = "停止预测";
predictButton.classList.add('stop');
resultElement.innerText = "正在分析...";
- allActionButtons.forEach(btn => btn.disabled = true);
- document.querySelectorAll('.class-name-input').forEach(input => input.disabled = true);
- checkExportAbility();
} else {
predictButton.innerText = "开始预测";
predictButton.classList.remove('stop');
resultElement.innerText = "已停止";
- allActionButtons.forEach(btn => btn.disabled = false);
- document.querySelectorAll('.class-name-input').forEach(input => input.disabled = false);
- checkExportAbility();
}
// 只有在有类别且有样本时才能预测
- predictButton.disabled = isPredicting ? false : classifier.getNumClasses() === 0;
+ predictButton.disabled = isPredicting ? false : classifier.getNumClasses() === 0 || isAutoCollecting;
+ checkExportAbility();
}
+/**
+ * 通用启用/禁用控件 (在自动采集停止后调用)
+ */
function enableControls() {
- [predictButton, importButton, exportButton, addClassButton].forEach(btn => btn.disabled = false);
- checkExportAbility();
+ // 重新评估所有按钮的状态
+ // 自动采集按钮的状态由其自身管理
+ predictButton.disabled = classifier.getNumClasses() === 0;
+ importButton.disabled = false; // 导入按钮总是可以手动启用
+ addClassButton.disabled = false;
+ checkExportAbility(); // 重新检查导出按钮
+ updatePredictionUI(); // 再次调用,确保其他按钮状态正确
}
/** 检查是否可以导出模型并更新按钮状态 */
function checkExportAbility() {
- exportButton.disabled = isPredicting || classifier.getNumClasses() === 0;
+ exportButton.disabled = isPredicting || classifier.getNumClasses() === 0 || isAutoCollecting;
}
function cleanup() {
if (detector) detector.dispose();
if (classifier) classifier.clearAllClasses();
if (animationFrameId) cancelAnimationFrame(animationFrameId);
+ if (autoCollectionIntervalId) clearInterval(autoCollectionIntervalId); // 清理自动采集定时器
}
// --- 启动应用 ---
diff --git a/姿态分类/style.css b/姿态分类/style.css
index d5dc028..212e748 100644
--- a/姿态分类/style.css
+++ b/姿态分类/style.css
@@ -169,7 +169,7 @@ h3 {
.class-actions {
display: flex;
align-items: center;
- gap: 0.5rem; /* 按钮间距 */
+ gap: 5px; /* 按钮间距 */
}
/* 📌 新增: 删除按钮样式 */
@@ -254,3 +254,32 @@ h3 {
display: flex;
gap: 1rem;
}
+
+
+.btn-auto-sample {
+ padding: 8px 15px;
+ font-size: 0.9em;
+ background-color: #5cb85c; /* 绿色 */
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+ margin-right: 8px; /* 与手动采集按钮之间留出间距 */
+ }
+
+ .btn-auto-sample:hover {
+ background-color: #4cae4c;
+ }
+
+ .btn-auto-sample:disabled {
+ background-color: #cccccc;
+ cursor: not-allowed;
+ }
+ /* 添加一个停止按钮样式 */
+ .btn-auto-sample.stop {
+ background-color: #d9534f; /* 红色 */
+ }
+ .btn-auto-sample.stop:hover {
+ background-color: #c9302c;
+ }
\ No newline at end of file