赞
踩
本项目主要实现了课堂专注度分析与考试作弊检测两个功能,通过对学生的姿态检测,可以有效的辅助老师有效监督学生的学习上课情况,对学生的上课行为进行分析及评分,避免出现课堂不认真听讲、考试作弊等不良的行为;考试作弊检测通过学生在考试的实时监控,在有系统判定的异常行为时,并对其实时抓拍记录
在我们日常的课堂上,教学秩序管理与严防考试作弊一直是在教育上被重视的问题,尤其在疫情反复的时期,许多学校会采用线上上课的方式进行教学,使得教学秩序与考试的监管更加困难,目前的教学秩序监管与考试作弊检测主要还是以人工为主,难免为老师的课堂增加了负担,同时人工考试监控难免有疏漏之处,对于自觉性不高的学生,线上教学模式为课堂教学管理与考试作弊检测更是难上加难,学生学习的效果也易参差不齐,为了进一步利用人工智能技术辅助课堂教学,因此我们使用深度学习技术对学生上课状态进行姿态估计与专注度分析,我们使用具有易用性、本土性、快速业务集成性等众多优点的PaddlePaddle深度学习框架作为我们的开发工具,支持我们项目从模型的构建,模型训练、模型预测等整套开发流程,实现了对学生在课堂上的专注度分析与考试作弊检测的两大功能。
本项目通过对学生的在上课时的头部姿态检测、骨骼关键点检测,对学生的上课状况进行分析评分,并将结果可视化呈现并实时反馈至老师端,实现了课堂行为专注度分析,能够有效地辅助老师监督学生的学习上课情况,及时提醒学生上课认真听讲,避免上课不认真听讲、低头睡觉等不良习惯出现在课堂,从而有效的提高学生在课堂中的学习效率和学习质量;同时,通过学生在考试时的实时监控,当学生存在异常行为时,经由系统判定事件,对该行为进行实时抓拍记录留证,老师可以直接得到学生考试时的行为照片并核验是否有作弊行为,实现对考试实时监测,减轻老师监考的负担,一并督促学生避免出现考试作弊的行为。
从网络摄像头或是本地视频获取视频流,分别进行目标检测、姿态检测、算法评估、数据可视化得到视频流处理后,对分析结果数据可视化
此处建议可以通过对学习率、batch_size进行修改,对迭代次数进行上下调整,尽可能的找到合适的数值使得训练的模型达到最佳效果
Learning Rate: Base _lr: 0.06 (学习率) schedulers: - !CosineDecay Max _iters: 29000 (最大的迭代) - !Linear Warm up Start _factor: 0.33333 steps: 3000 (预热迭代步数) Train Reader: Inputs _def: Image _shape: [3, 300, 300] fields: ['image', 'gt _bbox', 'gt _class'] dataset: !COCO Data Set Dataset _dir: /home/aistudio/work/data Anno _path: annotations/train.json Image _dir: /home/aistudio/work/data/images Sample _transforms: - !Decode Image To _rgb: true - !Random Distort Brightness _lower: 0.875 Brightness _upper: 1.125 Is _order: true - !Random Expand fill _value: [123.675, 116.28, 103.53] - !Random Crop allow _no_crop: false - !NormalizeBox {} - !Resize Image interp: 1 target _size: 300 use_cv2: false - !Random Flip Image is _normalized: false - !Norma lize Image mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] is _scale: true is _channel_first: false - !Permute to_bgr: false channel_first: true batch_size: 32
可以选择rtsp视频流作为视频源,视频通道下摄像头以外的选项在项目文件根目录中有一个video_sources.csv文件,通过编写名称和对应的地址可以添加多个网络摄像头;也可以添加本地视频作为视频源,在项目文件目录的中分有两个文件夹分别是cheating_detection和class_concentration,分别对应作弊检测和课堂专注度分析,将视频文件放入到对应的文件夹中便可在对应的功能模块中使用
def __init__(self, parent=None): super(ClassConcentrationApp, self).__init__(parent) self.setupUi(self) self.video_source = 0 self.frame_data_list = OffsetList() self.opened_source = None self.playing = None self.playing_real_time = False self.pushed_frame = False # 视频事件 # 设置视频源事件 self.open_source_lock = Lock() self.open_source_btn.clicked.connect( lambda: self.open_source(self.video_source_txt.text() if len(self.video_source_txt.text()) != 0 else 0)) self.video_resource_list.itemClicked.connect(lambda item: self.open_source(item.src)) self.video_resource_file_list.itemClicked.connect(lambda item: self.open_source(item.src)) self.close_source_btn.clicked.connect(self.close_source) self.play_video_btn.clicked.connect(self.play_video) self.stop_playing_btn.clicked.connect(self.stop_playing) self.video_process_bar.valueChanged.connect(self.change_frame) self.push_frame_signal.connect(self.push_frame) # 设置列表 self.draw_img_on_window_signal.connect(self.draw_img_on_window2) # 初始化视频源 self.init_video_source() # 图像坐标数据 self.x_time_data = [] self.y_action_data = [] self.y_face_data = [] self.y_head_pose_data = [] self.y_primary_level_data = [] self.primary_factor = None self.draw_img_timer = QTimer(self) self.draw_img_timer.timeout.connect(self.refresh_img_on_window) # 初始化界面剩余部分 self.init_rest_window() # 启动视频源 def open_source_func(self): fps = 12 self.opened_source = TaskSolution() \ .set_source_module(VideoModule(source, fps=fps)) \ .set_next_module(YoloV5Module(yolov5_weight, device)) \ .set_next_module(AlphaPoseModule(alphapose_weight, device)) \ .set_next_module(ConcentrationEvaluationModule(classroom_action_weight)) \ .set_next_module(ClassConcentrationVisModule(lambda d: self.push_frame_signal.emit(d))) self.opened_source.start() self.playing_real_time = True self.open_source_lock.release() Thread(target=open_source_func, args=[self]).start()
def init_video_source(self): # 添加视频通道 VideoSourceItem(self.video_resource_list, "摄像头", 0).add_item() # 添加本地视频文件 local_source = 'resource/videos/cheating_detection' if not os.path.exists(local_source): os.makedirs(local_source) else: print(f"本地视频目录已创建: {local_source}") videos = [*filter(lambda x: x.endswith('.mp4'), os.listdir(local_source))] for video_name in videos: VideoSourceItem(self.video_resource_file_list, video_name, os.path.join(local_source, video_name), ico_src=':/videos/multimedia.ico').add_item() with open('resource/video_sources.csv', 'r', encoding='utf-8') as f: reader = csv.reader(f) for row in islice(reader, 1, None): VideoSourceItem(self.video_resource_list, row[0], row[1], ico_src=':/videos/webcam.ico').add_item()
课堂专注度分析模块有群体专注曲线,头部姿态专注度评分曲线,情绪专注度评分曲线,行为专注度评分曲线,通过头背部姿态估计、传递动作识别等综合统计,抉出最佳行为
def process_data(self, data): data.num_of_cheating = 0 data.num_of_normal = 0 data.num_of_passing = 0 data.num_of_peep = 0 data.num_of_gazing_around = 0 if data.detections.shape[0] > 0: # 行为识别 data.classes_probs = self.classifier.classify(data.keypoints[:, self.use_keypoints]) # 最佳行为分类 data.raw_best_preds = torch.argmax(data.classes_probs, dim=1) data.best_preds = [self.reclassify(idx) for idx in data.raw_best_preds] data.raw_classes_names = self.raw_class_names data.classes_names = self.class_names # 头背部姿态估计 data.head_pose = [self.pnp.solve_pose(kp) for kp in data.keypoints[:, 26:94, :2].numpy()] data.draw_axis = self.pnp.draw_axis data.head_pose_euler = [self.pnp.get_euler(*vec) for vec in data.head_pose] # 传递动作识别 is_passing_list = CheatingActionAnalysis.is_passing(data.keypoints) # 头部姿态辅助判断转头 for i in range(len(data.best_preds)): if data.best_preds[i] == 0: if is_passing_list[i] != 0: data.best_preds[i] = 1 elif data.head_pose_euler[i][1][0] < peep_threshold: data.best_preds[i] = 2 data.pred_class_names = [self.class_names[i] for i in data.best_preds] # 统计人数 data.num_of_normal = data.best_preds.count(0) data.num_of_passing = data.best_preds.count(1) data.num_of_peep = data.best_preds.count(2) data.num_of_gazing_around = data.best_preds.count(3) data.num_of_cheating = data.detections.shape[0] - data.num_of_normal return TASK_DATA_OK
对从视频源获取的人脸关键点、头背部姿态等信息对上课状态进行分类
def class_action_reclassify(self, action_preds): """ 重新分类课堂动作标签 """ reclassified_preds = np.empty_like(action_preds) for lbl, new_class in enumerate(self.reclassified_class_actions): reclassified_preds[action_preds == lbl] = new_class min_len = self.action_fuzzy_matrix.shape[0] result = np.eye(min_len)[reclassified_preds] count_vec = np.bincount(reclassified_preds, minlength=min_len) return result, count_vec def face_action_reclassify(self, face_preds, face_hidden=None): """ 重新分类面部疲劳标签 0 "nature" 1 "happy" 2 "confused" 3 "amazing" """ result = np.empty_like(face_preds) result[(face_preds == 1) | (face_preds == 3)] = 1 result[face_preds == 0] = 2 result[face_preds == 2] = 3 if face_hidden is not None: result[face_hidden] = 0 min_len = self.face_fuzzy_matrix.shape[0] count_vec = np.bincount(result, minlength=min_len) return np.eye(min_len)[result], count_vec def head_pose_reclassify(self, head_pose_preds, face_hidden=None): """ 离散化分类头部角度 """ print(head_pose_preds.flatten().tolist()) discretization_head_pose = np.empty_like(head_pose_preds, dtype=np.int64) for d1, d2, lbl in self.head_pose_section: discretization_head_pose[(d1 < head_pose_preds) & (head_pose_preds <= d2)] = lbl if face_hidden is not None: discretization_head_pose[face_hidden] = 0 # 分解上课和自习状态 count_ = np.array([np.count_nonzero(discretization_head_pose == 1), np.count_nonzero(discretization_head_pose == 2)]) sum_count_ = np.sum(count_) count_ = np.array([0, 1]) if sum_count_ == 0 else count_ / sum_count_ # count_ = np.array([0, 1]) if sum_count_ == 0 else np.round(count_ / sum_count_) encode = np.array([ [1, 0, 0, 0, 0, 0], [0, count_[1], 0, count_[0], 0, 0], [0, 0, count_[1], 0, count_[0], 0], [0, 0, 0, 0, 0, 1] ]) discretization_head_pose = discretization_head_pose.flatten() result = encode[discretization_head_pose] min_len = self.head_pose_fuzzy_matrix.shape[0] count_vec = np.bincount(discretization_head_pose, minlength=min_len) return result, count_vec
通过基于关键点的伸手识别,判断考试异常行为中传纸条动作,头部姿态估计同样应用于这里的低头偷看异常行为
def stretch_out_degree(keypoints, left=True, right=True, focus=None): """ :param focus: 透视焦点 :param keypoints: Halpe 26 keypoints 或 136关键点 [N,keypoints] :param left: 是否计算作弊的伸手情况 :param right: 是否计算右臂的伸手情况 :return: ([N,left_hand_degree],[N,left_hand_degree]), hand_degree = (arm out?,forearm out?,straight arm?) """ if focus is None: shoulder_vec = keypoints[:, 6] - keypoints[:, 5] else: shoulder_vec = (keypoints[:, 6] + keypoints[:, 5]) / 2 - focus result = [] if left: arm_vec = keypoints[:, 5] - keypoints[:, 7] forearm_vec = keypoints[:, 7] - keypoints[:, 9] _results = torch.hstack([torch.cosine_similarity(shoulder_vec, arm_vec).unsqueeze(1), torch.cosine_similarity(shoulder_vec, forearm_vec).unsqueeze(1), torch.cosine_similarity(arm_vec, forearm_vec).unsqueeze(1)]) result.append(_results) if right: shoulder_vec = -shoulder_vec arm_vec = keypoints[:, 6] - keypoints[:, 8] forearm_vec = keypoints[:, 8] - keypoints[:, 10] _results = torch.hstack([torch.cosine_similarity(shoulder_vec, arm_vec).unsqueeze(1), torch.cosine_similarity(shoulder_vec, forearm_vec).unsqueeze(1), torch.cosine_similarity(arm_vec, forearm_vec).unsqueeze(1)]) result.append(_results) return result
def is_passing(keypoints): """ 是否在左右传递物品 :param keypoints: Halpe 26 keypoints 或 136关键点 [N,keypoints] :return: [N,左右传递?]+1 左传递,-1 右边传递 0 否 """ irh = CheatingActionAnalysis.is_raise_hand(keypoints) stretch_out_degree_L, stretch_out_degree_R = CheatingActionAnalysis.stretch_out_degree(keypoints) isoL = CheatingActionAnalysis.is_stretch_out(stretch_out_degree_L) isoR = CheatingActionAnalysis.is_stretch_out(stretch_out_degree_R) left_pass = isoL & ~irh[:, 0] # 是否是左传递 left_pass_value = torch.zeros_like(left_pass, dtype=int) left_pass_value[left_pass] = 1 right_pass = isoR & ~irh[:, 1] # 是否是右传递 right_pass_value = torch.zeros_like(right_pass, dtype=int) right_pass_value[right_pass] = -1 return left_pass_value + right_pass_value
对经过视频中人物行为的分析分类进行分级评价,从而实现对课堂人物行为的评分以及对实时数据曲线图的更新
def evaluate(self, action_preds: ndarray, face_preds: ndarray, head_pose_preds: ndarray, face_hidden: ndarray = None) -> ConcentrationEvaluation: # 分析二级评价因素 self.action_preds, self.action_count = self.class_action_reclassify(action_preds) self.face_preds, self.face_count = self.face_action_reclassify(face_preds, face_hidden) self.head_pose_preds, self.head_pose_count = self.head_pose_reclassify(head_pose_preds, face_hidden) # 二级评价等级 self.action_levels = self.action_preds @ self.action_fuzzy_matrix @ self.evaluation_level self.face_levels = self.face_preds @ self.face_fuzzy_matrix @ self.evaluation_level self.head_pose_levels = self.head_pose_preds @ self.head_pose_fuzzy_matrix @ self.evaluation_level self.secondary_levels = np.hstack([ self.action_levels[..., np.newaxis], self.face_levels[..., np.newaxis], self.head_pose_levels[..., np.newaxis] ]) # 分析一级评级因素 self.action_info_entropy = self.info_entropy(self.action_count / np.sum(self.action_count)) self.face_info_entropy = self.info_entropy(self.face_count / np.sum(self.face_count)) self.head_pose_info_entropy = self.info_entropy(self.head_pose_count / np.sum(self.head_pose_count)) self.primary_factor = self.softmax(np.array([self.action_info_entropy, self.face_info_entropy, self.head_pose_info_entropy])) # 一级评价因素等级 self.primary_levels = self.secondary_levels @ self.primary_factor return ConcentrationEvaluation(self.primary_levels, self.primary_factor, self.secondary_levels)
def add_data_to_list(self, data): """ 将数据绘制到界面图像得到位置 """ try: time_process = second2str(data.time_process) max_idx = len(self.x_time_data) - 1 if max_idx >= 0 and time_process == self.x_time_data[max_idx]: return # 更新数据 self.x_time_data.append(time_process) concentration_evaluation: ConcentrationEvaluation = data.concentration_evaluation secondary_mean_levels = np.mean(concentration_evaluation.secondary_levels, axis=0) self.y_action_data.append(secondary_mean_levels[0]) self.y_face_data.append(secondary_mean_levels[1]) self.y_head_pose_data.append(secondary_mean_levels[2]) self.y_primary_level_data.append(np.mean(concentration_evaluation.primary_levels)) while len(self.x_time_data) > self.line_data_limit_spin.value(): for i in [self.x_time_data, self.y_action_data, self.y_face_data, self.y_head_pose_data, self.y_primary_level_data]: i.pop(0) self.primary_factor = concentration_evaluation.primary_factor self.pushed_frame = True except Exception as e: self.y_face_data, self.y_head_pose_data, self.y_primary_level_data]: i.pop(0) self.primary_factor = concentration_evaluation.primary_factor self.pushed_frame = True except Exception as e: print("add_data_to_list", e)
为达到软件的轻量化与保持在不同设备上均能使用的较高兼容性,我们使用PyQt5编写了GUI用于我们的项目效果展示,将处理后反馈的视频流与分析检测结果可视化,这里我们设置了单镜头与连接多镜头角度下的行为分析效果与考试作弊检测测试。
在该模块当中,我们在界面左边部分设置有视频源,远程摄像头视频通道和本地视频都可以播放视频。在主界面的下方和右方分别有群体专注曲线,头部姿态专注度评分曲线,情绪专注度评分曲线,行为专注度评分曲线,将学生的课堂行为做出判断分析与数据的可视化。
在考试作弊检测模块中,界面的中间是视频播放的位置,左侧设置依然是视频源,视频流播放界面下方是实时抓拍记录学生疑似考试作弊行为的展示框,能够对视频中学生出现的异常的行为进行抓拍记录。目前支持的有四中状态:正常、东张西望、低头偷看和传纸条。界面的右侧分别是作弊发生的时间统计数据和异常行为时间曲线,分析时间段之间发生异常行为的频率,方便统计发生作弊动作次数与时间上的规律。在图片的下方记录了异常行为检测记录的时间点与发生的行为,点击图片可以返回记录的时间点进行人为进行判断。
学生课堂异常行为检测与分析系统基本能够达到预期所希望的效果,能够代替人工监控,达到主动监控,同时没有人工监控所存在的问题,比如说视觉疲劳、精神疲劳。能提减小外在因素的影响,并且能够对作弊的情况、异常行为进行保存,不用再去回放录像,十分的方便,但是当前我们所做的工作仍然是有限的,系统的可拓展性非常大,仍具有向上向大的方向发展的潜能,所以我对于我们项目的未来落地应用满怀期待,我们会继续改进我们的工作:
此文章为搬运
原项目链接
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。