基于微信小程序的面部动作检测系统
引言
本技术文档旨在详细阐述一个基于微信小程序的面部动作检测系统的技术路线、实现方法及关键技术框架。系统的核心功能包括检测用户的左右转头、眨眼和张嘴动作,并根据检测结果逐步引导用户完成任务。为确保系统的安全性和准确性,特别是防止用户通过图片或视频欺骗系统,本文进一步深入分析并提出了相应的优化和防护措施。
系统架构概述
系统采用前后端分离的架构,前端为微信小程序,负责用户交互和界面展示;后端为基于Python的API服务,负责图像处理、动作识别和任务状态管理。系统通过HTTPS协议进行数据交互,前后端通信采用JSON格式。
系统架构图
前端实现细节
技术框架与组件
- 微信小程序框架
- 使用WXML、WXSS、JavaScript或TypeScript进行开发。
- UI 组件库
- 使用WeUI或第三方UI库快速搭建界面。
- 数据交互
- 利用
wx.request
接口与后端API进行通信。
- 利用
- 防欺骗措施
- 实时性要求高,结合前端技术实现活体检测提示。
核心功能模块
- 任务显示模块
- 动态显示当前任务提示(如“请向左转头”)。
- 实时反馈模块
- 实时显示检测结果(成功/失败)。
- 进度条与状态提示
- 使用进度条展示任务完成进度。
- 重新开始选项
- 提供“重新开始”按钮,允许用户重新进行任务检测。
- 活体检测提示
- 在采集图像时提示用户进行自然动作(如眨眼、张嘴)以确保活体性。
数据采集与传输
数据采集
- 静态图片采集
- 使用
wx.chooseImage
捕捉用户当前图像。
- 使用
- 视频帧采集
- 使用摄像头实时捕捉视频流,并定时截取帧进行检测。
数据传输流程
- 捕捉图像/视频帧
- 用户点击“开始检测”后,前端启动摄像头并捕捉图像或视频帧。
- 编码图像数据
- 使用Base64对图像数据进行编码。
- 构建JSON请求
- 包含
user_id
和image_data
字段。
- 包含
- 发送HTTP POST请求
- 通过
wx.request
将JSON数据发送至后端API。
- 通过
示例代码:捕捉静态图片并发送至后端
// pages/capture/capture.js
Page({ captureImage: function() {wx.chooseImage({count: 1,sourceType: ['camera'],success: function(res) {const tempFilePath = res.tempFilePaths[0];wx.getFileSystemManager().readFile({filePath: tempFilePath,encoding: 'base64',success: function(data) {const base64Data = data.data;wx.request({url: 'https://192.8.56.100/api/task/detect',method: 'POST',header: {'Content-Type': 'application/json'},data: {user_id: 'unique_user_id',image_data: base64Data},success: function(response) {// 处理后端返回的检测结果console.log(response.data);// 更新界面提示},fail: function(error) {console.error('请求失败', error);// 提示用户网络错误}});},fail: function(error) {console.error('读取文件失败', error);// 提示用户读取文件失败}});},fail: function(error) {console.error('选择图片失败', error);// 提示用户选择图片失败}});}
});
示例代码:实时视频帧采集并发送至后端
// pages/capture/capture.js
Page({data: {cameraContext: null,intervalId: null},onLoad: function() {this.setData({cameraContext: wx.createCameraContext()});},startCapture: function() {const intervalId = setInterval(() => {this.data.cameraContext.takePhoto({quality: 'low',success: (res) => {const base64Path = res.tempImagePath;wx.getFileSystemManager().readFile({filePath: base64Path,encoding: 'base64',success: (data) => {wx.request({url: 'https://192.8.56.100/api/task/detect',method: 'POST',header: {'Content-Type': 'application/json'},data: {user_id: 'unique_user_id',image_data: data.data},success: (response) => {// 处理后端返回的检测结果console.log(response.data);// 更新界面提示},fail: (error) => {console.error('请求失败', error);// 提示用户网络错误}});},fail: (error) => {console.error('读取文件失败', error);// 提示用户读取文件失败}});},fail: (error) => {console.error('拍照失败', error);// 提示用户拍照失败}});}, 1000); // 每秒截取一帧this.setData({ intervalId });},stopCapture: function() {clearInterval(this.data.intervalId);}
});
用户界面设计
- 任务提示
- 显示当前任务描述,如“请向左转头”、“请眨眼”、“请张嘴”。
- 实时反馈
- 使用颜色变化或图标显示检测结果(成功/失败)。
- 进度条
- 展示任务完成的进度,例如三步任务进度。
- 重新开始按钮
- 提供用户在检测失败时重新开始任务的选项。
- 活体检测提示
- 在活体检测过程中,提示用户进行自然动作(如“请自然眨眼”),防止用户使用照片或视频欺骗系统。
后端实现细节
技术选型
- 编程语言:Python
- Web框架:FastAPI(高性能,支持异步处理)
- 图像处理库:OpenCV
- 人脸检测与关键点提取:MediaPipe
- 状态管理:Redis(高效管理用户任务状态)
- 容器化:Docker(可选,用于部署)
- 活体检测模型:基于动作识别的简单活体检测,结合动作提示确保用户实时互动
核心功能模块
- API 接口设计
POST /api/task/detect
:接收用户图像数据,进行动作检测,返回检测结果。GET /api/task/status
:获取当前任务状态。POST /api/task/reset
:重置任务状态。
- 图像预处理
- 解码Base64图像数据,转换为OpenCV图像数组。
- 人脸检测与关键点提取
- 使用MediaPipe Face Mesh提取面部关键点。
- 动作识别
- 分别检测左右转头、眨眼、张嘴。
- 增加活体检测逻辑,确保用户进行实时互动。
- 状态管理
- 使用Redis记录每个用户的当前任务进度和状态。
- 防欺骗措施
- 结合活体检测,确保用户进行实时的动作交互,防止使用图片或视频欺骗系统。
数据处理流程
- 接收图像数据
- 接收前端通过
POST /api/task/detect
发送的Base64编码图像数据和user_id
。
- 接收前端通过
- 解码与预处理
- 将Base64编码的图像数据解码为OpenCV图像数组。
- 人脸检测与关键点提取
- 使用MediaPipe提取面部关键点,获取468个关键点。
- 动作识别与活体检测
- 根据当前任务步骤,识别相应的动作。
- 增加活体检测逻辑,通过多次动作交互确保用户为真人。
- 结果封装与返回
- 根据动作识别结果和任务进度,构建JSON响应返回前端。
- 状态更新
- 更新用户的任务进度和状态,存储至Redis。
示例代码
# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import base64
import cv2
import numpy as np
import mediapipe as mp
import math
import redis
import json
import time
import randomapp = FastAPI()# 初始化 MediaPipe Face Mesh
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(static_image_mode=False,max_num_faces=1,refine_landmarks=True,min_detection_confidence=0.5,min_tracking_confidence=0.5
)# 初始化 Redis 客户端
redis_client = redis.Redis(host='localhost', port=6379, db=0)# 定义请求数据模型
class DetectRequest(BaseModel):user_id: strimage_data: str# 定义响应数据模型
class DetectResponse(BaseModel):success: boolmessage: strnext_task: str = Nonecurrent_step: int = None@app.post("/api/task/detect", response_model=DetectResponse)
async def detect_task(request: DetectRequest):user_id = request.user_idimage_base64 = request.image_dataif not user_id or not image_base64:raise HTTPException(status_code=400, detail="缺少 user_id 或 image_data")# 解码 Base64 图像数据try:image = decode_image(image_base64)except Exception as e:raise HTTPException(status_code=400, detail="图像解码失败")# 获取人脸关键点landmarks = get_face_landmarks(image)if not landmarks:return DetectResponse(success=False, message="未检测到人脸")# 获取或初始化用户状态state = get_user_state(user_id)# 识别动作action_results = recognize_actions(landmarks, state)# 评估当前步骤success, next_task, updated_step = evaluate_current_step(state, action_results)# 更新状态if success:if updated_step > 4:# 所有任务完成,重置状态reset_user_state(user_id)return DetectResponse(success=True, message="成功完成所有任务", next_task="完成", current_step=updated_step)else:update_user_state(user_id, 'current_step', updated_step)return DetectResponse(success=True, message="检测成功,进入下一步", next_task=next_task, current_step=updated_step)else:reset_user_state(user_id)return DetectResponse(success=False, message="检测失败,请重新开始", current_step=1)def decode_image(image_base64: str) -> np.ndarray:img_data = base64.b64decode(image_base64)np_arr = np.frombuffer(img_data, np.uint8)img = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)if img is None:raise ValueError("无法解码图像")return imgdef get_face_landmarks(image: np.ndarray):rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)results = face_mesh.process(rgb_image)if results.multi_face_landmarks:return results.multi_face_landmarks[0]else:return Nonedef get_user_state(user_id: str) -> dict:state_json = redis_client.get(f"user:{user_id}:state")if state_json:return json.loads(state_json)else:# 初始化状态initial_state = {'current_step': 1,'blink_counter': 0,'total_blinks': 0,'mouth_opened': False,'head_direction_history': [],'blink_history': [],'mouth_history': [],'last_action_time': time.time()}redis_client.set(f"user:{user_id}:state", json.dumps(initial_state))return initial_statedef update_user_state(user_id: str, key: str, value):state = get_user_state(user_id)state[key] = valueredis_client.set(f"user:{user_id}:state", json.dumps(state))def reset_user_state(user_id: str):initial_state = {'current_step': 1,'blink_counter': 0,'total_blinks': 0,'mouth_opened': False,'head_direction_history': [],'blink_history': [],'mouth_history': [],'last_action_time': time.time()}redis_client.set(f"user:{user_id}:state", json.dumps(initial_state))def recognize_actions(landmarks, state: dict) -> dict:action_results = {}# 检测左右转头head_direction = detect_head_turn(landmarks)action_results['head_turn'] = head_direction# 检测眨眼state['blink_counter'], state['total_blinks'] = detect_blink(landmarks, state['blink_counter'], state['total_blinks'])action_results['blink_count'] = state['total_blinks']# 检测张嘴state['mouth_opened'] = detect_mouth_open(landmarks, state['mouth_opened'])action_results['mouth_opened'] = state['mouth_opened']# 记录历史数据用于活体检测state['head_direction_history'].append(head_direction)state['blink_history'].append(state['total_blinks'])state['mouth_history'].append(state['mouth_opened'])# 限制历史记录长度state['head_direction_history'] = state['head_direction_history'][-10:]state['blink_history'] = state['blink_history'][-10:]state['mouth_history'] = state['mouth_history'][-10:]return action_resultsdef evaluate_current_step(state: dict, action_results: dict):current_step = state['current_step']success = Falsenext_task = ''updated_step = current_stepcurrent_time = time.time()if current_step == 1:if action_results['head_turn'] in ['left', 'right']:if current_time - state.get('last_action_time', current_time) <= 10:success = Truenext_task = '请眨眼'updated_step += 1elif current_step == 2:if action_results['blink_count'] >= 1:if current_time - state.get('last_action_time', current_time) <= 10:success = Truenext_task = '请张嘴'updated_step += 1elif current_step == 3:if action_results['mouth_opened']:if current_time - state.get('last_action_time', current_time) <= 10:success = Truenext_task = '完成所有任务'updated_step += 1elif current_step == 4:# 可根据需求增加更多步骤success = Truenext_task = '所有任务已完成'updated_step += 1else:# 所有任务完成success = Truenext_task = '所有任务已完成'if success:state['last_action_time'] = current_time# 活体检测逻辑if not perform_liveness_detection(state):success = Falsenext_task = '未通过活体检测,请重新开始'updated_step = 1return success, next_task, updated_stepdef perform_liveness_detection(state: dict) -> bool:"""活体检测逻辑,通过检测用户是否进行了多次不同动作。"""head_turns = state.get('head_direction_history', [])blinks = state.get('blink_history', [])mouth_ops = state.get('mouth_history', [])# 检测是否有多次头部转动head_turn_count = len([dir for dir in head_turns if dir in ['left', 'right']])# 检测是否有眨眼blink_total = sum(blinks)# 检测是否有张嘴mouth_opened_count = len([m for m in mouth_ops if m])# 简单阈值判断,具体阈值需根据实际测试调整if head_turn_count >= 2 and blink_total >= 2 and mouth_opened_count >= 1:return Trueelse:return False# 动作检测相关函数
def calculate_angle(p1, p2):delta_y = p2.y - p1.ydelta_x = p2.x - p1.xangle = math.degrees(math.atan2(delta_y, delta_x))return angledef detect_head_turn(landmarks):nose_tip = landmarks.landmark[1]left_eye_outer = landmarks.landmark[33]right_eye_outer = landmarks.landmark[263]left_angle = calculate_angle(nose_tip, left_eye_outer)right_angle = calculate_angle(nose_tip, right_eye_outer)avg_angle = (left_angle + right_angle) / 2TURN_LEFT_THRESHOLD = -15 # 向左转头TURN_RIGHT_THRESHOLD = 15 # 向右转头if avg_angle < TURN_LEFT_THRESHOLD:return 'left'elif avg_angle > TURN_RIGHT_THRESHOLD:return 'right'else:return 'straight'def eye_aspect_ratio(landmarks, eye_indices):p1 = landmarks.landmark[eye_indices[1]]p2 = landmarks.landmark[eye_indices[5]]p3 = landmarks.landmark[eye_indices[2]]p4 = landmarks.landmark[eye_indices[4]]p5 = landmarks.landmark[eye_indices[0]]p6 = landmarks.landmark[eye_indices[3]]vertical_1 = math.sqrt((p2.x - p4.x)**2 + (p2.y - p4.y)**2)vertical_2 = math.sqrt((p3.x - p5.x)**2 + (p3.y - p5.y)**2)horizontal = math.sqrt((p1.x - p6.x)**2 + (p1.y - p6.y)**2)ear = (vertical_1 + vertical_2) / (2.0 * horizontal)return eardef detect_blink(landmarks, blink_counter, total_blinks):LEFT_EYE = [362, 385, 387, 263, 373, 380]RIGHT_EYE = [33, 160, 158, 133, 153, 144]EAR_THRESHOLD = 0.21 # 根据实际测试调整CONSEC_FRAMES = 3 # 眨眼最少持续的帧数left_ear = eye_aspect_ratio(landmarks, LEFT_EYE)right_ear = eye_aspect_ratio(landmarks, RIGHT_EYE)ear = (left_ear + right_ear) / 2.0if ear < EAR_THRESHOLD:blink_counter += 1else:if blink_counter >= CONSEC_FRAMES:total_blinks += 1blink_counter = 0return blink_counter, total_blinksdef mouth_aspect_ratio(landmarks):upper_lip = landmarks.landmark[13]lower_lip = landmarks.landmark[14]left_mouth = landmarks.landmark[78]right_mouth = landmarks.landmark[308]vertical = math.sqrt((upper_lip.x - lower_lip.x)**2 + (upper_lip.y - lower_lip.y)**2)horizontal = math.sqrt((left_mouth.x - right_mouth.x)**2 + (left_mouth.y - right_mouth.y)**2)mar = vertical / horizontalreturn mardef detect_mouth_open(landmarks, mouth_opened):MAR_THRESHOLD = 0.6 # 根据实际测试调整mar = mouth_aspect_ratio(landmarks)if mar > MAR_THRESHOLD:mouth_opened = Trueelse:mouth_opened = Falsereturn mouth_opened# 增强的活体检测方法
def get_next_task(current_step: int) -> str:tasks = {1: ['请向左转头', '请向右转头'],2: ['请眨眼', '请微笑'],3: ['请张嘴', '请眨眼'],4: ['请向左转头', '请向右转头']}return random.choice(tasks.get(current_step, ['完成所有任务']))
防止欺骗与误导的措施
为了防止用户通过图片、视频或其他手段欺骗系统,确保检测过程中的用户为真人,系统在设计中引入了以下防护措施:
-
活体检测
- 逻辑:通过多次不同动作的检测,确保用户进行实时互动。
- 实现:
- 多动作验证:要求用户在不同任务步骤中完成多种不同类型的面部动作,如转头、眨眼、张嘴等。
- 动作多样性:系统随机提示用户进行不同的动作,增加用户实时交互的随机性,防止用户预先录制视频。
- 动作时间验证:记录用户完成每个动作的时间,确保动作在合理的时间内完成,防止使用延迟播放的预录制视频。
- 面部动态特征分析:利用MediaPipe提取的高精度关键点,分析面部的微动态特征,如眼睛的快速眨动、嘴部的微小动作等,进一步验证活体性。
-
图像质量与实时性要求
- 图像质量控制
- 分辨率限制:前端采集的图像分辨率需满足一定要求,确保后端能够准确提取面部关键点。
- 图像清晰度:要求用户在良好光线下进行检测,避免因光线不足或过强导致的图像模糊。
- 动态模糊处理:对采集的图像进行动态模糊检测,避免用户在运动中欺骗系统。
- 实时性要求
- 动作完成时间:系统要求用户在合理时间内完成指定动作,确保动作的实时性。
- 数据传输效率:优化前端与后端的数据传输,减少网络延迟,提升实时性。
- 图像质量控制
-
安全性措施
- 数据传输安全
- HTTPS协议:所有前后端通信均通过HTTPS协议,确保数据传输的安全性和完整性。
- 数据加密:对敏感数据进行加密存储,防止数据泄露。
- 输入验证与防护
- 数据验证:后端严格验证接收到的数据格式和内容,防止注入攻击和其他安全威胁。
- 图像大小限制:限制上传图像的大小和格式,防止恶意攻击。
- 用户隐私保护
- 数据最小化:仅收集必要的用户数据,避免过度收集。
- 隐私政策:明确告知用户数据的使用和存储规则,遵守相关隐私法规。
- 数据删除选项:提供用户数据删除选项,增强用户信任。
- 数据传输安全
-
用户体验优化
- 实时反馈与提示
- 检测结果展示:实时展示检测结果,提升用户参与感。
- 加载动画:在数据处理过程中,提供加载动画或进度指示,减少用户等待时的焦虑。
- 错误提示:提供明确的错误提示和解决方案,如“未检测到人脸,请调整位置并重试”。
- 界面设计优化
- 友好的UI组件:使用清晰、简洁的UI组件,提升整体用户体验。
- 响应式设计:确保界面在不同设备和屏幕尺寸下的良好展示效果。
- 实时反馈与提示
前后端数据交互详细细节
API 接口定义
1. 获取任务状态
GET /api/task/status
请求参数:
user_id
(可选):标识用户的唯一ID。
响应示例:
{"current_step": 1,"total_steps": 4,"task_description": "请向左转头"
}
2. 检测任务
POST /api/task/detect
请求参数:
user_id
:用户唯一ID。image_data
:Base64编码的图像数据。
请求数据格式:
{"user_id": "unique_user_id","image_data": "Base64编码的图像数据"
}
响应数据格式:
-
成功响应:
{"success": true,"message": "检测成功,进入下一步","next_task": "请眨眼","current_step": 2 }
-
失败响应:
{"success": false,"message": "检测失败,请重新开始","current_step": 1 }
3. 重置任务
POST /api/task/reset
请求参数:
user_id
:用户唯一ID。
请求数据格式:
{"user_id": "unique_user_id"
}
响应数据格式:
{"success": true,"message": "任务已重置","current_step": 1
}
数据格式与标准
1. 前后端通信数据格式
-
请求数据:
{"user_id": "unique_user_id","image_data": "Base64编码的图像数据" }
-
响应数据(成功):
{"success": true,"message": "检测成功,进入下一步","next_task": "请眨眼","current_step": 2 }
-
响应数据(失败):
{"success": false,"message": "检测失败,请重新开始","current_step": 1 }
2. 图像数据格式
- 编码方式:Base64编码。
- 图像格式:JPEG或PNG,根据前端配置。
- 传输方式:嵌入在JSON请求的
image_data
字段中。
3. 关键点数据格式
- MediaPipe关键点:每个关键点包含
x
,y
,z
坐标,归一化至[0,1]范围。 - 数据结构:列表形式,每个元素为关键点对象。
示例:
{"landmark": [{"x": 0.5, "y": 0.5, "z": 0.0},{"x": 0.6, "y": 0.5, "z": 0.0},...]
}
数据流转过程示意图
数据处理阶段详解
1. 数据采集阶段
- 输入:摄像头捕捉到的图像或视频帧。
- 处理:
- 捕捉图像。
- 读取文件并编码为Base64。
- 输出:Base64编码的图像数据。
技术框架:微信小程序API (wx.chooseImage
, wx.getFileSystemManager
)
2. 数据传输阶段
- 输入:Base64编码的图像数据。
- 处理:
- 构建JSON请求体。
- 通过HTTPS POST请求发送至后端。
- 输出:通过网络传输的JSON数据。
技术框架:微信小程序API (wx.request
)
3. 数据接收与解码阶段
- 输入:后端接收到的JSON请求。
- 处理:
- 解析JSON获取
user_id
和image_data
。 - 解码Base64图像数据为图像数组(NumPy)。
- 解析JSON获取
- 输出:解码后的图像数组。
技术框架:FastAPI, OpenCV
4. 人脸检测与关键点提取阶段
- 输入:图像数组。
- 处理:
- 使用MediaPipe Face Mesh提取人脸关键点。
- 输出:人脸关键点数据(MediaPipe Landmark对象)。
技术框架:MediaPipe, OpenCV
5. 动作识别与活体检测阶段
- 输入:人脸关键点数据。
- 处理:
- 计算头部转动角度,判断左右转头。
- 计算EAR,检测眨眼次数。
- 计算MAR,检测是否张嘴。
- 记录动作历史,用于活体检测。
- 评估活体性,决定是否通过检测。
- 输出:动作识别结果及活体检测结果。
技术框架:Python数学计算
6. 任务状态管理阶段
- 输入:当前动作识别结果。
- 处理:
- 根据当前任务步骤和检测结果更新用户状态。
- 使用Redis存储更新后的状态。
- 输出:更新后的任务状态。
技术框架:Redis
7. 结果封装与返回阶段
- 输入:动作识别结果及更新后的任务状态。
- 处理:
- 根据检测结果和任务进度构建响应消息。
- 封装为JSON格式。
- 输出:JSON响应数据。
技术框架:FastAPI, Pydantic
8. 前端接收与反馈阶段
- 输入:后端返回的JSON响应。
- 处理:
- 解析JSON数据。
- 根据
success
字段更新界面提示和进度条。
- 输出:更新后的用户界面,提示用户进行下一步任务或重新开始。
技术框架:微信小程序API (wx.request
回调函数)
关键技术选型
前端
- 微信小程序:用于跨平台用户界面开发,支持广泛的微信用户基础。
- WeUI:提供微信风格的UI组件,提升开发效率和用户体验。
- 关键API:
wx.chooseImage
:捕捉图像。wx.getFileSystemManager
:读取文件内容。wx.request
:发送HTTP请求。
后端
- FastAPI:高性能、易用的Python Web框架,支持异步处理。
- Uvicorn:ASGI服务器,用于运行FastAPI应用,提升性能。
- MediaPipe Face Mesh:高效的人脸检测与关键点提取,提供468个面部关键点。
- OpenCV:用于图像预处理,如颜色空间转换、图像解码等。
- Redis:高效的内存数据存储,适用于管理用户任务状态,支持高并发访问。
- Pydantic:用于数据验证和模型定义,确保数据的完整性和准确性。
安全与防护
- HTTPS:确保所有前后端通信的安全性,防止数据被窃取或篡改。
- Redis:高效管理用户状态,支持快速读写操作,适合高并发场景。
- 数据验证:后端使用Pydantic进行严格的数据验证,防止恶意数据输入。
部署与运维
- Docker:容器化部署后端服务,提升部署效率和环境一致性,简化运维管理。
- Nginx:作为反向代理服务器,处理前端请求转发,提供负载均衡和安全防护。
- 云服务:如AWS、阿里云,用于部署后端服务和Redis,确保系统的高可用性和可扩展性。
关键技术框架与算法详细说明
1. FastAPI
特点:
- 高性能,基于ASGI,支持异步处理。
- 自动生成OpenAPI文档,便于API的调试和测试。
- 内置数据验证与类型注解支持(Pydantic),确保数据的准确性。
优势:
- 支持异步编程,提升并发处理能力。
- 简洁的代码结构,易于维护和扩展。
- 兼容性强,易于与其他Python库集成。
2. MediaPipe Face Mesh
特点:
- 实时高精度人脸关键点检测,提供468个面部关键点。
- 支持多种平台和设备,适用于移动端应用。
- 高效,适用于实时应用场景。
优势:
- 精度高,覆盖面部各个区域,适合复杂的动作检测。
- 易于集成,与OpenCV配合使用,提升图像处理效率。
- 开源且持续更新,社区支持良好。
3. Redis
特点:
- 高性能的内存数据存储,支持快速读写操作。
- 支持多种数据结构(字符串、哈希、列表、集合等)。
- 提供持久化选项,确保数据的可靠性。
优势:
- 低延迟,适用于高并发场景,确保系统响应速度。
- 支持分布式部署,易于扩展,提升系统的可用性和可扩展性。
- 丰富的功能,如发布/订阅、事务处理等,满足多样化需求。
4. 动作识别算法
4.1 头部旋转检测
- 原理:通过计算鼻尖与左右眼外角的向量角度,判断头部旋转方向。
- 关键步骤:
- 提取鼻尖、左眼外角和右眼外角的关键点坐标。
- 计算鼻尖到左右眼外角的向量角度。
- 根据角度差异,判断头部是否向左或向右转动。
实现代码:
def calculate_angle(p1, p2):delta_y = p2.y - p1.ydelta_x = p2.x - p1.xangle = math.degrees(math.atan2(delta_y, delta_x))return angledef detect_head_turn(landmarks):nose_tip = landmarks.landmark[1]left_eye_outer = landmarks.landmark[33]right_eye_outer = landmarks.landmark[263]left_angle = calculate_angle(nose_tip, left_eye_outer)right_angle = calculate_angle(nose_tip, right_eye_outer)avg_angle = (left_angle + right_angle) / 2TURN_LEFT_THRESHOLD = -15 # 向左转头TURN_RIGHT_THRESHOLD = 15 # 向右转头if avg_angle < TURN_LEFT_THRESHOLD:return 'left'elif avg_angle > TURN_RIGHT_THRESHOLD:return 'right'else:return 'straight'
4.2 眨眼检测
- 原理:通过计算眼睛的纵横比(EAR, Eye Aspect Ratio),检测眨眼次数。EAR是眼睛纵向距离与横向距离的比值,眨眼时EAR会显著减小。
- 关键步骤:
- 提取左眼和右眼的关键点。
- 计算EAR值。
- 当EAR低于阈值时,视为闭眼,计数一次眨眼。
实现代码:
def eye_aspect_ratio(landmarks, eye_indices):p1 = landmarks.landmark[eye_indices[1]]p2 = landmarks.landmark[eye_indices[5]]p3 = landmarks.landmark[eye_indices[2]]p4 = landmarks.landmark[eye_indices[4]]p5 = landmarks.landmark[eye_indices[0]]p6 = landmarks.landmark[eye_indices[3]]vertical_1 = math.sqrt((p2.x - p4.x)**2 + (p2.y - p4.y)**2)vertical_2 = math.sqrt((p3.x - p5.x)**2 + (p3.y - p5.y)**2)horizontal = math.sqrt((p1.x - p6.x)**2 + (p1.y - p6.y)**2)ear = (vertical_1 + vertical_2) / (2.0 * horizontal)return eardef detect_blink(landmarks, blink_counter, total_blinks):LEFT_EYE = [362, 385, 387, 263, 373, 380]RIGHT_EYE = [33, 160, 158, 133, 153, 144]EAR_THRESHOLD = 0.21 # 根据实际测试调整CONSEC_FRAMES = 3 # 眨眼最少持续的帧数left_ear = eye_aspect_ratio(landmarks, LEFT_EYE)right_ear = eye_aspect_ratio(landmarks, RIGHT_EYE)ear = (left_ear + right_ear) / 2.0if ear < EAR_THRESHOLD:blink_counter += 1else:if blink_counter >= CONSEC_FRAMES:total_blinks += 1blink_counter = 0return blink_counter, total_blinks
4.3 张嘴检测
- 原理:通过计算嘴部的纵横比(MAR, Mouth Aspect Ratio),检测是否张嘴。MAR是嘴部纵向距离与横向距离的比值,张嘴时MAR会显著增大。
- 关键步骤:
- 提取上唇中点、下唇中点、左右嘴角的关键点。
- 计算MAR值。
- 当MAR超过阈值时,视为张嘴。
实现代码:
def mouth_aspect_ratio(landmarks):upper_lip = landmarks.landmark[13]lower_lip = landmarks.landmark[14]left_mouth = landmarks.landmark[78]right_mouth = landmarks.landmark[308]vertical = math.sqrt((upper_lip.x - lower_lip.x)**2 + (upper_lip.y - lower_lip.y)**2)horizontal = math.sqrt((left_mouth.x - right_mouth.x)**2 + (left_mouth.y - right_mouth.y)**2)mar = vertical / horizontalreturn mardef detect_mouth_open(landmarks, mouth_opened):MAR_THRESHOLD = 0.6 # 根据实际测试调整mar = mouth_aspect_ratio(landmarks)if mar > MAR_THRESHOLD:mouth_opened = Trueelse:mouth_opened = Falsereturn mouth_opened
优化与注意事项
1. 性能优化
1.1 后端优化
- 异步处理:利用FastAPI的异步特性,提升并发处理能力。
- GPU加速:在服务器端使用GPU加速MediaPipe和OpenCV的处理速度,提升实时性。
- 批处理请求:在高并发场景下,考虑批量处理图像请求,减少处理开销。
1.2 前端优化
- 图像压缩:在前端对图像进行适当压缩,减少传输数据量,提升传输效率。
- 采集频率控制:合理控制视频帧采集频率,平衡实时性与性能。
2. 安全性
2.1 数据传输安全
- 强制使用HTTPS:确保所有数据传输通过HTTPS,防止中间人攻击。
- 身份验证:引入用户身份验证机制,如Token验证,确保请求的合法性。
2.2 输入验证
- 严格数据验证:后端使用Pydantic进行严格的数据验证,确保数据格式和内容的正确性。
- 图像大小与格式限制:限制上传图像的大小和格式,防止恶意文件上传。
3. 用户体验
3.1 实时反馈
- 检测结果展示:前端实时展示检测结果,提升用户参与感和体验。
- 加载动画:在后端处理图像时,前端显示加载动画,减少用户等待时的焦虑。
3.2 错误处理
- 明确错误提示:在检测失败或网络异常时,提供明确的错误提示和解决方案。
- 重试机制:在网络请求失败时,提供重试选项,提升系统的鲁棒性。
4. 扩展性
4.1 模块化设计
- 代码结构清晰:前后端代码结构清晰,易于维护和扩展。
- 插件化组件:前端使用插件化的UI组件,后端使用模块化的函数库,便于添加新功能。
4.2 可配置性
- 参数化阈值:将动作检测的阈值(如EAR_THRESHOLD、MAR_THRESHOLD)配置化,方便后期调整优化。
- 任务步骤配置:将任务步骤和动作提示配置化,便于添加或修改检测任务。
4.3 服务扩展
- 微服务架构:考虑将不同功能模块(如人脸检测、动作识别、状态管理)拆分为独立的微服务,提升系统的可维护性和扩展性。
- 负载均衡:使用Nginx等负载均衡工具,提升系统的并发处理能力和稳定性。
相关文章:
基于微信小程序的面部动作检测系统
引言 本技术文档旨在详细阐述一个基于微信小程序的面部动作检测系统的技术路线、实现方法及关键技术框架。系统的核心功能包括检测用户的左右转头、眨眼和张嘴动作,并根据检测结果逐步引导用户完成任务。为确保系统的安全性和准确性,特别是防止用户通过…...
Java网络套接字
在Java的开发中,有一个很重要!很重要!很重要!的东西,叫做网络套接字,它被广泛的用来二次开发服务,比如大数据中台的服务链路调用等。 它的实现原理是依靠三次握手来完成通信的建立,…...
mapbox基础,测面功能实现
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️Turf 框架二、🍀测面功能实现1. ☘️实现思路2. ☘️代码样例一、🍀…...
如何通过设置失效时间清除本地存储的数据
一、使用localStorage和时间戳(JavaScript) 1. 原理 localStorage是浏览器提供的一种在本地存储数据的方式,数据没有过期时间限制。但是可以通过自己记录时间戳来模拟数据过期的功能。在存储数据时,同时存储一个时间戳ÿ…...
【QT】找不到qwt_plot.h
系统环境: linux 20.04 qt 6.7.2 cmake 3.22 原因: Qwt没有正式的FindQwt.cmake,Qwt也没有提供QwtConfig.cmake。而且cmake不支持qmake的配置特性,也不支持读取mkspecs (.prf)文件。也就是说cmake构建的qt项目不可用qwt。 解决步…...
程序员如何培养技术领导力?
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
C# 设计模式(创建型模式):原型模式
C# 设计模式(创建型模式):原型模式 引言 在面向对象的设计中,创建型模式关注于对象创建的方式和复杂度。原型模式(Prototype Pattern)是其中一种创建型设计模式,它允许通过复制现有的实例来创…...
Python自学 - 函数初步(内置函数、模块函数、自定义函数)
1 Python自学 - 函数初步(内置函数、模块函数、自定义函数) 1.1 内置函数 几乎所有的编程都会提供一些内置函数,以便完成一些最基本的任务,Python提供了丰富的内置函数,熟悉内置函数可以给工作带来极大便利。 Python官方的内置函数介绍网…...
Mono里运行C#脚本21—mono_image_init_name_cache
前面分析了怎么样加载mscorlib.dll文件,然后把文件数据读取到内存。 接着下来,就会遇到加载整个C#的类型系统,比如System. Object,大体类型如下图所示: 在对CIL编译之前,需要把这些类型全部加载到内存里,以便快捷地访问它们。 mono_image_init_name_cache函数就是完成…...
MySQL中distinct和group by去重的区别
MySQL中distinct和group by去重的区别 在MySQL中,我们经常需要对查询结果进行去重,而DISTINCT和GROUP BY是实现这一功能的两种常见方法。虽然它们在很多情况下可以互换使用,但它们之间还是存在一些差异的。接下来,我们将通过创建测…...
快速上手大模型的对话生成
本项目使用0.5B小模型,结构和大模型别无二致,以方便在如CPU设备上快速学习和上手大模型的对话上传 #mermaid-svg-Z86hUiQZ0hg9BVji {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Z86hUiQZ0h…...
SpringCloud(一)--SpringCloud简介
一. 引言 在微服务架构日益盛行的今天,Spring Cloud凭借其简单易用、功能强大的特性,成为了众多开发者的首选。本文仅为学习所用,联系侵删。 二. SpringCloud概述 2.1 定义 Spring Cloud是一系列框架的有序集合,它巧妙地…...
常见的 Redis 面试题
1. Redis 是什么?它解决了哪些问题? Redis 是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息中间件。它主要用于解耦应用程序的不同组件或服务,支持高吞吐量和低延迟的消息传递。解决了系统之间的同步调用导致的性能…...
面试准备备备备
职业技能 放到简历的黄金位置(HR刷选简历的重要参考) 基本准则:写在简历上的必须能聊,不然就别写 参考公式:职业技能 必要技术 其他技术 针对性的引导面试官(让他问一些你想让他问的) 寻找合…...
GeoIP + Nginx:实现网站的地域访问控制
1. 引言 在全球化的互联网环境中,地域访问控制已成为许多企业和个人网站管理的重要需求。通过限制特定地区的访问,网站管理员可以保护资源、提高安全性并优化用户体验。本文将介绍如何使用GeoIP和Nginx实现地域访问控制,并提供两种情况的详细…...
打造三甲医院人工智能矩阵新引擎(一):文本大模型篇--基于GPT-4o的探索
一、引言 当今时代,人工智能技术正以前所未有的速度蓬勃发展,深刻且广泛地渗透至各个领域,医疗行业更是这场变革的前沿阵地。在人口老龄化加剧、慢性疾病患病率上升以及人们对健康需求日益增长的大背景下,三甲医院作为医疗体系的核…...
算法题之将列表的数据复制到另一个列表中
方法一:for循环遍历list1中的每个元素append方法将元素逐个添加到list2列表中 list1 [‘q’,‘efe’,‘reb’,‘yhh’,2]list2 []for i in list1:list2.append(i) print(list2) 方法二:python列表自带copy方法,调用copy方法可以得到原列表的…...
Docker图形化界面工具Portainer最佳实践
前言 安装Portainer 实践-基于Portainer安装redis-sentinel部署 Spring Boot集成Redis Sentinel 前言 本篇文章笔者推荐一个笔者最常用的docker图形化管理工具——Portainer。 安装Portainer 编写docker-compose文件 Portainer部署的步骤比较简单,我们还是以…...
在Linux上获取MS(如Media Server)中的RTP流并录制为双轨PCM格式的WAV文件
在Linux上获取MS(如Media Server)中的RTP流并录制为双轨PCM格式的WAV文件 一、RTP流与WAV文件格式二、实现步骤三、伪代码示例四、C语言示例代码五、关键点说明六、总结在Linux操作系统上,从媒体服务器(如Media Server,简称MS)获取RTP(Real-time Transport Protocol)流…...
栈及栈的操作
栈及栈的操作 栈结构 栈是一种只能在一端进行插入或删除操作的数据结构。栈有两个基本的操作:入栈和出栈。 入栈:将一个新的元素放到栈顶。 出栈:从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的操作规则:LIFO(Last…...
YOLOv10-1.1部分代码阅读笔记-utils.py
utils.py ultralytics\nn\modules\utils.py 目录 utils.py 1.所需的库和模块 2.def _get_clones(module, n): 3.def bias_init_with_prob(prior_prob0.01): 4.def linear_init(module): 5.def inverse_sigmoid(x, eps1e-5): 6.def multi_scale_deformable_attn_py…...
JDK8源码分析Jdk动态代理底层原理
本文侧重分析JDK8中jdk动态代理的源码,若是想看JDK17源码分析可以看我的这一篇文章 JDK17源码分析Jdk动态代理底层原理-CSDN博客 两者之间有着略微的差别,JDK17在JDK8上改进了不少 目录 源码分析 过程 生成的代理类大致结构 本文侧重分析JDK8中jdk…...
springboot配置并使用RestTemplate
目录 一、RestTemplate配置 1、将RestTemplate初始化为Bean 2、使用HttpClient作为RestTemplate客户端 (1)引入HttpClient依赖 (2)修改RestTemplate配置类 3、设置拦截器 (1)新增拦截器类 …...
【MySQL关于数据库和表结构的增删查改】
数据库和表结构的基本语法 数据库命令关于字符集语法 表操作语法创建表查看表结构修改表修改表名增加字段同时修改字段名和字段数据类型仅修改字段数据类型删除字段 删除表 备份和恢复备份恢复 mysql -h 127.0.0.1 -P 3306 -u root -p mysql -u root -h 和 -p 默认 进入MySQL程…...
Linux 服务器启用 DNS 加密
DNS 加密的常用协议包括 DNS over HTTPS (DoH)、DNS over TLS (DoT) 和 DNSCrypt。以下是实现这些加密的步骤和工具建议: 1. 使用 DoH (DNS over HTTPS) 工具推荐: cloudflared(Cloudflare 提供的客户端)doh-client(…...
2025年,测试技能支棱起来。
你是否曾为提升自己的测试技能而烦恼?在这个日新月异的技术时代,2025年已经悄然而至,软件测试行业的需求和挑战也在不断变化。那么,如何在这个竞争激烈的环境中脱颖而出,成为一名更加优秀的测试工程师呢? …...
HTML5实现好看的二十四节气网页源码
HTML5实现好看的新年春节元旦网站源码 前言一、设计来源1.1 主界面1.2 关于我们界面1.3 春季节气界面1.4 夏季节气界面1.5 秋季节气界面1.6 冬季节气界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现好看的二十四节气网页源码,春季节气…...
每天你好20250103(距离春节26天!!!)
亲爱的朋友们,大家早上好! 🌞 今天是 1 月 3 日,星期五,2025 年的第三天,同时也是第 1 周的第五天,农历乙巳[蛇]年十一月十四日。祥龙辞岁去,瑞蛇迎春来,在这曙光初照的…...
【音频伴奏分离】UVR5软件介绍
Ultimate Vocal Remover 5 (UVR5) 是一款功能强大的AI人声伴奏音频分离软件,它能够利用深度学习模型从音频文件中分离出人声和伴奏。UVR5在音乐制作、音频编辑、学习等多种场景中都非常有用。以下是UVR5的一些主要特点和使用方法: 主要特点:…...
安卓触摸事件的传递
setOnTouchListener()返回值的副作用(触摸事件是否继续往下或往后传递)如下: 返回值效果是否往下层view传递是否往当前view的后续监听传递true该pointer离开屏幕前的后续所有触摸事件都会传递给该TouchListener否否false该pointer离开屏幕前…...
51c视觉~合集40
我自己的原文哦~ https://blog.51cto.com/whaosoft/12951385 #SparX 港大提出SparX:强化Vision Mamba和Transformer的稀疏跳跃连接机制 本文分享香港大学计算和数据科学学院俞益洲教授及其研究团队发表于 AAAI 2025 的论文——SparX,一种强化 Visio…...
25年1月更新。一图流Windows 上搭建 Python 开发环境:Python + PyCharm 安装全攻略(文中有安装包不用官网下载)
引言 随着 Python 在数据科学、Web 开发、自动化脚本等多个领域的广泛应用,越来越多的开发者选择它作为首选编程语言。而 PyCharm 作为一个功能强大的集成开发环境(IDE),为 Python 开发者提供了极大的便利。本文将详细介绍如何在 …...
C# 设计模式的六大原则(SOLID)
C# 设计模式的六大原则(SOLID) 引言 在面向对象编程中,设计模式提供了高效、可复用和可维护的代码结构。SOLID原则是软件设计中的一组重要原则,用于确保代码具有良好的可维护性、可扩展性和灵活性。SOLID是五个设计原则的首字母…...
MQ-导读
什么是MQ? MQ是一款消息中间件,通常被称为"消息队列",用于分布式架构中上下文的异步通信。 举个例子 就拿登录来说,用户在登录的时候需要去进行一些其他的操作,比如风控、短信、记录日志,如果采用同步的方式…...
Spring MVC的@ResponseBody与@RequestBody
ResponseBody注解用于将Controller的方法返回的对象,通过springmvc提供的HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端。 RequestBody注解用于读取http请求的内容(字符串),通过springmv…...
常见硬件及其对应的驱动模块列表
常见硬件及其对应的驱动模块列表 1. 电机类 硬件设备驱动模块功能步进电机ULN2003、A4988、DRV8825提供电流和电压,控制步进和方向直流电机L298N、L293D、TB6612FNG提供双向电流控制,实现正反转和调速伺服电机无(直接 PWM 控制)控…...
IDEA开发Java应用的初始化设置
一、插件安装 如下图所示: 1、Alibaba Java Coding Guidelines 2.1.1 阿里开发者规范,可以帮忙本地自动扫描出不符合开发者规范的代码,甚至是代码漏洞提示。 右击项目,选择《编码规约扫描》,可以进行本地代码规范扫…...
系统架构师考试-ABSD基于架构的设计方法
概念 ABSD是体系结构驱动,是指构成体系结构的软件构件和类的组合驱动的。 ABSD强调由商业、质量和功能需求的组合驱动软件架构设计。使用ABSD方法,设计活动可以从项目总体功能框架明确就开始,并且设计活动的开始并不意味着需求抽取和分析活…...
Node.js入门html,css,js 30年了nodejs环境 09年出现 15
Node.js入门 html,css,js 30年了 nodejs环境 09年出现 15年 nodejs为我们解决了2个方面的问题: 【锦上添花】让我们前端工程师拥有了后端开发能力(开接口,访问数据库) - 大公司BFF(50)【✔️】前端工程…...
2025跨年倒计时
<!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>2025年跨年倒计时</title><style>/* 页…...
淘宝京东电商商品SKU信息抓取API测试实战指南
在电商平台的商品管理中,SKU(Stock Keeping Unit,库存单位)信息扮演着至关重要的角色。它不仅关乎商品的库存管理,还直接影响到消费者的购买决策。为了高效地获取并验证电商平台(如淘宝、京东)上…...
SAP财务凭证的更改、冲销的方式
文章目录 一、财务凭证更改二、财务凭证冲销 【SAP系统研究】 #SAP #FICO #SAP财务 一、财务凭证更改 (1)已经过账的财务凭证 FB02:过完帐的允许更改的地方有限,只有凭证抬头文本、参照、分配、文本、原因代码等。 ࿰…...
RepPoints: Point Set Representation for Object Detection
CornerNet论文阅读整理-CSDN博客 可变形卷积(Deformable Conv)原理解析与torch代码实现-CSDN博客 RepPoints(本质是可变形卷积DCN)再理解-CSDN博客 简介: 时间:2019 会议:ICCV 作者:Ze Yang,Shaohui Liu,…...
高效使用AI完成编程项目任务的指南:从需求分析到功能实现
随着人工智能工具的普及,即便是零编程基础或基础薄弱的用户,也可以借助AI完成许多技术任务。然而,要高效地使用AI完成编程任务,关键在于如何清晰表达需求,并逐步引导AI实现目标。 在本文中,我们将通过开发…...
Linux命令——RPM与yum
文章目录 一、RPM包命令1.安装和升级包2.删除包3.查询包信息4.验证和检查5.其他操作 二、yum安装器1.安装包2.更新包3.卸载包4.查询和搜索5.清理缓存6.其他操作 一、RPM包命令 1.安装和升级包 命令解释rpm -ivh package.rpm安装一个 RPM 包,显示详细信息和进度条r…...
C++软件设计模式之责任链模式
责任链模式的动机与意图 动机: 在软件开发中,经常会遇到需要处理一系列请求或事件的情况。这些请求可能需要经过多个处理对象,每个对象根据其职责决定是否处理请求或将其传递给下一个对象。责任链模式(Chain of Responsibility P…...
Spring Cloud Security集成JWT 快速入门Demo
一、介绍 JWT (JSON Web Token) 是一种带有绑实和信息的简单标准化机制,在信息通信中用于验证和信息传递。尤其在应用中使用Spring Cloud实现分布式构建时,JWT可以作为一种无状态验证原理的证明。 本文将进一步描述如何在Spring Cloud Security中集成JW…...
Python 爬虫
一、创建项目 1.双击打开pycharm,点击新建项目 2.项目设置- 勾选[继承全局站点软件包]- 勾选[可用于所有项目]- 取消勾选[创建main.py欢迎脚本]- 点击创建 3.项目名称右键--新建--python文件 4.输入文件名--回车二、编辑代码 # 导入请求模块 import requests # 如…...
【PCIe 总线及设备入门学习专栏 4.1 -- PCI 总线的地址空间分配】
文章目录 Overview 本文转自:https://blog.chinaaet.com/justlxy/p/5100053219 Overview PCI 总线具有32位数据/地址复用总线,所以其存储地址空间为 2324GB。也就是PCI上的所有设备共同映射到这4GB上,每个PCI设备占用唯一的一段PCI地址&…...
虚拟电厂搭建指南:绿虫仿真设计软件的助力
在虚拟电厂的搭建中,绿虫仿真设计软件起着重要作用。 绿虫光伏仿真软件是一款综合性辅助工具,能为虚拟电厂中的光伏项目提供精准数据支持。它所提供的项目选址地气象数据,涵盖海拔、辐照、风速、温度等,数据源为 Meteonorm &…...