329 lines
16 KiB
HTML
329 lines
16 KiB
HTML
|
|
{% extends "layout.html" %}
|
||
|
|
|
||
|
|
{% block content %}
|
||
|
|
<div class="row">
|
||
|
|
<!-- 左侧面板:检测结果 -->
|
||
|
|
<div class="col-md-8">
|
||
|
|
<div class="card shadow-sm">
|
||
|
|
<div class="card-header bg-dark text-white">
|
||
|
|
<h5 class="card-title mb-0"><i class="bi bi-list-check"></i> 检测结果列表</h5>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
{% if reports %}
|
||
|
|
<div class="table-responsive">
|
||
|
|
<table class="table table-striped table-hover table-bordered">
|
||
|
|
<thead class="table-dark">
|
||
|
|
<tr>
|
||
|
|
<th><i class="bi bi-calendar"></i> 日期时间</th>
|
||
|
|
<th><i class="bi bi-film"></i> 视频数</th>
|
||
|
|
<th class="text-center"><i class="bi bi-eye"></i> 操作</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
{% for report in reports %}
|
||
|
|
<tr>
|
||
|
|
<td>{{ report.timestamp }}</td>
|
||
|
|
<td class="text-center">{{ report.video_count }}</td>
|
||
|
|
<td class="text-center">
|
||
|
|
<div class="d-flex justify-content-center gap-2">
|
||
|
|
<a href="{{ url_for('view_report', report_filename=report.id) }}" class="btn btn-sm btn-primary">
|
||
|
|
<i class="bi bi-file-text"></i> 查看报告
|
||
|
|
</a>
|
||
|
|
<form action="{{ url_for('delete_report', report_filename=report.id) }}" method="post" class="d-inline">
|
||
|
|
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('确定要删除此报告吗?')">
|
||
|
|
<i class="bi bi-trash"></i> 删除
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
{% endfor %}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
{% else %}
|
||
|
|
<div class="alert alert-info">
|
||
|
|
<i class="bi bi-info-circle me-2"></i> 暂无检测结果。请上传视频并开始检测。
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 右侧面板:文件处理和配置 -->
|
||
|
|
<div class="col-md-4">
|
||
|
|
<div class="card mb-4 shadow-sm">
|
||
|
|
<div class="card-header bg-dark text-white">
|
||
|
|
<h5 class="card-title mb-0"><i class="bi bi-box"></i> 模型管理</h5>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
<form action="{{ url_for('upload_model') }}" method="post" enctype="multipart/form-data">
|
||
|
|
<div class="upload-area mb-3" onclick="document.getElementById('model_file').click();">
|
||
|
|
<div class="upload-icon">
|
||
|
|
<i class="bi bi-cloud-arrow-up"></i>
|
||
|
|
</div>
|
||
|
|
<p>点击上传新模型 (.pt 文件)</p>
|
||
|
|
<input type="file" class="form-control d-none" id="model_file" name="model_file" accept=".pt">
|
||
|
|
</div>
|
||
|
|
<button type="submit" class="btn btn-primary w-100 py-2">
|
||
|
|
<i class="bi bi-upload"></i> 上传模型
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
|
||
|
|
<hr>
|
||
|
|
|
||
|
|
<div class="d-flex justify-content-between align-items-center mt-3">
|
||
|
|
<h6><i class="bi bi-list-check"></i> 可用模型:</h6>
|
||
|
|
</div>
|
||
|
|
<ul class="list-group mt-2">
|
||
|
|
{% if models %}
|
||
|
|
{% for model in models %}
|
||
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||
|
|
<div>
|
||
|
|
<i class="bi bi-file-earmark-binary me-2"></i> {{ model }}
|
||
|
|
</div>
|
||
|
|
<form action="{{ url_for('delete_model', filename=model) }}" method="post" class="d-inline">
|
||
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('确定要删除此模型吗?')">
|
||
|
|
<i class="bi bi-trash"></i>
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
</li>
|
||
|
|
{% endfor %}
|
||
|
|
{% else %}
|
||
|
|
<li class="list-group-item text-center text-muted">
|
||
|
|
<i class="bi bi-exclamation-circle"></i> 暂无可用模型
|
||
|
|
</li>
|
||
|
|
{% endif %}
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="card mb-4 shadow-sm">
|
||
|
|
<div class="card-header bg-dark text-white">
|
||
|
|
<h5 class="card-title mb-0"><i class="bi bi-camera-video"></i> 视频文件管理</h5>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
<div class="alert alert-info">
|
||
|
|
<i class="bi bi-info-circle me-2"></i> <strong>请直接将视频文件放入以下文件夹:</strong>
|
||
|
|
<div class="mt-2 p-2 bg-light rounded">
|
||
|
|
<code>{{ config.INPUT_FOLDER }}</code>
|
||
|
|
</div>
|
||
|
|
<p class="small mt-2 mb-0">支持格式: mp4, avi, mov, mkv, wmv, flv, webm</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="d-flex justify-content-between align-items-center mt-3">
|
||
|
|
<h6><i class="bi bi-list-check"></i> 当前视频文件:</h6>
|
||
|
|
<form action="{{ url_for('clear_input_folder') }}" method="post">
|
||
|
|
<button type="submit" class="btn btn-outline-danger btn-sm">
|
||
|
|
<i class="bi bi-trash"></i> 清空
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<ul class="list-group mt-2">
|
||
|
|
{% if videos %}
|
||
|
|
{% for video in videos %}
|
||
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||
|
|
<div>
|
||
|
|
<i class="bi bi-film me-2"></i> {{ video }}
|
||
|
|
</div>
|
||
|
|
<form action="{{ url_for('delete_video', filename=video) }}" method="post" class="d-inline">
|
||
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('确定要删除此视频吗?')">
|
||
|
|
<i class="bi bi-trash"></i>
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
</li>
|
||
|
|
{% endfor %}
|
||
|
|
{% else %}
|
||
|
|
<li class="list-group-item text-center text-muted">
|
||
|
|
<i class="bi bi-exclamation-circle"></i> 暂无视频文件
|
||
|
|
</li>
|
||
|
|
{% endif %}
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="card shadow-sm">
|
||
|
|
<div class="card-header bg-dark text-white">
|
||
|
|
<h5 class="card-title mb-0"><i class="bi bi-play-circle"></i> 开始检测</h5>
|
||
|
|
</div>
|
||
|
|
<div class="card-body">
|
||
|
|
<form action="{{ url_for('start_detection') }}" method="post" id="detection-form">
|
||
|
|
<div class="mb-3">
|
||
|
|
<label for="model_select" class="form-label"><i class="bi bi-box-seam"></i> 选择模型</label>
|
||
|
|
<select class="form-select" id="model_select" name="model_select" required>
|
||
|
|
<option value="" selected disabled>-- 请选择模型 --</option>
|
||
|
|
{% for model in models %}
|
||
|
|
{% if model is string %}
|
||
|
|
<option value="{{ model }}">{{ model }}</option>
|
||
|
|
{% else %}
|
||
|
|
<option value="{{ model.filename }}"
|
||
|
|
data-analyzed="{{ model.analyzed }}"
|
||
|
|
data-class-count="{{ model.class_count }}"
|
||
|
|
data-scene-types="{{ model.scene_types|join(', ') if model.scene_types else '' }}">
|
||
|
|
{{ model.filename }}
|
||
|
|
{% if model.analyzed and model.class_count and model.class_count > 0 %}
|
||
|
|
- {{ model.class_count }}种场景: {{ model.scene_types|join(', ') if model.scene_types else '未知' }}
|
||
|
|
{% endif %}
|
||
|
|
</option>
|
||
|
|
{% endif %}
|
||
|
|
{% endfor %}
|
||
|
|
</select>
|
||
|
|
<div id="model-info" class="mt-2" style="display: none;">
|
||
|
|
<small class="text-muted">
|
||
|
|
<i class="bi bi-info-circle"></i>
|
||
|
|
<span id="model-details"></span>
|
||
|
|
</small>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="mb-3">
|
||
|
|
<label for="confidence_threshold" class="form-label">
|
||
|
|
<i class="bi bi-sliders"></i> 置信度阈值: <span class="confidence-value" id="confidence_value">{{ confidence_threshold }}</span>
|
||
|
|
</label>
|
||
|
|
<input type="range" class="form-range" id="confidence_threshold" name="confidence_threshold"
|
||
|
|
min="0.1" max="0.9" step="0.05" value="{{ confidence_threshold }}">
|
||
|
|
<div class="d-flex justify-content-between">
|
||
|
|
<small>0.1 (低精度)</small>
|
||
|
|
<small>0.9 (高精度)</small>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<button type="submit" class="btn btn-success w-100 py-2" id="start-detection-btn" {% if detection_in_progress %}disabled="disabled"{% endif %}>
|
||
|
|
<i class="bi bi-play-fill"></i> 开始检测
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
|
||
|
|
{% if detection_in_progress %}
|
||
|
|
<div class="mt-3" id="detection-progress-container">
|
||
|
|
<h6 class="text-center">检测进行中...</h6>
|
||
|
|
<div class="progress mb-2">
|
||
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||
|
|
role="progressbar" data-progress="{{ detection_progress }}"
|
||
|
|
aria-valuenow="{{ detection_progress }}" aria-valuemin="0" aria-valuemax="100">
|
||
|
|
{{ detection_progress }}%
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-center">
|
||
|
|
<p class="mb-1" id="detection-status">{{ detection_status }}</p>
|
||
|
|
<small class="text-muted" id="video-info"></small>
|
||
|
|
<br>
|
||
|
|
<small class="text-muted" id="frame-info"></small>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{% endif %}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{% endblock %}
|
||
|
|
|
||
|
|
{% block scripts %}
|
||
|
|
<script>
|
||
|
|
// 初始化进度条宽度
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
const progressBar = document.querySelector('.progress-bar');
|
||
|
|
if (progressBar) {
|
||
|
|
const progress = progressBar.getAttribute('data-progress');
|
||
|
|
if (progress) {
|
||
|
|
progressBar.style.width = progress + '%';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 更新置信度显示值
|
||
|
|
const confidenceSlider = document.getElementById('confidence_threshold');
|
||
|
|
const confidenceValue = document.getElementById('confidence_value');
|
||
|
|
|
||
|
|
confidenceSlider.addEventListener('input', function() {
|
||
|
|
confidenceValue.textContent = this.value;
|
||
|
|
});
|
||
|
|
|
||
|
|
// 模型选择变化事件
|
||
|
|
const modelSelect = document.getElementById('model_select');
|
||
|
|
const modelInfo = document.getElementById('model-info');
|
||
|
|
const modelDetails = document.getElementById('model-details');
|
||
|
|
|
||
|
|
modelSelect.addEventListener('change', function() {
|
||
|
|
const selectedOption = this.options[this.selectedIndex];
|
||
|
|
const analyzed = selectedOption.getAttribute('data-analyzed') === 'True';
|
||
|
|
const classCount = selectedOption.getAttribute('data-class-count');
|
||
|
|
const sceneTypes = selectedOption.getAttribute('data-scene-types');
|
||
|
|
|
||
|
|
if (analyzed && classCount > 0) {
|
||
|
|
modelDetails.textContent = `此模型可检测 ${classCount} 种场景类型: ${sceneTypes}`;
|
||
|
|
modelInfo.style.display = 'block';
|
||
|
|
} else if (this.value) {
|
||
|
|
modelDetails.textContent = '模型信息未分析,将在首次使用时进行分析';
|
||
|
|
modelInfo.style.display = 'block';
|
||
|
|
} else {
|
||
|
|
modelInfo.style.display = 'none';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// 视频文件管理相关脚本已移除
|
||
|
|
|
||
|
|
document.getElementById('model_file').addEventListener('change', function() {
|
||
|
|
if (this.files.length > 0) {
|
||
|
|
const fileName = this.files[0].name;
|
||
|
|
this.closest('.upload-area').querySelector('p').innerHTML = `已选择: ${fileName}`;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
|
||
|
|
{% if detection_in_progress %}
|
||
|
|
<script>
|
||
|
|
// 检测进度更新
|
||
|
|
function updateProgress() {
|
||
|
|
fetch('{{ url_for("get_detection_progress") }}')
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(data => {
|
||
|
|
const progressBar = document.querySelector('.progress-bar');
|
||
|
|
progressBar.style.width = data.progress + '%';
|
||
|
|
progressBar.setAttribute('aria-valuenow', data.progress);
|
||
|
|
progressBar.textContent = data.progress + '%';
|
||
|
|
|
||
|
|
// 更新状态文本,添加检测时长信息
|
||
|
|
let statusText = data.status;
|
||
|
|
if (data.elapsed_time) {
|
||
|
|
statusText += ` (已用时间: ${data.elapsed_time})`;
|
||
|
|
}
|
||
|
|
document.getElementById('detection-status').textContent = statusText;
|
||
|
|
|
||
|
|
// 更新视频信息
|
||
|
|
const videoInfo = document.getElementById('video-info');
|
||
|
|
if (data.current_video && data.total_videos > 0) {
|
||
|
|
videoInfo.textContent = `当前视频: ${data.current_video} (${data.current_video_index}/${data.total_videos})`;
|
||
|
|
} else {
|
||
|
|
videoInfo.textContent = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
// 更新帧信息
|
||
|
|
const frameInfo = document.getElementById('frame-info');
|
||
|
|
if (data.current_frame_count > 0) {
|
||
|
|
frameInfo.textContent = `已处理帧数: ${data.current_frame_count}`;
|
||
|
|
} else {
|
||
|
|
frameInfo.textContent = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (data.in_progress) {
|
||
|
|
setTimeout(updateProgress, 1000);
|
||
|
|
} else {
|
||
|
|
// 检测完成,刷新页面
|
||
|
|
setTimeout(() => {
|
||
|
|
window.location.reload();
|
||
|
|
}, 2000);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
console.error('获取进度信息失败:', error);
|
||
|
|
// 如果获取进度失败,继续尝试
|
||
|
|
setTimeout(updateProgress, 2000);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 开始轮询进度
|
||
|
|
updateProgress();
|
||
|
|
</script>
|
||
|
|
{% endif %}
|
||
|
|
{% endblock %}
|