火灾检测模型由基于YOLOv10的火灾烟雾检测模型改为复合模型[基于YOLOv8的火灾检测模型(单火焰检测)+YOLOv10-M,专用火灾烟雾模型]
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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