戰(zhàn):OpenCV與SSD MobileNet全流程解析)
簡介基于Python的行人檢測視頻與源碼包面向計(jì)算機(jī)視覺初學(xué)者與進(jìn)階開發(fā)者解決視頻流中行人實(shí)時檢測與連續(xù)跟蹤問題。系統(tǒng)以HOG方向梯度直方圖提取特征結(jié)合SVM支持向量機(jī)分類器判斷行人區(qū)域并集成簡單跟蹤算法提升幀間穩(wěn)定性。壓縮包共25個文件約155.2MB包含2個Python主程序、2段avi測試視頻、11張bmp與5張png行人樣本圖片以及4個xml和1個iml項(xiàng)目配置說明可直接在OpenCV環(huán)境下運(yùn)行體驗(yàn)。已有8173人學(xué)習(xí)下載適合作為行人檢測入門實(shí)踐或課程設(shè)計(jì)參考。讀者可對照源碼梳理HOG特征計(jì)算、SVM分類器訓(xùn)練與調(diào)用、檢測窗口滑動、非極大值抑制及跟蹤模塊的實(shí)現(xiàn)細(xì)節(jié)借助附帶的多樣性樣本和視頻完成算法效果驗(yàn)證為后續(xù)拓展至其他目標(biāo)檢測任務(wù)打下基礎(chǔ)。1. 行人檢測視頻項(xiàng)目的真實(shí)價值先跑通一條完整鏈路再談?wù){(diào)優(yōu)在安防監(jiān)控人流統(tǒng)計(jì)、城市巡檢、輔助駕駛這類場景里行人檢測視頻處理是繞不開的計(jì)算機(jī)視覺基礎(chǔ)需求。但很多人拿到“python行人檢測視頻源碼”這類資源第一步就卡在環(huán)境上更別提后續(xù)的模型加載和視頻回寫。這份源碼的核心是一條完整鏈路視頻逐幀讀取、模型推理、行人框標(biāo)注、成品視頻輸出代碼量不大卻把視覺處理中最常碰到的視頻解碼、輸入預(yù)處理、坐標(biāo)映射、編碼器參數(shù)全串了起來。適合想快速跑出一版可運(yùn)行 demo 的從業(yè)者也適合想研究檢測框如何與視頻時間軸對齊的新手。先把這條鏈路跑通再考慮換模型提精度。2. 模型選型與環(huán)境搭建從 HOG 到 YOLO視頻場景為什么優(yōu)先選 SSD MobileNet2.1 三種常見行人檢測方案的橫向?qū)Ρ扰c選型邏輯行人檢測在工程落地時候選方案基本分三類傳統(tǒng) HOG SVM、兩階段檢測器Faster R-CNN 風(fēng)格、單階段檢測器SSD、YOLO 風(fēng)格。我拆過的很多視頻檢測源碼默認(rèn)帶的是 SSD MobileNet 預(yù)訓(xùn)練權(quán)重這不是隨手的決定背后是視頻處理場景對延遲和吞吐的硬約束。方案精度表現(xiàn)單幀推理速度CPU硬件要求適合場景主要坑點(diǎn)HOG SVM背景簡單時尚可遮擋/光照變化會漏較快純 CPU 即可固定機(jī)位、簡單背景對復(fù)雜背景魯棒性差Faster R-CNN 系高小目標(biāo)召回好很慢需要 GPU 加速離線分析、精度優(yōu)先視頻實(shí)時處理不現(xiàn)實(shí)SSD MobileNet 系中上平衡性好較快CPU 可跑有 GPU 更好視頻實(shí)時檢測小目標(biāo)漏檢輸入尺寸敏感YOLO 系列v8 風(fēng)格高速度快取決于版本推薦 GPU小模型可 CPU實(shí)時視頻/邊緣設(shè)備依賴配置復(fù)雜權(quán)重文件大視頻檢測和單張圖片檢測的差別在于單幀不能崩整體幀率必須穩(wěn)定。Faster R-CNN 在 CPU 上單幀要幾百毫秒處理 30fps 視頻得跳掉大量幀檢測結(jié)果前后跳變嚴(yán)重。HOG SVM 雖然快但對密集人群、部分遮擋幾乎沒有抵抗力。SSD MobileNet 的優(yōu)點(diǎn)是把 MobileNet 作為 backbone模型小、推理快輸出層直接用錨點(diǎn)回歸多個尺度目標(biāo)在一段 720P 視頻上CPU 環(huán)境下每幀處理時間能控制在幾十毫秒量級這就是視頻場景優(yōu)先選它的原因。YOLO 系在實(shí)際工程里精度確實(shí)更好比如最新版本的模型在小目標(biāo)召回上提升明顯但需要額外的權(quán)重轉(zhuǎn)換、顯存管理對新手而言攔路石更多。我的習(xí)慣是第一次跑視頻鏈路用 SSD MobileNet 摸清流程后續(xù)需要提精度再替換成 YOLO 權(quán)重。這樣檢測頭、坐標(biāo)解碼、NMS 邏輯都能復(fù)用改動集中在模型加載部分。2.2 虛擬環(huán)境、依賴安裝與預(yù)訓(xùn)練權(quán)重準(zhǔn)備拿到這份源碼第一步不是直接改代碼而是把運(yùn)行環(huán)境隔離出來。Python 項(xiàng)目最怕全局環(huán)境里已裝了大版本不一致的 cv2 和 numpy反復(fù)報(bào)錯后你會懷疑自己是不是手殘。我一般會在項(xiàng)目根目錄下建獨(dú)立虛擬環(huán)境。python -m venv ped_env source ped_env/bin/activate # Windows 下改為 ped_env\Scripts\activate pip install opencv-python numpy邏輯說明python -m venv創(chuàng)建獨(dú)立環(huán)境避免污染其他項(xiàng)目source激活后pip安裝的包只對當(dāng)前環(huán)境生效。Windows 用戶注意激活腳本路徑在Scripts目錄下不是bin。裝包時建議同時確認(rèn)版本cv2 的 API 在不同版本間變動不大但 numpy 版本過新時某些舊版 cv2 會出現(xiàn)二進(jìn)制接口不兼容的提示一旦遇到undefined symbol之類報(bào)錯先把 numpy 降到 1.x 版本再試。預(yù)訓(xùn)練權(quán)重這塊源碼里一般會附帶模型描述文件prototxt 后綴和二進(jìn)制權(quán)重文件。沒有附帶時需要去模型倉庫下載 SSD MobileNet 對應(yīng)的行人檢測版本。注意權(quán)重文件必須和描述文件配套只看文件名容易混。我習(xí)慣的做法是下載后先檢查文件大小prototxt 一般幾十 KB權(quán)重文件幾十 MB大小異?;揪褪窍螺d中斷了。2.3 模型驗(yàn)證加載后先打印輸出張量形狀模型加載成功不等于一切正常。我每次跑新源碼都會先單獨(dú)寫一小段腳本把模型輸出來看一眼確認(rèn)網(wǎng)絡(luò)確實(shí)工作而不是等整個視頻跑完才發(fā)現(xiàn)檢測結(jié)果全是 0 或空白。import cv2 prototxt deploy.prototxt caffemodel ssd_mobilenet.caffemodel net cv2.dnn.readNetFromCaffe(prototxt, caffemodel) print(網(wǎng)絡(luò)層名檢查, net.getLayerNames()[:5]) net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # 用一個全黑圖驗(yàn)證前向傳播是否正常 fake_input cv2.dnn.blobFromImage(np.zeros((300, 300, 3), dtypenp.uint8)) net.setInput(fake_input) out net.forward() print(輸出張量形狀, out.shape)邏輯說明先加載網(wǎng)絡(luò)打印前幾個層名確認(rèn)模型描述文件被正確解析然后構(gòu)造一張全黑圖做前向傳播驗(yàn)證網(wǎng)絡(luò)的輸入輸出鏈路。如果輸出張量形狀不是(1, 1, N, 7)說明模型版本或輸入尺寸配置不對得回去檢查描述文件里的輸入維度。參數(shù)說明readNetFromCaffe第一個參數(shù)是描述文本文件路徑第二個參數(shù)是二進(jìn)制權(quán)重路徑路徑寫錯在這一步就會拋異常不會拖到后面。blobFromImage的輸入尺寸要和模型描述文件里指定的維度一致常見的是300x300如果把尺寸改成別的值模型推理會靜默出錯檢測結(jié)果全是垃圾數(shù)據(jù)。3. 視頻行人檢測的落地實(shí)現(xiàn)抽幀、推理、標(biāo)注、回寫四步閉環(huán)3.1 VideoCapture 讀取視頻與幀率控制視頻處理的起點(diǎn)是讀幀。cv2 的VideoCapture支持?jǐn)z像頭和視頻文件兩種模式源碼里使用的是文件模式重點(diǎn)在于確認(rèn)視頻真的被打開、讀取的幀率是否和視頻原始幀率一致。import cv2 video_path input.mp4 cap cv2.VideoCapture(video_path) if not cap.isOpened(): raise IOError(f無法打開視頻文件{video_path}) fps cap.get(cv2.CAP_PROP_FPS) total_frames cap.get(cv2.CAP_PROP_FRAME_COUNT) print(f視頻幀率{fps}總幀數(shù){total_frames}) frame_idx 0 while True: ret, frame cap.read() if not ret: print(f讀取到第 {frame_idx} 幀時結(jié)束) break frame_idx 1 if frame_idx % 30 0: print(f已處理 {frame_idx} 幀) cap.release()邏輯說明cap.isOpened()返回False時直接拋異常能定位路徑或編碼問題CAP_PROP_FPS和CAP_PROP_FRAME_COUNT分別是視頻的原始幀率和總幀數(shù)后面計(jì)算跳幀策略和 FPS 基線都會用到。cap.read()返回兩個值ret為False表示視頻讀完了或讀取失敗必須把這個判斷放在循環(huán)最前面否則frame為空時報(bào)錯。參數(shù)說明視頻路徑最好用英文絕對路徑中文路徑在部分 Windows 環(huán)境下會讓VideoCapture直接返回False。另外某些翻轉(zhuǎn)封裝格式比如手機(jī)錄制的豎屏視頻可能帶旋轉(zhuǎn)元數(shù)據(jù)read()讀出來的畫面是橫著的檢測前要先用cv2.rotate調(diào)整方向不然框的位置全錯。3.2 基于 cv2 DNN 的行人推理與坐標(biāo)映射檢測部分是整條鏈路的中心。SSD MobileNet 的輸出不是直接給你坐標(biāo)而是經(jīng)過歸一化的檢測結(jié)果必須正確解碼才能映射回原圖。import cv2 import numpy as np net cv2.dnn.readNetFromCaffe(deploy.prototxt, ssd_mobilenet.caffemodel) confidence_threshold 0.5 def detect_pedestrians(frame): h, w frame.shape[:2] blob cv2.dnn.blobFromImage(frame, 0.007843, (300, 300), 127.5) net.setInput(blob) detections net.forward() boxes [] for i in range(detections.shape[2]): confidence detections[0, 0, i, 2] if confidence confidence_threshold: continue class_id int(detections[0, 0, i, 1]) # 假設(shè)行人對應(yīng)類別編號為 1具體以模型訓(xùn)練時的類別表為準(zhǔn) if class_id ! 1: continue xmin int(detections[0, 0, i, 3] * w) ymin int(detections[0, 0, i, 4] * h) xmax int(detections[0, 0, i, 5] * w) ymax int(detections[0, 0, i, 6] * h) boxes.append((xmin, ymin, xmax, ymax, confidence)) return boxes邏輯說明blobFromImage完成三件事縮放、減均值、通道轉(zhuǎn)換默認(rèn) BGR 轉(zhuǎn) RGB輸出一個四維張量送入網(wǎng)絡(luò)。net.forward()返回的detections形狀是(1, 1, N, 7)N 是模型預(yù)設(shè)的最大檢測候選數(shù)7 列包含圖片編號、類別編號、置信度、xmin、ymin、xmax、ymax。關(guān)鍵點(diǎn)坐標(biāo)是歸一化的 0~1 浮點(diǎn)數(shù)必須乘回原圖寬高否則框會畫到一個角落里頭去。參數(shù)說明縮放系數(shù)0.007843和均值127.5是 Caffe 模型里固定的預(yù)處理參數(shù)來源于 ImageNet 訓(xùn)練時的數(shù)據(jù)歸一化方式這兩個值不能隨便改。類別編號 1 對應(yīng)行人這是 Caffe 模型訓(xùn)練時的類別映射不同的預(yù)訓(xùn)練模型可能不同典型錯誤是把只檢測行人的模型當(dāng)成了 COCO 80 類模型導(dǎo)致類別編號錯位。我建議先打印detections[0, 0, i, 1]的取值分布確認(rèn)類別編號再寫死。3.3 VideoWriter 輸出成品視頻與編碼器選型檢測框畫完之后需要把幀寫回視頻文件。這里是最容易返工的地方源碼里給出的編碼器參數(shù)在幾臺機(jī)器上都有出入我把通用可用的寫法整理如下。import cv2 output_path output.mp4 fourcc cv2.VideoWriter_fourcc(*mp4v) out_writer cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height)) while True: # 假設(shè) frame 是當(dāng)前幀annotated_frame 是畫完框的幀 annotated_frame frame.copy() for (xmin, ymin, xmax, ymax, conf) in boxes: cv2.rectangle(annotated_frame, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) out_writer.write(annotated_frame) out_writer.release()邏輯說明VideoWriter_fourcc把四個字符編碼成整數(shù)標(biāo)識mp4v是 MP4 容器最常見的編碼器兼容性最好。VideoWriter構(gòu)造函數(shù)里的寬高必須和輸入幀完全一致否則寫入的文件要么直接是 0 字節(jié)要么播放器打不開。write方法接受一幀完整圖像內(nèi)部會自動編碼追加到文件中。參數(shù)說明fps參數(shù)決定輸出視頻的時間軸密度一般沿用輸入視頻的原始幀率。如果實(shí)際處理速度低于原始幀率最終輸出視頻會呈現(xiàn)慢放效果這是正常的因?yàn)閷懭氲膸瑪?shù)變少了但幀率保持不變。需要快放的話把fps調(diào)小需要慢放就調(diào)大。另外系統(tǒng)若沒有對應(yīng)的編碼器VideoWriter構(gòu)造時不會立刻報(bào)錯而是在第一次write時靜默失敗所以寫完文件后要檢查文件大小是否合理。4. 參數(shù)調(diào)優(yōu)與性能邊界置信度、NMS、跳幀策略怎么配才不翻車4.1 置信度閾值與 NMS 閾值的配對關(guān)系源碼里的置信度閾值默認(rèn) 0.5NMS 閾值默認(rèn) 0.4這兩個值是經(jīng)驗(yàn)配比但不一定適配你的真實(shí)視頻。置信度閾值管的是“框要不要留”NMS 閾值管的是“重疊的框要不要合并”兩者需要聯(lián)動看。場景置信度閾值NMS 閾值觀察結(jié)果固定機(jī)位、行人稀疏0.5~0.60.4漏檢少誤檢少密集人群0.7~0.80.3抑制大量低分誤檢但可能漏掉被遮擋的行人遠(yuǎn)距離小目標(biāo)0.3~0.40.5召回率提升但誤檢明顯增多需要人工核對置信度閾值調(diào)低到 0.3 之后最直觀的現(xiàn)象就是樹干、路燈、車窗反光都會出框調(diào)高到 0.8 以上雖然誤檢少了但遠(yuǎn)處的行人很容易漏。我拆視頻時習(xí)慣先跑一遍把置信度從低到高打印出檢測框數(shù)量分布再確定閾值而不是一開始就抄默認(rèn)值。NMS 閾值的作用容易被忽略。同一個行人被輸出多個框時NMS 閾值越低重疊框被壓得越狠但當(dāng)兩個行人靠得很近時過低的 NMS 會把真值行人也合并掉一個。如果你發(fā)現(xiàn)視頻里兩個人的框變成了一個框在兩人之間來回晃大概率是 NMS 閾值設(shè)太低了。4.2 跳幀檢測策略CPU 上處理 1080P 視頻的折中方案視頻處理速度和分辨率強(qiáng)相關(guān)。1080P 視頻在 CPU 上跑 SSD MobileNet單幀推理加畫框大約花 80~150 毫秒也就是每秒只能處理七八幀遠(yuǎn)遠(yuǎn)達(dá)不到 25fps 實(shí)時要求。這時候跳幀是不可避免的折中策略。import cv2 skip_frames 2 # 每 3 幀檢測一次 last_boxes [] frame_idx 0 while True: ret, frame cap.read() if not ret: break if frame_idx % (skip_frames 1) 0: last_boxes detect_pedestrians(frame) annotated_frame draw_boxes(frame, last_boxes) else: # 中間幀沿用最近一次檢測結(jié)果也可以用光流跟蹤插值 annotated_frame draw_boxes(frame, last_boxes) out_writer.write(annotated_frame) frame_idx 1邏輯說明每 3 幀才做一次完整推理中間 2 幀直接沿用最近一次檢測框。這樣推理次數(shù)降為原來的三分之一整體處理速度約提升 2~3 倍。假設(shè)原始處理 8fps跳幀后能達(dá)到 20fps 以上視覺上框的跳動感也不會太明顯。參數(shù)說明skip_frames的取值依據(jù)是單幀推理耗時。推理耗時 100 毫秒時跳 2 幀可以讓每幀平均耗時壓到 33 毫秒左右接近直播要求耗時 50 毫秒時跳 1 幀就夠了。跳幀數(shù)過大時行人快速移動會讓框明顯落后于實(shí)際位置這是時間軸上的滯后不是檢測模型的精度問題。后續(xù)要優(yōu)化可以用光流法或者跟蹤算法接在檢測結(jié)果后面而不是無限加大跳幀數(shù)。4.3 檢測結(jié)果統(tǒng)計(jì)用日志盯住漏檢與誤檢調(diào)參不能靠肉眼反復(fù)拖動視頻浪費(fèi)精力不說還不容易對比。我習(xí)慣把每幀的檢測結(jié)果輸出成結(jié)構(gòu)化日志用數(shù)據(jù)判斷當(dāng)前參數(shù)是否合適。import csv import time with open(detection_log.csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame_idx, pedestrian_count, avg_confidence]) frame_idx 0 while True: ret, frame cap.read() if not ret: break boxes detect_pedestrians(frame) if len(boxes) 0: avg_conf sum(b[4] for b in boxes) / len(boxes) else: avg_conf 0 writer.writerow([frame_idx, len(boxes), round(avg_conf, 3)]) frame_idx 1邏輯說明把每幀的行人數(shù)量和平均置信度寫入 CSV跑完視頻后用表格軟件拉一張折線圖可以直觀看到哪些時間段檢測數(shù)量突然下降或飆升。比如某段從 5 個行人突然變成 0 再變回 5基本是漏檢加恢復(fù)的過程那一段的置信度閾值需要單獨(dú)調(diào)低。參數(shù)說明日志記錄本身有 IO 損耗長期跑視頻時建議每 30 幀寫一次批量寫入而不是逐幀writerow否則會拖慢整體速度。檢測數(shù)量在閾值調(diào)整前后的對比是判斷參數(shù)是否合理的硬指標(biāo)。5. 行人檢測視頻常見問題排查五個實(shí)測踩坑記錄與修復(fù)5.1 視頻讀取失敗cap.isOpened() 返回 False現(xiàn)象代碼執(zhí)行時不報(bào)錯但進(jìn)不了讀取循環(huán)程序直接結(jié)束或者frame一直是空的。原因最常見的有三種。視頻路徑存在中文或空格部分系統(tǒng)底層解碼器不認(rèn)視頻文件本身是加密或損壞的容器格式太冷門比如某些老舊監(jiān)控導(dǎo)出的私有格式。解決先確認(rèn)路徑完整改寫為英文絕對路徑把待處理視頻用主流格式重新編碼一版推薦 H.264 編碼的 MP4。我平時會用格式轉(zhuǎn)換工具先統(tǒng)一轉(zhuǎn)一遍再跑檢測代碼。調(diào)試時打印cap.get(cv2.CAP_PROP_FRAME_COUNT)如果返回 0說明解碼器根本沒有讀取到幀索引。5.2 檢測框抖動漂移相鄰幀結(jié)果不穩(wěn)定現(xiàn)象畫面靜止時同一個行人身上的框忽大忽小或者框在行人身體邊緣來回移動行人走動時框時有時無。原因這是單幀檢測的典型問題。模型每次獨(dú)立推理對同一目標(biāo)在不同幀輸出的邊界框存在像素級抖動置信度也在閾值附近波動導(dǎo)致某些幀被過濾掉。解決最常見做法是對檢測框做時間維度的平滑。把最近幾幀的同一目標(biāo)框做加權(quán)平均或者按 IoU 匹配前后幀的框如果新幀的檢測框與上一幀的框重合度超過 0.5就用兩者坐標(biāo)的平均值替代當(dāng)前框。還有一種更省事的方法是調(diào)低置信度閾值到 0.4 左右讓更多候選框進(jìn)入 NMS 合并流程輸出的框穩(wěn)定性會好一些。5.3 輸出視頻為空或無法播放編碼器與分辨率不一致現(xiàn)象代碼跑完輸出文件大小接近 0 KB或者播放器提示文件損壞。原因VideoWriter創(chuàng)建時傳入的寬高和實(shí)際frame的寬高不一致導(dǎo)致數(shù)據(jù)寫入失敗也可能是fourcc指定的編碼器在當(dāng)前系統(tǒng)上沒有可用的動態(tài)庫寫入時靜默失敗。解決在創(chuàng)建 writer 之前打印frame.shape它返回(height, width, channels)而VideoWriter參數(shù)順序是(width, height)順序反了就會出現(xiàn)寬高不一致。編碼器問題上mp4v兼容性最好如果你的系統(tǒng)有 x264 編碼庫可以試avc1壓縮率更高文件更小。寫完后第一時間檢查輸出文件大小小于 1MB 基本就是寫失敗了。5.4 模型加載報(bào)錯prototxt 路徑與層名不匹配現(xiàn)象readNetFromCaffe直接拋異常說找不到文件或網(wǎng)絡(luò)解析失敗。也有情況是不報(bào)錯但檢測結(jié)果全為空。原因prototxt 文件和 caffemodel 文件不配套比如描述文件是某個版本生成的權(quán)重文件來自另一個訓(xùn)練任務(wù)兩者層名的對應(yīng)關(guān)系對不上?;蛘呤锹窂藉e誤導(dǎo)致加載了一個殘留的殘缺文件。解決先確認(rèn)兩個文件確實(shí)存在且大小正常。然后加載時打印net.getLayerNames()看前幾個層名是否符合預(yù)期。如果層名里出現(xiàn)不連續(xù)的編號說明模型結(jié)構(gòu)描述和權(quán)重不匹配。最有效的辦法是換用同一套模型倉庫提供的 prototxt 和 caffemodel不要混搭版本。我見過某開發(fā)者把別名的權(quán)重文件改名后混用結(jié)果推理結(jié)果全為 0排查了一天最后發(fā)現(xiàn)是文件不配套。5.5 遠(yuǎn)距離小目標(biāo)漏檢輸入尺寸與感受野的博弈現(xiàn)象畫面中近處的行人能檢測出來距離稍遠(yuǎn)的行人完全沒框或者框的位置嚴(yán)重偏移。原因SSD MobileNet 的默認(rèn)輸入是 300x300圖像被大幅壓縮后小尺寸行人的特征幾乎丟失另一個原因是 anchor 的尺度覆蓋有限小目標(biāo)對應(yīng)的先驗(yàn)框數(shù)量不足。解決把blobFromImage的輸入尺寸從 300x300 調(diào)大到 600x600小目標(biāo)召回會明顯提升但推理耗時也會翻倍。另一種更省資源的做法是只對圖像的下半部分區(qū)域進(jìn)行裁剪檢測因?yàn)楸O(jiān)控畫面中行人通常集中在中下部區(qū)域。我把檢測區(qū)域裁剪后放大兩倍再推理小目標(biāo)召回率提升了不少而整體耗時只增加了一部分。注意改輸入尺寸前先確認(rèn) prototxt 里定義的輸入層尺寸是否允許動態(tài)調(diào)整。部分舊模型把輸入尺寸寫死改大后會直接報(bào)錯。6. 進(jìn)階驗(yàn)證把檢測結(jié)果量化成 FPS 與軌跡數(shù)據(jù)6.1 用時間戳計(jì)算真實(shí)處理幀率視頻檢測項(xiàng)目交付時別人最常問的問題是“你能跑到多少幀”。不能用cap.get(CAP_PROP_FPS)冒充那是視頻的原始幀率不是你的處理幀率。用代碼里的時間戳統(tǒng)計(jì)才是真實(shí)數(shù)字。import time start_time time.time() processed_count 0 while True: ret, frame cap.read() if not ret: break # 檢測、畫框、寫入都放在這里 processed_count 1 if processed_count % 30 0: elapsed time.time() - start_time fps_output processed_count / elapsed print(f已處理 {processed_count} 幀平均處理幀率{fps_output:.2f} FPS)邏輯說明用time.time()記錄累計(jì)耗時每 30 幀算一次平均幀率這個數(shù)字才反映真實(shí)處理能力。如果輸出幀率低于目標(biāo)優(yōu)先調(diào)整跳幀策略和輸入尺寸而不是盲目換更重的模型。參數(shù)說明統(tǒng)計(jì)區(qū)間太短會抖動劇烈30 幀左右的窗口比較穩(wěn)。視頻最后不足 30 幀的剩余部分也要在循環(huán)結(jié)束后補(bǔ)算一次避免丟尾幀數(shù)據(jù)。6.2 檢測框坐標(biāo)導(dǎo)出 CSV 做軌跡后處理視頻里的檢測框坐標(biāo)是研究行人軌跡的基礎(chǔ)數(shù)據(jù)。把每幀的框?qū)懭?CSV后續(xù)可以統(tǒng)計(jì)某個區(qū)域的人流密度、計(jì)算行人的移動速度甚至做跨跟蹤算法。import csv with open(tracks.csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame_id, xmin, ymin, xmax, ymax, confidence]) frame_id 0 while True: ret, frame cap.read() if not ret: break boxes detect_pedestrians(frame) for (xmin, ymin, xmax, ymax, conf) in boxes: writer.writerow([frame_id, xmin, ymin, xmax, ymax, round(conf, 3)]) frame_id 1邏輯說明frame_id對應(yīng)原始視頻的幀序號即使跳幀處理也要按原始計(jì)數(shù)寫入后續(xù)才能對齊時間軸。CSV 里每一行是一個檢測框同一個行人在相鄰幀的框靠 IoU 做關(guān)聯(lián)就能粗糙拼出軌跡。參數(shù)說明坐標(biāo)是像素值如果后續(xù)要做物理距離計(jì)算還需要結(jié)合攝像頭的標(biāo)定參數(shù)做透視變換這一步不在源碼范圍內(nèi)但數(shù)據(jù)結(jié)構(gòu)已經(jīng)支持了。最后說一個我自己的教訓(xùn)。早期拆這類視頻檢測資源為了追求檢測框看著連貫我把置信度閾值直接拉到 0.2結(jié)果誤檢框覆蓋了大半個畫面整體效果還更差。后來我明白了檢測效果的瓶頸常常不在模型的單幀精度上而是在框的穩(wěn)定性、處理幀率、數(shù)據(jù)記錄這條完整鏈路的配合上。從那以后我每次拿到源碼都強(qiáng)制自己先跑 5 分鐘真實(shí)視頻做參數(shù)基線再調(diào)閾值先看日志統(tǒng)計(jì)再調(diào)畫面。這份源碼的價值不在某一幀的精確框而在于你把視頻處理的全流程真正走通了一次。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取