習(xí)的密度圖人流量檢測:PyTorch實(shí)現(xiàn)與畢設(shè)指南)
簡介本資源為面向畢業(yè)設(shè)計(jì)、課程設(shè)計(jì)及論文寫作的人流量檢測方向參考文檔適合計(jì)算機(jī)視覺、深度學(xué)習(xí)初學(xué)者及高校師生使用。文檔以MobileNet-SSD輕量級檢測模型為核心系統(tǒng)介紹從數(shù)據(jù)集構(gòu)建、模型訓(xùn)練到行人檢測與追蹤的完整流程并給出公園、文化廣場及傳染病疫情期間疏散人群等典型應(yīng)用場景。內(nèi)容同時(shí)涵蓋網(wǎng)絡(luò)結(jié)構(gòu)細(xì)節(jié)、開發(fā)環(huán)境配置與實(shí)驗(yàn)驗(yàn)證結(jié)果可作為項(xiàng)目開發(fā)、論文撰寫或答辯準(zhǔn)備的有力參照。資源包共1個(gè)文件為docx格式約224KB便于直接閱讀與引用。目前已有81人學(xué)習(xí)下載適合希望系統(tǒng)掌握輕量級目標(biāo)檢測落地思路的讀者進(jìn)一步研讀。1. 從選題到落地人流量檢測方法在畢設(shè)論文里到底要做什么“基于深度學(xué)習(xí)的人流量檢測方法”這個(gè)選題放在畢設(shè)或課設(shè)里目標(biāo)不是訓(xùn)練一個(gè)能畫框的目標(biāo)檢測器而是形成一條“輸入視頻幀、輸出人數(shù)”的完整鏈路。很多人選完題直接去跑YOLO跑完才發(fā)現(xiàn)密集場景下計(jì)數(shù)偏差大得沒法寫進(jìn)論文換個(gè)角度把任務(wù)定義成密度回歸人流量檢測會(huì)順手很多。這篇筆記按一個(gè)三周能跑通主線、一周補(bǔ)實(shí)驗(yàn)的課程設(shè)計(jì)節(jié)奏來講先選技術(shù)路線再做數(shù)據(jù)集再用PyTorch訓(xùn)練和評估最后給出一組避坑經(jīng)驗(yàn)。適合想把深度學(xué)習(xí)畢設(shè)做成完整閉環(huán)、GPU預(yù)算又有限的讀者。2. 先定技術(shù)路線檢測、密度估計(jì)還是多任務(wù)2.1 三種主流做法怎么選計(jì)數(shù)任務(wù)不是檢測任務(wù)的子集目標(biāo)檢測給的是每個(gè)人頭的邊界框把框的數(shù)量數(shù)出來就得到人數(shù)這個(gè)邏輯在稀疏場景沒問題但人流密集時(shí)會(huì)出現(xiàn)兩個(gè)連鎖問題一是NMS會(huì)把兩個(gè)重疊行人的框壓成一個(gè)二是低置信度行人被置信度閾值直接濾掉。地鐵車廂、校門口早高峰這類密集小目標(biāo)場景漏檢率一點(diǎn)就會(huì)被放大到10%以上論文里很難解釋。密度估計(jì)換了一條路不判斷“這里有幾個(gè)人”而是為每個(gè)頭部點(diǎn)生成一個(gè)高斯核讓模型去回歸整個(gè)密度分布最后對密度圖積分得到總?cè)藬?shù)。多任務(wù)方案則把兩條路合并在檢測分支基礎(chǔ)上增加密度圖分支兩個(gè)輸出互相約束精度更高但工程量和訓(xùn)練難度也更高。我一般會(huì)先問自己畢設(shè)的核心創(chuàng)新點(diǎn)到底放在哪里。如果放在應(yīng)用驗(yàn)證檢測方案足夠如果放在方法改進(jìn)密度圖是性價(jià)比最高的起點(diǎn)。另一個(gè)差異在標(biāo)注成本。檢測方案需要畫完整邊界框密度圖方案只需要點(diǎn)標(biāo)注頭部中心。點(diǎn)標(biāo)注的速度大約比框標(biāo)注快三分之一到一半對時(shí)間緊張的畢設(shè)來說這是實(shí)打?qū)嵉膬?yōu)勢。推理階段檢測要做NMS后處理密度圖是純卷積推理更容易壓到視頻流的實(shí)時(shí)幀率。這些差異都可以寫進(jìn)論文的需求分析和方案對比章節(jié)比空泛地寫“深度學(xué)習(xí)技術(shù)具有優(yōu)勢”要有說服力。2.2 密度圖方案的理論閉環(huán)高斯核如何把點(diǎn)標(biāo)注變成回歸目標(biāo)密度圖的核心思路是假設(shè)第i個(gè)行人頭部中心坐標(biāo)為x_i人群分布可以用delta函數(shù)疊加為F(x)Σδ(x-x_i)。這個(gè)離散點(diǎn)序列沒法直接作為回歸目標(biāo)于是把它與二維高斯核做卷積得到密度圖D(x)ΣG_σi(x-x_i)其中G_σi是標(biāo)準(zhǔn)差為σi的二維高斯核。式子里隱含著一個(gè)關(guān)鍵性質(zhì)對全圖積分密度圖的累積和等于總?cè)藬?shù)。也就是說訓(xùn)練階段用MSE讓預(yù)測密度圖逼近真實(shí)密度圖評估階段直接對預(yù)測密度圖求和兩個(gè)環(huán)節(jié)用的是同一個(gè)數(shù)學(xué)信號閉環(huán)非常干凈。人群稀疏時(shí)固定一個(gè)σ就夠了通常取3到5個(gè)像素。密集場景下固定σ會(huì)出問題遠(yuǎn)處人頭在圖像里只有十幾個(gè)像素近處人頭占幾十個(gè)像素同一個(gè)σ必然顧此失彼。這時(shí)候要用MCNN提出的幾何自適應(yīng)高斯核對每個(gè)頭部點(diǎn)求它到最近k個(gè)頭部點(diǎn)的平均距離d_i取σ_iβ*d_iβ一般取0.3k取3或4。這樣遠(yuǎn)處密集人群的核自動(dòng)變小近處稀疏人群的核變大與透視關(guān)系基本匹配。自適應(yīng)核這部分內(nèi)容寫進(jìn)論文的數(shù)據(jù)預(yù)處理小節(jié)時(shí)可以作為一條明確的“方法改進(jìn)點(diǎn)”。2.3 選型對照表與論文寫作的切入角度方案計(jì)數(shù)精度標(biāo)注成本訓(xùn)練難度論文展開空間適用場景目標(biāo)檢測稀疏場景高密集場景下降快需要邊界框標(biāo)注中低偏工程復(fù)現(xiàn)商場出入口、校園道路密度回歸密集場景穩(wěn)定稀疏場景也可用只要頭部點(diǎn)標(biāo)注中高高可在核函數(shù)/網(wǎng)絡(luò)結(jié)構(gòu)上改進(jìn)地鐵站、景區(qū)、集會(huì)檢測密度多任務(wù)綜合最好兩個(gè)輸出互相糾錯(cuò)框點(diǎn)標(biāo)注高高可設(shè)計(jì)聯(lián)合損失需要位置和數(shù)量同時(shí)輸出如果選密度圖方案論文的章節(jié)建議按“密度圖生成方法 → 網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計(jì) → 實(shí)驗(yàn)對比 → 場景驗(yàn)證”來排。這個(gè)結(jié)構(gòu)更接近學(xué)術(shù)論文的敘事方式指導(dǎo)老師也更愿意接受。對比實(shí)驗(yàn)至少要挑1到2個(gè)公開方法在同一個(gè)測試集上重跑而不是直接抄原論文里的數(shù)字因?yàn)閿?shù)據(jù)劃分不同直接抄數(shù)字在答辯時(shí)經(jīng)不起問。你還可以把“檢測方案在密集幀的NMS丟框問題”作為對比實(shí)驗(yàn)的一個(gè)觀察點(diǎn)用幾組數(shù)據(jù)說明為什么密度圖方案更適合人流量檢測。3. 數(shù)據(jù)先行行人數(shù)據(jù)集的采集、標(biāo)注與密度圖生成3.1 公開數(shù)據(jù)集與自建數(shù)據(jù)怎么搭配公開人群計(jì)數(shù)數(shù)據(jù)集里ShanghaiTech Part_A/Part_B是最常用的組合Part_A是密集人群Part_B是稀疏街景兩者交叉驗(yàn)證能看出方法在不同密度下的表現(xiàn)。UCF-QNRF的規(guī)模更大、密度更高適合用來寫泛化性實(shí)驗(yàn)Mall數(shù)據(jù)集是室內(nèi)固定攝像頭視角適合做場景化驗(yàn)證。下載后先確認(rèn)標(biāo)注格式因?yàn)橛械氖荕AT文件有的是JSON有的是XML不統(tǒng)一。自建數(shù)據(jù)建議抽幀標(biāo)注不要每幀都標(biāo)。每隔10幀抽一幀每幀標(biāo)出所有可見頭部中心500到1000個(gè)行人量級就夠用了。自采數(shù)據(jù)的價(jià)值不在數(shù)量而在“場景驗(yàn)證”論文里寫“對自采視頻人工抽樣核對10幀平均誤差在10%以內(nèi)”比單純在公開數(shù)據(jù)集上報(bào)一個(gè)MAE更有落地感。標(biāo)注時(shí)有三條清洗原則人物頭部小于10像素的不標(biāo)遮擋超過80%的不標(biāo)圖像邊界上只有半個(gè)頭且沒有完整輪廓的不標(biāo)。這些原則要寫進(jìn)論文的數(shù)據(jù)處理部分能增加可信度。3.2 用Python把XML標(biāo)注轉(zhuǎn)成密度圖腳本與參數(shù)如果你的標(biāo)注工具輸出的是Pascal VOC格式XML每個(gè)object是person的bndbox而密度估計(jì)需要的是頭部中心點(diǎn)。這里有一個(gè)通用做法取bbox上部大約15%處作為頭部點(diǎn)位置因?yàn)樾腥丝蛑行耐湓谲|干上直接取中心會(huì)把身體部分算進(jìn)頭部引入計(jì)數(shù)偏差。下面的腳本把XML解析成點(diǎn)坐標(biāo)列表再生成密度圖。import os import cv2 import numpy as np import xml.dom.minidom as minidom def parse_xml_heads(xml_path): 解析VOC標(biāo)注XML返回頭部中心點(diǎn)坐標(biāo)列表 [(x, y), ...] dom minidom.parse(xml_path) objs dom.getElementsByTagName(object) points [] for obj in objs: bndbox obj.getElementsByTagName(bndbox)[0] xmin float(bndbox.getElementsByTagName(xmin)[0].firstChild.data) ymin float(bndbox.getElementsByTagName(ymin)[0].firstChild.data) xmax float(bndbox.getElementsByTagName(xmax)[0].firstChild.data) ymax float(bndbox.getElementsByTagName(ymax)[0].firstChild.data) # 頭部中心點(diǎn)取框頂部偏下一點(diǎn)而不是框中心 cx (xmin xmax) / 2.0 cy ymin (ymax - ymin) * 0.15 points.append((cx, cy)) return points def make_density_map(shape, points, sigma4.0): 將點(diǎn)標(biāo)注展開為密度圖返回與圖像形狀一致的float32矩陣 density np.zeros(shape[:2], dtypenp.float32) h, w shape[:2] radius int(sigma * 3) for x, y in points: if not (0 x w and 0 y h): continue x_lo max(0, int(x) - radius) x_hi min(w, int(x) radius 1) y_lo max(0, int(y) - radius) y_hi min(h, int(y) radius 1) xs np.arange(x_lo, x_hi, dtypenp.float32) ys np.arange(y_lo, y_hi, dtypenp.float32) gx np.exp(-(xs - x) ** 2 / (2 * sigma ** 2)) gy np.exp(-(ys - y) ** 2 / (2 * sigma ** 2)) density[y_lo:y_hi, x_lo:x_hi] np.outer(gy, gx) return density邏輯說明腳本把二維高斯核拆成行向量和列向量的外積比逐像素meshgrid更快也更省內(nèi)存。每個(gè)頭部點(diǎn)的高斯值只疊加到一個(gè)局部窗口內(nèi)窗口半徑取3倍sigma因?yàn)楦咚购嗽?倍標(biāo)準(zhǔn)差外貢獻(xiàn)已經(jīng)可以忽略。多個(gè)點(diǎn)距離較近時(shí)密度值自然疊加因此密度圖積分仍然等于人數(shù)。參數(shù)說明sigma4適合攝像頭離人群中等距離的場景俯視攝像頭建議sigma2左右平視近距場景可以取5到6。radius取int(sigma*3)控制局部窗口大小。如果你的標(biāo)注是純頭部點(diǎn)而不是行人框直接把parse_xml_heads換成讀取點(diǎn)坐標(biāo)即可后面流程完全一樣。3.3 增廣必須做同步變換翻轉(zhuǎn)、裁剪、縮放的三條規(guī)則密度圖不是分類標(biāo)簽它是一張數(shù)值分布圖增廣時(shí)最容易出錯(cuò)的是只改圖像不改密度圖。我見過有人只對圖像做隨機(jī)翻轉(zhuǎn)訓(xùn)練時(shí)loss還在下降測試計(jì)數(shù)卻明顯偏差因?yàn)槟P鸵恢痹跀M合一對不一致的目標(biāo)。正確做法是圖像和密度圖永遠(yuǎn)做同一個(gè)幾何變換三條規(guī)則要同時(shí)遵守。第一條隨機(jī)翻轉(zhuǎn)時(shí)圖像鏡像密度圖也要鏡像。第二條隨機(jī)裁剪時(shí)裁同一個(gè)區(qū)域。第三條縮放時(shí)要特別注意積分修正。如果直接對密度圖做resize需要乘一個(gè)面積修正系數(shù)否則積分會(huì)變。下面這段代碼演示了縮放同步def sync_resize(img, density, size): h, w img.shape[:2] new_w, new_h size scale min(new_w / w, new_h / h) img cv2.resize(img, (int(w * scale), int(h * scale))) density cv2.resize(density, (int(w * scale), int(h * scale)), interpolationcv2.INTER_LINEAR) # 密度圖resize后像素?cái)?shù)量變了要乘修正系數(shù)保證積分等于人數(shù) density density / (scale ** 2) return img, density縮放系數(shù)scale是新尺寸與舊尺寸的比值。如果圖像縮小一半scale等于0.5密度圖每個(gè)像素代表的人數(shù)需要增大到原來的4倍代碼里用除以scale的平方來修正。更省事的做法是先把圖像resize到統(tǒng)一尺寸再基于原始點(diǎn)坐標(biāo)和更新后的sigma直接生成密度圖這樣完全繞開積分修正問題。我一般推薦后者邏輯更清晰也不會(huì)在訓(xùn)練階段引入額外誤差。4. 用PyTorch訓(xùn)練一個(gè)人流密度回歸模型網(wǎng)絡(luò)、參數(shù)與評估4.1 一個(gè)顯存友好的MCNN變體網(wǎng)絡(luò)MCNN是經(jīng)典的人群計(jì)數(shù)模型用三列不同卷積核尺寸并行提取特征。完整版包含多次池化和上采樣顯存占用偏高。如果只有4G顯存可以在保留多列結(jié)構(gòu)的前提下做簡化去掉全連接層、減小通道數(shù)、把特征融合改成1x1卷積最后用雙線性上采樣恢復(fù)到原圖尺寸。import torch import torch.nn as nn import torch.nn.functional as F class SimpleMCNN(nn.Module): def __init__(self): super(SimpleMCNN, self).__init__() # 三列不同感受野分別對應(yīng)小頭、肩部、小群體尺度 self.branch1 nn.Sequential( nn.Conv2d(3, 20, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(20, 24, kernel_size3, padding1), nn.ReLU(inplaceTrue) ) self.branch2 nn.Sequential( nn.Conv2d(3, 20, kernel_size5, padding2), nn.ReLU(inplaceTrue), nn.Conv2d(20, 24, kernel_size5, padding2), nn.ReLU(inplaceTrue) ) self.branch3 nn.Sequential( nn.Conv2d(3, 20, kernel_size7, padding3), nn.ReLU(inplaceTrue), nn.Conv2d(20, 24, kernel_size7, padding3), nn.ReLU(inplaceTrue) ) self.merge nn.Sequential( nn.Conv2d(72, 24, kernel_size1), nn.ReLU(inplaceTrue), nn.Conv2d(24, 1, kernel_size1) ) # 將特征圖恢復(fù)到與輸入密度圖一致的尺寸 self.upsample nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) def forward(self, x): b1 self.branch1(x) b2 self.branch2(x) b3 self.branch3(x) feat torch.cat([b1, b2, b3], dim1) out self.merge(feat) out self.upsample(out) return out邏輯說明三個(gè)分支的卷積核大小分別取3、5、7模擬小、中、大三種感受野。branch1適合捕捉單個(gè)人頭輪廓branch3能覆蓋小群體的上下文信息。三個(gè)輸出在通道維拼接后用1x1卷積做融合參數(shù)量比原始MCNN小很多。forward最后的上采樣是為了讓輸出分辨率與輸入一致計(jì)算損失時(shí)不需要再做插值對齊。參數(shù)說明分支里的通道數(shù)直接決定顯存占用量。把20和24改成16和20大約能再省20%顯存代價(jià)是計(jì)數(shù)精度略微下降。如果輸入是512x512這個(gè)模型在4G顯存的顯卡上可以跑batch_size1輸入降到384x384能跑batch_size4。4.2 訓(xùn)練腳本與關(guān)鍵超參數(shù)從環(huán)境配置到出曲線環(huán)境配置是新手的第一個(gè)坎。建議用conda建一個(gè)Python 3.8環(huán)境再按自己電腦的CUDA版本從PyTorch官網(wǎng)復(fù)制對應(yīng)的安裝命令。沒有獨(dú)立顯卡也能跑只是耗時(shí)長一些把圖像resize到512x512、batch_size設(shè)為1即可。數(shù)據(jù)加載器要同時(shí)輸出圖像和密度圖圖像歸一化到0到1之間密度圖保持float32。訓(xùn)練主循環(huán)里優(yōu)化器用Adam學(xué)習(xí)率從1e-4起步損失用MSELoss。這是密度回歸里最穩(wěn)的一組初始配置。如果loss在前幾個(gè)epoch不下降優(yōu)先檢查密度圖是不是有大量全零區(qū)域背景像素會(huì)把loss主導(dǎo)住這時(shí)要給密度圖加一個(gè)小常數(shù)再參與計(jì)算或者檢查預(yù)處理階段是否把密度圖讀丟了。model SimpleMCNN().cuda() criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-4) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor0.5, patience3) for epoch in range(40): model.train() total_loss 0.0 for img, density in train_loader: img, density img.cuda(), density.cuda() pred model(img) loss criterion(pred, density) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() mae, mse evaluate(model, val_loader) scheduler.step(mae) print(fepoch {epoch:02d}, loss {total_loss/len(train_loader):.6f}, fMAE {mae:.2f}, MSE {mse:.2f})參數(shù)說明batch_size建議為1因?yàn)槊芏葓D是稠密預(yù)測顯存隨圖面積增長。想要大batch效果可以用梯度累積每8步做一次optimizer.step等價(jià)于batch_size8的效果。ReduceLROnPlateau會(huì)在驗(yàn)證MAE連續(xù)3輪不降時(shí)把學(xué)習(xí)率乘以0.5這是密度回歸任務(wù)里比較實(shí)用的調(diào)節(jié)方式。如果想在損失函數(shù)上做文章可以加一個(gè)計(jì)數(shù)一致性損失把預(yù)測密度圖積分得到的預(yù)測人數(shù)與真實(shí)人數(shù)做L1損失。代碼上就是count_loss torch.abs(pred.flatten(1).sum(dim1) - density.flatten(1).sum(dim1)).mean() loss criterion(pred, density) 0.01 * count_loss0.01這個(gè)權(quán)重要調(diào)小因?yàn)槊芏葓D的逐像素MSE數(shù)值通常比總?cè)藬?shù)誤差大一到兩個(gè)數(shù)量級。這個(gè)自定義損失可以獨(dú)立成為論文里的一節(jié)答辯時(shí)能講出設(shè)計(jì)動(dòng)機(jī)。4.3 MAE與MSE公式、代碼與論文描述MAE和MSE是人群計(jì)數(shù)論文里最核心的兩個(gè)指標(biāo)。MAE反映平均計(jì)數(shù)偏差MSE反映計(jì)數(shù)誤差的波動(dòng)程度。先算每個(gè)樣本預(yù)測人數(shù)與真實(shí)人數(shù)之差MAE是誤差絕對值的平均MSE是誤差平方均值再開根號。def evaluate(model, val_loader): model.eval() pred_counts, gt_counts [], [] with torch.no_grad(): for img, density in val_loader: img img.cuda() pred model(img) # flatten(1)把每個(gè)樣本展平成 H*W沿dim1求和得到單幀人數(shù) pred_count pred.flatten(1).sum(dim1).cpu().numpy() gt_count density.flatten(1).sum(dim1).numpy() pred_counts.extend(pred_count) gt_counts.extend(gt_count) pred_counts np.array(pred_counts) gt_counts np.array(gt_counts) mae np.mean(np.abs(pred_counts - gt_counts)) mse np.sqrt(np.mean((pred_counts - gt_counts) ** 2)) return mae, mse邏輯說明密度圖的積分等于人數(shù)所以對預(yù)測輸出做flatten(1)后沿通道和空間維度求和直接得到每幀的預(yù)測人數(shù)。評估階段必須用model.eval()并包在torch.no_grad()里否則計(jì)算圖會(huì)占滿顯存跑幾個(gè)batch就OOM。論文結(jié)果表建議按“數(shù)據(jù)集、方法、MAE、MSE”四列展示。要特別注意MAE和MSE的差異如果MAE小但MSE偏大說明大多數(shù)幀誤差小但有少數(shù)密集幀計(jì)數(shù)明顯偏離這時(shí)候可以挑幾幀誤差最大的圖片在論文里做案例分析解釋原因。這個(gè)分析過程本身就是一個(gè)加分項(xiàng)。5. 人流量檢測避坑指南5個(gè)翻車現(xiàn)場與排查思路以下是做這個(gè)題目最容易踩的五個(gè)坑按我遇到頻率從高到低排。每一條都按“現(xiàn)象、原因、解決”展開排查時(shí)可以對照。5.1 密度圖高斯核選錯(cuò)模型學(xué)到的是模糊背景而不是人頭現(xiàn)象訓(xùn)練了幾十輪預(yù)測密度圖看起來是一大片模糊色塊預(yù)測人數(shù)只有真實(shí)值的三成左右。原因所有頭部點(diǎn)共用同一個(gè)固定σ而攝像頭是平視角度近處人頭占幾十像素遠(yuǎn)處人頭只占幾個(gè)像素固定核不可能同時(shí)表示兩種尺度。解決改成幾何自適應(yīng)高斯核先為每個(gè)頭部點(diǎn)計(jì)算k近鄰平均距離再按σ_i0.3*d_i生成密度圖。如果不想一開始就做自適應(yīng)至少把圖像按透視關(guān)系分成上下兩區(qū)分別用兩個(gè)σ值。這條修改在論文里可以寫成“基于局部幾何自適應(yīng)的密度圖生成”有理論依據(jù)也有實(shí)驗(yàn)對比。5.2 增廣不同步訓(xùn)練loss在降驗(yàn)證指標(biāo)卻在漂現(xiàn)象訓(xùn)練損失一路下降驗(yàn)證MAE卻居高不下而且預(yù)測人數(shù)表現(xiàn)出系統(tǒng)性偏高或偏低。原因代碼里只對圖像做了翻轉(zhuǎn)或裁剪密度圖還是變換前的舊目標(biāo)模型被迫在同一個(gè)輸入上擬合兩個(gè)不一致的輸出。解決圖像和密度圖永遠(yuǎn)走同一個(gè)變換入口。我習(xí)慣用一個(gè)固定隨機(jī)種子在同一個(gè)函數(shù)里對兩者依次做翻轉(zhuǎn)和裁剪確保操作一致。具體做法是先用random.seed生成種子再對圖像和密度圖分別調(diào)用cv2.flip和cv2.resize。5.3 圖像resize后sigma沒跟著縮放現(xiàn)象訓(xùn)練集MAE還不錯(cuò)一到測試集就明顯變差預(yù)測密度圖邊緣發(fā)糊。原因?yàn)榱思铀儆?xùn)練把圖像縮到512x512但密度圖用的還是原圖尺寸下的σ4核在縮小后的圖上覆蓋范圍偏大人頭周圍的密度被過度擴(kuò)散。解決先確定輸入尺寸再按縮放比例更新σ??s放系數(shù)scale等于新尺寸除以原尺寸σ_new等于σ_old乘以scale。如果按“先resize后生成密度圖”的流程做這個(gè)坑可以完全繞開。注意多個(gè)尺寸變換時(shí)要保證每個(gè)階段σ都跟著變化否則越到后面偏差越大。5.4 視角漂移訓(xùn)練集和測試集根本不是同一個(gè)場景現(xiàn)象在ShanghaiTech Part_A上訓(xùn)練的模型直接到Part_B上測試MAE漲了一倍。原因Part_A是密集人群Part_B是稀疏街景攝像頭俯仰角和行人像素范圍都不一樣深度學(xué)習(xí)模型對視角非常敏感這是領(lǐng)域偏移問題不是模型bug。解決要么在訓(xùn)練集里混合多視角數(shù)據(jù)做成多源訓(xùn)練要么做目標(biāo)場景微調(diào)。微調(diào)時(shí)用目標(biāo)場景的幾十幀標(biāo)注數(shù)據(jù)把整個(gè)模型以1e-5的學(xué)習(xí)率再訓(xùn)20個(gè)epoch效果通常很明顯。論文里可以把“跨場景泛化測試微調(diào)恢復(fù)”單獨(dú)寫成實(shí)驗(yàn)小節(jié)比只報(bào)一個(gè)數(shù)據(jù)集的結(jié)果更有說服力。5.5 論文創(chuàng)新點(diǎn)寫“我用了YOLO”會(huì)在答辯時(shí)被問住現(xiàn)象答辯老師問“你選YOLO是什么依據(jù)和密度圖方法比優(yōu)勢在哪”回答不上來。原因把工程選型當(dāng)成了方法創(chuàng)新而畢設(shè)論文要求的是方法層面的比較和取舍。解決即使主線是密度回歸也要在論文里放一個(gè)對照實(shí)驗(yàn)用YOLOv5做人數(shù)統(tǒng)計(jì)與密度圖方法在密集幀和稀疏幀上分別對比MAE。分析時(shí)指出YOLO的NMS在密集幀會(huì)壓制相鄰框密度圖方法沒有NMS后處理因此計(jì)數(shù)更穩(wěn)。這樣一來“面向密集場景的密度回歸方案選型與優(yōu)化”就成了你的創(chuàng)新點(diǎn)而不是“我調(diào)用了某個(gè)模型”。6. 給畢設(shè)加分消融實(shí)驗(yàn)與真實(shí)場景驗(yàn)證的收尾技巧6.1 一次消融實(shí)驗(yàn)的設(shè)計(jì)模板消融實(shí)驗(yàn)的目的是證明網(wǎng)絡(luò)結(jié)構(gòu)和數(shù)據(jù)預(yù)處理里的每個(gè)設(shè)計(jì)都有作用。最省事的做法是拿訓(xùn)練好的模型作為基線依次做三個(gè)改動(dòng)把三個(gè)卷積分支去掉一個(gè)、把1x1融合層替換成直接相加、把自適應(yīng)σ換回固定σ。每次只改一個(gè)變量重新訓(xùn)練并記錄MAE和MSE做成一張兩列指標(biāo)的表。配置MAEMSE三分支1x1融合自適應(yīng)σ12.418.9去掉三分支中的branch314.121.31x1融合改為直接相加15.623.5自適應(yīng)σ改回固定σ416.825.1表格里的數(shù)字只是示例要替換成你自己實(shí)驗(yàn)的結(jié)果。消融實(shí)驗(yàn)不用做太多組三到四組足夠說明問題答辯時(shí)也不容易把自己繞暈。每組實(shí)驗(yàn)在論文里配一小段解釋說明改動(dòng)后精度下降是因?yàn)閬G失了哪種尺度信息。6.2 用一段監(jiān)控視頻做端到端驗(yàn)證最后一步建議用一段自采視頻跑端到端驗(yàn)證把每幀人數(shù)輸出成一條曲線和人工抽查的幾幀做對比。這段代碼很短但它能串起整個(gè)系統(tǒng)import cv2 import torch import numpy as np cap cv2.VideoCapture(demo.mp4) writer open(flow_curve.csv, w) with torch.no_grad(): while True: ret, frame cap.read() if not ret: break img cv2.resize(frame, (512, 512)) / 255.0 img torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0).float().cuda() density model(img).squeeze().cpu().numpy() count float(density.sum()) frame_id int(cap.get(cv2.CAP_PROP_POS_FRAMES)) writer.write(f{frame_id},{count:.2f}\n) writer.close() cap.release()這段代碼直接把模型輸出變成時(shí)間序列你可以在論文里畫一張“幀號-人數(shù)”曲線再人工核對其中三幀誤差控制在10%以內(nèi)就能寫進(jìn)實(shí)驗(yàn)結(jié)論??梢暬糠挚梢园衙芏葓D用熱力圖疊加到原圖上選一個(gè)密集幀放對比圖視覺沖擊力比你貼十行表格都強(qiáng)。我自己做這個(gè)題目時(shí)第一次跑通模型后就把所有注意力放在“把loss降得更低”上結(jié)果答辯前才發(fā)現(xiàn)連MAE和MSE的分工都講不清楚。后來養(yǎng)成一個(gè)習(xí)慣任何模型實(shí)驗(yàn)先記錄驗(yàn)證指標(biāo)的變化過程再去看loss曲線而不是只留一張訓(xùn)練截圖。這個(gè)習(xí)慣幫我少走了很多彎路希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取