☰
Python违规驾驶行为识别系统:基于YOLO的目标检测与毕设实战指南
2026/10/7 5:44:33 网站建设 项目流程

简介:面向课程设计与毕业设计准备的Python违规驾驶行为识别系统源码,是一套覆盖从环境搭建、数据准备、模型训练到检测结果输出的完整项目,适合计算机视觉、人工智能方向的学生用作毕设基线或进行二次功能扩展。压缩包共128个文件,整体约64.93MB。81个Python脚本承载核心算法与流程控制,Shell脚本提供一键运行或环境初始化入口,Markdown和TXT文档用于说明使用方式、目录结构与配置参数,pkl、pth权重文件可直接加载预训练模型,PNG与npy数据文件配合完成效果预览和输入验证。整体目录组织清晰,方便按模块定位阅读。资源目前已有1061人学习/下载,对缺少完整可运行代码的同学尤其友好。通过源码可理解驾驶行为识别的主流技术路线,包括数据读取、特征提取/模型搭建、训练验证、结果可视化等环节;配套文档和脚本能帮助快速复现运行环境,理清模块间调用关系,同时附带的版本变更记录与License信息也有助于追溯更新动态和合规使用,在此代码基础上替换数据集或调整网络结构即可开展进一步实验,有效降低毕设开发门槛。

1. Python违规驾驶行为识别系统:这东西到底解决什么问题

你手上这份「Python违规驾驶行为识别系统源码,可用做毕设.zip」,说白了就是一个用摄像头画面自动判断司机有没有在打电话、抽烟、打瞌睡、分神看手机的系统。它属于计算机视觉里的目标检测加行为分类,输入是一段视频或实时摄像头流,输出是“第几秒、哪个区域、什么违规行为、置信度多少”。对毕设来说,它的价值在于技术栈完整:Python、OpenCV、YOLO系列目标检测、时序判定逻辑、GUI展示,一套下来能撑起一个完整的本科课题。适合的人群很明确:计算机、软件、大数据相关专业,想做一个有实际场景、能演示、能讲清楚原理的毕业设计,而不是纯算法玩具。这套系统还能继续往疲劳驾驶、车队管理、驾校考试等方向延伸,是你毕设里少数几个能“做完还有商业想象力”的方向。我见过太多人拿到这类压缩包,解压完发现跑不起来就放弃了,实际上问题大多出在环境、路径和模型权重这三处,这篇文章就是把这条路给你趟平。

2. 系统的整体构成与数据准备:先搞清楚一个识别系统里都有什么

2.1 模块拆分:检测、分类、决策、展示四层结构

不管压缩包里代码怎么组织,一个能用的违规驾驶行为识别系统在逻辑上一定分四层。第一层是图像采集与预处理,负责从视频文件或者摄像头读帧、缩放、转色彩空间,这一层通常由OpenCV完成,代码里体现为cv2.VideoCapture加一个while True循环。第二层是目标检测,常见做法是用YOLO系列模型检测画面里的“人手”“手机”“香烟”“人脸”这些对象,输出边界框和类别。第三层是行为判定,也是这个系统真正有“智能”的地方——检测到手机和人手同时出现在画面里,不代表司机在打电话,可能是正在拿手机导航,所以需要结合边界框位置关系、持续帧数、手与耳朵或嘴部的距离做时序判断。第四层是展示与告警,把检测结果画在画面上、记录日志、触发声音或截图报警。

理解了这四层,你去读zip里的源码就不会懵。绝大多数毕设源码的组织方式都是main.py做入口、detector.py封装模型、utils.py放工具函数、models/放权重文件。如果压缩包里连这个基本结构都没有,说明这份源码质量存疑。另外提醒一句,拿到压缩包第一件事不是打开代码看,而是先看有没有requirements.txt和README.md,这两个文件决定你本地能不能在两小时内跑起来。

2.2 数据集从哪来:公开数据集与自建数据集怎么选

