基于MediaPipe与rPPG的实时生理信号与情绪分析系统实现
简介这是一套面向计算机视觉与情感计算初学者的智能测谎实验项目源码适用于高校课程设计、AI兴趣实践及轻量级行为分析研究。项目基于MediaPipe实现高精度面部关键点检测并融合心率估测与微表情线索识别逻辑支持实时摄像头输入下的多模态生理与行为特征联动分析。压缩包共13个文件含3个核心Python脚本main.py、launcher.py、log.py、环境配置文件environment.yml、requirements.txt、UI资源ico.ico、meter.png、demo.png及说明文档README.md、config.ini、LICENSE总大小1.55MB结构简洁、模块职责清晰便于快速理解整体流程与调试入口。目前已有129人学习下载提供完整图形界面启动方案、命令行灵活调参方式、日志实时监控工具及详细使用说明覆盖从环境搭建、功能启用到视频录制与线索可视化全流程是入门情感识别与生物信号视觉估计的实用参考实现。1. 项目概述当摄像头成为“测谎仪”最近在整理过往项目时翻出了一个挺有意思的旧作——一个基于摄像头输入的智能测谎分析工具。这玩意儿不是什么严肃的刑侦设备更像是一个结合了计算机视觉和生理信号分析的趣味性技术Demo。它的核心思路很简单人在说谎或经历强烈情绪波动时面部微表情、瞳孔、皮肤颜色等会不自觉地产生细微变化。这个项目就是尝试用普通的电脑摄像头捕捉这些变化通过算法进行分析给出一个“紧张度”或“情绪波动”的参考指标。整个项目完全基于Python构建核心是Google的MediaPipe框架辅以OpenCV进行图像处理并整合了基于PPG光电容积脉搏波描记法原理的心率估计算法。最终产出的是一套完整的源代码以及详细的使用说明。它实现的功能包括实时面部468个关键点的检测与跟踪、基于面部ROI感兴趣区域的心率估计、以及结合关键点位移和心率变异性的简易情感/压力状态分析。说白了这就是一个技术探索项目目的是验证“能否用消费级硬件实现一些基础的生理信号监测和情绪分析”。它不适合、也绝不能用于任何需要法律效力的严肃测谎场景但在人机交互研究、压力监测、或是作为理解计算机视觉与信号处理的一个综合性练手项目上有着不小的价值。如果你是对CV、信号处理或Python自动化感兴趣的朋友或者想找一个有深度的毕业设计课题这个项目的代码和思路或许能给你不少启发。2. 核心思路与技术选型解析2.1 为什么是MediaPipe而不是OpenCV DNN或Dlib在面部关键点检测这个任务上可选的方案很多。早期我考虑过Dlib的68点模型经典且稳定但点数较少对于细微的面部肌肉运动捕捉不够精细。也试过直接用OpenCV加载一些轻量级的DNN模型比如Facemark但在易用性、速度和精度平衡上总不尽如人意。最终选择MediaPipe Face Mesh是基于以下几个硬核考量精度与点数的完美平衡MediaPipe提供了468个3D面部关键点远超Dlib的68点。这不仅包含了眉毛、眼睛、嘴巴的轮廓甚至覆盖了脸颊、下颌线乃至整个面部网格。对于分析微表情如嘴角的轻微抽动、眉毛的瞬间上扬和计算面部特定区域如脸颊的颜色变化以估计心率更多的点意味着更丰富的信号源和更高的分析潜力。惊人的实时性能MediaPipe的管道Pipeline优化极其出色即使在CPU上也能达到实时30 FPS处理。这对于需要连续分析视频流、不能有显著延迟的交互式应用至关重要。Dlib在CPU上处理高分辨率图像时帧率是个问题。开箱即用的3D感知MediaPipe直接输出带有深度信息Z坐标的3D关键点。这允许我们估算头部的姿态偏航、俯仰、翻滚这对于后续分析非常重要。例如当用户大幅转头时单纯2D点的位移会带来巨大噪声有了3D姿态就可以进行补偿或直接忽略该时间段的数据。强大的生态与易用性MediaPipe Python包安装简单API清晰。几行代码就能初始化检测器并获取详细的结果大大降低了开发门槛让我们能把精力集中在核心的信号处理和分析逻辑上。注意MediaPipe Face Mesh有两个模型face_detection快速人脸检测和face_landmarks详细关键点。在本项目中我们直接使用face_landmarks因为它一步到位。但在极端性能要求下可以先检测再详细标记这是一种常见的优化策略。2.2 “心率监测”是如何通过摄像头实现的这是项目中最具巧思也最需要谨慎对待的部分。我们用的是远程光电容积脉搏波描记法rPPG。其生理原理是心脏搏动推动血液流动导致皮下组织中的血容量发生周期性变化。血液对绿光吸收强对红光吸收弱。当血容量增加时吸收的绿光增多反射的绿光减少。我们的摄像头传感器虽然主要是RGB但其绿色通道对血液容积变化引起的反射光强度微小波动最为敏感。具体实现步骤如下选取ROI区域不是整个脸而是选择血流丰富、皮肤表面相对平坦、受表情变化影响小的区域。通常选择前额或脸颊。我们利用MediaPipe获取的面部关键点可以精准地框定前额区域例如使用眉毛上方和发际线下方的一组点围成的区域。提取绿色通道信号在每一帧中计算选定ROI区域内所有像素的绿色通道G通道的平均强度值。这样我们就得到了一个随时间变化的原始光强信号序列G(t)。信号预处理G(t)信号非常微弱且混杂了大量噪声包括光照变化环境光晃动。运动伪影头部移动、表情变化。相机自动调节自动曝光、自动白平衡。为了抑制这些噪声我们进行一系列处理滑动平均去趋势移除信号中的缓慢变化趋势如光照渐变。带通滤波心脏跳动频率通常在0.8 Hz到3 Hz之间对应48到180 BPM。设计一个0.8-3 Hz的带通滤波器如巴特沃斯滤波器只保留这个频段的信号。标准化将信号幅度归一化。心率计算对预处理后的干净信号有两种主流方法计算心率频域法FFT对一段时间的信号例如30秒的数据做快速傅里叶变换找到功率谱中0.8-3 Hz之间的峰值对应的频率f_peak则心率HR f_peak * 60(BPM)。这种方法抗干扰能力较强但需要较长时间窗口实时性稍差。时域法峰值检测在预处理后的时域信号中直接检测波峰。计算连续波峰之间的时间间隔IBI然后求平均得到平均心率。这种方法更实时但对信号质量要求高容易受运动伪影影响。 在本项目中为了平衡实时性和稳定性我采用了滑动窗口FFT的方法。例如每秒钟用过去30帧假设1秒数据计算一次心率通过重叠窗口实现近乎实时的更新。实操心得rPPG的准确性严重依赖环境。理想条件是稳定、均匀的室内光源避免荧光灯频闪用户保持相对静止。任何大幅头部运动都会导致信号失效。因此在代码中必须加入“信号质量评估”模块当检测到过大运动或ROI丢失时应给出提示或暂停心率计算而不是输出一个明显错误的值。2.3 情感识别与“测谎”的逻辑构建这是最前沿也最玄学的部分。严格意义上的“情感识别”或“测谎”需要多模态数据语音、文本、生理信号和复杂的模型。我们这里做的是一种非常简化的、基于规则和阈值的“紧张度/情绪波动度”估计主要作为演示。我们结合了两种信息来源面部动作单元AU强度通过MediaPipe的关键点我们可以计算一些经典面部动作的强度。例如眉毛上扬计算左右眉毛中心点与上参考点如发际线点距离的变化率。眼睛睁大计算上下眼睑关键点之间的平均距离。嘴角拉动计算嘴角点到鼻翼点距离的变化。 当这些动作单元在短时间内如1秒内发生快速、小幅度的激活微表情可能预示着情绪波动。我们为每个关注的AU设定一个基线用户平静状态下的值和一个激活阈值。心率变异性HRV与心率升高单纯的静息心率升高可能源于运动、咖啡因等。但结合心率变异性HRV的降低则是压力或紧张的一个更强生理指标。HRV指连续心跳间期RR间期的变化通常通过时域SDNN、频域LF/HF分析获得。紧张状态下交感神经兴奋HRV通常会降低。 我们从rPPG信号中间接提取IBI心跳间期虽然精度不如心电图ECG但趋势仍有参考价值。“测谎”评分逻辑示例非常简化基线期让用户平静观看中性内容30秒记录面部AU基线和平均心率、HRV基线。问答期向用户提问。特征提取实时计算心率相对于基线的升高百分比。HRV如SDNN相对于基线的降低百分比。特定AU如眉毛上扬AU01嘴角收紧AU14的瞬时强度是否超过阈值。加权融合给上述特征分配权重例如心率变化权重0.4HRV变化权重0.4AU强度权重0.2计算一个0-100的“紧张度指数”。重要声明这个指数绝非谎言指示器。它只能提示“用户在该时刻出现了生理和面部行为的应激反应”。反应的原因可能是说谎也可能是问题触及了紧张、回忆、情绪激动等。因此所有输出必须明确标注为“紧张度/唤醒度指数”并附带免责说明。3. 项目架构与核心模块详解3.1 系统整体工作流程整个软件是一个单线程的实时处理管道其核心流程可以概括为以下步骤这些步骤在每一帧图像中循环执行图像捕获使用OpenCV的VideoCapture从默认摄像头或指定视频文件读取一帧BGR图像。人脸检测与关键点定位将BGR图像转换为RGB格式MediaPipe要求送入MediaPipe Face Mesh模型。获取468个3D面部关键点、人脸边界框以及3D头部姿态欧拉角。ROI提取与信号预处理心率ROI根据关键点索引提取前额区域。计算该区域在绿色通道上的平均像素值作为原始PPG信号的一个采样点存入一个固定长度的信号缓冲区。动作单元计算根据关键点坐标实时计算预设的几个面部动作单元AU的强度值。滑动窗口分析与特征提取每当信号缓冲区被填满例如对应30帧数据就触发一次分析。心率/HRV分析对缓冲区内的原始绿色通道信号进行去趋势、带通滤波。然后采用频域法FFT估算当前窗口的平均心率并通过峰值检测估算IBI序列进而计算近似的HRV指标如RMSSD。动作单元分析计算当前窗口内各AU强度的平均值、方差并与基线值比较。紧张度指数融合与输出将计算得到的心率变化、HRV变化、AU强度异常值进行标准化和加权求和生成当前的“紧张度指数”。同时在图像上可视化关键点、心率、HRV和指数。显示与交互使用OpenCV的imshow显示处理后的可视化图像。监听键盘事件如‘q’退出‘r’重置基线。3.2 核心代码模块拆解项目源代码主要包含以下几个文件main.py程序主入口负责流程控制、摄像头读写、主循环和可视化。face_analyzer.py核心分析类封装MediaPipe操作、关键点处理、ROI管理。ppg_processor.pyPPG信号处理模块负责信号缓存、滤波、心率与HRV计算。affect_estimator.py情感/紧张度评估模块负责基线管理、特征融合与指数计算。utils.py工具函数如绘制函数、数学计算辅助函数。config.yaml配置文件集中管理所有参数如摄像头索引、信号窗口大小、滤波器截止频率、各特征权重等。这里重点剖析两个最核心的模块ppg_processor.py中的信号处理链import numpy as np from scipy import signal import collections class PPGProcessor: def __init__(self, window_size30, fps30): self.window_size window_size # 分析窗口长度帧数 self.fps fps # 视频帧率 self.signal_buffer collections.deque(maxlenwindow_size*2) # 使用双倍缓冲区避免边缘效应 self.bandpass self._create_bandpass_filter() # 创建带通滤波器系数 def _create_bandpass_filter(self): # 设计一个2阶巴特沃斯带通滤波器通带0.8Hz - 3.0Hz nyquist 0.5 * self.fps low 0.8 / nyquist high 3.0 / nyquist b, a signal.butter(2, [low, high], btypeband) return (b, a) def add_sample(self, g_value): 添加一个新的绿色通道强度采样值 self.signal_buffer.append(g_value) def analyze(self): 分析缓冲区内的信号返回心率和HRV估计 if len(self.signal_buffer) self.window_size: return None, None raw_signal np.array(self.signal_buffer)[-self.window_size:] # 取最近一个窗口的数据 # 1. 去趋势移除线性趋势消除缓慢变化 detrended signal.detrend(raw_signal) # 2. 带通滤波 filtered signal.filtfilt(self.bandpass[0], self.bandpass[1], detrended) # 3. 标准化 normalized (filtered - np.mean(filtered)) / (np.std(filtered) 1e-8) # 4. 频域法求心率 fft_result np.fft.rfft(normalized) freqs np.fft.rfftfreq(len(normalized), d1.0/self.fps) magnitude np.abs(fft_result) # 将频率限制在人类心率范围内 valid_mask (freqs 0.8) (freqs 3.0) if not np.any(valid_mask): return None, None peak_freq freqs[valid_mask][np.argmax(magnitude[valid_mask])] heart_rate peak_freq * 60 # 转换为BPM # 5. 时域法粗略估计IBI用于HRV # 寻找滤波后信号的波峰 peaks, _ signal.find_peaks(normalized, distanceself.fps/3, height0.2) # 最小间隔0.3秒 if len(peaks) 2: hrv_rmssd None else: ibi_intervals np.diff(peaks) / self.fps # 波峰间隔秒 # 计算RMSSD相邻IBI差值的均方根这是一个常用的HRV时域指标 diff_ibi np.diff(ibi_intervals) hrv_rmssd np.sqrt(np.mean(diff_ibi ** 2)) * 1000 # 转换为毫秒 return heart_rate, hrv_rmssdaffect_estimator.py中的紧张度指数计算class AffectEstimator: def __init__(self, config): self.hr_baseline None self.hrv_baseline None self.au_baselines {} # 存储各个动作单元的基线值 self.config config # 包含各特征权重的配置 def update_baseline(self, current_hr, current_hrv, current_aus): 更新生理和面部基线通常在平静期调用 if self.hr_baseline is None: self.hr_baseline current_hr self.hrv_baseline current_hrv for au_name, au_value in current_aus.items(): self.au_baselines[au_name] au_value else: # 平滑更新 self.hr_baseline 0.9 * self.hr_baseline 0.1 * current_hr self.hrv_baseline 0.9 * self.hrv_baseline 0.1 * current_hrv for au_name, au_value in current_aus.items(): self.au_baselines[au_name] 0.9 * self.au_baselines.get(au_name, au_value) 0.1 * au_value def calculate_tension_index(self, current_hr, current_hrv, current_aus): 计算当前帧的紧张度指数0-100 if None in [self.hr_baseline, self.hrv_baseline] or not self.au_baselines: return 50 # 返回中性值 # 1. 心率变化特征 (0-1) hr_change max(0, (current_hr - self.hr_baseline) / self.hr_baseline) # 心率升高比例 hr_feature min(1.0, hr_change / 0.2) # 假设升高20%为最大特征值1 # 2. HRV变化特征 (0-1) if current_hrv and self.hrv_baseline: hrv_change max(0, (self.hrv_baseline - current_hrv) / self.hrv_baseline) # HRV降低比例 hrv_feature min(1.0, hrv_change / 0.4) # 假设降低40%为最大特征值1 else: hrv_feature 0 # 3. 动作单元特征 (0-1) au_feature 0 for au_name, au_value in current_aus.items(): baseline self.au_baselines.get(au_name) if baseline: # 计算AU强度超出基线的比例 au_intensity max(0, (au_value - baseline) / baseline) au_feature max(au_feature, min(1.0, au_intensity / 0.5)) # 假设强度增加50%为最大 # 4. 加权融合 tension (self.config[weight_hr] * hr_feature self.config[weight_hrv] * hrv_feature self.config[weight_au] * au_feature) # 5. 映射到0-100 tension_index int(tension * 100) return min(100, max(0, tension_index))3.3 参数配置的艺术 (config.yaml)所有可调参数都集中在配置文件中这是保证代码可维护性和可复现性的关键。camera: index: 0 # 摄像头索引0通常是默认内置摄像头 width: 640 # 采集宽度降低可提升速度 height: 480 # 采集高度 processing: fps: 30 # 假设帧率用于计算 landmark_model_complexity: 1 # MediaPipe模型复杂度 (0,1,2)越高越准越慢 ppg: window_duration: 1.0 # 心率分析窗口时长秒 roi_name: forehead # 使用的ROI区域可选 forehead, left_cheek, right_cheek bandpass_low: 0.8 # 带通滤波器低截止频率 (Hz) bandpass_high: 3.0 # 带通滤波器高截止频率 (Hz) min_peak_height: 0.2 # 峰值检测最小高度 affect: baseline_duration: 30 # 基线记录时长秒 weights: heart_rate: 0.4 # 心率变化权重 hrv: 0.4 # HRV变化权重 action_unit: 0.2 # 面部动作单元权重 au_to_track: # 要跟踪的动作单元列表 - au01_inner_brow_raiser # 眉毛上扬 - au14_dimpler # 嘴角收紧酒窝肌 - au45_blink # 眨眼频率 visualization: draw_landmarks: true draw_roi: true show_hr: true show_hrv: true show_tension_index: true通过修改这个文件你可以轻松调整整个系统的行为无需改动代码。4. 实战部署与操作指南4.1 环境搭建与依赖安装项目基于Python 3.7。强烈建议使用Conda或venv创建独立的虚拟环境。创建并激活虚拟环境conda create -n lie_detection_demo python3.8 conda activate lie_detection_demo安装核心依赖pip install opencv-python mediapipe numpy scipy pyyamlopencv-python: 用于图像捕获、处理和显示。mediapipe: 核心的面部关键点检测库。numpyscipy: 数值计算和信号处理。pyyaml: 用于读取YAML配置文件。验证MediaPipe安装可以运行一个简单的测试脚本确保MediaPipe能正确导入并初始化面部关键点检测器。4.2 运行与校准流程启动程序python main.py --config config.yaml程序启动后会打开摄像头窗口。初始时“紧张度指数”会显示为50中性心率等读数可能不稳定。基线校准至关重要程序运行后前30秒baseline_duration配置项为自动基线记录期。用户需要保持平静、放松直视摄像头避免大幅移动和说话。屏幕上应有倒计时或提示告知基线记录状态。30秒后系统会提示“基线记录完成”。此时计算出的心率、HRV和面部动作强度平均值将被设为基线。开始分析基线记录完成后系统开始实时计算并显示“紧张度指数”。你可以尝试问自己一些问题或回忆一些不同情绪的事件观察指数的变化。注意观察心率和HRV数值的变化趋势。4.3 可视化界面解读程序主窗口会显示如下信息原始视频流带有面部网格关键点和前额ROI框绿色矩形的图像。实时数据面板通常叠加在图像一侧或下方HR: 72 BPM– 实时估算的心率。HRV(RMSSD): 45 ms– 估算的心率变异性RMSSD数值越低可能表示压力越大。T-Index: 65– 紧张度指数0-100越高表示相对基线状态的反应越大。Status: Baseline / Analyzing– 当前状态。信号曲线图可选可单独窗口显示原始绿色通道信号曲线。滤波后的PPG信号曲线并标记出检测到的心跳峰值。5. 常见问题、调优与避坑指南5.1 心率读数不准或跳动剧烈这是最常见的问题几乎都是信号质量问题。症状心率值在几十到一百多之间乱跳没有稳定趋势。排查与解决检查光照这是首要因素。确保面部光线均匀、稳定。避免头顶强光造成额头反光、侧光产生阴影或闪烁的光源如某些LED灯。自然柔和的室内窗光是理想选择。减少运动叮嘱被测者尽量保持头部静止。可以尝试使用靠背椅。在代码中可以增加一个基于MediaPipe头部姿态角度的运动补偿或数据质量判断模块如果头部转动角度超过阈值如15度则暂停心率计算或给出低质量警告。调整ROI前额并不总是最佳选择。对于有刘海或额头反光的人可以尝试切换到脸颊ROI。在config.yaml中修改roi_name为left_cheek或right_cheek并在face_analyzer.py中调整对应的关键点索引。调整滤波器参数如果用户心率异常如运动员静息心率低于50需要调整config.yaml中ppg.bandpass_low到0.7甚至0.6。同样高心率上限也可调整。延长窗口时间增加ppg.window_duration例如从1.0秒增加到1.5或2.0秒可以提高FFT的频率分辨率让心率峰值更明显但会降低实时性。5.2 MediaPipe检测不到脸或抖动症状面部框时有时无或关键点抖动严重。排查与解决摄像头分辨率MediaPipe对低分辨率图像如低于240p检测效果会变差。确保config.yaml中的camera.width和camera.height设置在640x480或以上。模型复杂度将processing.landmark_model_complexity从1改为2。复杂度2的模型更精确但更耗资源。如果帧率下降明显可能需要降低分辨率。光照再次强调过暗或过曝的环境都会导致检测失败。确保人脸清晰可见。使用静态图像测试用一张清晰的正脸照片运行检测确认MediaPipe本身安装无误。5.3 紧张度指数没有反应或始终很高症状无论做什么指数都变化不大或者基线期后指数就一直维持在80以上。排查与解决基线记录失败这是最可能的原因。确保在30秒基线记录期内用户真正处于平静状态。程序应在基线期结束后打印出记录的基线值心率、HRV。检查这些值是否合理静息心率60-100HRV因人而异但不应为0。权重配置不当如果心率信号质量很差其变化hr_feature可能始终为0或噪声很大。可以尝试在config.yaml中调整affect.weights例如将heart_rate权重调低增加action_unit的权重。动作单元计算不敏感当前代码中AU的计算是基于几何距离的非常简易。某些微表情可能无法被有效捕捉。可以尝试增加更多AU的计算或引入更复杂的基于特征点的运动历史信息。个体差异生理反应因人而异。对某些人来说说谎时可能没有明显的心率飙升而是表现为HRV的特定频段变化。我们的简化模型可能无法覆盖所有情况。5.4 性能优化技巧如果感觉程序卡顿帧率过低降低处理分辨率不要用摄像头原始分辨率处理。可以在main.py中将捕获的图像先缩放到一个较小的尺寸如256x256再送给MediaPipe显示时再用原图。process_img cv2.resize(frame, (256, 256)) results face_mesh.process(cv2.cvtColor(process_img, cv2.COLOR_BGR2RGB)) # 注意获取的关键点坐标需要按比例映射回原始图像坐标进行绘制和ROI计算。减少MediaPipe的渲染选项初始化MediaPipe时关闭不需要的选项如static_image_modeFalse视频模式max_num_faces1只检测一张脸min_detection_confidence0.5min_tracking_confidence0.5。优化信号处理触发不必每帧都进行完整的FFT计算。可以每积累够一个窗口的新数据如5帧再计算一次心率和HRV。使用多进程将图像采集/显示、人脸检测、信号处理分别放到不同的进程中通过队列通信。但这会显著增加代码复杂度仅在对实时性要求极高时考虑。5.5 项目的局限性、伦理与扩展方向必须清楚的局限性非医学精度摄像头rPPG的心率测量误差通常在±5 BPM以上远不如心率带或医疗设备。HRV的测量就更不准确了。其结果绝不能用于任何健康诊断。非测谎仪如前所述“紧张度指数”反映的是生理和表情的唤醒度与说谎无必然因果关系。焦虑、兴奋、思考甚至身体不适都会导致指数升高。环境依赖性对光照、运动极度敏感在非受控环境下实用性有限。伦理考量 开发和使用此类技术必须遵守伦理。在项目说明和界面中必须明确标注“本项目为技术演示其输出结果不具备科学或法律效力不可用于任何形式的真实测谎、人员评估或决策支持。” 尊重用户隐私不应在未经同意的情况下录制或分析他人视频。有趣的扩展方向多模态融合集成麦克风加入语音情感分析如音调、语速变化或语音转文本后的语义分析构建更全面的压力检测模型。深度学习模型收集一个“平静”与“应激”状态下的面部微视频片段数据集训练一个简单的CNN-LSTM分类模型直接端到端地输出状态概率可能比规则系统更鲁棒。长期监测与个性化让用户多次使用记录其在不同真实情境下的数据通过个性化校准来提升对该用户状态判断的准确性。交互应用用于游戏或交互艺术中根据玩家的紧张程度调整游戏难度或音乐节奏创造新颖的体验。这个项目就像一把瑞士军刀它集成了多个有趣的CV和信号处理技术。它的价值不在于成为一个可用的“测谎仪”而在于提供了一个完整的、可运行的框架让你能亲手触摸到从摄像头像素到生理信号、再到高层行为推断的整个技术链条。每一个环节的调试和优化都是一次深刻的学习。本文还有配套的精品资源点击获取