
這兩年森林防火的智能化需求越來越明確光靠人工巡護和傳統視頻監控已經很難滿足早期火情發現的要求。尤其是野外環境下火焰和煙霧的特征受光照、霧氣、植被遮擋影響非常大傳統圖像算法誤報率極高。我今年完整落地了一套基于YOLOv8/v10/v11/v12/26的野外火災火焰煙霧檢測系統后端用Spring Boot整合業務算法側用Flask單獨部署推理服務前端Vue做可視化大屏還接入了DeepSeek和千問大模型做火情研判與報告生成。這篇就把整個實現過程、模型選型對比、部署踩坑全部拆開講。這套系統適合誰參考如果你正在做畢業設計、智慧應急、林業信息化項目或者想在企業里快速搭一套AI視覺檢測的完整前后端鏈路都可以直接參考。1. 內容整體設計與思路拆解1.1 為什么選擇“Spring Boot Vue Flask 大模型”這套組合先回答一個最常被問到的問題為什么要用兩套后端一套Spring Boot一套Flask職責劃分為什么這么干脆我最初的方案是全部用Python寫Flask直接提供所有API前端Vue調用就行。但真做下去發現兩個問題一是工程項目里Spring Boot這種企業級框架對用戶管理、角色權限、日志審計、數據庫事務的支持要成熟得多二是在算法場景里Python側的庫依賴、GPU資源管理、模型熱加載這些事用Flask單獨隔離更干凈不會因為算法服務重啟影響業務服務穩定性。所以我最終定下來的架構是Spring Boot負責業務主服務包括用戶認證、巡檢任務、歷史記錄、模型版本管理、統計報表等。Flask獨立的推理服務只負責加載模型、接收圖像/視頻流、執行推理、返回檢測結果JSON。Vue管理端大屏和操作界面實時展示檢測框、置信度、火情等級、攝像頭點位。DeepSeek/千問大模型拿到檢測結果后自動生成火情研判報告比如可能的蔓延方向、建議調度資源、風險等級評估。MySQL RedisMySQL存業務數據Redis做熱數據緩存和實時告警隊列。這套組合最大的優勢是“算法服務能用Python生態業務系統能用Java生態”兩邊互不拖累。后來我在多個項目里復用了這套骨架改模型和改業務都很省事。1.2 YOLO多版本對比的思路v8到v26為什么都要試YOLO從v8一路迭代到v26每次版本更新的側重點都不一樣。有些版本在檢測精度上提升明顯有些版本在推理速度上做了大量優化有些版本則改進了訓練穩定性和小目標召回。但這不是說版本號越大就一定越適合火災場景野外火災檢測有它自己的特殊性火焰和煙霧的邊緣模糊、形態變化劇烈、目標尺度差異大、背景極其復雜。我選擇把五個版本全部復現和評測而不是直接用一個版本做到底原因很簡單只有同數據集、同訓練配置、同評估腳本下的橫向對比才有說服力。網上很多文章說“v11比v8強”往往是在COCO數據集上的結果遷移到煙霧火焰這類特殊目標上很可能結論反轉。所以這個系統的模型模塊從一開始就設計成多版本可插拔配置文件中指定model_type就能切換檢測后端。這樣既能做對比實驗也能在生產環境根據不同攝像頭算力靈活切換。1.3 系統整體模塊劃分與數據流設計整個系統的數據流是這樣的視頻流從攝像頭或無人機回傳經過RTMP/HLS接入到Flask推理服務推理服務對關鍵幀做檢測將帶有檢測框的結果推給前端實時展示同時把檢測記錄寫入數據庫。如果檢測到火情業務后端會觸發告警流程調用大模型生成火情描述和處置建議推送給值班人員并將處置結果回寫。從模塊上看系統分為六個核心模塊視頻接入管理支持RTSP/RTMP/HLS/本地視頻文件/圖片上傳協議轉換由Nginx和FFmpeg處理。模型推理引擎統一封裝YOLO系列模型的加載、推理、后處理輸出標準化檢測結果。數據管理模塊負責訓練集/驗證集管理、標注格式轉換、數據增強配置。業務功能模塊巡檢任務、告警工單、用戶權限、統計分析。大模型接入模塊統一封裝DeepSeek和千問API調用支持流式輸出和結構化JSON返回。前端可視化模塊GIS點位地圖、實時視頻墻、檢測記錄列表、火情趨勢圖表。這套數據流設計從實際部署效果來看非常穩。特別是把推理服務和業務服務分離之后模型升級不需要重啟業務系統攝像頭并發拉流也不會影響用戶操作體驗。2. 核心細節解析與實操要點2.1 數據集構建野外火災檢測的成敗關鍵做目標檢測項目數據集永遠是最關鍵的一環。剛開始我用的是公開火災數據集包括D-Fire、FLAME等但發現兩個問題一是公開數據集大多以美國、歐洲的植被環境為主國內松林、灌木叢、農田秸稈這類場景覆蓋不足二是煙霧樣本拍攝距離普遍較近遠距離大面積煙霧的樣本太少。后面我的做法是在公開數據集基礎上自己補充了大概8000張野外火災圖像主要來源是森林防火監控公開視頻抽幀、無人機巡視視頻、新聞現場畫面截取。標注用的工具是LabelImg和X-anyLabeling類別只保留兩類fire和smoke不細分火焰類型因為模型先判斷有沒有火具體火勢分析交給大模型去處理。標注時候有幾個細節必須注意第一絕大多數煙霧邊界是漸變的標注框寧大勿小框住煙霧核心可視區域就行框太小會讓模型學會“只看濃煙核心區域”對淡煙漏檢嚴重第二夜間火焰和白天火焰的表現完全不同日照強烈時火焰區域接近白色和云層反光的區分度很低所以夜間樣本要單獨挑出來增強第三遠距離小目標的煙霧可能只占十幾個像素這種樣本也要保留并單獨統計召回率。處理完標注后我用腳本把數據集按7:2:1切分為訓練集、驗證集、測試集注意是按視頻來源切分而不是按幀隨機切分。這樣做是為了避免同一段視頻的相鄰幀同時出現在訓練集和驗證集里導致驗證分數虛高。2.2 YOLO各版本網絡結構演進與火災檢測適配性YOLOv8是2023年發布的版本核心結構是C2f模塊加Anchor-Free檢測頭。C2f模塊借鑒了CSPNet的思想把梯度流拆分成多條分支在保持輕量化的同時提升了特征復用能力。在火災場景下C2f的好處是淺層特征能保留更多煙霧紋理細節對大范圍煙霧檢測比較有利。YOLOv10在v8基礎上做了NMS-free設計推理時不再需要非極大值抑制直接通過雙標簽分配和一致匹配輸出結果。這個改動的主要價值在推理延遲降低在單張GPU測試大概能減少2-4毫秒的預處理時間。但實際檢測精度提升不大更適合對延遲敏感的邊緣設備部署。YOLOv11在C3k2模塊和C2PSA注意力機制上做了改進對小目標的檢測能力有明顯增強。我測試下來在煙霧遠距離小目標這個子集上YOLOv11的召回率比v8提高了約4個百分點。代價是模型參數量增加在GTX 1660 Ti這種顯卡上訓練時間會拉長30%左右。YOLOv12引入了區域注意力機制改變了傳統注意力機制對全局特征依賴過重的局面。它把注意力計算限制在局部區域理論計算量呈線性增長對高分辨率輸入更友好。在處理4K森林監控畫面時YOLOv12的推理速度比同規模注意力模型更快。YOLOv26是目前系列里最新的迭代主要圍繞多尺度特征融合和動態卷積做升級。它引入了尺度感知的特征選擇機制對大范圍煙霧擴散和小火點同時出現這類極端尺度差異場景效果更好。但YOLOv26的權重文件目前對部署環境要求較高在部分舊版TensorRT上需要手動適配。2.3 訓練配置與超參數選擇以YOLOv8為例的完整說明我選YOLOv8作為基準模型因為它在精度和速度上最均衡。先用一半數據做快速驗證迭代100輪確認loss能正常收斂后再全量數據訓練300輪。圖像分辨率統一用640×640batch size根據顯卡顯存動態調整。這里給出一份實驗環境下的訓練配置參考GPUNVIDIA RTX 3090 24GB或者多卡RTX 2080 Ti訓練框架ultralytics YOLO官方庫輸入尺寸640×640后續對比過1280分辨率mAP50提升約1.8%但推理耗時翻倍優化器SGD初始學習率0.01權重衰減0.0005動量0.937學習率調度cosine annealing最終學習率0.001數據增強Mosaic 1.0MixUp 0.2隨機透視0.5HSV增強0.5訓練輪數300 epochs前3輪用warmup損失函數CIoU BCE訓練命令很直接ultralytics封裝得比較完善yolo detect train datafire_smoke.yaml modelyolov8m.pt epochs300 imgsz640 batch16 device0,1 patience50 projectruns/fire_smoke nameexp_v8m這里有個很重要的參數patience50意思是連續50輪驗證集指標沒有改善就提前終止。野外火災數據相對單一一般訓練到150-180輪就會收斂繼續硬train容易過擬合。2.4 模型評估指標設計不能只看mAP目標檢測領域的常規指標是mAP0.5和mAP0.5:0.95但火災檢測系統只看這兩個還不夠。我對模型做評估時額外增加了四個指標漏檢率真實火焰或煙霧框中完全沒被檢測出的比例這項指標在火災場景比誤報重要得多。首幀檢出時間針對視頻流從火焰出現到第一次產生正確告警的幀數差這決定了系統響應速度。小目標召回率目標尺寸小于32×32像素的檢測框召回情況對應遠距離火點。誤報率FPS每小時每路攝像頭產生的平均誤報次數直接影響值班人員對系統的信任度。為什么小目標召回率單獨拿出來因為野外火災早期往往就是一個幾像素的小點如果模型只對大目標敏感等煙霧大面積擴散時才告警已經錯過了最佳撲救時機。我測試過在640×640輸入下YOLOv8s對32×32以下小目標的召回率只有61%換成YOLOv11m后提升到72%但這種提升在普通mAP指標上體現不大。3. 實操過程與核心環節實現3.1 Flask推理服務的完整實現Flask推理服務是整個系統的算法出口。我把它設計成無狀態的HTTP服務只保留模型實例和預處理結果緩存每個請求直接通過POST上傳圖像或視頻幀返回檢測結果。服務端代碼核心部分from flask import Flask, request, jsonify from ultralytics import YOLO import base64 import numpy as np import cv2 import os app Flask(__name__) MODEL_DIR os.getenv(MODEL_DIR, ./weights) MODEL_MAP { v8: os.path.join(MODEL_DIR, fire_v8m.pt), v10: os.path.join(MODEL_DIR, fire_v10m.pt), v11: os.path.join(MODEL_DIR, fire_v11m.pt), v12: os.path.join(MODEL_DIR, fire_v12m.pt), v26: os.path.join(MODEL_DIR, fire_v26m.pt), } models_cache {} def get_model(model_type): if model_type not in models_cache: models_cache[model_type] YOLO(MODEL_MAP[model_type]) return models_cache[model_type] detect_args { conf: 0.35, iou: 0.5, imgsz: 640, classes: None, device: 0, verbose: False, } app.route(/detect, methods[POST]) def detect(): data request.get_json() image_bytes base64.b64decode(data.get(image_base64, )) model_type data.get(model_type, v8) detect_args[conf] float(data.get(conf, 0.35)) np_arr np.frombuffer(image_bytes, np.uint8) img cv2.imdecode(np_arr, cv2.IMREAD_COLOR) model get_model(model_type) results model.predict(img, **detect_args)[0] boxes results.boxes output_boxes [] for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) cls int(box.cls[0]) output_boxes.append({ x1: round(x1, 2), y1: round(y1, 2), x2: round(x2, 2), y2: round(y2, 2), confidence: round(conf, 4), class: model.names[cls], }) return jsonify({code: 0, boxes: output_boxes, count: len(output_boxes)}) app.route(/health, methods[GET]) def health(): return jsonify({code: 0, msg: ok}) if __name__ __main__: app.run(host0.0.0.0, port6006, threadedTrue)這里緩存模型實例非常重要。如果不加緩存每次請求都重新加載一次幾百MB的權重文件推理服務根本扛不住并發。實際生產里我會再加一個模型熱更新接口通過修改版本號觸發重新加載不重啟服務。Flask服務部署在獨立GPU機器上通過Nginx反向代理暴露7001端口只允許內網訪問。Spring Boot側用RestTemplate調用超時設置為2秒避免算法服務卡頓拖慢業務主流程。3.2 Spring Boot四層架構與核心接口實現Spring Boot側嚴格遵循Controller、Service、Mapper、Entity四層架構。工程目錄結構如下src/main/java ├── controller │ ├── AuthController.java │ ├── DetectTaskController.java │ ├── AlarmController.java │ ├── ModelVersionController.java │ └── DashboardController.java ├── service │ ├── AuthService.java │ ├── DetectTaskService.java │ ├── AlarmService.java │ ├── ModelVersionService.java │ └── ReportService.java ├── mapper │ ├── UserMapper.java │ ├── DetectRecordMapper.java │ ├── AlarmRecordMapper.java │ └── CameraMapper.java └── entity ├── User.java ├── DetectRecord.java ├── AlarmRecord.java └── CameraInfo.java以火情告警接口為例核心流程是接收前端上報的檢測結果 → 去重判斷同一攝像頭30秒內重復告警不觸發→ 寫入告警記錄 → 通過Redis發布告警事件 → 調用大模型生成初步研判報告 → 通過WebSocket推送給大屏和值班端。關鍵代碼Service public class AlarmService { Autowired private AlarmRecordMapper alarmRecordMapper; Autowired private RedisTemplateString, String redisTemplate; Autowired private LLMReportService llmReportService; Value(${alarm.duplicate.interval:30}) private long duplicateInterval; public AlarmRecord handleDetectResult(DetectRecord detectResult) { String redisKey alarm:dup: detectResult.getCameraId(); Boolean firstTime redisTemplate.opsForValue() .setIfAbsent(redisKey, 1, duplicateInterval, TimeUnit.SECONDS); if (Boolean.FALSE.equals(firstTime)) { detectResult.setAlarmStatus(2); return null; } AlarmRecord alarm new AlarmRecord(); alarm.setCameraId(detectResult.getCameraId()); alarm.setFireType(detectResult.getTopClass()); alarm.setConfidence(detectResult.getTopConfidence()); alarm.setSnapshotUrl(detectResult.getSnapshotUrl()); alarm.setAlarmTime(new Date()); alarm.setAlarmStatus(0); // 0-待處理 alarmRecordMapper.insert(alarm); String report llmReportService.generateFireReport(alarm); alarm.setLlmReport(report); alarmRecordMapper.updateReport(alarm.getId(), report); redisTemplate.convertAndSend(topic:alarm, JSON.toJSONString(alarm)); return alarm; } }用Redis做去重鍵這個設計在真實場景里非常關鍵。攝像頭在檢測到火焰后會持續產生告警如果不去重同一個火情一分鐘能刷出上百條告警值班人員很快就麻木了系統的可信度也會大幅下降。Spring Boot的配置中心和環境隔離也用起來了application-dev.yml、application-prod.yml分別管理數據庫連接、Redis地址、算法服務地址和大模型API Key避免測試環境把生產數據寫壞了。3.3 Vue前端屏幕顯示與M3U8視頻流播放前端用的是Vue 3 Vite Element Plus這套技術棧。實時視頻展示是核心難點因為監控攝像頭的視頻流格式通常是RTSP而瀏覽器不能直接播放RTSP需要先通過媒體服務器轉成HLS或WebRTC格式。我在服務器上用Nginx加FFmpeg做流轉封裝。直接把RTSP流轉成HLS通過M3U8協議在網頁端播放。前端播放M3U8視頻流我用了vue-video-player這個組件底層封裝的是video.js和videojs-contrib-hls插件。template div classvideo-player-wrap video-player classvideo-player refvideoPlayer :optionsplayerOptions readyonPlayerReady timeupdateonTimeUpdate / /div /template script setup import { ref, watch } from vue import VideoPlayer from videojs-player/vue import video.js/dist/video-js.css const props defineProps({ streamUrl: { type: String, required: true }, cameraId: { type: String, required: true } }) const playerOptions ref({ autoplay: true, muted: true, controls: true, fluid: true, sources: [{ src: props.streamUrl, type: application/x-mpegURL }] }) const player ref(null) const onPlayerReady (playerInstance) { player.value playerInstance } watch(() props.streamUrl, (newUrl) { if (player.value newUrl) { player.value.src({ src: newUrl, type: application/x-mpegURL }) player.value.play() } }) /script播放時要注意兩個坑第一muted必須設為true因為瀏覽器自動播放策略不允許帶聲音的視頻直接自動播放除非用戶主動點擊第二HLS的延遲一般在3到10秒如果對實時性要求高需要換成WebRTC方案代價是部署復雜度更高。前端界面上還集成了ECharts統計圖表、百度地圖/高德地圖GIS點位圖層AlarmRecord列表支持按時間、攝像頭、置信度篩選檢測記錄可以做時間軸回放。3.4 DeepSeek與千問大模型的接入火情研判的生產級實現大模型在這個系統里不是可有可無的噱頭而是承擔了非常具體的生產任務火情研判報告生成。原來值班人員看告警后需要人工判斷火情等級、可能的蔓延方向、需要調度的資源這些信息比較依賴經驗而且容易漏項。現在我把檢測結果、火點位置、周邊地形、氣象數據拼裝成Prompt請求大模型生成結構化報告把人的工作從判斷轉變成復核。DeepSeek和千問這兩種模型我都實現了接入層使用統一的接口封裝通過配置切換。核心調用代碼import os from openai import OpenAI client OpenAI( api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL) # 切換DeepSeek/Qwen的endpoint ) def generate_fire_report(detect_info, camera_info): prompt f 你是森林防火應急研判專家。根據以下信息生成一份火情研判報告 檢測類型{detect_info[fire_type]} 置信度{detect_info[confidence]} 檢測時間{detect_info[detect_time]} 攝像頭位置{camera_info[location]} 周邊地形{camera_info[terrain]} 當前風向風速{camera_info[wind]} 請輸出JSON格式包含 1. risk_level: 低/中/高/極高 2. possible_spread: 可能的蔓延方向分析 3. suggestion: 處置建議 4. dispatch_resources: 建議調度的資源清單 response client.chat.completions.create( modelos.getenv(LLM_MODEL), messages[{role: user, content: prompt}], response_format{type: json_object}, temperature0.3, max_tokens1024 ) return response.choices[0].message.content這里temperature設成0.3很關鍵火情研判是嚴謹場景不希望模型自由發揮太多低溫度能讓輸出更穩定。同時通過response_format強制JSON結構輸出方便后端直接解析存入數據庫。大模型接入的穩定性問題需要重視。實際使用中大模型API偶爾會超時或者返回格式錯誤所以我在接入層做了三者兜底超時重試一次、JSON解析失敗時降級為純文本存儲、完全不可用時回退到規則模板生成的簡要報告。這樣即使大模型故障也不會影響告警主流程。3.5 模型訓練效果對比五版本橫向評測實錄訓練完成后統一在相同測試集上評測。測試集包含3000張圖像其中2000張包含火焰/煙霧目標1000張為復雜背景負樣本。硬件統一為一張RTX 3090。評測結果如下模型版本mAP0.5mAP0.5:0.95小目標召回率推理耗時(ms/幀)模型大小(MB)YOLOv8m87.8%62.4%61.3%9.849.7YOLOv10m87.2%61.8%60.7%8.551.2YOLOv11m89.1%64.5%66.8%10.654.3YOLOv12m88.3%63.2%63.5%9.255.8YOLOv26m90.2%65.7%70.1%11.458.6從數據看有幾點很直接YOLOv11和YOLOv26在小目標召回率上確實有提升特別是YOLOv26比v8高了接近9個百分點但推理耗時也相應增加。在邊緣設備上比如Jetson Orin Nano跑YOLOv8m可以達到25fps而YOLOv26m只有12fps左右這時候就要考慮用v8n或v8s的輕量模型替換。另外我還在同一測試集上對比了增強策略的影響。加入Mosaic和MixUp增強后各模型平均mAP0.5提升了2-3個百分點。數據增強對小目標的提升尤其明顯因為Mosaic把多張圖片拼接后再隨機裁剪讓模型看到了更多不同尺度的目標分布。3.6 模型部署與TensorRT加速訓練好的PyTorch模型直接部署在GPU上其實沒完全榨干硬件性能我試過把模型導出成TensorRT的engine格式推理速度大概能再快1.5到2倍。以YOLOv8m為例導出命令yolo export modelruns/fire_smoke/exp_v8m/weights/best.pt formatengine device0 halfTrue dynamicFalse imgsz640導出后需要在推理代碼中指定加載engine文件model YOLO(runs/fire_smoke/exp_v8m/weights/best.engine)注意TensorRT的engine文件綁定GPU型號和驅動版本在A卡或老驅動上直接搬運容易報錯。另外啟用了halfTrue后GPU必須支持FP16運算我用GTX 1660 Ti測試時會掉精度建議只在RTX 20系以上顯卡上開。4. 常見問題與排查技巧實錄4.1 M3U8視頻流播放失敗的排查路徑播放M3U8時最常見的現象是黑屏或者一直轉圈。排查路徑通常是先用VLC確認源流的原始地址能否正常播放。如果VLC都打不開問題大概率在源流或轉碼參數。確認Nginx是否配置了正確的MIME type。application/vnd.apple.mpegurl必須配置否則video.js不認識文件類型。查看瀏覽器控制臺網絡請求M3U8文件如果返回404檢查FFmpeg轉出的文件路徑是否在Nginx的alias或root目錄下。跨域問題。如果前端頁面和視頻服務器域名不一致Nginx需要開CORS。一個我踩過的坑HLS切片默認長度是3秒但如果攝像頭網絡波動FFmpeg拉流中斷后會產生損壞的TS切片播放器會一直卡在白屏。解決方法是加一個自動重連機制另外給Nginx加proxy_next_upstream策略提升容錯能力。4.2 訓練時loss不收斂的常見原因訓練過程中loss曲線不下降基本逃不出這幾個原因標注類別不平衡、學習率過大、數據集中含有大量空圖、模型預訓練權重和數據集類型差距過大。我在煙霧數據集上遇到過一次比較典型的loss震蕩問題。原因是數據集里純天空背景的圖片太多負樣本數量遠超正樣本模型初期一直在學習“什么是天空”沒有真正關注火焰。后面我把負樣本比例壓到30%以內又通過focal loss調整正負樣本權重loss曲線很快就穩定了。查看訓練日志時重點關注box_loss和cls_loss兩個分量。如果box_loss降得很低但cls_loss始終不降說明模型能定位到目標但無法區分火焰和煙霧這時候需要檢查類別標注是否有歧義。4.3 大模型API接入時的穩定性問題接入DeepSeek和千問API在測試環境一切正常部署到生產后發現兩個典型問題一是并發量上來后被限流二是模型偶爾返回不可解析的內容。限流問題需要做本地緩存和隊列削峰。我的做法是Redis做一個請求隊列每秒最多放行5個請求給大模型超過部分排隊等待。返回不可解析的內容時用正則從文本中抽取關鍵字段同時設置3次解析失敗就回退到模板報告。另一個優化是Prompt模板不斷迭代。最開始生成的報告內容太泛后來在Prompt里加入了具體的攝像頭點位名稱和當地的植被類型生成的報告才真正具備參考價值。4.4 模型對比分析中的“虛假提升”現象做多版本對比時一定要警惕“虛假提升”。最常見的坑是不同版本訓練時使用了不一致的數據增強參數導致指標差異不是來自模型結構而是來自訓練配置。我做對比實驗時嚴格保證以下變量一致同一種數據增強配置、同一種學習率調度器、同一個預訓練權重來源、同一個隨機種子。隨機種子非常重要如果不固定seed即使完全相同的代碼和參數兩次訓練出來的精度差距都可能超過1個百分點這個誤差足以干擾結論判斷。建議在訓練腳本開頭統一設置import random import torch import numpy as np def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)4.5 常見問題速查表問題現象可能原因排查方法檢測框閃爍劇烈conf閾值設置偏低將conf從0.25提高到0.35-0.4夜間漏檢嚴重訓練集中夜間樣本不足對夜間圖像做亮度增強補充夜間負樣本GPU顯存不足batch size過大或輸入尺寸過大降低batch size到8或4或改用640輸入大模型響應超時API高峰期限流增加超時時間做隊列削峰M3U8播放延遲過大HLS切片時長太長將FFmpeg切片時長從3秒改為1秒告警重復刷屏Redis去重鍵失效確認key過期時間設置正確檢查服務器時鐘5. 系統部署與性能優化實錄5.1 服務部署拓撲與資源規劃整套系統我放在兩臺服務器上應用服務器8核16G內存部署Spring Boot、Nginx、MySQL、Redis、Vue前端靜態文件。算法服務器i7 RTX 3090部署Flask推理服務、FFmpeg轉碼服務、大模型接入代理。兩臺服務器通過內網互通算法服務只監聽內網端口。攝像頭接入走獨立視頻專網避免公網流量占用影響視頻傳輸質量。資源規劃上有兩個建議第一MySQL的max_connections至少要調到200因為檢測記錄寫入很頻繁連接數不夠會出現Too many connections第二Flask服務的threadedTrue開啟后單進程可以處理并發請求但要注意模型推理是CPU/GPU密集操作并發太高會出現排隊建議用Gunicorn多worker部署每個worker單獨加載一份模型副本。啟動Gunicorn的參考命令gunicorn -w 2 -b 0.0.0.0:6006 app:app -t 120-t 120是設置worker超時時間推理可能在處理高分辨率圖像時超過默認30秒超時設置120秒更穩妥。5.2 推理性能優化批量推理與緩存策略在實時視頻監控場景下每路攝像頭每秒產生約25幀畫面如果攝像頭數量超過20路逐幀推理的壓力非常大。我的優化策略是每路攝像頭只抽取關鍵幀進行推理默認每秒抽2幀。火焰蔓延是個漸進過程2幀每秒的采樣率足夠覆蓋早期火情變化。另一個技巧是場景變化檢測。如果攝像頭畫面長時間靜止比如夜間林區幾乎無變化可以進一步降低采樣率到每5秒1幀等檢測到運動區域后再恢復高頻采樣。這一步能將算法服務器的負載降低60%以上。圖像預處理也做了緩存。同一個攝像頭連續幀的背景變化很小我把上一幀的檢測框映射到當前幀如果檢測框內像素變化低于閾值直接復用上一幀的檢測結果不重新推理。這樣在穩定畫面下的推理次數能再減少一半。5.3 告警消息推送的可靠性保障告警消息推送面臨的主要問題是“推送丟失”和“重復推送”。我采用的技術方案是Spring Boot通過Redis的Pub/Sub發布告警事件WebSocket服務訂閱后推送給前端。為了保證前端斷線重連后能補齊消息數據庫中先落庫前端重新連接后拉取最近未讀告警。考慮Redis Pub/Sub的“不持久化”特性我增加了一層本地消息隊列。如果Redis服務重啟積壓的消息從MySQL中讀取補償。這種Redis MySQL雙寫的設計在告警不丟失和系統簡單性之間取得了平衡。5.4 Vue前端發布與Nginx配置Vue項目通過npm run build打包后生成dist目錄部署到Nginx的html目錄下。同時配置反向代理將/api路徑代理到Spring Boot服務/llm路徑代理到算法服務。一個關鍵配置是前端路由的history模式刷新404問題需要在Nginx配置try_filesserver { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }5.5 攝像頭接入的協議適配目前系統支持RTSP、RTMP、HLS三種協議接入但不同品牌攝像頭的RTSP地址規則各不相同海康、大華、宇視三家地址格式完全不同。我的做法是做了一個設備接入配置界面讓運維人員直接填寫攝像頭廠家和型號后端自動拼接參考地址并做連通性測試。FFmpeg轉碼服務啟動后可以用以下命令驗證流是否正常播放ffprobe -v error -show_entries formatformat_name -of defaultnoprint_wrappers1:nokey1 http://127.0.0.1:8888/live/stream_001.m3u8如果輸出hls說明轉碼和切片正常。如果長時間無輸出多半是源流斷開了需要檢查攝像頭輸出編碼格式。6. 大模型與檢測系統的深度融合6.1 從“檢測結果”到“處置建議”的自動化鏈路純目標檢測系統的輸出是“圖片中某個位置有火焰置信度多少”但對值班人員來說這句話的價值有限他們需要知道“這個火情嚴不嚴重需要派多少人往哪個方向撲”。大模型的價值在這里體現得很充分。我將檢測結果、攝像頭點位、歷史火情數據、氣象數據拼裝成結構化的上下文通過大模型生成處置建議。實現后形成一個完整的自動化鏈路視頻流 → 目標檢測 → 火情識別 → 大模型分析 → 報告推送 → 處置指令下發。舉個例子系統檢測到某林區山頂出現煙霧置信度0.87大模型結合周邊地形坡向西南、當前風向西北風3級后生成的分析是火勢可能向東南方向蔓延建議優先調度無人機進行空中偵察并安排一支地面隊伍從東北側接近火點。這類結構化建議已經可以很大程度輔助指揮決策。6.2 DeepSeek與千問的選型經驗兩個模型我都做了實際測試。DeepSeek在中文場景下的推理能力更強生成的火情分析更有條理特別是對多因素綜合分析時邏輯更嚴謹。千問在結構化輸出JSON的穩定性上略好響應速度也稍快且長文本生成更穩定。我的建議是如果系統偏向決策輔助用DeepSeek如果偏向快速預警和數據分析用千問。如果你的業務對數據隱私要求高還可以用Ollama部署本地化千問模型避免數據出域。6.3 大模型幻覺問題的處理大模型在生成火情報告時偶爾會出現“幻覺”比如把天氣數據中的風速單位搞混或者把火點坐標描述成不存在的街道名。這類錯誤在應急場景中是致命的。我的處理辦法是第一在Prompt中加入嚴格的格式限制和單位說明要求所有風速字段必須保留原始單位第二在輸出端增加規則校驗對關鍵字段做范圍和格式檢查不合規則丟棄重試第三最終的處置建議必須由值班人員確認后才下發給撲救隊伍大模型只提供參考選項。7. 項目經驗總結與后續擴展方向7.1 我踩過的幾個關鍵坑第一模型訓練階段不要盲目上最重的模型。YOLOv8x參數量大訓練慢但在火災場景下的精度提升相比m版本微乎其微部署時推理速度又跟不上。對絕大多數場景m和l版本是性價比最優的選擇。第二Spring Boot調用Flask服務時一定要做超時熔斷。算法服務偶發卡頓很常見如果不熔斷上游接口會一直阻塞積累的請求最終拖垮整個業務服務。我用的是Hystrix后來遷移到Resilience4j設定了超時500ms失敗率超過閾值直接降級返回空結果。第三數據標注的一致性直接影響模型上限。多人協作標注時不同人對“煙霧邊界”的理解不一致導致同樣的目標在不同圖片上的標注框差異很大。我用Label Studio的預標注功能先讓一個強模型初標人工修正大大提升了標注一致性。7.2 這個項目后續還能怎么擴展目前這套系統已經穩定運行后續有幾個我準備繼續做的方向一是引入視頻時序信息。現在的檢測是逐幀獨立的如果利用相鄰幀的時序關聯可以對“靜止煙霧”和“移動人員”做更好的區分進一步降低誤報。二是煙霧濃度評估。在大模型分析層面把煙霧透明度、擴散面積等量化指標納入判斷對火情等級做更精細的劃分。三是多級聯動調度。當檢測到火情后自動生成撲救資源調度預案聯動附近的消防水車、無人機和人工巡護隊伍的GPS定位形成一張實時作戰圖。這套基于YOLO多版本對比 Spring Boot Vue Flask 大模型的系統完整覆蓋了從算法訓練、模型評測、服務部署到業務集成的大鏈路。以后有新的YOLO版本發布時我的建議是不要盲目追求新版本先在你自己的數據集上跑一輪對比實驗用數據說話比看任何宣傳都有說服力。