Compare commits
3 Commits
30ea6eb0fb
...
9be93340a7
| Author | SHA1 | Date | |
|---|---|---|---|
| 9be93340a7 | |||
| ca4f977ff0 | |||
| 0e011dacfd |
@@ -20,7 +20,8 @@ async def detect_image(
|
|||||||
model_id: str = Query("fire_detection"),
|
model_id: str = Query("fire_detection"),
|
||||||
confidence: float = Query(0.5),
|
confidence: float = Query(0.5),
|
||||||
iou: float = Query(0.45),
|
iou: float = Query(0.45),
|
||||||
algorithm_config: Optional[str] = Query(None, description="算法配置JSON字符串")
|
algorithm_config: Optional[str] = Query(None, description="算法配置JSON字符串"),
|
||||||
|
composite: bool = Query(False, description="是否启用复合检测(火灾检测时同时检测火焰和烟雾)")
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
图片检测接口
|
图片检测接口
|
||||||
@@ -61,9 +62,15 @@ async def detect_image(
|
|||||||
data={}
|
data={}
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await detection_service.detect_image(
|
# 判断是否启用复合火灾检测
|
||||||
frame, model_id, confidence, iou, algorithm_config=algo_config
|
if composite and model_id == 'fire_detection':
|
||||||
)
|
result = await detection_service.detect_fire_composite(
|
||||||
|
frame, confidence, iou
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result = await detection_service.detect_image(
|
||||||
|
frame, model_id, confidence, iou, algorithm_config=algo_config
|
||||||
|
)
|
||||||
|
|
||||||
if result['success']:
|
if result['success']:
|
||||||
annotated_frame = detection_service.draw_detections(
|
annotated_frame = detection_service.draw_detections(
|
||||||
|
|||||||
@@ -235,13 +235,15 @@ class CameraService:
|
|||||||
confidence = config.get('confidence', 0.5)
|
confidence = config.get('confidence', 0.5)
|
||||||
iou = config.get('iou', 0.45)
|
iou = config.get('iou', 0.45)
|
||||||
draw = True
|
draw = True
|
||||||
|
composite = config.get('composite', False)
|
||||||
|
|
||||||
processed_frame, result = await detection_service.detect_frame(
|
processed_frame, result = await detection_service.detect_frame(
|
||||||
frame,
|
frame,
|
||||||
model_id=model_id,
|
model_id=model_id,
|
||||||
confidence=confidence,
|
confidence=confidence,
|
||||||
iou=iou,
|
iou=iou,
|
||||||
draw=draw
|
draw=draw,
|
||||||
|
composite=composite
|
||||||
)
|
)
|
||||||
|
|
||||||
if result['success']:
|
if result['success']:
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import numpy as np
|
|||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
import torch
|
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
from .loitering_service import get_loitering_service
|
from .loitering_service import get_loitering_service
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -148,19 +149,39 @@ class DetectionService:
|
|||||||
model_id: str,
|
model_id: str,
|
||||||
confidence: float = 0.5,
|
confidence: float = 0.5,
|
||||||
iou: float = 0.45,
|
iou: float = 0.45,
|
||||||
draw: bool = True
|
draw: bool = True,
|
||||||
|
composite: bool = False
|
||||||
) -> tuple:
|
) -> tuple:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
model = await self.model_service.load_model(model_id)
|
|
||||||
if not model:
|
|
||||||
return frame, {
|
|
||||||
'success': False,
|
|
||||||
'detections': [],
|
|
||||||
'stats': None
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 如果是火灾检测模型且启用了复合检测
|
||||||
|
if composite and model_id == 'fire_detection':
|
||||||
|
result_data = await self.detect_fire_composite(frame, confidence=confidence, iou=iou)
|
||||||
|
|
||||||
|
if result_data['success']:
|
||||||
|
detections = result_data['detections']
|
||||||
|
processing_time = time.time() - start_time
|
||||||
|
fps = 1.0 / processing_time if processing_time > 0 else 0
|
||||||
|
|
||||||
|
# 更新 stats 中的 fps 和处理时间
|
||||||
|
result_data['stats']['fps'] = round(fps, 2)
|
||||||
|
result_data['stats']['processing_time'] = round(processing_time, 3)
|
||||||
|
|
||||||
|
if draw:
|
||||||
|
frame = self.draw_detections(frame, detections, fps)
|
||||||
|
|
||||||
|
return frame, result_data
|
||||||
|
|
||||||
|
# 普通单模型检测
|
||||||
|
model = await self.model_service.load_model(model_id)
|
||||||
|
if not model:
|
||||||
|
return frame, {
|
||||||
|
'success': False,
|
||||||
|
'detections': [],
|
||||||
|
'stats': None
|
||||||
|
}
|
||||||
|
|
||||||
results = model(frame, conf=confidence, iou=iou, verbose=False)
|
results = model(frame, conf=confidence, iou=iou, verbose=False)
|
||||||
|
|
||||||
detections = []
|
detections = []
|
||||||
@@ -282,6 +303,115 @@ class DetectionService:
|
|||||||
'stats': None
|
'stats': None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def detect_fire_composite(
|
||||||
|
self,
|
||||||
|
image: np.ndarray,
|
||||||
|
confidence: float = 0.1,
|
||||||
|
iou: float = 0.45
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
复合火灾检测:同时检测火焰和烟雾
|
||||||
|
"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 检测火焰
|
||||||
|
fire_model = await self.model_service.load_model('fire_detection')
|
||||||
|
fire_results = fire_model(image, conf=confidence, iou=iou, verbose=False)
|
||||||
|
|
||||||
|
fire_detections = []
|
||||||
|
for result in fire_results:
|
||||||
|
for box in result.boxes:
|
||||||
|
try:
|
||||||
|
xyxy_values = box.xyxy.squeeze().tolist()
|
||||||
|
if len(xyxy_values) >= 4:
|
||||||
|
x1, y1, x2, y2 = float(xyxy_values[0]), float(xyxy_values[1]), float(xyxy_values[2]), float(xyxy_values[3])
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
conf = float(box.conf[0]) if hasattr(box.conf, '__getitem__') else float(box.conf)
|
||||||
|
cls = int(box.cls[0]) if hasattr(box.cls, '__getitem__') else int(box.cls)
|
||||||
|
class_name = result.names[cls]
|
||||||
|
|
||||||
|
if class_name == 'Fire':
|
||||||
|
fire_detections.append({
|
||||||
|
'class': 'Fire',
|
||||||
|
'label': '火焰',
|
||||||
|
'confidence': round(conf, 3),
|
||||||
|
'bbox': [int(x1), int(y1), int(x2), int(y2)],
|
||||||
|
'type': 'fire'
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"火焰检测解析失败: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 2. 检测烟雾(使用YOLOv10-M专用火灾烟雾模型,只保留 Smoke 类别)
|
||||||
|
smoke_model = await self.model_service.load_model('smoke_detection')
|
||||||
|
smoke_results = smoke_model(image, conf=confidence, iou=iou, verbose=False)
|
||||||
|
|
||||||
|
smoke_detections = []
|
||||||
|
for result in smoke_results:
|
||||||
|
for box in result.boxes:
|
||||||
|
try:
|
||||||
|
xyxy_values = box.xyxy.squeeze().tolist()
|
||||||
|
if len(xyxy_values) >= 4:
|
||||||
|
x1, y1, x2, y2 = float(xyxy_values[0]), float(xyxy_values[1]), float(xyxy_values[2]), float(xyxy_values[3])
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
conf = float(box.conf[0]) if hasattr(box.conf, '__getitem__') else float(box.conf)
|
||||||
|
cls = int(box.cls[0]) if hasattr(box.cls, '__getitem__') else int(box.cls)
|
||||||
|
class_name = result.names[cls]
|
||||||
|
|
||||||
|
# 只保留 Smoke 类别(注意模型输出为大写 S)
|
||||||
|
if class_name == 'Smoke':
|
||||||
|
smoke_detections.append({
|
||||||
|
'class': 'Smoke',
|
||||||
|
'label': '烟雾',
|
||||||
|
'confidence': round(conf, 3),
|
||||||
|
'bbox': [int(x1), int(y1), int(x2), int(y2)],
|
||||||
|
'type': 'smoke'
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"烟雾检测解析失败: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 3. 合并所有检测
|
||||||
|
all_detections = fire_detections + smoke_detections
|
||||||
|
|
||||||
|
# 4. 判定是否疑似火灾(火焰或烟雾任一检测到即判定为是)
|
||||||
|
suspected_fire = len(fire_detections) > 0 or len(smoke_detections) > 0
|
||||||
|
|
||||||
|
processing_time = time.time() - start_time
|
||||||
|
avg_confidence = sum(d['confidence'] for d in all_detections) / len(all_detections) if all_detections else 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'message': '复合火灾检测完成',
|
||||||
|
'detections': all_detections,
|
||||||
|
'stats': {
|
||||||
|
'total_detections': len(all_detections),
|
||||||
|
'fire_count': len(fire_detections),
|
||||||
|
'smoke_count': len(smoke_detections),
|
||||||
|
'avg_confidence': round(avg_confidence, 3),
|
||||||
|
'suspected_fire': suspected_fire,
|
||||||
|
'suspected_fire_label': '是' if suspected_fire else '否',
|
||||||
|
'processing_time': round(processing_time, 3),
|
||||||
|
'model_used': 'fire_composite'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"复合火灾检测失败: {e}")
|
||||||
|
import traceback
|
||||||
|
logger.error(f"错误堆栈: {traceback.format_exc()}")
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'message': f'复合火灾检测失败: {str(e)}',
|
||||||
|
'detections': [],
|
||||||
|
'stats': None
|
||||||
|
}
|
||||||
|
|
||||||
def _apply_behavior_analysis(
|
def _apply_behavior_analysis(
|
||||||
self,
|
self,
|
||||||
result_data: Dict,
|
result_data: Dict,
|
||||||
|
|||||||
@@ -40,12 +40,21 @@ class ModelService:
|
|||||||
self.model_configs = {
|
self.model_configs = {
|
||||||
'fire_detection': {
|
'fire_detection': {
|
||||||
'path': os.path.join(base_dir, 'models', 'fire_detection', 'best.pt'),
|
'path': os.path.join(base_dir, 'models', 'fire_detection', 'best.pt'),
|
||||||
|
'type': 'yolov8',
|
||||||
|
'classes': ['Fire'],
|
||||||
|
'labels': {'Fire': '火焰'},
|
||||||
|
'size': '22MB',
|
||||||
|
'description': '基于YOLOv8的火焰检测模型',
|
||||||
|
'name': '火焰检测'
|
||||||
|
},
|
||||||
|
'smoke_detection': {
|
||||||
|
'path': os.path.join(base_dir, 'models', 'fire_detection', 'yolov10_fire_smoke_best.pt'),
|
||||||
'type': 'yolov10',
|
'type': 'yolov10',
|
||||||
'classes': ['Fire', 'Smoke'],
|
'classes': ['Fire', 'Smoke'],
|
||||||
'labels': {'Fire': '火焰', 'Smoke': '烟雾'},
|
'labels': {'Fire': '火焰', 'Smoke': '烟雾'},
|
||||||
'size': '61MB',
|
'size': '33MB',
|
||||||
'description': '基于YOLOv10的火灾烟雾检测模型',
|
'description': '基于YOLOv10-M的火灾烟雾检测模型(来自GitHub开源项目,123K+图片训练)',
|
||||||
'name': '火灾检测'
|
'name': '烟雾检测 (YOLOv10-M)'
|
||||||
},
|
},
|
||||||
'helmet_detection': {
|
'helmet_detection': {
|
||||||
'path': os.path.join(base_dir, 'models', 'helmet_detection', 'yolov8n.pt'),
|
'path': os.path.join(base_dir, 'models', 'helmet_detection', 'yolov8n.pt'),
|
||||||
|
|||||||
@@ -117,6 +117,36 @@
|
|||||||
<div class="slider-value">{{ config.iou.toFixed(2) }}</div>
|
<div class="slider-value">{{ config.iou.toFixed(2) }}</div>
|
||||||
</el-form-item>
|
</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
|
<AlgorithmConfig
|
||||||
v-model="config.algorithmConfig"
|
v-model="config.algorithmConfig"
|
||||||
@@ -129,7 +159,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import {
|
import {
|
||||||
Setting,
|
Setting,
|
||||||
Picture,
|
Picture,
|
||||||
@@ -167,9 +197,15 @@ const config = ref({
|
|||||||
model: props.defaultModel || (props.models.length > 0 ? props.models[0].id : ''),
|
model: props.defaultModel || (props.models.length > 0 ? props.models[0].id : ''),
|
||||||
confidence: 0.5,
|
confidence: 0.5,
|
||||||
iou: 0.45,
|
iou: 0.45,
|
||||||
|
composite: false,
|
||||||
algorithmConfig: {}
|
algorithmConfig: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 判断当前是否是火灾检测模型
|
||||||
|
const isFireDetectionModel = computed(() => {
|
||||||
|
return config.value.model === 'fire_detection'
|
||||||
|
})
|
||||||
|
|
||||||
const formatConfidence = (value) => {
|
const formatConfidence = (value) => {
|
||||||
return `置信度: ${value.toFixed(2)}`
|
return `置信度: ${value.toFixed(2)}`
|
||||||
}
|
}
|
||||||
@@ -191,9 +227,16 @@ watch(configType, (newType) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 监听配置变化
|
// 监听配置变化
|
||||||
watch(() => [config.value.model, config.value.confidence, config.value.iou], () => {
|
watch(() => [config.value.model, config.value.confidence, config.value.iou, config.value.composite], () => {
|
||||||
emit('config-change', config.value)
|
emit('config-change', config.value)
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
// 监听模型变化,如果不是火灾模型,关闭复合检测
|
||||||
|
watch(() => config.value.model, (newModel) => {
|
||||||
|
if (newModel !== 'fire_detection') {
|
||||||
|
config.value.composite = false
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -112,6 +112,21 @@
|
|||||||
<div class="stat-label">检测数量</div>
|
<div class="stat-label">检测数量</div>
|
||||||
<el-tag size="large" type="primary">{{ stats.total_detections }} 个</el-tag>
|
<el-tag size="large" type="primary">{{ stats.total_detections }} 个</el-tag>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 复合检测统计 -->
|
||||||
|
<div v-if="stats.fire_count !== undefined" class="stat-item">
|
||||||
|
<div class="stat-label">火焰数量</div>
|
||||||
|
<el-tag size="large" type="danger">{{ stats.fire_count }} 个</el-tag>
|
||||||
|
</div>
|
||||||
|
<div v-if="stats.smoke_count !== undefined" class="stat-item">
|
||||||
|
<div class="stat-label">烟雾数量</div>
|
||||||
|
<el-tag size="large" type="warning">{{ stats.smoke_count }} 个</el-tag>
|
||||||
|
</div>
|
||||||
|
<div v-if="stats.suspected_fire !== undefined" class="stat-item">
|
||||||
|
<div class="stat-label">疑似火灾</div>
|
||||||
|
<el-tag size="large" :type="stats.suspected_fire ? 'danger' : 'success'">
|
||||||
|
{{ stats.suspected_fire_label || (stats.suspected_fire ? '是' : '否') }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<div class="stat-label">平均置信度</div>
|
<div class="stat-label">平均置信度</div>
|
||||||
<el-tag size="large" :type="getConfidenceType(stats.avg_confidence)">
|
<el-tag size="large" :type="getConfidenceType(stats.avg_confidence)">
|
||||||
@@ -310,6 +325,7 @@ const config = ref({
|
|||||||
model: props.models.length > 0 ? props.models[0].id : 'fire_detection',
|
model: props.models.length > 0 ? props.models[0].id : 'fire_detection',
|
||||||
confidence: 0.5,
|
confidence: 0.5,
|
||||||
iou: 0.45,
|
iou: 0.45,
|
||||||
|
composite: false,
|
||||||
algorithmConfig: {}
|
algorithmConfig: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -392,6 +408,11 @@ const uploadUrl = computed(() => {
|
|||||||
iou: config.value.iou
|
iou: config.value.iou
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 添加复合检测参数(火灾检测模型时)
|
||||||
|
if (config.value.composite && config.value.model === 'fire_detection') {
|
||||||
|
params.append('composite', 'true')
|
||||||
|
}
|
||||||
|
|
||||||
if (config.value.algorithmConfig && Object.keys(config.value.algorithmConfig).length > 0) {
|
if (config.value.algorithmConfig && Object.keys(config.value.algorithmConfig).length > 0) {
|
||||||
params.append('algorithm_config', JSON.stringify(config.value.algorithmConfig))
|
params.append('algorithm_config', JSON.stringify(config.value.algorithmConfig))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ const config = ref({
|
|||||||
model: props.models.length > 0 ? props.models[0].id : 'fire_detection',
|
model: props.models.length > 0 ? props.models[0].id : 'fire_detection',
|
||||||
confidence: 0.5,
|
confidence: 0.5,
|
||||||
iou: 0.45,
|
iou: 0.45,
|
||||||
|
composite: false,
|
||||||
algorithmConfig: {}
|
algorithmConfig: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -346,7 +347,8 @@ const startCamera = async () => {
|
|||||||
config: {
|
config: {
|
||||||
model_id: config.value.model,
|
model_id: config.value.model,
|
||||||
confidence: config.value.confidence,
|
confidence: config.value.confidence,
|
||||||
iou: config.value.iou
|
iou: config.value.iou,
|
||||||
|
composite: config.value.composite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +418,8 @@ const updateCameraConfig = () => {
|
|||||||
config: {
|
config: {
|
||||||
model_id: config.value.model,
|
model_id: config.value.model,
|
||||||
confidence: config.value.confidence,
|
confidence: config.value.confidence,
|
||||||
iou: config.value.iou
|
iou: config.value.iou,
|
||||||
|
composite: config.value.composite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
models/fire_detection/best.pt
LFS
BIN
models/fire_detection/best.pt
LFS
Binary file not shown.
Reference in New Issue
Block a user