违规驾驶行为识别这个方向,公开数据集说多不多说少不少。常见的有State Farm的驾驶员状态检测数据集(分心驾驶分类)、SFU3D手势数据集、以及一些开源项目里整理好的“打电话/抽烟/正常驾驶”三分类图片集。这些数据集的特点是场景偏美国或欧洲,方向盘在左边,司机的人种和穿着跟你最终演示环境有差异,导致你直接拿训练好的权重在本地跑,效果可能不理想。

我建议的做法是两条腿走路:先下载公开数据集,比如从Kaggle找distracted driver detection相关的数据,用现成的跑通整个pipeline;然后自己录20到30分钟视频,覆盖打电话、抽烟、看手机、正常驾驶四个场景,用OpenCV把视频抽帧成图片,再用标注工具框出目标。自建数据的好处是你答辩时导师问“这个样本从哪来的”,你可以理直气壮说现场采集的,而不是支支吾吾说从网上扒的。抽帧代码很简单,一个cap.read()循环加间隔计数,每5帧存一帧就能用。

2.3 标注与格式转换:把标注结果变成YOLO能吃的TXT

数据集准备好后,最花时间的环节就是标注。常见标注工具有LabelImg和Labelme,前者输出的是Pascal VOC格式的XML,后者输出JSON。YOLO系列训练需要的格式是每个图片对应一个TXT文件,每行内容为类别id x_center y_center width height,四个坐标值都是归一化到0到1之间的浮点数。

