習(xí)駕駛行為監(jiān)測(cè)預(yù)警系統(tǒng):目標(biāo)檢測(cè)與姿態(tài)估計(jì)實(shí)戰(zhàn)解析)
簡(jiǎn)介這是一份基于深度學(xué)習(xí)的駕駛者行為監(jiān)測(cè)預(yù)警系統(tǒng)完整源碼與技術(shù)報(bào)告面向計(jì)算機(jī)、電子信息等專業(yè)學(xué)生適用于課程設(shè)計(jì)、期末大作業(yè)或畢業(yè)設(shè)計(jì)。系統(tǒng)融合深度學(xué)習(xí)與車聯(lián)網(wǎng)技術(shù)通過(guò)語(yǔ)音識(shí)別、面部狀態(tài)識(shí)別、肢體動(dòng)作識(shí)別三大模塊對(duì)駕駛者潛在危險(xiǎn)行為進(jìn)行實(shí)時(shí)評(píng)估并發(fā)出警報(bào)可有效降低交通事故風(fēng)險(xiǎn)。資源共包含43個(gè)文件以Python源碼為主21個(gè)py配套技術(shù)報(bào)告PDF、說(shuō)明文檔及模型文件壓縮包整體21.76MB。源碼模塊劃分清晰VGGNet、CNN等網(wǎng)絡(luò)用于肢體與面部識(shí)別語(yǔ)音模塊實(shí)現(xiàn)語(yǔ)音交互便于讀者按模塊理解數(shù)據(jù)加載、模型構(gòu)建、訓(xùn)練測(cè)試及攝像頭實(shí)時(shí)預(yù)測(cè)全流程。目前已有619人學(xué)習(xí)下載。通過(guò)本項(xiàng)目可掌握深度學(xué)習(xí)在駕駛場(chǎng)景下的完整應(yīng)用思路技術(shù)報(bào)告和README能輔助理解整體架構(gòu)與實(shí)現(xiàn)細(xì)節(jié)適合需要完整項(xiàng)目參考并做二次開(kāi)發(fā)的學(xué)習(xí)者。1. 把駕駛行為識(shí)別做成能現(xiàn)場(chǎng)演示的畢業(yè)設(shè)計(jì)這套深度學(xué)習(xí)源碼包里裝了什么“基于深度學(xué)習(xí)的駕駛者行為監(jiān)測(cè)預(yù)警系統(tǒng)”這類題目在人工智能方向里不算冷門難的是把它從“離線跑通一個(gè)模型”升級(jí)成“攝像頭前實(shí)時(shí)看到行為并觸發(fā)預(yù)警”。這個(gè)源碼包選的正是后者而且它把畢業(yè)設(shè)計(jì)最耗時(shí)間的膠水代碼提前做完了視頻讀取、目標(biāo)檢測(cè)、關(guān)鍵點(diǎn)提取、行為規(guī)則、報(bào)警輸出全串在一條流水線上配套的技術(shù)報(bào)告又能直接支撐“研究背景、方案選型、實(shí)驗(yàn)結(jié)果”這幾章寫作。適合正在做深度學(xué)習(xí)畢業(yè)設(shè)計(jì)或期末大作業(yè)、想要一份能現(xiàn)場(chǎng)演示并拿高分的學(xué)生也適合想快速搭一套駕駛行為監(jiān)控 demo 的開(kāi)發(fā)者。它不是論文的復(fù)述而是一個(gè)打開(kāi)就能照著復(fù)現(xiàn)的工程模板。2. 行為識(shí)別鏈路目標(biāo)檢測(cè)與姿態(tài)關(guān)鍵點(diǎn)如何協(xié)同工作2.1 從視頻幀到行為結(jié)論目標(biāo)檢測(cè)、姿態(tài)估計(jì)與時(shí)序狀態(tài)機(jī)駕駛行為監(jiān)測(cè)最容易踩的坑是把“疲勞駕駛識(shí)別”當(dāng)成一個(gè)端到端分類任務(wù)。真實(shí)打開(kāi)攝像頭以后畫面里有擋風(fēng)玻璃、方向盤、儀表臺(tái)、手臂和手機(jī)如果不先把“人”從畫面里分出來(lái)任何行為概率都會(huì)被背景淹沒(méi)。這套源碼的鏈路是一套比較典型的工程方案視頻幀輸入目標(biāo)檢測(cè)模型先定位駕駛員的人體區(qū)域再細(xì)分出頭部、手部候選框在定位到的區(qū)域上提取面部關(guān)鍵點(diǎn)和手部關(guān)鍵點(diǎn)得到眼睛、嘴巴、頭、手的位置用幾何指標(biāo)計(jì)算單幀狀態(tài)比如眼睛縱橫比 EAR、嘴部開(kāi)合度、手部與耳朵的重疊程度把連續(xù)多幀的狀態(tài)送進(jìn)一個(gè)時(shí)序平滑邏輯或狀態(tài)機(jī)最終決定是否觸發(fā)疲勞、分心、接打電話、吸煙預(yù)警。為什么要拆成兩段而不是用一個(gè)網(wǎng)絡(luò)直接輸出行為類別一是目標(biāo)檢測(cè)對(duì)光照和遮擋的魯棒性更好先定位人再判斷行為輸入更干凈二是兩段可以解耦調(diào)參攝像頭安裝角度變了只重新標(biāo)注檢測(cè)數(shù)據(jù)行為規(guī)則完全不用動(dòng)。這個(gè)設(shè)計(jì)在技術(shù)報(bào)告里也對(duì)應(yīng)做了方案對(duì)比答辯時(shí)是“為什么不用純分類模型”的現(xiàn)成論據(jù)。行為判斷的代碼在源碼包里通常長(zhǎng)這樣def infer_behavior(det_result, landmarks, frame_id): # det_result 是檢測(cè)輸出的 dict包含 head 與 hand 兩類框 head_box det_result.get(head) hand_boxes det_result.get(hand, []) if head_box is None: return no_driver, 0.0 left_eye landmarks[left_eye] # 6 個(gè)關(guān)鍵點(diǎn) right_eye landmarks[right_eye] ear (eye_aspect_ratio(left_eye) eye_aspect_ratio(right_eye)) / 2.0 mar mouth_aspect_ratio(landmarks[mouth]) if ear 0.20 and mar 0.30: return distracted, 0.8 if hand_near_ear(hand_boxes, landmarks[head]): return phone_call, 0.7 return normal, 0.9這里eye_aspect_ratio計(jì)算的是上下眼瞼關(guān)鍵點(diǎn)之間的歐氏距離與左右眼角距離的比值正常的情況下 EAR 在 0.25 到 0.35 之間低于 0.2 基本可以認(rèn)定眼睛閉合mouth_aspect_ratio同理用來(lái)捕捉打哈欠的特征。返回的置信度不直接決定報(bào)警它只作為下游狀態(tài)機(jī)的權(quán)重。參數(shù)說(shuō)明det_result的字段名稱取決于訓(xùn)練檢測(cè)模型時(shí)用的標(biāo)注類別源碼包里一般把“頭”和“手”作為獨(dú)立類別。換數(shù)據(jù)集后這些 key 一定要同步改否則一運(yùn)行就是KeyError。0.20和0.30不是全局通用攝像頭離人遠(yuǎn)、角度側(cè)偏時(shí)這兩個(gè)值都要重新采幾組數(shù)據(jù)再定直接照搬通常會(huì)有不同程度的誤報(bào)。2.2 行為規(guī)則與預(yù)警邏輯沒(méi)有精細(xì)行為標(biāo)注也能先跑通想訓(xùn)練一個(gè)“接電話/吸煙/疲勞犯困”的端到端分類器需要大量帶時(shí)間軸標(biāo)注的駕駛員行為視頻這在一兩周的課程設(shè)計(jì)周期內(nèi)不太現(xiàn)實(shí)。源碼包采用的是“幾何規(guī)則 持續(xù)幀計(jì)數(shù)”的低成本方案把行為識(shí)別退化成可解釋的狀態(tài)判斷行為判定條件觸發(fā)時(shí)長(zhǎng)疲勞PERCLOS 閉眼時(shí)間占比超過(guò) 0.4或打哈欠次數(shù)超限連續(xù) 2 秒接打電話手部關(guān)鍵點(diǎn)靠近耳部/頭部區(qū)域且保持不動(dòng)持續(xù) 3 秒吸煙手部關(guān)鍵點(diǎn)靠近嘴部區(qū)域且有短暫往復(fù)動(dòng)作持續(xù) 2 秒分心駕駛頭部偏轉(zhuǎn)角度大于 45°或視線長(zhǎng)時(shí)間偏離前方持續(xù) 1.5 秒這張表的閾值就是這套系統(tǒng)最重要的調(diào)參對(duì)象。它的弱點(diǎn)也很明顯手靠近臉既可能是打電話也可能只是揉眼睛側(cè)臉角度下“手部靠近嘴”的位置判斷會(huì)偏差很大。所以源碼里通常加的是“與”條件比如接打電話必須同時(shí)滿足“手部框中心點(diǎn)落在耳朵附近”和“該狀態(tài)在時(shí)間窗口內(nèi)持續(xù)出現(xiàn)”只靠一兩幀的偶然重疊不會(huì)觸發(fā)報(bào)警。另一種有效的防御手段是把“手在耳邊”和“頭部姿態(tài)估計(jì)”結(jié)合起來(lái)。低頭看手機(jī)與疲勞閉眼的頭部落差比較相似如果不加“眼睛是否睜開(kāi)”這個(gè)特征系統(tǒng)很難區(qū)分。源碼包在這塊還會(huì)把視線方向也合并進(jìn)規(guī)則拿不準(zhǔn)的時(shí)候優(yōu)先信頭部姿態(tài)的連續(xù)性而不是某一個(gè)瞬間的檢測(cè)分?jǐn)?shù)。時(shí)序?yàn)V波在代碼里通常長(zhǎng)這樣class BehaviorSmoother: def __init__(self, window15, min_hits10): self.window window self.min_hits min_hits self.frames [] def push(self, behavior): self.frames.append(behavior) if len(self.frames) self.window: self.frames.pop(0) hits sum(1 for b in self.frames if b behavior) return behavior if hits self.min_hits else None這是一個(gè)滑窗投票器窗口window15幀某行為出現(xiàn)min_hits10次及以上才被確認(rèn)。按 30fps 的攝像頭折算窗口大約是 0.5 秒既能濾掉閃斷也不會(huì)把真實(shí)預(yù)警拖沒(méi)。min_hits設(shè)得過(guò)小等于沒(méi)濾波設(shè)得過(guò)大則預(yù)警總是慢半拍答辯演示時(shí)很容易被看出延遲。在演示場(chǎng)景里證明這套機(jī)制有效的方法是準(zhǔn)備一段“正常駕駛 2 分鐘、接打電話 1 分鐘”的錄像回灌系統(tǒng)后統(tǒng)計(jì)輸出行為隨時(shí)間的變化。這個(gè)測(cè)試可以直接當(dāng)技術(shù)報(bào)告“實(shí)驗(yàn)與結(jié)果分析”的數(shù)據(jù)支撐比空口說(shuō)模型效果好更有說(shuō)服力。3. 環(huán)境搭建與快速?gòu)?fù)現(xiàn)從解壓源碼到看到第一屏預(yù)警3.1 依賴清單與版本建議先解決 import 報(bào)錯(cuò)再談效果這套源碼包第一步卡住最多的是環(huán)境依賴。雖然包內(nèi)通常有requirements.txt但直接pip install -r requirements.txt不一定一次成功因?yàn)?torch、opencv、numpy 三者的版本互相牽制。我拆這類包的習(xí)慣是先把版本限制死而不是裝最新版依賴建議版本出現(xiàn)問(wèn)題的表現(xiàn)Python3.8 ~ 3.103.11 上部分模型導(dǎo)出與 onnxruntime 報(bào)錯(cuò)torch/torchvision1.12 ~ 2.0版本跨度過(guò)大直接 ImportErroropencv-python4.5 ~ 4.8cv2.dnn 接口在 4.4 后有破壞性變更numpy1.21 ~ 1.24numpy 2.x 會(huì)讓部分舊代碼的 dtype 判斷出錯(cuò)PyYAML6.0 及以上太低讀不了新版配置文件安裝之前先確認(rèn)顯卡再?zèng)Q定 torch 的下載版本。NVIDIA 顯卡環(huán)境下先執(zhí)行nvidia-smi輸出里的 CUDA 版本是驅(qū)動(dòng)支持的版本不是讓你照抄去裝而是告訴你最高能支持到什么范圍的預(yù)編譯 torch。裝好之后不要急著跑完整系統(tǒng)先用一段“導(dǎo)入體檢”確認(rèn)環(huán)境是通的import torch import cv2 import numpy as np import yaml print(torch:, torch.__version__) print(cuda_available:, torch.cuda.is_available()) print(cv2:, cv2.__version__) print(numpy:, np.__version__)如果cuda_available打印False說(shuō)明裝的是 CPU 版 torch后面演示還能跑只是幀率會(huì)很難看如果 import 直接報(bào)錯(cuò)就按報(bào)錯(cuò)信息把對(duì)應(yīng)包改成表里的版本不要?jiǎng)釉创a。3.2 三種輸入源先跑視頻再跑圖片最后接攝像頭主程序一般支持三種輸入方式命令行參數(shù)大致是# 用本地視頻驗(yàn)證整體鏈路推薦先跑這個(gè) python main.py --source test_data/driver.mp4 # 用攝像頭做實(shí)時(shí)演示0 是默認(rèn)攝像頭索引 python main.py --source 0 # 用圖片目錄做單幀檢測(cè)方便生成報(bào)告里的效果圖 python main.py --source test_data/images --save_dir outputs/--source參數(shù)決定輸入源它貫穿整個(gè)主循環(huán)。視頻與攝像頭走同一套讀幀邏輯只是來(lái)源不同圖片目錄會(huì)跳過(guò)時(shí)序判斷只輸出檢測(cè)框和關(guān)鍵點(diǎn)圖。答辯前想出“效果圖”用第三種方式從幾十張照片里挑幾張姿態(tài)自然的放進(jìn)論文或 PPT比直接截視頻清晰得多。第一次跑通建議先跑視頻文件因?yàn)橐曨l掉幀不會(huì)影響觀看者判斷而攝像頭一旦卡頓評(píng)委很容易追問(wèn)“為什么沒(méi)有實(shí)時(shí)性”。視頻鏈路驗(yàn)證通過(guò)后再接攝像頭環(huán)境、模型、規(guī)則三塊能逐一驗(yàn)收。程序啟動(dòng)后如果只顯示綠框、沒(méi)有任何行為提示這屬于正常現(xiàn)象畢竟“正常駕駛”狀態(tài)下本來(lái)就沒(méi)有預(yù)警。想驗(yàn)證報(bào)警邏輯拿手機(jī)放到耳邊過(guò) 3 秒左右看是否彈提醒。如果完全沒(méi)反應(yīng)優(yōu)先檢查配置里的alert.enabled開(kāi)關(guān)很多包默認(rèn)關(guān)掉避免開(kāi)發(fā)調(diào)試時(shí)彈個(gè)不停。3.3 配置文件里必改的五個(gè)字段這套系統(tǒng)把可調(diào)項(xiàng)基本收斂到了 YAML 配置里不要再去翻 Python 源碼改參數(shù)。打開(kāi)配置文件后這五個(gè)字段幾乎是必改的model_path: weights/best.pt # 一定要檢查路徑是否存在 conf_thres: 0.35 # 檢測(cè)置信度閾值 alert_enabled: true # 預(yù)警總開(kāi)關(guān) alert_timeout: 3.0 # 同一行為去重時(shí)間 camera_index: 0 # 攝像頭索引外接攝像頭常需要改 1model_path最容易翻車因?yàn)樵创a包解壓后目錄層級(jí)一變相對(duì)路徑就會(huì)失效運(yùn)行時(shí)報(bào)“文件不存在”又不說(shuō)清是哪個(gè)文件。conf_thres控制檢測(cè)框的敏感度降到 0.3 會(huì)撿回一些低置信度目標(biāo)但也會(huì)多一些誤框升高到 0.5 則更干凈但可能漏檢。alert_timeout是同一行為觸發(fā)后的冷卻時(shí)間設(shè) 3 秒意思是同一個(gè)行為在 3 秒內(nèi)不會(huì)重復(fù)報(bào)警否則打電話不掛斷界面會(huì)一直被報(bào)警刷屏。4. 訓(xùn)練參數(shù)與效果調(diào)試讓模型認(rèn)得出你的攝像頭4.1 數(shù)據(jù)集與標(biāo)注格式先解決“模型認(rèn)不認(rèn)”的問(wèn)題很多同學(xué)拿到源碼后直接拿預(yù)訓(xùn)練權(quán)重跑效果在自己攝像頭上總比演示差一截原因不是模型不行而是訓(xùn)練數(shù)據(jù)里沒(méi)有“你的攝像頭”這個(gè)視角。微調(diào)前要檢查數(shù)據(jù)集目錄結(jié)構(gòu)常見(jiàn)標(biāo)準(zhǔn)結(jié)構(gòu)長(zhǎng)這樣datasets/driver/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ └── val/ └── driver.yamldriver.yaml里最關(guān)鍵的是類別名names: 0: head 1: hand改數(shù)據(jù)之后第一步是確認(rèn)names與你的標(biāo)注順序一致。訓(xùn)練腳本一般支持 YOLO 格式的 txt 標(biāo)注每行依次是class x_center y_center width height數(shù)值都是相對(duì)于圖片寬高的比例。換用別家的可視化標(biāo)注工具導(dǎo)出時(shí)容易把坐標(biāo)從xyxy格式錯(cuò)當(dāng)成xywh格式訓(xùn)練損失會(huì)降不下來(lái)現(xiàn)象就是模型什么都檢不出來(lái)。如果只是想適配自己的攝像頭而不是完整重訓(xùn)做法可以是用自己攝像頭拍 10 分鐘駕駛畫面每隔幾幀抽一張差不多能湊 300500 張圖只標(biāo)頭部和手部?jī)深惙诺皆杏?xùn)練集后面微調(diào) 20 到 30 輪。這個(gè)數(shù)據(jù)量足以讓模型學(xué)會(huì)新的視角和光照。4.2 訓(xùn)練腳本超參epoch、batch、學(xué)習(xí)率要聯(lián)動(dòng)看以常見(jiàn)的 YOLO 系 pose 任務(wù)為例訓(xùn)練參數(shù)一般長(zhǎng)這樣model: yolo_v8n task: pose epochs: 100 batch: 16 imgsz: 640 lr0: 0.01 lrf: 0.01 device: 0這些參數(shù)不是孤立數(shù)字。batch受顯存限制6G 顯存建議 88G 以上可以試 16batch 翻倍時(shí)學(xué)習(xí)率也應(yīng)該同步放大否則收斂會(huì)變慢。epochs并不是越多越準(zhǔn)畢設(shè)數(shù)據(jù)量下 60 到 80 輪基本收斂再往后主要是在擬合訓(xùn)練集。lr0是初始學(xué)習(xí)率0.01 是常見(jiàn)起點(diǎn)如果損失前期震蕩降到 0.005 通常更穩(wěn)lrf控制最終衰減比例lrf0.01意思是最后一段學(xué)習(xí)率是初始值的百分之一。imgsz640是為了匹配大多數(shù)預(yù)訓(xùn)練模型的輸入尺寸改小到 416 會(huì)提速度但掉精度駕駛場(chǎng)景更建議保持 640。訓(xùn)練完成后模型目錄里通常有best.pt和last.pt。前者是驗(yàn)證集分?jǐn)?shù)最高的后者是最后一輪的。演示時(shí)務(wù)必用best.pt不要因?yàn)閘ast.pt在目錄里排后面就用了它兩種權(quán)重的可視化效果差距肉眼可見(jiàn)。4.3 從損失曲線、mAP 和混淆矩陣判斷是不是過(guò)擬合訓(xùn)練跑完別只看一個(gè)總損失至少要看三樣?xùn)|西box_loss曲線逐輪下降是正常后期反彈說(shuō)明在過(guò)擬合cls_loss和姿態(tài)回歸損失出現(xiàn)平臺(tái)期可以先停mAP50和mAP50-95前者看粗判準(zhǔn)后者更嚴(yán)格。驗(yàn)證集上 mAP50 達(dá)不到 0.6 的模型不適合直接做答辯演示效果會(huì)非常不穩(wěn)定。正常情況下簡(jiǎn)單場(chǎng)景能到 0.85 以上關(guān)鍵點(diǎn)回歸的 mAP 則稍微低一些屬于正常范圍。打開(kāi)訓(xùn)練輸出目錄里的confusion_matrix.png如果對(duì)角線上的值明顯偏低最優(yōu)先做的不是換更大的網(wǎng)絡(luò)而是補(bǔ)負(fù)樣本。負(fù)樣本不是背景圖它必須有駕駛員但姿勢(shì)不屬于任何目標(biāo)行為類別比如手抬到方向盤上緣、揉鼻子、喝完水放下杯子這些動(dòng)作。補(bǔ) 300 到 500 張負(fù)樣本微調(diào) 20 輪通常比把 epoch 翻倍對(duì)降低誤報(bào)更有效。還有一類常見(jiàn)問(wèn)題發(fā)生在推理階段模型訓(xùn)練的置信度大多落在 0.3 到 0.5而演示腳本把閾值寫成了 0.7導(dǎo)致大量目標(biāo)被過(guò)濾掉。先用較低閾值跑一段測(cè)試觀察單幀輸出框的數(shù)量和穩(wěn)定程度再逐步往回升找到置信度分布的“懸崖點(diǎn)”這個(gè)操作比反復(fù)調(diào)網(wǎng)絡(luò)參數(shù)更直接。5. 駕駛行為監(jiān)測(cè)預(yù)警系統(tǒng)避坑指南五個(gè)高頻翻車點(diǎn)5.1 現(xiàn)象pip 裝完依賴import torch 或 cv2 直接報(bào)錯(cuò)原因手動(dòng)升級(jí)了 numpy 或其它包破壞了 opencv 輪子的編譯環(huán)境也常發(fā)生在虛擬環(huán)境混用上當(dāng)前 shell 激活了 A 環(huán)境pip 卻裝到了 B 環(huán)境。解決重建一個(gè)干凈環(huán)境用固定版本的 requirements 安裝。我一般按這個(gè)順序conda create -n driver python3.9 -y conda activate driver pip install -r requirements.txt裝完后跑一次上一章的導(dǎo)入體檢確認(rèn) torch、cv2、numpy 全部正常再繼續(xù)。5.2 現(xiàn)象實(shí)時(shí)畫面只有個(gè)位數(shù)幀率像幻燈片原因讀幀和推理串行執(zhí)行每一幀都送進(jìn)模型模型處理完才讀下一幀畫框、寫文字也全擠在主線程里。解決把讀幀拆到獨(dú)立線程推理按跳幀策略執(zhí)行。行為判定本身不需要每幀都更新10 到 15fps 已足夠識(shí)別打電話和疲勞這些慢動(dòng)作cap cv2.VideoCapture(0) frame_queue queue.Queue(maxsize2) def read_frames(): while True: ok, frame cap.read() if ok and not frame_queue.full(): frame_queue.put(frame) threading.Thread(targetread_frames, daemonTrue).start() while True: frame frame_queue.get() if frame_id % 2 0: result model(frame)跳幀后的代價(jià)是行為狀態(tài)機(jī)不再逐幀更新行為規(guī)則里的“連續(xù) 3 秒”要做等價(jià)換算比如每?jī)蓭瑱z測(cè)一次時(shí)觸發(fā)時(shí)長(zhǎng)要相應(yīng)放寬。5.3 現(xiàn)象白天一切正常晚上攝像頭畫面很暗什么都檢不到原因駕駛場(chǎng)景光照變化劇烈模型和關(guān)鍵點(diǎn)算法都不太吃極端低照度畫面夜間暗部細(xì)節(jié)丟失后面部關(guān)鍵點(diǎn)提取直接失效。解決在預(yù)處理階段加自適應(yīng)直方圖均衡化也就是 CLAHE或者對(duì)暗幀做一次 gamma 校正。另一個(gè)更省事的辦法是加一個(gè)小型 USB 補(bǔ)光燈成本不高效果立竿見(jiàn)影。這類問(wèn)題優(yōu)先走工程手段不要反復(fù)調(diào)模型閾值后者在這種場(chǎng)景下基本是玄學(xué)。5.4 現(xiàn)象誤報(bào)壓不住明明沒(méi)打電話卻一直彈“接打電話”原因手部關(guān)鍵點(diǎn)與耳部區(qū)域的重疊判斷本身是弱條件手自然抬到方向盤上緣時(shí)會(huì)偽裝成耳邊動(dòng)作觸發(fā)時(shí)長(zhǎng)設(shè)成 1 秒也顯得過(guò)短。解決把電話行為判定改成“手部框質(zhì)心落在頭部框的側(cè)上方”加上“頭部姿態(tài)偏轉(zhuǎn)朝下”兩個(gè)條件同時(shí)滿足。觸發(fā)時(shí)長(zhǎng)從 1 秒改到 2 到 3 秒。改之前先看手部檢測(cè)框的坐標(biāo)散點(diǎn)判斷手在畫面里到底處于什么位置再?zèng)Q定加規(guī)則還是改閾值。拿數(shù)據(jù)說(shuō)話不要憑感覺(jué)調(diào)。如果時(shí)間不夠有一個(gè)妥協(xié)方案把“接打電話”判定改嚴(yán)保證準(zhǔn)確率另一檔“疑似分心”放寬保證召回率。兩個(gè)檔位分開(kāi)統(tǒng)計(jì)答辯時(shí)反而更好講。5.5 現(xiàn)象攝像頭剛打開(kāi)就黑屏或者程序一啟動(dòng)就崩潰原因OpenCV 在 Windows 上默認(rèn)使用 MSMF 后端個(gè)別攝像頭驅(qū)動(dòng)配合不好另外筆記本自帶攝像頭和外接攝像頭并存時(shí)索引 0 可能不是你想要的。解決明確指定后端和索引。Windows 下用 DirectShowLinux 下用 V4L2cap cv2.VideoCapture(0, cv2.CAP_DSHOW) if not cap.isOpened(): cap cv2.VideoCapture(1, cv2.CAP_DSHOW)每次打開(kāi)失敗后記得把cap.release()和cv2.destroyAllWindows()放進(jìn)finally塊否則下次運(yùn)行可能報(bào)攝像頭被占用。這個(gè)問(wèn)題在實(shí)機(jī)上很容易踩特別是反復(fù)調(diào)試攝像頭的時(shí)候。6. 進(jìn)階讓預(yù)警變成可持續(xù)追蹤、可驗(yàn)收的結(jié)果6.1 結(jié)構(gòu)化日志與報(bào)警聯(lián)動(dòng)實(shí)時(shí)界面彈窗消失之后無(wú)法回看而畢業(yè)設(shè)計(jì)答辯恰恰需要證據(jù)。把每次預(yù)警的時(shí)間、行為類別、置信度、幀號(hào)落成結(jié)構(gòu)化日志效果會(huì)明顯不一樣def append_log(log_path, behavior, conf, frame_id): entry { time: time.strftime(%Y-%m-%d %H:%M:%S), behavior: behavior, confidence: round(float(conf), 3), frame_id: frame_id, level: warning if conf 0.7 else remind, } with open(log_path, a, encodingutf-8) as f: f.write(json.dumps(entry, ensure_asciiFalse) \n)ensure_asciiFalse防止中文被寫成\uXXXX日志直接用文本編輯器就能讀frame_id用來(lái)回看原視頻定位問(wèn)題幀。這一層數(shù)據(jù)在寫技術(shù)報(bào)告時(shí)可以直接引用比如“測(cè)試集共觸發(fā) 23 次其中誤報(bào) 2 次”。報(bào)警聯(lián)動(dòng)可以再進(jìn)一步把邏輯收斂成一個(gè)統(tǒng)一接口def trigger_alarm(level): if level warning: print(\a, end) # 終端響鈴 serial_write(bALARM_ON) # 外接繼電器或蜂鳴器 else: serial_write(bREMIND)這種方式幫行為規(guī)則部分與硬件解耦之后想改成推送通知到 Web 后臺(tái)只需要替換serial_write這一個(gè)函數(shù)。分級(jí)提醒比一刀切報(bào)警更接近真實(shí)車載場(chǎng)景。6.2 三個(gè)可復(fù)現(xiàn)的驗(yàn)收測(cè)試錄像回灌測(cè)試準(zhǔn)備三段視頻分別對(duì)應(yīng)正常駕駛、接打電話、疲勞犯困用系統(tǒng)跑完后對(duì)比日志與真實(shí)標(biāo)簽算出準(zhǔn)確率和漏報(bào)率數(shù)字寫進(jìn)報(bào)告。漏報(bào)壓力測(cè)試在正常駕駛視頻里故意做手靠近嘴但不打電話的動(dòng)作統(tǒng)計(jì) 5 分鐘內(nèi)的誤報(bào)次數(shù)能控制在 2 次以內(nèi)就可以接受。實(shí)時(shí)性測(cè)試用time.time()記錄單幀檢測(cè)耗時(shí)確保低于 100ms。如果達(dá)不到就要描述清楚跳幀策略和降幀后的效果。這套驗(yàn)收流程既是在給代碼打分也順手把技術(shù)報(bào)告里的“實(shí)驗(yàn)與結(jié)果分析”寫掉大半。我每次拿到這類源碼包都會(huì)強(qiáng)制自己按“先查依賴、再跑視頻、最后接攝像頭”的順序走一遍不越過(guò)任何一步從那以后演示現(xiàn)場(chǎng)翻車的概率小了很多希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取