news 2026/8/30 11:12:52

OpenCV+PyQt5实现课堂抬头率检测系统:从人脸检测到姿态估计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenCV+PyQt5实现课堂抬头率检测系统:从人脸检测到姿态估计

简介:本资源是一套面向本科毕业设计的课堂抬头率检测系统实现方案,基于OpenCV与传统人脸识别技术,解决教学过程中的学生专注度量化评估问题,适用于教育技术、计算机视觉初学者及毕设选题学生。压缩包共22个文件(2.88MB),含4个Jupyter Notebook(.ipynb)主程序——涵盖摄像头调用、人脸检测、GUI界面集成与完整流程运行;3个Python脚本(.py)提供轻量部署选项;5个XML级联分类器模型文件(如haarcascade_frontalface_alt2.xml)直接支持人脸定位;另含测试图片、Excel统计模板及README说明文档,结构清晰、开箱即用。已有736人学习下载,提供从图像采集、实时识别到抬头率计算(结合预设选课人数)的全链路代码,UI界面简洁可交互,并附初始环境配置提示与中英文混合注释,便于理解原理与快速调试。 做毕设选“基于OpenCV人脸识别的课堂抬头率检测系统”这个题目的同学,我见得挺多。原因是这个题目方向明确、技术栈成熟、学习资料多,最关键的是“人脸识别”和“GUI界面”都是评委老师一眼就能看到的东西,展示效果好。但真动手做的时候,很多人会卡在同一个地方:人脸检测好跑,抬头率到底怎么算?GUI和视频处理又怎么衔接?我自己调试过一套完整的可运行方案,这篇文章就把整体设计、核心算法、关键代码、踩坑记录全部拆开讲一遍,照着走基本能跑通。

这套方案适合三类人:一是正在做相关毕设、需要快速搭建演示系统的同学;二是想做一个能放到简历里的综合项目的开发者;三是想了解OpenCV人脸检测、头部姿态估计和PyQt5 GUI开发如何结合的新手。下面开始。

1. 项目整体设计与技术选型

1.1 系统到底要做什么

先把需求聊清楚。课堂抬头率检测系统,场景是这样的:摄像头放在教室前方,对准学生区域,系统实时采集画面,检测画面里的每一张人脸,判断每个学生是抬头还是低头,然后统计这一段课堂时间内的“抬头率”。抬头率这个概念在不同论文里定义不完全一样,有的按人数算,有的按时间算,有的两者结合,但本质上都是想量化“学生认真听讲的程度”。

我在设计时把它拆成了几个功能模块:

  • 实时视频采集:支持摄像头输入,也支持视频文件回放,方便没有摄像头环境时测试。
  • 人脸检测:从每帧画面中检测出所有人脸位置。
  • 抬头状态判断:对每张人脸判断“抬头”或“低头”。
  • 实时统计:每秒或每分钟计算一次抬头率,画出变化曲线。
  • 数据保存:把统计结果导出成CSV文件,方便写论文时做数据分析。
  • GUI界面:能显示实时画面、检测结果、统计数据,提供开始/停止/导出等操作。

GUI我用的是PyQt5,后面会细说为什么。整个系统用Python开发,核心算法用OpenCV,辅助用dlib提供人脸关键点,再用OpenCV的solvePnP做头部姿态估计。全部依赖都是Python生态里比较成熟的库,跑通难度不大。

1.2 技术选型:OpenCV、GUI库、姿态估计方案

先说人脸检测。这个题目里写了“基于OpenCV”,所以主力肯定是用OpenCV自带的人脸检测器。OpenCV里最经典的人脸检测方案是Haar级联分类器,文件就一个haarcascade_frontalface_default.xml,加载方便,CPU上跑得也快。但它的缺点也明显:对侧脸、遮挡、光线变化比较敏感,课堂场景下学生低头或者用手托腮时容易漏检。

所以我在实际代码里做了两手准备:默认用Haar级联,因为代码最简单、依赖最少;如果追求更好的检测效果,可以换成OpenCV的DNN人脸检测器,加载一个Caffe模型文件,检测精度和鲁棒性会高不少。两种方案的代码我都写在下面,可以根据自己的需求切换。

