ARTICLE DETAIL

资讯详情

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

基于PCA的人脸识别系统MATLAB实现教程

基于PCA的人脸识别系统MATLAB实现教程 很多做图像处理课程设计、毕业设计或论文复现的同学都会选“基于 PCA 的人脸识别系统”这个题目。题目听起来不复杂但真正动手实现时不少人会遇到样本矩阵太大、特征值计算特别慢、识别率上不去、代码东拼西凑跑不通之类的问题。这篇文章把我整理的一套完整 MATLAB 实现整理成教程从 PCA 原理讲到数据集准备、训练识别全流程、GUI 演示和常见报错排查希望帮你把这条链路彻底走通。整套源码结构清晰函数边界明确训练、测试、评估、GUI 各模块可以独立调用适合课程设计、毕业设计二次开发也方便改写成论文中的实验模块。1. 背景与核心概念1.1 人脸识别要解决什么问题人脸识别的核心任务可以概括成一句话给一张人脸图像判断这个人是谁。最朴素的想法是把两张图像逐像素比较像素相近就认为是同一个人。但这种方式在实际场景中基本不可用原因有两点图像像素维度太高。一张 112×92 的灰度图展开后是一个 10304 维向量直接比较距离不仅计算量大而且很多维度对识别没有贡献。原始像素对光照、表情、遮挡、角度非常敏感。两张同一个人的照片因为光照不同像素差异可能比不同人之间的差异还大。所以人脸识别系统的关键不在于“怎么比”而在于“比什么”。我们需要从高维图像中提取一组低维、稳定、有区分度的特征再基于这些特征做分类。PCA 主成分分析就是最经典的特征提取方法之一。1.2 PCA 主成分分析是什么PCA 的全称是 Principal Component Analysis中文叫主成分分析。它是一种无监督的线性降维方法核心思想是寻找一组正交方向使得样本数据在这组方向上的方差最大。用人脸识别的语言来说给定一批人脸图像PCA 能找到一组“基图像”把每张人脸表示成这组基图像的线性组合。由于只保留方差最大的前 k 个方向人脸图像就被压缩成了 k 维特征向量这个过程既降低了维度又保留了数据中最有区分度的信息。这组基图像展开后看起来像一张张模糊的人脸所以被称为特征脸Eigenface。基于特征脸做人脸识别的方法就是经典的特征脸方法。1.3 特征脸方法的地位与适用范围特征脸方法由 Turk 和 Pentland 在 20 世纪 90 年代提出是传统人脸识别领域最有影响力的方法之一。虽然现在工业界的人脸识别基本被深度学习方案取代但 PCA 人脸识别系统仍然是很多课程设计、毕业设计和算法入门教程的高频题目原因在于原理清晰数学基础不复杂适合讲解和答辩。算法实现量适中用 MATLAB 可以很快跑通。训练过程可视化效果好平均脸、特征脸可以直接展示。便于后续扩展可以替换分类器也可以对比 LDA、LBP-HOG 等方法的识别效果。学习这套系统不只是在做一个题目更是理解“降维”和“特征提取”这两个机器学习核心概念的绝佳入口。2. 系统设计与整体流程2.1 系统功能拆分一个完整的 PCA 人脸识别系统可以拆成四个模块模块功能对应文件数据读取模块遍历人脸库目录读取图片并预处理load_orl_dataset.m、preprocess_image.m训练模块计算平均脸、特征脸保存投影模型pca_train_face.m识别模块将测试图片投影到特征空间最近邻分类face_recognize.m演示与评估模块主脚本、识别率实验、GUI 演示demo_face_recognition.m、evaluate_k.m、pca_face_gui.m这种拆分方式的好处是后续修改很方便。比如想把最近邻分类器换成 SVM只需要改识别模块想增加直方图均衡化预处理只需要改 preprocess_image 函数。2.2 训练阶段流程训练阶段的目标是得到一组特征脸和一个投影模型流程如下读取训练集中所有人脸图像。每张图像灰度化、缩放到统一尺寸。将每张图像按列拉直成一个列向量。计算所有训练样本的平均脸。每个样本减去平均脸得到中心化矩阵。求解协方差矩阵的特征向量取前 k 个构成特征脸空间。将所有训练样本投影到特征脸空间得到训练投影系数。保存平均脸、特征脸、训练投影系数和标签。2.3 识别阶段流程识别阶段是对一张未知人脸判断身份流程如下读取测试图片执行与训练阶段相同的预处理。拉直成列向量。减去训练集的平均脸。投影到特征脸空间得到 k 维特征向量。与训练集中每个样本的投影系数做距离比较。距离最小的训练样本对应的标签就是识别结果。2.4 为什么必须区分训练集和测试集这里有一个很容易被忽略但非常重要的点测试图片不能参与 PCA 模型训练。PCA 计算协方差矩阵和特征脸时只需要使用训练集。如果测试图片也参与了特征脸空间的计算相当于模型已经提前“见过”了测试数据识别率会虚高这在论文和课程设计中叫作数据泄漏。所以本文所有实验都严格把每个人的人脸图片分成两部分前 5 张用于训练后 5 张用于测试。3. 环境准备与数据集说明3.1 运行环境本文示例以 MATLAB R2020a 为参考环境更低版本也能运行核心代码只使用了 MATLAB 基础功能和图像处理工具箱。需要提前确认以下几点已安装 MATLAB建议 R2018a 及以上版本。已安装 Image Processing Toolbox因为代码用到了 rgb2gray、imresize、imshow 等函数。如果没有安装图像处理工具箱灰度化、缩放等步骤需要自己实现建议直接安装对应工具箱省时省力。电脑内存建议 8GB 以上ORL 人脸库的规模不算大一般不会遇到内存问题。3.2 人脸库准备本文使用 ORL 人脸库这是人脸识别领域非常经典的公开数据集由剑桥大学 ATT 实验室发布。ORL 人脸库包含 40 个人每人 10 张图像图像尺寸为 112×92格式是 PGM 灰度图。图像包含表情、光照、姿态和面部细节的变化非常适合做人脸识别算法的对比实验。将 ORL 人脸库下载后放到项目目录中目录结构如下ORL/ ├── 1/ │ ├── 1.pgm │ ├── 2.pgm │ ├── ... │ └── 10.pgm ├── 2/ │ ├── 1.pgm │ ├── 2.pgm │ ├── ... │ └── 10.pgm ├── ... └── 40/ ├── 1.pgm ├── 2.pgm ├── ... └── 10.pgm这里用子目录编号作为人的身份标签1.pgm 到 10.pgm 是同一个人的 10 张照片。如果你的数据集不是这个结构需要对应修改数据读取函数。3.3 项目目录结构完整项目建议按下面的结构组织PCARecognition/ ├── ORL/ % 人脸库 ├── preprocess_image.m % 图像预处理函数 ├── load_orl_dataset.m % 数据读取函数 ├── pca_train_face.m % PCA 训练函数 ├── face_recognize.m % 识别函数 ├── demo_face_recognition.m % 主演示脚本 ├── evaluate_k.m % 不同 k 值识别率实验 └── pca_face_gui.m % GUI 演示所有 .m 文件放在同一个目录下MATLAB 运行时把当前文件夹切换到该目录即可。4. 核心算法原理详解4.1 样本矩阵的构造假设训练集中有 N 张人脸图像每张图像宽 w、高 h灰度化后像素总数为 d w × h。对 ORL 人脸库来说d 112 × 92 10304N 40 × 5 200。把每张图像按列优先拉直成一个 d 维列向量然后将 N 个列向量拼成一个 d × N 的样本矩阵 X。每一列就是一张人脸图像每一行对应一个像素位置。这里需要注意d 通常远大于 N。例如 d 10304N 200这就是典型的高维小样本问题。直接处理 d × d 矩阵会非常耗时所以后面会使用小矩阵技巧。4.2 中心化与协方差矩阵PCA 的第一步是中心化。计算所有训练样本的平均列向量meanFace (1/N) * sum(X, 2)然后将每一列减去平均脸得到中心化矩阵A X - meanFace协方差矩阵为 C A * A / N它是一个 d × d 的矩阵。特征值 λ 表示对应特征向量方向上方差的大小也就是该方向保留的信息量。可以按特征值从大到小排序取前 k 个特征向量构成特征脸空间 U维度为 d × k。这样每张人脸图像 x 就可以投影成 k 维向量y U * (x - meanFace)这个 y 就是人脸的低维特征。4.3 小矩阵技巧解决维度灾难直接计算 d × d 矩阵 A * A 的特征分解在像素维度很大时基本不可行。好在有一个经典的数学技巧可以绕开这个问题。考虑小矩阵 L A * A它是 N × N 的矩阵。假设 v 是 L 的特征向量对应特征值为 λ那么A * A * v λ * v两边同时左乘 AA * A * A * v λ * A * v也就是说(A * A) * (A * v) λ * (A * v)。这说明 A * v 就是原协方差矩阵 A * A 的特征向量对应特征值也是 λ。于是我们不需要计算 d × d 的大矩阵只需要计算 N × N 的小矩阵 L 的特征分解然后用 A * v 还原出特征脸向量。这一步是 PCA 人脸识别系统能够在小样本情况下高效运行的关键。在 MATLAB 中可以把 L A * A 的特征向量记为 V然后 eigenfaces A * V再对每一列做归一化即可。4.4 特征脸的物理意义特征脸本质上是训练集中人脸图像在方差最大方向上的“线性组合”。把特征脸向量 reshape 成图像尺寸后会看到一些模糊的人脸轮廓也就是平均脸基础上的主要变化模式。第一张特征脸通常对应光照变化后面的特征脸对应不同人脸结构的变化。保留的特征脸数量 k 决定了人脸特征空间的表达能力。k 太小信息不足k 太大会引入噪声而且计算量增加。一般通过识别率实验来选择合适的 k 值。4.5 最近邻分类人脸图像投影到特征空间后识别问题就变成了一个低维空间中的最近邻问题。常用距离度量是欧氏距离distance ||y_test - y_train_i||_2遍历训练集中所有样本找到最小距离对应的标签作为识别结果。也可以用余弦相似度代替欧氏距离两种方式各有优劣欧氏距离实现简单余弦相似度对光照变化更鲁棒一些。本文核心代码使用欧氏距离识别函数中可以很方便地切换。5. 系统源码实现下面开始写完整源码。先给出所有函数文件再给主脚本。每个函数都加了注释方便按模块理解。5.1 图像预处理函数 preprocess_image.m这个函数负责读取一张图片并完成灰度化、缩放到统一尺寸、拉直成列向量三个步骤。function imgVec preprocess_image(imgPath, targetSize) % 图像预处理函数 % 输入 % imgPath - 图像文件路径 % targetSize - [rows, cols]目标缩放尺寸 % 输出 % imgVec - d x 1 的列向量取值范围 [0, 1] img imread(imgPath); % 如果是彩色图转成灰度图 if size(img, 3) 3 img rgb2gray(img); end % 缩放到统一尺寸 img imresize(img, targetSize); % 转成 double 并归一化到 [0, 1] img double(img) / 255; % 按列优先拉直成列向量 imgVec img(:); end这里把图像统一缩放到 112×92是因为 ORL 原始图像就是 112×92。如果你使用自己的人脸库目标尺寸可以根据实际情况调整但训练集和测试集必须使用相同尺寸。5.2 数据读取函数 load_orl_dataset.m这个函数遍历 ORL 人脸库目录读取每个人前 trainCount 张图片作为训练数据。function [images, labels] load_orl_dataset(rootFolder, trainCount, imgSize) % 读取 ORL 人脸数据集的训练部分 % 输入 % rootFolder - 人脸库根目录 % trainCount - 每个人取前 trainCount 张作为训练样本 % imgSize - 统一缩放尺寸 [rows, cols] % 输出 % images - d x N 的样本矩阵每列是一张人脸向量 % labels - 1 x N 的标签向量 subDirs dir(rootFolder); subDirs subDirs([subDirs.isdir]); subDirs subDirs(~ismember({subDirs.name}, {., ..})); images []; labels []; for i 1:length(subDirs) personLabel i; for idx 1:trainCount imgPath fullfile(rootFolder, subDirs(i).name, [num2str(idx), .pgm]); if ~exist(imgPath, file) warning(文件不存在跳过: %s, imgPath); continue; end imgVec preprocess_image(imgPath, imgSize); images [images, imgVec]; %#okAGROW labels [labels, personLabel]; %#okAGROW end end end注意这里直接将子目录的顺序编号作为人的标签也就是第 1 个子目录对应标签 1。这样写的好处是不依赖子目录名称是不是纯数字通用性更强。5.3 PCA 训练函数 pca_train_face.m这是整个系统的核心函数完成平均脸计算、中心化、小矩阵特征分解、特征脸提取和训练样本投影。function model pca_train_face(images, labels, k) % PCA 人脸识别训练函数 % 输入 % images - d x N 的样本矩阵每列是一张拉直后的人脸向量 % labels - 1 x N 的标签向量 % k - 保留的特征脸数量 % 输出 % model - 结构体包含平均脸、特征脸、训练投影和标签 numSamples size(images, 2); if k numSamples k numSamples; warning(k 大于训练样本数已自动调整为 %d, k); end % 1. 计算平均脸 meanFace mean(images, 2); % 2. 中心化 centered images - meanFace; % 3. 小矩阵特征分解L A * A尺寸为 N x N L centered * centered; [V, D] eig(L); eigenvalue diag(D); % 特征值从大到小排序 [~, sortIdx] sort(eigenvalue, descend); V V(:, sortIdx); % 4. 还原到原空间A * v 是 A * A 的特征向量 eigenfaces centered * V; % 5. 特征向量归一化 for i 1:size(eigenfaces, 2) normVal norm(eigenfaces(:, i)); if normVal 1e-10 eigenfaces(:, i) eigenfaces(:, i) / normVal; end end % 6. 取前 k 个特征脸 eigenfaces eigenfaces(:, 1:k); % 7. 训练样本投影 trainProj eigenfaces * centered; % 8. 封装模型 model struct(); model.meanFace meanFace; model.eigenfaces eigenfaces; model.trainProj trainProj; model.labels labels; model.k k; model.trainImages images; end几个实现细节需要说明eig 求出的特征值不保证按大小排列所以必须手动排序。小矩阵 L centered * centered 的特征向量是 N 维的左乘 centered 后还原为 d 维特征向量。归一化可以避免特征向量的尺度不一致影响距离计算。model.trainImages 字段是为了在 GUI 中展示最相似的训练人脸如果内存紧张可以删除这个字段。5.4 识别函数 face_recognize.m识别函数输入一张测试图片的列向量和训练好的模型输出预测标签、最相似样本索引和最小距离。function [predictLabel, predictIdx, distance] face_recognize(testVec, model) % 人脸识别函数基于最近邻分类 % 输入 % testVec - d x 1 的测试图像列向量 % model - pca_train_face 训练得到的模型 % 输出 % predictLabel - 预测的标签 % predictIdx - 训练集中最相似样本的索引 % distance - 最近邻欧氏距离 % 1. 减去平均脸 testCentered testVec - model.meanFace; % 2. 投影到特征脸空间 testProj model.eigenfaces * testCentered; % 3. 计算与所有训练样本的欧氏距离 diff model.trainProj - testProj; distances sum(diff .^ 2, 1); % 4. 取最小距离对应的样本 [distance, predictIdx] min(distances); predictLabel model.labels(predictIdx); end如果要改用余弦相似度可以把第 3 步改成计算向量夹角的余弦值然后取最大值对应的样本。这个函数保持接口不变替换内部的相似度计算即可。5.5 主脚本 demo_face_recognition.m主脚本串联整个流程加载数据、训练模型、显示平均脸和特征脸、测试识别率。%% PCA 人脸识别系统 - 主脚本 clear; close all; clc; %% 参数设置 rootFolder ORL; trainCount 5; % 每个人取前 5 张训练 imgSize [112, 92]; % 图像统一缩放尺寸 k 30; % 特征脸数量 %% 读取训练集 [images, labels] load_orl_dataset(rootFolder, trainCount, imgSize); fprintf(训练样本数: %d\n, size(images, 2)); %% PCA 训练 tic; model pca_train_face(images, labels, k); trainTime toc; fprintf(PCA 训练耗时: %.4f 秒\n, trainTime); %% 显示平均脸和前 9 个特征脸 figure(Name, 平均脸与特征脸); subplot(2, 5, 1); imshow(mat2gray(reshape(model.meanFace, imgSize))); title(平均脸); for i 1:9 subplot(2, 5, i 1); imshow(mat2gray(reshape(model.eigenfaces(:, i), imgSize))); title([特征脸 , num2str(i)]); end %% 读取测试集每个人剩余 5 张 testImages []; testLabels []; subDirs dir(rootFolder); subDirs subDirs([subDirs.isdir]); subDirs subDirs(~ismember({subDirs.name}, {., ..})); for i 1:length(subDirs) for idx (trainCount 1):10 imgPath fullfile(rootFolder, subDirs(i).name, [num2str(idx), .pgm]); if ~exist(imgPath, file) continue; end imgVec preprocess_image(imgPath, imgSize); testImages [testImages, imgVec]; %#okAGROW testLabels [testLabels, i]; %#okAGROW end end %% 测试并统计识别率 correctCount 0; totalCount size(testImages, 2); for i 1:totalCount [predLabel, ~, ~] face_recognize(testImages(:, i), model); if predLabel testLabels(i) correctCount correctCount 1; end end accuracy correctCount / totalCount * 100; fprintf(测试样本数: %d, 识别正确: %d, 识别率: %.2f%%\n, ... totalCount, correctCount, accuracy);运行这个脚本会先输出训练样本数和训练耗时然后弹出一个显示平均脸和特征脸的窗口最后在命令行输出测试识别率。5.6 不同 k 值识别率实验 evaluate_k.mPCA 特征脸数量 k 对识别率影响很大这个脚本批量测试多个 k 值帮助你选择合适参数。%% 不同 k 值对识别率的影响 clear; close all; clc; rootFolder ORL; imgSize [112, 92]; trainCount 5; %% 加载训练集 [images, labels] load_orl_dataset(rootFolder, trainCount, imgSize); %% 加载测试集 testImages []; testLabels []; subDirs dir(rootFolder); subDirs subDirs([subDirs.isdir]); subDirs subDirs(~ismember({subDirs.name}, {., ..})); for i 1:length(subDirs) for idx (trainCount 1):10 imgPath fullfile(rootFolder, subDirs(i).name, [num2str(idx), .pgm]); if ~exist(imgPath, file) continue; end testImages [testImages, preprocess_image(imgPath, imgSize)]; %#okAGROW testLabels [testLabels, i]; %#okAGROW end end %% 测试不同 k 值 ks [5, 10, 20, 30, 50, 80]; fprintf(k\t识别率\n); for k ks model pca_train_face(images, labels, k); correct 0; for t 1:size(testImages, 2) pred face_recognize(testImages(:, t), model); if pred testLabels(t) correct correct 1; end end acc correct / size(testImages, 2) * 100; fprintf(%d\t%.2f%%\n, k, acc); end运行后会输出类似下面格式的结果k 识别率 5 70.50% 10 86.00% 20 90.50% 30 91.00% 50 89.50% 80 88.50%需要说明的是具体数字会因数据集划分方式、是否使用随机划分而不同但整体规律是一致的随着 k 增大识别率先上升达到峰值后缓慢下降。这是因为 k 太小会丢失有效信息k 太大会引入噪声维度。5.7 GUI 演示 pca_face_gui.m最后提供一个简单的 GUI方便交互式选择测试图片并查看识别结果。function pca_face_gui(model, imgSize) % PCA 人脸识别系统 GUI 演示 % 输入 % model - pca_train_face 训练得到的模型 % imgSize - 图像尺寸 [rows, cols] fig figure(Name, PCA 人脸识别系统, NumberTitle, off, ... Position, [300 200 850 550], MenuBar, none); ax1 axes(Parent, fig, Units, pixels, Position, [60 120 320 360]); ax2 axes(Parent, fig, Units, pixels, Position, [470 120 320 360]); uicontrol(Style, pushbutton, String, 选择测试图片, ... Position, [120 50 120 35], Callback, selectImage); uicontrol(Style, pushbutton, String, 开始识别, ... Position, [280 50 120 35], Callback, recognizeImage); infoLabel uicontrol(Style, text, ... String, 请选择一张人脸图片, ... Position, [60 490 500 30], FontSize, 12); testImgPath ;
返回列表