简介基于Matlab的表情识别源码项目围绕LBP与LPQ特征提取、PCA降维和SVM分类构建了从特征到分类的完整技术链路适合计算机视觉、图像处理及机器学习方向的初学者和研究者参考。资源包共235个文件包含213张Jaffe人脸表情tiff图、18个m格式源码、3个txt说明文档以及1个asv备份整体约2.66MB体积紧凑。源码模块划分清晰覆盖特征提取、核函数计算、多分类SVM训练与测试等关键步骤并附带数据集与说明文档便于按流程复现和修改。已有1197人学习下载对表情识别初学者有一定参考价值。通过阅读和运行代码可以理解这两种特征在表情纹理描述上的差异体会PCA降维对高维特征的压缩效果掌握SVM在多分类问题上的具体实现也可用于课程设计、毕业设计或科研实验。1. 项目起点为什么LBPLPQ的组合适合表情识别做表情识别这个方向很多新手一上来就想着上深度学习好像不搞个CNN就不好意思开口。但真当你拿到一个实际课题比如基于Matlab的表情识别系统设计你会发现传统手工特征经典分类器的方案依然有它的独特价值——训练快、可解释性强、对硬件要求低而且特别适合毕设、课设和论文对比实验。这个项目的技术栈很典型LBP局部二值模式 LPQ局部相位量化做特征提取PCA主成分分析降维SVM支持向量机做分类。说实话这个组合在表情识别领域是非常成熟的一套pipeline成熟意味着稳妥、可复现、易扩展。先说说为什么选LBP和LPQ而不是只用其中一个。LBP擅长捕捉纹理细节对光照变化有不错的鲁棒性它把每个像素和邻域像素比较得到一串二值编码本质上是描述局部纹理结构。但LBP有个短板——它对噪声比较敏感而且对模糊图像的表现一般。LPQ则是基于图像局部频谱的相位信息来编码它对模糊和光照变化更鲁棒正好弥补LBP在这方面的不足。两者提取的特征互为补充串行融合之后分类性能通常比单独使用任何一种都要好。PCA在这里的角色也很有意思。LBP和LPQ提取出来的特征维度不低比如一张64x64的人脸图分块之后LBP特征可能上千维LPQ也类似。高维特征直接扔给SVM一来训练慢二来容易过拟合三来特征之间存在大量冗余。PCA通过线性变换把高维数据投影到主成分空间在保留绝大部分方差的前提下大幅降维。从实际实验来看把上千维降到80~120维分类准确率不但不降反而经常有轻微提升。SVM作为分类器就更不用多说了。在小样本、高维模式识别任务里SVM的理论基础和实际表现都非常可靠尤其配合RBF核函数能有效处理非线性可分的数据。表情识别数据集通常不算大几百张到几千张图片的规模SVM在这种场景下比随机森林、KNN这些对手更有优势。这套方案的适用范围很清晰人脸表情识别、疲劳检测、人机交互中的情感判断甚至扩展到性别识别、年龄估计也都能套用。我在实际项目里的体会是这套代码写完之后换一个数据集只需要改数据读取路径和类别标签通用性很强。2. 整体流程设计从图像预处理到分类输出的完整链路先把这个项目的整体流程理清楚后面写代码的时候才不会乱。表情识别的标准pipeline是图像采集、人脸检测与对齐、预处理、特征提取、特征降维、分类器训练、性能评估。2.1 数据集的选取与处理表情识别最常用的公开数据集是JAFFE和CK。JAFFE有213张由10位日本女性拍摄的7种表情图片生气、厌恶、恐惧、开心、中性、悲伤、惊讶图片已经裁剪好并对齐非常适合做算法验证。CK则有更多样本但需要自己做人脸检测和序列帧提取处理起来稍麻烦。如果是在Matlab里做我建议先用JAFFE跑通整个流程因为它的图片是256x256的灰度图背景简单、人脸位置居中省去很多人脸检测的麻烦。CK可以留到后面做泛化实验再上。图片读取和预处理这块Matlab代码大概是这样的% 读取图片并统一尺寸 img imread(jaffe/TIANG.AN1.89.tiff); if size(img, 3) 3 img rgb2gray(img); % 转灰度 end img imresize(img, [128 128]); % 统一尺寸 img histeq(img); % 直方图均衡化削弱光照影响这里有个细节值得注意直方图均衡化不是必须的。如果LBP和LPQ本身对光照已经有不错的鲁棒性均衡化反而不一定带来增益。我自己的测试结果是有时候会提升1-2个百分点有时候基本没变化。建议你做一个消融实验对比一下加不加的差异这也是论文里一个不错的对比点。2.2 分块策略对特征表达的影响LBP和LPQ的常见做法不是对整张图直接提特征而是先把图像划分成若干小块分别提取每个块的特征直方图然后拼接起来。这样做的好处是保留了空间位置信息——比如眼睛区域和嘴巴区域的纹理特征是不一样的把它们分开统计比混在一起更有利于分类器刻画面部不同部位的表情特征。分块大小是个需要调的参数。对128x128的图像常见划分是4x4或8x8即块大小32x32或16x16。分块越细空间分辨率越高但特征维度也越高分块越粗特征越紧凑但空间细节丢失也越多。JAFFE数据集上我用4x4分块配合uniform LBP59维单块特征维度是59x16944维8x8分块加上uniform LBP就是59x643776维对SVM来说这个维度已经偏大所以PCA就显得必不可少。3. LBP和LPQ特征提取的实现细节3.1 LBP的Matlab实现与参数选择LBP的核心思想很直观对图像中每个像素取其3x3邻域或圆形邻域内的像素值与中心像素比较大于中心像素记1否则记0得到一个二进制数即该像素的LBP码。统计整张图所有像素的LBP码直方图就是这张图的LBP特征。Matlab中可以用extractLBPFeatures这个内置函数但自己做毕设的话我更推荐手写或使用经典版本这样你能完全掌控细节论文里解释起来也更有底气。圆邻域的LBP实现function lbp lbp_circle(img, radius, neighbors) % 圆形邻域LBP % radius: 半径 % neighbors: 采样点数 img double(img); [m, n] size(img); lbp zeros(m, n); for k 1:neighbors % 计算第k个采样点的坐标小数坐标用双线性插值 theta 2*pi*(k-1)/neighbors; x radius * cos(theta); y -radius * sin(theta); % 双线性插值取像素值 x1 floor(x); y1 floor(y); x2 x11; y2 y11; % ... 插值代码略 ... lbp lbp (img_interp img) * 2^(k-1); end end参数上最常用的是radius1, neighbors8即3x3近似的圆形邻域。如果半径改大比如radius2, neighbors16特征更鲁棒但维度更高、计算更慢。JAFFE这种小样本数据集上radius1已经够用我测试过radius2准确率没有明显提升训练时间却翻了几倍。考虑使用uniform LBP等价模式它把二进制码中跳变次数不超过2的模式作为uniform模式其余统一归为一类。8采样点的uniform LBP模式数量是59种而常规LBP是256种维度减到1/4分类器训练更快而且实践中uniform LBP往往对分类精度还有小幅提升。Matlab里extractLBPFeatures默认就是uniform模式用的参数是Uprightfalse时还会做旋转不变处理但表情识别里不建议旋转不变因为脸部不同朝向本身是有区分度的。3.2 LPQ的Matlab实现与关键参数LPQ的原理比LBP稍微绕一点。它基于短时傅里叶变换STFT在每个像素的局部窗口内计算频谱信息重点提取频谱的相位信息。相位不容易受光照变化和模糊影响所以LPQ在模糊人脸识别上很有优势。核心参数是局部窗口大小通常是3、5、7、9等奇数。窗口越小对纹理细节越敏感窗口越大对整体结构的刻画越好但计算量也越大。我实验下来窗口取5或者7效果最好9就开始有点过平滑了。至于频率点的选择Matlab实现里常见做法是在四个频率点(0, a)、(-a, 0)、(a, 0)、(0, -a)处计算其中a和窗口大小相关一个常用的取值是a 1/(winsize-1)。LPQ算法的基本步骤是对图像每个像素取winsize x winsize的局部窗口对窗口做2D DFT或短时傅里叶变换在选定的四个频率点采样将四个复数值的实部和虚部取出来得到8维向量对这8维向量做去相关变换白化对每个像素按8维向量中每个分量的正负生成8位二进制码统计整个图像的LPQ码直方图Matlab里C风格的Fourier矩阵可以预先计算避免对每个窗口重复计算DFT否则速度会很慢。核心函数轮廓function lpq lpq_compute(img, winsize) % 构造傅里叶基矩阵 rho 1/(winsize-1); x -1:rho:1; kernel ones(winsize, 1) * x; % 四个频率点 w 2*pi*rho; % 生成变换矩阵然后对图像逐窗口计算 % 去相关矩阵采用高斯分布假设下的Whitening变换 % 量化并生成LPQ编码 endLPQ的特征维度计算方式和LBP类似如果采用256种编码组合的直方图单块是256维。但实际实现中可以对码值做压缩常见做法是保留到64维或128维。JAFFE上我用winsize7、4x4分块LPQ特征维度是256x164096维比LBP高出不少PCA降维的必要性在这里体现得特别明显。3.3 特征融合的两种方式LBP和LPQ特征融合有两种思路串行融合和并行融合。串行融合是把两段特征向量首尾相接得到一个新向量维度为两者之和并行融合则是把两者叠加或拼接成复数/联合直方图形式。实际操作中串行融合最简单、效果也稳定feat_lbp extractLBPFeatures(img, CellSize, [32 32]); % 944维 feat_lpq lpq_compute(img, 7); % 4096维 feat_fused [feat_lbp, feat_lpq]; % 串行拼接拼接之后两个特征的量纲和数值范围不一样LBP直方图的每个bin落在0到几千之间LPQ也类似所以后续做归一化很重要。推荐用z-score标准化也就是每个维度减去均值再除以标准差尤其在PCA之前标准化能避免方差大的维度主导主成分方向。4. PCA降维保留多少主成分才算合适4.1 PCA在特征压缩中的角色SVM本质上是基于距离度量的分类器特征维度越高、特征之间相关性越强越容易导致模型复杂度膨胀。LBP和LPQ的特征之间确实存在大量冗余尤其LPQ本身就包含去相关过程但分块拼接之后块与块之间的相关性依然存在。PCA的目标就是在尽量保留数据方差的前提下把高维特征投影到一个低维子空间。Matlab实现PCA非常方便但有几个细节需要特别注意。训练阶段用fitpca或pcacov计算主成分系数然后把投影矩阵保存下来测试阶段必须用同一个投影矩阵做变换绝不能重新计算PCA。这个坑很多新手会踩——测试数据单独跑一次PCA导致结果异常混乱因为PCA的坐标轴是数据依赖的。% 训练集上拟合PCA [coeff, score, latent] pca(feat_train); % 保留累积方差贡献率95% cumsum_ratio cumsum(latent) / sum(latent); num_dim find(cumsum_ratio 0.95, 1); feat_train_pca score(:, 1:num_dim); % 测试集使用同一投影矩阵 feat_test_centered feat_test - mean(feat_train); feat_test_pca feat_test_centered * coeff(:, 1:num_dim);4.2 保留维度怎么定累积方差贡献率 vs 固定维度PCA保留多少维的问题没有标准答案。两种常见策略策略一是根据累积方差贡献率决定一般选95%或99%。对LBPLPQ融合特征3000多维降到大约80~150维就达到95%贡献率这个范围我实测过很多次分类性能稳定。策略二是直接固定维度比如统一降到50、80、100、120维然后比较不同维度下的分类准确率画一条曲线论文里这叫做维度敏感性分析评审专家通常会觉得这个实验做得规范。我个人推荐的做法是先做主成分的能量占比曲线看看前几个主成分贡献了多少方差然后在90%~99%的范围内做一次网格实验选出准确率最高点。这个实验结果本身就能放进论文里一举两得。4.3 归一化在PCA之前的关键性PCA对特征的尺度敏感这是它的一大特点。如果某个特征维度数值范围特别大它在协方差矩阵里的影响力就会异常突出导致主成分偏向这个维度而不是真正捕捉数据结构的最大方差方向。所以PCA之前做标准化是标准操作。但这里有个坑LBP直方图和LPQ直方图都是计数型特征量纲类似有人会想不标准化应该也没事。实测下来差别还挺明显标准化之后SVM的交叉验证准确率能提升2到3个百分点。既然代价只是一行代码没有理由不加上。5. SVM分类器核选取、训练策略与性能评估5.1 为什么是SVM而不是别的分类器表情识别数据集样本量小JAFFE只有213张图CK按序列抽取也就几百到上千张。这种规模下深度学习模型容易过拟合而SVM天然适合小样本、高维问题。SVM通过最大化分类间隔来选择决策边界泛化能力强配合核技巧又能处理非线性问题。在Matlab里fitcsvm是最常用的SVM实现支持一对一和一对多的多分类方案。表情识别有7类需要扩展成多分类。有两种思路一是用fitcecoc错误纠正输出码框架封装多个二分类SVM这是最省事的方法Matlab内部自动处理一对多或一对一策略。二是手动实现一对多策略训练7个SVM每个SVM负责区分第i类与其余类预测时取决策值最大的那个类别。% 使用fitcecoc自动处理多分类 template templateSVM(KernelFunction, rbf, BoxConstraint, 1, KernelScale, 1); model fitcecoc(feat_train_pca, train_labels, Learners, template, Coding, onevsone); % 预测 pred_labels predict(model, feat_test_pca); accuracy mean(pred_labels test_labels);fitcecoc默认使用一对一编码在类别数不算太多时效果很好。如果你要显式声明一对多可以设置Coding, onevsall但注意一对多策略在类别不平衡时会有偏差JAFFE各类样本数量差不多影响不大。5.2 RBF核的两大关键参数C和gammaRBF核有两个核心超参数BoxConstraint即C值控制误分类惩罚和KernelScale控制RBF的宽度等价于gamma的倒数。C越大模型对训练集拟合越强容易过拟合C越小模型越平滑容易欠拟合。KernelScale越小决策边界越复杂越大则越平滑。这两个参数配合交叉验证搜索是必做的一步。我常用的方法是5折交叉验证网格搜索% 网格搜索C和KernelScale c_list [0.1, 1, 10, 100]; sigma_list [0.1, 1, 5, 10, 20]; best_acc 0; for c c_list for s sigma_list template templateSVM(KernelFunction, rbf, BoxConstraint, c, KernelScale, s); cv_model fitcecoc(feat_train_pca, train_labels, Learners, template, ... CrossVal, on, KFold, 5); acc 1 - kfoldLoss(cv_model); if acc best_acc best_acc acc; best_c c; best_s s; end end end从经验来看JAFFE上PCA降到100维左右时C在10附近、KernelScale在5~10附近通常有一个不错的组合。但数据不同最优参数差异很大一定不要直接抄参数要自己搜一遍。5.3 评估指标准确率之外还要看什么多分类任务里光看整体准确率是不够的。表情识别中不同类别的难度差别非常大比如开心和惊讶识别率很高恐惧和厌恶经常互相混淆。这时候需要看混淆矩阵、精确率、召回率和F1分数。Matlab里confusionchart可以很方便地画混淆矩阵cm confusionchart(test_labels, pred_labels); cm.RowSummary row-normalized; % 每行归一化显示召回率 cm.ColumnSummary column-normalized; % 每列归一化显示精确率在JAFFE数据集上我实测的典型结果是整体准确率约85%~92%开心、惊讶、中性识别率超过95%恐惧和厌恶经常跌到75%以下两者也最容易互相误判。原因在于恐惧和厌恶的肌肉运动模式本身就有重叠——眼睛睁大、眉毛上挑这些特征两者都有。这个现象不管换什么特征组合都会存在是数据集和问题本身的固有难度。6. 实测结果、参数调优与常见坑6.1 分不同情况对比实验把LBP、LPQ、LBPLPQ融合分别跑一遍是论文里很有说服力的对比实验。我拿JAFFE做了完整的对比结果大致如下5折交叉验证平均值特征方案原始维度PCA后维度95%贡献率交叉验证准确率LBP (uniform, 4x4分块)9446878.4%LPQ (window7, 4x4分块)409610582.2%LBP LPQ 串行融合504013288.3%可以看到融合之后的准确率比单独使用任何一种都有明显提升这正是LBP和LPQ互补性的直接证据。单纯LBP的表现差点是因为它在光照变化和噪声下的鲁棒性不如LPQ。不同分块数量的结果对比也值得做4x4分块总体优于2x2和8x8因为2x2太粗糙丢失位置信息8x8虽然空间分辨率更高但单个块内统计的样本太少直方图过于稀疏反而不利。这背后的直觉是分块相当于在全局统计和空间细节之间取平衡对128x128的图像来说4x432x32每块是经验上的甜点区。6.2 数据集划分方式要注意的细节表情识别里最容易犯的错误是数据集划分不当。常见做法是随机划分训练测试但如果你不做留一法leave-one-subject-out或者至少保证同一人的不同表情图片不会同时出现在训练和测试里实验结果的泛化性就要打个问号。JAFFE里同一个人有多张不同表情的图片随机划分时这些图片可能一部分在训练集一部分在测试集模型就能记住这个人的人脸特征而不是真正的表情特征测试准确率虚高。严谨的做法是按人物划分比如10个人训练1个人测试。这样一来准确率通常会下降5到10个百分点但才更真实地反映模型在新人脸上的泛化能力。做毕设的话两种划分方式都可以跑结果对比着展示能体现你考虑了泛化问题。6.3 踩过的几个坑第一个坑是特征提取时的浮点误差。Matlab的图像默认是uint8类型如果把uint8直接参与乘法运算会出现溢出或截断。比如LBP编码时2^(k-1)参与累加如果不先把图像转成double边界情况容易出错。我的习惯是一进函数就img double(img)。第二个坑是直方图归一化。提取完LBP和LPQ直方图之后如果不做归一化直接拼接送给SVM特征数值受图像分辨率影响很大。归一化方法一般选直方图除以像素总数或者用L2范数归一化两者效果差不多但一定要做。第三个坑是SVM的预测输出。fitcecoc的predict返回的是分类标签如果要获取各个类别的得分decision score需要用predict的第二个返回值score。做ROC曲线或者需要细粒度分析时这个得分很有用。不要在大循环里反复调用fitcecoc训练模型Matlab的SVM训练虽然快但多次重复训练也很耗时建议一次性完成网格搜索后固定最优参数。第四个坑是图像尺寸对LBP的影响。extractLBPFeatures的CellSize参数必须能整除图像尺寸否则会报错。如果你做数据增强或者不同来源的图片尺寸不一一定要先统一imresize到固定尺寸。6.4 扩展思路还能往哪个方向改进这套baseline跑通之后可以考虑几个低成本高收益的改进方向。第一个方向是特征选择替代PCA比如用卡方检验或互信息做特征筛选和PCA做对比实验。第二个方向是把LBP替换为改进版本如CLBPCompleted LBP、MB-LBP多块LBP对比不同局部描述子的效果。第三个方向是引入注意力机制或决策级融合——训练两个SVM一个吃LBP特征一个吃LPQ特征然后对两个模型的决策做加权投票融合策略比特征级融合更灵活也可以单独写一节实验。如果时间和算力允许还可以把SVM换成ELM极限学习机或随机森林做对比很多论文里都会有这样的分类器横向比较。回看整个项目我最深的体会是传统特征提取路线在表情识别任务上依然能打尤其是数据量不大的场景下它的稳定性和可解释性是深度方法很难替代的。代码结构清晰、每一步都有明确的物理含义这也让后续改进和写论文变得非常顺手。如果你正在做类似课题建议先把这套baseline完整跑通再决定要不要往深度学习方向扩展。本文还有配套的精品资源点击获取