設(shè)計(jì)與大模型智能分析)
做密集行人檢測最讓人頭疼的時(shí)刻不是模型精度不夠而是模型明明能檢測出目標(biāo)一到真實(shí)場景商場扶梯口、地鐵站臺、景區(qū)檢票口就各種翻車——人擠人的時(shí)候互相遮擋遠(yuǎn)處的人小到只有十幾個(gè)像素近處的人又大到超出邊界框傳統(tǒng)的NMS后處理在人群密集區(qū)域還會把本應(yīng)保留的檢測框誤刪。這個(gè)項(xiàng)目就是圍繞這個(gè)問題展開的用YOLOv8、YOLOv10、YOLOv11、YOLOv12四個(gè)版本的模型作為檢測引擎SpringBoot作為后端服務(wù)框架再配合千問和DeepSeek兩個(gè)大模型做場景級智能分析最后用前后端分離的Web界面把整個(gè)流程串起來形成一個(gè)從圖片/視頻上傳、目標(biāo)檢測、智能分析到結(jié)果可視化的完整閉環(huán)。這套系統(tǒng)做出來之后既能作為獨(dú)立的行人檢測服務(wù)供其他業(yè)務(wù)調(diào)用也能直接在瀏覽器里看檢測效果和AI分析結(jié)論。無論你是算法工程師想了解YOLO系列在密集場景下的選型和部署還是Java后端想學(xué)習(xí)SpringBoot怎么集成檢測和大模型能力或者是在做畢設(shè)、搞工業(yè)級Demo這篇內(nèi)容都值得花幾分鐘看完。我會把架構(gòu)設(shè)計(jì)、模型選型對比、關(guān)鍵代碼實(shí)現(xiàn)、踩過的坑全部攤開來講。1. 密集行人檢測為什么難——項(xiàng)目定位與核心問題1.1 密集場景的三座大山遮擋、小目標(biāo)、尺度不均行人檢測在公開數(shù)據(jù)集上刷點(diǎn)已經(jīng)不難難的是在真實(shí)密集場景里穩(wěn)定工作。我總結(jié)下來密集人群場景主要卡在三個(gè)問題上。第一是遮擋。人群一密集人與人之間的IoU極高目標(biāo)之間互相覆蓋檢測器只能看到人的上半身甚至只有頭部。這時(shí)候如果模型本身沒有充足的上下文感知能力很容易把兩個(gè)人當(dāng)成一個(gè)人或者干脆漏檢。第二是小目標(biāo)。監(jiān)控?cái)z像頭的角度決定了遠(yuǎn)處的人很小在1080P畫面里可能只有15×30像素。常規(guī)檢測頭在小目標(biāo)上特征響應(yīng)弱加上下采樣倍數(shù)高小目標(biāo)的特征圖空間分辨率嚴(yán)重不足。第三是尺度分布極度不均。同一個(gè)畫面里近處的人占據(jù)大半個(gè)邊界框遠(yuǎn)處的人只有幾個(gè)像素模型需要同時(shí)兼顧兩種極端尺度。大多數(shù)單尺度訓(xùn)練出來的模型在均勻尺度的常規(guī)數(shù)據(jù)集上表現(xiàn)良好但到了這種場景就露餡。1.2 傳統(tǒng)檢測方案在密集場景下的失效模式很多團(tuán)隊(duì)直接拿通用目標(biāo)檢測模型套到密集人群場景結(jié)果在評估時(shí)發(fā)現(xiàn)mAP看著還行實(shí)際落地卻問題不斷。典型的表現(xiàn)有三類NMS誤殺兩個(gè)高度重疊的真實(shí)行人被NMS當(dāng)成一個(gè)目標(biāo)處理置信度較低的框被直接抑制。這是密集場景最典型的失效模式。定位漂移遮擋導(dǎo)致檢測框的回歸不穩(wěn)定框的位置在頭和身體之間搖擺實(shí)際畫出來沒法用。召回率虛高但精確率低模型把所有疑似行人的區(qū)域全框出來結(jié)果畫面里一半的框都是誤檢。這類問題在人群密度不均勻時(shí)特別明顯。1.3 為什么這套系統(tǒng)要YOLO SpringBoot 大模型組合檢測模型解決的是人在哪里的問題但客戶和業(yè)務(wù)方真正關(guān)心的往往是人群現(xiàn)在是什么狀態(tài)有沒有安全隱患需不需要預(yù)警。這一層語義分析能力傳統(tǒng)目標(biāo)檢測給不了。所以我把系統(tǒng)拆成三層YOLO負(fù)責(zé)感知層快速輸出檢測框、類別和置信度SpringBoot服務(wù)層負(fù)責(zé)串聯(lián)所有能力包括文件上傳、推理調(diào)度、結(jié)果持久化千問和DeepSeek負(fù)責(zé)語義分析層把檢測結(jié)果轉(zhuǎn)化為對人類友好的結(jié)構(gòu)化結(jié)論。三層之間用HTTP和WebSocket通信前后端徹底分離前端只負(fù)責(zé)展示和交互所有計(jì)算都收斂到后端服務(wù)。2. 多版本YOLO選型v8、v10、v11、v12到底怎么選2.1 四代YOLO的核心差異與技術(shù)演進(jìn)先說結(jié)論YOLO系列遠(yuǎn)不止是版本號遞增每一代的架構(gòu)改動都會直接影響密集場景下的表現(xiàn)。我結(jié)合源碼和實(shí)測把這四個(gè)版本的核心差異做了個(gè)對比。版本發(fā)布方核心亮點(diǎn)對密集行人場景的影響YOLOv8UltralyticsC2f模塊、Anchor-Free、集成分類/檢測/分割/姿態(tài)生態(tài)最成熟資料最多穩(wěn)定首選YOLOv10清華去除NMS的端到端檢測、雙標(biāo)簽分配大幅緩解NMS在密集場景的誤殺問題YOLOv11UltralyticsC3k2模塊、改進(jìn)的C2PSA注意力、更強(qiáng)特征提取精度和速度均有提升小目標(biāo)表現(xiàn)更好YOLOv12社區(qū)/學(xué)術(shù)界注意力中心架構(gòu)、Area Attention全局建模更強(qiáng)遮擋場景下的上下文利用更好YOLOv10的端到端特性值得多說一句。它通過One-to-One匹配策略替代了傳統(tǒng)NMS相當(dāng)于從機(jī)制上繞過了重疊框被抑制的死結(jié)。我在CrowdHuman密集子集上測試YOLOv10在人群高度重疊區(qū)域的召回率比v8高了約4-6個(gè)百分點(diǎn)這個(gè)差距在真實(shí)監(jiān)控畫面里就是少漏檢好幾個(gè)人的差距。YOLOv12引入的注意力機(jī)制則更擅長捕捉行人之間的關(guān)系當(dāng)一個(gè)人被另一個(gè)人遮擋50%以上的時(shí)候v8和v10基本只能靠猜測v12卻可以利用周圍行人的上下文信息輔助判斷。當(dāng)然注意力機(jī)制也帶來了更高的計(jì)算開銷實(shí)際部署時(shí)要在速度和準(zhǔn)確率之間做權(quán)衡。2.2 密集行人場景下的實(shí)測對比我基于同一個(gè)數(shù)據(jù)集約1.2萬張標(biāo)注圖像混合了監(jiān)控視角和手持設(shè)備視角分別訓(xùn)練了四個(gè)版本的YOLO模型輸入分辨率統(tǒng)一設(shè)置為640×640硬件環(huán)境是單張RTX 3090。版本模型體積推理耗時(shí)(GPU)mAP0.5人群密集子集Recall小目標(biāo)APYOLOv8s22.5MB6.2ms0.8310.7420.386YOLOv10s24.1MB5.8ms0.8450.7940.412YOLOv11s26.8MB5.5ms0.8620.8030.434YOLOv12s32.3MB7.9ms0.8710.8110.455只看這個(gè)表YOLOv12好像全面勝出但實(shí)際部署要考慮硬件差異。我的目標(biāo)運(yùn)行環(huán)境有一部分是CPU服務(wù)器v8和v10在CPU上的推理速度能跑到300ms左右v12直接飆到600ms以上。所以我的策略是GPU環(huán)境用v12CPU環(huán)境切回v8或者v10后端寫了一個(gè)模型路由邏輯根據(jù)當(dāng)前運(yùn)行環(huán)境自動選擇最優(yōu)模型。2.3 我的選型結(jié)論與切換策略沒有絕對的最優(yōu)模型只有最適合當(dāng)前場景的模型。我最終的落地配置是雙模型策略提示生產(chǎn)環(huán)境別只部署一個(gè)模型。我的做法是同一套接口背后維護(hù)兩個(gè)模型文件一個(gè)極致追求精度的YOLOv12用于GPU推理服務(wù)器一個(gè)均衡型的YOLOv8s用于CPU兜底。后端感知到GPU資源緊張或推理超時(shí)時(shí)自動降級。這個(gè)策略在流量高峰時(shí)特別有用。GPU的推理隊(duì)列打滿之后新的檢測請求自動路由到CPU上的輕量模型雖然精度略有下降但至少保證服務(wù)不掛、響應(yīng)不超時(shí)。Java后端通過一個(gè)簡單的權(quán)重配置和模型ID參數(shù)就能實(shí)現(xiàn)動態(tài)切換不需要重啟服務(wù)。# 模型加載層抽象后端通過模型ID指定需要加載的版本 from ultralytics import YOLO MODEL_REGISTRY { v8: weights/yolov8s_crowd.pt, v10: weights/yolov10s_crowd.pt, v11: weights/yolov11s_crowd.pt, v12: weights/yolov12s_crowd.pt, } def load_any_model(version: str) - YOLO: if version not in MODEL_REGISTRY: raise ValueError(fUnsupported YOLO version: {version}) return YOLO(MODEL_REGISTRY[version])3. 基于SpringBoot的后端服務(wù)體系架構(gòu)設(shè)計(jì)3.1 前后端分離架構(gòu)下的后端模塊拆解SpringBoot在這個(gè)項(xiàng)目里不是簡單起個(gè)HTTP接口就完事。整套后端按照職責(zé)拆成了六個(gè)模塊每個(gè)模塊之間通過接口通信互不干擾gateway-controller統(tǒng)一接收前端請求負(fù)責(zé)參數(shù)校驗(yàn)、鑒權(quán)和路由分發(fā)。detection-service管理YOLO推理服務(wù)封裝了HTTP調(diào)用Python推理服務(wù)的邏輯。analysis-service對接千問和DeepSeek大模型API負(fù)責(zé)提示詞拼接、接口調(diào)用、響應(yīng)解析。>RestController RequestMapping(/api/detection) public class DetectionController { private final DetectionService detectionService; private final AnalysisService analysisService; PostMapping(/image) public ApiResultDetectionResponse detectImage(RequestParam(file) MultipartFile file, RequestParam(value modelVersion, defaultValue v11) String modelVersion, RequestParam(value enableAnalysis, defaultValue false) boolean enableAnalysis) { // 1. 文件校驗(yàn)與存儲 String fileUrl fileService.storeFile(file); // 2. 調(diào)用YOLO推理服務(wù)獲取檢測框 DetectionRawResult rawResult detectionService.detectImage(fileUrl, modelVersion); // 3. 可選調(diào)用大模型進(jìn)行智能分析 if (enableAnalysis) { AnalysisResult analysis analysisService.analyzeDetection(rawResult, fileUrl); return ApiResult.success(DetectionResponse.of(rawResult, analysis)); } return ApiResult.success(DetectionResponse.of(rawResult, null)); } }這里有一個(gè)值得注意的細(xì)節(jié)大模型分析我沒法和檢測做成同步的因?yàn)镈eepSeek和千問的API響應(yīng)時(shí)間波動很大從幾百毫秒到十幾秒都有可能。同步調(diào)用會讓前端一直等待體驗(yàn)很差。所以我把檢測和分析拆成兩個(gè)階段檢測走同步接口分析走異步任務(wù)WebSocket推送。數(shù)據(jù)流是這樣的前端上傳圖片 → SpringBoot存儲文件 → 調(diào)用Python YOLO服務(wù)檢測 → 檢測結(jié)果寫入數(shù)據(jù)庫 → 如果開啟了智能分析則把檢測框數(shù)據(jù)拼接成結(jié)構(gòu)化文本發(fā)送給大模型 → 拿到分析結(jié)果后通過WebSocket推送給前端。這個(gè)流程下用戶先看到檢測框幾秒后再看到AI分析文字體驗(yàn)很自然。3.3 與Python推理服務(wù)的通信方案選擇YOLO模型用Python訓(xùn)練和推理效率最高但SpringBoot的主體是Java。兩者通信我對比過三種方案方案優(yōu)點(diǎn)缺點(diǎn)我的結(jié)論HTTP REST調(diào)用實(shí)現(xiàn)簡單、調(diào)試方便、語言無關(guān)序列化開銷、單次請求延遲略高最推薦適合大部分場景gRPC性能高、支持流式傳輸需要生成stub、調(diào)試相對麻煩高并發(fā)專用場景推薦Java直接加載ONNX模型省掉中間網(wǎng)絡(luò)開銷部署復(fù)雜、算子支持不全不推薦維護(hù)成本高我最終選了HTTP REST方案。Python端用FastAPI封裝YOLO推理接口SpringBoot通過RestTemplate做調(diào)用。實(shí)測下來單次檢測請求從Java到Python再返回網(wǎng)絡(luò)和序列化開銷大約15-20ms對檢測這個(gè)場景完全可接受。FastAPI的異步特性也能輕松扛住并發(fā)請求。# Python端FastAPI推理服務(wù) from fastapi import FastAPI, UploadFile import numpy as np from ultralytics import YOLO app FastAPI() model_pool { v8: YOLO(weights/yolov8s_crowd.pt), v10: YOLO(weights/yolov10s_crowd.pt), v11: YOLO(weights/yolov11s_crowd.pt), v12: YOLO(weights/yolov12s_crowd.pt), } app.post(/detect) async def detect(file: UploadFile, model_version: str v11, conf_thres: float 0.25): img_bytes await file.read() results model_pool[model_version].predict( sourceimg_bytes, confconf_thres, verboseFalse ) boxes results[0].boxes return { boxes: boxes.xyxy.tolist(), confidences: boxes.conf.tolist(), class_ids: boxes.cls.tolist() }4. 千問與DeepSeek雙模型智能分析模塊的實(shí)現(xiàn)思路4.1 大模型在檢測系統(tǒng)中到底扮演什么角色很多朋友問我YOLO已經(jīng)把框畫出來了為什么還要接大模型這個(gè)問題問到了點(diǎn)子上。檢測框本身是數(shù)值信息業(yè)務(wù)方看不懂也不關(guān)心。他們需要的是當(dāng)前畫面里大約多少人人群密度是否超標(biāo)有沒有出現(xiàn)異常行為聚集、奔跑、滯留這類結(jié)論。大模型在系統(tǒng)里的角色就是只會畫框的YOLO和需要語義理解的人類之間的翻譯官。我把YOLO輸出的結(jié)構(gòu)化數(shù)據(jù)檢測框坐標(biāo)、數(shù)量、置信度轉(zhuǎn)成一段有語義的文本描述讓大模型基于這些描述結(jié)合場景上下文輸出分析結(jié)論。4.2 檢測結(jié)果的結(jié)構(gòu)化與提示詞設(shè)計(jì)大模型理解力的上限很大程度取決于你喂給它的提示詞。我踩過不少坑之后總結(jié)出一套比較穩(wěn)定的提示詞模板核心思想是把檢測數(shù)據(jù)轉(zhuǎn)換為人可讀的文本再送入模型而不是直接貼JSON。你是負(fù)責(zé)商場監(jiān)控的安防分析助手。 以下是YOLO目標(biāo)檢測系統(tǒng)剛剛輸出的檢測數(shù)據(jù) - 檢測時(shí)間2025-06-18 14:32:07 - 共檢測到行人47人 - 畫面尺寸1920x1080 - 行人在畫面中的分布位置(這里按區(qū)域說明例如入口扶梯區(qū)域12人中庭區(qū)域25人收銀臺區(qū)域10人) - 檢測框重疊情況入口扶梯區(qū)域超過60%的檢測框存在高度重疊 請根據(jù)以上數(shù)據(jù)從以下幾個(gè)維度進(jìn)行分析 1. 當(dāng)前人群整體密度是否正常 2. 哪些區(qū)域存在擁擠或安全隱患 3. 是否建議啟動限流或疏導(dǎo)措施 4. 如果要給現(xiàn)場安保人員一條簡潔提醒你會說什么這個(gè)提示詞把大模型從看懂檢測框的重?fù)?dān)中解放出來它只需要專注分析這件事。實(shí)際測試下來千問和DeepSeek對這種結(jié)構(gòu)化文本的響應(yīng)質(zhì)量遠(yuǎn)好于直接扔一堆JSON數(shù)組。4.3 雙模型聯(lián)動的容錯(cuò)與增強(qiáng)策略同時(shí)接千問和DeepSeek不是噱頭而是出于兩個(gè)實(shí)際考慮容錯(cuò)和視角互補(bǔ)。調(diào)用大模型API最煩的事情就是服務(wù)不穩(wěn)定。千問偶爾會超時(shí)DeepSeek偶爾會返回格式錯(cuò)誤。我的策略是配置了主備切換默認(rèn)走DeepSeek的deepseek-chat模型如果超時(shí)或返回異常自動切換到千問的qwen-plus重試一次。這個(gè)邏輯在Java里實(shí)現(xiàn)很簡單用一個(gè)枚舉標(biāo)識模型供應(yīng)商再用一個(gè)Router類做切換。// 雙模型路由核心邏輯 public AnalysisResult analyzeWithFailover(String prompt) { // 優(yōu)先嘗試DeepSeek for (SupplierAnalysisResult strategy : List.of(modelStrategies)) { try { return strategy.get(); } catch (RemoteApiException e) { log.warn(model call failed, switching to backup. cause: {}, e.getMessage()); } } throw new BizException(all model services unavailable); }更深一層我讓兩個(gè)模型做交叉驗(yàn)證。DeepSeek先給出分析結(jié)論千問再針對同一份檢測數(shù)據(jù)給出它的判斷然后系統(tǒng)對兩個(gè)結(jié)論做簡單的一致性校驗(yàn)。如果兩個(gè)模型對人群密度是否超標(biāo)的結(jié)論一致直接采信如果不一致則默認(rèn)取更保守的那個(gè)比如建議限流并標(biāo)記為雙模型分歧請注意人工復(fù)核。這個(gè)機(jī)制在安防場景里真的有用能把單模型的極端誤判風(fēng)險(xiǎn)降一半以上。5. 前后端分離的Web交互界面與聯(lián)調(diào)細(xì)節(jié)5.1 頁面設(shè)計(jì)與核心交互流程前端我選了Vue 3 Element Plus組合Vite作為構(gòu)建工具。這里不討論框架優(yōu)劣純粹是生態(tài)成熟、團(tuán)隊(duì)上手快、社區(qū)資料多。整體頁面分為四個(gè)核心區(qū)域左側(cè)工具欄上傳圖片、選擇YOLO版本、開關(guān)智能分析、設(shè)置置信度閾值。中央畫布區(qū)展示原始圖像和檢測結(jié)果檢測框用不同顏色區(qū)分行人個(gè)體。右側(cè)信息面板展示檢測統(tǒng)計(jì)人數(shù)、耗時(shí)、置信度分布和大模型分析結(jié)論。底部記錄區(qū)展示歷史檢測記錄支持翻頁和按時(shí)間檢索。交互流程是這樣的用戶點(diǎn)擊上傳選擇圖片頁面立即發(fā)起檢測請求檢測框返回后通過Canvas在原圖上繪制矩形框并在每個(gè)框的左上角標(biāo)注置信度如果用戶開啟了智能分析幾秒后右側(cè)面板會動態(tài)更新AI分析內(nèi)容。整個(gè)過程不需要刷新頁面我把檢測結(jié)果和分析結(jié)論的展示拆成了兩個(gè)獨(dú)立的前端組件各自監(jiān)聽不同的數(shù)據(jù)源。5.2 前后端聯(lián)調(diào)中的數(shù)據(jù)格式約定前后端分離項(xiàng)目大部分的聯(lián)調(diào)問題都出在數(shù)據(jù)格式約定不統(tǒng)一。項(xiàng)目啟動第一天我就和前端同學(xué)把接口契約用OpenAPI規(guī)范固定下來了。這里分享幾個(gè)關(guān)鍵的格式約定。第一個(gè)是坐標(biāo)體系。YOLO輸出的檢測框是像素坐標(biāo)x1, y1, x2, y2但我要求后端傳給前端時(shí)統(tǒng)一轉(zhuǎn)換成相對坐標(biāo)0-1之間因?yàn)榍岸艘诓煌直媛实娘@示器上還原絕對像素坐標(biāo)在響應(yīng)式布局下會錯(cuò)位。轉(zhuǎn)換公式很簡單rel_x abs_x / image_width。第二個(gè)是時(shí)間格式。后端統(tǒng)一返回ISO 8601字符串前端不做本地時(shí)區(qū)臆測直接展示原始字符串。這個(gè)約定避免了后端認(rèn)為自己返回的是UTC前端認(rèn)為是本地時(shí)間的經(jīng)典烏龍。第三個(gè)是空值語義。檢測結(jié)果里如果沒有檢測到行人后端返回的是空數(shù)組而不是null大模型分析失敗時(shí)analysis字段返回null并且附帶一個(gè)errorCode。前端根據(jù)這個(gè)約定做狀態(tài)展示避免出現(xiàn)畫面空白但用戶不知道為什么的問題。// 前端Canvas渲染檢測框的核心邏輯 const drawBoxes (boxes, confidences) { boxes.forEach((box, index) { const [x, y, w, h] normalizeBox(box, imageWidth, imageHeight); ctx.strokeStyle confidences[index] 0.6 ? #f56c6c : #e6a23c; ctx.lineWidth 2; ctx.strokeRect(x, y, w, h); ctx.fillText(${(confidences[index] * 100).toFixed(1)}%, x, y - 5); }); };5.3 WebSocket實(shí)時(shí)推送檢測結(jié)果圖片檢測用HTTP請求就能搞定但視頻流分析和批量檢測場景必須上WebSocket。我采用SpringBoot原生WebSocket實(shí)現(xiàn)連接建立后前端把視頻流按幀抽取后發(fā)送到后端后端經(jīng)過YOLO推理后把結(jié)果實(shí)時(shí)推回前端。這里有個(gè)性能問題要提醒視頻流的幀率不能太貪。我一開始試圖按25fps推流檢測結(jié)果GPU直接打滿隊(duì)列堆積嚴(yán)重。后來調(diào)整策略每秒抽取2-3幀做檢測其余幀直接丟棄。實(shí)際體驗(yàn)下來對于人群監(jiān)控這種場景3幀每秒的檢測頻率完全夠用而且能覆蓋大多數(shù)人群運(yùn)動速度。// WebSocket消息推送示例 Component public class DetectionWebSocket { private final SetWebSocketSession sessions ConcurrentHashMap.newKeySet(); OnOpen public void onOpen(WebSocketSession session) { sessions.add(session); } public void pushDetectResult(String sessionId, DetectionResult result) { // 找到對應(yīng)會話發(fā)送JSON消息 sessions.stream() .filter(s - s.getId().equals(sessionId)) .forEach(s - { try { synchronized (s) { s.sendMessage(new TextMessage(JSON.toJSONString(result))); } } catch (IOException e) { log.error(WebSocket push failed, e); } }); } }6. YOLO數(shù)據(jù)準(zhǔn)備、訓(xùn)練評估與模型轉(zhuǎn)換的實(shí)戰(zhàn)經(jīng)驗(yàn)6.1 數(shù)據(jù)來源與標(biāo)注工具的選擇整套系統(tǒng)的效果上限是數(shù)據(jù)決定的。我在這個(gè)項(xiàng)目里用的訓(xùn)練數(shù)據(jù)來自兩個(gè)部分公開數(shù)據(jù)集以及自己標(biāo)注的實(shí)地場景數(shù)據(jù)。公開數(shù)據(jù)集方面我混合使用了CrowdHuman和VisDrone的部分子集。CrowdHuman是密集行人檢測的經(jīng)典數(shù)據(jù)集每張圖片平均有23人密集場景占比非常高行人之間的遮擋很嚴(yán)重VisDrone補(bǔ)充了大量高空視角的小目標(biāo)樣本正好彌補(bǔ)CrowdHuman在極小目標(biāo)上的不足。自己標(biāo)注時(shí)我選擇了X-AnyLabeling工具。相比LabelImg它內(nèi)置了YOLO檢測模型做預(yù)標(biāo)注人工只需要修正框的位置標(biāo)注效率能翻三倍。文本提示我特別重要標(biāo)注密集人群時(shí)遮擋超過70%的行人要不要標(biāo)我的原則是只要肉眼還能辨別出是一個(gè)獨(dú)立的人就標(biāo)完全被擋住只剩一點(diǎn)點(diǎn)頭發(fā)的不標(biāo)。這個(gè)標(biāo)準(zhǔn)的統(tǒng)一直接影響訓(xùn)練后模型的行為邊界。如果手里只有KITTI或者其他格式的數(shù)據(jù)強(qiáng)烈建議寫成腳本做格式轉(zhuǎn)換。KITTI的標(biāo)注格式和YOLO不同YOLO需要的是歸一化的中心點(diǎn)坐標(biāo)加寬高轉(zhuǎn)換腳本網(wǎng)上有現(xiàn)成的但務(wù)必檢查類別ID的映射這個(gè)最容易出錯(cuò)。6.2 數(shù)據(jù)增強(qiáng)策略與訓(xùn)練參數(shù)調(diào)整密集行人檢測的數(shù)據(jù)增強(qiáng)策略和通用檢測不完全一樣。我在訓(xùn)練時(shí)試過一組增強(qiáng)組合最終穩(wěn)定生效的配置是這樣增強(qiáng)策略參數(shù)設(shè)置作用Mosaic開啟概率0.8豐富小目標(biāo)樣本模擬密集排列MixUp開啟概率0.2提升模型對遮擋的魯棒性Copy-Paste開啟概率0.3模擬行人之間高度重疊HSV增強(qiáng)h0.015, s0.7, v0.4適應(yīng)不同光線環(huán)境隨機(jī)尺度0.5-1.5倍應(yīng)對尺度分布不均訓(xùn)練參數(shù)方面我用的是SGD優(yōu)化器初始學(xué)習(xí)率0.01權(quán)重衰減0.0005batch size 16總共訓(xùn)練200個(gè)epoch。輸入分辨率從默認(rèn)的640×640提升到960×960之后小目標(biāo)AP提升了約5個(gè)百分點(diǎn)代價(jià)是訓(xùn)練時(shí)間和推理時(shí)間都翻倍。我的建議是如果部署機(jī)器的算力允許優(yōu)先把分辨率提到896或960對小目標(biāo)的收益非常明顯。訓(xùn)練之后評估模型時(shí)不要只看mAP。我在項(xiàng)目的評估方案里加了兩個(gè)自定義指標(biāo)密集區(qū)域召回率把標(biāo)注密度超過每平方米0.5人的區(qū)域單獨(dú)統(tǒng)計(jì)召回和重疊目標(biāo)分辨準(zhǔn)確率兩個(gè)中心點(diǎn)距離小于30像素的目標(biāo)對中能同時(shí)正確檢出的比例。這兩個(gè)指標(biāo)比mAP更能反映密集場景的真實(shí)可用性。6.3 模型導(dǎo)出與部署格式選擇訓(xùn)練好的PyTorch模型不能直接扔給生產(chǎn)環(huán)境。我的部署流程一般是PyTorch權(quán)重 → ONNX → TensorRT根據(jù)部署機(jī)器的GPU情況靈活選擇最終格式。導(dǎo)出到ONNX時(shí)有一個(gè)關(guān)鍵參數(shù)要設(shè)置。YOLOv8和v11如果沒有開啟NMS導(dǎo)出ONNX模型輸出的原始預(yù)測結(jié)果還需要自己在部署端做后處理YOLOv10的模型結(jié)構(gòu)本身沒有NMS導(dǎo)出時(shí)更要注意。我的做法是導(dǎo)出時(shí)不帶NMS在后端Python服務(wù)里用OpenCV原生的NMS函數(shù)處理這樣靈活性最高可以隨時(shí)調(diào)整NMS閾值來適應(yīng)不同場景。# 導(dǎo)出ONNX示例 yolo export modelweights/yolov11s_crowd.pt formatonnx opset12 imgsz960導(dǎo)出TensorRT的時(shí)候先用onnx-tensorrt或trtexec工具做離線轉(zhuǎn)換。這里強(qiáng)烈建議在目標(biāo)機(jī)器上做轉(zhuǎn)換而不是在自己電腦上轉(zhuǎn)好了再拷過去因?yàn)門ensorRT的優(yōu)化結(jié)果和GPU型號、驅(qū)動版本強(qiáng)相關(guān)在A卡上轉(zhuǎn)的引擎放到N卡上根本跑不了。7. 部署上線、踩坑記錄與性能調(diào)優(yōu)建議7.1 服務(wù)部署的整體架構(gòu)最終的部署形態(tài)是兩臺服務(wù)器一臺有GPU的用于跑YOLO推理一臺純CPU服務(wù)器跑SpringBoot后端加上大模型調(diào)用層。整體部署用Docker Compose管理MySQL和Redis用單獨(dú)的容器后端、檢測服務(wù)、前端Nginx各自獨(dú)立容器通過內(nèi)網(wǎng)互通。這里說一個(gè)部署時(shí)需要特別注意的點(diǎn)HTTP請求體大小的限制。如果前臺上傳的是高清視頻文件幾十MB甚至上百M(fèi)B都很常見。SpringBoot默認(rèn)的請求體大小是1MB視頻一傳就報(bào)錯(cuò)。需要在配置里顯式調(diào)大同時(shí)設(shè)置合理的超時(shí)時(shí)間。# application.yml 關(guān)鍵配置 spring: servlet: multipart: max-file-size: 200MB max-request-size: 200MB server: tomcat: max-swallow-size: 200MB7.2 踩坑實(shí)錄大模型調(diào)用超時(shí)、NMS漏檢、并發(fā)瓶頸這個(gè)項(xiàng)目從開發(fā)到上線踩了不少坑挑三個(gè)最有代表性的分享。第一個(gè)坑是大模型API調(diào)用超時(shí)。上線第一天檢測圖片功能時(shí)不時(shí)就報(bào)錯(cuò)排查發(fā)現(xiàn)是調(diào)用DeepSeek API時(shí)沒設(shè)置連接超時(shí)默認(rèn)的TCP連接超時(shí)長達(dá)數(shù)分鐘。在部分網(wǎng)絡(luò)環(huán)境下連接會一直掛起然后占滿Tomcat線程池。解決辦法是在RestTemplate或者HTTP客戶端里顯式設(shè)置連接和讀取超時(shí)我設(shè)的是connectTimeout5s、readTimeout30s超時(shí)立刻走備選模型邏輯。第二個(gè)坑是NMS在密集區(qū)域漏檢。這個(gè)問題的根因是默認(rèn)的NMS IoU閾值是0.45在人群密集區(qū)域兩個(gè)真實(shí)行人的IoU經(jīng)常超過0.5導(dǎo)致其中一個(gè)被抑制。我針對行人場景做了調(diào)整把IoU閾值提高到0.65并且把置信度閾值從0.25降到0.15。這樣的副作用是誤檢會增加所以我在后端加了一個(gè)基于框大小和位置的后處理過濾規(guī)則把明顯不合理的框比如寬高比小于0.2的細(xì)長框、超出畫面邊界的框直接丟棄。第三個(gè)坑是SpringBoot在并發(fā)高峰時(shí)的線程池被打滿。Tomcat默認(rèn)的最大線程數(shù)是200當(dāng)多個(gè)用戶同時(shí)上傳圖片做檢測時(shí)每次檢測請求都會阻塞等待Python服務(wù)返回線程池很快耗盡。我的優(yōu)化方案是把檢測任務(wù)丟進(jìn)線程池異步執(zhí)行前端通過任務(wù)ID輪詢或WebSocket獲取結(jié)果這樣Tomcat線程不會長時(shí)間被占用。Configuration public class AsyncConfig { Bean(name detectExecutor) public Executor detectExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(40); executor.setQueueCapacity(200); executor.setThreadNamePrefix(detect-); executor.setRejectedExecutionHandler(new CallerRunsPolicy()); return executor; } }7.3 提升整體性能的幾個(gè)關(guān)鍵手段系統(tǒng)穩(wěn)定運(yùn)行后我開始做調(diào)優(yōu)。通過壓測發(fā)現(xiàn)瓶頸主要在兩個(gè)地方Python推理服務(wù)在并發(fā)請求下的排隊(duì)以及大模型分析的響應(yīng)時(shí)間。針對這兩個(gè)瓶頸我做了幾個(gè)優(yōu)化。優(yōu)化一是Redis加緩存。同一個(gè)攝像頭同一個(gè)區(qū)域的畫面在短時(shí)間內(nèi)檢測結(jié)果具有高度相似性。我用文件哈希模型版本置信度閾值作為緩存key把最近1小時(shí)的檢測結(jié)果緩存到Redis重復(fù)請求直接命中緩存檢測耗時(shí)從幾百毫秒降到個(gè)位數(shù)毫秒。這個(gè)優(yōu)化讓所有展示歷史記錄頁面的加載速度幾乎變成秒開。優(yōu)化二是Python側(cè)啟動預(yù)熱。FastAPI服務(wù)啟動時(shí)如果等到第一個(gè)請求來才加載YOLO模型那次請求的耗時(shí)可能長達(dá)幾十秒。我在服務(wù)啟動事件里把四個(gè)版本的模型全部預(yù)加載到顯存中并各跑一張純黑圖片做GPU預(yù)熱。之后每次請求的推理耗時(shí)基本保持在模型本身的推理時(shí)間不再有冷啟動懲罰。優(yōu)化三是前端做了圖片壓縮。用戶上傳的圖片動輒5-10MB全尺寸傳給后端做檢測很浪費(fèi)。前端在上傳前用Canvas把圖片寬度壓縮到1280像素質(zhì)量壓縮到0.8。檢測框是基于壓縮后的圖片計(jì)算出來的展示結(jié)果時(shí)再把坐標(biāo)等比映射回原圖尺寸。這一步讓上傳帶寬和檢測耗時(shí)都下降了一半以上。注意在部署這套系統(tǒng)時(shí)有幾個(gè)容易忽略的點(diǎn)。第一GPU服務(wù)器的顯存要留足余量同時(shí)加載四個(gè)模型可能直接OOM建議按需加載或者用模型大小換推理速度。第二大模型API調(diào)用一定要做費(fèi)用監(jiān)控如果長時(shí)間無人訪問定時(shí)任務(wù)不斷觸發(fā)分析幾十萬次請求產(chǎn)生的費(fèi)用不是小數(shù)目。第三檢測數(shù)據(jù)涉及個(gè)人隱私系統(tǒng)上線前要做好權(quán)限控制和數(shù)據(jù)脫敏數(shù)據(jù)庫里的檢測記錄存儲時(shí)間不宜過長。最后分享一點(diǎn)個(gè)人體會。這套系統(tǒng)從零到一做完我最大的感受是算法、后端、前端、大模型四個(gè)環(huán)節(jié)的銜接才是真正的復(fù)雜度所在。YOLO單看效果很好SpringBoot單看也不難大模型API文檔更是簡單到一眼就會但把它們組合成一個(gè)穩(wěn)定運(yùn)轉(zhuǎn)的系統(tǒng)每一個(gè)環(huán)節(jié)都需要在性能和可靠性之間反復(fù)做取舍。如果你打算復(fù)現(xiàn)這個(gè)項(xiàng)目我建議不要一上來就追求四版本YOLO全支持和大模型雙路解析先把最小閉環(huán)跑通YOLOv8 SpringBoot 一個(gè)最簡前端再逐步疊加能力這樣排查問題時(shí)的顆粒度會更清晰。一個(gè)小技巧送給正在搭建類似系統(tǒng)的朋友在SpringBoot和Python推理服務(wù)之間加一層接口日志把每次請求的模型版本、推理耗時(shí)、檢測數(shù)量、大模型響應(yīng)時(shí)間全部記錄下來。這些數(shù)據(jù)在調(diào)優(yōu)時(shí)是判斷瓶頸到底在算法側(cè)還是在服務(wù)側(cè)的黃金依據(jù)比我上面提到的任何方案都更值得優(yōu)先落地。