戰(zhàn):從數(shù)據(jù)集到部署)
做熱成像行人檢測這個(gè)方向我是被一個(gè)實(shí)際工程場景逼出來的。某安防預(yù)研項(xiàng)目要在夜間完成行人闖入告警普通可見光攝像機(jī)到了沒路燈的時(shí)段基本就廢了靠補(bǔ)光燈又容易暴露自己還受霧天、雨雪天氣影響。后來把傳感器換成熱成像模組畫面里只有一團(tuán)灰白色的輪廓行人因?yàn)轶w溫輻射變成一個(gè)個(gè)亮白色的移動目標(biāo)問題一下子清晰了。但也正是從這時(shí)候開始我意識到目標(biāo)檢測的一整套流程放在熱成像圖像上并沒有那么簡單沒有顏色紋理、環(huán)境溫度干擾、小目標(biāo)占比高直接把那些在可見光數(shù)據(jù)集上表現(xiàn)很好的權(quán)重拿過來用效果慘不忍睹。折騰了大半年我最終把方案鎖定在 YOLOv8 上并且把數(shù)據(jù)集制作、訓(xùn)練、改進(jìn)點(diǎn)組合、Web 前端展示和實(shí)機(jī)部署整理成了一條能直接復(fù)用的完整鏈路也就是標(biāo)題里說的“一條龍”項(xiàng)目。這篇文章我打算把這條鏈路完整地過一遍為什么選 YOLOv8 而不是其他模型、熱成像數(shù)據(jù)集怎么標(biāo)才不坑、一鍵訓(xùn)練腳本里每個(gè)參數(shù)到底在調(diào)什么、70 多個(gè)改進(jìn)點(diǎn)應(yīng)該怎么挑怎么組合、Web 前端怎么跟檢測后端對接以及部署到實(shí)機(jī)上最容易踩的坑。不管你是拿它做畢業(yè)設(shè)計(jì)、寫小論文還是給單位做一版初測原型這套流程里既有可以直接抄作業(yè)的部分也有需要你根據(jù)自己數(shù)據(jù)重新調(diào)的部分我盡量把關(guān)鍵的決策邏輯都寫清楚。1. 項(xiàng)目的定位與技術(shù)選型為什么熱成像場景下我鎖定了 YOLOv81.1 熱成像行人檢測和普通可見光檢測到底差在哪很多人第一次接觸熱成像檢測第一反應(yīng)是“不就是把輸入圖片換成紅外圖嘛跑個(gè)通用檢測器不就完了”。真上手才會發(fā)現(xiàn)這兩者之間的差異比想象中大得多。先看圖像本身的差異。熱成像圖像體現(xiàn)的是物體表面溫度分布通常以單通道灰度形式呈現(xiàn)。行人、車輛發(fā)動機(jī)、動物、路燈、太陽照射過的水泥地面都可能是高溫目標(biāo)。換句話說在紅外圖像里你需要檢測的“正樣本”并不像可見光那樣具備顏色、紋理、文字等豐富的語義信息很多時(shí)候就是一個(gè)亮斑加邊緣輪廓。這就導(dǎo)致直接使用 ImageNet 上預(yù)訓(xùn)練的模型時(shí)底層特征提取的有效性會打折扣模型更依賴形狀和空間上下文。再看環(huán)境干擾的差異。熱成像受光照變化影響小但受環(huán)境溫度影響非常大。夏天午后地面被暴曬后溫度可能接近甚至超過人體體表溫度行人與背景的對比度顯著下降冬天凌晨人體溫度相對背景很高對比度強(qiáng)但容易出現(xiàn)亮斑過曝、輪廓溢出的現(xiàn)象。同一套模型在不同季節(jié)、不同時(shí)段、不同天氣下性能波動可能非常大。這也是熱成像項(xiàng)目里最容易被低估的難點(diǎn)你的模型不能只在一個(gè)晚上跑通就算完得考慮全天候的穩(wěn)定性。最后是小目標(biāo)與遮擋問題。監(jiān)控和安防場景里行人距離攝像機(jī)往往比較遠(yuǎn)目標(biāo)可能只有幾十個(gè)像素再加上密集人群互相遮擋、半身出畫檢測難度比常規(guī)目標(biāo)檢測高不少。YOLOv8 雖然沒有原生小目標(biāo)檢測層但它的無錨框設(shè)計(jì)配合合適的數(shù)據(jù)增強(qiáng)和 Neck 改進(jìn)對這類目標(biāo)的擬合能力是夠用的。1.2 為什么選 YOLOv8生態(tài)、結(jié)構(gòu)特性與部署友好度的綜合權(quán)衡選 YOLOv8 作為基線模型并不是因?yàn)樗仍谒袛?shù)據(jù)集上一定最高而是綜合了開發(fā)效率、改進(jìn)空間和部署成熟度之后得出的結(jié)論。從結(jié)構(gòu)上看YOLOv8 有幾個(gè)對熱成像場景很友好的設(shè)計(jì)。無錨框Anchor-Free機(jī)制讓模型不需要針對目標(biāo)尺寸預(yù)設(shè)大量先驗(yàn)框熱成像行人尺度變化大的時(shí)候回歸分支可以更靈活地適配。C2f 結(jié)構(gòu)通過多分支梯度流增強(qiáng)了特征提取能力相比于前代的 C3在相同算力下能保留更多細(xì)節(jié)對紅外圖像里邊緣模糊的小目標(biāo)有一定幫助。解耦檢測頭將分類和回歸分支分開訓(xùn)練收斂更穩(wěn)定調(diào)參成本也低一些。從生態(tài)上看ultralytics 庫把數(shù)據(jù)加載、增強(qiáng)、訓(xùn)練、驗(yàn)證、導(dǎo)出、推理封裝得非常完整對于搞畢設(shè)和工程預(yù)研的人來說省去了大量重復(fù)造輪子的時(shí)間。更關(guān)鍵的是YOLOv8 的架構(gòu)約定非常清晰Backbone、Neck、Head 三個(gè)模塊可以獨(dú)立替換這為后面那 70 多個(gè)改進(jìn)創(chuàng)新點(diǎn)的插入提供了極好的基礎(chǔ)。你想換注意力模塊就換注意力模塊想加檢測頭就加檢測頭改動邊界明確出 bug 也好排查。從部署角度看YOLOv8 支持從 PyTorch 權(quán)重導(dǎo)出 ONNX、TensorRT Engine、OpenVINO 等格式Web 后端和邊緣設(shè)備都能直接調(diào)用不需要重新實(shí)現(xiàn)前處理和后處理邏輯。對于一個(gè)既要做 Web 前端展示又要跑實(shí)機(jī)驗(yàn)證的項(xiàng)目來說這套鏈路能省掉一半工作量。補(bǔ)充一點(diǎn)如果你是新手不建議一上來就用自己的需求去魔改結(jié)構(gòu)先把 YOLOv8 的原版權(quán)重在自有數(shù)據(jù)集上跑通、把 loss 曲線和 mAP 看明白再談改進(jìn)。基線都跑不穩(wěn)后面加什么模塊都像是在沙地上蓋樓。2. 數(shù)據(jù)集的構(gòu)建與標(biāo)注細(xì)節(jié)熱成像標(biāo)注完全指南2.1 熱成像數(shù)據(jù)獲取的三種渠道模型要能干活第一步永遠(yuǎn)是數(shù)據(jù)。熱成像的行人數(shù)據(jù)集不像 COCO 那樣隨手能下載必須根據(jù)你的實(shí)際場景去準(zhǔn)備這也是項(xiàng)目周期里最耗時(shí)的一環(huán)。我在實(shí)際操作中主要用三種渠道。第一種是自有設(shè)備采集。這是最推薦的方式因?yàn)槟阕罱K部署在什么環(huán)境就用什么環(huán)境的數(shù)據(jù)訓(xùn)練沒有域差異問題。工業(yè)熱像儀、監(jiān)控級熱成像機(jī)芯、甚至手機(jī)外接的紅外熱成像模組只要能輸出圖像就能用。采集的時(shí)候注意覆蓋不同時(shí)段、不同天氣、不同距離、不同姿態(tài)不要只在晴天中午拍幾百張就完事。一個(gè)好的熱成像數(shù)據(jù)集樣本多樣性比絕對數(shù)量更重要。第二種是公開數(shù)據(jù)集。學(xué)術(shù)社區(qū)里有不少紅外行人數(shù)據(jù)集比如常見的 FLIR ADAS、KAIST、OTCBVS 等適合做預(yù)研和算法驗(yàn)證。不過使用前務(wù)必確認(rèn)許可協(xié)議商用和發(fā)表論文的約束不一樣別給自己挖坑。公開數(shù)據(jù)集的另一個(gè)問題是場景偏差道路駕駛場景的樣本和安防監(jiān)控場景分布差異很大直接訓(xùn)練出來的模型不一定能遷移。第三種是視頻截幀加自動清洗。如果你有一整段監(jiān)控視頻或車載錄像可以用 FFmpeg 按固定間隔抽幀再用一個(gè)初步的檢測器或人工快速篩選剔除模糊幀、重復(fù)幀和無人幀。這樣能在短時(shí)間內(nèi)積累大量有效樣本但要注意連續(xù)幀之間目標(biāo)高度相似隨機(jī)劃分后訓(xùn)練集和驗(yàn)證集可能“串?dāng)?shù)據(jù)”導(dǎo)致指標(biāo)虛高。劃分?jǐn)?shù)據(jù)集的時(shí)候最好按視頻片段來切分而不是逐幀隨機(jī)切。2.2 標(biāo)注工具、標(biāo)注規(guī)范和類別定義標(biāo)注工具方面我用得最多的是 LabelImg 和 X-AnyLabeling。LabelImg 老牌、穩(wěn)定、學(xué)習(xí)成本低適合純矩形框標(biāo)注X-AnyLabeling 自帶半自動輔助能力對于行人這種輪廓相對清晰的目標(biāo)可以先跑一版檢測結(jié)果再人工修正效率高很多。如果數(shù)據(jù)量大也可以考慮用 Labelme 做多邊形標(biāo)注但對普通行人檢測來說矩形框標(biāo)到 bounding box 級別就足夠了多邊形標(biāo)注的邊際收益不高耗費(fèi)的工時(shí)倒是成倍增長。標(biāo)注規(guī)范是數(shù)據(jù)質(zhì)量的生命線。我給自己定了幾條死規(guī)矩目標(biāo)被遮擋超過一半的默認(rèn)不標(biāo)目標(biāo)只有半個(gè)身體或者剛出畫面邊緣的單獨(dú)歸一個(gè) hard 類別或直接不標(biāo)熱成像圖中行人輪廓和背景對比度極低、肉眼都無法確認(rèn)的樣本一律刪除而不是硬標(biāo)。很多新手為了湊樣本數(shù)量把模糊到難以辨認(rèn)的目標(biāo)也標(biāo)進(jìn)去結(jié)果訓(xùn)練出來的模型在真實(shí)場景里頻繁抖動邊界框忽大忽小這種數(shù)據(jù)還不如刪掉。類別定義上絕大多數(shù)安防場景只需要一個(gè) person 類就夠了。如果項(xiàng)目里還需要區(qū)分“戴安全帽的人”和“未戴安全帽的人”那需要在采集時(shí)確保熱像儀分辨率足夠否則遠(yuǎn)距離小目標(biāo)上根本無法分辨安全帽細(xì)節(jié)強(qiáng)行分兩類只會把模型搞糊涂。注意熱成像標(biāo)注最容易被忽略的問題是人眼判斷和模型判斷的差異。你看著很清晰的一個(gè)亮斑在模型眼里可能被周邊高溫地面干擾成另外一個(gè)形狀。所以標(biāo)注完成后我強(qiáng)烈建議做一次“標(biāo)注可視化復(fù)查”把每張圖的標(biāo)簽框繪制在原圖上逐張翻看重點(diǎn)檢查是否有框偏移、類別錯(cuò)誤和漏標(biāo)。2.3 數(shù)據(jù)增強(qiáng)策略別把紅外物理特性給破壞了YOLOv8 自帶的數(shù)據(jù)增強(qiáng)管線很豐富Mosaic、MixUp、隨機(jī)透視、平移縮放等默認(rèn)都是開著的。對于熱成像數(shù)據(jù)這些增強(qiáng)大多數(shù)有效尤其是 Mosaic可以把四張圖拼成一張讓模型在小目標(biāo)上見到更多上下文信息。但有幾類增強(qiáng)需要特別小心。第一類是和顏色空間相關(guān)的增強(qiáng)。熱成像是單通道圖像通常我們要么直接轉(zhuǎn)成三通道灰度要么做偽彩色映射后再喂給模型。無論哪種方式HSV 色域變換都不應(yīng)該使用因?yàn)樗鼤诩俚念伾臻g里引入無意義的擾動反而讓模型學(xué)到錯(cuò)誤特征。第二類是隨機(jī)擦除和 Cutout熱成像目標(biāo)本來就缺少紋理擦除過多容易把僅有的形狀特征破壞掉如果要用擦除面積比例要控制得很小。我更推薦做的增強(qiáng)是圍繞紅外物理語義展開的。比如隨機(jī)調(diào)整圖像的對比度和亮度模擬不同環(huán)境溫度和窗寬窗位設(shè)置下行人對比度的差異比如加入少量高斯噪聲模擬熱像儀傳感器噪聲再比如做小幅度的旋轉(zhuǎn)和尺度抖動讓模型對姿態(tài)和距離變化更魯棒。我經(jīng)常在項(xiàng)目里給數(shù)據(jù)管線里加一個(gè)可選的隨機(jī)窗寬調(diào)整模塊自定義實(shí)現(xiàn)對圖像明暗區(qū)間做非線性拉伸。這比在模型結(jié)構(gòu)上花心思更容易獲得指標(biāo)提升因?yàn)闊岢上癞嬅娴牧炼确秶诓煌O(shè)備上差異非常大模型能適應(yīng)這種差異比什么都重要。提示YOLOv8 里默認(rèn)的 Mosaic 增強(qiáng)會在最后 10 個(gè) epoch 自動關(guān)閉這是為了讓模型在接近真實(shí)數(shù)據(jù)分布的條件下做最后的收斂。我在熱成像項(xiàng)目里一般把這個(gè)關(guān)閉值調(diào)成 15 甚至 20因?yàn)榧t外圖背景相對單調(diào)模型更容易依賴 Mosaic 拼接出的“虛擬上下文”不多給幾個(gè)正常訓(xùn)練的 epoch驗(yàn)證集指標(biāo)會有所回退。3. 一鍵訓(xùn)練腳本與核心參數(shù)解析從數(shù)據(jù)目錄到訓(xùn)練完成的完整流程3.1 數(shù)據(jù)目錄結(jié)構(gòu)與 YOLOv8 訓(xùn)練配置所謂“一鍵訓(xùn)練”本質(zhì)上是把數(shù)據(jù)處理、模型初始化、訓(xùn)練參數(shù)、日志保存這些流程固化成一個(gè)自動化腳本讓使用者不需要深入理解每個(gè)細(xì)節(jié)也能跑起來。但你要真正用好它還是得知道腳本背后發(fā)生了什么。YOLOv8 要求的數(shù)據(jù)集目錄結(jié)構(gòu)是固定的。項(xiàng)目里我構(gòu)建了如下結(jié)構(gòu)datasets/ ├─ thermal_person/ │ ├─ images/ │ │ ├─ train/ │ │ └─ val/ │ ├─ labels/ │ │ ├─ train/ │ │ └─ val/ │ ├─ train.txt │ ├─ val.txt │ └─ data.yamlimages 下放原始圖像labels 下放每個(gè)圖像對應(yīng)的 txt 標(biāo)注文件。每個(gè) txt 文件的每一行是一行標(biāo)注格式為類別 id、歸一化后的中心點(diǎn) x、歸一化后的中心點(diǎn) y、歸一化后的寬 w、歸一化后的高 h。注意坐標(biāo)是歸一化到 0 到 1 之間的浮點(diǎn)數(shù)不要寫成像素坐標(biāo)。data.yaml 是訓(xùn)練入口的配置文件內(nèi)容大致如下path: datasets/thermal_person train: images/train val: images/val nc: 1 names: 0: person如果類別多于一個(gè)names 繼續(xù)往下加編號就行。這里有個(gè)容易踩的坑path 字段如果填絕對路徑在換機(jī)器訓(xùn)練時(shí)必須同步修改如果填相對路徑又要確保腳本的工作目錄正確。我的做法是在訓(xùn)練腳本里動態(tài)生成這個(gè) yaml 文件用 os.path.abspath 獲取當(dāng)前項(xiàng)目根目錄拼接出完整路徑這樣不管項(xiàng)目拷到哪臺機(jī)器上都能直接用。3.2 訓(xùn)練腳本核心邏輯與關(guān)鍵參數(shù)詳解一鍵訓(xùn)練腳本的核心部分并不復(fù)雜我還是用 ultralytics 庫來寫但做了一些工程化封裝。核心邏輯可以簡化為下面這段代碼import os import torch from ultralytics import YOLO def train_model( cfg_pathconfigs/train_config.yaml, data_yamldatasets/thermal_person/data.yaml, weightsyolov8n.pt, epochs200, imgsz640, batch_size8, device0, projectruns/thermal_person, nameexp, ): # 加載模型傳入權(quán)重路徑表示從預(yù)訓(xùn)練權(quán)重繼續(xù)訓(xùn)練 model YOLO(weights) # 開始訓(xùn)練 model.train( datadata_yaml, epochsepochs, imgszimgsz, batchbatch_size, devicedevice, projectproject, namename, patience30, optimizerSGD, lr00.01, lrf0.01, close_mosaic15, cacheTrue, ampTrue, plotsTrue, ) if __name__ __main__: train_model()這個(gè)腳本看起來簡單但每個(gè)參數(shù)我都建議你親手試一遍理解它對訓(xùn)練行為的影響。epochs 的取值是第一個(gè)需要斟酌的地方。對于熱成像行人檢測這種單類別、目標(biāo)尺度相對集中的任務(wù)200 個(gè) epoch 通常足夠模型收斂配合早停機(jī)制 patience30即驗(yàn)證集指標(biāo)連續(xù) 30 個(gè) epoch 不提升就自動停止訓(xùn)練可以省下不少時(shí)間。如果數(shù)據(jù)集特別小比如只有幾百張反而不要硬上大 epoch模型很容易在后期過擬合表現(xiàn)為訓(xùn)練 loss 一直降但驗(yàn)證 mAP 不再上升甚至下降。imgsz 默認(rèn) 640 是速度和精度的折中。如果你的部署環(huán)境算力充足而且場景里小目標(biāo)特別多可以嘗試 768 或 1024我實(shí)測下來對遠(yuǎn)距離行人的 recall 有明顯改善。但要注意訓(xùn)練輸入尺寸和推理輸入尺寸必須保持一致否則模型性能會有肉眼可見的下降。batch_size 受顯卡顯存限制。8G 顯存跑 yolov8n 用 640 輸入batch 8 會比較穩(wěn)如果想調(diào)大又擔(dān)心 OOM可以用更大的 batch 配合梯度累積。ultralytics 里對梯度累積的封裝是自動的只要 batch 設(shè)大、顯存不夠它會嘗試自動拆分但我不建議完全依賴它最好一開始就手動估算好顯存余量。lr0 和 lrf 的學(xué)習(xí)率策略也值得多說一句。熱成像數(shù)據(jù)集規(guī)模一般不大初始學(xué)習(xí)率設(shè)太高很容易震蕩設(shè)太低收斂又慢。我一般用 SGD初始學(xué)習(xí)率 0.01配合線性或者余弦退火降到 0.0001 左右。如果你用的是 AdamW初始學(xué)習(xí)率建議降到 0.001 附近。不要不同優(yōu)化器沿用同一套學(xué)習(xí)率參數(shù)這是我見過新手最容易犯的錯(cuò)之一。3.3 訓(xùn)練過程中的指標(biāo)觀察比 mAP 更重要的是這些曲線訓(xùn)練啟動之后別急著切后臺聊天。我習(xí)慣同時(shí)開著終端日志和 tensorboardultralytics 會自動生成 runs/下的結(jié)果文件重點(diǎn)觀察幾個(gè)曲線。第一個(gè)是 box_loss、cls_loss、dfl_loss 三條訓(xùn)練曲線的下降趨勢。正常情況它們應(yīng)該平滑下降如果有突然的尖峰大概率是數(shù)據(jù)里有異常樣本或?qū)W習(xí)率設(shè)置不當(dāng)。第二個(gè)是驗(yàn)證集的 mAP50 和 mAP50-95。熱成像行人檢測因?yàn)楸尘跋鄬Ω蓛鬽AP50 往往能沖到 0.9 以上但 mAP50-95 的數(shù)值更能反映邊界框定位精度和模型對多尺度目標(biāo)的魯棒性如果 mAP50 很高而 mAP50-95 偏低說明模型雖然能框出人但框的位置、大小不準(zhǔn)。第三個(gè)是每個(gè)類別的召回率單類模型看起來簡單實(shí)際最容易出現(xiàn)的問題是在遠(yuǎn)距離小目標(biāo)上 recall 掉得很厲害框倒是預(yù)測了不少全是假陽性的高 IoU 預(yù)測真陽性一個(gè)沒抓住。我還會在訓(xùn)練到一半的時(shí)候手動跑一版驗(yàn)證集的預(yù)測圖重點(diǎn)看三類樣本密集人群場景、單目標(biāo)遠(yuǎn)距離場景、地面與人體溫度接近的場景。指標(biāo)是整體情況的反映但工程上必須落到具體場景里才敢判斷模型能不能上線。提示訓(xùn)練結(jié)束后best.pt 和 last.pt 會在輸出目錄里。best.pt 是按驗(yàn)證集指標(biāo)選擇的最優(yōu)權(quán)重last.pt 是最后一個(gè) epoch 的權(quán)重。我在熱成像項(xiàng)目里幾乎總是用 best.pt但有個(gè)例外如果驗(yàn)證集劃分不合理best.pt 可能會在某個(gè)片段上過擬合而 last.pt 在全局分布上更均衡。遇到這種情況最好的辦法不是糾結(jié)用哪一個(gè)而是回頭重新劃分?jǐn)?shù)據(jù)集。4. 70 改進(jìn)創(chuàng)新點(diǎn)的組織邏輯與發(fā)刊思路找到能寫進(jìn)論文的 3 個(gè)亮點(diǎn)4.1 改進(jìn)點(diǎn)按模塊分類從 Backbone 到 Loss 的完整地圖標(biāo)題里提到的“70 全套改進(jìn)創(chuàng)新點(diǎn)”實(shí)際是指項(xiàng)目里預(yù)置了一整套可以插入 YOLOv8 的可選模塊覆蓋了網(wǎng)絡(luò)結(jié)構(gòu)的各個(gè)部分。按我的經(jīng)驗(yàn)這些改進(jìn)點(diǎn)可以分為六大類。第一類是 Backbone 替換。把 YOLOv8 默認(rèn)的 CSPDarknet 替換成輕量化網(wǎng)絡(luò)或更強(qiáng)的主干特征提取網(wǎng)絡(luò)比如 MobileNet 系列、ShuffleNet 系列、StarNet、PPLCNet 等。這類改進(jìn)的主要賣點(diǎn)是降低參數(shù)量和計(jì)算量讓模型更容易部署在嵌入式設(shè)備上同時(shí)通過結(jié)構(gòu)設(shè)計(jì)彌補(bǔ)輕量化帶來的精度損失。第二類是 Neck 特征融合改進(jìn)。YOLOv8 用的是 PAN-FPN 結(jié)構(gòu)改進(jìn)方向包括引入 BiFPN 加權(quán)雙向特征融合、AFPN 漸進(jìn)式特征金字塔、SE-FPN 注意力特征融合等。這類改進(jìn)的核心邏輯是讓不同尺度的特征圖在融合時(shí)各司其職而不是簡單相加。對于熱成像行人檢測來說遠(yuǎn)距離小目標(biāo)和近距離大目標(biāo)同時(shí)存在Neck 的融合能力直接影響檢測精度上限。第三類是注意力機(jī)制。SE、CBAM、CA、EMA、SA 等注意力模塊可以插入 Backbone 末端、Neck 融合節(jié)點(diǎn)或者 Head 前面。注意力機(jī)制的本質(zhì)是讓模型更關(guān)注行人的區(qū)域特征抑制高溫地面、樹葉晃動等背景干擾。我在熱成像場景里最常用的是坐標(biāo)注意力和 EMA它們對空間位置的建模能力比通道注意力更適合小目標(biāo)。第四類是檢測頭改進(jìn)。包括 ASFF 自適應(yīng)特征融合、Dynamic Head 動態(tài)頭、輕量解耦頭等。檢測頭的改進(jìn)往往能直接提升邊界框回歸精度代價(jià)是會增加一些計(jì)算量。第五類是 Loss 改進(jìn)。用 Wise-IoU、Focal-EIoU、Inner-IoU 等替代默認(rèn)的 CIoU。熱成像行人目標(biāo)尺度變化大IoU 損失對邊界框的尺度敏感合適的損失函數(shù)能讓回歸更穩(wěn)定。第六類是訓(xùn)練策略與輔助結(jié)構(gòu)。包括添加 P2 小目標(biāo)檢測層、輔助訓(xùn)練頭、知識蒸餾、EMA 指數(shù)移動平均等。這些改進(jìn)不改變網(wǎng)絡(luò)結(jié)構(gòu)但對訓(xùn)練過程的穩(wěn)定性和小目標(biāo)性能提升明顯。4.2 改進(jìn)點(diǎn)不能盲目堆疊組合邏輯決定論文故事很多同學(xué)拿到 70 多個(gè)改進(jìn)點(diǎn)第一反應(yīng)是“全給我加上”這是最典型的誤區(qū)。如果你把 10 個(gè)模塊全塞進(jìn)網(wǎng)絡(luò)里模型漲點(diǎn)可能反而被負(fù)優(yōu)化消融實(shí)驗(yàn)還完全沒法寫因?yàn)楦菊f不清楚漲點(diǎn)來自哪個(gè)模塊。我的建議是死守“一個(gè)故事三個(gè)模塊”的組合原則。所謂故事就是你論文的核心論點(diǎn)。比如你要講“面向低功耗熱成像行人檢測的輕量化改進(jìn)方法”那么故事主線就是“輕量化”。你要選的三個(gè)模塊就是輕量化 Backbone 替換把參數(shù)量降下來在 Neck 中引入某種注意力特征融合把輕量化帶來的精度損失補(bǔ)回來再用一個(gè)改進(jìn)的 Loss 函數(shù)把邊界框回歸精度拉上去。這樣一個(gè)組合三個(gè)模塊各有分工論文的實(shí)驗(yàn)部分可以分別做消融最后再展示整體疊加的效果邏輯順暢審稿人看著也舒服。另一個(gè)常見組合是面向小目標(biāo)場景加 P2 小目標(biāo)檢測層讓模型在最淺層特征上多一次檢測機(jī)會引入 BiFPN 加權(quán)特征融合讓小目標(biāo)特征在跨層傳遞時(shí)不丟失用 Focal-EIoU 損失加強(qiáng)對困難樣本的回歸約束。這個(gè)組合在遠(yuǎn)距離行人檢測場景下非常能打。我不建議一次性在同一個(gè)模型里塞超過 4 個(gè)改進(jìn)模塊。改進(jìn)點(diǎn)越多訓(xùn)練時(shí)間越長超參數(shù)調(diào)起來越痛苦最終結(jié)果還不一定好。先把模型在你自己的數(shù)據(jù)集上跑出基線然后一次只加一個(gè)模塊分別對比指標(biāo)變化這個(gè)過程中你會對每個(gè)模塊的實(shí)際作用建立直觀認(rèn)識寫論文時(shí)也能言之有物。4.3 發(fā)刊與論文寫作需要準(zhǔn)備的圖和表清單如果你做這個(gè)項(xiàng)目的目標(biāo)是寫小論文或者畢業(yè)設(shè)計(jì)那么這套改進(jìn)點(diǎn)的價(jià)值不在于“能跑”而在于“能證明”?!白C明”靠的是嚴(yán)謹(jǐn)?shù)膶Ρ葘?shí)驗(yàn)和可視化的圖表。我建議從第一天訓(xùn)練就對實(shí)驗(yàn)過程留痕否則后面補(bǔ)實(shí)驗(yàn)會崩潰。第一要準(zhǔn)備的是消融實(shí)驗(yàn)表。這是論文核心。表格形式大概是第一行是 YOLOv8 基線列出參數(shù)量、計(jì)算量、mAP50、mAP50-95、FPS接下來每一行在基線上加一個(gè)改進(jìn)點(diǎn)最后一行是所有改進(jìn)點(diǎn)疊加后的最終模型。每加一個(gè)模塊單獨(dú)跑一次訓(xùn)練保證訓(xùn)練參數(shù)一致只改變模塊本身這才有對比價(jià)值。第二要準(zhǔn)備的是可視化對比圖。訓(xùn)練結(jié)束后用最終模型和基線模型分別對同一批驗(yàn)證集圖片做推理挑出 6 到 8 張具有代表性的圖像包含密集人群、遠(yuǎn)距離小目標(biāo)、低對比度場景并排放在一起圈出模型預(yù)測的差異。這張圖在論文里非常有說服力因?yàn)閷徃迦巳庋劬湍芸闯龈倪M(jìn)帶來的實(shí)際效果。第三是 PR 曲線和混淆矩陣。PR 曲線反映模型的精度召回綜合表現(xiàn)混淆矩陣反映誤檢和漏檢的具體分布。對于熱成像行人單類檢測混淆矩陣雖然只有兩行兩列卻能清楚地展示模型把什么錯(cuò)當(dāng)成行人、把什么行人漏掉了。注意發(fā)論文和做工程是兩種思維。工程追求精度和效率的平衡論文更強(qiáng)調(diào)可解釋性和模塊創(chuàng)新。對于“70 改進(jìn)點(diǎn)”這個(gè)數(shù)量級真正寫進(jìn)論文的只需要 3 到 4 個(gè)其余部分是你在探索過程中的試錯(cuò)記錄。建議每一項(xiàng)改進(jìn)都單獨(dú)保留權(quán)重文件和實(shí)驗(yàn)日志避免到寫論文時(shí)才發(fā)現(xiàn)某個(gè)關(guān)鍵對比實(shí)驗(yàn)沒跑。5. Web 前端展示與實(shí)時(shí)檢測后端推理與前端渲染的完整對接方案5.1 Web 展示整體架構(gòu)采集、推理、推送、渲染四層解耦“Web 前端展示”看起來是個(gè)加分項(xiàng)但實(shí)際上它是整個(gè)項(xiàng)目能不能對外演示的關(guān)鍵環(huán)節(jié)。導(dǎo)師、客戶、評委不可能去看你在終端里敲指令輸出 log他們要看到的是網(wǎng)頁上實(shí)時(shí)視頻流里一個(gè)個(gè)框在跟人走。我采用的是一套經(jīng)典的兩端分離架構(gòu)前端負(fù)責(zé)視頻流展示和交互后端負(fù)責(zé)圖像采集、推理和結(jié)果推送。整個(gè)鏈路分成四層第一層是圖像采集層可以是本地?cái)z像頭、網(wǎng)絡(luò) RTSP 流、視頻文件或單張圖片第二層是推理層加載訓(xùn)練好的 pt 模型對每一幀圖像執(zhí)行目標(biāo)檢測第三層是結(jié)果推送層把檢測到的目標(biāo)類別、置信度、邊界框坐標(biāo)序列化成 JSON 數(shù)據(jù)通過 WebSocket 實(shí)時(shí)推送到前端第四層是前端渲染層前端把視頻幀顯示在 canvas 上同時(shí)根據(jù)推過來的坐標(biāo)數(shù)據(jù)繪制邊界框和標(biāo)簽。這樣一個(gè)四層解耦的結(jié)構(gòu)有兩個(gè)明顯好處。第一每一層都可以獨(dú)立替換比如采集層可以從視頻文件換成 RTSP 攝像頭推理層可以把 PyTorch 權(quán)重?fù)Q成 TensorRT Engine對前端零影響。第二排查問題特別方便哪一層出 bug 就在哪一層單獨(dú)測試。5.2 后端的實(shí)現(xiàn)FastAPI 承載推理服務(wù)與 WebSocket 推送后端我選擇 FastAPI它的異步能力和 WebSocket 支持都很好代碼量又少。下面是一個(gè)簡化版的推理服務(wù)實(shí)現(xiàn)核心邏輯分為三步接受前端連接、從視頻源讀取幀、執(zhí)行推理并通過 WebSocket 推送結(jié)果。import asyncio import cv2 import json from fastapi import FastAPI, WebSocket from ultralytics import YOLO app FastAPI() model YOLO(runs/thermal_person/exp/best.pt) cap cv2.VideoCapture(0) # 根據(jù)實(shí)際攝像頭/視頻源調(diào)整 app.websocket(/ws/detect) async def websocket_detect(websocket: WebSocket): await websocket.accept() try: while True: ret, frame cap.read() if not ret: continue # 推理streamFalse 等待單幀結(jié)果streamTrue 則異步 results model.predict(frame, imgsz640, conf0.5, verboseFalse) boxes results[0].boxes.xyxy.tolist() confs results[0].boxes.conf.tolist() clss results[0].boxes.cls.tolist() payload { boxes: boxes, confs: confs, clss: clss, } await websocket.send_text(json.dumps(payload)) await asyncio.sleep(0.03) # 控制發(fā)送頻率約30FPS except Exception as e: print(fWebSocket error: {e}) finally: await websocket.close()這個(gè)實(shí)現(xiàn)有兩點(diǎn)需要注意。第一model.predict 是阻塞式的在 WebSocket 循環(huán)里串行執(zhí)行如果推理時(shí)間超過一幀間隔實(shí)際幀率會下降。想要跑滿幀率可以改用異步推理或者把模型加載到單獨(dú)的推理線程里用隊(duì)列傳遞幀和結(jié)果。第二conf 閾值 0.5 是前端的默認(rèn)值更合理的做法是把閾值做在 WebSocket 消息里由前端動態(tài)調(diào)節(jié)因?yàn)椴煌瑘鼍跋履阈枰萑痰恼`檢率不一樣。視頻幀本身我沒有通過 WebSocket 推送給前端而是讓前端直接拉取攝像頭或視頻源的 MJPEG 流或者訪問后端的 /video 端點(diǎn)拿原始視頻幀檢測結(jié)果只推送坐標(biāo)數(shù)據(jù)。這樣設(shè)計(jì)的好處是大幅度降低 WebSocket 帶寬壓力只要你用 H5 的 video 標(biāo)簽或 canvas 繪制幀結(jié)合繪制邊界框的 JS體驗(yàn)就會很流暢。5.3 前端渲染Canvas 實(shí)時(shí)繪圖與交互界面前端的核心功能其實(shí)只有兩個(gè)顯示視頻幀疊加繪制檢測框。如果用傳統(tǒng)的 JS 操作 DOM 去畫框每秒鐘要畫幾十個(gè)元素性能會很差。正確做法是把視頻幀繪制到 canvas 上同時(shí)在 canvas 的同一坐標(biāo)系里繪制檢測框。下面是我常用的核心繪圖邏輯視頻標(biāo)簽隱藏或者直接繪制到 canvas 作為背景WebSocket 每收到一幀檢測結(jié)果就先用 drawImage 繪出視頻當(dāng)前幀再遍歷 boxes 數(shù)組用 strokeRect 繪制矩形邊框用 fillText 寫上類別和置信度。關(guān)鍵點(diǎn)在于 canvas 的尺寸和視頻實(shí)際尺寸之間的比例關(guān)系必須一致否則檢測框會偏移。我提供的簡化版實(shí)現(xiàn)const video document.getElementById(video); const canvas document.getElementById(overlay); const ctx canvas.getContext(2d); const socket new WebSocket(ws://localhost:8000/ws/detect); socket.onmessage (event) { const data JSON.parse(event.data); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const scaleX canvas.width / video.videoWidth; const scaleY canvas.height / video.videoHeight; data.boxes.forEach((box, i) { const [x1, y1, x2, y2] box; const cls data.clss[i]; const conf data.confs[i]; ctx.strokeStyle #00ff00; ctx.lineWidth 3; ctx.strokeRect(x1 * scaleX, y1 * scaleY, (x2 - x1) * scaleX, (y2 - y1) * scaleY); ctx.fillStyle #00ff00; ctx.font 16px sans-serif; ctx.fillText(person ${conf.toFixed(2)}, x1 * scaleX, y1 * scaleY - 5); }); };除了最基本的檢測框顯示我建議再加三個(gè)小功能置信度閾值滑塊調(diào)節(jié)后實(shí)時(shí)影響后端的 conf 參數(shù)歷史檢測統(tǒng)計(jì)列表記錄最近一分鐘內(nèi)的行人數(shù)量變化曲線截圖按鈕把當(dāng)前 canvas 保存成 PNG方便做現(xiàn)場演示的素材記錄。這三個(gè)功能加起來代碼量不大但演示效果會質(zhì)變尤其是人數(shù)統(tǒng)計(jì)曲線和熱成像檢測結(jié)合起來非常直觀。前端頁面布局可以參考經(jīng)典監(jiān)控墻風(fēng)格左側(cè)大屏顯示視頻流和檢測框右側(cè)信息欄顯示實(shí)時(shí)人群數(shù)量、FPS、檢測到的目標(biāo)列表。不需要引入大型前端框架原生 HTML JavaScript 就足夠這也降低了整套系統(tǒng)的部署依賴。6. 部署與常見問題排查實(shí)錄一次跑通深度學(xué)習(xí)項(xiàng)目的必備清單6.1 環(huán)境搭建與模型導(dǎo)出部署訓(xùn)練環(huán)境是一回事部署環(huán)境是另一回事。訓(xùn)練階段你可能用的是自己的 GPU 工作站而實(shí)際部署可能是一臺只有 CPU 的工控機(jī)或者一塊 Jetson 開發(fā)板。我見過太多項(xiàng)目死在部署這一步模型在電腦上跑的飛快到了現(xiàn)場設(shè)備上每秒只有一兩幀。環(huán)境搭建的第一步是把 PyTorch、ultralytics、OpenCV、FastAPI、uvicorn 這些基礎(chǔ)依賴裝好。如果部署機(jī)有 NVIDIA 顯卡務(wù)必裝對 CUDA 版本的 PyTorch我建議直接用 conda 創(chuàng)建獨(dú)立環(huán)境避免污染系統(tǒng) Python。如果沒有顯卡也不要絕望YOLOv8n 模型用 CPU 在 640 輸入下推理一幀大概 100 到 200 毫秒如果你的檢測目標(biāo)不是高速運(yùn)動這個(gè)速度勉強(qiáng)夠用如果要做實(shí)時(shí)視頻流就必須考慮模型輕量化或硬件加速。如果你的部署機(jī)有顯卡下一步是把 PyTorch 權(quán)重導(dǎo)出成 TensorRT Engine這一步能把推理速度提升數(shù)倍。導(dǎo)出命令很簡單yolo export modelruns/thermal_person/exp/best.pt formatengine device0導(dǎo)出后的 engine 文件可以被 ultralytics 直接加載對于推理端到端的耗時(shí)縮短非常明顯。我實(shí)測過同一個(gè) YOLOv8n 模型PyTorch 權(quán)重推理耗時(shí)約 30 毫秒TensorRT Engine 能壓到 8 到 10 毫秒差距是非常可觀的。部署機(jī)上還要注意一個(gè)常見問題OpenCV 的版本和編譯選項(xiàng)。一些嵌入式設(shè)備自帶的 OpenCV 不支持 FFmpeg導(dǎo)致無法讀取 RTSP 流或視頻文件。這種情況下優(yōu)先用 pip 安裝 opencv-python 替代系統(tǒng)自帶的庫如果還不行就得把后端推理代碼改成用 imageio 或 ffmpeg 命令先抽幀再送入模型。6.2 高頻報(bào)錯(cuò)與排查方法一張速查表我在搭建和調(diào)試這個(gè)項(xiàng)目的過程中踩過大量問題。為了讓你少走彎路我把最高頻的幾個(gè)問題整理成了一張速查表現(xiàn)象大概率原因排查與解決辦法訓(xùn)練時(shí)報(bào) CUDA out of memorybatch 過大或 imgsz 過大調(diào)小 batch 和 imgsz或開啟梯度累積訓(xùn)練 loss 出現(xiàn) NaN學(xué)習(xí)率過高、數(shù)據(jù)里存在異常樣本降低 lr0關(guān)閉 AMP 試一次檢查標(biāo)注文件是否有 NaN 坐標(biāo)驗(yàn)證集 mAP 為 0標(biāo)簽格式錯(cuò)誤、類別 id 對不上檢查 txt 標(biāo)注文件確認(rèn) class id 在 nc 范圍內(nèi)用可視化腳本檢查標(biāo)簽框推理時(shí)檢測框全部偏移輸入尺寸與訓(xùn)練時(shí)不一致、前處理尺度變化保證推理 imgsz 與訓(xùn)練一致檢查是否把原圖直接送入模型而沒有做 letterbox模型加載報(bào) missing keys模型類別數(shù)與預(yù)訓(xùn)練權(quán)重不匹配修改 nc 后重新訓(xùn)練避免直接從預(yù)訓(xùn)練權(quán)重強(qiáng)行加載WebSocket 連接后沒有消息推理線程卡死或異常在推理循環(huán)里加 print 或日志輸出定位是否在模型推理階段阻塞部署機(jī) CPU 推理極慢模型過大或輸入尺寸過大換更輕量的模型或者把輸入尺寸從 640 降到 480啟用 OpenVINO我發(fā)現(xiàn)一個(gè)高發(fā)問題特別想強(qiáng)調(diào)數(shù)據(jù)標(biāo)注文件里出現(xiàn)了科學(xué)計(jì)數(shù)法格式的坐標(biāo)比如 “1e-3”YOLOv8 的一些舊版本讀取時(shí)會把這一行解析錯(cuò)結(jié)果整個(gè)標(biāo)簽文件作廢訓(xùn)練時(shí)模型直接學(xué)不到正樣本。解決辦法很簡單標(biāo)注完成后用腳本統(tǒng)一清洗一遍 txt把數(shù)字格式化成正則能識別的十進(jìn)制格式并且檢查坐標(biāo)范圍都在 0 到 1 之間。6.3 給新手的 5 條壓箱底建議來自實(shí)際項(xiàng)目復(fù)盤最后分享幾條壓箱底的建議都是我在真實(shí)項(xiàng)目里交過學(xué)費(fèi)換來的。第一條先把從數(shù)據(jù)集到部署的整條鏈路用最小規(guī)模跑通一次。我見過太多同學(xué)數(shù)據(jù)集標(biāo)了大半個(gè)月到最后才發(fā)現(xiàn)標(biāo)注格式完全不對浪費(fèi)了大量時(shí)間。正確做法是拿到幾百張圖先標(biāo)一版馬上開訓(xùn)哪怕只訓(xùn) 20 個(gè) epoch先讓模型能跑出結(jié)果再回頭去擴(kuò)充數(shù)據(jù)標(biāo)注量。鏈路通暢了后面的事情才有意義。第二條一個(gè)改進(jìn)點(diǎn)一個(gè)實(shí)驗(yàn)不要并行堆模塊。每次只加一個(gè)改進(jìn)模塊記錄完整的實(shí)驗(yàn)日志數(shù)據(jù)集版本、參數(shù)配置、訓(xùn)練時(shí)間、最終指標(biāo)。不要相信記憶力項(xiàng)目周期一長你根本記不清哪個(gè)實(shí)驗(yàn)用了什么組合。用表格記錄每一輪實(shí)驗(yàn)的修改內(nèi)容和調(diào)節(jié)因素這會讓你在寫論文時(shí)受益匪淺。第三條數(shù)據(jù)集的劃分一定要按視頻片段、場景來源、時(shí)間切片來分離而不是逐幀隨機(jī)分。熱成像監(jiān)控視頻相鄰幀高度相似隨機(jī)切分會造成同一個(gè)人同時(shí)出現(xiàn)在訓(xùn)練集和驗(yàn)證集里模型指標(biāo)虛高得離譜部署后立刻現(xiàn)原形。第四條部署前必須模擬現(xiàn)場數(shù)據(jù)分布做測試。我吃過一次虧訓(xùn)練集全是晴天晚上拍的數(shù)據(jù)結(jié)果項(xiàng)目方在陰雨天摸底測試行人溫度被雨水沖刷后和地面溫度幾乎一致模型召回率掉得慘不忍睹。后來我在數(shù)據(jù)集里專門加了“低對比度”類別樣本并用隨機(jī)窗位調(diào)整模擬這類場景才把召回率救回來。模型的泛化能力不是靠堆結(jié)構(gòu)堆出來的是靠數(shù)據(jù)覆蓋度撐起來的。第五條邏輯上先跑通原版 YOLOv8再加改進(jìn)點(diǎn)。原版 baseline 的指標(biāo)是你后續(xù)所有改進(jìn)的參照系。沒有 baseline你加的任何模塊都無法回答“它到底有沒有用”這個(gè)問題。在熱成像數(shù)據(jù)集上原版 YOLOv8n 的 mAP50 通常已經(jīng)能達(dá)到一個(gè)可接受的水平改進(jìn)點(diǎn)的作用是在這個(gè)基礎(chǔ)上往 90 分以上沖順便給你的論文湊出故事線。提示這個(gè)項(xiàng)目如果后續(xù)要繼續(xù)擴(kuò)展可以考慮加一個(gè)簡單的目標(biāo)跟蹤模塊比如 ByteTrack 或 StrongSORT把檢測結(jié)果關(guān)聯(lián)成軌跡線不僅能在 Web 前端上顯示運(yùn)動軌跡還能做人流統(tǒng)計(jì)、越界報(bào)警和軌跡回放和熱成像行人檢測的場景契合度非常高。我個(gè)人在實(shí)際操作中的體會是熱成像行人檢測這個(gè)方向技術(shù)本身不算特別復(fù)雜難的是數(shù)據(jù)、場景和系統(tǒng)整合。數(shù)據(jù)集決定了模型的上限部署環(huán)境決定了你能不能讓這個(gè)上限落地而 Web 展示則是把技術(shù)價(jià)值翻譯給人看的那最后一公里。希望這篇文章能幫你少走幾個(gè)月的彎路也歡迎你在復(fù)現(xiàn)這套流程的時(shí)候把你遇到的那些有意思的坑回來說給我聽。