资讯动态

OpenCV作弊检测系统:YOLOv3+Haar级联实战解析

发布时间:2026/9/28 13:25:16 来源:尧图企业网站定制
简介这是一套面向高校计算机视觉课程设计、毕业设计及考试监考应用的教学辅助作弊检测系统基于OpenCV、YOLOv3与Haar Cascade搭建融合深度学习和传统图像处理技术可识别考生携带手机、书籍检测转身、距离过近等异常行为共实现四个典型监考场景。资源为RAR压缩包共87个文件整体大小264.16MB主要包含Python源码、YOLO权重与配置文件、OpenCV级联分类器、TensorFlow模型、测试图片、GIF演示以及依赖清单其中源码和配置文件对应目标检测、姿态识别、距离分析等核心模块图片和GIF用于效果演示XML级联文件与模型文件可直接调用。项目目录划分清晰从数据、模型到应用脚本均有对应文件便于按需修改和复用。目前已有1573人浏览学习适合希望快速搭建教室智能监考原型、深入理解YOLO与OpenCV整合应用的开发者和研究者。1. 考试监考太枯燥这套 OpenCV 作弊检测系统到底能做什么做学生考试监考系统最烦的就是盯监控一盯三小时眼睛酸还容易漏动作。这套教学辅助系统本质是一个作弊检测系统用 OpenCV 把手机、书本、转头、交头接耳这些动作自动标出来监考老师只需要看后台标记就行。它不是一个云端 SaaS而是一份完整的本地跑源码包解压后配好 Python 环境就能用。核心能力有四块YOLOv3 识别考场里的手机和书籍Haar 级联判断学生是否转身回头距离检测判断两人是否贴得太近顺便还带了一个基于人脸识别的考勤签到。适合做毕业设计、课程设计的同学也适合想在校内机房自己搭一套监考原型的教务老师。下面我会把模型选型、脚本调用顺序、参数坑和实测经验全拆开讲。2. 模型与检测链路YOLOv3 查手机书籍、Haar 查转身先搞清选型再跑代码2.1 检测项的设定为什么用 YOLO 和 COCO 而不是自训练模型这套包拿到的第一印象是文件多yolov3.weights、yolov3-416.cfg、coco.names放在根目录cascades文件夹里塞了一堆haarcascade_*.xml。一开始我以为是两套模型随便选后来把代码翻完才明白这是两条完全不同的检测链路分别对应“物品检测”和“姿态/转向检测”。物品检测用 YOLOv3模型权重就是公开的 COCO 预训练权重识别 80 类日常物体。考场这个场景里真正用来判定作弊的只有cell phone手机和book书本两类。为什么不自己标注训练一个专用模型因为训练数据不好搞考场照片涉及隐私正样本取不到而且 YOLOv3 在 COCO 上已经学得很充分手机和书本都是高频物体直接过滤类别就能用。对于毕设级别的演示预训练权重足够省下几个月标注时间。但注意YOLOv3 对考场这种俯拍大场景的密集小物体会漏检。比如手机放在桌面上被手半遮住或者书本只露出一角置信度可能降到 0.3 以下。代码里如果conf_threshold设太高检测结果会非常稀疏。后面避坑章我会给建议值。转身检测用的是 Haar 级联分类器文件在cascades目录下比如haarcascade_frontalface_default.xml、haarcascade_eye.xml。Haar 的思路不是识别“转身”这个动作而是识别“人脸还看不看得到”。检测不到正面人脸、但能检测到上半身就判定为转身进而标记成作弊嫌疑。这个逻辑对学生来说有点反直觉但实现简单CPU 上跑也飞快适合作为 YOLO 的辅助校验。2.2 关键代码加载 YOLOv3 权重并过滤出 phone/book 类别先看负责物品检测的核心代码这段逻辑贯穿objectDetection.py和main.pyimport cv2 import numpy as np # 读取类别名coco.names 里每行一个类别 with open(coco.names, r) as f: classes [line.strip() for line in f.readlines()] # 加载 YOLOv3 网络cfg 与 weights 必须匹配 net cv2.dnn.readNetFromDarknet(yolov3-416.cfg, yolov3.weights) # 目标类别COCO 里手机是 class 67书本是 class 73 target_classes {cell phone: 67, book: 73} def detect_cheat_items(img): h, w img.shape[:2] # 构建输入 blob尺寸固定为 416x416并归一化到 0~1 blob cv2.dnn.blobFromImage(img, 1/255.0, (416, 416), (0, 0, 0), swapRBTrue, cropFalse) net.setInput(blob) # 获取三个 YOLO 输出层的名字 layer_names net.getLayerNames() out_layers [layer_names[i - 1] for i in net.getUnconnectedOutLayers()] detections net.forward(out_layers) boxes, confs, class_ids [], [], [] for output in detections: for det in output: scores det[5:] class_id np.argmax(scores) confidence scores[class_id] if confidence 0.5 and class_id in target_classes.values(): cx, cy, bw, bh det[:4] * np.array([w, h, w, h]) x int(cx - bw / 2) y int(cy - bh / 2) boxes.append([x, y, int(bw), int(bh)]) confs.append(float(confidence)) class_ids.append(class_id) # NMS 去掉重叠框 idxs cv2.dnn.NMSBoxes(boxes, confs, 0.5, 0.4) result [] for i in idxs: i i[0] if len(idxs.shape) 1 else i label classes[class_ids[i]] result.append((label, boxes[i], confs[i])) return result代码逻辑很简单blobFromImage把输入图缩放到 416×416net.forward拿三个尺度的输出解码出中心点、宽高和类别概率然后用 NMS 合并重叠框。参数里1/255.0是像素归一化swapRBTrue是因为 OpenCV 默认 BGR 而 YOLO 训练时用 RGB。如果你在自己的项目中调整注意conf_threshold代码里的0.5和 NMS 阈值0.4的关系前者太严容易漏检后者太松容易一个物体出两个框。我一般把置信度放到 0.35~0.4因为考场里书本被遮挡很常见0.5 以上漏检率肉眼可见。2.3 Haar cascade 转身检测基于面部/眼睛的辅助判定转身检测的代码在main.py的另一个线程里核心是用两个级联分类器叠加import cv2 # 加载 Haar 级联 face_cascade cv2.CascadeClassifier(cascades/haarcascade_frontalface_default.xml) eye_cascade cv2.CascadeClassifier(cascades/haarcascade_eye.xml) upperbody_cascade cv2.CascadeClassifier(cascades/haarcascade_upperbody.xml) def detect_turn(img_gray): # 第一步检测正面人脸 faces face_cascade.detectMultiScale( img_gray, scaleFactor1.1, minNeighbors5, minSize(60, 60)) # 第二步在每个人脸区域内检测眼睛 eye_count 0 for (x, y, w, h) in faces: roi img_gray[y:yh, x:xw] eyes eye_cascade.detectMultiScale(roi) eye_count len(eyes) # 第三步如果正面脸缺失但能检测到上半身说明学生在转身 if len(faces) 0: upper_bodies upperbody_cascade.detectMultiScale( img_gray, scaleFactor1.1, minNeighbors3, minSize(100, 100)) if len(upper_bodies) 0: return turning return normal这个逻辑的核心是“正面脸消失 上半身存在”就判定为转身。scaleFactor1.1表示每次缩放窗口乘以 1.1越小检测越慢但越准minNeighbors5表示至少 5 个相邻矩形框才确认一个目标值越大误报越少但漏报越多。实测中学生低头写字时正面人脸可能检测不到这时如果上半身还在不会被判为转身——逻辑里先看脸再看上半身两个条件缺一不可。有一点要特别提醒Haar 检测非常吃光线考场侧光严重时正脸误检率会飙升导致转身误报。如果你发现班里一大半人都在“转身”先把minNeighbors调到 7 或者 8。3. 把代码跑起来依赖安装、目录结构与四个主要脚本3.1 requirements 版本对照Python 3.7 下的坑项目给了一份requirements.txt最关键的几个版本如下包名版本要求我当时用的版本备注tensorflow2.5.02.9.1CPU 版够用不用 GPUkeras2.4.32.4.3必须锁死跟 TF2.9 硬怼会报错imutils0.5.40.5.4旧版本避免自动升级numpy1.19.51.19.5版本高了 OpenCV 兼容性差opencv-python4.2.0.324.5.5.644.x 都行matplotlib3.4.13.4.1画图表用scipy1.6.21.6.2人脸编码计算依赖scikit-learn0.24.10.24.1考勤分类用streamlit0.79.00.79.0仪表盘页面onnx1.10.11.10.1模型转换备用tf2onnx1.9.31.9.3口罩模型转 ONNX 时用我自己的血泪经验是keras2.4.3是这套代码的分水岭。如果你用pip install -r requirements.txt时没锁版本pip 会把 keras 装成 2.10 或更高然后from keras.preprocessing.image import img_to_array直接报错cannot import name。正确做法是创建一个干净的虚拟环境python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txt如果安装不顺利numpy和opencv的冲突最常见先装 numpy 1.19.5再装 opencv 4.5.5最后装其他包顺序错了容易出现“cv2装好了但np.float不存在”这种玄学问题。3.2 从头跑通主要流程从 static 图片到摄像头实时解压后的目录结构有几个关键文件和文件夹需要认清楚cascades/Haar 级联 XML转身检测用。ImagesAttendance/考勤人脸照片每张照片是一个人文件名是名字。Attendance.csv考勤结果输出。yolov3.weights与yolov3-416.cfg物品检测模型注意weights文件约 236MB如果解压后只有几十 KB那是下载被截断了。main.py主入口能跑图片检测、摄像头检测也负责把结果汇总给 Streamlit。objectDetection.py只负责 YOLO 物品检测的函数库。facedetection.py人脸检测与编码供考勤使用。examData.py生成考试记录的虚拟数据给仪表盘展示用。dashboard.pyStreamlit 服务端。sign.py与attend.py考勤签到逻辑。跑通图片检测最简单直接执行python objectDetection.py --image img0.png这里img0.png是项目自带的示例考场图片检测结果会在终端打印出检测到的物品类别和坐标并在图片上画框。想要摄像头实时检测跑python main.py --source 0--source 0表示摄像头设备号传一个视频文件路径也可以。main.py会启动一个 OpenCV 窗口不停刷新检测框。我第一次跑的时候 FPS 只有 2~3因为每一帧都跑完整 YOLO后来我把检测跳帧改成每 3 帧处理一次窗口显示瓶能到 10 FPS 左右具体做法放在避坑章节。如果你想马上看到整个系统的“作弊报警”效果项目里img_textBox0.png到img_textBox5.png这些测试图都是带作弊物实景的直接用--image跑就能复现论文里的效果图。注意传入中文路径会报错建议先把这些图片复制到英文目录下。3.3 examData 与 dashboardStreamlit 可视化界面这套系统不是只给你一个黑漆漆的 OpenCV 窗口它还用 Streamlit 做了个 web 页面。examData.py会生成一份模拟的考试数据格式大概是“学生编号、开始时间、交卷时间、作弊标记、状态”存在内存对象里。dashboard.py负责把这些数据渲染成表格和折线图。启动方式python examData.py # 会生成一个 examData.pkl 或内存缓存文件 streamlit run dashboard.py浏览器会自动打开http://localhost:8501。页面上能看到几个区域左侧是当前考试状态统计中间是作弊事件流水包括时间、类型、截图缩略图右边一个柱状图展示不同作弊类型的占比。如果你只想看实时检测结果不一定要开 dashboard——main.py本身就带窗口。但 dashboard 的价值在于它把 YOLO 检测出的“手机/书本”和 Haar 检测出的“转身”汇总成一屏监考老师不用看代码也能操作。examData.py里的数据是硬编码生成的不是现场检测的真实数据。这一点容易被误以为“系统直接出报表”其实是两回事现场检测结果要接 dashboard得在main.py里把检测记录写进同一个数据对象而不是直接调examData.py。代码里已经有接口但默认没串起来我是自己改了几行才打通的这个细节后面也会提。4. 距离检测与考勤用 OpenCV 算“距离过近”用人脸识别补签到4.1 距离检测实现思路边界框中心点与像素比例摘要里第三个场景是用 OpenCV 和 YOLO 分析学生之间距离是否太近。这个功能在main.py里调用核心思路先用 YOLO 检测出图片里的所有person类别拿到每个人的边界框然后计算两人中心点的欧氏距离。如果中心点距离小于某个像素阈值就判定为距离过近。import numpy as np from scipy.spatial import distance as dist def check_distance(boxes): # boxes: 每个人检测框 [x, y, w, h] 的列表 centers [] for (x, y, w, h) in boxes: cx x w // 2 cy y h // 2 centers.append((cx, cy)) violations [] if len(centers) 2: # 计算所有中心点之间的欧氏距离矩阵 D dist.cdist(np.array(centers), np.array(centers)) for i in range(len(centers)): for j in range(i 1, len(centers)): if D[i][j] 200: # 像素距离阈值需要按实际场景标定 violations.append((i, j, D[i][j])) return violations这里的200是一个像素阈值但像素值在不同摄像头分辨率下完全不可通用。1280×720 的画面里两个人间隔 1 米可能对应 150 像素1920×1080 画面里同样的物理距离可能对应 300 像素。所以我强烈建议不要在代码里写死而是先做一步标定在考场摆两把椅子距离 0.8 米时在画面里量一下中心点像素差填进去。后面进阶章我会给一个标定流程。另外这个检测只对“横向距离”敏感。如果前后排座位在画面里上下排列中心点距离很容易小于阈值导致误报。我自己的处理办法是只取同一水平线上的坐标差忽略垂直距离或者用实际课桌的固定间距做裁剪。4.2 考勤模块从 ImagesAttendance 生成 Attendance.csv考勤不是一个重新训练的人脸识别模型而是基于face_detector和ImagesAttendance文件夹里已有的照片做编码比对。facedetection.py里用了 OpenCV 的 DNN 人脸检测器res10_300x300_ssd_iter_140000.caffemodel然后用深度学习特征提取人脸编码再和ImagesAttendance里的已知人脸比对。import cv2 import face_recognition def generate_attendance(): known_names [] known_encodings [] # 遍历 ImagesAttendance 里的照片 for filename in os.listdir(ImagesAttendance): if filename.endswith((.jpg, .png)): name os.path.splitext(filename)[0] image_path os.path.join(ImagesAttendance, filename) img cv2.imread(image_path) rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 用 face_recognition 库提取人脸编码 encodings face_recognition.face_encodings(rgb) if len(encodings) 0: known_names.append(name) known_encodings.append(encodings[0]) # 考勤结果写入 CSV with open(Attendance.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([Name, Time, Status]) for name, encoding in zip(known_names, known_encodings): # 后面接实际摄像头抓拍比对 writer.writerow([name, time.time(), Present]) return known_names, known_encodings实际签到逻辑在sign.py里会从摄像头读取当前帧抓一张人脸和known_encodings做face_recognition.compare_faces欧氏距离小于 0.6 就认为是同一个人。注意face_recognition这个库不在 requirements.txt 里我第一次跑就卡在ModuleNotFoundError上。原因是项目作者默认你已经装好了 dlib 和 face_recognition但没写进依赖。安装是另一个坑dlib 在 Windows 上必须用 prebuilt wheel 或者装 Visual Studio 编译环境我在避坑章里会细说。考勤生成的Attendance.csv可以直接用 Excel 打开记录包含姓名、签到时间和状态。这套思路做课程设计很合适但注意它只能做“到场”验证不能防“替考”因为签到后摄像头就不再持续追踪了。4.3 四个应用场景怎么串起来项目摘要提到四个应用场景但代码默认并没有把它们做成一键全流程需要自己串。我的实现是按这个顺序来的学生进场时运行sign.py摄像头拍脸比对ImagesAttendance里的照片写入Attendance.csv。开始考试后运行main.py进入检测主循环YOLO 每 3 帧检测一次手机/书本/人员Haar 每 5 帧检测一次转身每 10 帧调用一次距离检测函数。检测到作弊行为时main.py会把当前帧裁剪保存到captures/目录文件名包含时间戳和行为类型。考试结束后运行dashboard.py展示所有记录监考老师人工复核captures/里的截图。这个流程里最关键的是检测频率错开。如果三组检测都压在同一帧上CPU 直接跑满画面像幻灯片。我是把检测任务拆成三个独立线程用队列传帧每个线程处理频率不同。你可以直接借鉴这个思路不用每帧都全量检测。5. 避坑与常见问题我跑这套系统时踩过的五个坑5.1 现象OpenCV 读 caffemodel 报错 “Invalid arg”跑facedetection.py时cv2.dnn.readNetFromCaffe(deploy.prototxt, res10_300x300_ssd_iter_140000.caffemodel)偶尔会报错或者加载后检测结果全为空白。原因通常是两点一是deploy.prototxt和 caffemodel 不匹配比如用了另一个版本的 prototxt二是 OpenCV 版本太老4.0 以下对某些 caffe 层支持不全。解决方法是确认两个文件都来自 OpenCV 官方模型库并且升级opencv-python到 4.5 以上。如果还不行把 prototxt 文件用文本编辑器打开检查最后一层是否为DetectionOutput是就没问题。5.2 现象import keras 时报错或者卡死这是最高频的坑。requirements.txt里 keras 锁死 2.4.3但 tensorflow 是2.5.0pip 解析时会把 keras 升级到 2.9 或 2.10导致keras和tf.keras混用。现象是from keras.preprocessing.image import img_to_array直接报ModuleNotFoundError。解决很简单装依赖时先单独装keras2.4.3再装 tensorflow最后装其他包。如果已经装乱了就在虚拟环境里pip uninstall keras tensorflow然后重新按顺序装。不要用pip install -r一把梭我的项目至今保持这个习惯。5.3 现象YOLO 检测结果全是空的一张图片都识别不出来先说现象什么框都没画终端打印置信度也都是 0。原因大概率是yolov3.weights文件不完整。这个文件原始大小约 236MB如果是从某些网盘下载的压缩包解压后只有十几 MB那基本是下载被截断了。另一个常见原因是 cfg 和 weights 不配套项目里同时有yolov3-416.cfg如果你用官方 608 的 cfg 去配 416 的 weights前向传播尺寸对不上输出全是 nan。检查方法看weights文件大小再看cfg文件里width和height都是 416。如果文件不完整最好重新下载完整权重。5.4 现象摄像头实时检测卡顿FPS 低到没法用全分辨率帧跑 YOLOi5 的 CPU 基本每帧 0.5~1 秒完全没法实时。我的解决办法是跳帧加降分辨率。在main.py里把摄像头帧先缩放到 640×480然后用一个全局计数器只有frame_count % 3 0时才执行 YOLO中间两帧直接画上一帧的检测框。Haar 检测也一样5 帧跑一次。实际效果是画面 25 FPS作弊检测延迟约 0.5 秒学生翻书动作能被捕捉到。如果你要检测更严可以把缩放分辨率提高到 640×480 以上但延迟会明显增加。5.5 现象imread 读取中文路径图片返回 None项目本身附带的图片都是英文名但你自己收集的考场照片往往是中文名比如 “教室1_张三.jpg”。OpenCV 的cv2.imread对中文路径支持极差返回None还没报错导致下游cv2.dnn.blobFromImage直接崩。解决办法是用 numpy 的fromfile转码读取import cv2 import numpy as np def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) return img这个函数替换掉所有cv2.imread的调用中文路径问题一次根治。注意imdecode不支持视频文件所以摄像头VideoCapture不受影响。6. 进阶把检测结果存成日志并自动标注作弊证据6.1 保存带标签的截图与 CSV 日志main.py默认检测到作弊物后只在画面里画框没有落盘。我后来加了一段日志逻辑每次检测到目标就把当前帧裁剪并保存同时往 CSV 里写一行记录import csv import time from datetime import datetime def log_detection(frame, label, bbox, save_dircaptures): timestamp datetime.now().strftime(%Y%m%d_%H%M%S) x, y, w, h bbox crop frame[max(0, y-20):yh20, max(0, x-20):xw20] filename f{save_dir}/{timestamp}_{label}.jpg cv2.imwrite(filename, crop) with open(detection_log.csv, a, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([timestamp, label, x, y, w, h, filename])这样每一条作弊记录都有原始截图证据链完整。我一般会在考试结束后把detection_log.csv和captures/文件夹一起压缩归档方便老师复核。注意cv2.imwrite同样有中文路径问题所以保存目录和文件名都用英文。6.2 校准距离阈值的实际操作距离检测的像素阈值必须现场标定我的做法是写一个小脚本先在教室里拍一张包含两个座位的俯视图然后手动点击两人的头顶位置算出像素距离再乘以一个比例系数。比如实际物理距离是 0.8 米画面像素距离是 160那么每像素对应的物理距离是 0.005 米当你设定“小于 0.6 米算距离过近”时像素阈值就是 120。标定脚本代码如下import cv2 calib_frame cv2.imread(calibration.png) points [] def click_event(event, x, y, flags, param): if event cv2.EVENT_LBUTTONDOWN: points.append((x, y)) cv2.circle(calib_frame, (x, y), 5, (0, 255, 0), -1) cv2.imshow(Calibration, calib_frame) if len(points) 2: dist_px ((points[0][0] - points[1][0])**2 (points[0][1] - points[1][1])**2) ** 0.5 real_dist_m float(input(输入两点的实际距离(米): )) scale real_dist_m / dist_px print(f比例尺: {scale:.5f} 米/像素) cv2.destroyAllWindows() cv2.imshow(Calibration, calib_frame) cv2.setMouseCallback(Calibration, click_event) cv2.waitKey(0)我第一次部署时没做标定直接沿用代码里的 200 像素阈值结果后排两个相邻女生正常坐着写卷子屏幕上一直报“距离过近”搞得监考老师一度以为她们真在递纸条。后来我把标定做完阈值从 200 调到 85误报率立刻降下来。从那以后我每次部署新考场都强制先拍校准图、点两个点、算出比例尺再进正式检测。这步花不了三分钟但能省掉一整个考场的误报希望也能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