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