import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_folder, txt_folder, class_names=None): xml_files = Path(xml_folder).glob("*.xml") class_map = {} if class_names else {"phone": 0, "smoke": 1, "normal": 2} for xml_file in xml_files: tree = ET.parse(str(xml_file)) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) txt_path = Path(txt_folder) / (xml_file.stem + ".txt") lines = [] for obj in root.findall("object"): cls_name = obj.find("name").text if cls_name not in class_map: class_map[cls_name] = len(class_map) cls_id = class_map[cls_name] box = obj.find("bndbox") xmin = int(box.find("xmin").text) ymin = int(box.find("ymin").text) xmax = int(box.find("xmax").text) ymax = int(box.find("ymax").text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(txt_path, "w") as f: f.write("\n".join(lines)) return class_map

这段代码读入XML里的目标框,把左上右下的像素坐标换算成归一化的中心点和宽高。换算公式不复杂,核心就一句:(xmin + xmax) / 2 / 图片宽得到中心点x坐标,宽高同理除以图片尺寸。这里有个新手容易踩的坑:YOLO的坐标是中心点加宽高,不是左上角加右下角,如果你用VOC标注工具习惯了x1 y1 x2 y2的思维,写转换脚本时很容易忘记这步。标注的类别不要设太多,我见过有人把“打电话”拆成“左手打电话”“右手打电话”“拿手机到耳边”三个类,训练出来一团糟。类别就设phone、smoke、normal三个,最多加一个drink喝水,够了。

2.4 数据集划分:按“采集成片”划分,别按单张图随机打乱

划分训练集和验证集看似简单,实际上有个容易翻车的细节。一张一张图随机划分会导致同一个人的同一段视频帧既出现在训练集又出现在验证集里,模型等于提前见过答案,验证指标虚高,答辩现场换成新视频立刻现原形。正确做法是先按视频片段分组,整个片段的帧要么全进训练集,要么全进验证集。

import os import shutil import random from pathlib import Path def split_by_video(src_img_dir, src_txt_dir, train_dir, val_dir, split_ratio=0.8): videos = {} for img_path in Path(src_img_dir).glob("*.jpg"): video_id = img_path.stem.split("_frame_")[0] videos.setdefault(video_id, []).append(img_path.stem) video_ids = list(videos.keys()) random.seed(42) random.shuffle(video_ids) train_count = int(len(video_ids) * split_ratio) for i, vid in enumerate(video_ids): target = train_dir if i < train_count else val_dir for stem in videos[vid]: shutil.copy(str(Path(src_img_dir) / f"{stem}.jpg"), str(Path(target) / "images" / f"{stem}.jpg")) shutil.copy(str(Path(src_txt_dir) / f"{stem}.txt"), str(Path(target) / "labels" / f"{stem}.txt"))

注意这里的文件名命名规则,我习惯把视频ID放在文件名最前面,比如video01_frame_0012.jpg,这样按split("_frame_")[0]就能取出视频ID。如果你的源码里文件名格式不同,那这行切片逻辑就要相应改掉。划分完以后,YOLO训练时还需要一个data.yaml文件,声明训练集路径、验证集路径和类别名列表,很多毕设源码里的yaml路径是写死的绝对路径,你换台电脑就报错,这也属于常规操作了。

3. 模型训练路线与关键参数:直接用现成权重,还是从零微调

3.1 三条路线对比:YOLOv5微调、YOLOv8微调、传统OpenCV方案

对于毕设这个场景,目标检测部分不外乎三条路线。第一条是用YOLOv5的官方预训练权重做迁移学习,只冻结骨干(backbone)微调检测头(head),优点是对硬件要求低、社区资料多、网上能找到大量现成经验帖。第二条是用YOLOv8,也就是现在ultralytics主推的系列,API更友好、训练命令更简单、精度略高,但配套的毕设代码相对少一些。第三条是用传统OpenCV加HOG特征或者肤色检测做手和手机的定位,优点是代码量看起来“很硬核”,缺点是鲁棒性差,稍微换个光线就崩。我的建议是选YOLOv8,理由很实在:ultralytics这个库把训练、验证、导出、推理封装得极其顺滑,你不需要写太多底层代码,可以把精力放在行为判定逻辑和系统集成上,这两个部分才是答辩时能讲出东西的地方。

不要从零训练一个YOLO模型。YOLO的预训练权重是在COCO数据集上训出来的,COCO里有person、cell phone这些类别,直接在这个基础上去微调你自建的违规行为数据集,数据量只要几百张也能出效果。从零训练的话,没有几万张图根本训不出来,那是把毕设搞成科研项目了。

3.2 训练命令与核心参数:跑通一个最小可用的训练流程

选定路线后,训练本身不复杂,复杂的是参数理解和环境配置。先给一个最小可用的YOLOv8训练命令:

pip install ultralytics opencv-python numpy yolo train data=dataset/data.yaml model=yolov8n.pt epochs=50 batch=16 imgsz=640 device=0

这段命令里model=yolov8n.pt是从官方下载nano版本的预训练权重,dataset/data.yaml换成你的数据配置文件路径。epochs设50轮足够判断模型能不能收敛,batch=16在8GB显存上跑nano模型没有问题,device=0指定使用第一块GPU,纯CPU环境改成device=cpu但速度会慢得多。

有几个参数需要格外解释。imgsz是训练时输入图像的尺寸,640是默认值也是性价比最高的值。超过640精度提升有限但显存占用和推理耗时直线上升,对于毕设场景完全没必要。patience参数控制早停,如果验证集损失连续多少轮不下降就提前结束训练,我一般设20,防止过拟合。lr0初始学习率默认0.01,不用动。还有一个参数mosaic是数据增强里的马赛克拼接,YOLOv8默认开启,它会同时拼4张图训练,小数据集上效果很好,但如果你发现训练曲线震荡得厉害,可以先关掉试一轮。

训练过程里重点看两个指标:mAP50和mAP50-95。前者是IoU阈值0.5时的平均精度,后者是0.5到0.95的均值,毕设答辩你只需要讲明白mAP50就够了,它衡量的是“模型框得准不准”。如果训完mAP50低于0.8,先别急调参,去检查标注文件里的坐标值有没有归一化错、类别id有没有对不上。

3.3 自建数据集的训练策略:数据增广与类别不均衡怎么处理

如果你最终用的是自建数据集,几个问题会立刻冒出来。第一个是类别不均衡,正常驾驶的帧数可能占了70%,打电话和抽烟各占15%,模型会倾向于把所有图都判成normal。解决办法是在训练命令里加cls参数提升小类别的损失权重,YOLOv8里对应的是cls=0.7之类的配置。第二个问题是样本多样性不足,你的自建数据可能只有固定角度、固定光线,模型上了考场换个摄像头就翻车,此时加大hsv_h、hsv_s、hsv_v这些颜色增广参数会有效果,默认值分别是0.015、0.7、0.4。

还有一个值得说的技巧:单类别训练和双类别训练的区别。如果你只检测手机和手,就设两个类;如果想把“手拿着手机靠近耳朵”这种动作也检测出来,那要加一个phone_to_ear类,但代价是标注工作量翻倍。毕设不需要做那么细,你可以在行为判定逻辑层用距离关系推出来,这个后面第4章会讲到。

4. 行为识别逻辑与推理管线:从检测框到“违规”判断

4.1 后处理的核心:连续帧判定、重叠度计算、行为冲突消解

目标检测模型输出的只是一堆边界框和置信度,真正决定“有没有违规”的是一段决策逻辑。常见的坑是单帧判定,也就是某一帧检测到手机就直接截图报警,这样误报率极高。合理的做法是加一个帧计数窗口:连续N帧中至少M帧检测到同一行为才触发报警,N和M的关系一般是M = ceil(N * 0.6),比如5帧里至少3帧判定违规才报警,相当于做了一次简单的时间维度的去抖。

第二个关键点是行为冲突消解。检测器可能在同一次推理里既输出了phone又输出了smoke,这往往是因为手部区域同时和手机、香烟的框高度重叠。处理方式是按置信度排序,高置信度的类别胜出,或者按优先级表,比如打电话的优先级高于看手机。更稳妥的做法是引入类别间互斥规则:一个手部区域同一时刻只能关联一种违规行为。

第三个点是IoU重叠计算,用来判断检测框是否在同一个手部区域。这个计算在ultralytics的utils里有现成函数,但如果你的源码里不是用YOLOv8而是自己写的后处理,就需要手算。10行左右就能实现,核心公式是交集面积除以并集面积,不需要额外装库。

4.2 推理代码的可复现示例:摄像头流接入与行为判定

下面这段代码展示了一个完整的推理循环:读取本地视频,逐帧送入模型,根据检测框位置关系做行为判定,连续触发后输出告警。

import cv2 from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") cap = cv2.VideoCapture("test_video.mp4") frame_buffer = [] alert_triggered = False while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model(frame, conf=0.45, iou=0.5, verbose=False) boxes = results[0].boxes phone_boxes = [] smoke_boxes = [] for box in boxes: cls_id = int(box.cls[0]) xyxy = box.xyxy[0].cpu().numpy() if cls_id == 0: phone_boxes.append(xyxy) elif cls_id == 1: smoke_boxes.append(xyxy) violation = False for phone in phone_boxes: for hand in hand_boxes: iou = compute_iou(phone, hand) if iou > 0.3: violation = True break if violation: frame_buffer.append(1) else: frame_buffer.append(0) if sum(frame_buffer[-5:]) >= 3: if not alert_triggered: cv2.imwrite("alert_snapshot.jpg", frame) print("违规驾驶行为触发,截图已保存") alert_triggered = True else: alert_triggered = False cap.release()

说明几个关键点。conf=0.45是置信度阈值,低于这个值的检测结果会被丢弃。这个参数直接影响误报和漏报的平衡,调低到0.3会漏检变少但误报变多,调高到0.6则反过来,建议在0.4到0.5之间。iou=0.5是NMS的IoU阈值,控制重叠框的合并力度,一般不需要动。frame_buffer[-5:]取最近5帧的判定结果,sum >= 3就是前面说的“5帧里至少3帧违规”。这里有个隐藏问题:frame_buffer列表无限增长的话,切片操作会越来越慢,严谨的写法是用collections.deque(maxlen=5)替代列表。压缩包里如果连这种细节都注意到了,起码说明源码作者是认真写的。

4.3 手部区域检测:单独检测手还是用人体关键点推理

很多违规驾驶系统在实现“打电话”行为时,不只是看手机有没有出现,还要看手离耳朵近不近。这里有两条路:一条是单独训练一个hand类别,然后用手机框和手框的IoU判断;另一条是用人体姿态估计模型如YOLOv8-pose或MediaPipe提取手腕和耳朵的关键点坐标,计算欧氏距离。后者的优势是抗遮挡能力强,手举起来但被方向盘挡住一半时,关键点仍然能估算出来;劣势是计算量更大,推理速度可能会掉到10帧以下。毕设演示场景用的是录好的视频,帧率要求不高,姿态估计路线显得更“高级”,答辩时有得讲。如果用的是实时摄像头,我建议还是走第一条路,因为关键点模型的稳定性在教室灯光、摄像头角度受限的情况下并不好,容易抖动。

5. 毕设集成与排查避坑:源码跑不通的常见原因和处理办法

5.1 环境与依赖:解压后第一小时最容易踩的四个坑

拿到zip压缩包后,第一小时里踩的坑往往不在代码本身,而在环境。第一个坑是Python版本不对,很多老毕设源码基于Python 3.8或3.9,你用Python 3.12直接pip install -r requirements.txt,大概率有包编译失败。常见做法是先用python --version确认版本,再决定是装一个虚拟环境还是降级。第二个坑是torch和torchvision版本对不上,YOLOv5源码要求torch>=1.7,但如果你的显卡驱动只支持新版CUDA,装老版本torch反而起不来。第三个坑是路径中文问题,压缩包解压出来如果路径里带了中文,opencv读图片和torch加载权重时经常报找不到文件的错,把整个项目挪到纯英文路径下能解决90%的加载报错。第四个坑是缺失opencv-python的特定版本,有些源码里用了cv2.dnn模块,默认装最新的opencv会报属性错误,这时候pip install opencv-python==4.5.5.64这类老版本反而稳。

5.2 运行报错排查:从“No module named”到“CUDA out of memory”

下面按概率从高到低列几个高频报错和处理办法。

现象一:ModuleNotFoundError: No module named 'ultralytics'。原因就是没装依赖包。解决:按源码里requirements.txt逐个安装,不要用pip install -r requirements.txt一次跑完然后坐等,而是装完一个包就导入验证一次,至少能定位到哪个包装失败了。

现象二:RuntimeError: CUDA out of memory.。原因有两个可能:一是batch size设太大,二是显存被其他进程占了。解决:先看nvidia-smi确认显存占用;再调小batch,YOLOv8推理模式下加batch=1,训练模式调成batch=4或batch=8。还不行就换yolov8n.pt,nano模型显存占用只有几百兆。

现象三:AttributeError: 'NoneType' object has no attribute 'shape'。这个报错几乎都出现在视频路径错误或摄像头读取失败。原因:代码里cv2.VideoCapture("video.mp4")打开失败,返回的cap是空对象,而代码没有判断ret就继续读帧。解决:在cap.read()后加一个判空,同时确认视频文件路径有没有写错。这一步也是源码里最常见的偷懒点。

现象四:训练时loss一直不降,反而震荡上升。原因大概率是学习率过大或者标注文件有脏数据。解决:先关掉mosaic增强试一轮;再检查标注TXT里有没有数值大于1或小于0的坐标,这类脏数据会直接拉崩训练曲线。

现象五:中文路径导致yolo train报找不到data.yaml。解决:路径里不要出现任何中文字符,包括你的用户名目录,比如C:\Users\张三\这种就是典型的雷区。

5.3 效果不好时优先调整的三个方向

如果模型训练完了但实际效果差,先别急着换模型架构。优先调整三个方向:第一个是置信度阈值,把推理时的conf从0.25往0.5方向调,往往误报会显著下降。第二个是训练数据的光线多样性,如果你只用白天录的数据,加一批夜间或逆光的帧,效果提升往往比调参更明显。第三个是输入分辨率,推理时把imgsz从640降到480,速度变快但精度略降;从640升到960则反过来。这三个方向成本低、见效快,比你去换检测头或者改backbone实际得多。

6. 进阶功能与效果验证:让系统从“能跑”到“能答辩”

6.1 验证方法:量化指标与演示视频录制

系统做完了,怎么证明它有效?我见过太多同学在答辩时只会说“看,能检测出来”,然后被老师一个问题问倒:“准确率多少?误报率多少?”所以模型训完后,第一件事是在测试集上跑一个指标统计脚本,计算每个类别的精确率(Precision)、召回率(Recall)和F1值。精确率衡量的是“你报警的里面有多少是真正违规的”,召回率衡量的是“真正违规的有多少被你报出来了”。对于一个毕设来说,精确率比召回率更重要,因为误报会严重损害系统的可信度,漏报反而可以解释为“受限于摄像头视角”。ultralytics自带的model.val()会输出这些指标,直接抄进论文即可。

演示视频的录制也有讲究,别用训练集里的视频片段做演示,导师一眼就能看出来效果虚高。正确做法是单独录一段2到3分钟的视频,包含两次打电话、一次抽烟和几段正常驾驶的过渡,注意一个细节:动作要缓慢、明显、持续,别一晃而过,检测器还没反应就结束了。如果演示时画面是黑的或者卡帧,大概率是摄像头读取问题,提前用ffplay或VLC测试一下视频文件的编码格式,H.264通常没问题,某些特殊编码会导致OpenCV打不开。

6.2 轻量化与落地:导出成ONNX模型提升推理速度

如果演示时觉得推理速度太慢,可以做一个轻量化操作:把训练好的模型导出成ONNX格式,用ONNX Runtime做推理,速度通常能提升1.5到3倍。

from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") model.export(format="onnx", imgsz=640, half=True, simplify=True)

这段代码把PyTorch权重导出为ONNX格式。half=True开启半精度推理,显存占用减半但精度损失很小;simplify=True会做一些图优化,去掉冗余计算。导出后可以用onnxruntime加载做推理,或者再进一步转成TensorRT或OpenVINO的格式。不过这一步对毕设来说是加分项不是必选项,只有在答辩演示电脑性能比较差时才需要。导出成功以后记得验证一下输入输出维度,ONNX的输入张量格式是NCHW,部分代码里会用torch.randn(1, 3, 640, 640)做一次假推理确认没有报错。

6.3 我习惯的一个收尾动作:把“误报样本”收集起来

一个我自己吃了亏才养成的习惯:系统跑了一段时间后,把每次误报的截图都自动存档,单独建一个false_positive_samples/目录。这些误报样本后续有两种用途:一是追加到训练集里做增量训练,模型会慢慢学会之前不会的东西;二是答辩时可以作为“系统局限性分析”的素材,主动说出你的系统在什么场景下会误报、为什么误报、怎么改善,这比被导师问住然后支支吾吾要体面得多。很多毕设系统做完即弃,能主动收误报样本的,答辩时讲出来的东西完全不是一个层次。市面上有些源码包里会附带一个hard_examples目录,里面放的就是这类样本,如果没有,你自己建一个,花不了多少时间。

另一个收尾动作是检查告警日志的格式。系统里应该有一个log目录,每次违规行为触发时记录时间、类别、置信度和截图文件名的对应关系。日志别用中文命名,编码会折腾你,violation_20250101_153025.jpg这种命名足够直观且不会有编码问题。有了日志,你论文里就能写“本系统对X段测试视频进行验证,共检出违规事件N次,其中真实违规M次,误报K次,精确率P%,召回率R%”,这一串数据下来,毕设的实证章节就是实的,不是虚的。

最后说句心里话,做这类毕设题目,真正拉开差距的不是模型精度那零点几个百分点,而是整个流程能不能闭环:数据从哪来、标注怎么做、训练参数怎么解释、推理结果怎么验证、误报怎么改进。这一套走下来,哪怕模型效果不是顶尖,你也能把这个课题讲得清清楚楚。我自己带过几个学生做类似题目,发现凡是踏实走完这套流程的,答辩基本不会被问倒;凡是只想着把代码跑通截几张图交差的,最后都在“这个检测是怎么实现的”这个问题上卡了壳。希望这篇笔记能帮你少走点弯路,现在就去把你电脑上那个zip解压出来,按顺序把环境装好、跑通推理、再录一段自己的视频吧。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询