摔倒檢測:原理、部署與調(diào)優(yōu))
簡介本資源是一套基于YOLOv5與OpenPose融合實現(xiàn)的摔倒/跌倒檢測Python工程面向計算機視覺初學(xué)者與安防、養(yǎng)老、智能監(jiān)控等場景開發(fā)者解決人體姿態(tài)異常識別這一典型行為分析問題。資源包共306個文件包含58個核心Python腳本含模型加載、姿態(tài)估計、傾角計算與報警邏輯、111張標注圖像與測試樣本、19個配置用YAML文件、14個Jupyter Notebook含YOLOv7對比實驗與TensorRT加速示例、6個演示視頻及4個預(yù)訓(xùn)練.pt模型整體壓縮后136.9MB。已有3713人學(xué)習(xí)下載內(nèi)容覆蓋從單幀圖片檢測到視頻流實時分析的完整流程附帶B站效果演示視頻鏈接并整合了TensorRT與ONNX Runtime部署方案、多版本YOLO對比實驗及訓(xùn)練日志緩存文件便于讀者理解模型選型依據(jù)與工程落地關(guān)鍵點。 前陣子接了個摔倒檢測的小項目甲方要求能檢測視頻也能檢測圖片交付的是Python源碼。我第一反應(yīng)就是拿YOLOv5和OpenPose組合一下YOLOv5負責把人從畫面里框出來OpenPose負責提取骨骼關(guān)鍵點然后根據(jù)關(guān)鍵點的高度、角度和速度變化來判斷有沒有摔倒。整個過程寫下來大概一千多行代碼視頻和圖片都能跑今天把思路和踩過的坑都翻出來說說。如果你正準備做類似的人體行為識別、異常姿態(tài)檢測這篇應(yīng)該能幫你少走不少彎路尤其是YOLOv5和OpenPose兩個模型怎么銜接、摔倒判定閾值怎么定我會講得很細。1. 項目整體設(shè)計與思路拆解1.1 這個摔倒檢測系統(tǒng)到底解決了什么問題摔倒檢測不是新概念但落地難。早期我用過傳統(tǒng)的背景差分法先建背景模型再提取前景目標最后用寬高比、輪廓面積做判斷。效果一言難盡光照一變化就誤報人蹲下來撿東西也會觸發(fā)報警坐個板凳也可能被認為是摔倒。后來換成深度學(xué)習(xí)方案問題就清晰多了先檢測人再分析姿態(tài)最后根據(jù)姿態(tài)變化判定摔倒。這個項目要同時支持圖片和視頻。圖片檢測相對簡單輸入一張圖輸出標注框和判斷結(jié)果。視頻就不一樣了需要處理連續(xù)幀之間的時間關(guān)系比如關(guān)鍵點高度的突變、速度的異常變化這些都需要設(shè)計狀態(tài)邏輯。所以項目最終拆成了三個模塊目標檢測模塊、姿態(tài)估計模塊、摔倒邏輯判斷模塊。三個模塊相互獨立替換起來也方便。1.2 技術(shù)組合選型的邏輯為什么是YOLOv5加OpenPose市面上有成熟的單人姿態(tài)估計方案比如MediaPipe Pose、MoveNet也有YOLOv5的Pose變體。但甲方明確在標題里寫了YOLOv5和OpenPose說明要么任務(wù)單要求這么搭要么他們需要兼容已有的OpenPose生態(tài)。我做的時候也對比過發(fā)現(xiàn)這個組合其實很科學(xué)主要有三點原因。第一YOLOv5最擅長的是目標檢測它能把畫面里所有“人”的邊界框快速找出來而且對遮擋、小目標的效果比較穩(wěn)。OpenPose雖然是端到端的姿態(tài)估計模型但直接跑整張圖對算力要求高多人場景下延遲會明顯上升。先用YOLOv5把人框出來再把框內(nèi)區(qū)域送給OpenPose檢測精度和速度都能兼顧。第二YOLOv5和OpenPose是“檢測姿態(tài)”的經(jīng)典流水線前者的輸出目標框和后者的輸入圖像區(qū)域天然兼容不需要做復(fù)雜的坐標轉(zhuǎn)換。第三兩套模型都有大量預(yù)訓(xùn)練權(quán)重和現(xiàn)成部署案例遇到問題容易排查不會卡在環(huán)境依賴上。當然也可以只用OpenPose一個人體關(guān)鍵點檢測模型來完成摔倒判斷但那樣得自己做人的檢測和跟蹤代碼量會大不少。YOLOv5的作用更像是給OpenPose指定“作業(yè)區(qū)域”讓姿態(tài)估計更聚焦。這個組合的本質(zhì)是把一個復(fù)雜任務(wù)拆成兩個簡單任務(wù)降低單模型壓力這也是我推薦這種架構(gòu)的原因。2. 環(huán)境準備與模型部署2.1 Python虛擬環(huán)境與依賴安裝環(huán)境是第一個坑。我的建議是別直接在系統(tǒng)Python里裝容易把全局依賴搞亂。我習(xí)慣用conda創(chuàng)建專用環(huán)境Python版本選3.8或3.9太新的版本有些底層庫還沒適配。我自己用的是Python 3.8.10YOLOv5官方要求Python3.7OpenPose的Python接口在3.8下很穩(wěn)定。創(chuàng)建環(huán)境后先安裝YOLOv5的依賴# 創(chuàng)建環(huán)境 conda create -n fall_detection python3.8 conda activate fall_detection # 安裝PyTorch注意CUDA版本 pip install torch1.12.1 torchvision0.13.1 --extra-index-url https://download.pytorch.org/whl/cu113 # 克隆YOLOv5并安裝依賴 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtOpenPose的Python依賴更敏感。官方OpenPose需要編譯C代碼如果只想快速跑通建議使用已經(jīng)編譯好的wheel包或者下載OpenPose的Python API后手動編譯。我這里用的是openpose-python這個封裝本質(zhì)上還是調(diào)用C庫但省去了很多編譯麻煩。安裝命令pip install openpose-python如果安裝失敗常見原因是缺少OpenCV、numpy等底層依賴先把這兩個基礎(chǔ)庫裝好再裝OpenPose順序很重要。Windows用戶還需要安裝Visual Studio的C運行庫否則運行時會提示找不到DLL文件。2.2 YOLOv5和OpenPose模型獲取YOLOv5的權(quán)重文件很好找官方提供yolov5s.pt、yolov5m.pt等多個版本。我測試時用yolov5s.pt體積小CPU上也能跑精度在摔倒檢測場景里完全夠用。下載方式也很簡單直接運行YOLOv5倉庫里的下載腳本python -c from yolov5 import utils; utils.download_weights()或者直接從GitHub Release頁面下載權(quán)重文件。OpenPose的模型文件需要單獨下載官方提供了多個關(guān)鍵點模型這個項目用的是25點模型對應(yīng)文件是pose_iter_440000.caffemodel和pose_deploy_linevec.prototxt。下載后放在OpenPose的models/pose/coco/目錄下。沒有模型權(quán)重OpenPose只能報錯。網(wǎng)上有很多網(wǎng)盤分享但我不建議亂下載容易被篡改。最好是去OpenPose官方官方GitHub倉庫的README里找官方下載鏈接穩(wěn)定且安全。下載慢可以用鏡像站或者連接手機熱點試試文件大約200MB左右。2.3 環(huán)境坑位預(yù)警這里提前說幾個高概率會遇到的問題免得你卡住。第一個是OpenCV版本沖突。YOLOv5要求opencv-pythonOpenPose有時候會依賴opencv-contrib-python兩個包同時存在會造成混亂。我的做法是統(tǒng)一用opencv-python遇到OpenPose報找不到某些函數(shù)時再切換到opencv-contrib-python。優(yōu)先保證YOLOv5正常運行因為OpenPose的報錯信息更友好容易排查。第二個是protobuf版本問題。OpenPose早期版本依賴的protobuf和PyTorch要求的版本不一致典型報錯是“Cannot import google.protobuf”。解決辦法是固定protobuf版本pip install protobuf3.20.1第三個是CUDA版本和cuDNN。如果你沒有GPU建議直接用CPU模式運行YOLOv5用CPU跑OpenPose也可以跑CPU就是慢一些。有GPU的話要確保PyTorch的CUDA版本和系統(tǒng)驅(qū)動里的CUDA版本匹配否則會出現(xiàn)couldnt load CUDA library錯誤。我的測試機是RTX 3060裝了CUDA 11.3PyTorch也選了cu113版本這樣最穩(wěn)。3. 核心代碼結(jié)構(gòu)與關(guān)鍵邏輯實現(xiàn)3.1 人體檢測模塊把YOLOv5封裝成檢測器YOLOv5官方提供了一套完整的推理代碼但我們要嵌入到自己的項目里最好封裝成一個類方便后續(xù)調(diào)用。我這里用了一個輕量封裝核心是加載模型、處理圖片、過濾person類別。import cv2 import torch import numpy as np class YOLOv5Detector: def __init__(self, weightsyolov5s.pt, devicecpu, conf_thres0.4): self.model torch.hub.load(ultralytics/yolov5, custom, pathweights) self.model.conf conf_thres self.model.classes [0] # 只保留person類別 self.device device if device ! cpu and torch.cuda.is_available(): self.model.to(device) def detect(self, image): # image是BGR格式numpy數(shù)組 results self.model(image) boxes results.xyxy[0].cpu().numpy() # N * (x1, y1, x2, y2, conf, cls) return boxes這里有幾個細節(jié)要注意。self.model.classes [0]是強制只保留person類別因為刪除其他類別可以減少后處理負擔還能防止誤把其他物體當成人。conf_thres閾值默認設(shè)0.4可以根據(jù)場景調(diào)整如果檢測漏人就把閾值降低到0.25如果誤檢多就調(diào)高到0.5。YOLOv5輸出的坐標是原始圖像尺寸下的絕對坐標可以直接用來裁剪圖像。它還包括置信度和類別ID但我們已經(jīng)過濾成只有person所以主要用的是前四列坐標。3.2 姿態(tài)估計模塊把OpenPose接入并提取關(guān)鍵點OpenPose的Python API有多種調(diào)用方式。我這里用的是OpenPose官方封裝輸入一個numpy數(shù)組輸出每個人的關(guān)鍵點數(shù)組和熱度圖。關(guān)鍵點的坐標系和輸入圖像一致可以直接和YOLOv5的檢測框關(guān)聯(lián)。封裝代碼如下import pyopenpose as op class OpenPoseEstimator: def __init__(self, model_pathmodels/, keypoint_num25): params dict() params[model_folder] model_path params[keypoint_number] keypoint_num self.op_wrapper op.WrapperPython() self.op_wrapper.configure(params) self.op_wrapper.start() def estimate(self, image): # image為BGR圖 datum op.Datum() datum.cvInputData image self.op_wrapper.emplaceAndPop([datum]) return datum.poseKeypoints # shape (num_people, 25, 3)datum.poseKeypoints是一個數(shù)組每行對應(yīng)一個人每一行有25個關(guān)鍵點每個關(guān)鍵點有x、y、置信度三個值。注意如果畫面里沒有檢測到人這個數(shù)組會是None一定要做空值判斷否則后續(xù)代碼會報錯。由于我們先用YOLOv5框出了人可以直接把裁剪后的人體區(qū)域傳給OpenPose。這樣OpenPose只需要估計一個目標速度和準確率都會提高。但關(guān)鍵點是相對于裁剪區(qū)域的局部坐標需要換算回原圖坐標。具體做法是把YOLO框的左上角坐標加上局部關(guān)鍵點坐標公式很簡單original_x int(keypoint[0] box[0]) original_y int(keypoint[1] box[1])3.3 摔倒判定算法角度、高度和速度的綜合判斷摔倒判定是整個項目的靈魂。我采用的是多維度特征結(jié)合閾值判斷而不是單一指標這樣能大幅降低誤報漏報。下面詳細拆解。3.3.1 核心關(guān)鍵點選取OpenPose的25個關(guān)鍵點中最常用的是鼻子0脖子1左右肩2, 5左右髖8, 11左右膝9, 12左右踝10, 13摔倒判斷主要參考脖子、髖部和膝蓋這幾個點。相比頭部脖子點的位置更穩(wěn)定不容易因為低頭、抬頭動作而劇烈變化。髖部中心點則代表人體重心的大致位置用它來判斷人體高度下降很直觀。3.3.2 垂直高度與速度檢測摔倒最顯著的特征是人體重心短時間內(nèi)大幅下移。我計算髖部垂直坐標的平均值作為人體的垂直位置然后比較當前幀與前幾幀的差值。在視頻流中連續(xù)3到5幀內(nèi)髖部中心下降超過一定像素值就可能是摔倒。def get_center_y(keypoints): # 取左右髖關(guān)鍵點的平均y坐標 left_hip keypoints[8] right_hip keypoints[11] if left_hip[2] 0 and right_hip[2] 0: return (left_hip[1] right_hip[1]) / 2 return None這里的像素閾值和圖像分辨率強相關(guān)。如果圖像高度為720像素正常行走時髖部中心y坐標變化較平緩而摔倒瞬間可能下降200-300像素。所以閾值可以設(shè)置為圖像高度的0.2到0.3倍。我一般用0.25倍也就是720像素的圖設(shè)180像素。這個閾值需要根據(jù)攝像頭安裝高度和角度微調(diào)。3.3.3 身體傾斜角度檢測摔倒后人體通常會從直立變成水平或接近水平。我計算肩膀中心和髖部中心連線與垂直方向的夾角。站立時夾角接近0度嚴格說是肩膀在髖部正上方倒地時夾角變大接近90度。def compute_tilt_angle(keypoints): # 脖子(1)與髖部中心(8和11平均)連線 neck keypoints[1] hip_center (keypoints[8] keypoints[11]) / 2 vector (hip_center[0] - neck[0], hip_center[1] - neck[1]) # 垂直向量是(0, 1)計算夾角 angle math.atan2(vector[0], vector[1]) * 180 / math.pi return abs(angle)如果脖子y坐標大于髖部y坐標說明人處于下蹲或倒地狀態(tài)但只用坐標判斷還不夠。例如正常彎腰撿東西時脖子也會低于髖部所以需要結(jié)合速度檢測。3.3.4 最終判斷邏輯我把摔倒判斷做成一個狀態(tài)機先檢測人體關(guān)鍵點計算高度、角度、速度三個指標然后加權(quán)打分。具體規(guī)則是髖部中心下降速度超過閾值記一分。身體傾斜角度大于45度記一分。倒地后持續(xù)一段時間比如2秒沒有恢復(fù)豎直狀態(tài)記一分??偡执笥诘扔?分就判定為摔倒。這個邏輯的好處是單純彎腰撿東西可能角度分有但高度下降速度不夠或者很快就恢復(fù)不會觸發(fā)報警真正的摔倒往往同時滿足多個條件。def is_fall(keypoints_history): # keypoints_history保存最近5幀的關(guān)鍵點 fall_score 0 if check_height_drop(keypoints_history): fall_score 1 if check_tilt_angle(keypoints_history[-1]): fall_score 1 if check_duration(keypoints_history): fall_score 1 return fall_score 23.4 圖片與視頻推理流程圖片檢測流程最簡單讀圖YOLO檢測OpenPose估計跌倒判斷繪制結(jié)果輸出。視頻檢測則要處理幀序列我使用了隊列來保存歷史關(guān)鍵點每處理一幀就更新隊列。def process_video(video_path): cap cv2.VideoCapture(video_path) history deque(maxlen5) while True: ret, frame cap.read() if not ret: break boxes yolo.detect(frame) for box in boxes: crop frame[int(box[1]):int(box[3]), int(box[0]):int(box[2])] keypoints openpose.estimate(crop) if keypoints is not None: keypoints convert_to_original_coords(keypoints, box) history.append(keypoints) if len(history) 5: if is_fall(history): cv2.putText(frame, FALL, (int(box[0]), int(box[1])-10), ...) cv2.imshow(Fall Detection, frame)視頻幀率一般25到30fps5幀大約0.2秒足夠捕捉摔倒瞬間。如果網(wǎng)絡(luò)更慢可以把歷史幀數(shù)加大到10但延遲會增大建議根據(jù)實時性需求調(diào)整。4. 實操過程從終端命令到結(jié)果輸出4.1 測試素材準備在跑正式數(shù)據(jù)之前我建議你先準備兩類素材一類是正常行走、彎腰、坐下的視頻另一類是模擬摔倒的視頻。模擬摔倒可以用手機拍攝也可以從公開數(shù)據(jù)集里獲取。如果沒有專門數(shù)據(jù)可以先用一段動作片或體育視頻代替關(guān)鍵在于包含人體快速倒地的畫面。圖片測試素材可以用視頻截圖也可以去現(xiàn)成的標注數(shù)據(jù)集拿幾張多人場景圖。我測試時用過UR Fall Detection數(shù)據(jù)集里的樣例圖片也自己拍過幾張辦公室環(huán)境下的模擬圖。測試階段素材不求多但最好覆蓋以下情況單人場景、多人場景、遮擋場景、光照變化場景。這能檢驗?zāi)P偷姆夯芰Α?.2 運行檢測與參數(shù)調(diào)整我寫了一個統(tǒng)一的入口文件fall_detection.py通過命令行參數(shù)指定輸入源和輸出路徑python fall_detection.py --input test.jpg --output result.jpg python fall_detection.py --input test_video.mp4 --output result_video.mp4第一次運行時先別急著調(diào)整閾值用默認參數(shù)跑一遍觀察結(jié)果。如果發(fā)現(xiàn)檢測框抖動厲害可以把YOLOv5的conf閾值調(diào)高到0.5。如果發(fā)現(xiàn)OpenPose漏檢可以把conf閾值調(diào)低到0.3同時檢查人體區(qū)域裁剪是否正確。如果摔倒判斷總不出來可以臨時打印歷史關(guān)鍵點看高度和角度值變化再針對性調(diào)整閾值。我調(diào)試時常用一個可視化函數(shù)把關(guān)鍵點和角度畫到畫面上這樣能直觀看到判斷依據(jù)。比如在每個人體上方顯示當前角度值、髖部高度變化值、最終得分。這一招在調(diào)參階段非常管用。4.3 結(jié)果保存與異常報警檢測結(jié)果要可復(fù)現(xiàn)不能只在窗口里看。圖片檢測直接保存帶標注的圖片視頻檢測則使用OpenCV的VideoWriter保存輸出視頻。在摔倒觸發(fā)時我還會把當前幀保存為單獨的JPG文件便于事后核查。如果后續(xù)要接入報警系統(tǒng)可以在is_fall返回True時調(diào)用一個回調(diào)函數(shù)。比如發(fā)送微信通知、寫入日志、播放聲音等。這個項目的核心是檢測所以我在報警這塊留了接口真正接物聯(lián)網(wǎng)或短信平臺時直接擴展就行。5. 常見問題排查與調(diào)優(yōu)心得5.1 模型加載慢怎么破YOLOv5和OpenPose兩個模型加一起首次加載可能要20秒甚至更久主要是OpenPose的caffemodel比較大加載和初始化都需要時間。如果是多次運行檢測建議只在程序啟動時加載一次不要每次調(diào)用都重新初始化。如果每次運行腳本都加載重復(fù)模型可以把兩個模型封裝成全局單例或者用Python的模塊級對象這樣后續(xù)調(diào)用會快很多。對于CPU用戶可以嘗試用更小的YOLOv5模型比如yolov5n.pt體積只有幾MB精度略低但加載快。OpenPose模型的輸入尺寸也可以調(diào)小把圖像resize到368x368或者256x256檢測速度會明顯提升代價是關(guān)鍵點精度略微下降。摔倒檢測對精度要求不算特別極致尺寸小一點完全夠用。5.2 誤判太多怎么調(diào)誤判是所有摔倒檢測系統(tǒng)的通病。最常見的誤判場景是人蹲下?lián)鞏|西、坐下、彎腰系鞋帶。我的經(jīng)驗是從兩個方向入手。第一個方向是增加“恢復(fù)時間”判斷。真正的摔倒發(fā)生后人體會長時間保持倒地狀態(tài)而彎腰撿東西通常2秒內(nèi)就會恢復(fù)直立體態(tài)。我在代碼里增加了跌倒持續(xù)時間檢測當觸發(fā)跌倒后持續(xù)檢測后續(xù)幀數(shù)如果短時間內(nèi)恢復(fù)直立就取消報警。這個邏輯可以有效減少瞬時誤判。第二個方向是調(diào)整OpenPose置信度閾值。OpenPose給出的每個關(guān)鍵點都有置信度如果某個關(guān)鍵點置信度低于0.3說明這個點并不可靠在計算角度時應(yīng)該忽略它。比如如果髖部關(guān)鍵點置信度很低就不應(yīng)該用高度下降作為判斷依據(jù)。這能避免因姿態(tài)估計誤差導(dǎo)致的誤報。5.3 幀率太低怎么辦如果視頻實時幀率低于10fps就需要優(yōu)化了。先看瓶頸在哪YOLOv5檢測人和OpenPose姿態(tài)估計都可能占大量時間??梢园裏OLOv5的輸入尺寸從默認640降到416OpenPose輸入尺寸同步降低。另一個思路是隔幀檢測比如每2幀或3幀做一次完整檢測中間幀沿用上一幀的結(jié)果。摔倒動作持續(xù)大概0.5到1秒隔幀檢測不會漏掉關(guān)鍵動作但速度能提升兩三倍。如果用的是CPU建議直接關(guān)閉OpenCV的GUI窗口顯示只保存輸出視頻能節(jié)省不少資源。5.4 環(huán)境依賴沖突處理最常見的問題就是前面提到的OpenCV和protobuf沖突。我專門整理了一個環(huán)境修復(fù)清單報錯信息可能原因解決辦法cannot import name detect from torchPyTorch版本過低升級到1.10以上ImportError: No module named pyopenposeOpenPose編譯失敗安裝openpose-python或者編譯源碼TypeError: NoneType object is not subscriptable檢測不到關(guān)鍵點增加空值判斷或降低conf閾值Unexpected key in model state_dict權(quán)重文件與模型版本不匹配重新下載對應(yīng)權(quán)重CUDA error: device-side assert triggered圖像尺寸為0或非法檢查裁剪區(qū)域是否越界遇到環(huán)境問題先縮小范圍到哪一步報錯然后單獨驗證不要直接重裝整個環(huán)境。我曾經(jīng)因為OpenPose編譯失敗以為是C編譯器問題折騰了半天最后發(fā)現(xiàn)只是需要安裝一個Visual Studio的Redistributable包血淚教訓(xùn)。6. 后續(xù)擴展方向與個人體會這次項目做完我后來又做了兩個方向的擴展一個是把跌倒檢測的判定結(jié)果接入到攝像頭RTSP流里做成一個實時監(jiān)控服務(wù)另一個是把OpenPose換成輕量化的姿態(tài)估計模型讓整個系統(tǒng)可以在低功耗設(shè)備上跑。如果你的項目也要落地建議優(yōu)先優(yōu)化推理速度因為摔倒檢測對實時性要求很高晚報警5秒可能就失去意義。我個人在實際操作中的體會是摔倒檢測的重心不在模型選型而在于判定邏輯的魯棒性。YOLOv5和OpenPose都是成熟的工具只要能跑通準確率不會差太多。真正需要花心思的地方是各種閾值參數(shù)的調(diào)節(jié)、狀態(tài)機的設(shè)計、誤判和漏判的平衡。起步階段建議先用視頻離線測把邏輯跑順了再上實時視頻流這樣排查問題會快很多。最后分享一個小技巧在調(diào)試摔倒判定時可以把人的歷史軌跡畫出來比如用不同顏色標記過去5幀的髖部中心這樣摔倒瞬間的軌跡突變會非常直觀。看著軌跡調(diào)閾值效率比悶頭調(diào)參數(shù)高得多。下一篇可以再聊聊怎么把這套代碼封裝成HTTP接口或者在Jetson這類嵌入式設(shè)備上部署有需要的讀者可以留言交流。本文還有配套的精品資源點擊獲取