MATLAB人脸朝向识别:BP、SVM、LVQ三分类实战与对比
简介:这份资源面向图像识别与模式识别方向的初学者及课程实践者,聚焦人脸朝向分类这一典型任务,提供基于BP神经网络、支持向量机与LVQ神经网络三种算法的MATLAB实现方案,可用于对比不同分类器在同一数据集上的表现差异。压缩包共59个文件,包含50个bmp人脸图像样本与9个m脚本文件,图像按朝向类别编号组织,脚本覆盖特征提取、模型训练、预测与交叉验证等环节,整体约4.89MB,结构紧凑便于直接运行调试。目前已有471人学习下载,说明该案例在教学中具有一定参考价值。读者可借此理解人脸朝向识别从数据准备、特征提取到多模型训练与评估的完整流程,并对比BP、SVM与LVQ在分类精度和训练效率上的差异,适合作为课程设计、实验报告或算法入门的实操素材。
1. 从 50 张 bmp 到三套分类器:这套 MATLAB 人脸朝向识别代码到底能跑出什么
手里有一批人脸图像,想判断脸是朝左、朝右还是朝前,但不想一上来就搭深度学习环境——这个场景下,一套纯 MATLAB 实现的三分类方案就很有价值。这个资源包做的事很明确:用 BP 神经网络、支持向量机(SVM)和 LVQ 神经网络三种方法,对同一批人脸图像做朝向识别,并给出可对比的完整流程。图像目录里是 50 张 bmp 文件,命名规律是「编号_朝向.bmp」,比如 1_1 到 10_5,覆盖 10 个人、5 种朝向。代码侧提供特征提取、训练、预测、交叉验证等脚本,适合做课程设计、模式识别大作业,或者想横向对比三种经典分类器在图像任务上差异的从业者。它不依赖深度学习工具箱,跑通门槛低,但想跑出稳定结果,特征和参数这两关必须过。
2. 三套分类器的分工:BP、SVM、LVQ 各自吃什么样的特征
2.1 为什么同一批图像要喂给三种网络
人脸朝向识别本质是一个「图像 → 朝向标签」的分类问题。难点不在分类器本身,而在图像怎么变成分类器能吃的数值向量。这个包里三种方法共用同一套特征提取逻辑,区别只在分类器结构:BP 是反向传播的前馈网络,靠梯度下降调权;SVM 找最大间隔超平面,对小样本更稳;LVQ 是有监督的竞争学习,用原型向量代表每个类别。把三者放在一起,好处是同一份特征下能直接看出哪种分类器更适合当前数据规模。50 张图属于典型小样本,SVM 和 LVQ 往往比 BP 更不容易过拟合,但 BP 调好后也能用。选型时先看样本量,再看特征维度,最后才看分类器复杂度。
2.2 特征提取脚本 feature_extraction.m 在做什么
常见做法是把人脸区域归一化后提取灰度统计或降维特征。这个包里 feature_extraction.m 负责把每张 bmp 转成特征向量,供后续训练使用。下面是一个符合该资源场景的特征提取骨架,思路是读图、灰度化、缩放、拉平并归一化:
% feature_extraction.m 核心逻辑示意 function feature = feature_extraction(imgPath) img = imread(imgPath); % 读取 bmp 图像 if size(img,3) == 3 img = rgb2gray(img); % 彩色转灰度,统一通道 end img = imresize(img, [32 32]); % 统一尺寸,避免维度不一致 img = im2double(img); % 转 double,范围 0~1 feature = img(:)'; % 拉平成 1x1024 行向量 feature = (feature - mean(feature)) / (std(feature) + eps); % 零均值归一化 end逻辑说明:先保证所有图像通道和尺寸一致,否则后面拼接特征矩阵会直接报维度错误。im2double 把像素压到 0~1,避免大数值主导距离计算。拉平后得到 1024 维向量,归一化让不同光照下的样本处在同一尺度。参数上,[32 32] 是常见折中,太小丢细节,太大在小样本下容易过拟合;eps 防止标准差为零时除零。如果换成 PCA 降维,可以在拉平后接 pca 函数保留 95% 方差,维度能压到几十维,训练会快很多。
2.3 标签怎么从文件名里取出来
图像命名 1_1.bmp 到 10_5.bmp,下划线后一位就是朝向类别。训练前必须把文件名解析成标签,否则分类器不知道每张图属于哪类。常见做法是用 dir 批量读取,再用正则或字符串分割取类别:
files = dir(fullfile('Images', '*.bmp')); X = []; Y = []; for i = 1:numel(files) fname = files(i).name; label = str2double(fname(end-4)); % 取 "x_y.bmp" 中的 y feat = feature_extraction(fullfile('Images', fname)); X = [X; feat]; Y = [Y; label]; end这里 fname(end-4) 依赖命名长度固定,如果文件名位数变化就会取错,稳妥写法是用 split 或 regexp 提取下划线和点之间的数字。X 是 50×1024 的特征矩阵,Y 是 50×1 标签向量。标签类别数决定输出层节点数,BP 和 LVQ 的输出维度都要跟它对齐。
3. 把代码跑起来:训练、预测与交叉验证的完整链路
3.1 BP 网络训练脚本 chapter22_bp.m 的关键参数
BP 部分的核心是设置网络结构、训练函数和迭代次数。这个包里 chapter22_bp.m 承担训练职责,典型配置如下:
% chapter22_bp.m 训练流程示意 net = feedforwardnet(20); % 单隐层,20 个神经元 net.trainParam.epochs = 1000; % 最大迭代次数 net.trainParam.goal = 1e-4; % 训练目标误差 net.trainParam.lr = 0.01; % 学习率 net.divideParam.trainRatio = 0.7; % 70% 训练 net.divideParam.valRatio = 0.15; % 15% 验证 net.divideParam.testRatio = 0.15; % 15% 测试 net = train(net, X', Y'); % 注意样本按列存放逻辑说明:feedforwardnet(20) 建的是前馈网络,隐层 20 个节点对小样本够用,再多容易记住噪声。epochs 设 1000 是保险值,实际看验证误差早停。学习率 0.01 偏保守,太大震荡,太小收敛慢。MATLAB 神经网络工具箱要求输入按列排,所以 X 和 Y 都要转置。如果报「输入维度不匹配」,先检查是不是漏了转置。训练完用 net 对测试集预测,再算混淆矩阵看每类召回。
3.2 SVM 脚本 chapter_svm.m 的核函数与多分类处理
SVM 原生是二分类,5 类朝向需要多分类策略。MATLAB 的 fitcecoc 用纠错输出码把多分类拆成多个二分类,适合这种场景:
% chapter_svm.m 多分类 SVM 示意 t = templateSVM('KernelFunction', 'rbf', 'BoxConstraint', 1); mdl = fitcecoc(X, Y, 'Learners', t); pred = predict(mdl, X); acc = mean(pred == Y);逻辑说明:rbf 核适合特征维度和样本量都不极端的情况,BoxConstraint 控制间隔与误分类的权衡,值越大越不允许错分,但小样本下太大会过拟合。fitcecoc 内部对每对类别训练一个二分类器再投票。这里直接用全量数据训练和预测,只是为了验证流程能通;真实评估必须留出测试集,否则准确率虚高。如果换成线性核,速度更快,但非线性边界可能欠拟合。
3.3 LVQ 训练与预测:lvq1_train.m、lvq2_train.m、lvq_predict.m 的配合
LVQ 用原型向量代表类别,训练时把原型往正确类别样本拉、远离错误类别。这个包提供 lvq1_train.m 和 lvq2_train.m 两个版本,区别在更新规则:LVQ1 只更新最近原型,LVQ2 同时更新最近的两个原型,边界更平滑。预测由 lvq_predict.m 完成,算样本到各原型的距离,取最近原型的类别:
% lvq_predict.m 预测逻辑示意 function pred = lvq_predict(prototypes, protoLabels, X) dist = pdist2(X, prototypes); % 样本到每个原型的欧氏距离 [~, idx] = min(dist, [], 2); % 找最近原型 pred = protoLabels(idx); % 返回对应类别 end逻辑说明:pdist2 一次算出所有样本到所有原型的距离矩阵,min 沿第二维取最近。原型数量和初始化位置对结果影响很大,常见做法是每类取若干样本做初始原型。学习率通常从 0.1 开始逐步衰减,衰减太快学不动,太慢后期震荡。crossvalind_lvq.m 负责生成交叉验证索引,保证每折里各类别都有样本,避免某折缺类导致训练失败。
3.4 用交叉验证判断结果是不是「运气好」
50 张图如果只做一次 7:3 划分,准确率波动会很大。crossvalind_lvq.m 的作用就是生成分层交叉验证索引,让每折的类别分布接近整体。常见做法是 5 折或 10 折,每折轮流做测试集,最后取平均准确率。看结果时不要只盯最高的一次,要看均值和方差。如果某折准确率骤降,多半是那折里某个朝向样本太少。样本量小的时候,交叉验证比单次划分更能反映真实泛化能力,这也是这套代码里比较值得用的部分。
4. 避坑与排查:跑这套代码最容易翻车的五个地方
4.1 现象:训练报错「矩阵维度不一致」 → 原因:样本按行存放 → 解决:统一转置
MATLAB 神经网络工具箱默认样本按列排,即每列一个样本。如果 X 是 50×1024,直接喂进去会被当成 1024 个样本、每个 50 维。解决方法是训练前统一 X' 和 Y',或者在建特征矩阵时就按列拼。这个坑几乎每个第一次用工具箱的人都会踩,报错信息还不一定直说维度问题。
4.2 现象:准确率接近 100% 但换数据就崩 → 原因:全量训练后又在全量上预测 → 解决:严格划分训练测试
很多示例脚本为了演示方便,用全部数据训练再全部预测,算出来的准确率没有意义。正确做法是先划分训练集和测试集,或者用交叉验证。判断是否数据泄漏,就看预测时用的样本有没有参与过训练。这个包里 test.m 如果直接对训练数据预测,结果只能证明流程通,不能证明模型好。
4.3 现象:LVQ 训练不收敛或某类全错 → 原因:原型初始化不合理或学习率过大 → 解决:按类初始化并衰减学习率
LVQ 对初始原型敏感。如果所有原型随机初始化,可能一开始就偏向某类。常见做法是从每类样本里随机选几个作为初始原型,学习率从 0.1 起步按轮次衰减。如果某类全错,先检查那类原型数量是不是为零,再看学习率是不是大到每次更新都跨过最优位置。
4.4 现象:特征提取后不同图像维度不同 → 原因:原图尺寸不一致 → 解决:强制 imresize 到统一尺寸
Images 目录里的 bmp 如果尺寸不统一,拉平后向量长度就不同,拼接矩阵直接失败。feature_extraction.m 里必须有无条件 imresize。判断方法是在循环里打印 size(feature),看是否所有样本一致。统一尺寸建议 32×32 或 64×64,太大在小样本下维度灾难,太小丢失朝向信息。
4.5 现象:SVM 训练很慢或内存爆 → 原因:特征维度太高且未降维 → 解决:先 PCA 再训练
1024 维特征对 50 个样本来说维度远高于样本量,SVM 核矩阵会很大。常见做法是先做 PCA 降到 30~50 维,再喂给 fitcecoc。降维后不仅快,还能减少过拟合。判断是否需要降维,看特征维度和样本量的比值,超过 10:1 就值得先降维。
5. 进阶技巧:把三套结果放在一张表里对比,才算真正用透这套代码
跑通单个脚本只是第一步,这套资源真正的价值在于横向对比。我一般会固定同一份特征矩阵和同一套交叉验证索引,分别喂给 BP、SVM、LVQ,把每折准确率记下来,最后算均值和标准差。这样对比才公平,否则特征一变、划分一变,数字没有可比性。下面是一个汇总对比的写法:
% 统一交叉验证下对比三种分类器 indices = crossvalind('Kfold', Y, 5); accBP = zeros(5,1); accSVM = zeros(5,1); accLVQ = zeros(5,1); for k = 1:5 testIdx = (indices == k); trainIdx = ~testIdx; % BP net = feedforwardnet(20); net = train(net, X(trainIdx,:)', Y(trainIdx)'); predBP = vec2ind(net(X(testIdx,:)')); accBP(k) = mean(predBP' == Y(testIdx)); % SVM t = templateSVM('KernelFunction','rbf'); mdl = fitcecoc(X(trainIdx,:), Y(trainIdx), 'Learners', t); accSVM(k) = mean(predict(mdl, X(testIdx,:)) == Y(testIdx)); % LVQ 用包内函数 [proto, protoLab] = lvq1_train(X(trainIdx,:), Y(trainIdx)); predLVQ = lvq_predict(proto, protoLab, X(testIdx,:)); accLVQ(k) = mean(predLVQ == Y(testIdx)); end fprintf('BP: %.3f±%.3f\n', mean(accBP), std(accBP)); fprintf('SVM: %.3f±%.3f\n', mean(accSVM), std(accSVM)); fprintf('LVQ: %.3f±%.3f\n', mean(accLVQ), std(accLVQ));逻辑说明:crossvalind 生成统一折号,三种方法用完全相同的训练测试划分,排除划分差异带来的干扰。vec2ind 把 BP 输出转成类别索引。最后打印均值和标准差,标准差大的方法说明对数据划分敏感,小样本下要谨慎选。参数上,5 折是样本量 50 时的常见选择,折数太多每折测试样本太少,折数太少评估不稳。
除了准确率,还建议看混淆矩阵,尤其关注相邻朝向之间是否容易混。人脸朝向里「左前」和「左」这类边界类别,三种分类器都可能出错,看混淆矩阵能定位到底是特征不够还是分类器不行。如果某个类别在三种方法下都错得多,问题多半在特征提取,而不是分类器选型。
从那以后我每次拿到这类小样本分类代码,都会先固定交叉验证索引再对比模型,不然数字好看也说明不了问题。希望帮到你。
本文还有配套的精品资源,点击获取