再说GUI库。Python的GUI选择无非就是Tkinter、PyQt5/PySide2、wxPython这几个。Tkinter虽然内置、简单,但界面比较朴素,做毕设展示时第一印象会打折扣。PyQt5虽然安装体积大一些,但界面美观、控件丰富,还支持QThread多线程,处理视频流非常顺手。考虑到毕设答辩时界面是重要的呈现部分,我推荐PyQt5。如果不想承担PyQt5的安装风险,用Tkinter也能做,只是代码里视频帧的显示逻辑要自己多写一点。

最后说抬头判断。这是整个系统的核心难点,也是最容易让评委眼前一亮的地方。纯靠OpenCV的Haar人脸框只能知道“有没有人”,判断不了“抬头还是低头”,所以我引入了dlib的68点人脸关键点检测,结合OpenCV的solvePnP做头部姿态估计,算出人脸的俯仰角(pitch角),根据俯仰角是否超过阈值来判断抬头状态。这个方案兼顾了精度和实现可行性,也是很多相关毕设论文里的主流做法。

2. 核心算法与抬头判断原理

2.1 人脸检测的两种方案对比

先给一份对比表格,方便你在动手前决定选哪套。

方案模型/文件检测速度精度依赖复杂度适用场景
Haar级联haarcascade_frontalface_default.xml一般最低,OpenCV自带即可正脸为主、光线稳定
OpenCV DNNres10_300x300_ssd_iter_140000.caffemodel中等较高需要下载模型文件课堂实景、姿态多样

Haar级联的原理是基于Haar-like特征和AdaBoost分类器,用滑动窗口在图像金字塔上逐级判断是否为人脸。它的优点是计算量小,在普通CPU上也能跑到几十毫秒一帧;缺点是对角度敏感,超过30度的侧脸基本失效。

OpenCV DNN人脸检测器是基于SSD网络结构训练的,精度高很多,对侧脸和遮挡的容忍度更好。我在实际测试中,把摄像头放在教室前方斜45度角的位置时,Haar普遍只能检测到正对镜头的前排学生,而DNN可以覆盖到中排侧身的学生。如果你的毕设想做出更好的效果,建议直接用DNN方案。

代码方面,Haar检测就几行:

import cv2 face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) def detect_faces_haar(gray_frame, scale_factor=1.1, min_neighbors=5): faces = face_cascade.detectMultiScale( gray_frame, scaleFactor=scale_factor, minNeighbors=min_neighbors, minSize=(60, 60) ) return faces # 返回 [x, y, w, h] 列表

DNN方案稍微复杂一点,但也不难:

import cv2 import numpy as np net = cv2.dnn.readNetFromCaffe( "deploy.prototxt", "res10_300x300_ssd_iter_140000.caffemodel" ) def detect_faces_dnn(frame, conf_threshold=0.6): h, w = frame.shape[:2] blob = cv2.dnn.blobFromImage( cv2.resize(frame, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0) ) net.setInput(blob) detections = net.forward() faces = [] for i in range(detections.shape[2]): confidence = detections[0, 0, i, 2] if confidence > conf_threshold: box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) x1, y1, x2, y2 = box.astype("int") faces.append((x1, y1, x2 - x1, y2 - y1)) return faces

实际跑下来,DNN的漏检率低很多。但如果你只是想在毕设里先跑通,Haar可以先用着,后面再升级。

2.2 抬头判断:从人脸框到头部姿态估计

这里是我要重点讲的部分。很多人一开始会想当然地认为,检测到人脸就等于抬头,检测不到就等于低头。这个想法做演示还行,但在教室实景里站不住脚:学生低头时,如果摄像头角度偏上,依然能检测到半张脸;学生抬头但手挡住脸,反而检测不到。纯粹靠“有没有人脸”来判断抬头率,论文里说不过去。

正确的思路是:对检测到的每张人脸,先定位脸部的关键点——比如鼻尖、下巴、左右眼角、左右嘴角——然后用这些2D像素坐标,结合一组标准3D人脸模型坐标,通过solvePnP算法解算头部的旋转向量,再转换成欧拉角。这里的pitch角就是俯仰角,抬头时pitch为正值,低头时为负值。设定一个阈值,pitch低于阈值就判定为低头。

