简介:这套供Python毕业设计项目参考的违规驾驶行为识别系统完整源码与数据库包,适合计算机相关专业学生用于课程设计或毕业设计。系统围绕驾驶行为检测任务,涵盖数据处理、模型训练、推理识别与结果展示等环节,能够帮助初学者理解从数据准备到模型落地的完整流程。资源共包含128个文件,结构清晰:以Python脚本为主体(约81个py及pyc文件),负责模型训练、推理及业务逻辑;另配有Shell脚本用于环境搭建与自动化处理,以及模型权重(pth/pkl)、特征数据(npy)、示例图片(png)和说明文档(md/txt)等辅助材料,压缩包整体大小约64.93MB。已有295人学习下载,具备一定的参考热度。通过该资源可以获得一套可运行的违规驾驶行为识别项目代码,包含数据库文件及完整的代码逻辑,可快速在此基础上进行二次开发、调试及论文撰写,适合希望节省重复搭建时间、直接学习项目实现细节的毕业设计人群。
1. 别看是毕设,这套违规驾驶行为识别系统真能跑起来
做驾驶行为识别,最难的不是写模型,而是拿到一份能闭环的工程代码。这套Python毕业设计源码本身就是一个完整可运行的违规驾驶行为识别系统,从数据处理、模型训练到结果保存都有现成实现。我拆过很多号称“毕设级”的识别项目,大部分卡在数据格式不匹配、训练脚本跑不通或者界面和逻辑脱节,而这一套直接给了数据库文件、视频帧处理脚本和训练好的权重流程,整体结构是照着“能交差、能答辩、能演示”的路子设计的。适合三类人:正在做Python毕业设计、想快速实现违规驾驶检测Demo、以及刚接触行为识别想弄懂全流程的初学者。
2. 把驾驶行为变成模型能懂的数据:从视频帧到特征向量
2.1 数据从哪来:视频抽帧与关键帧标注
不管是检测打电话、抽烟还是疲劳驾驶,第一步都是把视频变成图片。项目里默认用OpenCV读取视频流,按固定帧率抽帧,把连续动作切成一张张静态图。常见做法是设定每N帧取一帧,比如fps = 25时每5帧抽一帧,这样既保留动作连续性,又不会让数据太冗余。
import cv2 import os def extract_frames(video_path, output_dir, frame_interval=5): os.makedirs(output_dir, exist_ok=True) cap = cv2.VideoCapture(video_path) count = 0 saved = 0 while True: ret, frame = cap.read() if not ret: break if count % frame_interval == 0: cv2.imwrite(os.path.join(output_dir, f"frame_{saved:06d}.jpg"), frame) saved += 1 count += 1 cap.release() print(f"抽取完成,共保存 {saved} 帧")这段代码的作用是把原始视频拆成jpg图片集,frame_interval是抽帧间隔,数值越小数据量越大,训练越慢但动作细节越完整。我第一次跑的时候用的原始25fps全量抽帧,6000帧视频直接抽出6000张图,后面才发现完全是浪费存储。实际项目里建议先抽帧再人工筛选,把模糊帧、场景切换帧删掉,否则模型会把噪声也学进去。
抽完帧之后是标注环节。常见的做法是建一个JSON或TXT文件,每一行记录图片路径和对应的行为标签,比如0表示正常驾驶、1表示打电话、2表示抽烟、3表示疲劳闭眼。这一套系统里标好的数据格式已经按类别划分好了目录结构,训练时直接用文件夹名当作标签,省去了写标注工具的麻烦。
2.2 模型选型:为什么用姿态估计而不是纯目标检测
很多初学者一上来就想用YOLO做驾驶行为识别,这其实是个坑。YOLO能检测出“人”的位置,但判断不了“人是否在打电话”,因为打电话这个动作需要手部与头部的相对位置关系。这套系统采用的是姿态估计思路:先提取人体关键点,再根据关键点之间的几何关系判断行为类别。
用MediaPipe或OpenPose能拿到人体关键点坐标,比如左肩、右肩、左肘、右肘、左腕、右腕等。打电话的典型特征是手腕关键点靠近耳朵关键点,且这个状态持续超过一定帧数;抽烟的特征是手部关键点靠近嘴部区域。这套系统里的特征提取模块就是干这个的。
import mediapipe as mp mp_pose = mp.solutions.pose pose = mp_pose.Pose(static_image_mode=True, model_complexity=1) def extract_keypoints(image_path): import cv2 img = cv2.imread(image_path) rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results = pose.process(rgb_img) if not results.pose_landmarks: return None landmarks = results.pose_landmarks.landmark keypoints = [(lm.x, lm.y, lm.z) for lm in landmarks] return keypointsmodel_complexity控制模型精度和速度的平衡,0最快但关键点抖动大,2最准但推理慢,实时检测场景用1就够了。这里拿到的33个关键点坐标就是后续行为判断的输入特征,不需要原始图像再进分类器。
判断逻辑本身并不复杂:计算手腕关键点和耳朵关键点的欧氏距离,设置一个阈值,距离小于阈值就判定为手在耳边。但这套系统没有只用单帧判断,而是加了一个帧序列投票机制,连续10帧里如果有7帧以上都是手在耳边,才认定是打电话,这样做能大幅减少误报。
2.3 训练流程与关键参数
数据准备好之后就是训练分类器。这套系统里可用两种方案:一种是用提取好的关键点坐标训练一个轻量分类模型,比如MLP或LSTM;另一种是端到端训练一个行为识别网络。毕设场景我建议用前者,训练快、解释性强,答辩时能说清楚每个特征的含义。
from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier import numpy as np X = np.load("features.npy") # 形状 (n_samples, 33*3) y = np.load("labels.npy") X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) clf = RandomForestClassifier(n_estimators=200, max_depth=12) clf.fit(X_train, y_train) print("测试集准确率:", clf.score(X_test, y_test))n_estimators是树的数量,200是一个折中值,再加大收益很小且训练变慢;max_depth限制树的深度,防止过拟合。要注意的是特征顺序必须和提取时一致,我第一次训练时把关键点的x、y坐标按不同顺序拼接,导致准确率只有50%,排查半天才发现是特征拼接逻辑不一样。
如果用LSTM做时序分类,输入要组织成连续帧序列,每段取30帧,形状是(batch, 30, 99),99就是33个关键点乘以3维坐标。LSTM的优势是能捕捉动作的时序变化,比如“拿起手机放到耳边”这个过程本身是逐渐发生的,单帧CNN很难捕捉这种渐变信息。
3. 跑通源码:环境配置与启动步骤
3.1 环境依赖与版本匹配
这套系统依赖的库很常规:Python 3.8以上、OpenCV、NumPy、scikit-learn、TensorFlow或PyTorch(二选一)。最容易翻车的是Python版本和依赖库版本不匹配,比如Python 3.10配旧版TensorFlow会直接报错。我建议在项目根目录创建一个虚拟环境,用requirements.txt安装依赖。
python -m venv venv source venv/bin/activate pip install -r requirements.txtrequirements.txt里通常会有版本约束,比如opencv-python==4.8.1.78、numpy==1.24.3。不要直接pip install opencv-python装最新版,因为最新版可能和已编译的扩展模块有ABI兼容问题。如果看到ImportError: libGL.so.1这类报错,说明系统缺OpenCV的底层库,Ubuntu下执行sudo apt install libgl1就能解决。
3.2 训练与测试的完整命令
项目里的主训练脚本一般是train.py或main.py,核心入口逻辑是先加载数据、再建模型、然后训练和评估。跑起来之前先确认数据目录结构,默认路径是./dataset/train和./dataset/val,每个类别一个子文件夹。
python train.py --epochs 50 --batch_size 32 --lr 0.001 --backbone mediapipe--backbone mediapipe表示使用MediaPipe提取关键点后再走分类器;如果想用LSTM端到端训练就把这个参数换成lstm。学习率lr初始设0.001,训练到20轮左右如果loss下降变缓可以手动降一半。训练过程中会实时打印每个epoch的loss和准确率,正常情况是loss逐步下降、准确率逐步上升。如果准确率一直卡在某个值不动,先检查数据标签是否均衡,再看学习率是不是太大导致震荡。
测试阶段运行python test.py --weights best_model.pth,这会输出每种类别的精确率、召回率和F1分数。我习惯额外跑一遍混淆矩阵,因为只看总准确率会掩盖某类行为完全没学会的问题。
3.3 实时检测与可视化结果
训练完模型之后,真正用于演示的是实时检测脚本。它读取摄像头或视频文件,逐帧检测行为并叠加画框和标签。为了让演示效果稳定,脚本里还会加关键点连线绘制,把检测到的人体骨骼画在画面上,答辩时一眼就能看出系统确实理解了行为。
import cv2 def draw_result(frame, landmarks, label): annotated = frame.copy() # 画关键点 for lm in landmarks: x, y = int(lm[0] * frame.shape[1]), int(lm[1] * frame.shape[0]) cv2.circle(annotated, (x, y), 3, (0, 255, 0), -1) # 画标签 cv2.putText(annotated, label, (30, 60), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 0, 255), 3) return annotated这里的坐标系转换很关键,MediaPipe返回的关键点坐标是归一化的0到1浮点数,直接乘图像宽高才能得到像素坐标。很多新手漏了这一步,画出来的点在画面里乱飞。检测速度方面,CPU上MediaPipe单帧推理约30到50毫秒,加上后处理能跑到20帧左右;如果想要更快的实时效果,可以降低输入帧分辨率,比如从1920x1080缩到960x540,检测速度能提升一倍多。
4. 违规驾驶行为识别避坑指南:数据、训练与推理的常见问题
4.1 数据集不平衡导致模型只会预测正常驾驶
训练完成后发现测试准确率有95%,看起来很漂亮,但仔细看漏报率,发现打电话、抽烟这些违规行为几乎全被漏掉了。原因是数据分布严重失衡,正常驾驶帧占了九成,违规行为帧很少。模型为了整体准确率最大化,干脆把所有输入都预测成正常驾驶。解决方法是做类别重采样,对少数类样本做过采样,或者用加权损失函数给少数类更高的惩罚权重。检查项目里的数据加载代码,看看有没有做类别平衡处理;如果没有,可以手动给少数类的loss乘一个系数,常见做法是设为1 / 类别频率。
4.2 加载grid.npy时报维度不匹配
项目里带了一个grid.npy文件,这个文件在某些模型里是预生成的网格坐标,用于行为定位或锚框生成。加载报错通常是ValueError: operands could not be broadcast together,原因是保存时用的NumPy版本和当前加载的版本在默认dtype上有差异。解决方法是重新生成一次这个文件,或者在加载时显式指定dtype:
grid = np.load("grid.npy", allow_pickle=True).astype(np.float32)还有一个细节是,如果训练脚本里用了不同图像尺寸,grid的维度也要跟着变。比如训练时输入是224x224,grid就是224对应的网格;如果推理时改成256x256,必须重新生成grid。
4.3 实时检测卡顿,帧率只有个位数
这是视频类项目的经典问题。原因往往是两个:一是视频帧分辨率太高,每帧处理时间过长;二是检测、画图、显示三个环节没有合理拆分,全部串行执行导致吞吐量上不去。我的处理习惯是先做帧缩放,再做一个简单的双缓冲队列,检测线程处理完一帧就丢到队列里,显示线程只管从队列取结果画出来,这样帧率能提升30%以上。另外一个容易被忽略的点是,OpenCV的imshow如果直接阻塞在检测循环里,实际上会拖慢整个管线。
4.4 摄像头输入画面倒置或镜像
很多笔记本摄像头在OpenCV里读出来是镜像的,画框和标签的位置却按原始坐标系计算,导致标注和实际动作错位。这个不是模型问题,是显示层问题。处理方式用cv2.flip(frame, 1)做水平翻转,翻转之后所有关键点坐标的x方向也要做对应变换,也就是x_new = 1 - x。我通常只在显示层做翻转,不改变数据层的坐标,避免把问题绕复杂。
5. 进阶:把识别系统封装成Web平台,顺便把结果存进数据库
5.1 用Flask包一层HTTP接口
毕设如果想拿高分,最好让系统不只是跑在终端里,而是有一个Web界面。用Flask把检测脚本包成一个接口,前端每次上传一段视频或一张图片,后端处理完返回识别结果和检测到的行为标签,这种做法在毕业设计里很常见。
from flask import Flask, request, jsonify import base64 import cv2 app = Flask(__name__) @app.route("/detect", methods=["POST"]) def detect(): data = request.get_json() img_bytes = base64.b64decode(data["image"]) nparr = np.frombuffer(img_bytes, np.uint8) frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR) label, confidence = predict_behavior(frame) return jsonify({"label": label, "confidence": confidence}) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)接口层的核心是图片传输格式,前端传base64字符串,后端解码成numpy数组再进模型。confidence返回的是模型对该行为判断的置信度,这个数值在后面做报警阈值判定时很重要。host="0.0.0.0"表示允许局域网内其他设备访问,答辩演示时可以用手机或另一台电脑访问,效果比只在本机跑好得多。
5.2 数据库表结构设计与存储
项目标题里的“数据库”不是摆设。识别结果需要落库才能形成完整闭环,常见的表结构是记录video_id、frame_time、behavior_label、confidence_score、created_at这五个字段。用MySQL或SQLite都行,毕设级别用SQLite更省事,直接一个文件搞定。
CREATE TABLE violation_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, video_id VARCHAR(64), frame_time INTEGER, behavior_label VARCHAR(32), confidence REAL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );插入记录时注意frame_time保存的是视频帧序号而不是时间点,这样后续回溯分析时能精确定位到具体是第几帧发生的行为。索引方面在behavior_label字段上加一个普通索引,因为后面大概率会按行为类型做统计查询。
5.3 报警阈值与业务联动
识别系统输出的置信度不能直接用,因为模型在模糊场景下可能给出50%的置信度,实际却是在吃零食而不是打电话。我的做法是设置一个双阈值:超过0.8直接报警,低于0.3直接忽略,处于中间态的高频行为持续超过3秒才触发报警。具体数值根据你自己的模型表现调整,先跑一批测试视频,统计真实行为的置信度分布再定阈值。
报警触发后可以接入一个简单的消息通知模块,比如写入另一个alert_log表,或者调用Webhook推送到企业微信、钉钉。答辩时能把“识别到风险行为、写入数据库、触发提醒”这个流程完整演示出来,项目深度和工程完整度都会上一个台阶。
我把这套流程在真实视频上完整跑通之后,最大的感受是:这类系统真正难的地方不在算法本身,而在数据组织和工程链路。从那以后我每次拿到一份新源码,都会先花半小时把数据格式、依赖版本、入口脚本这三个位置搞清楚,再动手调参。这样能避开绝大部分隐藏的坑,省下好几个晚上的折腾时间。希望这篇拆解能帮你把项目跑通,少走一点弯路。
本文还有配套的精品资源,点击获取