ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于BP神经网络的人脸识别MATLAB实现:特征提取、训练与测试详解

基于BP神经网络的人脸识别MATLAB实现:特征提取、训练与测试详解 简介压缩包内含一套基于反向传播BP神经网络实现人脸识别的Matlab完整工程面向需要完成课程设计、毕业设计或入门神经网络项目实践的开发者。压缩包共67个文件包含23张人脸样本图片、22个数据文件、21个源码脚本和1个界面文件整体大小约120MB。工程内提供网络训练、主成分分析、遗传算法优化、图形界面等核心模块并配有多个针对性测试脚本数据文件保存完成训练的特征与结果图片构成训练和测试样本集界面可直接打开查看操作面板。项目按测试集、样本数据和代码模块组织便于理清数据读取、特征降维、网络训练到精度评估的完整链路。已有100人学习下载适合结合反向传播原理对照代码实操也可作为后续引入卷积神经网络或改进特征表达方法的起点。1. 为什么这个老方案至今还有人翻出来BP神经网络人脸识别的实用边界手机门禁机上的人脸识别早被卷积神经网络包揽但当你的任务是“在几百张样本上快速交差、在MATLAB里复现一套课程设计、或者搞懂特征怎么从像素变成分类结果”时BP神经网络反而比深度学习更容易让入门工程师看穿底牌。这个标题指向的正是这样一套方案用BP神经网络作为分类器在MATLAB里完成人脸数据的读取、特征提取、模型训练和识别测试。它能解决的是小样本、受限姿态与光照环境下的身份判别问题适合课程设计、毕业设计以及想对比浅层模型与深度学习差异的从业者。难点不在网络本身而在数据预处理和训练参数多数人跑不出理想准确率是因为把BP当成了黑匣子却忽略了输入特征的细心打理。2. 跑通前先想清楚BP神经网络与人脸识别的匹配点、数据集与特征选型2.1 人脸识别为什么可以用BP从模式映射到分类面的原理BP神经网络是一种多层前馈网络核心机制是误差反向传播输入信号经过隐藏层逐层映射输出与真实标签差异的梯度再回传给每一层权重完成参数更新。把人脸识别看成分类问题本质就是让网络学会“这张像素图属于哪个人”——输入是图像特征向量输出是人员编号或类别概率。BP网络没有卷积层那种天生的平移和尺度不变性它只能对“已经对齐好”的输入做分类。所以这个方案里最重要的不是网络结构而是输入特征怎么整理。你在搜“bp神经网络结构图”时看到的那些三层结构——输入层、隐藏层、输出层——对入门者很够用了输入层节点数等于特征维数隐藏层节点数决定网络容量输出层节点数等于要识别的人数。很多人把BP结构画得非常复杂但对人脸识别这种小规模分类两层隐藏层基本顶天再多反而更难收敛。从数学角度看BP网络通过激活函数通常用tansig或logsig把原始输入的线性组合映射到非线性空间理论上可以逼近任意分类边界。人脸图像本质是高维空间中的低维流形光照、表情、姿态都会扰动像素值。BP网络要做得稳必须靠特征工程先把这些扰动压下去。所以下面两步——数据规范化和特征降维——比网络参数更值得先花时间。2.2 数据集与预处理用MATLAB读入人脸库和灰度归一化常见的人脸库如Yale、ORL、FERET都能在公开渠道拿到。以ORL为例每类人10张灰度照片尺寸112×92。这类小样本库很适合BP试手。第一步不是直接扔给网络而是把图片读进来统一尺寸和灰度范围。% 读取人脸库文件夹统一尺寸为64x64并线性归一化到[0,1] imPath faces; files dir(fullfile(imPath, *.pgm)); numSamples length(files); imageSize [64, 64]; data zeros(numSamples, prod(imageSize)); labels zeros(numSamples, 1); for i 1:numSamples img imread(fullfile(files(i).folder, files(i).name)); if size(img, 3) 3 img rgb2gray(img); end img imresize(img, imageSize); img double(img) / 255; % 把灰度值压到[0,1] data(i, :) img(:); % 展成一行 % 假设文件名形如 person_01.pgm从数字解析标签 tokens regexp(files(i).name, _(\d)\., tokens); labels(i) str2double(tokens{1}{1}); end这段代码做了三件事用dir定位所有图片imresize把所有图像缩放到同样大小将灰度值从[0,255]缩放到[0,1]。这三步在BP人脸识别里缺一不可。imresize缩小到64×64可以显著降低输入维度从4096维出发后面的PCA和训练压力都小很多。没有统一尺寸网络输入层就无法固定训练也毫无意义。这里的关键参数是imageSize。64×64是我常用的折中过大会让输入维度过高训练时间暴涨且容易过拟合过小比如32×32可能丢失眼睛、嘴部等判别信息识别率下滑。如果你的数据集光照差异较大可以在归一化前加一句img histeq(img);做直方图均衡化能缓解一部分光照干扰。注意归一化必须用double转浮点数否则除以255后会被截断成整数等于白做。2.3 特征怎么取像素、PCA、LBP该选什么原始像素是最简单、最不推荐直接塞给BP的特征。一张64×64灰度图展开是4096维如果你只有几十张训练样本BP网络要拟合的目标数远大于样本数必然会过拟合到训练集上测试集惨不忍睹。所以通常会对原始像素做降维。下面这个对比表是我实际测试时常用的选择参考特征类型维度对光照鲁棒性计算成本适合场景原始像素4096弱低无要求纯练手PCA降维20~100中中样本少、光照稳定LBP直方图128~2048强中高光照变化大、门禁模拟用PCA降维在MATLAB里只需一行核心调用但要用好有几个参数值得交代% 对原始像素做PCA保留累计能量95%的维度 meanFace mean(data, 1); dataCentered data - meanFace; [coeff, score, latent] pca(dataCentered); energy cumsum(latent) / sum(latent); k find(energy 0.95, 1); feature score(:, 1:k);coeff是主成分方向矩阵score是投影后的特征latent是各主成分对应的特征值。find(energy 0.95, 1)会找出第一个累计能量超过95%的维度k通常人脸库用30~80维就能稳住识别率。这里有个容易被忽略的点PCA的coeff和meanFace必须保存下来。测试阶段读取新图片时要用同一套meanFace和coeff做投影而不是重新计算否则训练和测试特征空间不一致识别率会崩掉。如果你不想自己写PCA投影也可以用MATLAB的pca结合transform的方式但上面这段已经够用。LBP则对光照更鲁棒MATLAB图像处理工具箱里有extractLBPFeatures返回直方图特征缺点是维度更高需要配合PCA或卡方距离做筛选。对于课程设计想快速出效果我建议用PCA降维到50维左右就接BP效果比直接用原始像素稳定一个台阶。3. 把BP神经网络在MATLAB里从零搭起来网络构建、训练与参数设置3.1 用newff还是feedforwardnet网络对象选择的差异老教程和旧教材里会写newff而新版MATLAB强烈建议用feedforwardnet。两者底层都是前馈网络但API风格和使用方式不同。newff来自旧版本到R2020以后仍能用但警告一堆很多参数必须用net.trainFcn和net.trainParam去设置新手容易漏。feedforwardnet是讲得顺一点的封装可以指定隐藏层神经元数直接返回一个网络对象配合train和sim调用。下面这段用的是feedforwardnet。3.2 训练函数的选择traingd/traingdm/trainlm对收敛的影响BP神经网络的“学力”很大程度来自训练函数。MATLAB里常见的三个梯度训练函数分别是traingd标准梯度下降、traingdm加入动量和trainlmLevenberg-Marquardt。人脸识别样本量小、维度不高我最常用trainlm它收敛快精度也不错。但trainlm需要计算雅可比矩阵内存开销大如果特征维度几百、样本几千可能会内存不足。这时换回traingdm反而更稳。我的经验是样本数小于500且特征维度低于100时果断用trainlm数据再大一些就用traingdm并把动量和学习率调低。traingd基本不用收敛太慢跑几百轮都不见底。具体配置可以通过net.trainParam设置比如net.trainParam.lr 0.01;。3.3 完整训练代码数据划分、归一化、训练、保存模型下面这一段把前面讲的东西串起来是完整的训练流程。假设你已经通过2.3节得到了feature和labels。% 按7:3划分训练集与测试集保持类别分布大致均匀 rng(42); % 固定随机数保证结果可复现 cv cvpartition(labels, HoldOut, 0.3); trainIdx training(cv); testIdx test(cv); X_train feature(trainIdx, :); Y_train labels(trainIdx, :); X_test feature(testIdx, :); Y_test labels(testIdx, :); % 标签转成0/1矩阵输出层节点数类别数 classes unique(labels); numClasses length(classes); Y_mat zeros(length(Y_train), numClasses); for i 1:length(Y_train) Y_mat(i, classes Y_train(i)) 1; end % 构建BP网络一个隐藏层20个节点 net feedforwardnet(20); net.layers{1}.transferFcn tansig; net.layers{2}.transferFcn logsig; net.trainFcn trainlm; net.trainParam.epochs 300; net.trainParam.goal 1e-4; net.divideFcn dividetrain; % 手动已经划好测试集训练时不再次划分 % 训练 [net, tr] train(net, X_train, Y_mat); % 预测测试集 predMat sim(net, X_test); [~, predIdx] max(predMat, [], 1); predictions classes(predIdx); % 保存模型 save(bp_face_model.mat, net, meanFace, coeff, k, classes);这段代码有几个参数值得单独说明。feedforwardnet(20)里的20是隐藏层神经元数对人脸分类来说20到50都常见。太小欠拟合太大容易记住训练集中的噪声。我先给20通常能到90%以上的识别率再根据验证集调整。transferFcn隐藏层用tansig输出层用logsig因为logsig输出范围是0到1适合做多分类的概率输出。trainFcn用了trainlm收敛速度很快但如果你内存吃紧改成traingdm并设置net.trainParam.lr 0.01; net.trainParam.mc 0.9;。另一个重点是net.divideFcn dividetrain。默认feedforwardnet会自动把数据分成训练、验证、测试三份而我们已经手动做了7:3划分如果再让网络自己划分测试集就会被网络偷看识别率虚高。所以必须显式关闭自动划分。后面对sim输出的结果取最大一列作为预测身份再映射回classes就得到了预测标签。保存时记得把PCA参数meanFace、coeff、k一起存进去没有它们在测试阶段无法加载新图片。4. 人脸识别测试与验证识别率、混淆矩阵和阈值判断4.1 测试集上跑通识别流程训练完的网络不是终点还要在真实测试集上完整跑一遍。很多同学训练误差很小一上测试集就翻车问题出在测试流程和训练流程不一致测试图片没有减训练集的均值或者没有经过同一套PCA投影。下面是正确顺序的测试代码function predLabel recognizeFace(imgPath, net, meanFace, coeff, k, classes, imageSize) img imread(imgPath); if size(img, 3) 3 img rgb2gray(img); end img imresize(img, imageSize); img double(img) / 255; imgVec img(:); imgCentered imgVec - meanFace; % 用训练集的均值中心化 proj imgCentered * coeff(:, 1:k); % 投影到主成分空间 out sim(net, proj); [~, idx] max(out); predLabel classes(idx); end这里最容易漏的是imgCentered imgVec - meanFace。训练时PCA是在中心化后的数据上做的测试时也必须先用同一均值中心化。coeff(:, 1:k)取前k个主成分方向和训练时保持一致。proj是一个1×k向量sim会输出一个numClasses×1向量取最大值位置查classes得到预测身份。这一步通过了才算真正“跑通识别流程”。4.2 用混淆矩阵和ROC判断模型是否真的可用光看整体识别率不够分不清模型是把所有人像认成同一个还是真正学到判别信息。用MATLAB的confusionmat能看到每一类人的识别情况confMat confusionmat(Y_test, predictions); disp(confMat); % 计算各类别召回率 accuracies zeros(numClasses, 1); for i 1:numClasses tp confMat(i, i); total sum(confMat(i, :)); accuracies(i) tp / total; end bar(accuracies); xlabel(人员ID); ylabel(召回率);混淆矩阵的行是真实身份列是预测身份。如果某一行大部分落在别的列说明这一类人被系统性误判。比如第3类总是被认成第5类多半是这两类人的原始图像长得像或者训练样本中第3类的图片光照条件更差。这时不要盲目增加网络节点优先检查样本。ROC曲线能进一步判断模型判别能力但对多分类需要做“一对多”处理。我更建议直接用混淆矩阵和各类召回率因为人脸识别业务里最怕的是某个人永远认不出来。4.3 识别门禁场景下的阈值设置实际做人脸门禁时网络输出不是直接判身份而是要加一个阈值判断“来的人是否在登记库中”。BP网络的输出经过logsig之后每一类都在0到1之间理想情况下已知人脸会有一个输出接近1其余接近0。但如果来的人不在库里网络也会硬输出一个类别。这时候需要在分类结果外再加一层置信度判断。[outValue, idx] max(out); if outValue 0.7 % 置信度阈值 predLabel classes(idx); disp([身份ID num2str(predLabel)]); else disp(未匹配到库中人脸拒绝通行); end这里outValue取自sim(net, proj)的最大输出值。0.7是我常用的起始阈值但不是固定值。你需要画出所有测试样本的outValue分布再决定阈值。假设样本中坏人不在库里的脸的max输出普遍在0.3~0.6那就把阈值设高一些到0.8如果库中人脸也有不少max输出在0.6左右阈值设太高会导致误拒率上升。这个阈值调整就是一个“通信博弈”阈值越低越容易被陌生人混进来阈值越高自己人更容易被拦在外面。门禁机的典型做法是把阈值调高宁可误拒也不误放。5. BP人脸识别的5个经典踩坑从黑匣子到翻车的血泪记录5.1 现象网络不收敛误差曲线一条直线训练时你盯着MATLAB的nntraintool窗口误差曲线从一开始就没下降损失恒定在某个值。网络输出全是同一类或者全是0.5。原因输入特征范围没统一。如果原始像素没有除以255数值几百上千梯度可能在反向传播时溢出或者隐藏层激活函数用了purelin输出没有非线性能力。也常见于学习率设置太大导致权重更新在损失面上震荡但不落地。解决先把输入特征做线性归一化到[0,1]或标准化到均值0方差1隐藏层激活函数改成tansig学习率从0.01往下调traingd时用0.001起。训练前打印一下min(feature)和max(feature)确认不是极端数值。5.2 现象训练集准确率99%测试集却只有30%这是最经典的过拟合。BP网络隐藏层节点数设得太高比如100个隐藏节点而训练样本只有50张网络会把每个训练样本的噪声和背景都背下来。测试集遇到新表情或新光照就彻底失效。解决隐藏层节点数降到10~30之间用TrainLM时设net.trainParam.goal 1e-3而不是1e-6提前结束最重要的加入PCA降维把特征从4096压到50维让网络根本没有机会去背太多细节。我做过一次对比同样网络和样本直接用像素时测试集只有35%PCA降维后测试集提高到82%这就是特征工程的价值。5.3 现象不同光照下人脸识别率骤降训练集里都是均匀光照的照片测试时换了强左侧光识别率掉到一半以下。原因是BP网络对光照敏感原始像素值受光照影响巨大同一个人的脸亮暗分布完全变了网络以为换了人。解决预处理时加histeq(img)做直方图均衡化或者特征改用LBP直方图LBP对局部纹理的编码方式天然不受全局亮度变化影响。如果你的数据源光照复杂直接上LBP识别率会稳健很多。再不行就做MNI归一化把图像均值和方差对齐到一个固定值。5.4 现象MATLAB内存不足或训练极慢特征维度4096加上几百个样本trainlm计算雅可比矩阵时内存占用会指数级上涨。我遇到过一台8GB内存的电脑训练到一半MATLAB直接弹出“Out of Memory”。解决把图像尺寸从64×64降到32×32维度变成1024先用PCA把特征降到30维再训练训练速度会快几十倍。如果你必须保留高维度改训练函数为traingdm它不计算雅可比矩阵内存友好很多但需要把epochs加到2000多等几分钟。另外记得用clear清掉不用的变量尤其别让整个图像库的原始数据一直挂在workspace里。5.5 现象识别结果永远偏向某一个人测试集里无论输入谁网络总是预测成同一个人ID。查混淆矩阵发现大部分样本都落在那个人那一列。原因样本不均衡。如果你的人脸库里某类人有50张其他人只有5张网络学到的是“输出那个人损失最小”。特别是输出层用了logsig这类别偏差会被进一步放大。解决确保每个类别的样本数尽量接近不够就用数据增强——对图片做小幅平移、旋转、加噪声来补齐或者改进训练损失函数对样本少的类别给更高权重。MATLAB里手动给Y_mat乘以权重矩阵也可以。最简单的做法是重新挑选训练样本让每类至少8张通常就能解决。6. 让这个老方案跑得更稳模型压缩、早停与跨库泛化验证6.1 用早停和正则化提升泛化能力feedforwardnet内置了自动验证集早停机制但前面我用dividetrain关掉了为的是手动控制测试集。如果你不用手动划分可以让网络自己把数据分成70/15/15的训练/验证/测试三份验证集的作用就是早停——当验证误差连续多次不下降时训练自动终止防止过拟合。用net.trainParam.max_fail 10;控制连续失败次数。手动划分时也可以用早停但不要用同一个测试集做始终停否则你又偷看测试集了。正则化方面MATLAB的trainbr是“贝叶斯正则化”训练函数它能在训练过程中自动调整权重大小抑制过拟合。把net.trainFcn trainbr;就能换用。注意trainbr收敛慢但最终模型的泛化能力通常比trainlm好特别适合小样本人脸库。6.2 把MATLAB模型导出为可部署的形式课程设计交差是一回事真要把模型在门禁机上跑又是另一回事。MATLAB训练好的net可以用genFunction生成独立的MATLAB函数这样部署环境不需要完整的神经网络工具箱genFunction(net, bp_face_predict.m);生成的bp_face_predict函数接收输入特征返回网络输出。你可以像普通MATLAB函数一样调用它适合打包给其他同事。如果目标是C或嵌入式还可以用coder工具生成C代码但前提是你不能用feedforwardnet里那些自动划分的复杂操作必须用训练好的简单网络对象。我通常把PCA投影和网络输出打包成一个函数再生成C代码这样门禁机的ARM板子能跑起来。6.3 验证模型是否真的“会用”的最后一个技巧我的习惯是准备一份“没见过的人脸集”——从网上找几张不同光照、不同角度的人脸图片不属于训练库里的任何人。先拿它们测试如果模型的max输出低于阈值说明网络还知道“有些脸不是库里的”这是判断模型是否真正有用的分水岭。很多BP网络实现做到这一步就露馅了。接着我会故意把同一张人脸裁剪掉下半部分再测试看看输出有什么变化。如果特征全挤在嘴巴和下颌面部细节一丢识别率就崩我就知道这个模型离产品化还很远。这一步不是要追求灵丹妙药而是帮你建立对模型边界的感觉。靠着这个习惯我后来在门禁项目里提前发现了很多不敢上线的问题也少挨了不少骂。希望帮到你。正文完本文还有配套的精品资源点击获取
返回列表