3D人脸模型坐标用的是通用值,这是我用的标准坐标点(单位毫米):

model_points_3d = np.array([ [0.0, 0.0, 0.0], # 鼻尖,对应2D点索引30 [0.0, -330.0, -65.0], # 下巴,对应2D点索引8 [-225.0, 170.0, -135.0],# 左眼左眼角,对应2D点索引36 [225.0, 170.0, -135.0], # 右眼右眼角,对应2D点索引45 [-150.0, -150.0, -125.0],# 嘴巴左角,对应2D点索引48 [150.0, -150.0, -125.0] # 嘴巴右角,对应2D点索引54 ], dtype=np.float64)

对应的2D点来自dlib的68点关键点检测结果。dlib需要安装并下载一个模型文件shape_predictor_68_face_landmarks.dat,检测关键点的代码:

import dlib detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") def get_face_landmarks(frame, face_rect): gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) rect = dlib.rectangle(face_rect[0], face_rect[1], face_rect[0] + face_rect[2], face_rect[1] + face_rect[3]) shape = predictor(gray, rect) return shape # 通过 shape.part(i) 获取第 i 个关键点坐标

拿到关键点后,做姿态估计:

def calculate_head_pose(shape): # 提取7个关键点的2D坐标,注意顺序要和 model_points_3d 对应 img_points_2d = np.array([ (shape.part(30).x, shape.part(30).y), # 鼻尖 (shape.part(8).x, shape.part(8).y), # 下巴 (shape.part(36).x, shape.part(36).y), # 左眼左眼角 (shape.part(45).x, shape.part(45).y), # 右眼右眼角 (shape.part(48).x, shape.part(48).y), # 嘴巴左角 (shape.part(54).x, shape.part(54).y) # 嘴巴右角 ], dtype=np.float64) # 相机内参,实际项目中应该通过标定获得 # 这里用近似值:焦距取图像宽度,光心取图像中心 focal_length = frame_width center = (frame_width / 2, frame_height / 2) camera_matrix = np.array( [[focal_length, 0, center[0]], [0, focal_length, center[1]], [0, 0, 1]], dtype=np.float64 ) dist_coeffs = np.zeros((4, 1)) success, rotation_vector, translation_vector = cv2.solvePnP( model_points_3d, img_points_2d, camera_matrix, dist_coeffs, flags=cv2.SOLVEPNP_ITERATIVE ) # 旋转向量转旋转矩阵,再转欧拉角 rotation_matrix, _ = cv2.Rodrigues(rotation_vector) sy = np.sqrt(rotation_matrix[0, 0] ** 2 + rotation_matrix[1, 0] ** 2) singular = sy < 1e-6 if not singular: pitch = np.arctan2(rotation_matrix[2, 1], rotation_matrix[2, 2]) * 180.0 / np.pi else: pitch = np.arctan2(-rotation_matrix[1, 2], rotation_matrix[1, 1]) * 180.0 / np.pi return pitch

这里有一个关键注释要说明:相机内参矩阵我没有做真正的标定,而是用近似值,即焦距取图像宽度、光心取图像中心。这样做在绝大多数场景下误差在可接受范围内。如果你想要更精确的结果,可以用棋盘格标定相机,但对课堂抬头率这个应用来说,近似值足够区分抬头和低头了。

抬头/低头的判定阈值,我经过多轮测试后建议初始值为pitch < 12判定为低头,pitch >= 12判定为抬头。这个12度不是写死的,和摄像头安装高度、拍摄角度都有关系。实际使用中可以加一个调试滑块,实时调整阈值观察效果。

2.3 抬头率的数学定义与统计口径

抬头率听起来简单,但统计口径不提前定清楚,后面写论文会很难受。我在系统里用了三层统计:

  • 瞬时抬头率:某一帧画面中,抬头人数占检测到总人数的比例。比如一帧检测到8张脸,其中6张抬头,瞬时抬头率就是75%。
  • 平均抬头率:整个统计时段内所有有效帧的瞬时抬头率平均值。这是最终输出的核心指标。
  • 分时段抬头率:把整段时间按分钟切分,每分钟求一个平均抬头率,这样可以画出一条“时间-抬头率”曲线,观察课堂不同阶段的专注度变化。

用公式表达就是:

单帧抬头率 R(t) = 抬头人数(t) / 检测总人数(t) * 100% 整节课平均抬头率 R_avg = (1/N) * Σ R(t)

这里要注意的是分母的统计口径。有人会把“检测总人数”理解成画面里所有出现过的学生总数,但这样在有人暂时离座、被遮挡时,分母就会不准。更稳妥的做法是:只计算“当前帧能检测到的脸”作为分母,这一帧有多少张脸,就在这些脸里算抬头比例,然后对所有有效帧取平均。这样即使某帧只检测到两个人,也不会影响整体数据的合理性。

代码实现里就是维护几个累加变量:

total_frames = 0 # 有效帧数 total_rate_sum = 0.0 # 瞬时抬头率累加和 current_rate = 0.0 # 当前帧抬头率 # 每处理一帧: if detected_count > 0: head_up_count = ... current_rate = head_up_count / detected_count * 100 total_frames += 1 total_rate_sum += current_rate # 结束时: avg_rate = total_rate_sum / total_frames if total_frames > 0 else 0

3. 完整代码实现与模块分解

3.1 项目结构规划

写代码之前先规划好目录结构。我的项目是这样的:

head_up_rate_system/ │ ├── main.py # 程序入口,启动GUI ├── detector.py # 人脸检测与姿态估计模块 ├── statistics.py # 抬头率统计模块 ├── ui_main.py # PyQt5主窗口 ├── video_thread.py # 视频处理线程 │ ├── models/ │ ├── haarcascade_frontalface_default.xml │ ├── deploy.prototxt │ ├── res10_300x300_ssd_iter_140000.caffemodel │ └── shape_predictor_68_face_landmarks.dat │ └── output/ └── result.csv # 统计结果输出

模块之间职责分明,detector.py负责算法,video_thread.py负责在后台线程里处理视频帧,statistics.py负责统计,ui_main.py只负责界面显示和用户交互。这样分开的好处是调bug的时候不用在一个文件里反复翻,论文里画系统结构图也方便。

3.2 GUI主窗口与摄像头画面显示

用PyQt5写界面,核心是把OpenCV的BGR帧转成QImage,再显示到QLabel上。这个转换代码非常固定:

def cv2_to_qimage(frame): rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape bytes_per_line = ch * w qimage = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) return qimage.copy()

bytesPerLine不写对的话,图像会裂开或者颜色错乱,这是新手最容易踩的坑。copy()也不能省,因为原图数据在OpenCV侧可能被释放,不拷贝会出现花屏。

主窗口布局我用了左图右信息的方式:左边是一个大QLabel显示实时视频画面,右边放检测状态、抬头率实时数值、控制按钮和统计曲线。右侧信息区从上到下依次是:当前帧抬头数/总人数、实时抬头率、平均抬头率、开始/停止/导出按钮、阈值调节滑条、绘制曲线的QLabel。

3.3 视频处理线程与检测逻辑

GUI里跑视频处理必须用线程,否则摄像头帧一多,界面会卡成PPT。我封装了一个VideoThread,继承自QThread,每一帧的处理流程是:

  • VideoCapture读取一帧
  • 转灰度图,做直方图均衡化(equalizeHist),增强低光照下的人脸检测效果
  • 调用人脸检测器,得到所有人脸框
  • 对每个人脸框提取关键点,计算pitch角
  • 根据pitch阈值判断抬头/低头
  • 把检测结果绘制到帧上(画框,抬头画绿色,低头画红色)
  • 把统计结果存入统计模块
  • 把处理后的帧转成QImage,通过信号发给主窗口显示

核心逻辑:

import cv2 import numpy as np import dlib from PyQt5.QtCore import QThread, pyqtSignal class VideoThread(QThread): change_pixmap = pyqtSignal(QImage) update_stats = pyqtSignal(dict) def __init__(self, detector, statistics, parent=None): super().__init__(parent) self.detector = detector self.statistics = statistics self.running = True self.cap = cv2.VideoCapture(0) def run(self): while self.running: ret, frame = self.cap.read() if not ret: continue # 缩小画面加速处理,长边控制在640左右 frame = self.detector.resize_to_width(frame, 640) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) faces = self.detector.detect_faces(gray) head_up_count = 0 total_count = 0 for (x, y, w, h) in faces: # 扩一下人脸框,给关键点检测提供更多上下文 expand = 20 x1 = max(0, x - expand) y1 = max(0, y - expand) x2 = min(frame.shape[1], x + w + expand) y2 = min(frame.shape[0], y + h + expand) shape = self.detector.get_landmarks(gray, (x1, y1, x2, y2)) if shape is None: continue pitch = self.detector.get_pitch(shape, frame.shape[1], frame.shape[0]) is_head_up = pitch >= self.detector.pitch_threshold color = (0, 255, 0) if is_head_up else (0, 0, 255) cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) cv2.putText(frame, f"pitch:{pitch:.1f}", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) total_count += 1 if is_head_up: head_up_count += 1 self.statistics.update_frame(head_up_count, total_count) stats = self.statistics.get_current_stats() self.update_stats.emit(stats) qt_image = cv2_to_qimage(frame) self.change_pixmap.emit(qt_image) # 控制处理帧率,避免CPU占用过高 self.msleep(50) # 约20FPS self.cap.release() def stop(self): self.running = False self.wait()

这里有个细节:self.msleep(50)把帧率限制在20FPS左右。人脸检测和姿态估计在CPU上跑,分辨率越高越慢。把图像长边缩到640像素之后,单帧处理时间通常在30到80毫秒之间,加上msleep间隔,界面还是很流畅的。如果追求更低的CPU占用,可以把帧率压到10FPS,对抬头率统计影响也不大,因为抬头动作本身就是慢变的。

3.4 抬头率统计与数据导出

统计模块用了一个独立类来维护所有数据。除了前面提到的三个统计指标,我还会记录每分钟的平均抬头率,最后导出的CSV结构是:

时间,累计平均抬头率,当前帧抬头率,当前帧人数,抬头人数

为了避免中途程序崩溃丢数据,我会在界面停止时自动保存一份CSV,另外加一个手动导出按钮,可以随时把当前统计结果写出去。导出代码很简单:

import csv def export_to_csv(self, filepath): with open(filepath, "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["second", "avg_rate", "current_rate", "total_count", "head_up_count"]) for record in self.records: writer.writerow(record) writer.writerow(["OVERALL", self.avg_rate, "", "", ""])

utf-8-sig编码是为了让Excel直接打开CSV时中文不乱码。这个细节很多人的代码里都没有,但用的时候特别实用。

数据可视化我额外用matplotlib画了一张“每分钟平均抬头率”的柱状图,在界面停止后显示出来。画图这块单独写在统计模块里,生成图片后保存到输出目录,答辩时可以当素材用。

4. 环境搭建与运行指南

4.1 依赖安装与模型文件准备

要跑起来,需要安装的Python包如下:

pip install opencv-python pip install dlib pip install PyQt5 pip install numpy pip install matplotlib

这里面最容易出问题的是dlib。Windows环境下装dlib经常报错,原因是需要编译C++代码。我的建议是:

  • 如果用的是Python 3.10以下的版本,先试试pip install dlib,装不上再找预编译的wheel文件。
  • Python 3.11以上,dlib官方包经常没有对应wheel,可以换用pip install dlib-bin,或者干脆用Anaconda环境,conda install -c conda-forge dlib会省事很多。
  • 如果实在装不上dlib,还有一个退路:用OpenCV自带的cv2.getRotationMatrix2D做简单的模板匹配,但效果会差不少,不推荐。

模型文件有三个:

  • haarcascade_frontalface_default.xml:OpenCV安装包自带,通过cv2.data.haarcascades能找到。
  • res10_300x300_ssd_iter_140000.caffemodeldeploy.prototxt:需要从OpenCV的GitHub仓库下载。
  • shape_predictor_68_face_landmarks.dat:从dlib官方或GitHub下载,约100MB,这个是关键点检测的核心文件。

4.2 运行流程与实测效果

运行很简单,python main.py就会弹出GUI,点击“开始检测”后摄像头启动。没有摄像头的电脑,可以把代码里的VideoCapture(0)换成视频文件路径,系统就会按视频文件逐帧处理。

我在一个模拟课堂环境的场景下实测过:用一台普通笔记本,720P摄像头,拍摄距离2到4米,室内光线正常。Haar方案下,单帧检测到2到4张人脸,处理速度约20FPS;换成DNN方案后,检测到的人脸数量提升到4到6张,处理速度降到12FPS左右。抬头判断的准确率很难精确量化,因为缺少标注真值,但目测下来,阈值设在12到15度之间时,判断结果和实际动作基本吻合。

整个检测过程中,GUI右侧的平均抬头率数字会平滑变动,而当前帧抬头率波动比较大,这是因为单帧画面里可能正好有学生低头捡东西,导致瞬时值跳变。所以最终输出看平均值更有意义。

4.3 常见问题排查实录

这里把最常见的坑整理一下,都是我在测试过程中真实遇到过的。

问题一:摄像头打不开,报错Unable to capture

优先检查摄像头是否被占用,比如微信、腾讯会议之类的软件正在使用摄像头,在任务管理器里把占用程序关掉再试。另外VideoCapture(0)里的0是设备索引,笔记本自带摄像头通常为0,外接USB摄像头可能需要改成1,可以写循环尝试多个索引。

问题二:dlib装不上

Windows下pip install dlib编译报错,基本是缺少Visual Studio Build Tools或CMake。最省事的方法是用Anaconda装:conda install -c conda-forge dlib。装完之后在Python里执行import dlib验证一下。

问题三:人脸检测结果全是零

先看摄像头画面是否在GUI中正常显示,再检查模型路径是否正确。很多人把xml文件路径写死,换机器跑就找不到文件。我建议用os.path.join拼接路径,并检查文件是否存在。

问题四:OpenCV报error: (-215:Assertion failed)

这种情况大多是imread读到空图,也就是模型文件路径不对。建议在代码里先打印路径和cv2.os.path.exists()的结果。

问题五:界面卡顿

界面卡顿一般是没开线程,或者视频帧太大。把处理线程独立出来、把图片缩放到640宽度、限制帧率在20FPS左右,卡顿基本能解决。如果还用matplotlib画图,注意画图不要阻塞主线程,尽量只在停止统计后画一次。

问题六:抬头率数值跳变太剧烈

把“当前帧抬头率”和“平均抬头率”分开看待,界面显示的时候对当前帧做滑动平均,比如取最近10帧的平均值来抑制噪声。统计上报的时候还是用原始值,这样可以兼顾实时性和稳定性。

5. 作为毕设的进阶扩展与答辩经验

5.1 功能层面的扩展思路

如果时间充裕,下面这些扩展方向可以选一两个做,能明显提升毕设的完成度。

第一,把检测模型升级为YOLOv8。YOLOv8在精度和速度上都远超Haar和OpenCV DNN,而且官方提供了Python接口,训练和推理都很方便。换成YOLO后,人脸检测的召回率会有明显提升,尤其是低头时检测到半张脸的情况。

第二,加入课程管理功能。把系统从“单次课堂统计”升级成“多课程数据管理系统”,用SQLite保存每次检测的课程名称、教师、时段、抬头率曲线,还能按课程横向对比。这个功能一加,系统就从工具变成了平台,论文里的系统设计章节能多写好几页。

第三,联动人脸识别考勤。抬头率检测系统里本来就有“检测到人脸”和“识别到是谁”的能力。如果把第二步的人脸特征提取加上,就可以同时实现“查到课人数”和“判断听课状态”,一个系统解决两个问题,答辩时很容易讲出应用价值。

第四,支持摄像头云台追踪。用两个舵机把摄像头架起来,根据人脸在画面中的位置控制舵机转动,让摄像头自动跟踪学生区域。这个属于硬件结合的方向,如果你们实验室有相关的板卡和伺服电机,可以尝试。

5.2 论文写作与答辩展示建议

论文核心章节的框架,我建议按“需求分析-总体设计-模块详细设计-系统测试-总结”来写,跟代码模块一一对应。很多人写论文时只贴代码,不画图,这是大忌。我在上一篇提到,模块结构图、时序图、流程图都能直观展示工作量,建议用Visio或者draw.io画清楚。

答辩演示时,有几点经验值得注意:

  • 提前准备好一个录好的演示视频。现场摄像头有时候受光线、位置限制,效果不稳定,录好的视频可以保证演示效果。
  • 展示抬头率变化曲线时,准备一段学生明显在“低头写作业-抬头看黑板”之间切换的测试视频,这样曲线变化会特别明显,评委一眼就能看懂系统逻辑。
  • 准备2到3张效果对比图:Haar和DNN的检测结果对比、阈值不同时抬头率曲线对比、光线正常与光线偏暗时的检测效果对比。这些对比图是论文和PPT里的加分项。

最后再分享一个小技巧:用系统的时候,摄像头的高度会直接影响抬头判断。摄像头放在讲台平视位置时,学生正常看黑板基本上是0度左右的pitch角;摄像头放高往下俯拍时,所有学生的pitch角会整体偏负。所以如果你的摄像头安装角度比较特殊,一定要用阈值调节滑条先做一次校准,让一个明确抬头的人被判定为抬头,再开始正式统计。拿到一套代码先用起来,再根据实际环境调参数,这比一开始就追求完美方案要实在得多。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 11:11:00

LX Music 桌面版:一个免费聚合多音源的音乐搜索播放器

LX Music 桌面版&#xff1a;一个免费聚合多音源的音乐搜索播放器 【免费下载链接】lx-music-desktop 一个基于 Electron 的音乐软件 项目地址: https://gitcode.com/GitHub_Trending/lx/lx-music-desktop 听一首歌要在好几个 App 之间来回切换&#xff0c;还总被会员墙…

作者头像 李华
网站建设 2026/8/30 11:10:48

Docling 文档解析:让 200 份 PDF 变 RAG 就绪只需 3 行代码

Docling 文档解析&#xff1a;让 200 份 PDF 变 RAG 就绪只需 3 行代码 【免费下载链接】docling Get your documents ready for gen AI 项目地址: https://gitcode.com/GitHub_Trending/do/docling 当 200 份 PDF 要喂进 RAG&#xff0c;先确认 Docling 管哪一段 你的…

作者头像 李华
网站建设 2026/8/30 11:06:51

旅行者1号FDS模拟器:探秘老式航天计算机的指令级仿真

旅行者 1 号上的 FDS 计算机模拟器&#xff0c;听起来像是一小撮航天爱好者的自嗨&#xff0c;但真正上手以后你会发现&#xff0c;它比跑一个大模型或者调一个视频渲染管线更能逼你理解“计算机到底是怎么工作的”。FDS 的全称是 Flight Data System&#xff0c;中文通常叫飞行…

作者头像 李华
网站建设 2026/8/30 11:06:46

S2-LP驱动外部PA:从14dBm到27dBm的射频设计实战

做无线数传项目时&#xff0c;S2-LP这颗sub-1GHz收发器用得挺多。它的优点大家都清楚&#xff1a;功耗低、协议栈灵活、灵敏度也不错&#xff0c;但有一个限制很现实——内置PA的输出能力上限不高&#xff0c;典型配置下也就14dBm上下。LAT1385这份应用笔记&#xff0c;讲的就是…

作者头像 李华
网站建设 2026/8/30 11:06:38

vLLM的C++实现:从PagedAttention到KV Cache管理实战

最近在整理大模型推理相关的内容时&#xff0c;很多朋友都问过同一个问题&#xff1a;“vLLM 有 C 版本吗&#xff1f;我看它底层好像用到了很多 C&#xff0c;能不能直接用 C 写一个&#xff1f;”这个问题其实藏着一个非常关键的技术认知&#xff1a;vLLM 表面上是一个 Pytho…

作者头像 李华
网站建设 2026/8/30 11:04:41

K3I-Core:从内核隔离到硬件级否决开关的安全架构解析

安全团队往往有一种错觉&#xff1a;把权限收得足够紧&#xff0c;系统就足够安全。但真正经历过内核级攻击的人会告诉你&#xff0c;用户态的权限控制只是第一道墙&#xff0c;墙后面还有一层更关键的东西——即使攻击者已经拿到 root 权限&#xff0c;甚至已经坐在内核态里&a…

作者头像 李华