代码实现(HTML+JavaScript整合版)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>儿童视力保护辅助程序</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; }
.container { max-width: 800px; width: 100%; }
video, canvas { width: 100%; max-width: 640px; border: 1px solid #ccc; margin-bottom: 10px; }
.stats { margin: 20px 0; padding: 10px; border: 1px solid #eee; border-radius: 5px; }
.alert { color: red; font-weight: bold; margin: 10px 0; display: none; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; }
button:hover { background: #0056b3; }
</style>
</head>
<body>
<div class="container">
<h1>儿童视力保护辅助程序</h1>
<button id="startBtn">启动检测</button>
<div class="alert" id="alertBox"></div>
<video id="video" autoplay playsinline></video>
<canvas id="canvas"></canvas>
<div class="stats">
<p>今日健康用眼时长:<span id="healthyTime">0</span> 分钟</p>
<p>当前状态:<span id="currentStatus">未启动</span></p>
</div>
</div>
<!-- 引入TensorFlow.js与BlazePose模型 -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/blazepose"></script>
<script>
// 全局变量
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const alertBox = document.getElementById('alertBox');
const healthyTimeEl = document.getElementById('healthyTime');
const currentStatusEl = document.getElementById('currentStatus');
const startBtn = document.getElementById('startBtn');
let model = null;
let isDetecting = false;
let healthyStartTime = null;
let dailyHealthyTime = 0; // 当日健康时长(秒)
const DISTANCE_THRESHOLD = 50; // 安全距离阈值(cm,小于则警报)
const POSTURE_ANGLE_THRESHOLD = 15; // 坐姿倾斜阈值(度,大于则警报)
// 1. 初始化摄像头
async function initCamera() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
return new Promise((resolve) => {
video.onloadedmetadata = () => resolve();
});
} catch (err) {
throw new Error('摄像头访问失败:' + err.message);
}
}
// 2. 加载BlazePose姿势检测模型
async function loadModel() {
try {
model = await blazepose.load();
console.log('模型加载完成');
} catch (err) {
throw new Error('模型加载失败:' + err.message);
}
}
// 3. 计算眼睛到屏幕的距离(简化:用头部宽度估算)
function calculateDistance(keypoints) {
// 获取双眼关键点(BlazePose索引:33=左眼,263=右眼)
const leftEye = keypoints[33];
const rightEye = keypoints[263];
if (!leftEye || !rightEye) return Infinity;
// 计算两眼间距(像素)
const eyeDistancePx = Math.sqrt(
Math.pow(rightEye.x - leftEye.x, 2) + Math.pow(rightEye.y - leftEye.y, 2)
);
// 经验公式:实际距离(cm) ≈ (已知眼距cm × 图像宽度px) / 眼距px(假设已知成人眼距约6.5cm)
const knownEyeDistanceCm = 6.5;
const imageWidthPx = video.videoWidth;
return (knownEyeDistanceCm * imageWidthPx) / eyeDistancePx;
}
// 4. 检查坐姿(计算肩膀倾斜角度)
function checkPosture(keypoints) {
// 获取左右肩关键点(索引:11=左肩,12=右肩)
const leftShoulder = keypoints[11];
const rightShoulder = keypoints[12];
if (!leftShoulder || !rightShoulder) return 0;
// 计算肩膀连线与水平线的夹角(度)
const dx = rightShoulder.x - leftShoulder.x;
const dy = rightShoulder.y - leftShoulder.y;
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
return Math.abs(angle); // 返回绝对值(倾斜程度)
}
// 5. 触发警报(视觉+听觉)
function triggerAlert(message) {
alertBox.textContent = message;
alertBox.style.display = 'block';
// 播放提示音(用浏览器内置音频)
const audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqMkJKTlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zNzs/Q0dLT1NXW19jZ2tvc3d7f4OHi4+Tl5ufo6err7O3u7/Dx8vP09fb3+Pn6+/z9/v8AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJysvMzc7P0NHS09TV1tfY2drb3N3e3+Dh4uPk5ebn6Onq6+zt7u/w8fLz9PX29/j5+vv8/f7/');
audio.play().catch(() => {}); // 忽略自动播放限制错误
setTimeout(() => alertBox.style.display = 'none', 3000);
}
// 6. 更新健康用眼时长(localStorage存储每日数据)
function updateHealthyTime(isHealthy) {
const now = Date.now();
if (isHealthy) {
if (!healthyStartTime) healthyStartTime = now;
} else {
if (healthyStartTime) {
dailyHealthyTime += (now - healthyStartTime) / 1000; // 转为秒
healthyStartTime = null;
}
}
// 存储到localStorage(按日期分割)
const today = new Date().toISOString().split('T')[0];
localStorage.setItem(`healthyTime_${today}`, dailyHealthyTime.toString());
// 更新UI(转为分钟)
healthyTimeEl.textContent = Math.floor(dailyHealthyTime / 60);
}
// 7. 实时检测循环
async function detectLoop() {
if (!isDetecting) return;
// 绘制视频帧到画布
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
try {
// 检测姿势关键点
const predictions = await model.estimatePoses(canvas);
if (predictions.length > 0) {
const keypoints = predictions[0].keypoints;
// 计算距离与坐姿
const distance = calculateDistance(keypoints);
const postureAngle = checkPosture(keypoints);
// 判断是否健康
const isDistanceOk = distance >= DISTANCE_THRESHOLD;
const isPostureOk = postureAngle <= POSTURE_ANGLE_THRESHOLD;
const isHealthy = isDistanceOk && isPostureOk;
// 更新状态与统计
currentStatusEl.textContent = isHealthy ? '健康' : '需注意';
currentStatusEl.style.color = isHealthy ? 'green' : 'red';
updateHealthyTime(isHealthy);
// 超阈值警报
if (!isDistanceOk) triggerAlert(`距离过近!当前约${Math.round(distance)}cm(建议≥${DISTANCE_THRESHOLD}cm)`);
if (!isPostureOk) triggerAlert(`坐姿倾斜!当前${Math.round(postureAngle)}°(建议≤${POSTURE_ANGLE_THRESHOLD}°)`);
}
} catch (err) {
console.error('检测错误:', err);
}
// 下一帧
requestAnimationFrame(detectLoop);
}
// 启动检测
startBtn.addEventListener('click', async () => {
if (isDetecting) return;
try {
await initCamera();
await loadModel();
isDetecting = true;
startBtn.disabled = true;
currentStatusEl.textContent = '检测中...';
detectLoop();
// 加载当日已存时长
const today = new Date().toISOString().split('T')[0];
dailyHealthyTime = parseFloat(localStorage.getItem(`healthyTime_${today}`)) || 0;
healthyTimeEl.textContent = Math.floor(dailyHealthyTime / 60);
} catch (err) {
alert('启动失败:' + err.message);
}
});
</script>
</body>
</html>
README文件
# 儿童视力保护辅助程序
## 简介
通过电脑摄像头实时检测儿童坐姿(肩膀倾斜≤15°)与屏幕距离(≥50cm),超阈值时弹窗+声音警报,统计每日健康用眼时长(localStorage存储)。
## 安装与使用
1. **环境要求**:现代浏览器(Chrome/Firefox)、摄像头权限。
2. **启动方式**:直接打开`index.html`(建议用本地服务器避免跨域,如`npx http-server`)。
3. **操作流程**:
- 点击「启动检测」→ 允许摄像头访问;
- 程序自动检测,下方显示实时状态与健康时长;
- 超阈值时弹出红色警报(3秒后消失)。
## 核心模块
| 模块名 | 功能 |
|-----------------|-----------------------------------|
| initCamera | 初始化摄像头视频流 |
| loadModel | 加载BlazePose姿势检测模型 |
| calculateDistance | 用头部大小估算屏幕距离(cm) |
| checkPosture | 计算肩膀倾斜角度(度) |
| triggerAlert | 视觉+听觉警报(弹窗+提示音) |
| updateHealthyTime | 统计健康时长(localStorage存储) |
## 依赖说明
- TensorFlow.js Core(CDN引入);
- BlazePose模型(CDN引入,用于姿势检测)。
使用说明
1. 准备工作:确保电脑有可用摄像头,浏览器开启摄像头权限(地址栏锁图标→允许)。
2. 启动程序:双击
"index.html"打开页面,点击「启动检测」按钮。
3. 阈值调整:如需修改安全距离(默认50cm)或坐姿倾斜度(默认15°),直接修改代码中
"DISTANCE_THRESHOLD"和
"POSTURE_ANGLE_THRESHOLD"的值。
4. 数据查看:健康时长存储在浏览器
"localStorage"中,按日期分割(键名如
"healthyTime_2025-12-17")。
核心知识点卡片
1. 模块化设计
- 定义:将程序拆分为独立功能模块(摄像头、检测、警报、统计),降低耦合度。
- 应用:本程序用
"initCamera"/
"loadModel"等函数拆分功能,便于维护。
- 关联课程:战略管理中的分工协作(各模块专注单一目标)。
2. 实时计算机视觉
- 定义:用BlazePose模型实时检测人体关键点(眼睛、肩膀),分析姿势。
- 应用:估算屏幕距离(眼距像素→实际距离)、判断坐姿(肩膀倾斜角度)。
- 关联课程:创新思维中的技术赋能(用AI解决传统监测痛点)。
3. 阈值管理
- 定义:设定安全边界(如距离≥50cm、倾斜≤15°),超界触发反馈。
- 应用:本程序用常量定义阈值,清晰可控。
- 关联课程:战略管理中的目标设定与风险控制。
4. 用户体验反馈
- 定义:通过视觉(红底弹窗)+听觉(提示音)及时提醒,强化正确行为。
- 应用:超阈值时
"triggerAlert"函数触发双模态反馈。
- 关联课程:创新思维中的用户中心设计。
5. 数据驱动的持续改进
- 定义:用
"localStorage"记录每日健康时长,帮助用户跟踪进展。
- 应用:统计时长并在UI展示,支持长期视力保护。
- 关联课程:战略管理中的复盘与迭代。
关注我,有更多编程干货等着你!