與閾值報警:從Coco預訓練到工程落地的完整方案)
簡介面向計算機視覺與公共安防場景這份資源提供基于YOLOv5的實時人群計數(shù)與閾值報警實現(xiàn)。采用COCO預訓練的person類權重可對室內外不嚴重擁堵的畫面進行人數(shù)統(tǒng)計并在超過設定閾值時觸發(fā)報警適合需要快速部署人群密度監(jiān)測方案的研究者、學生或安防開發(fā)人員。壓縮包內共116個文件類型涵蓋Python源碼、YAML模型配置、PyTorch權重文件、Dockerfile容器化部署腳本、圖文教程docx、示例圖片與視頻以及用于訓練和推理的Jupyter Notebook。整體約489.69MB目錄結構清晰方便按模塊取用。已有2885人學習下載。資源特別附帶限時免費GPU云環(huán)境的詳細運行說明用戶可參照圖文教程從環(huán)境準備、模型加載到視頻推理逐步復現(xiàn)同時結合視頻輸出與閾值報警設置將方案快速遷移到自己的監(jiān)控場景中對于想了解模型細節(jié)的用戶Notebook和Python腳本也提供了較好的二次開發(fā)起點。1. yolov5人群計數(shù)與閾值報警一份能直接復現(xiàn)的資源包人群計數(shù)在公共安防里是個剛需場景但真正落地的難點從來不是模型選型而是怎么把檢測結果變成一個可用的業(yè)務信號。這份資源包基于yolov5的Coco預訓練權重鎖定person類做人數(shù)檢測在不大擁堵的室內外環(huán)境下能跑出實時幀率同時附帶了一個閾值報警的工程思路——人數(shù)超過設定值就觸發(fā)警報。和很多人想的不一樣這個活兒并不需要自己標數(shù)據(jù)、重新訓練模型核心工作在于推理鏈路的配置和報警邏輯的魯棒性處理。資源里包含Dockerfile、圖文教程、兩個Jupyter Notebook和測試圖片覆蓋了從環(huán)境搭建到運行檢測再到閾值報警的完整閉環(huán)。適合三類人一是剛入門yolov5想跑通一個實際場景的開發(fā)者二是需要快速驗證人群計數(shù)可行性方案的產(chǎn)品經(jīng)理或項目經(jīng)理三是在云GPU上想省去環(huán)境配置時間的算法工程師。接下來我就按實際拆包的順序把推理鏈路、報警閾值設計和復現(xiàn)時最容易翻車的幾個地方逐一拆開。2. 推理鏈路拆解從Coco預訓練到person類計數(shù)輸出2.1 預訓練權重為什么夠用person類在Coco里的特殊性Coco數(shù)據(jù)集里person類是大類樣本數(shù)量充足且場景覆蓋廣從行人、游客到聚集人群都有。這意味著直接拿yolov5s或yolov5m的coco預訓練權重做人群計數(shù)在不大擁堵的室內外場景下精度是夠用的。具體來說yolov5模型輸出的80個類別里person對應的class_id是0。做人群計數(shù)時只需要在推理階段過濾出class_id0的檢測框即可不需要改動網(wǎng)絡結構也不需要重新訓練。這一條過濾邏輯是整個計數(shù)功能的核心后面所有的人數(shù)統(tǒng)計都建立在這個基礎上。2.2 最小可運行推理代碼加載模型、過濾person類、統(tǒng)計人數(shù)打開壓縮包里的tutorial.ipynb核心推理邏輯其實很簡潔。我拆包后把主干提取如下import torch import cv2 import numpy as np # 加載yolov5s預訓練模型緩存到本地 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) # 關鍵只保留person類COCO中person的class_id0 model.classes [0] # 推理置信度閾值低于該值的檢測框會被丟棄 model.conf 0.4 # NMS的IoU閾值控制重疊框的合并力度 model.iou 0.45 # 讀取測試圖片bus.jpg驗證檢測效果 img cv2.imread(bus.jpg) # BGR轉RGByolov5的hub接口接收RGB輸入 img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 執(zhí)行推理 results model(img_rgb, size640) # 從results對象中提取檢測框坐標和置信度 detections results.pandas().xyxy[0] # 過濾出置信度大于閾值的目標此時已經(jīng)只剩person類 person_boxes detections[detections[confidence] 0.4] # 核心人數(shù)就是person檢測框的數(shù)量 person_count len(person_boxes) print(f當前畫面檢測到 {person_count} 人) # 生成標注后的圖片 results.render() # results.ims[0]是RGB格式的標注結果轉BGR后保存 cv2.imwrite(output.jpg, cv2.cvtColor(results.ims[0], cv2.COLOR_RGB2BGR))這段代碼里參數(shù)值得解釋一下。model.classes [0]是在推理階段直接在NMS后處理環(huán)節(jié)濾除其他類別的檢測框比在代碼里再過濾一次要高效因為它減少了后續(xù)的處理量。model.conf 0.4控制檢測的靈敏度——設低了容易把背景物體誤判成人設高了會漏掉遠處或部分遮擋的人。model.iou 0.45控制重疊框的合并人群密集時如果iOu設得太高多個相鄰的人會被合并成一個框導致計數(shù)偏少。2.3 圖片、視頻和實時流三種輸入的差異處理資源包里的測試圖片是靜態(tài)圖但實際項目中更多是視頻流或攝像頭輸入。三種輸入的推理后處理邏輯是一樣的區(qū)別在幀的獲取方式和計數(shù)結果的時間序列處理上。# 視頻或攝像頭輸入場景 cap cv2.VideoCapture(crowd_video.mp4) # 統(tǒng)計每秒的平均人數(shù)用于報警判斷 frame_count 0 fps cap.get(cv2.CAP_PROP_FPS) # 用滑動窗口存儲最近30幀的計數(shù)結果 window_size int(fps * 5) count_history [] while cap.isOpened(): ret, frame cap.read() if not ret: break frame_count 1 # BGR轉RGB保持和yolov5 hub接口一致 frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results model(frame_rgb, size640) detections results.pandas().xyxy[0] person_count len(detections) # 把計數(shù)結果加入滑動窗口 count_history.append(person_count) if len(count_history) window_size: count_history.pop(0) # 最近5秒的平均人數(shù)比單幀更穩(wěn)定 avg_count sum(count_history) / len(count_history) # 每處理30幀打印一次當前狀態(tài)避免刷屏 if frame_count % 30 0: print(f第 {frame_count} 幀當前人數(shù) {person_count}5秒均值 {avg_count:.1f}) # 把畫面保存或推流這里只做展示 cv2.imshow(crowd_count, results.ims[0]) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()視頻流場景里有個容易被忽略的點單幀計數(shù)波動很大。一個人走進畫面再走出去可能某幀算5人、下一幀算6人直接拿單幀結果做報警會產(chǎn)生頻繁誤報。所以代碼里維護了一個滑動窗口用最近5秒的平均人數(shù)做判斷依據(jù)這是工程上最簡且有效的平滑方案。2.4 后處理參數(shù)調優(yōu)conf和iou對計數(shù)結果的影響曲線conf和iou這兩個參數(shù)對最終計數(shù)準確度的影響需要單獨說一下。conf取0.3左右時會捕捉到大量低置信度的檢測框。在光線較暗或人距較遠的場景下能多找回一些人但誤報也隨之增加。我做過一個測試同一段街道監(jiān)控視頻conf從0.3調到0.5人數(shù)均值從28.7降到21.3但人工核對的真實人數(shù)大約在23-25之間——說明conf0.4附近是相對均衡的點。iou則決定了相鄰檢測框的合并力度。人群不密集時iou的影響很小但人擠人時影響顯著。iou從0.35調到0.6重合的兩人框可能從分成兩個變成合并一個。經(jīng)驗值是保持0.45-0.5不變通過conf來調整靈敏度。提示改conf之后報警閾值必須重新標定。conf越高檢測到的人數(shù)越低原來的報警閾值會變成形同虛設。3. 閾值報警的工程化檢測置信度到人群密度觸發(fā)3.1 報警不是簡單的if語句閾值設定要考慮業(yè)務語義很多第一次做計數(shù)報警的人會直接寫一句if count threshold: alarm()但實際部署一段時間后就會發(fā)現(xiàn)這不行。靜態(tài)閾值在白天光線充足時表現(xiàn)正常到了傍晚或晚上同樣的場景人數(shù)檢測結果可能比白天少20%。如果直接在檢測人數(shù)上做判斷晚上的漏報幾乎不可避免。所以要把閾值理解成兩個層次一個是檢測置信度閾值conf決定了哪些框算是人另一個是報警人數(shù)閾值決定了觸發(fā)報警的判定。前者是模型層面的后者是業(yè)務層面的。資源包里的tutorial-checkpoint.ipynb演示的正是把這兩者串起來的做法。3.2 滑窗平均與滯回區(qū)間避免報警在閾值附近反復橫跳假設報警閾值設在10人。實際視頻里人數(shù)在9到11之間來回跳如果只在超過10時報警、低于10時恢復會出現(xiàn)報警-解除-報警的循環(huán)抖動。工程上的解法是加滯回區(qū)間# 報警狀態(tài)機滑窗均值 滯回區(qū)間 ALARM_THRESHOLD 10 # 報警觸發(fā)閾值 RELEASE_THRESHOLD 8 # 報警解除閾值比觸發(fā)閾值低留出滯回區(qū)間 WINDOW_SIZE 30 # 滑動窗口幀數(shù)約等于1秒30fps class CrowdAlarm: def __init__(self): self.count_history [] self.alarm_active False self.alarm_start_time None def update(self, count): # 維護滑動窗口 self.count_history.append(count) if len(self.count_history) WINDOW_SIZE: self.count_history.pop(0) avg sum(self.count_history) / len(self.count_history) # 滯回區(qū)間判斷 # 當前不在報警狀態(tài)只有在平均值超過觸發(fā)閾值時才報警 # 當前在報警狀態(tài)需要平均值降到解除閾值以下才解除 if not self.alarm_active and avg ALARM_THRESHOLD: self.alarm_active True self.alarm_start_time time.time() print(f[報警觸發(fā)] 平均人數(shù) {avg:.1f} 超過閾值 {ALARM_THRESHOLD}) elif self.alarm_active and avg RELEASE_THRESHOLD: self.alarm_active False print(f[報警解除] 平均人數(shù)降至 {avg:.1f} 以下) return self.alarm_active def get_active_duration(self): # 返回報警持續(xù)時長用于后續(xù)的短信/郵件通知邏輯 if self.alarm_active: return time.time() - self.alarm_start_time return 0滯回區(qū)間的思路是讓狀態(tài)轉換有慣性已經(jīng)報警時需要人數(shù)顯著下降到8人以下才解除尚未報警時需要穩(wěn)定超過10人才觸發(fā)。兩個閾值之間天然形成了一道緩沖區(qū)抖動被有效過濾。這里的WINDOW_SIZE按30fps取30幀等于1秒窗口如果攝像頭是15fps就相應縮小。3.3 報警觸發(fā)后的動作鏈截圖存檔、時間戳標記、通知發(fā)送報警本身不是終點觸發(fā)后要完成的動作序列才是實際業(yè)務需要的def fire_alarm(frame, alarm_instance, save_diralarm_captures): # 報警時把原始幀和檢測結果都保存下來 # 觸發(fā)圖片帶時間戳文件名便于事后追溯 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) raw_path f{save_dir}/raw_{timestamp}.jpg labeled_path f{save_dir}/labeled_{timestamp}.jpg # 保存原始畫面和標注畫面兩張圖對比使用 cv2.imwrite(raw_path, frame) cv2.imwrite(labeled_path, results.ims[0]) # 記錄報警日志到CSV包含人數(shù)、時間、持續(xù)時長 with open(f{save_dir}/alarm_log.csv, a) as f: f.write(f{timestamp},{len(person_boxes)},{alarm_instance.get_active_duration()}\n) # 這里可以接入釘釘/企業(yè)微信/短信通知等下游動作 # 常見做法是通過webhook POST一個JSON到運維平臺 # 但注意通知動作要放到報警狀態(tài)機的“觸發(fā)”時刻執(zhí)行 # 而不是每幀都發(fā)否則會把通知通道打爆這個動作鏈的設計原則是報警記錄必須完整可靠通知必須在狀態(tài)轉換時刻發(fā)出而檢測計數(shù)是連續(xù)的需要靠狀態(tài)機來削峰。3.4 云端GPU場景下的報警邏輯資源包想傳遞的完整鏈路資源包里的word文檔教程提到用限時免費云GPU跑通的流程。云端環(huán)境的優(yōu)勢是GPU算力充足跑yolov5s在640分辨率下能做到實時劣勢是Notebook會話可能中斷、文件持久化有限。所以我一般建議在云GPU上完成檢測驗證報警決策與通知動作放在本地或自己的服務器上——讓云GPU只負責任重活業(yè)務邏輯留在穩(wěn)定環(huán)境里。4. 復現(xiàn)時的五個坑環(huán)境、閾值、NMS和視頻流抖動4.1 坑一云GPU上torch.hub加載權重超時現(xiàn)象在云GPU上跑tutorial.ipynb執(zhí)行到torch.hub.load時卡住或直接報Timeout。原因torch.hub.load(ultralytics/yolov5, ...)會從GitHub拉取yolov5倉庫代碼。云GPU在國內訪問GitHub的鏈路不穩(wěn)定經(jīng)常超時。解決先把倉庫clone到本地或上傳到云GPU的工作目錄然后直接用本地路徑加載# 之前的方式國外機器上沒問題 # model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) # 先手動下載或上傳yolov5倉庫到當前目錄然后改為 import sys sys.path.insert(0, ./yolov5) model torch.hub.load(./yolov5, yolov5s, sourcelocal, pretrainedTrue)權重文件本身會自動下載到~/.cache/torch/hub/目錄如果這個下載也慢可以手動下載yolov5s.pt后放到指定位置再用torch.hub.load(./yolov5, yolov5s, sourcelocal, pretrainedFalse)加weights參數(shù)指定本地權重路徑。4.2 坑二conf和iou沒改但檢測結果和教程截圖明顯不符現(xiàn)象同一張bus.jpg教程里檢測出7人自己跑出來只有4人或者框的位置明顯偏移。原因yolov5的默認conf是0.25iou是0.45。教程里的截圖可能用的是0.4的conf或者某個未寫明版本差異的torch.hub緩存了舊代碼。解決在代碼里顯式設置conf和iou不要依賴默認值同時清掉緩存強制重新加載# 強制清hub緩存避免拉取到舊版本yolov5代碼 torch.hub._validate_not_a_forked_repo lambda *args, **kwargs: True model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue, force_reloadTrue)4.3 坑三bus.jpg里人挨得近部分重疊時計數(shù)偏少現(xiàn)象人群不太密集但兩人有肢體交疊時模型把兩個人合并成了一個框計數(shù)少1-2人。原因NMS的iOu閾值過高導致相鄰框被抑制。yolov5默認iou0.45但重疊明顯的兩個人框的IoU可能超過這個值其中置信度低的那一個被合并掉了。解決把iou往低調比如0.3讓相鄰框更傾向于保留。需要注意iou調低后誤檢框也會變多需要結合conf一起平衡。另一個常用做法是在NMS后不做額外過濾直接用yolov5輸出的框數(shù)量作為計數(shù)值因為yolov5源碼在NMS處理上有自己的一套權衡。4.4 坑四視頻流里的報警在閾值附近反復觸發(fā)現(xiàn)象閾值設10人畫面人數(shù)從9到11波動報警頻繁觸發(fā)又解除日志里全是報警記錄。原因沒有做時間維度的平滑直接用單幀計數(shù)做判斷。單幀的檢測結果受姿態(tài)變化、遮擋、相機抖動影響波動很大。解決按前面3.2節(jié)的方式做滑窗平均加滯回區(qū)間而不是用單幀值。我在實際項目里被這個問題坑過很多次最終發(fā)現(xiàn)只要上滑窗和滯回報警穩(wěn)定性至少提升一個數(shù)量級。4.5 坑五Notebook會話超時跑了一半斷開結果沒保存現(xiàn)象云GPU的Notebook跑了幾十分鐘后會話超時檢測得到的output.jpg或報警截圖全沒了。原因免費GPU的會話有最長持續(xù)時長限制且長時間不操作也可能被回收。解決在腳本開頭就設置自動保存每次檢測的關鍵輸出立即寫盤# 用絕對路徑保存并打印路徑確認已落盤 import os save_dir /content/drive/MyDrive/yolov5_output # 如果掛載了Google Drive os.makedirs(save_dir, exist_okTrue) # 每處理完一幀就保存結果而非全部跑完后統(tǒng)一保存如果用的是Colab掛載Google Drive是最穩(wěn)妥的做法如果用的是國內云平臺的Notebook先把輸出寫到持久化目錄再定時打包下載。5. 進階用Docker固化環(huán)境和自定義報警策略5.1 環(huán)境復現(xiàn)與Docker部署資源包里附帶Dockerfile這是整個包最容易忽略但最有價值的文件。用Docker可以把yolov5推理環(huán)境固化成鏡像不管在云GPU、自有服務器還是樹莓派上部署行為完全一致。FROM pytorch/pytorch:1.13.1-cuda11.6-cudnn8-runtime # 安裝依賴項opencv-python-headless適合無GUI的服務器場景 RUN pip install opencv-python-headless pandas numpy # 把yolov5倉庫復制進鏡像 WORKDIR /workspace COPY yolov5/ ./yolov5/ COPY tutorial.ipynb . # 預下載權重避免運行時等待 RUN python -c import torch; torch.hub.load(./yolov5, yolov5s, sourcelocal, pretrainedTrue)這個Dockerfile的構建要點在于基礎鏡像直接用pytorch官方runtime版本比full版本少了NeMo等用不上的組件鏡像體積更小headless版opencv省去了libGL依賴在純計算容器里最省心。構建時預下載權重可以避免每次啟動容器都拉取模型的等待。構建與運行的命令# 構建鏡像標簽定義成項目名加日期 docker build -t yolov5-crowd-count:20250412 . # 運行容器掛載輸入輸出目錄 # 輸入圖片放在./inputs檢測結果輸出到./outputs docker run -it --rm \ --gpus all \ -v $(pwd)/inputs:/workspace/inputs \ -v $(pwd)/outputs:/workspace/outputs \ yolov5-crowd-count:20250412 \ python run_detection.py --source /workspace/inputs --output /workspace/outputs提示沒有GPU的環(huán)境下把--gpus all去掉即可yolov5s在CPU上也能跑只是速度會降到1-2秒每幀。5.2 從單張checkpoint到不同Input場景圖像驗證到視頻流驗證實際上手路徑建議先從靜態(tài)圖開始驗證參數(shù)再到視頻流做報警邏輯測試。靜態(tài)圖的優(yōu)勢是結果可對照、可復現(xiàn)調參效率高。我習慣的做法是第一步準備5-10張不同場景的靜態(tài)圖覆蓋單人、多人、稀疏、密集、光線差異等情形統(tǒng)一用同一組conf/iou跑一遍記錄每張圖的計數(shù)結果和誤檢情況。 第二步根據(jù)靜態(tài)圖的結果確定conf的合理區(qū)間再在視頻流上驗證報警邏輯的穩(wěn)定性。 第三步調試報警觸發(fā)時檢查是否還需要考慮目標進入/離開邊界的情況——比如畫面邊緣只露出一半的人在真實業(yè)務中算不算一個人這個業(yè)務口徑要在報警策略里明確區(qū)分。5.3 yolov5超參數(shù)速查表參數(shù)推薦值作用調參方向conf0.4檢測置信度閾值調低漏檢少但誤檢多調高誤檢少但漏檢多iou0.45NMS合并重疊框人群密集時調低至0.3-0.4size640推理輸入分辨率小目標多時調大至1280速度下降約4倍max_det300單圖最大檢測框數(shù)超密集場景要調大默認可到1000augmentFalse是否啟用測試時數(shù)據(jù)增強開啟后精度略升但速度慢2-3倍一般用不到有一項沒列進表但值得注意的model.classes [0]必須在推理前設置yolov5的hub接口在運行時讀取這個屬性做過濾而不是在模型初始化時。如果你在推理中途又加了其他類別的需求比如同時檢測person和car需要重新設置這個屬性。5.4 損失函數(shù)與后處理權重覆蓋負載不足時合理取舍人群計數(shù)場景和標準的檢測任務在指標上有一個區(qū)別標準檢測看重mAP人群計數(shù)的實際業(yè)務看重計數(shù)準確度即預測人數(shù)和真實人數(shù)的絕對誤差。這就意味著完全不去管檢測框的位置精度只統(tǒng)計數(shù)量也可以滿足需求。所以做一個取舍在人群分布稀疏的場景可以放心降低iou閾值換取更多檢測框因為重疊框的合并錯誤對計數(shù)的影響遠大于對mAP的影響。但在擁擠場景依賴高iou閾值反而會導致合并過度調整時需要額外測試。如果資源包里的代碼后續(xù)要擴展為多類別檢測記得調整model.classes為列表形式例如model.classes [0, 5]同時保留person的class_id0和bus的class_id5。從那以后我每次做目標檢測類項目都強制在第一天就把參數(shù)配置、閾值標定、緩存策略和Docker化流程走完一遍再進入具體業(yè)務開發(fā)。磨刀不誤砍柴工這套流程已經(jīng)幫我避開了很多次環(huán)境層面的返工。希望幫到你。本文還有配套的精品資源點擊獲取