ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

违规驾驶行为识别系统实战:YOLO与数据库构建

违规驾驶行为识别系统实战:YOLO与数据库构建 简介面向Python毕业设计的违规驾驶行为识别系统以完整源码与数据库打包适合计算机相关专业学生用于课题研究、功能复现或二次开发。包内含81个py源码文件并配套sh部署脚本、md说明文档、png结构图以及pth模型权重、pkl/npy数据序列化文件等覆盖模型训练、推理与部署的关键环节全部文件共128个压缩包大小64.93MB。项目自带README与CHANGELOG工程说明目录结构清晰便于快速理解模块划分与项目演进过程。目前已有295人学习下载对需要搭建驾驶行为识别毕业设计原型或参考完整项目组织的同学是一份可直接落地的参考资料。1. 违规驾驶行为识别系统到底在做什么一套源码加数据库能解决的问题在一堆“免费python源码大全”里这类系毕设题目最常见也最容易踩坑表面上案是深度学习实际上一半工作量在数据清洗、行为逻辑和数据库。违规驾驶行为识别系统做的事很简单——用车载摄像头对准驾驶员实时判断他是否在打电话、抽烟、打哈欠、长时间闭眼一旦命中就把行为、时间、截图写进数据库并在界面上弹告警。它能直接复用到疲劳驾驶预警、网约车安全监管、驾校模拟考试评判等场景。适合三类人做Python毕业设计的学生、想在本地跑通一套CV识别流程的入门者、以及需要给监控系统加行为记录模块的工程师。朴素地说它把“深度学习”和“业务系统”缝在了一起这才是真正值钱的地方。2. 系统的核心链路目标检测、行为判定与数据库的三层架构2.1 用目标检测完成“谁在哪”YOLO 与 MediaPipe 的选型分工先说清楚这套系统的技术栈分工别拿到源码就到处找模型。常见做法是双头结构一个基于 YOLO 系列的目标检测器负责在每一帧里定位人、手、手机、烟这些物体另一个用 MediaPipe 或者 dlib 负责提取面部关键点计算眼睛开合程度和嘴部开合程度。为什么这样拆因为 YOLO 擅长找“物体框”但判断“眼睛闭没闭”这种事直接交给目标检测器去做不仅精度差训练数据也难凑反过来MediaPipe 能给出 468 个面部关键点但让它去框出手机又在遮挡场景下非常不稳定。两个模型各干各的用代码串起来比硬塞一个端到端模型更稳也更接近公司里做行为识别时的真实方案。拿打电话这个行为举例子。摄像头画面里出现手机并不代表在打电话可能只是拿在手里。所以 YOLO 的作用是给出 phone 这个类别的检测框MediaPipe 的作用是给出脸框和嘴部/耳朵区域。程序要做的是计算手机框中心是否落在脸框的上下左右一个可调偏移范围里同时去看是否有手部检测框和手机框重叠。两个条件同时满足并持续 N 帧才判定为“打电话”。手、手机、脸的关系本质上是一个空间位置判定而位置判定的输入全部来自目标检测的输出。下面是一段最简化的目标检测调用代码用的是 ultralytics 库加载训练好的权重文件from ultralytics import YOLO # 加载权重常见的有检测烟雾/手机/手的专用权重也有COCO预训练权重 model YOLO(weights/best.pt) # 对单帧图像推理conf就是置信度阈值iou是NMS的IOU阈值 results model.predict( sourceframe.jpg, conf0.45, iou0.45, verboseFalse, ) for result in results: boxes result.boxes for box in boxes: cls_id int(box.cls[0]) score float(box.conf[0]) x1, y1, x2, y2 [float(v) for v in box.xyxy[0]] print(f类别{cls_id}, 置信度{score:.2f}, 坐标({x1:.0f},{y1:.0f},{x2:.0f},{y2:.0f}))这段代码逻辑不复杂但参数值得看conf0.45表示只保留置信度高于 0.45 的框低于这个值的香烟或手机很容易被滤掉iou0.45是 NMS 去重阈值多个重叠框合并时 IoU 超过 0.45 才会被压制。这两个值会在第 4 章单独讲怎么调。weights/best.pt是训练好的权重如果是 COCO 预训练权重识别类别编号要自己映射源码里一般会在config/classes.yaml里写好类别名。2.2 把“在做什么”变成程序逻辑状态机与计时判定目标检测解决“谁在哪”但“在做什么”是时间维度上的问题。一张图里有人拿着手机不能立刻判定违规连续 5 秒保持这个姿态才是违规。所以源码里一定会有一个状态机模块负责把单帧检测结果累计成行为事件。打电话和抽烟的判定逻辑基本都是“空间条件 持续时间”。空间条件好理解烟头检测框中心点离嘴部关键点小于某个像素距离手机框与手框重叠且与脸部区域接近。持续时间则是为了抵抗误检——画面抖动、手部短暂划过脸前都会产生单帧误判。没有计时器系统会把同一通电话切成几十条记录这在第 5 章会专门讲翻车案例。疲劳驾驶是另一个独立判定链路依赖面部关键点而不是 YOLO。常用指标是 EAR眼睛纵横比和 PERCLOS闭眼帧占比。EAR 计算方法是取眼睛周围 6 个关键点纵向距离与横向距离的比值正常睁眼时大概 0.25~0.35闭眼时会掉到 0.15 以下。源码里一般会有一个函数import math def eye_aspect_ratio(eye_points): # eye_points 是 MediaPipe 返回的 6 个 2D 坐标按眼角到眼尾顺序排列 a math.dist(eye_points[1], eye_points[5]) b math.dist(eye_points[2], eye_points[4]) c math.dist(eye_points[0], eye_points[3]) return (a b) / (2.0 * c) def is_drowsy(ear_history, ear_threshold0.2, perclos_threshold0.4): closed sum(1 for ear in ear_history if ear ear_threshold) return closed / len(ear_history) perclos_threshold这里要注意的是坐标系MediaPipe 返回的关键点是归一化坐标有的源码直接拿去算欧氏距离会得到错误比例必须先乘以图像宽高还原成像素坐标再计算。ear_threshold0.2是经验值戴眼镜、单眼皮人群需要重新标定否则疲劳误报会高得离谱。判定完成的动作通常是一个状态转移过程空闲态 → 疑似态累计帧数加一→ 确认态生成记录写数据库。等行为结束且连续多帧无异常状态回到空闲。这个状态机是整个系统的骨架也是最容易被学生写成一坨 if/else 的地方。2.3 把行为落成记录数据库表设计与增删改查行为识别完成还不算结束毕业设计答辩时老师一定会打开数据库看表设计。源码包里的“数据库”指的就是这里。常见做法是 MySQL 或 SQLite单机演示用 SQLite 最省事但多数模板会提供 MySQL 建表脚本方便展示数据能力也方便后续接管理后台。数据库的核心是两张表驾驶员表和违规行为记录表。驾驶员表存司机 ID、姓名、车牌号、联系电话行为记录表存每一次确认的违规行为包括类型、开始时间、结束时间、持续时间、抓拍截图路径、处理状态。再加一张可选的告警日志表记录触发告警的时间点和当前帧。字段设计要注意behavior_type用 TINYINT 存枚举值而不是直接存中文节省空间也方便扩展screenshot_path存相对路径而不是 BLOB 二进制否则数据库体积会迅速膨胀。CREATE TABLE driver ( id INT NOT NULL AUTO_INCREMENT, driver_name VARCHAR(50) NOT NULL, license_plate VARCHAR(20) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE behavior_record ( id INT NOT NULL AUTO_INCREMENT, driver_id INT NOT NULL, behavior_type TINYINT NOT NULL COMMENT 1phone 2smoke 3drowsy, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration_sec FLOAT DEFAULT NULL, screenshot_path VARCHAR(255) DEFAULT NULL, status TINYINT DEFAULT 0 COMMENT 0未处理 1已处理, PRIMARY KEY (id), KEY idx_driver_time (driver_id, start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;为什么强制 utf8mb4因为很多国产 Windows 环境下 MySQL 默认字符集是 latin1写入“打电话”这类中文会直接变问号这是第 5 章的一个经典坑。索引idx_driver_time是为了后续查“某司机某段时间的违规次数”时避免全表扫描毕业设计里体现这一步说明你真的做过多表查询优化。Python 侧的访问一般用 PyMySQL简化代码里每次行为判定后就 insert 一条记录。需要注意连接不能每帧都创建常见做法是启动时建一个连接或使用 DBUtils 连接池。连接池的意义在于系统跑的是长时间视频流高频开关连接会让 MySQL 本身报“Too many connections”同时拖慢每帧处理速度用连接池能把写库开销稳定在毫秒级。import pymysql class Database: def __init__(self, host, port, user, password, db_name): self.conn pymysql.connect( hosthost, portport, useruser, passwordpassword, databasedb_name, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor ) self.cur self.conn.cursor() def insert_behavior(self, driver_id, behavior_type, start, end, duration, screenshot): sql INSERT INTO behavior_record (driver_id, behavior_type, start_time, end_time, duration_sec, screenshot_path) VALUES (%s, %s, %s, %s, %s, %s) self.cur.execute(sql, (driver_id, behavior_type, start, end, duration, screenshot)) self.conn.commit() def query_by_driver(self, driver_id): sql SELECT * FROM behavior_record WHERE driver_id %s ORDER BY start_time DESC self.cur.execute(sql, (driver_id,)) return self.cur.fetchall()调用时注意conn.commit()一定要在 execute 之后手动执行PyMySQL 默认不自动提交。同时建议把behavior_type的枚举意义写进注释或常量类不然半个月后回来看代码“这个 3 是什么行为” 得翻半天这也是毕业设计代码里最常见的黑匣子。3. 拿到源码后怎么跑起来从 Python 环境到第一次出图3.1 搭建运行环境Python 版本与依赖安装毕业设计源码最怕的情况是环境不对模型加载直接报兼容性错误。系统基于 Python 开发建议用 3.8 到 3.10不要一上来就装 3.12。原因很简单MediaPipe 和 PyTorch 的预编译轮子对 3.11 之后的支持时间有滞后装不上时还要折腾源码编译对毕业设计来说完全没有必要。Windows 用户去 python官网下载安装 3.10 的安装包安装时勾选 Add Python to PATH就能省掉后面一堆命令找不到的问题。拿到源码包第一件事不是双击运行而是用虚拟环境把依赖隔离。很多人的翻车现场是直接把依赖装进全局环境和已有的 OpenCV、NumPy 版本撞车。下面这套命令是稳妥的入口conda create -n driving_behavior python3.10 -y conda activate driving_behavior cd 违规驾驶行为识别系统源码 pip install -r requirements.txtrequirements.txt 的内容大致是这样版本号是兼容性比较稳的组合ultralytics8.2.0 opencv-python4.8.1.78 mediapipe0.10.9 pymysql1.1.0 PyQt55.15.9 numpy1.24.3注意numpy1.24.3这个版本约束。新版 NumPy 2.x 移除了很多旧接口Ultralytics 和 MediaPipe 依赖的np.float、np.int在 2.x 里直接被删除装上就跑不了。如果 pip 在安装时自动把 NumPy 升到 2.x你会在 import 阶段看到AttributeError: module numpy has no attribute float这不是代码写错是版本冲突。装完依赖后用pip list检查一下关键包版本比跑起来再排错省时间。3.2 初始化数据库建库建表与连接测试源码包一般会带sql/init.sql文件有些会带一个init_db.py。先用 MySQL 客户端把建表脚本执行一遍。没有 MySQL 的可以用 Docker 快速起一个或者直接用源码里可能提供的 SQLite 替代方案。MySQL 的好处是答辩时老师习惯性会问“数据存哪了”你打开 Navicat 之类的可视化工具给他看表结构比口头解释更有说服力。mysql -u root -p sql/init.sql是对是错先跑一个连接测试脚本确认数据库写得进去。下面这段连接测试代码会顺带把driver表插入一条测试数据并打印出刚才插入的 ID# test_db.py import pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, password123456, databasedriving_behavior, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, ) cur conn.cursor() cur.execute( INSERT INTO driver (driver_name, license_plate) VALUES (%s, %s), (测试司机, 京A12345), ) conn.commit() cur.execute(SELECT LAST_INSERT_ID() AS id) row cur.fetchone() print(写入成功driver_id , row[id])执行成功的标志是终端输出 driver_id。如果报Unknown database driving_behavior说明建库步骤没执行如果报Access denied for user说明账号密码不对。到这里能跑通后面的行为记录写入才有保障。记得把源码里的数据库配置也改成你本地的账号密码一般在config.py或database.py里。3.3 跑通第一条检测视频模式和摄像头模式环境就绪、数据库能写接下来运行主程序。这类系统的主入口通常支持两个输入源本地视频和摄像头。本地视频适合开发和演示摄像头适合真机展示。命令行参数一般长这样python main.py --source test_video.mp4 --output result.mp4或python main.py --source 0第二个命令里的0代表第 0 路摄像头也就是笔记本内置摄像头。如果把 0 改成视频文件路径就自动切到视频模式这个分支逻辑在主程序里通常是这个样子import cv2 from detector import BehaviorDetector def main(source): # source 可以是整数摄像头ID或文件路径 cap cv2.VideoCapture(source) detector BehaviorDetector() while True: ret, frame cap.read() if not ret: break events, annotated detector.process_frame(frame) cv2.imshow(Driving Behavior Monitor, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()第一次跑起来帧率大概率是每秒五、六帧这很正常。YOLO 推理本身要几十毫秒加上 MediaPipe 关键点检测整体开销不小。看到画面叠加了彩色检测框、右上角有实时状态标签就算跑通了。此时去 MySQL 里查behavior_record表应该还没有记录因为单一画面没有被连续多帧确认拿一个抽烟或打电话的视频片段去测记录就会开始出现。这里有一个常见误解需要破除系统跑起来不等于系统可用。如果只用摄像头对着自己乱晃误报会非常多只有用带真实现场的测试视频连续跑几分钟才能看出阈值调得合不合理。第 4 章就是解决这个问题的。4. 参数调优检测阈值、疲劳判定和误报率的三组必调项4.1 置信度阈值 conf_thres 和 IOU 阈值 iou_thres这两个参数决定“一个物体算不算数”。conf_thres太低影子里的一团黑色会被当成手机太高真手机在暗光下又检不出来。多数源码默认给 0.4~0.5但实际场景要按摄像头位置重新标定。推荐的调法不是拍脑袋而是拿 5 段真实场景视频每段截 200 帧人工标注里面有多少个真手机、真烟头然后改阈值看漏检率和误检率的变化。conf_thres表现特征适用场景0.25~0.35小目标召回率高误报明显增多烟和手机容易被环境干扰物触发摄像头距离驾驶员较远目标像素小0.40~0.50平衡点白天正常光照下误报可接受常规车载视角0.55~0.70误报很少但手机遮挡、暗光时漏检严重靠近驾驶员的高清摄像头iou_thres调整的是两个框要不要合并成一个。默认 0.45 一般不用动但如果发现同一个手机被框了两层且两个框中心点很近说明这个值偏低可以往 0.5 调。反过来如果两个相邻的框被错误合并成一个覆盖到脸的大框就往下调到 0.4。4.2 疲劳判定阈值EAR、PERCLOS 与连续闭眼时间疲劳识别是这套系统里翻译成中文最折腾的部分。EAR 的基准值因人而异眯眼型的人睁眼时 EAR 就很低直接用固定的 0.2 阈值会把正常眼睛看成闭眼。所以源码里一般会写一个“标定”功能启动后让驾驶员正视摄像头 30 秒取这段时间 EAR 的均值作为个人基线闭眼阈值设为基线的 0.6 倍。baseline_ear 0.28 # 标定后得到 ear_threshold baseline_ear * 0.6 close_frames 0 CLOSE_FRAME_LIMIT 20 # 按25fps计算大约连续闭眼0.8秒 for ear in ear_stream: if ear ear_threshold: close_frames 1 if close_frames CLOSE_FRAME_LIMIT: print(疲劳状态触发) else: close_frames 0这里的CLOSE_FRAME_LIMIT是第二个可调点。如果坚持要按“连续闭眼 2 秒”来判就把这个值设为帧率乘以 2比如 25 帧率设 50。太灵敏会在驾驶员眨眼时误报太迟钝会漏掉真实的点头瞌睡。比较实用的做法是同时计算 30 秒内的 PERCLOS闭眼帧占比超过 0.4 再触发一次这样既能抓到长时间疲劳又不会因为单次眨眼就崩。4.3 去抖窗口与状态保持把瞬时误判过滤掉行为记录刷屏是最常见的调参问题。原因就是去抖窗口太小甚至根本没有。打个比方驾驶员只是抬手挠了一下头手框和脸框瞬间重叠打电话判定就触发了一次下一秒手放下来判定又消失。于是 10 秒视频里产生了 8 条“打电话”记录数据库瞬间变得很假。解决方法是引入一个 pending 计数器和 cooldown 计时器核心逻辑是“连续 M 帧命中才算确认连续 N 帧不命中才复位”。M 一般取 5~15N 一般取 10~30具体按帧率缩放。PENDING_CONFIRM 10 # 连续10帧命中确认行为发生 PENDING_RELEASE 20 # 连续20帧未命中确认行为结束 class BehaviorState: def __init__(self): self.hit_frames 0 self.miss_frames 0 self.active False def update(self, is_violation): if self.active: if is_violation: self.miss_frames 0 else: self.miss_frames 1 if self.miss_frames PENDING_RELEASE: self.active False else: if is_violation: self.hit_frames 1 if self.hit_frames PENDING_CONFIRM: self.active True self.hit_frames 0 else: self.hit_frames 0 return self.active这个状态机的好处是真正的违规行为会稳定触发短暂的误检只会把hit_frames加几个不等到达 10 就被清零。同时行为结束不会被瞬间复位而是等到连续 20 帧无异常这样生成的行为记录持续时间更真实。写数据库的事件只发生在这个状态机的active从 False 变 True 的瞬间而不是每一帧都写从根源上杜绝记录刷屏。参数调整是一个依赖反馈的过程每改一个阈值都拿同一段测试视频跑一轮对比标注答准确率和记录条数再做决定这就是数据驱动调参毕业设计里能讲出这套过程老师通常会很认可。5. 避坑指南从依赖冲突到答辩翻车五条血泪排查记录5.1 import 阶段直接崩OpenCV 自动装了高版本 NumPy现象装完 requirements.txt运行import cv2终端报AttributeError: module numpy has no attribute float或者ImportError: numpy.core.multiarray failed to import。原因requirements.txt 没有锁死 NumPy 版本pip 又喜欢把依赖更新到最新版OpenCV 老版本与 NumPy 2.x 不兼容。解决先把所有依赖卸载重装按pip install numpy1.24.3固定版本再装 OpenCV。如果还不行就删掉虚拟环境重建确保没有全局环境干扰。这也是我反复提醒建虚拟环境的原因直接装进全局环境的同学排查两小时才发现是之前跑爬虫时装的高版本 NumPy 在打架。5.2 行为记录刷屏同一通电话被记了几十次现象摄像头对准自己假装打电话5 秒钟视频结束后数据库里多出 30 条behavior_type1的记录而且每条的start_time都是同一秒。原因写库逻辑放在单帧检测到 phonehand 之后没有经过状态机判定和去抖每一帧命中都触发一次 insert。解决把写库调用移到状态机active从 False 转 True 的代码分支里加一个self._recorded标记行为没有真正结束前不允许再次写入。这个坑在几乎所有初版源码里都存在“率好调状态机难写”是这套系统的真实写照。5.3 数据库写入成功但中文全是问号现象程序运行正常MySQL 里behavior_type字段也能写进去但页面和终端查出来全是“????”。原因建表时用的不是 utf8mb4 字符集数据库连接也没有指定charsetutf8mb4Windows 默认的 MySQL 配置在插入中文时转换失败。解决在CREATE TABLE语句里显式写ENGINEInnoDB DEFAULT CHARSETutf8mb4同时在 PyMySQL 连接参数里加上charsetutf8mb4。已经建好的表用ALTER TABLE behavior_record CONVERT TO CHARACTER SET utf8mb4;修复。这属于数据库基础问题但如果不用可视化客户端看很难发现数据已经存坏了。5.4 推理速度慢成PPT模型权重加载的是 CPU 版 PyTorch现象启动后每帧处理时间超过 2 秒别说实时检测看输出画面都卡顿GPU 利用率一直是 0%。原因pip install torch默认安装的是 CPU 版或者电脑上 CUDA 没有正确安装Ultralytics 在.cpu()上跑推理。解决先跑一行python -c import torch; print(torch.cuda.is_available())检查 GPU 是否可用。如果输出 False去 PyTorch 官网按自己的 CUDA 版本重装 GPU 版。笔记本上跑不出高帧率也别灰心YOLO 加 MediaPipe 双模型架构对算力要求不低没有 N 卡时可以用--half半精度推理或者换轻量级的 YOLO 模型文件前提是源码里留了模型切换的配置项。5.5 答辩演示现场黑屏摄像头权限和视频路径同时出问题现象PPT 讲完一切正常双击运行时控制台没有任何报错但图像窗口一片黑或者直接弹窗说cant open camera。原因有的实验室电脑禁用摄像头权限Windows 隐私设置里没有给 Python 打开摄像头的权限另外演示视频路径用了相对路径而当前工作目录被 IDE 切到了别的位置文件读不到。解决演示前提前把视频文件复制到项目根目录并测试一次绝对路径启动摄像头模式在 Windows 设置里把“允许桌面应用访问相机”打开并在代码里加一段cap cv2.VideoCapture(0)后的空帧判断如果读不出帧就自动切回视频文件模式。裁判老师坐在台下等你去修 bug 的心理压力试过一次你就懂这个兜底逻辑是真正的后悔药。6. 验收与进阶怎么证明系统靠谱再把准确率往上提6.1 用一段带标注的测试视频算准确率系统不是跑通就完事了答辩时被问到“准确率多少”不能只说大概。我习惯的做法是准备一段 5 分钟真实驾驶视频里面包含 3 次打电话、2 次抽烟、1 段疲劳状态先把时间点标进 Excel再让系统跑一遍比对识别结果。准确率按“识别出的事件数 / 实际事件数”算误报数单独记录这套验收数据比单帧指标更有说服力。跑完之后把 MySQL 里的记录导出成 CSV画个时间轴柱状图就能直接放进论文的“实验与分析”章节。6.2 换自己的检测模型训练与导出如果不想用现成权重可以自己训练 YOLO 目标检测器。把公开的违规驾驶数据集整理成 YOLO 格式标注手机、烟、手三类然后在ultralytics框架下用一个多小时微调。训练完导出成 ONNX 格式再在源码里把加载权重的路径换成你的模型即可。关键在于数据集的场景要贴近真实摄像头视角别拿一堆网络图片训练完直接上实车跨域之后精确率会断崖式下降。6.3 把数据库记录做成驾驶行为报表进阶用法是把行为记录表里的数据变成日报按司机 ID 分组统计每天的违规次数和总时长输出成 HTML 或 Excel。这一步对喜欢问“你系统有什么亮点”的老师来说很加分。凡是跑题跑偏的时候我都会守住一个准则这类毕业设计的核心不只是“识别”而是把识别结果做成了完整的业务闭环。把这份经验按上面步骤复现一遍你会比单纯再换一套源码的人收获更多希望帮到你。本文还有配套的精品资源点击获取
返回列表