打架斗殴模型集成
This commit is contained in:
@@ -36,25 +36,51 @@
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:before-upload="beforeUpload"
|
||||
:show-file-list="false"
|
||||
accept="image/*"
|
||||
:accept="isActionDetection ? 'video/*' : 'image/*'"
|
||||
:disabled="isDetecting"
|
||||
class="header-upload"
|
||||
>
|
||||
<el-button type="primary" size="small">
|
||||
<el-button type="primary" size="small" :loading="isDetecting">
|
||||
<el-icon><UploadFilled /></el-icon>
|
||||
<span>{{ resultImage ? '上传新图片' : '上传图片' }}</span>
|
||||
<span>{{ resultImage || resultVideo ? '上传新文件' : (isActionDetection ? '上传视频' : '上传图片') }}</span>
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
<div class="image-container">
|
||||
<!-- 图片显示 -->
|
||||
<img
|
||||
v-if="resultImage"
|
||||
v-if="resultImage && !isActionDetection"
|
||||
:src="resultImage"
|
||||
class="display-image"
|
||||
alt="检测结果"
|
||||
/>
|
||||
<!-- 视频显示 -->
|
||||
<div v-else-if="isActionDetection" class="video-result-container">
|
||||
<video
|
||||
v-if="resultVideo"
|
||||
:src="resultVideo"
|
||||
class="display-video"
|
||||
controls
|
||||
alt="检测结果视频"
|
||||
/>
|
||||
<div v-else-if="isDetecting" class="detection-loading">
|
||||
<div class="loading-spinner">
|
||||
<el-icon class="loading-icon"><Loading /></el-icon>
|
||||
</div>
|
||||
<p class="loading-text">正在进行打架检测...</p>
|
||||
<p class="loading-hint">请稍候,视频检测可能需要几秒钟</p>
|
||||
</div>
|
||||
<div v-else class="empty-placeholder">
|
||||
<el-icon class="empty-icon"><VideoCamera /></el-icon>
|
||||
<p class="empty-text">请上传视频进行检测</p>
|
||||
<p class="empty-hint">支持 MP4、AVI 等格式</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 默认空状态 -->
|
||||
<div v-else class="empty-placeholder">
|
||||
<el-icon class="empty-icon"><Picture /></el-icon>
|
||||
<p class="empty-text">请上传图片进行检测</p>
|
||||
@@ -97,8 +123,21 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- Docker 检测结果 JSON 显示 -->
|
||||
<el-card v-if="dockerResult && isActionDetection" class="json-result-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon class="header-icon"><Files /></el-icon>
|
||||
<span>检测结果 (JSON)</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="json-content">
|
||||
<pre class="json-pre">{{ formattedJson }}</pre>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 检测详情 -->
|
||||
<el-card v-if="detections.length > 0" class="details-card" shadow="hover">
|
||||
<el-card v-if="detections.length > 0 && !isActionDetection" class="details-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon class="header-icon"><List /></el-icon>
|
||||
@@ -121,6 +160,40 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- 打架检测结果卡片 -->
|
||||
<el-card v-if="actionResult && isActionDetection" class="action-result-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon class="header-icon"><Warning /></el-icon>
|
||||
<span>打架检测结果</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="action-result-content">
|
||||
<div class="result-item">
|
||||
<span class="result-label">视频名称</span>
|
||||
<span class="result-value">{{ actionResult.video_name }}</span>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span class="result-label">检测结果</span>
|
||||
<el-tag :type="actionResult.result === 'fight' ? 'danger' : 'success'" size="large">
|
||||
{{ actionResult.result_text }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span class="result-label">置信度</span>
|
||||
<el-tag :type="getConfidenceType(actionResult.confidence / 100)" size="large">
|
||||
{{ actionResult.confidence_text }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<span class="result-label">置信度阈值</span>
|
||||
<el-tag type="info" size="large">
|
||||
{{ actionResult.confidence_threshold?.toFixed(2) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -131,9 +204,13 @@ import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
UploadFilled,
|
||||
Picture,
|
||||
VideoCamera,
|
||||
View,
|
||||
DataLine,
|
||||
List
|
||||
List,
|
||||
Files,
|
||||
Warning,
|
||||
Loading
|
||||
} from '@element-plus/icons-vue'
|
||||
import { detectionApi } from '@/api/detection'
|
||||
import DetectionConfig from './DetectionConfig.vue'
|
||||
@@ -192,16 +269,29 @@ const stopResize = () => {
|
||||
|
||||
const originalImage = ref('')
|
||||
const resultImage = ref('')
|
||||
const uploadedVideoUrl = ref('')
|
||||
const resultVideo = ref('')
|
||||
const detections = ref([])
|
||||
const stats = ref(null)
|
||||
const alerts = ref([])
|
||||
const isDetecting = ref(false)
|
||||
const dockerResult = ref(null)
|
||||
const actionResult = ref(null)
|
||||
|
||||
const isActionDetection = computed(() => {
|
||||
return config.value.model === 'action_detection'
|
||||
})
|
||||
|
||||
const uploadUrl = computed(() => {
|
||||
if (isActionDetection.value) {
|
||||
return null
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
model_id: config.value.model,
|
||||
confidence: config.value.confidence,
|
||||
iou: config.value.iou
|
||||
})
|
||||
|
||||
// 添加算法配置
|
||||
if (config.value.algorithmConfig && Object.keys(config.value.algorithmConfig).length > 0) {
|
||||
params.append('algorithm_config', JSON.stringify(config.value.algorithmConfig))
|
||||
}
|
||||
@@ -209,35 +299,61 @@ const uploadUrl = computed(() => {
|
||||
return `/api/detect/image?${params.toString()}`
|
||||
})
|
||||
|
||||
const beforeUpload = (file) => {
|
||||
const isImage = file.type.startsWith('image/')
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件')
|
||||
const formattedJson = computed(() => {
|
||||
if (!dockerResult.value) return ''
|
||||
return JSON.stringify(dockerResult.value, null, 2)
|
||||
})
|
||||
|
||||
const beforeUpload = async (file) => {
|
||||
isDetecting.value = true
|
||||
|
||||
if (isActionDetection.value) {
|
||||
const isVideo = file.type.startsWith('video/')
|
||||
if (!isVideo) {
|
||||
ElMessage.error('只能上传视频文件')
|
||||
isDetecting.value = false
|
||||
return false
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('video', file)
|
||||
formData.append('confidence', config.value.confidence)
|
||||
|
||||
try {
|
||||
const response = await detectionApi.detectVideo(formData)
|
||||
handleActionDetectionSuccess(response.data)
|
||||
} catch (error) {
|
||||
handleUploadError(error)
|
||||
}
|
||||
|
||||
return false
|
||||
} else {
|
||||
const isImage = file.type.startsWith('image/')
|
||||
if (!isImage) {
|
||||
ElMessage.error('只能上传图片文件')
|
||||
isDetecting.value = false
|
||||
return false
|
||||
}
|
||||
originalImage.value = URL.createObjectURL(file)
|
||||
return true
|
||||
}
|
||||
originalImage.value = URL.createObjectURL(file)
|
||||
return true
|
||||
}
|
||||
|
||||
const handleUploadSuccess = (response) => {
|
||||
isDetecting.value = false
|
||||
console.log('Upload success response:', response)
|
||||
|
||||
if (response.success) {
|
||||
// 使用 base64 图片数据
|
||||
if (response.data.image_base64) {
|
||||
resultImage.value = `data:image/jpeg;base64,${response.data.image_base64}`
|
||||
console.log('Result image set, length:', response.data.image_base64.length)
|
||||
} else {
|
||||
console.error('No image_base64 in response:', response.data)
|
||||
}
|
||||
detections.value = response.data.detections || []
|
||||
stats.value = response.data.stats
|
||||
|
||||
// 处理告警信息
|
||||
if (response.data.alerts && response.data.alerts.length > 0) {
|
||||
alerts.value = response.data.alerts
|
||||
console.log('收到告警:', response.data.alerts)
|
||||
|
||||
// 显示告警通知
|
||||
response.data.alerts.forEach(alert => {
|
||||
ElMessage({
|
||||
message: `行为告警: ${alert.type} - ${alert.message}`,
|
||||
@@ -253,6 +369,38 @@ const handleUploadSuccess = (response) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleActionDetectionSuccess = (response) => {
|
||||
isDetecting.value = false
|
||||
console.log('Action detection response:', response)
|
||||
|
||||
if (response.code === 200) {
|
||||
dockerResult.value = response
|
||||
actionResult.value = response.data
|
||||
|
||||
if (response.data.output_video) {
|
||||
const videoPath = response.data.output_video.replace(/^output\//, '')
|
||||
resultVideo.value = `/docker-output/${videoPath}`
|
||||
}
|
||||
|
||||
ElMessage.success(response.message)
|
||||
} else {
|
||||
ElMessage.error(response.message || '检测失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleUploadError = (error) => {
|
||||
isDetecting.value = false
|
||||
console.error('Upload error:', error)
|
||||
|
||||
if (error.response) {
|
||||
ElMessage.error(`上传失败: ${error.response.data.message || error.response.status}`)
|
||||
} else if (error.request) {
|
||||
ElMessage.error('请求超时或服务器未响应,请检查Docker服务是否正常运行')
|
||||
} else {
|
||||
ElMessage.error(`上传失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
const getConfidenceType = (confidence) => {
|
||||
if (!confidence && confidence !== 0) return 'info'
|
||||
if (confidence >= 0.8) return 'success'
|
||||
@@ -285,7 +433,6 @@ const modelName = computed(() => {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 拖拽调整条 */
|
||||
.resize-handle {
|
||||
width: 8px;
|
||||
flex-shrink: 0;
|
||||
@@ -326,7 +473,6 @@ const modelName = computed(() => {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 卡片通用样式 */
|
||||
:deep(.el-card) {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
@@ -348,7 +494,6 @@ const modelName = computed(() => {
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
/* 卡片头部样式 */
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -374,7 +519,6 @@ const modelName = computed(() => {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 配置卡片样式 */
|
||||
.config-card {
|
||||
height: auto;
|
||||
max-height: calc(100vh - 100px);
|
||||
@@ -401,14 +545,12 @@ const modelName = computed(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 模型选择器 */
|
||||
.model-size {
|
||||
float: right;
|
||||
color: #64748B;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 滑块样式 */
|
||||
.slider-value {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
@@ -424,7 +566,6 @@ const modelName = computed(() => {
|
||||
font-family: 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
/* 空状态占位区域 */
|
||||
.empty-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -444,8 +585,53 @@ const modelName = computed(() => {
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
color: #94A3B8;
|
||||
}
|
||||
|
||||
.detection-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
font-size: 48px;
|
||||
color: #22C55E;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 16px;
|
||||
color: #F8FAFC;
|
||||
font-weight: 500;
|
||||
margin: 0 0 8px 0;
|
||||
font-family: 'Fira Sans', sans-serif;
|
||||
}
|
||||
|
||||
.loading-hint {
|
||||
font-size: 13px;
|
||||
color: #64748B;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
@@ -454,7 +640,6 @@ const modelName = computed(() => {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 图片展示区域 */
|
||||
.image-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -488,6 +673,21 @@ const modelName = computed(() => {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.video-result-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.display-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
text-align: center;
|
||||
color: #64748B;
|
||||
@@ -505,7 +705,6 @@ const modelName = computed(() => {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-card {
|
||||
margin-bottom: 20px;
|
||||
height: calc(100% - 20px);
|
||||
@@ -553,7 +752,6 @@ const modelName = computed(() => {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 详情卡片 */
|
||||
.details-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -627,7 +825,6 @@ const modelName = computed(() => {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 帮助图标样式 */
|
||||
.help-icon {
|
||||
margin-left: 6px;
|
||||
font-size: 14px;
|
||||
@@ -640,7 +837,6 @@ const modelName = computed(() => {
|
||||
color: #22C55E;
|
||||
}
|
||||
|
||||
/* 告警卡片 */
|
||||
.alerts-card {
|
||||
margin-bottom: 20px;
|
||||
border: 2px solid #EF4444;
|
||||
@@ -722,7 +918,70 @@ const modelName = computed(() => {
|
||||
font-family: 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
/* 响应式布局 */
|
||||
.json-result-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.json-result-card :deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.json-content {
|
||||
background: #0F172A;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.json-pre {
|
||||
color: #22C55E;
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
margin: 0;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.action-result-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.action-result-card :deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.action-result-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
background: rgba(15, 23, 42, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 13px;
|
||||
color: #94A3B8;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 16px;
|
||||
color: #F8FAFC;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.image-detection-container {
|
||||
flex-direction: column;
|
||||
@@ -751,14 +1010,8 @@ const modelName = computed(() => {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.stats-descriptions :deep(.el-descriptions__body) {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.stats-descriptions :deep(.el-descriptions__cell) {
|
||||
padding: 12px;
|
||||
.action-result-content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -768,4 +1021,4 @@ const modelName = computed(() => {
|
||||
min-height: 180px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
Reference in New Issue
Block a user