系統(tǒng)實(shí)戰(zhàn))
人行道和盲道上的障礙物一直是視障人士日常出行里最頭疼的問題。我之前在某公益項(xiàng)目里接觸過相關(guān)需求也調(diào)研過市面上同類方案大多數(shù)要么停留在傳統(tǒng)的圖像差分、邊緣檢測(cè)階段要么直接套通用目標(biāo)檢測(cè)模型不管實(shí)際場(chǎng)景。真正落地時(shí)你會(huì)發(fā)現(xiàn)盲道上的共享單車、人行道中間的石墩、被樹蔭遮了半邊身子的行人單靠常規(guī)檢測(cè)根本壓不住誤檢和漏檢。所以這次基于YOLOv13做一個(gè)人行道和盲道障礙物檢測(cè)系統(tǒng)算是一個(gè)比較務(wù)實(shí)的解法——既用上了主流檢測(cè)框架的成熟能力又把場(chǎng)景針對(duì)性做足了。這個(gè)項(xiàng)目我定位成一套可以直接跑起來的完整方案支持圖像、視頻文件、攝像頭實(shí)時(shí)流三種輸入底層用PyTorch訓(xùn)練和推理純Python源碼模型選用YOLOv13。適合三類人看一是做智慧城市和無障礙設(shè)施相關(guān)課題的同學(xué)二是想快速掌握YOLO系列從訓(xùn)練到部署全流程的開發(fā)者三是有實(shí)際業(yè)務(wù)需求、需要評(píng)估這種方案可行性的產(chǎn)品或者項(xiàng)目經(jīng)理。先把我做完這個(gè)項(xiàng)目后最有體會(huì)的一件事放前面說這個(gè)系統(tǒng)的核心難點(diǎn)不在模型本身而在數(shù)據(jù)。YOLOv13作為檢測(cè)器能力上限很高但如果你喂給它的數(shù)據(jù)是通用場(chǎng)景的那到了人行道這種特定環(huán)境照樣拉胯。后面我會(huì)把你需要關(guān)注的每一個(gè)環(huán)節(jié)拆開講包括數(shù)據(jù)怎么采、標(biāo)注怎么定、模型怎么調(diào)、三種輸入怎么落地最后還會(huì)把我踩過的一些坑整理成速查表。1. 方案選型與整體設(shè)計(jì)思路1.1 為什么選擇YOLOv13作為檢測(cè)核心先聊一下為什么這個(gè)項(xiàng)目要用YOLOv13而不是其他檢測(cè)方案。YOLO系列能走到今天核心優(yōu)勢(shì)就兩條速度快、精度均衡。到了v13這個(gè)版本它在小目標(biāo)檢測(cè)、遮擋場(chǎng)景下的表現(xiàn)又上了一個(gè)臺(tái)階這對(duì)人行道場(chǎng)景特別關(guān)鍵。人行道和盲道上的障礙物有個(gè)顯著特點(diǎn)——尺度和形態(tài)差異極大。共享單車是長(zhǎng)條狀石墩是矮圓柱垃圾桶是豎立長(zhǎng)方體行人可能是全身也可能只露半邊。早期我做調(diào)研時(shí)拿Faster R-CNN試過兩階段的檢測(cè)器精度確實(shí)不錯(cuò)但實(shí)時(shí)性太差攝像頭模式下FPS扛不住。后來也試過SSD速度上去了小目標(biāo)召回率又掉得厲害。YOLOv13在這兩者之間找平衡找得比較好尤其是它的多尺度特征融合機(jī)制能讓網(wǎng)絡(luò)同時(shí)關(guān)注到大目標(biāo)的整體輪廓和小目標(biāo)的局部細(xì)節(jié)。另外一點(diǎn)YOLOv13的生態(tài)很完整。官方給了預(yù)訓(xùn)練權(quán)重訓(xùn)練腳本、導(dǎo)出腳本、推理腳本一條龍都是齊的用它做二次開發(fā)能省掉大量工程時(shí)間。做實(shí)際項(xiàng)目最怕的不是算法本身難而是工程鏈路斷節(jié)這邊模型訓(xùn)完了那邊推理代碼還得自己從零寫YOLO系列的成熟生態(tài)很好地避開了這個(gè)坑。還有一點(diǎn)要提醒YOLOv13雖然新但網(wǎng)絡(luò)結(jié)構(gòu)沒有做那種激進(jìn)的改動(dòng)很多在YOLOv5、v8上積累的經(jīng)驗(yàn)比如錨框設(shè)置、數(shù)據(jù)增強(qiáng)策略都能平滑遷移過來網(wǎng)上資料也多遇到問題好查。1.2 系統(tǒng)整體架構(gòu)與模塊劃分這個(gè)系統(tǒng)的架構(gòu)我按三個(gè)層次來組織數(shù)據(jù)輸入層、檢測(cè)推理層、業(yè)務(wù)應(yīng)用層。數(shù)據(jù)輸入層主要解決“畫面從哪里來”的問題。圖像模式支持常見的jpg、png、bmp格式視頻模式支持mp4、avi、mov等格式用OpenCV的VideoCapture做讀取攝像頭模式走RTSP或者USB攝像頭直采。這一層的關(guān)鍵是做統(tǒng)一封裝不管輸入源是什么到檢測(cè)層時(shí)都是統(tǒng)一的幀格式這樣后面處理邏輯不用重復(fù)寫。檢測(cè)推理層是整個(gè)系統(tǒng)的心臟包含以下幾個(gè)子模塊預(yù)處理模塊對(duì)輸入幀做尺寸縮放、歸一化把圖像resize到模型輸入尺寸我這邊用的是640x640同時(shí)保持寬高比防止目標(biāo)變形。推理模塊加載訓(xùn)練好的權(quán)重執(zhí)行前向傳播輸出detection結(jié)果。后處理模塊對(duì)模型輸出做NMS非極大值抑制過濾剔除重復(fù)框同時(shí)做坐標(biāo)映射把縮放后的坐標(biāo)系還原到原始圖像坐標(biāo)系。業(yè)務(wù)應(yīng)用層就是給不同場(chǎng)景提供接口。圖像模式直接返回帶標(biāo)注框的輸出圖和檢測(cè)結(jié)果列表視頻模式逐幀檢測(cè)并疊加結(jié)果支持實(shí)時(shí)顯示和保存攝像頭模式要保證低延遲檢測(cè)幀率不能拖后腿。另外我還加了一個(gè)結(jié)果導(dǎo)出功能可以把檢測(cè)信息存成JSON或者CSV方便后續(xù)做數(shù)據(jù)統(tǒng)計(jì)和分析挖掘。這三個(gè)層次各干各的活層與層之間通過標(biāo)準(zhǔn)接口對(duì)接。做這套設(shè)計(jì)的核心思路就是可替換性——以后如果想換更強(qiáng)的骨干網(wǎng)絡(luò)或者換更快的推理后端比如TensorRT只需要替換對(duì)應(yīng)模塊其他部分不用動(dòng)。1.3 障礙物類別怎么定這是整個(gè)項(xiàng)目里最容易出岔子的地方必須單獨(dú)拿出來講。很多做類似項(xiàng)目的人喜歡一上來就定義十幾個(gè)類別恨不得把路邊所有東西都框出來。我試過這條路結(jié)果非常難受。類別太多會(huì)導(dǎo)致兩個(gè)問題一是標(biāo)注工作量爆炸二是一些“良性的”物體和“真正的障礙物”之間邊界模糊模型很容易被繞暈。這個(gè)系統(tǒng)里我最終定了這樣幾類類別說明典型場(chǎng)景非機(jī)動(dòng)車主要是共享單車、電動(dòng)車、自行車停放在盲道上或者人行道中間石墩/水泥墩固定障礙物矮圓柱或方柱形態(tài)路口隔離、小區(qū)門口施工設(shè)施錐桶、圍擋、警示牌道路施工臨時(shí)占用行人包括站姿、坐姿、行走狀態(tài)行人占用盲道或擁擠影響通行垃圾箱垃圾桶、垃圾堆放點(diǎn)擺放在人行道邊緣或盲道旁邊為什么沒把“電線桿”“路燈桿”這類固定設(shè)施算進(jìn)來因?yàn)樗鼈兪庆o止且常設(shè)的對(duì)熟悉路段的人來說是已知障礙真正危險(xiǎn)的是移動(dòng)的或者突發(fā)的東西。而且固定設(shè)施在數(shù)據(jù)里出現(xiàn)頻率極高會(huì)讓正負(fù)樣本失衡反而干擾模型對(duì)真正障礙物的判斷。還有一類需要單獨(dú)考慮的是“機(jī)動(dòng)車違?!薄H绻诵械辣容^寬經(jīng)常會(huì)有轎車開上來停著這確實(shí)是障礙物。但機(jī)動(dòng)車和行人的尺度差太大放到同一個(gè)檢測(cè)器里反而容易互相干擾。我的做法是如果有停車場(chǎng)景需求單獨(dú)訓(xùn)練一個(gè)專門檢測(cè)機(jī)動(dòng)車的分支否則就歸到“非機(jī)動(dòng)車”以外不單獨(dú)設(shè)類。這個(gè)取舍要根據(jù)項(xiàng)目實(shí)際需求來定。2. 數(shù)據(jù)準(zhǔn)備與標(biāo)注細(xì)節(jié)2.1 數(shù)據(jù)采集策略自己拍還是網(wǎng)上找數(shù)據(jù)是這類項(xiàng)目成敗的第一關(guān)先明確一個(gè)態(tài)度直接用通用目標(biāo)檢測(cè)數(shù)據(jù)集的模型到人行道場(chǎng)景上效果不會(huì)好。通用數(shù)據(jù)集里有車、有人、有貓有狗但“停在盲道中間的共享單車”“人行道上的錐桶”這種組合很少而且視角、光照、周圍環(huán)境都和真實(shí)場(chǎng)景差很遠(yuǎn)。我建議至少一半的數(shù)據(jù)要依賴現(xiàn)場(chǎng)采集。找個(gè)城市里人流量中等的人行道和盲道路段用手機(jī)或者運(yùn)動(dòng)相機(jī)固定在三腳架上按不同時(shí)間段采集。早晨和傍晚光線比較斜中午頂光強(qiáng)陰影重晚上還有路燈和霓虹燈的混合光源——這些光照條件都要覆蓋到模型才能真正扛住全天候運(yùn)行。網(wǎng)上數(shù)據(jù)也不是不能用。大型公開數(shù)據(jù)集中的人、車類目可以作為基礎(chǔ)補(bǔ)充數(shù)量多、質(zhì)量高能幫模型建立良好的基礎(chǔ)特征表達(dá)。我的做法是公開數(shù)據(jù)集提供的人和車樣本加上自采數(shù)據(jù)提供的“障礙物組合”樣本比例大概控制在4:6左右。這里我得提醒一個(gè)容易踩的坑同一個(gè)路段的畫面不要采集太多。如果某一條街反復(fù)出現(xiàn)在訓(xùn)練集里模型會(huì)產(chǎn)生“場(chǎng)景記憶”——它記住了那個(gè)特定背景而不是學(xué)會(huì)識(shí)別障礙物本身。測(cè)試的時(shí)候換一條新街精度立刻掉下來。我在項(xiàng)目中特意做了一個(gè)場(chǎng)景去重相鄰10幀內(nèi)只保留1幀保證訓(xùn)練集的場(chǎng)景多樣性。2.2 標(biāo)注規(guī)范的制定標(biāo)注質(zhì)量直接影響模型上限。YOLO系列用的是txt格式的標(biāo)注文件每行記錄一個(gè)目標(biāo)類別id 中心點(diǎn)x 中心點(diǎn)y 寬度w 高度h坐標(biāo)都是歸一化到0-1之間的相對(duì)值。但“怎么框”比“用什么格式存”更重要。做這個(gè)項(xiàng)目我定了幾條標(biāo)注鐵律遮擋目標(biāo)只標(biāo)可見部分。如果一輛共享單車被樹叢擋了半邊標(biāo)被擋住的那部分沒有意義只會(huì)讓模型學(xué)糊涂。只標(biāo)能看到的部分完整邊界讓模型自己去推測(cè)。小目標(biāo)必須標(biāo)不能漏。遠(yuǎn)距離的行人雖然很小但恰恰是這類系統(tǒng)需要警惕的目標(biāo)。漏標(biāo)一個(gè)小目標(biāo)等于告訴模型“這種小東西不重要”推理時(shí)它就真的不管了。邊界清晰的算模糊的寧可不標(biāo)。如果某幀里一個(gè)目標(biāo)模糊到連人都難分辨是不是障礙物就不要標(biāo)。標(biāo)了只會(huì)給模型引入噪聲。標(biāo)注工具有很多選擇我常用的是LabelImg和X-AnyLabeling。前者輕量穩(wěn)定適合批量標(biāo)注后者有半自動(dòng)輔助功能能用分割模型預(yù)打標(biāo)簽再人工修正能省不少力氣。團(tuán)隊(duì)協(xié)作的情況下可以用CVAT支持多人同時(shí)標(biāo)注和在線審核。標(biāo)注完成后一定要做復(fù)查。找一個(gè)不參與標(biāo)注的人隨機(jī)抽20%的標(biāo)注文件對(duì)照原圖和標(biāo)注框檢查。如果發(fā)現(xiàn)同一個(gè)目標(biāo)在不同幀里類別標(biāo)注不一致或者框明顯偏離目標(biāo)實(shí)體必須打回重標(biāo)。數(shù)據(jù)清洗這一步花的時(shí)間會(huì)在訓(xùn)練和調(diào)優(yōu)階段十倍省回來。2.3 數(shù)據(jù)增強(qiáng)策略數(shù)據(jù)增強(qiáng)是解決樣本不夠、場(chǎng)景覆蓋不全問題的關(guān)鍵手段也是訓(xùn)練階段最值得花時(shí)間調(diào)的部分。基礎(chǔ)的增強(qiáng)我用了這些隨機(jī)翻轉(zhuǎn)水平翻轉(zhuǎn)概率0.5。注意垂直翻轉(zhuǎn)不要用人行道場(chǎng)景里“上下顛倒”是不正常的。隨機(jī)旋轉(zhuǎn)角度范圍正負(fù)15度。人行道上的障礙物基本都垂直于地面旋轉(zhuǎn)幅度大了會(huì)讓模型學(xué)到錯(cuò)誤的空間關(guān)系。HSV色域變換亮度、飽和度、色調(diào)各做隨機(jī)擾動(dòng)提升對(duì)不同光照環(huán)境的魯棒性??s放變換隨機(jī)縮放0.5到1.5倍模擬目標(biāo)的不同距離。除了上面這些我還用了兩個(gè)對(duì)這個(gè)場(chǎng)景特別有效的增強(qiáng)手段隨機(jī)遮擋模擬。人行道場(chǎng)景里障礙物經(jīng)常被樹干、行人、路燈柱擋住訓(xùn)練時(shí)在圖像上隨機(jī)加一些黑色矩形塊相當(dāng)于人為制造遮擋強(qiáng)迫模型學(xué)會(huì)從可見部分去推斷完整目標(biāo)。這個(gè)增強(qiáng)在后期明顯提升了模型對(duì)遮擋目標(biāo)的召回率。Mosaic增強(qiáng)。把多張圖拼成一張訓(xùn)練等于變相增大batch size同時(shí)增加了不同場(chǎng)景的組合可能。YOLO系列的實(shí)現(xiàn)里已經(jīng)內(nèi)置了這個(gè)功能直接開啟就行。我實(shí)際用下來的經(jīng)驗(yàn)是增強(qiáng)強(qiáng)度要控制在一個(gè)合理范圍太強(qiáng)反而傷精度。調(diào)整策略也很簡(jiǎn)單訓(xùn)練幾個(gè)epoch后看驗(yàn)證集的表現(xiàn)如果loss能降但驗(yàn)證集精度不上來多半就是增強(qiáng)過度了這時(shí)候把翻轉(zhuǎn)概率降一點(diǎn)、遮擋的矩形塊改小一點(diǎn)就行。3. 模型訓(xùn)練與調(diào)優(yōu)實(shí)踐3.1 訓(xùn)練環(huán)境的準(zhǔn)備和參數(shù)配置環(huán)境這塊沒什么玄學(xué)直接按標(biāo)準(zhǔn)配置來。Python版本我用的是3.10PyTorch用2.x版本CUDA環(huán)境對(duì)應(yīng)裝好。如果只有CPU機(jī)器也能跑就是訓(xùn)練速度會(huì)慢到讓人懷疑人生建議至少有一張顯存8GB以上的顯卡。關(guān)鍵依賴就這幾個(gè)torch、torchvision、opencv-python、numpy還有一些配套庫(kù)比如tqdm顯示進(jìn)度條tensorboard或者wandb看訓(xùn)練曲線。訓(xùn)練參數(shù)這一塊我結(jié)合了YOLOv8時(shí)代積累的經(jīng)驗(yàn)在這個(gè)場(chǎng)景上反復(fù)試出來的配置是參數(shù)名設(shè)置值說明輸入尺寸640x640精度和速度的平衡點(diǎn)不做太大Batch Size16顯存夠就往上加影響收斂穩(wěn)定性Epoch150到后面精度提升很慢基本是微調(diào)初始學(xué)習(xí)率0.01用余弦退火方式逐步下降權(quán)重衰減0.0005防過擬合的常規(guī)配置Warmup3個(gè)epoch預(yù)熱階段避免早期loss震蕩優(yōu)化器SGD這個(gè)項(xiàng)目SGD比AdamW效果好泛化更強(qiáng)有兩點(diǎn)我想多說一下為什么用SGD不用AdamW目標(biāo)檢測(cè)任務(wù)里SGD配合合理的warmup策略最終收斂到的解往往泛化性能更好。Adam系列的優(yōu)點(diǎn)是收斂快但訓(xùn)出來的模型在驗(yàn)證集上的表現(xiàn)通常略遜于SGD。這個(gè)不是玄學(xué)很多檢測(cè)項(xiàng)目都有同樣結(jié)論。不過如果你時(shí)間緊張用AdamW起步更快后面再換SGD微調(diào)也可以。預(yù)訓(xùn)練權(quán)重怎么選我用了COCO預(yù)訓(xùn)練權(quán)重作為初始點(diǎn)。這樣做有兩個(gè)好處一是網(wǎng)絡(luò)已經(jīng)學(xué)到了通用的特征表達(dá)不需要從頭訓(xùn)練主干網(wǎng)絡(luò)的底層特征二是收斂速度快很多能省下大幾十個(gè)epoch的時(shí)間。做遷移學(xué)習(xí)就是這樣能用別人訓(xùn)好的成熟特征就盡量用別從零折騰。3.2 訓(xùn)練過程中的關(guān)鍵監(jiān)控指標(biāo)訓(xùn)練不是把命令扔進(jìn)去等著完事就行中間環(huán)節(jié)必須要盯著看。重點(diǎn)看幾個(gè)指標(biāo)Loss曲線。YOLO的訓(xùn)練loss由三部分組成邊界框回歸loss、目標(biāo)置信度loss、分類loss。正常情況這三條曲線都應(yīng)該穩(wěn)步下降后期趨于平緩。如果loss在某個(gè)點(diǎn)突然跳高通常是學(xué)習(xí)率太大或者數(shù)據(jù)里有臟樣本這時(shí)候降學(xué)習(xí)率或者清洗數(shù)據(jù)。mAP曲線。mAP是目標(biāo)檢測(cè)領(lǐng)域最通用的精度指標(biāo)通俗點(diǎn)講就是“檢測(cè)得到底準(zhǔn)不準(zhǔn)”。在驗(yàn)證集上每隔一定epoch做一次評(píng)估畫出mAP0.5和mAP0.5:0.95的曲線。前者看重“框大概準(zhǔn)就行”后者對(duì)框的精確度要求更高。實(shí)際業(yè)務(wù)中我更關(guān)注mAP0.5因?yàn)檎系K物檢測(cè)主要看位置夠不夠準(zhǔn)供決策參考。Precision和Recall。這對(duì)指標(biāo)在這個(gè)場(chǎng)景里需要特別權(quán)衡。Precision高表示“框出來的基本都是真障礙物”誤報(bào)少Recall高表示“真障礙物基本都被框出來了”漏報(bào)少。對(duì)無障礙檢測(cè)系統(tǒng)來說漏報(bào)比誤報(bào)更危險(xiǎn)。漏報(bào)一個(gè)盲道上的共享單車可能直接導(dǎo)致視障人士撞上去誤報(bào)一個(gè)影子最多是讓系統(tǒng)多提醒一次。所以我在調(diào)參時(shí)會(huì)優(yōu)先保證Recall再把閾值往上提來壓制誤報(bào)。3.3 精度調(diào)優(yōu)的實(shí)操心得訓(xùn)練完第一輪以后通常精度不達(dá)標(biāo)是常態(tài)調(diào)優(yōu)才是重頭戲。我把自己在這個(gè)項(xiàng)目里用過的幾招分享出來先分析錯(cuò)檢再看數(shù)據(jù)。這是調(diào)優(yōu)的大方向。把所有預(yù)測(cè)錯(cuò)的樣本導(dǎo)出來看一遍漏檢的到底是因?yàn)槟繕?biāo)太小、太模糊還是因?yàn)楸徽趽跽`檢的是把什么當(dāng)成障礙物了看到一個(gè)規(guī)律馬上回?cái)?shù)據(jù)層去解決。比如我發(fā)現(xiàn)石墩常被漏檢就去翻數(shù)據(jù)結(jié)果是石墩樣本太少且大多在遠(yuǎn)處不清晰。補(bǔ)采一批近處的石墩數(shù)據(jù)問題直接解決大半。針對(duì)小目標(biāo)做策略調(diào)整。人行道場(chǎng)景里小目標(biāo)是真多——遠(yuǎn)處的行人、路盡頭的錐桶。如果驗(yàn)證集結(jié)果顯示小目標(biāo)召回率偏低有幾個(gè)手段可以試增加輸入分辨率到768甚至1024對(duì)小目標(biāo)有直接幫助代價(jià)是速度變慢調(diào)整anchor設(shè)計(jì)增加小目標(biāo)的Mosaic剪貼次數(shù)。用好測(cè)試時(shí)增強(qiáng)TTA。推理時(shí)候?qū)ν粡垐D做翻轉(zhuǎn)、縮放、多尺度預(yù)測(cè)最后把結(jié)果合并。TTA能穩(wěn)定提升1-2個(gè)點(diǎn)的mAP代價(jià)是推理時(shí)間翻幾倍。實(shí)時(shí)檢測(cè)場(chǎng)景下不推薦但圖像模式和圖集批量處理時(shí)非常香。最后還有一招模型集成。我訓(xùn)了不止一個(gè)模型用不同的隨機(jī)種子和增強(qiáng)策略跑出了兩個(gè)效果相當(dāng)?shù)臋?quán)重推理時(shí)把它們的結(jié)果做加權(quán)融合。這個(gè)方式在圖像模式下能再提升一點(diǎn)精度但攝像頭實(shí)時(shí)檢測(cè)的幀率會(huì)受影響所以一般只在關(guān)鍵場(chǎng)景用。4. 推理代碼實(shí)現(xiàn)與三種檢測(cè)模式落地4.1 圖像檢測(cè)流程實(shí)現(xiàn)當(dāng)模型訓(xùn)練完、精度到了能用的水平接下來就要進(jìn)入推理代碼的編寫階段。圖像檢測(cè)邏輯不算復(fù)雜核心流程是讀圖 → 預(yù)處理 → 推理 → 后處理 → 畫框保存。關(guān)鍵代碼大致是這樣的import cv2 import torch import numpy as np def detect_image(model, image_path, conf_thres0.5, iou_thres0.45): 單張圖像檢測(cè)函數(shù) # 讀取圖像 image cv2.imread(image_path) if image is None: raise ValueError(f無法讀取圖像: {image_path}) # 預(yù)處理BGR轉(zhuǎn)RGB、歸一化、縮放并保持寬高比 img_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) height, width image.shape[:2] # 計(jì)算縮放比例640為模型輸入尺寸 scale min(640 / width, 640 / height) new_w int(width * scale) new_h int(height * scale) # 等比縮放并填充到640x640防止目標(biāo)變形 resized cv2.resize(img_rgb, (new_w, new_h)) canvas np.full((640, 640, 3), 114, dtypenp.uint8) offset_x (640 - new_w) // 2 offset_y (640 - new_h) // 2 canvas[offset_y:offset_ynew_h, offset_x:offset_xnew_w] resized # 轉(zhuǎn)為PyTorch張量并歸一化 input_tensor torch.from_numpy(canvas.transpose(2, 0, 1)).float() input_tensor input_tensor / 255.0 input_tensor input_tensor.unsqueeze(0) # 模型推理 with torch.no_grad(): preds model(input_tensor)[0] # 后處理把預(yù)測(cè)結(jié)果還原到原圖坐標(biāo)系 # 過濾低置信度目標(biāo) preds preds[preds[:, 4] conf_thres] if preds.shape[0] 0: return image, [] # 坐標(biāo)還原先把填充縮放去掉 preds[:, :4] - torch.tensor([offset_x, offset_y, offset_x, offset_y]) preds[:, :4] / scale # NMS去除重復(fù)框 boxes preds[:, :4].cpu().numpy() scores preds[:, 4].cpu().numpy() classes preds[:, 5].cpu().numpy().astype(int) indices cv2.dnn.NMSBoxes( boxes.tolist(), scores.tolist(), conf_thres, iou_thres ) # 這里用cv2.dnn.NMSBoxes做NMS注意返回格式在不同OpenCV版本有差異 ...代碼里有幾個(gè)細(xì)節(jié)值得注意。預(yù)處理為什么先等比縮放再填充如果直接把任意尺寸的圖拉成640x640目標(biāo)會(huì)被橫向或縱向拉伸變形檢測(cè)精度會(huì)明顯掉。等比縮放加灰色填充的好處是保持原始畫面比例只在兩側(cè)或上下補(bǔ)灰邊。推理完再把灰邊裁剪掉、坐標(biāo)映射回原圖就行。NMS用誰實(shí)現(xiàn)OpenCV自帶的cv2.dnn.NMSBoxes直接調(diào)用就行不需要自己寫。這里有個(gè)小坑OpenCV不同版本里這個(gè)函數(shù)的返回格式不一樣老版本返回的是list新版本返回的是多維數(shù)組。寫代碼時(shí)要兼容這兩種情況否則換環(huán)境就報(bào)錯(cuò)。4.2 視頻文件檢測(cè)的實(shí)現(xiàn)要點(diǎn)圖像檢測(cè)擴(kuò)展成視頻檢測(cè)核心區(qū)別在于按幀讀取和處理。但如果你以為只是把一個(gè)for循環(huán)套上去就完事那后面大概率會(huì)出問題。視頻幀率、目標(biāo)是否快速移動(dòng)、畫面是否抖動(dòng)這些都會(huì)影響最終檢測(cè)效果。我的視頻檢測(cè)實(shí)現(xiàn)思路是這樣的def detect_video(model, video_path, output_pathNone, showTrue): 視頻文件檢測(cè)函數(shù) cap cv2.VideoCapture(video_path) # 讀取視頻基本信息 fps cap.get(cv2.CAP_PROP_FPS) frame_width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) frame_height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) total_frames int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 初始化視頻寫入器 writer None if output_path: fourcc cv2.VideoWriter_fourcc(*mp4v) writer cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height)) frame_count 0 while True: ret, frame cap.read() if not ret: break # 對(duì)當(dāng)前幀做檢測(cè) detected_frame, detections detect_image(model, frame) # 如果開啟保存寫入結(jié)果幀 if writer: writer.write(detected_frame) # 顯示 if show: cv2.imshow(Detection, detected_frame) if cv2.waitKey(1) 0xFF ord(q): break frame_count 1 if frame_count % 100 0: print(f[INFO] 已處理 {frame_count}/{total_frames} 幀) cap.release() if writer: writer.release() cv2.destroyAllWindows()這里面的設(shè)計(jì)邏輯有這么幾點(diǎn)當(dāng)畫面里的目標(biāo)快速移動(dòng)時(shí)視頻相鄰幀之間的目標(biāo)位置跳躍比較大偶爾會(huì)有一兩個(gè)中間幀檢測(cè)不到。這個(gè)不用太慌后續(xù)對(duì)多幀檢測(cè)結(jié)果做一個(gè)時(shí)間維度的平滑處理就能緩解。比如維護(hù)一個(gè)短窗口如果某幀沒檢測(cè)到但前后幀都檢測(cè)到了同一位置的目標(biāo)中間幀就補(bǔ)一個(gè)。視頻檢測(cè)的性能也不能忽略。逐幀檢測(cè)的耗時(shí)就是單幀耗時(shí)的倍數(shù)所以是否跳幀要取決于業(yè)務(wù)需求。如果目標(biāo)是共享單車那種靜止物隔幾幀檢測(cè)一次完全夠用如果還要兼顧行人這種動(dòng)態(tài)目標(biāo)跳幀太多容易漏。如果視頻是1080P分辨率逐幀做640x640推理其實(shí)利用率不高。我實(shí)測(cè)下來可以先對(duì)視頻做一次縮放預(yù)處理800寬度以內(nèi)效果幾乎不受影響速度能快不少。這個(gè)技巧在攝像頭場(chǎng)景里更加實(shí)用。4.3 攝像頭實(shí)時(shí)檢測(cè)與性能優(yōu)化攝像頭實(shí)時(shí)檢測(cè)是這個(gè)系統(tǒng)最有價(jià)值的模式也是技術(shù)上最考驗(yàn)功力的地方。實(shí)時(shí)的意思是畫面每來一幀就要處理完一幀不能出現(xiàn)畫面越來越卡的積壓情況。攝像頭采集有兩種常見方式USB免驅(qū)攝像頭直接本地采集或者用網(wǎng)絡(luò)攝像頭走RTSP流。本地?cái)z像頭用OpenCV的VideoCapture(0)就能打開RTSP流需要把地址傳給VideoCapture。注意RTSP流是網(wǎng)絡(luò)傳輸有延遲波動(dòng)要做好重連和緩沖處理。實(shí)時(shí)檢測(cè)的優(yōu)化核心就一個(gè)字——快。我做了這么幾件提速的事輸入尺寸縮小。實(shí)時(shí)場(chǎng)景下目標(biāo)通常出現(xiàn)在近距離用416x416作為輸入分辨率速度幾乎翻倍精度損失在可接受范圍內(nèi)。這個(gè)不是拍腦袋定的我對(duì)比過416和640兩個(gè)分辨率在自建驗(yàn)證集上的效果誤檢漏檢率差距在2%以內(nèi)但幀率從30FPS漲到50FPS。預(yù)處理并行。攝像頭讀幀和模型推理是兩件獨(dú)立的事可以用一個(gè)獨(dú)立線程讀幀、主線程做推理這樣讀幀耗時(shí)不會(huì)阻塞推理。實(shí)現(xiàn)上用Python標(biāo)準(zhǔn)庫(kù)的threading加一個(gè)隊(duì)列就能搞定。推理后端加速。如果對(duì)延遲要求極高可以考慮用TensorRT或者OpenVINO做推理加速。這一步需要把PyTorch模型先導(dǎo)出為對(duì)應(yīng)的格式批量轉(zhuǎn)換并不復(fù)雜。實(shí)測(cè)下來TensorRT在支持的顯卡上能做到PyTorch原生推理3倍左右的速度提升。不過這部分改造工作量要看有沒有必要普通項(xiàng)目用PyTorch直接上也能跑。攝像頭模式還做了目標(biāo)跟蹤聯(lián)動(dòng)。只用檢測(cè)器的話每一幀都是獨(dú)立的目標(biāo)快速移動(dòng)時(shí)框會(huì)一直跳。聯(lián)合一個(gè)輕量跟蹤算法比如ByteTrack或DeepSORT讓同一目標(biāo)在時(shí)間軸上保持穩(wěn)定的ID視覺體驗(yàn)會(huì)好很多。具體做法是檢測(cè)器給出當(dāng)前幀的目標(biāo)位置跟蹤器負(fù)責(zé)跨幀關(guān)聯(lián)可以說這是實(shí)時(shí)模塊里體驗(yàn)提升最大的一步。5. 常見問題與排查技巧實(shí)錄5.1 高頻問題速查表我把這個(gè)項(xiàng)目從開發(fā)到落地過程中遇到的高頻問題整理成了一張速查表每個(gè)問題都給出了定位路徑和解決辦法問題現(xiàn)象可能原因排查方法解決方案訓(xùn)練時(shí)Loss不降學(xué)習(xí)率過大或過小看前20個(gè)iter的loss變化趨勢(shì)調(diào)整學(xué)習(xí)率warmup階段重新驗(yàn)證小目標(biāo)大量漏檢輸入分辨率偏低或小目標(biāo)樣本不足觀察驗(yàn)證集按尺寸分組的召回率提高輸入分辨率補(bǔ)充小目標(biāo)數(shù)據(jù)夜間檢測(cè)效果差夜間數(shù)據(jù)占比太少檢查訓(xùn)練集中光照條件分布補(bǔ)充夜間拍攝數(shù)據(jù)做亮度增強(qiáng)攝像頭畫面卡頓推理耗時(shí)超過幀間隔打印單幀推理耗時(shí)縮小輸入尺寸、跳幀、跟蹤輔助同一目標(biāo)重復(fù)框NMS閾值設(shè)置不合適檢查后處理輸出框數(shù)量調(diào)低IoU閾值誤把影子當(dāng)障礙物訓(xùn)練數(shù)據(jù)缺少這種負(fù)樣本收集誤報(bào)樣本核查補(bǔ)充“非障礙物”負(fù)樣本做難例挖掘換了電腦報(bào)CUDA錯(cuò)誤環(huán)境版本不匹配檢查torch與CUDA版本兼容性卸載重裝對(duì)應(yīng)版本或者CPU模式跑通自檢這些問題里**“誤把影子當(dāng)障礙物”**是我印象最深的一個(gè)得單獨(dú)展開聊。某個(gè)陰天下午測(cè)試模型一直把樹影和人行道上欄桿影子的邊緣當(dāng)成“石墩”來報(bào)。打開數(shù)據(jù)一看發(fā)現(xiàn)自采數(shù)據(jù)里那些晴天強(qiáng)光照條件下的深色陰影被模型學(xué)成了“深色塊狀物≈障礙物”。這個(gè)問題靠常規(guī)增強(qiáng)很難解決最后我的做法是收集一批有陰影的圖像把陰影區(qū)域的標(biāo)注框刪除讓模型看到“這個(gè)區(qū)域雖然有深色邊緣但沒標(biāo)注就不是目標(biāo)”作為負(fù)樣本壓回國(guó)。后來又加了一批陰天和雨天的圖像這個(gè)問題才基本解決。5.2 關(guān)于誤報(bào)和漏報(bào)的深度權(quán)衡在無障礙檢測(cè)這種偏安全敏感的場(chǎng)景里誤報(bào)和漏報(bào)的權(quán)衡不能只看技術(shù)指標(biāo)還得考慮實(shí)際使用者感受。誤報(bào)的情況如果太頻繁使用者會(huì)進(jìn)入“狼來了”狀態(tài)對(duì)提醒變得麻木真正遇到障礙物時(shí)反而忽視提醒。這個(gè)不是技術(shù)能解決的問題是產(chǎn)品層面的心理學(xué)問題。我能做的技術(shù)層面調(diào)整是通過置信度閾值來控制誤報(bào)率到可接受范圍。漏報(bào)則完全不能忍。一個(gè)盲道上的障礙物沒檢測(cè)出來可能導(dǎo)致使用者的直接碰撞。所以我在做閾值設(shè)定時(shí)會(huì)先評(píng)估如果把閾值從0.5降到0.4召回率能提升多少誤報(bào)增加多少在這個(gè)項(xiàng)目里我對(duì)閾值做了AB對(duì)比最終定在0.42為一個(gè)曲線上的平衡點(diǎn)。這里還要補(bǔ)充一個(gè)后期很管用的手段——難例挖掘。把每次運(yùn)行時(shí)的檢測(cè)結(jié)果保存下來定期人工看一遍凡是誤報(bào)和漏報(bào)的樣本都收集起來加入訓(xùn)練集重新訓(xùn)練。這個(gè)“收集-訓(xùn)練-再檢驗(yàn)”的閉環(huán)跑上兩三輪效果提升非常顯著。難例挖掘聽起來簡(jiǎn)單但很多項(xiàng)目都沒堅(jiān)持做其實(shí)這比改模型結(jié)構(gòu)性價(jià)比高太多了。5.3 部署和上線階段的其他問題模型訓(xùn)練好了不代表項(xiàng)目就到頭了。實(shí)際部署階段還有一個(gè)躲不開的問題模型體積和推理性能的權(quán)衡。YOLOv13有多種規(guī)格可以選擇從輕量級(jí)到超大型有梯度的不同型號(hào)。這個(gè)項(xiàng)目我最終選了中間檔次的版本模型文件大小適中精度和速度的平衡比較好。如果你需要部署到嵌入式設(shè)備比如邊緣計(jì)算盒子上那么輕量級(jí)版本更合適——犧牲一點(diǎn)精度換推理速度畢竟實(shí)時(shí)性在這個(gè)場(chǎng)景里優(yōu)先級(jí)很高。另外跨平臺(tái)部署時(shí)要注意路徑分隔符、OpenCV版本差異這類工程問題。之前遇到過一次在Windows上開發(fā)好的代碼放到Linux服務(wù)器上攝像頭打不開排查半天發(fā)現(xiàn)是RTSP地址的格式解析差異。這些看起來不起眼的問題往往是最耗時(shí)間的。6. 項(xiàng)目擴(kuò)展方向與我的個(gè)人體會(huì)6.1 從檢測(cè)走向識(shí)別與信息融合檢測(cè)只是第一步做完這個(gè)項(xiàng)目我最有感觸的一點(diǎn)是障礙物檢測(cè)應(yīng)該往“結(jié)構(gòu)化描述”的方向走而不僅僅是畫一個(gè)框?,F(xiàn)在的輸出是“圖像坐標(biāo)加類別”比如“畫面中心位置有一個(gè)共享單車”。但實(shí)際應(yīng)用中使用者真正需要的信息是這個(gè)障礙物相對(duì)自己有多遠(yuǎn)、在哪個(gè)方向、該怎么繞開。所以一個(gè)比較自然的擴(kuò)展方向是在檢測(cè)框的基礎(chǔ)上做深度估計(jì)并結(jié)合攝像頭標(biāo)定結(jié)果把像素坐標(biāo)換算成實(shí)際距離最終輸出類似“前方2米偏右有障礙物”的語義信息。另一個(gè)擴(kuò)展方向是和多模態(tài)技術(shù)結(jié)合。做一個(gè)智能導(dǎo)盲終端的話檢測(cè)結(jié)果可以通過語音合成播報(bào)或者通過震動(dòng)反饋傳遞給視障用戶。這塊涉及硬件但軟件層的算法邏輯已經(jīng)可以把基礎(chǔ)打好。6.2 數(shù)據(jù)閉環(huán)系統(tǒng)的重要性回看這個(gè)項(xiàng)目如果讓我從頭再做一次我會(huì)更早啟動(dòng)數(shù)據(jù)閉環(huán)的機(jī)制而不是等模型訓(xùn)完才開始收集難例。具體做法很簡(jiǎn)單做一個(gè)自動(dòng)化的數(shù)據(jù)回流通道讓所有運(yùn)行中的誤報(bào)、漏報(bào)案例自動(dòng)進(jìn)存儲(chǔ)每周定期抽樣標(biāo)注滾動(dòng)合并到訓(xùn)練集里。每次出新版本模型前跑一遍全量回歸測(cè)試集避免修了一個(gè)問題又帶回另一個(gè)問題。這個(gè)做法的收益是滾雪球式的。第一次難例挖掘可能只提升一兩個(gè)點(diǎn)精度但迭代到第五輪第六輪時(shí)邊緣場(chǎng)景的覆蓋率已經(jīng)遠(yuǎn)遠(yuǎn)超過初始階段。很多團(tuán)隊(duì)把模型上線就當(dāng)成結(jié)束其實(shí)那才是數(shù)據(jù)積累真正的開始。6.3 最后想說的話踩過一輪坑之后我個(gè)人體會(huì)最深的一點(diǎn)是這類所謂“AIoT場(chǎng)景”的落地點(diǎn)功夫更多在數(shù)據(jù)工程和場(chǎng)景理解上而不是模型本身的網(wǎng)絡(luò)結(jié)構(gòu)。一個(gè)工程上扎實(shí)的YOLOv13方案配合高質(zhì)量的人行道盲道場(chǎng)景數(shù)據(jù)效果勝過那些看著花哨但不貼合實(shí)際的復(fù)雜網(wǎng)絡(luò)。如果說項(xiàng)目做到現(xiàn)在還有什么遺憾那就是場(chǎng)景邊界還是太依賴攝像頭安裝位置和視角。如果后續(xù)能把同一路段的多個(gè)攝像頭圖像做跨鏡融合或者結(jié)合地圖信息提前預(yù)判“前方50米為盲道中斷區(qū)域”整體的出行安全體驗(yàn)應(yīng)該還能再上一個(gè)臺(tái)階。這也是我下一步想繼續(xù)折騰的方向。