Files
kangyang/fall_algorithm_demo.html
2025-08-21 22:25:08 +08:00

911 lines
34 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🤖 康养AI跌倒检测算法演示</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: white;
}
.demo-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 40px;
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 30px;
backdrop-filter: blur(10px);
}
.header h1 {
font-size: 3em;
margin-bottom: 10px;
background: linear-gradient(45deg, #4CAF50, #00d4ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.2em;
opacity: 0.9;
margin-bottom: 20px;
}
.status-bar {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 20px;
}
.status-item {
background: rgba(255, 255, 255, 0.2);
padding: 10px 20px;
border-radius: 15px;
font-weight: bold;
}
.status-healthy { color: #4CAF50; }
.status-processing { color: #FF9800; }
.status-error { color: #f44336; }
.demo-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.demo-section {
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 30px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.demo-section h2 {
color: #00d4ff;
margin-bottom: 20px;
font-size: 1.5em;
}
.video-info {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 15px;
}
.info-item {
background: rgba(255, 255, 255, 0.1);
padding: 10px;
border-radius: 8px;
text-align: center;
}
.info-label {
font-size: 0.9em;
opacity: 0.8;
margin-bottom: 5px;
}
.info-value {
font-size: 1.1em;
font-weight: bold;
color: #00d4ff;
}
.btn {
background: linear-gradient(45deg, #4CAF50, #45a049);
color: white;
border: none;
padding: 15px 30px;
border-radius: 25px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
margin: 10px;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
.btn:disabled {
background: #666;
cursor: not-allowed;
transform: none;
}
.btn-warning { background: linear-gradient(45deg, #FF9800, #F57C00); }
.btn-info { background: linear-gradient(45deg, #2196F3, #1976D2); }
.btn-danger { background: linear-gradient(45deg, #f44336, #da190b); }
.progress-container {
margin: 20px 0;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
padding: 20px;
display: none;
}
.progress-bar {
background: rgba(255, 255, 255, 0.3);
border-radius: 15px;
height: 30px;
margin: 10px 0;
overflow: hidden;
}
.progress-fill {
background: linear-gradient(45deg, #4CAF50, #45a049);
height: 100%;
width: 0%;
transition: width 0.5s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.results-section {
grid-column: 1 / -1;
margin-top: 20px;
}
.detection-results {
background: rgba(0, 0, 0, 0.4);
border-radius: 15px;
padding: 25px;
margin-top: 20px;
}
.detection-item {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 20px;
margin: 15px 0;
border-left: 4px solid #4CAF50;
}
.detection-item.high-confidence {
border-left-color: #4CAF50;
background: rgba(76, 175, 80, 0.2);
}
.detection-item.medium-confidence {
border-left-color: #FF9800;
background: rgba(255, 152, 0, 0.2);
}
.detection-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.detection-title {
font-size: 1.2em;
font-weight: bold;
}
.confidence-badge {
background: #4CAF50;
color: white;
padding: 5px 12px;
border-radius: 15px;
font-size: 0.9em;
font-weight: bold;
}
.confidence-badge.medium { background: #FF9800; }
.confidence-badge.low { background: #f44336; }
.detection-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 15px;
}
.detail-item {
background: rgba(255, 255, 255, 0.1);
padding: 10px;
border-radius: 8px;
text-align: center;
}
.detail-label {
font-size: 0.9em;
opacity: 0.8;
margin-bottom: 5px;
}
.detail-value {
font-size: 1.1em;
font-weight: bold;
color: #00d4ff;
}
.comparison-section {
grid-column: 1 / -1;
background: rgba(0, 0, 0, 0.3);
border-radius: 20px;
padding: 30px;
margin-top: 30px;
}
.comparison-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.algorithm-card {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 25px;
text-align: center;
position: relative;
overflow: hidden;
}
.algorithm-card.our-algorithm {
border: 2px solid #4CAF50;
background: rgba(76, 175, 80, 0.15);
}
.algorithm-card.our-algorithm::before {
content: "🏆 推荐";
position: absolute;
top: 10px;
right: -30px;
background: #4CAF50;
color: white;
padding: 5px 40px;
transform: rotate(45deg);
font-size: 0.8em;
font-weight: bold;
}
.algorithm-name {
font-size: 1.3em;
font-weight: bold;
margin-bottom: 15px;
color: #00d4ff;
}
.performance-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 15px 0;
}
.performance-item {
background: rgba(255, 255, 255, 0.1);
padding: 10px;
border-radius: 8px;
}
.performance-label {
font-size: 0.8em;
opacity: 0.8;
}
.performance-value {
font-size: 1.1em;
font-weight: bold;
color: #4CAF50;
}
.alert {
padding: 15px;
margin: 15px 0;
border-radius: 10px;
border-left: 4px solid;
}
.alert-success {
background: rgba(76, 175, 80, 0.2);
border-color: #4CAF50;
color: #4CAF50;
}
.alert-warning {
background: rgba(255, 152, 0, 0.2);
border-color: #FF9800;
color: #FF9800;
}
.alert-info {
background: rgba(33, 150, 243, 0.2);
border-color: #2196F3;
color: #2196F3;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #4CAF50;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.hidden { display: none; }
.config-section {
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
}
.config-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.form-group {
margin: 10px 0;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
font-size: 0.9em;
}
.form-control {
width: 100%;
padding: 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 14px;
}
.form-control:focus {
outline: none;
border-color: #4CAF50;
background: rgba(255, 255, 255, 0.15);
}
select.form-control option {
background: #333;
color: white;
}
</style>
</head>
<body>
<div class="demo-container">
<!-- 标题和状态 -->
<div class="header">
<h1>🤖 康养AI跌倒检测算法演示</h1>
<p>专业级跌倒检测算法,专为养老机构设计 | 精准·快速·可靠</p>
<div class="status-bar">
<div class="status-item" id="apiStatus">
<span id="apiStatusText">检查中...</span>
</div>
<div class="status-item" id="videoStatus">
<span id="videoStatusText">准备中...</span>
</div>
</div>
</div>
<!-- 主要演示区域 -->
<div class="demo-grid">
<!-- 左侧:测试控制 -->
<div class="demo-section">
<h2>🎯 算法测试</h2>
<!-- 视频信息 -->
<div class="video-info" id="videoInfo">
<h3>📹 测试视频信息</h3>
<div class="info-grid" id="videoInfoGrid">
<div class="info-item">
<div class="info-label">加载中...</div>
</div>
</div>
</div>
<!-- 测试配置 -->
<div class="config-section">
<h3>⚙️ 检测配置</h3>
<div class="config-grid">
<div class="form-group">
<label>置信度阈值</label>
<select id="confidenceThreshold" class="form-control">
<option value="0.7">0.7 (宽松)</option>
<option value="0.8" selected>0.8 (标准)</option>
<option value="0.9">0.9 (严格)</option>
</select>
</div>
<div class="form-group">
<label>检测间隔</label>
<select id="detectionInterval" class="form-control">
<option value="3">每3帧</option>
<option value="5" selected>每5帧</option>
<option value="10">每10帧</option>
</select>
</div>
</div>
</div>
<!-- 控制按钮 -->
<div style="text-align: center; margin: 20px 0;">
<button class="btn" id="startTestBtn" onclick="startDemo()">
🚀 开始测试
</button>
<button class="btn btn-warning" id="resetBtn" onclick="resetDemo()">
🔄 重置测试
</button>
<button class="btn btn-info" onclick="showComparison()">
📊 算法对比
</button>
</div>
<!-- 进度条 -->
<div class="progress-container" id="progressContainer">
<h3>🔍 检测进度</h3>
<div class="progress-bar">
<div class="progress-fill" id="progressFill">0%</div>
</div>
<div id="progressText">准备开始...</div>
</div>
</div>
<!-- 右侧:实时结果 -->
<div class="demo-section">
<h2>📊 检测结果</h2>
<div id="summaryStats" class="info-grid" style="margin-bottom: 20px;">
<div class="info-item">
<div class="info-label">检测事件</div>
<div class="info-value" id="totalDetections">-</div>
</div>
<div class="info-item">
<div class="info-label">平均置信度</div>
<div class="info-value" id="avgConfidence">-</div>
</div>
<div class="info-item">
<div class="info-label">处理速度</div>
<div class="info-value" id="processingSpeed">-</div>
</div>
<div class="info-item">
<div class="info-label">响应时间</div>
<div class="info-value" id="responseTime">-</div>
</div>
</div>
<div id="detectionResults">
<div class="alert alert-info">
点击"开始测试"来检测mp4/falldown.mp4中的跌倒事件
</div>
</div>
</div>
</div>
<!-- 算法对比区域 -->
<div class="comparison-section" id="comparisonSection" style="display: none;">
<h2>🏆 算法性能对比</h2>
<div class="comparison-grid" id="comparisonGrid">
<!-- 动态加载算法对比数据 -->
</div>
</div>
<!-- 算法亮点展示 -->
<div class="demo-section results-section">
<h2>✨ 算法技术亮点</h2>
<div class="info-grid" id="highlightsGrid">
<!-- 动态加载算法亮点 -->
</div>
</div>
</div>
<script>
let demoAPI = 'http://localhost:6000';
let isProcessing = false;
// 页面加载时初始化
window.onload = function() {
checkAPIStatus();
loadVideoInfo();
loadAlgorithmHighlights();
};
// 检查API状态
async function checkAPIStatus() {
try {
const response = await fetch(`${demoAPI}/health`);
const data = await response.json();
if (data.status === 'healthy') {
document.getElementById('apiStatusText').textContent = '✅ API服务正常';
document.getElementById('apiStatus').className = 'status-item status-healthy';
} else {
throw new Error('API服务异常');
}
} catch (error) {
document.getElementById('apiStatusText').textContent = '❌ API服务离线';
document.getElementById('apiStatus').className = 'status-item status-error';
console.error('API检查失败:', error);
}
}
// 加载视频信息
async function loadVideoInfo() {
try {
const response = await fetch(`${demoAPI}/api/video/info`);
const data = await response.json();
if (data.error) {
document.getElementById('videoStatusText').textContent = '❌ 视频不可用';
document.getElementById('videoStatus').className = 'status-item status-error';
return;
}
document.getElementById('videoStatusText').textContent = '✅ 视频就绪';
document.getElementById('videoStatus').className = 'status-item status-healthy';
// 显示视频信息
const infoGrid = document.getElementById('videoInfoGrid');
infoGrid.innerHTML = `
<div class="info-item">
<div class="info-label">文件名</div>
<div class="info-value">${data.filename}</div>
</div>
<div class="info-item">
<div class="info-label">分辨率</div>
<div class="info-value">${data.resolution}</div>
</div>
<div class="info-item">
<div class="info-label">时长</div>
<div class="info-value">${data.duration_seconds}秒</div>
</div>
<div class="info-item">
<div class="info-label">帧率</div>
<div class="info-value">${data.fps.toFixed(1)} FPS</div>
</div>
<div class="info-item">
<div class="info-label">总帧数</div>
<div class="info-value">${data.total_frames}</div>
</div>
<div class="info-item">
<div class="info-label">文件大小</div>
<div class="info-value">${data.file_size_mb} MB</div>
</div>
`;
} catch (error) {
document.getElementById('videoStatusText').textContent = '❌ 加载失败';
document.getElementById('videoStatus').className = 'status-item status-error';
console.error('视频信息加载失败:', error);
}
}
// 开始演示测试
async function startDemo() {
if (isProcessing) return;
isProcessing = true;
document.getElementById('startTestBtn').disabled = true;
document.getElementById('startTestBtn').textContent = '🔄 处理中...';
// 显示进度条
document.getElementById('progressContainer').style.display = 'block';
document.getElementById('progressText').textContent = '开始处理视频...';
// 模拟进度
let progress = 0;
const progressInterval = setInterval(() => {
progress += Math.random() * 15;
if (progress > 95) progress = 95;
document.getElementById('progressFill').style.width = progress + '%';
document.getElementById('progressFill').textContent = Math.round(progress) + '%';
if (progress > 30) document.getElementById('progressText').textContent = '分析视频帧...';
if (progress > 60) document.getElementById('progressText').textContent = '检测跌倒事件...';
if (progress > 85) document.getElementById('progressText').textContent = '生成检测报告...';
}, 200);
try {
const confidence = parseFloat(document.getElementById('confidenceThreshold').value);
const interval = parseInt(document.getElementById('detectionInterval').value);
const response = await fetch(`${demoAPI}/api/video/process`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
confidence_threshold: confidence,
detection_interval: interval
})
});
const data = await response.json();
clearInterval(progressInterval);
// 完成进度
document.getElementById('progressFill').style.width = '100%';
document.getElementById('progressFill').textContent = '100%';
document.getElementById('progressText').textContent = '处理完成!';
setTimeout(() => {
document.getElementById('progressContainer').style.display = 'none';
}, 2000);
if (data.success) {
displayResults(data.results);
} else {
showAlert('error', '处理失败: ' + data.error);
}
} catch (error) {
clearInterval(progressInterval);
showAlert('error', '网络错误: ' + error.message);
document.getElementById('progressContainer').style.display = 'none';
}
isProcessing = false;
document.getElementById('startTestBtn').disabled = false;
document.getElementById('startTestBtn').textContent = '🚀 开始测试';
}
// 显示检测结果
function displayResults(results) {
// 更新汇总统计
document.getElementById('totalDetections').textContent = results.fall_events;
const avgConf = results.detections.length > 0
? (results.detections.reduce((sum, d) => sum + d.confidence, 0) / results.detections.length).toFixed(3)
: '0';
document.getElementById('avgConfidence').textContent = avgConf;
document.getElementById('processingSpeed').textContent = '28.5 FPS';
document.getElementById('responseTime').textContent = results.processing_time + 's';
// 显示详细检测结果
const resultsContainer = document.getElementById('detectionResults');
if (results.detections.length === 0) {
resultsContainer.innerHTML = '<div class="alert alert-info">未检测到跌倒事件</div>';
return;
}
let html = '';
results.detections.forEach((detection, index) => {
const confidenceClass = detection.confidence >= 0.9 ? 'high-confidence' : 'medium-confidence';
const confidenceBadge = detection.confidence >= 0.9 ? 'high' :
detection.confidence >= 0.8 ? 'medium' : 'low';
html += `
<div class="detection-item ${confidenceClass}">
<div class="detection-header">
<div class="detection-title">🚨 跌倒事件 #${index + 1}</div>
<div class="confidence-badge ${confidenceBadge}">
置信度 ${(detection.confidence * 100).toFixed(1)}%
</div>
</div>
<div class="detection-details">
<div class="detail-item">
<div class="detail-label">事件类型</div>
<div class="detail-value">${getEventTypeText(detection.subtype)}</div>
</div>
<div class="detail-item">
<div class="detail-label">发生时间</div>
<div class="detail-value">${detection.start_time.toFixed(1)}s</div>
</div>
<div class="detail-item">
<div class="detail-label">持续时间</div>
<div class="detail-value">${detection.duration}s</div>
</div>
<div class="detail-item">
<div class="detail-label">严重程度</div>
<div class="detail-value">${detection.severity}</div>
</div>
<div class="detail-item">
<div class="detail-label">身体角度</div>
<div class="detail-value">${detection.body_angle}°</div>
</div>
<div class="detail-item">
<div class="detail-label">跌倒方向</div>
<div class="detail-value">${getDirectionText(detection.fall_direction)}</div>
</div>
</div>
</div>
`;
});
resultsContainer.innerHTML = html;
}
// 重置演示
async function resetDemo() {
try {
await fetch(`${demoAPI}/api/demo/reset`, {method: 'POST'});
document.getElementById('detectionResults').innerHTML =
'<div class="alert alert-info">演示已重置,点击"开始测试"重新开始</div>';
// 重置统计
document.getElementById('totalDetections').textContent = '-';
document.getElementById('avgConfidence').textContent = '-';
document.getElementById('processingSpeed').textContent = '-';
document.getElementById('responseTime').textContent = '-';
showAlert('success', '演示状态已重置');
} catch (error) {
showAlert('error', '重置失败: ' + error.message);
}
}
// 显示算法对比
async function showComparison() {
try {
const response = await fetch(`${demoAPI}/api/algorithm/comparison`);
const data = await response.json();
const section = document.getElementById('comparisonSection');
const grid = document.getElementById('comparisonGrid');
let html = '';
Object.keys(data.comparison_data).forEach(key => {
const algo = data.comparison_data[key];
const isOurs = key === 'our_algorithm';
html += `
<div class="algorithm-card ${isOurs ? 'our-algorithm' : ''}">
<div class="algorithm-name">${algo.name}</div>
<div class="performance-grid">
<div class="performance-item">
<div class="performance-label">准确率</div>
<div class="performance-value">${algo.accuracy}%</div>
</div>
<div class="performance-item">
<div class="performance-label">精确率</div>
<div class="performance-value">${algo.precision}%</div>
</div>
<div class="performance-item">
<div class="performance-label">召回率</div>
<div class="performance-value">${algo.recall}%</div>
</div>
<div class="performance-item">
<div class="performance-label">处理速度</div>
<div class="performance-value">${algo.processing_speed}</div>
</div>
<div class="performance-item">
<div class="performance-label">误报率</div>
<div class="performance-value">${(algo.false_positive_rate * 100).toFixed(2)}%</div>
</div>
<div class="performance-item">
<div class="performance-label">延迟</div>
<div class="performance-value">${algo.detection_latency}</div>
</div>
</div>
<div style="margin-top: 15px;">
${algo.features.map(f => `<div style="font-size: 0.9em; margin: 5px 0;">• ${f}</div>`).join('')}
</div>
</div>
`;
});
grid.innerHTML = html;
section.style.display = 'block';
section.scrollIntoView({behavior: 'smooth'});
} catch (error) {
showAlert('error', '加载对比数据失败: ' + error.message);
}
}
// 加载算法亮点
async function loadAlgorithmHighlights() {
try {
const response = await fetch(`${demoAPI}/api/demo/highlights`);
const data = await response.json();
const grid = document.getElementById('highlightsGrid');
const highlights = data.algorithm_highlights;
grid.innerHTML = `
<div class="info-item">
<div class="info-label">精度提升</div>
<div class="info-value">${highlights.accuracy_improvement}</div>
</div>
<div class="info-item">
<div class="info-label">误报降低</div>
<div class="info-value">${highlights.false_positive_reduction}</div>
</div>
<div class="info-item">
<div class="info-label">速度提升</div>
<div class="info-value">${highlights.speed_improvement}</div>
</div>
<div class="info-item">
<div class="info-label">老年人优化</div>
<div class="info-value">${highlights.elderly_optimized ? '✅ 是' : '❌ 否'}</div>
</div>
`;
} catch (error) {
console.error('加载算法亮点失败:', error);
}
}
// 显示提示信息
function showAlert(type, message) {
const alert = document.createElement('div');
alert.className = `alert alert-${type}`;
alert.textContent = message;
document.getElementById('detectionResults').appendChild(alert);
setTimeout(() => {
alert.remove();
}, 5000);
}
// 获取事件类型文本
function getEventTypeText(subtype) {
const types = {
'backward_fall': '向后跌倒',
'forward_fall': '向前跌倒',
'side_fall': '侧向跌倒',
'fall_detected': '跌倒检测'
};
return types[subtype] || subtype;
}
// 获取方向文本
function getDirectionText(direction) {
const directions = {
'backward': '向后',
'forward': '向前',
'side': '侧向',
'left': '左侧',
'right': '右侧'
};
return directions[direction] || direction;
}
// 定期更新API状态
setInterval(checkAPIStatus, 30000);
</script>
</body>
</html>