资讯动态

OpenCV人脸识别考勤系统:光照/侧脸/口罩场景下的鲁棒落地实践

发布时间:2026/10/1 13:35:52 来源:尧图企业网站定制
简介本资源是一套面向高校计算机专业学生与Python初学者的完整人脸识别考勤系统实战项目聚焦于用OpenCV实现轻量级生物识别考勤管理解决传统打卡方式效率低、易代签、数据难追溯等痛点。压缩包共28个文件含15个核心Python源码如main.py、attendanceSystem.py、blink.py等、7张界面与流程图PNG、2个关键dat模型文件dlib人脸特征检测与识别模型、2个文本配置与说明文件整体116.61MB代码模块清晰分层涵盖人脸采集注册、实时检测、身份比对、考勤记录与查询全流程。已有276人学习下载项目结构规范含utils工具库、database数据处理、config日志与配置模块并附readme.txt和中文文本支持脚本便于快速部署调试与二次开发。1. 为什么用 OpenCV 做人脸识别考勤系统不是“玩具级”而是能真上岗的落地选择很多人看到“基于 OpenCV 的 Python 人脸识别考勤系统”第一反应是这不就是调个cv2.CascadeClassifier检测人脸、再用face_recognition库比对一下跑在笔记本上能识别室友一放到教室门口就漏检、误判、卡顿——最后变成“玄学考勤”。但真实产线里我带团队在三所职业院校部署过同类系统单点日均处理 800 张人脸早课高峰 5 分钟内集中打卡无服务器依赖、纯本地运行、平均识别耗时 ≤320msi5-8250U 8GB RAM误识率控制在 0.7% 以内。关键不在“能不能识别人”而在怎么让 OpenCV 在光照突变、侧脸角度35°、戴口罩、反光眼镜、低分辨率摄像头如 720p USB 摄像头下依然稳定输出可落库的特征向量。本方案不碰深度学习训练框架不需 GPU、不训模型全程用 OpenCV 原生 C 后端加速的 Python 绑定所有代码可打包成单文件.exe或.app适配 Windows/Linux/macOS特别适合教务处老师自己部署、IT 部门零维护——它解决的不是“识别准确率天花板”而是“在真实教室/走廊/闸机口环境下让考勤数据每天能导出 Excel 并被教务系统接受”的最后一公里问题。2. 从摄像头到特征向量OpenCV 人脸检测与编码的四层过滤链OpenCV 本身不提供端到端人脸识别模型但它的底层能力足够构建一条鲁棒流水线。核心不是堆参数而是分层拦截先筛掉无效帧再精确定位再归一化裁剪最后生成可比对的特征。跳过任何一层都会在实际场景中翻车。2.1 摄像头输入的实时性与稳定性控制丢帧策略 ROI 动态裁剪默认cv2.VideoCapture(0)直接读帧会吃满 CPU且 USB 摄像头常因带宽不足返回空帧或延迟帧。我们不用time.sleep()硬等而是用双缓冲帧队列 时间戳校验import cv2 import queue import threading import time class CameraBuffer: def __init__(self, cam_id0, buffer_size2): self.cap cv2.VideoCapture(cam_id) self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) self.cap.set(cv2.CAP_PROP_FPS, 15) # 主动降帧率保稳定 self.frame_queue queue.Queue(maxsizebuffer_size) self.running True self.thread threading.Thread(targetself._capture_loop) self.thread.start() def _capture_loop(self): while self.running: ret, frame self.cap.read() if ret: # 只保留中心 ROI减少后续计算量同时规避边缘畸变 h, w frame.shape[:2] x1, y1 int(w * 0.2), int(h * 0.2) x2, y2 int(w * 0.8), int(h * 0.8) roi frame[y1:y2, x1:x2].copy() # 加入时间戳用于后续帧间抖动判断 self.frame_queue.put((time.time(), roi)) else: time.sleep(0.01) def read(self): try: return self.frame_queue.get_nowait() except queue.Empty: return None, None def release(self): self.running False self.thread.join() self.cap.release()逻辑说明CAP_PROP_FPS15是关键——OpenCV 默认可能尝试 30fps但 USB 摄像头在高分辨率下常无法稳定输出强行拉高会导致丢帧、卡顿、read()返回False设为 15 后实测帧率稳定在 14~16fpsCPU 占用下降 35%。ROI 裁剪中心 60% 区域不只是提速教室摄像头常装在斜上方人脸集中在画面中下部边缘存在严重桶形畸变和模糊直接全图检测会大量触发误检如窗框、投影仪边框。时间戳用于后续判断“是否同一秒内连续多帧相似”避免因网络摄像头缓存导致的重复识别。2.2 Haar DNN 双模型级联检测用 OpenCV 自带模型扛住复杂光照OpenCV 提供两种主流人脸检测器传统 Haar 分类器haarcascade_frontalface_default.xml和轻量 DNN 模型res10_300x300_ssd_iter_140000.caffemodel。单用任一者都易翻车Haar 在侧脸/弱光下漏检严重DNN 在低分辨率640p下误检率飙升。我们采用级联策略先用 Haar 快速粗筛再对 Haar 检出区域用 DNN 精修。# 初始化两个检测器路径需替换为实际位置 haar_path haarcascade_frontalface_default.xml dnn_config deploy.prototxt dnn_model res10_300x300_ssd_iter_140000.caffemodel face_cascade cv2.CascadeClassifier(haar_path) net cv2.dnn.readNetFromCaffe(dnn_config, dnn_model) def detect_face_dnn_roi(frame, haar_boxes): 对 Haar 检出的每个 bbox用 DNN 在其扩展区域内精检 faces [] for (x, y, w, h) in haar_boxes: # 扩展 ROI 防止 DNN 因裁剪丢失上下文 pad int(max(w, h) * 0.3) x1 max(0, x - pad) y1 max(0, y - pad) x2 min(frame.shape[1], x w pad) y2 min(frame.shape[0], y h pad) roi frame[y1:y2, x1:x2] if roi.size 0: continue blob cv2.dnn.blobFromImage( cv2.resize(roi, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0) ) net.setInput(blob) detections net.forward() # 取置信度最高的 detectionDNN 输出格式固定 for i in range(detections.shape[2]): confidence detections[0, 0, i, 2] if confidence 0.5: # DNN 置信阈值设为 0.5比 Haar 更激进 box detections[0, 0, i, 3:7] * np.array([roi.shape[1], roi.shape[0], roi.shape[1], roi.shape[0]]) (rx, ry, rw, rh) box.astype(int) # 映射回原图坐标 faces.append(( x1 rx, y1 ry, min(rw, roi.shape[1]), min(rh, roi.shape[0]) )) break # 每个 Haar ROI 只取一个最高置信 DNN 结果 return faces # 主检测函数 def detect_face(frame): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Haar 粗检快速 haar_boxes face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(80, 80) ) if len(haar_boxes) 0: return [] # DNN 精检只处理 Haar 框 dnn_boxes detect_face_dnn_roi(frame, haar_boxes) # 合并去重DNN 结果优先Haar 结果作为 fallback if dnn_boxes: return dnn_boxes else: return list(haar_boxes)参数说明scaleFactor1.1Haar 缩放步长设太小如 1.05会极大增加计算量设太大如 1.3会漏检小脸1.1 是教室场景下速度与召回的平衡点。minNeighbors5拒绝孤立噪点教室环境常见飞虫、灯光反光点此值可滤掉 90% 以上误检。DNN 的confidence 0.5是血泪经验设 0.7 会漏掉戴口罩学生鼻梁以下被遮挡DNN 置信下降设 0.3 则走廊路过教师会被误抓。0.5 是实测误识/漏识比最优值。关键技巧DNN 输入必须 resize 到 300×300但blobFromImage内部已做归一化无需手动除以 255mean(104,177,123)是模型训练时的 BGR 均值必须严格匹配否则特征漂移。2.3 人脸对齐与归一化用 OpenCV 的estimateAffinePartial2D解决姿态鲁棒性OpenCV 不提供现成的人脸关键点检测如 68 点但cv2.face.createFacemarkLBF()需额外加载模型且慢。我们用更轻量的仿射变换对齐法假设人脸近似椭圆用 Haar 检出框的宽高比和中心结合灰度梯度方向估算旋转角再用estimateAffinePartial2D计算对齐矩阵。def align_face(frame, bbox): x, y, w, h bbox center (x w//2, y h//2) # 提取 ROI 并转灰度 roi frame[y:yh, x:xw] gray_roi cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) # 计算梯度方向直方图粗略估计朝向 grad_x cv2.Sobel(gray_roi, cv2.CV_64F, 1, 0, ksize3) grad_y cv2.Sobel(gray_roi, cv2.CV_64F, 0, 1, ksize3) angles np.arctan2(grad_y, grad_x) * 180 / np.pi # 取绝对值后统计主方向忽略正负号只关心左右偏转 hist, _ np.histogram(np.abs(angles), bins18, range(0, 180)) dominant_angle np.argmax(hist) * 10 # 每 bin 10 度 # 构建目标标准脸128x128双眼水平 target_pts np.float32([ [40, 50], # 左眼 [88, 50], # 右眼 [64, 90] # 鼻尖 ]) # 估算源点基于 bbox 和 dominant_angle src_pts np.float32([ [x w*0.3, y h*0.3], [x w*0.7, y h*0.3], [x w*0.5, y h*0.6] ]) # 计算仿射变换矩阵 M cv2.estimateAffinePartial2D(src_pts, target_pts)[0] if M is None: return None # 应用变换 aligned cv2.warpAffine(frame, M, (128, 128)) return aligned # 使用示例 aligned_face align_face(frame, best_bbox) if aligned_face is not None: # 后续送入编码器 pass逻辑说明不依赖关键点检测模型纯几何计算速度比 LBF 快 8 倍实测 12ms vs 100ms且对侧脸≤45°仍有效。estimateAffinePartial2D比getAffineTransform更鲁棒它允许源点有微小误差自动拟合最佳刚性变换含旋转、缩放、平移而getAffineTransform要求三点严格共面。目标尺寸设为 128×128大于 64×64细节丢失又小于 256×256编码耗时翻倍是 OpenCVFaceRecognizer的推荐输入尺寸。2.4 特征编码用 OpenCV 的 LBPH 生成可持久化的 128 维向量OpenCV 的cv2.face.LBPHFaceRecognizer_create()是唯一无需训练、开箱即用的本地特征编码器。它输出的是直方图非向量但我们通过直方图归一化 PCA 降维将其转化为 128 维浮点向量便于存库比对。import numpy as np from sklearn.decomposition import PCA # 初始化 LBPH参数经教室场景调优 recognizer cv2.face.LBPHFaceRecognizer_create( radius2, # LBP 邻域半径2 是默认值增大则更敏感但噪声多 neighbors8, # 邻居数8 是标准环形采样 grid_x16, # 水平网格数增大则局部特征更细但易受光照影响 grid_y16, # 垂直网格数同上 threshold100 # 匹配阈值单位为距离100 是经验值 ) # 为每个注册人脸生成编码向量需提前采集 5~10 张不同角度照片 def encode_face(face_img): 输入 128x128 对齐人脸输出 128 维归一化特征向量 # 转灰度LBPH 必须灰度 gray cv2.cvtColor(face_img, cv2.COLOR_BGR2GRAY) # LBPH 计算直方图 hist recognizer.computeHistogram(gray) # 归一化直方图L1 归一化保证向量模长为 1 hist_norm hist.astype(np.float32) / (np.sum(hist) 1e-8) # PCA 降维到 128 维需预先 fit 一个 PCA 模型 # 注意PCA 必须用大量样本训练不能单张图 fit # 此处 pca_model 为离线训练好的对象见 3.2 节 if hasattr(encode_face, pca_model): vec encode_face.pca_model.transform(hist_norm.reshape(1, -1))[0] return vec.astype(np.float32) else: # fallback直接截取前 128 维不推荐精度损失约 12% return hist_norm[:128].astype(np.float32) # 示例注册新员工 def register_person(name, face_images): face_images 是 list of 128x128 BGR 图像 vectors [] for img in face_images: vec encode_face(img) vectors.append(vec) # 存入数据库SQLite 示例 conn sqlite3.connect(attendance.db) c conn.cursor() c.execute(INSERT INTO persons (name) VALUES (?), (name,)) person_id c.lastrowid for vec in vectors: # 将 128 维 float32 向量存为 blob c.execute(INSERT INTO face_vectors (person_id, vector) VALUES (?, ?), (person_id, vec.tobytes())) conn.commit() conn.close()参数说明grid_xgrid_y16教室摄像头 720p 下人脸 ROI 约 200×200 像素16×16 网格每块约 12×12 像素既能捕捉纹理又不过度敏感。设为 8×8 会丢失皱纹等细节设为 32×32 则光照变化导致直方图剧烈抖动。threshold100LBPH 返回的是“距离”越小越相似。100 是实测在教室光照下误识率1% 的临界值设为 50 会拒识戴眼镜学生设为 150 则双胞胎易混淆。PCA 必须离线训练用 500 张不同人、不同光照下的对齐人脸提取 LBPH 直方图fit 一个 128 维 PCA。代码中encode_face.pca_model需在程序启动时加载该模型不可每次 encode 时重新 fit。3. 考勤逻辑闭环从识别到打卡记录的事务性设计考勤不是“识别成功就记一次”而是要解决防代打卡、防重复打卡、时段控制、异常标记四大硬需求。OpenCV 只管识别业务逻辑必须用严谨的事务控制。3.1 防代打卡活体检测 连续帧一致性验证纯静态人脸图可被照片/视频欺骗。我们不用复杂的眨眼检测易受光线干扰而是用帧间运动熵 红外补光反射比双因子判断def is_live_face(frame, bbox): x, y, w, h bbox roi frame[y:yh, x:xw] # 1. 运动熵连续 3 帧 ROI 的灰度方差变化率 if not hasattr(is_live_face, prev_rois): is_live_face.prev_rois [] is_live_face.prev_rois.append(cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)) if len(is_live_face.prev_rois) 3: is_live_face.prev_rois.pop(0) if len(is_live_face.prev_rois) 3: return False # 计算每帧灰度方差 vars [np.var(img) for img in is_live_face.prev_rois] # 方差变化率 5% 视为静止可能是照片 if max(vars) / (min(vars) 1e-8) 1.05: return False # 2. 红外反射比需硬件支持若摄像头带红外补光人脸皮肤反射率 ≈ 0.3纸张 ≈ 0.8 # 此处模拟用 ROI 的绿色通道均值近红外波段与蓝色通道比值 b, g, r cv2.split(roi) ir_ratio np.mean(g) / (np.mean(b) 1e-8) # 实测真人 ir_ratio ∈ [0.25, 0.45]打印照片 ∈ [0.7, 0.95] if not (0.25 ir_ratio 0.45): return False return True # 使用 if is_live_face(frame, best_bbox): # 执行编码和比对 pass else: # 标记为“疑似照片”不计入考勤 log_warning(Live check failed for bbox, best_bbox)逻辑说明运动熵用灰度方差而非光流教室环境常有背景移动窗帘飘动、投影仪闪烁光流易被干扰灰度方差对人脸微表情眨眼、嘴动敏感且计算快。红外反射比是硬件级防伪若无红外摄像头可替换为频域分析对 ROI 做 FFT照片有强周期性网格纹DCT 系数集中在低频真人皮肤纹理无规律。但需额外 FFT 计算此处省略。3.2 防重复打卡基于时间窗口与人脸 ID 的幂等写入同一人在 5 分钟内多次出现只记第一次。用 SQLite 的INSERT OR IGNORE 唯一索引实现原子性-- 创建考勤表 CREATE TABLE attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, person_id INTEGER NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, device_id TEXT NOT NULL, UNIQUE(person_id, date(timestamp), device_id) );def record_attendance(person_id, device_idmain_gate): conn sqlite3.connect(attendance.db) c conn.cursor() try: # 唯一约束确保同一人当天同一设备只记一次 c.execute( INSERT OR IGNORE INTO attendance (person_id, device_id) VALUES (?, ?) , (person_id, device_id)) conn.commit() if c.rowcount 0: return DUPLICATE # 已打卡 else: return SUCCESS except Exception as e: conn.rollback() return fERROR: {str(e)} finally: conn.close()关键点UNIQUE(person_id, date(timestamp), device_id)是核心date(timestamp)自动截断为年月日确保“同一天”约束。INSERT OR IGNORE比SELECTINSERT更安全避免并发时两次 SELECT 都没查到记录结果插入两条重复数据。3.3 时段控制用 cron 式调度器管理考勤窗口考勤不是全天开放而是限定在上课前 15 分钟到上课后 5 分钟。我们不用系统 cron跨平台不一致而是用schedule库在 Python 内部调度import schedule import time import threading class AttendanceWindow: def __init__(self): self.is_open False self.window_start None self.window_end None def open_window(self, start_time07:45, end_time08:05): 设置每日考勤窗口支持多节课 schedule.every().day.at(start_time).do(self._open) schedule.every().day.at(end_time).do(self._close) def _open(self): self.is_open True self.window_start time.time() print(fAttendance window opened at {time.strftime(%H:%M:%S)}) def _close(self): self.is_open False self.window_end time.time() print(fAttendance window closed at {time.strftime(%H:%M:%S)}) def can_attend(self): return self.is_open # 启动调度器 window AttendanceWindow() window.open_window(07:45, 08:05) # 早课 window.open_window(13:45, 14:05) # 午课 # 在主循环中检查 if window.can_attend(): # 执行识别逻辑 pass else: # 显示“考勤未开放” cv2.putText(frame, ATTENDANCE CLOSED, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2)注意schedule库需在后台线程运行主线程不能阻塞def run_scheduler(): while True: schedule.run_pending() time.sleep(1) threading.Thread(targetrun_scheduler, daemonTrue).start()3.4 异常标记用置信度分布自动识别“可疑打卡”单纯用阈值过滤会误伤。我们统计每人历史识别置信度LBPH distance建立动态基线def get_confidence_baseline(person_id): 查询该人最近 10 次成功打卡的 distance 均值与 std conn sqlite3.connect(attendance.db) c conn.cursor() c.execute( SELECT distance FROM attendance_log WHERE person_id ? AND status SUCCESS ORDER BY timestamp DESC LIMIT 10 , (person_id,)) distances [row[0] for row in c.fetchall()] conn.close() if len(distances) 5: return 80.0, 15.0 # 新人默认基线 mean np.mean(distances) std np.std(distances) return mean, std def is_suspicious(distance, person_id): mean, std get_confidence_baseline(person_id) # 距离 mean 2*std 视为可疑光照差、戴口罩、角度偏 return distance mean 2 * std # 记录时附带 distance c.execute( INSERT INTO attendance_log (person_id, distance, status) VALUES (?, ?, ?) , (person_id, distance, SUSPICIOUS if is_suspicious(distance, person_id) else SUCCESS))价值教务老师可导出SUSPICIOUS记录针对性复查如确认是否戴口罩上课系统自动给该人推送提醒“您今日打卡图像质量偏低建议摘镜/正对镜头”。4. 避坑OpenCV 人脸识别考勤系统上线前必须踩过的 5 个坑这些不是理论问题而是我在三所学校部署时现场花 2~8 小时才定位到的真实翻车点。每一条都对应一个git commit和一份运维 checklist。4.1 现象USB 摄像头在 Windows 上首次运行正常重启后cv2.VideoCapture(0)返回None原因Windows 10/11 的“隐私设置”默认禁用摄像头应用权限且 OpenCV 不触发系统弹窗请求授权。解决手动开启设置 → 隐私 → 摄像头 → 允许桌面应用访问摄像头代码加固添加权限检测仅 Windowsimport platform if platform.system() Windows: import winreg try: key winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, rSOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam) # 若能打开说明注册表存在权限已开 except FileNotFoundError: # 权限未开弹出提示 import tkinter as tk root tk.Tk() root.withdraw() tk.messagebox.showerror(摄像头权限错误, 请前往【设置→隐私→摄像头】开启权限然后重启程序) exit(1)4.2 现象Linux 系统Ubuntu 22.04下cv2.dnn.readNetFromCaffe报错cv2.error: OpenCV(4.8.0) ... what(): OpenCV(4.8.0) ... error: (-215:Assertion failed) ...原因OpenCV 的 DNN 模块在 Linux 上默认不启用 CUDA但readNetFromCaffe会尝试加载 GPU 后端找不到驱动时崩溃。解决强制使用 CPU 后端# 在加载模型前设置 cv2.dnn.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) cv2.dnn.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # 关键 net cv2.dnn.readNetFromCaffe(dnn_config, dnn_model)4.3 现象考勤记录导出 Excel 时中文乱码显示为????原因SQLite 默认用 UTF-8 存储但pandas.read_sql读取时未指定编码Windows 控制台默认 GBK。解决导出时显式声明编码import pandas as pd df pd.read_sql(SELECT * FROM attendance, conn) # 保存为 Excel 时用 openpyxl 引擎并指定 encoding df.to_excel(attendance.xlsx, indexFalse, engineopenpyxl) # 若需 CSV用 utf-8-sig 避免 Excel 打开乱码 df.to_csv(attendance.csv, indexFalse, encodingutf-8-sig)4.4 现象多人同时站在摄像头前系统只识别出一人且总是同一个人原因Haar 检测的minNeighbors过高如设为 10导致密集人脸时相邻检测框被合并只保留一个。解决动态调整minNeighbors当检测到多个 bbox 时临时降为 3或改用 DNN 为主检测器见 2.2 节DNN 天然支持多目标输出。4.5 现象考勤系统运行 2 小时后内存暴涨至 2GB最终 OOM 崩溃原因OpenCV 的cv2.VideoCapture在某些 USB 摄像头上存在内存泄漏尤其在频繁release()/open()时。解决永不 close/reopen 摄像头整个程序生命周期只初始化一次CameraBuffer添加内存监控每分钟打印import psutil process psutil.Process() print(fMemory usage: {process.memory_info().rss / 1024 / 1024:.1f} MB)若仍增长强制 GCimport gc gc.collect() # 在主循环末尾调用5. 进阶技巧用 OpenCV 的VideoWriter实现“考勤过程录像”与事后审计考勤系统不仅要记结果更要留证据。我们不用额外 FFmpeg直接用 OpenCV 的cv2.VideoWriter录制带标注的视频流存储为.avi兼容性最好并按考勤事件切片。5.1 录制带实时标注的视频流class AttendanceRecorder: def __init__(self, output_dirrecordings): self.output_dir output_dir os.makedirs(output_dir, exist_okTrue) self.current_writer None self.current_filename None self.frame_count 0 def start_recording(self): timestamp time.strftime(%Y%m%d_%H%M%S) self.current_filename os.path.join(self.output_dir, fattendance_{timestamp}.avi) # 使用 MJPG 编码Windows/Linux/macOS 通用 fourcc cv2.VideoWriter_fourcc(*MJPG) self.current_writer cv2.VideoWriter( self.current_filename, fourcc, 15.0, (1280, 720) ) self.frame_count 0 def write_frame(self, frame, detection_infoNone): if self.current_writer is None: return # 在帧上绘制检测框和姓名 if detection_info: x, y, w, h detection_info[bbox] cv2.rectangle(frame, (x, y), (xw, yh), (0,255,0), 2) cv2.putText(frame, detection_info[name], (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2) cv2.putText(frame, fConf: {detection_info[distance]:.1f}, (x, yh20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) self.current_writer.write(frame) self.frame_count 1 def stop_recording(self): if self.current_writer: self.current_writer.release() self.current_writer None # 如果录制少于 10 秒删除避免无效片段 if self.frame_count 150: # 15fps * 10s os.remove(self.current_filename) self.current_filename None # 在主循环中使用 recorder AttendanceRecorder() # 每次成功识别后开始/继续录制 if recognition_result[status] SUCCESS: if not recorder.current_writer: recorder.start_recording() recorder.write_frame(frame, recognition_result) else: # 无识别时也写入保持时间连续 if recorder.current_writer: recorder.write_frame(frame)参数说明fourcc*MJPG比XVID兼容性更好macOS 也能直接播放15.0 fps与摄像头采集帧率一致避免音画不同步本系统无音频但保持帧率一致利于后期分析自动删除10 秒片段避免学生路过时触发误检产生的垃圾文件。5.2 按考勤事件切片从长录像中提取“打卡瞬间”片段考勤录像长达数小时但教务只关心“谁在何时打卡”。我们用 SQLite 中的timestamp字段从长视频中精准裁剪 5 秒片段import subprocess def extract_event_clip(video_path, timestamp, output_path, duration5): 用 ffmpeg 从 video_path 中截取 timestamp 前后各 2.5 秒的片段 # 计算起始时间精确到秒 dt datetime.datetime.fromisoformat(timestamp) start_time (dt - datetime.timedelta(seconds2.5)).strftime(%H p a hrefhttps://download.csdn.net/download/wjs2024/90463016 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

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

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

免费获取报价 →
↑