Files
jc-video-recognize/apps/web/src/components/DetectionConfig.vue

494 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="detection-config-container">
<el-card class="config-card" shadow="hover">
<template #header>
<div class="card-header">
<el-icon class="header-icon"><Setting /></el-icon>
<span>{{ configType === 'image' ? '图片检测配置' : '视频检测配置' }}</span>
</div>
</template>
<!-- 检测类型切换 -->
<div class="type-switcher">
<el-select
v-model="configType"
placeholder="选择检测类型"
class="type-select"
size="large"
>
<el-option
v-for="type in detectionTypes"
:key="type.value"
:label="type.label"
:value="type.value"
>
<div class="select-option">
<el-icon class="option-icon">
<component :is="type.icon" />
</el-icon>
<span class="option-label">{{ type.label }}</span>
</div>
</el-option>
</el-select>
</div>
<el-divider />
<el-form label-position="top" class="config-form">
<el-form-item label="选择模型">
<el-select
v-model="config.model"
placeholder="选择检测模型"
class="full-width"
>
<el-option
v-for="model in models"
:key="model.id"
:label="model.name"
:value="model.id"
>
<span>{{ model.name }}</span>
<span class="model-size">{{ model.size }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<template #label>
<span>置信度阈值</span>
<el-tooltip placement="top" :show-after="200">
<template #content>
<div style="max-width: 300px; line-height: 1.6;">
<p><strong>置信度是什么</strong></p>
<p>表示模型对检测结果的"确定程度"范围 0-1</p>
<ul style="margin: 8px 0; padding-left: 16px;">
<li>0.8高置信度绿色- 模型非常确定</li>
<li>0.6-0.8中等置信度黄色- 模型比较确定</li>
<li>&lt;0.6低置信度红色- 模型不太确定</li>
</ul>
<p><strong>阈值作用</strong></p>
<p>低于此值的检测结果会被过滤掉</p>
<p>建议0.3-0.5火灾检测可适当降低</p>
</div>
</template>
<el-icon class="help-icon"><QuestionFilled /></el-icon>
</el-tooltip>
</template>
<el-slider
v-model="config.confidence"
:min="0.1"
:max="1.0"
:step="0.05"
:format-tooltip="formatConfidence"
/>
<div class="slider-value">{{ config.confidence.toFixed(2) }}</div>
</el-form-item>
<el-form-item>
<template #label>
<span>IOU阈值</span>
<el-tooltip placement="top" :show-after="200">
<template #content>
<div style="max-width: 320px; line-height: 1.6;">
<p><strong>IOU是什么</strong></p>
<p>交并比(Intersection Over Union)衡量两个检测框的重叠程度</p>
<p style="margin: 8px 0;"><strong>计算公式</strong></p>
<p style="background: #f5f5f5; padding: 4px 8px; border-radius: 4px; font-family: monospace;">IOU = 交集面积 / 并集面积</p>
<p style="margin: 8px 0;"><strong>阈值作用</strong></p>
<p>用于NMS去重IOU超过此值的框被认为是重复检测</p>
<ul style="margin: 8px 0; padding-left: 16px;">
<li>高阈值(0.7-0.9)保留更多框适合密集场景</li>
<li>中阈值(0.45-0.6)平衡适合一般场景</li>
<li>低阈值(0.1-0.3)结果更精简适合稀疏场景</li>
</ul>
<p><strong>建议0.45-0.6</strong></p>
</div>
</template>
<el-icon class="help-icon"><QuestionFilled /></el-icon>
</el-tooltip>
</template>
<el-slider
v-model="config.iou"
:min="0.1"
:max="0.9"
:step="0.05"
:format-tooltip="formatIOU"
/>
<div class="slider-value">{{ config.iou.toFixed(2) }}</div>
</el-form-item>
<!-- 复合检测开关仅对火灾检测模型显示 -->
<el-form-item v-if="isFireDetectionModel">
<template #label>
<span>复合火灾检测</span>
<el-tooltip placement="top" :show-after="200">
<template #content>
<div style="max-width: 300px; line-height: 1.6;">
<p><strong>复合火灾检测是什么</strong></p>
<p>同时检测火焰和烟雾提高火灾识别准确率</p>
<p style="margin: 8px 0;"><strong>检测内容</strong></p>
<ul style="margin: 8px 0; padding-left: 16px;">
<li>火焰检测识别明火区域</li>
<li>烟雾检测识别烟雾区域</li>
<li>综合判断根据两者结果评估火灾等级</li>
</ul>
<p><strong>建议火灾检测场景建议开启</strong></p>
</div>
</template>
<el-icon class="help-icon"><QuestionFilled /></el-icon>
</el-tooltip>
</template>
<el-switch
v-model="config.composite"
active-text="开启"
inactive-text="关闭"
:active-value="true"
:inactive-value="false"
/>
</el-form-item>
<!-- 算法配置仅对人员检测模型显示 -->
<AlgorithmConfig
v-model="config.algorithmConfig"
@change="onAlgorithmChange"
:model-id="config.model"
/>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
import {
Setting,
Picture,
VideoCamera,
QuestionFilled
} from '@element-plus/icons-vue'
import AlgorithmConfig from './AlgorithmConfig.vue'
const props = defineProps({
models: {
type: Array,
default: () => []
},
defaultType: {
type: String,
default: 'image'
},
defaultModel: {
type: String,
default: ''
}
})
const emit = defineEmits(['type-change', 'config-change'])
const configType = ref(props.defaultType)
const detectionTypes = [
{ value: 'image', label: '图片检测', icon: Picture },
{ value: 'video', label: '视频检测', icon: VideoCamera }
]
const config = ref({
type: props.defaultType,
model: props.defaultModel || (props.models.length > 0 ? props.models[0].id : ''),
confidence: 0.5,
iou: 0.45,
composite: false,
algorithmConfig: {}
})
// 判断当前是否是火灾检测模型
const isFireDetectionModel = computed(() => {
return config.value.model === 'fire_detection'
})
const formatConfidence = (value) => {
return `置信度: ${value.toFixed(2)}`
}
const formatIOU = (value) => {
return `IOU: ${value.toFixed(2)}`
}
const onAlgorithmChange = (algoConfig) => {
config.value.algorithmConfig = algoConfig
emit('config-change', config.value)
}
// 监听类型变化
watch(configType, (newType) => {
config.value.type = newType
emit('type-change', newType)
emit('config-change', config.value)
})
// 监听配置变化
watch(() => [config.value.model, config.value.confidence, config.value.iou, config.value.composite], () => {
emit('config-change', config.value)
}, { deep: true })
// 监听模型变化,如果不是火灾模型,关闭复合检测
watch(() => config.value.model, (newModel) => {
if (newModel !== 'fire_detection') {
config.value.composite = false
}
})
</script>
<style scoped>
.detection-config-container {
display: flex;
flex-direction: column;
gap: 0;
}
.config-card {
height: auto;
max-height: calc(100vh - 100px);
overflow-y: auto;
}
.config-card :deep(.el-card__body) {
padding: 24px;
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
font-size: 16px;
color: #F8FAFC;
font-family: 'Fira Sans', sans-serif;
}
.header-icon {
font-size: 18px;
color: #22C55E;
}
/* 类型切换 */
.type-switcher {
margin-bottom: 24px;
}
.type-select {
width: 100%;
}
.type-select :deep(.el-input__wrapper) {
background: rgba(30, 41, 59, 0.6);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.type-select :deep(.el-input__wrapper:hover) {
border-color: rgba(34, 197, 94, 0.4);
background: rgba(30, 41, 59, 0.8);
}
.type-select :deep(.el-input__wrapper.is-focus) {
border-color: #22C55E;
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1);
}
.type-select :deep(.el-input__inner) {
color: #E2E8F0;
font-weight: 600;
font-size: 15px;
font-family: 'Fira Sans', sans-serif;
}
.type-select :deep(.el-select__caret) {
color: #64748B;
transition: all 0.3s ease;
}
.type-select:hover :deep(.el-select__caret) {
color: #22C55E;
}
.type-select :deep(.el-select__placeholder) {
color: #343637;
}
.type-select :deep(.el-select-dropdown__item) {
background: rgba(15, 23, 42, 0.95);
border: 1px solid rgba(255, 255, 255, 0.05);
margin: 4px 8px;
border-radius: 8px;
padding: 0;
transition: all 0.3s ease;
}
.type-select :deep(.el-select-dropdown__item:hover) {
background: rgba(34, 197, 94, 0.1);
border-color: rgba(34, 197, 94, 0.3);
transform: translateX(4px);
}
.type-select :deep(.el-select-dropdown__item.is-selected) {
background: rgba(34, 197, 94, 0.15);
border-color: #22C55E;
}
.type-select :deep(.el-select-dropdown__item.is-selected:hover) {
background: rgba(34, 197, 94, 0.2);
}
.type-select :deep(.el-select-dropdown) {
background: rgba(15, 23, 42, 0.98);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
backdrop-filter: blur(10px);
padding: 8px;
}
.type-select :deep(.el-popper__arrow) {
display: none;
}
.select-option {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
height: 100%;
padding: 12px 16px;
}
.option-icon {
font-size: 20px;
color: #3a3c3f;
transition: all 0.3s ease;
}
.type-select :deep(.el-select-dropdown__item:hover) .option-icon,
.type-select :deep(.el-select-dropdown__item.is-selected) .option-icon {
color: #22C55E;
transform: scale(1.1);
}
.option-label {
font-size: 14px;
font-weight: 600;
color: #3a3c3f;
}
:deep(.el-divider) {
border-color: rgba(255, 255, 255, 0.1);
margin: 20px 0;
}
.config-form :deep(.el-form-item) {
margin-bottom: 20px;
}
.config-form :deep(.el-form-item__label) {
font-weight: 500;
font-size: 14px;
color: #E2E8F0;
padding-bottom: 8px;
font-family: 'Fira Sans', sans-serif;
}
.full-width {
width: 100%;
}
.model-size {
float: right;
color: #64748B;
font-size: 12px;
}
.slider-value {
text-align: center;
font-size: 14px;
font-weight: 600;
color: #22C55E;
margin-top: 8px;
padding: 6px 12px;
background: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.3);
border-radius: 20px;
display: inline-block;
min-width: 60px;
font-family: 'Fira Code', monospace;
}
.help-icon {
margin-left: 6px;
font-size: 14px;
color: #64748B;
cursor: pointer;
transition: color 0.2s;
}
.help-icon:hover {
color: #22C55E;
}
/* 卡片通用样式 */
:deep(.el-card) {
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(30, 41, 59, 0.6);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
:deep(.el-card:hover) {
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
border-color: rgba(34, 197, 94, 0.2);
}
:deep(.el-card__header) {
padding: 16px 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
background: linear-gradient(135deg, rgba(30, 41, 59, 0.8) 0%, rgba(51, 65, 85, 0.8) 100%);
border-radius: 16px 16px 0 0;
}
/* 响应式 */
@media (max-width: 768px) {
.type-select :deep(.el-select) {
width: 100%;
}
.type-select :deep(.el-select__selected-item) {
font-size: 14px;
}
.option-icon {
font-size: 18px;
}
.option-label {
font-size: 13px;
}
}
@media (max-width: 480px) {
.type-select :deep(.el-select__selected-item) {
font-size: 13px;
}
.option-icon {
font-size: 16px;
}
.option-label {
font-size: 12px;
}
}
</style>