简介资源以人脸情感/表情特征提取为核心面向人脸表情识别、情感计算方向的初学者和算法复现者采用几何法提取面部关键特征解决视频帧或静态图像中情感特征的有效表示问题。压缩包共3个文件一个Python脚本实现从关键点标记、距离计算到曲率估计的完整流程一个Markdown格式的README详细介绍环境配置、运行步骤与预期输出一个文本文件保存项目依赖或补充说明整体体量仅3KB结构清晰小巧实用。内容基于表情依赖于面部器官变化的机理通过标记特征点并计算关键点之间的距离与所在曲线曲率将人脸表情转化为可供分类器使用的几何特征向量符合经典表情识别研究思路。已有8488人学习下载适合入门者动手复现、理解特征提取原理也可作为算法实验中的轻量级基线。说明文档对代码逻辑与扩展方向均有提示能有效减少复现环节的弯路便于快速迁移到自身研究。1. 视频级人脸情感特征提取几何特征向量才是这份资源的重点先给一句结论这份资源不是训练情感识别模型而是把视频里每一帧人脸转成可用的几何特征向量。很多开源的情感分析项目表面光鲜实际跑起来要么依赖冲突要么检测框乱跳真正能稳定批量出特征的并不多。这套extractVideoFeatures走的是经典几何法路线依靠面部关键点的距离、角度和曲率变化来描述表情不烧显卡也不依赖大模型适合做行为分析、表情数据集预处理、情感识别前特征工程的从业者。新手照 README 能跑通熟手可以拿它的特征输出接自己的分类器。我拆完代码后最大的感受是情感特征这件事几何法虽然朴素但可控性和可解释性比黑盒特征好得多。2. 几何法特征提取的原理为什么关键点距离比深度学习特征更可控2.1 静态与动态方法的选型逻辑人脸表情特征提取大体分两类基于静态图像的方法和基于动态图像的方法。静态方法里又区分整体法和局部法整体法把整张脸当输入局部法只盯着眼睛、嘴巴、眉毛这些区域动态方法则有光流法、模型法和几何法。代码包走的是最后一条路先定位面部关键点再计算几何量。为什么选几何法而不是直接上卷积网络因为表情的产生和表达很大程度上靠面部器官的形状变化反映。嘴角是不是上翘、嘴巴张开多大、眉毛抬了多少这些差异用关键点之间的距离和曲线曲率就能描述。常见做法是标记面部器官区域的特征点然后计算特征点之间的距离以及特征点所在曲线的曲率这就是摘要里说的几何形式人脸表情提取。相比光流法几何法对光照变化不敏感相比模型法它不需要复杂的三维形变建模相比端到端的深度特征它每个维度都有物理含义特征坏了你能立刻知道是眼睛没测准还是嘴巴区域歪了。这套流程处理的是视频不是单张图所以最终输出的是一段时间序列的特征向量每一帧对应一行。后续你可以对这些向量做时序平滑、差分、甚至接一个轻量分类器都能保留可解释性。这也是我推荐用它做视频级情感特征提取的核心原因中间结果可检查每一帧的特征向量都能回溯到原始人脸。2.2 人脸关键点驱动的几何特征如何设计人脸 68 点关键点模型把面部划分为眉毛、眼睛、鼻子、嘴巴和下颌轮廓。代码里计算的特征我建议按下表组织。这几个量基本覆盖了喜怒哀惧的主要肌肉运动模式也是别人复现这类资源时最常用的特征组合特征维度计算方式对应表情含义眼睛开合度上下眼睑关键点距离 / 眼裂宽度惊讶、恐惧时增大嘴巴开合度上下唇关键点距离 / 脸宽惊讶、大笑时增大嘴角上扬角嘴角点与鼻唇角连线夹角微笑、开心时变大眉毛弯曲度眉峰点相对两端连线的偏移量悲伤、愤怒时变化明显眉眼距离眉心与上眼睑距离 / 内眦距惊讶抬高、愤怒压低下颌张开距下颌尖与下唇距离说话、打哈欠时变化提取时先归一化再输出。用关键点之间的相对比例而不是绝对距离这样同一个表情在不同人脸尺寸、不同拍摄距离下数值接近。归一化的分母一般选两眼瞳孔间距或者两眼角距离这是最稳的尺度基准。下面这一段是核心特征计算逻辑我把常见的实现思路补全成可运行片段真正上传的代码包里把视频读取和检测部分也串好了import cv2 def compute_geo_features(shape, normalizer): shape: dlib 关键点结果 normalizer: 归一化基准长度通常是两眼中点距离 features {} # 眼睛开合度 EAR上下眼睑关键点垂直距离 / 眼宽 left_eye_ear ( dist(shape[37], shape[41]) dist(shape[38], shape[40]) ) / (2.0 * normalizer) right_eye_ear ( dist(shape[43], shape[47]) dist(shape[44], shape[46]) ) / (2.0 * normalizer) # 嘴巴开合度上唇中心到下唇中心距离 / 归一化长度 mouth_open dist(shape[62], shape[66]) / normalizer # 嘴角上扬角左右嘴角分别与鼻唇沟点的夹角平均值 left_mouth_angle angle_between(shape[49], shape[48], shape[54]) right_mouth_angle angle_between(shape[55], shape[48], shape[54]) mouth_corner_angle (left_mouth_angle right_mouth_angle) / 2.0 features[ear_left] left_eye_ear features[ear_right] right_eye_ear features[mar] mouth_open features[mouth_angle] mouth_corner_angle return featuresnormalizer这个参数比较容易忽视如果你在多人脸视频里发现提取结果忽大忽小第一反应就应该是它出了问题。各算法生成的视频和实拍视频中人脸占比差异很大不做归一化系统会把远处的小脸判成「面无表情」把近处的大脸判成「情绪激动」这是几何法最典型的坑。代码包里没有给可视化模块我会在复现时补一个把特征点画回原帧的功能。否则跑起来纯看数字出了问题很难判断是检测错位还是算法算错黑匣子状态下调参纯属玄学。2.3 代码包结构入口、依赖和 README 的角色extractVideoFeatures.zip里的核心件有四个extractVideoFeatures.py是主流程requirements.txt是依赖清单README.md是运行指南外层目录名与主文件名重名属于常见的打包习惯。README 里写了运行步骤和注意事项强烈建议先把它读一遍再动代码不然后面会在环境问题上白白耗时间。requirements 文件通常包含opencv-python、dlib、numpy这几个核心库。其中 dlib 的安装依赖 CMake 和 C 编译环境Windows 上经常在这一步翻车。后面第 4 章我会单列一个排查清单。3. 完整流程环境准备到批量出特征3.1 环境安装与模型文件的前置准备拿到代码后的第一件事是建环境不要直接 pip install 到全局。用 virtualenv 或 conda 都可以我习惯用 conda因为 dlib 的二进制包在 conda 源里兼容性更好。conda create -n face_feature python3.8 conda activate face_feature pip install -r requirements.txtPython 版本建议锁定 3.8 或 3.9太高的版本搭配某些 dlib 预编译包容易出现 ABI 不兼容问题。requirements.txt里如果有版本号就按它的来没有的话opencv-python用 4.5.x 系列、dlib用 19.22 以上比较稳。主程序一般要加载一个人脸关键点模型文件常见的是 68 点模型。注意模型文件路径不要写绝对路径东西拷到别的机器上会直接跑断。我一般会在代码开头加一段MODEL_PATH os.path.join(os.path.dirname(__file__), models, shape_predictor_68_face_landmarks.dat) if not os.path.exists(MODEL_PATH): raise FileNotFoundError(f模型文件缺失: {MODEL_PATH})这个模型文件属于公开的通用人脸关键点模型不在 zip 包里需要自己放进去README 里通常会写明放到哪个目录。如果输出目录和模型目录混在一起后面跑批时会很难受建议把所有外部依赖都收进external/子目录。3.2 主程序跑批参数怎么改extractVideoFeatures.py的核心流程是读取视频文件 → 逐帧检测人脸 → 提取关键点 → 计算几何特征 → 输出结果。通常设计成命令行入口输入一个视频路径输出一个特征文件。常见参数有参数作用建议值--input输入视频路径支持 mp4 / avi--output特征输出路径可指定 csv 或 npy--max_frames最多处理帧数默认全片调试时设 100--interval抽帧间隔25 fps 视频建议 2--detect_interval多人脸检测频率默认每帧检测--side_thresh侧脸过滤阈值0.3 左右下面是一个调用示例处理 30 秒的短视频每 2 帧取一次输出成 tab 分隔的文本特征文件。先跑短片段确认流程没问题再上长视频否则卡在一半才发现参数错了时间全浪费。python extractVideoFeatures.py \ --input demo.mp4 \ --output output/demo_features.csv \ --interval 2 \ --max_frames 300 \ --side_thresh 0.3interval参数最影响计算耗时。25 帧每秒的视频如果逐帧提取一帧的人脸检测加 68 点定位大约需要几十到一百毫秒1 分钟视频就得跑数分钟。抽帧之后特征序列的时间分辨率下降但表情变化本身是缓变的2 到 3 帧取一个点不会损失太多信息。如果后面要接时序模型你可以把间隔缩短到 1并靠后处理补插值。3.3 输出格式与特征文件的使用代码输出通常有两种形式纯文本表格或者二进制数组。我建议在落地时输出为 CSV并且把frame_id写进第一列这样后面可视化时能精确对齐到帧。frame_id,ear_left,ear_right,mar,mouth_angle,nose_angle 0,0.182,0.179,0.024,21.3,1.02 2,0.184,0.181,0.041,22.7,1.04这里的frame_id是抽帧后的索引不是原始帧号。比如设置interval2原始视频第 100 帧对应的frame_id是 50。区分不开的话调试对齐时很容易错位。输出文件建议放进独立的output/目录不要和输入视频放同一层。跑批量数据时文件名用{video_name}_features.csv的格式后续合并也方便。4. 避坑指南人脸情感特征提取中常见的五个大坑4.1 dlib 安装失败与 OpenCV 版本冲突现象pip install dlib在 Windows 上直接报错提示找不到CMake或C compiler即使装成功导入dlib后发现cv2的版本把整个进程带崩。原因dlib 需要从源码编译依赖 CMake、Visual Studio Build Tools 和 Python 开发头文件。OpenCV 版本不匹配会导致底层接口冲突尤其是 4.6 之后的findContours接口变化明显。解决Windows 直接安装 dlib 预编译 wheel或者改用 conda 安装conda install -c conda-forge dlib。OpenCV 固定到 4.5.x 系列最省心不要盲目升级。4.2 侧脸帧检测错位导致特征跳变现象视频里人物转头到 60 度以上检测框还在但 68 点直接歪掉嘴部特征瞬间出现异常尖峰。原因68 点模型对正脸训练充分大幅侧脸时关键点回归结果不稳定但置信度过滤没生效。解决加一道侧脸过滤。用两眼关键点的水平距离比上两眼外眦距离比值低于阈值直接丢弃该帧不要强行计算特征。阈值我一般设为 0.3宁缺毋滥。def is_frontal(shape, thresh0.3): left_eye_w dist(shape[36], shape[39]) right_eye_w dist(shape[42], shape[45]) return left_eye_w / right_eye_w (1 - thresh) and left_eye_w / right_eye_w (1 thresh)4.3 光照过暗时检测失败但程序不报错现象视频进入暗场景后输出特征全是零或全是同一个默认值程序不报错看起来像表情没变化。原因dlib 的人脸检测器在低光照下人脸召回率下降代码里检测不到人脸时选择了静默跳过。解决给读取帧加一个亮度判断亮度低于阈值就补一帧前一帧的特征而不是填 0。否则后面做时序分析时会出现人为的「情绪低谷」。4.4 间隔输出导致特征序列时间轴错位现象抽帧间隔设大后用特征序列对齐表情发生时间点发现比真实事件慢了半秒。原因interval抽帧后frame_id没有换算回原始时间戳。例如视频 25 fpsinterval5第 4 个特征点对应原始 20 帧就是 0.8 秒不是 0.16 秒。解决输出时强制加两个字段原始帧号和原始时间戳。或者统一保存原始帧号后处理阶段再换算不要在特征提取阶段丢原始帧信息。4.5 多人同框时的特征归属混乱现象会议室视频里有两个人输出序列一会儿是 A 的特征一会儿是 B 的特征后段分类直接乱掉。原因多人脸视频里主程序每次可能检测到多个框代码默认取面积最大的框但镜头切换或人员走动时最大框可能在两个人之间跳变。解决加tracking参数对检测到的人脸做 ID 分配。没有追踪模块的话至少把面积连续性和位置连续性写进去当前后两帧人脸框中心距离超过阈值时断开序列另起一行输出。几何法本身是单人的方法多人场景必须配合人脸追踪先做身份绑定。5. 进阶用法把几何特征接到分类器上验证有效性特征提取出来之后第一件事不是直接接模型而是先验证特征的有效性。我通常会走一遍「类内紧密、类间可分」的检查同一个人同一个表情的特征欧式距离应该小同一个人不同表情的特征欧式距离应该大。from sklearn.svm import SVC from sklearn.model_selection import cross_val_score import numpy as np X np.load(features_all.npy) # (样本数, 帧数, 特征维度) y np.load(labels_all.npy) # 表情类别标签 # 每个视频取均值把时序特征压成单向量 X_mean X.mean(axis1) clf SVC(kernellinear, C1.0) scores cross_val_score(clf, X_mean, y, cv5) print(5-fold ACC:, scores.mean())X_mean的做法是每个视频的特征序列取时间维平均值。表情是连续变化的平均抹平了瞬时波动基本保留了情感倾向。如果平均后准确率就明显高于随机水平说明几何特征有效如果平均后很差再尝试用最大值或差分特征。进阶一点的做法是保留时序信息把每帧特征窗口堆积成固定长度的矩阵再喂给 1D CNN 或 LSTM。但在这个资源场景下我会先以均值 SVM 为基准因为基线便宜、可复现、不容易过拟合。踩坑提示特征维度不要直接拼到几十维几何特征之间相关性高SVM 线性核容易受影响可以先跑一下标准化再进分类器。我个人的习惯是把特征有效性验证做成一个固定动作每次从这套流程拿到新数据先画一组不同表情的特征轨迹图盯着曲线看三分钟确认不是抖动噪声才把数据交出去给下游模型。这样做既能提前堵住检测错位导致的脏数据也能让你对几何法能描述什么、描述不了什么有真实手感。希望这个习惯能帮到你。本文还有配套的精品资源点击获取