基于小波矩的MATLAB人脸识别算法源码与实现
简介这是一份基于MATLAB的小波矩人脸识别算法项目源码适合计算机、电子信息等专业学生用于课程设计、期末大作业或毕业设计参考。项目实现了从人脸检测、分割到小波矩特征提取与匹配的完整流程包含YCL.m、Humoment.m、detectFaceParts.m等多个核心脚本并配有README说明便于理解算法思路和运行方式。压缩包共30个文件以MATLAB的.m源文件为主辅以bmp、jpg格式的测试人像和.mat格式的预存特征数据整体大小仅1.44MB轻量易用。目前已有56人学习下载可作为入门级人脸识别算法研究的参考资料。资源注重动手实践代码结构清晰、注释与说明到位适合能看懂代码并愿意自行调试的读者在此基础上扩展功能。1. 为什么人脸识别要用小波矩多尺度形状特征的取舍做了一整年 MATLAB 人脸识别的离线项目我慢慢发现真正能被复现和调试的往往不是深度学习那套端到端模型而是像小波矩这样特征明确、依赖少的算法。小波矩把人脸图像变换到极坐标在径向方向用小波基做多分辨率分解在角向方向再保留分量信息最终得到一组同时对平移、旋转、尺度变化不敏感的特征。它与传统 Hu 矩最大的差别是Hu 矩只能给出全局平均量而小波矩能捕捉眼睛边缘、下颌曲线这些局部的细节差异。这个题目里的“源码项目说明”指的是一套完整的 MATLAB 实现适合小样本、算力有限或需要把识别过程拆给其他模块解释的教学场景。如果你不想被显卡和设备驱动绑架想在一台普通笔记本上把人脸识别跑起来这条路值得试。2. 小波矩的数学结构与MATLAB离散化2.1 从几何矩到小波矩两个缺陷的补法人脸识别本质上是在做形状描述。最早大家用几何矩比如图像的零阶面积矩、一阶重心矩和二阶中心矩再由 Hu 在 1962 年提出 7 个不变矩。这些矩的优点是计算简单但存在两个明显的坏毛病第一它是全局积分把整张脸的平均灰度和轮廓“揉”成一团对鼻子、嘴、颧骨这些局部组织的辨识力不够第二高阶矩容易受噪声和量化误差影响实际工程中经常出现同一个人的两个样本比不同人的样本还远的情况。小波矩的思路是用小波基替换几何矩里的多项式核步骤如下先把原始灰度图做极坐标变换得到 f(r,θ)其中径向坐标 r 归一化到 0 到 1角向坐标 θ 取 0 到 2π。接着在 r 方向用小波族 ψ_m,n(r) 2^(m/2) ψ(2^m r − n) 分解在 θ 方向保留傅里叶核 e^(−jkθ)。于是小波矩写成F_mnk ∫∫ f(r,θ) ψ_m,n(r) e^(−jkθ) r dr dθ这个公式看起来复杂实际只做了两件事径向小波变换取多尺度局部特征角向傅里叶分解取圆上谐波特征。旋转时图像在 θ 方向平移会让 F_mnk 的相位乘上 e^(jkα)但幅度不变所以取绝对值abs(coeff)就能获得旋转不变性。平移不变性靠预处理阶段把脸的中心搬到极坐标原点实现尺度不变性靠对径向坐标归一化实现。2.2 小波基函数与矩的离散化实现选择径向小波就像选择特征粒度的放大镜。不同基函数生成的矩向量分布完全不同直接影响最终识别率。下表给出我在 MATLAB 里用过的三种小波基基函数MATLAB名称波形特点适用场景Haarhaar分段常数有跳变快速原型、光照均匀的证件照Mexican Hatmexh平滑带通特性脸部轮廓和眼睛区域的分层描述Morletmorl振荡明显频域局部好纹理较多、有表情变化的人脸表面实际项目里我更倾向于 Mexican Hat原因是它对重采样噪声不敏感而且和小波矩的积分核配合时不需要额外做滤噪。Haar 虽然快但径向基在 r 轴上有阶跃会在极坐标网格的边界产生伪高峰。离散化时把积分替换成累加先对图像做极坐标重采样得到一个 Nr × Ntheta 的矩阵然后对每个 m、n、k 做加权求和。权重必须包含雅可比因子 r否则小波矩在原点附近会被过分放大。exp(−1jkθ) 的计算可以用 MATLAB 的复数广播完成不需要逐个像素循环。2.3 构造特征向量的尺度与维数选择特征向量不是把所有 m、n、k 的系数全塞进去就好。维度膨胀会让分类器的正则化非常难受。常见做法是限制尺度 m 不超过 3位移 n 不超过 2k 取 0 到 3这时特征个数为 4×3×4 48 维。在 ORL 这种 92×112 的人脸图上48 维已经能支撑 5 类以上的分类。如果你只区分两个人m 到 2、n 到 1 就够了。我一般把特征组织成行向量形式如下function Feat waveletMoments(polarIm, mMax, nMax, kMax) % waveletMoments 小波矩离散化计算 % 输入polarIm - 极坐标重采样后的图像尺寸 Nr x Ntheta % mMax, nMax, kMax - 尺度、位移、角频率上限 % 输出Feat - 行向量每段按 m-n-k 次序排列 [Nr, Nth] size(polarIm); r (0:Nr-1) / Nr; % 径向坐标归一化到 [0,1) theta (0:Nth-1) / Nth * 2 * pi; % 角向坐标 [R, TH] meshgrid(r, theta); % 生成坐标网格 polarIm polarIm; % 调整成 Nr x Nth 对齐 rWeight repmat(r(:), 1, Nth); % 雅可比权重 r Feat []; for m 0:mMax for n 0:nMax % 用 mexh 母小波生成径向基 psi 2^(m/2) * wavefun(mexh, 8); psi interp1(linspace(0, 1, length(psi)), psi, r, linear, 0); psi psi(:); for k 0:kMax % 角向傅里叶核与 theta 相关 basis repmat(psi, 1, Nth) .* exp(-1j * k * theta); coeff sum(sum(polarIm .* basis .* rWeight)); Feat [Feat, abs(coeff)]; % 取模消除旋转相位 end end end end这段代码的核心是先用wavefun生成母小波在连续轴上的近似值再用interp1采样到径向网格上。rWeight对应公式里的 r dr 权重abs(coeff)去掉复相位得到旋转不变特征。有一个容易错的地方Nth采样点如果太少高频角向分量会相互混叠建议Nth至少取 64。3. 在MATLAB中搭一条能跑通的小波矩特征提取管线光有核心公式还不够。我接触不少基于这个思路的项目问题大多出在特征提取前的人脸归一化和极坐标变换上。这一章给出一条能在普通电脑上跑通的管线并且每一步都留参数检查点。3.1 人脸检测与预处理从 RGB 到极坐标网格先读取图片用 MATLAB 自带的级联检测器定位人脸。读取后转为灰度截取人脸框还要把两只眼睛对齐到固定位置否则后续小波矩对左右脸的旋转变化会混入特征。最保险的预处理不做缩放直接以两眼连线作为水平参考围绕人脸中心裁剪一个正方形区域。function [gray, box] preprocessFace(imgPath) % 返回对齐后的灰度图和人脸框位置 detector vision.CascadeObjectDetector(FrontalFaceCART); img imread(imgPath); box step(detector, img); if isempty(box) error(未检测到人脸); end % 使用第一张脸 face imcrop(img, box(1,:)); gray rgb2gray(face); gray imresize(gray, [128, 128]); % 统一尺寸 end这里只做尺寸统一还不够。人脸检测给出的框是矩形但眼睛不一定水平。更严谨的做法是把左眼和右眼位置作为参照计算旋转变换。我一般会在项目说明里附一个alignFace.m脚本内部用fitgeotrans生成相似变换矩阵把两只眼睛映射到固定位置。检测框最好外扩 20%以免脸贴边时极坐标圆缺了一块影响后面的角向分解。3.2 极坐标变换与重采样参数得到 128×128 灰度图后下一步是把笛卡尔坐标网格映射到极坐标。这里不能直接调用cart2pol就算完还要用interp2把像素值搬到规则极坐标网格上否则后续矩阵运算是错位的。常见做法是建立两套网格一套是笛卡尔坐标的meshgrid另一套是径向采样点 r 和角向采样点 θ。function polarIm convertToPolar(gray, center, radius, Nr, Nth) % 将128x128灰度图转换为Nr x Nth的极坐标图 x 1:size(gray,2); y 1:size(gray,1); [xx, yy] meshgrid(x, y); % 生成极坐标采样点 r linspace(0.1, 1, Nr) * radius; % 跳开中心奇异点 theta linspace(0, 2*pi, Nth1); theta(end) []; % 去掉端点避免重复 [TH, R] meshgrid(theta, r); % 转换到笛卡尔坐标 px center(1) R .* cos(TH); py center(2) R .* sin(TH); % 双线性插值边界填充0 polarIm interp2(xx, yy, double(gray), px, py, linear, 0); end这里的r从 0.1 而不是 0 开始是为了避开光轴上的单像素点否则插值会在中心产生异常高值。Nth1去掉最后一个点是为了保证 0 和 2π 不会重复计入否则再做角向傅里叶时会产生直流偏置。这个点我排错排了整整一个下午最后用sum(abs(diff(polarIm,[],2)))检查各行边界才发现。3.3 验证平移、旋转、尺度不变性的参数表特征提取之后不要急着送分类器先做一个不变性自检把同一张脸旋转 30°、平移 5 像素、缩放 0.8 倍再分别提取小波矩看两两之间的欧氏距离。我常用下面这组参数配合自检检查项输入改动允许特征距离需要微调的参数旋转不变性顺时针旋转 30° 0.15kMax是否过高Nth是否足够平移不变性上下移动 5 像素 0.20center是否真的在质心检测框是否偏移尺度不变性缩放 0.8 倍 0.25半径radius是否包含整个脸如果旋转误差过大把角向采样Nth从 64 提到 128如果平移误差过大说明预处理阶段的人脸中心校准不稳定应该在imcrop前先做眼睛对齐。如果尺度误差大确认radius是根据人脸框一半宽度算出来的而不是固定 64。4. 识别引擎特征训练、分类与效果验证特征提取只是把每个人的脸转成向量。接下来需要做的是训练分类器和识别流程。在 MATLAB 里常见的选择是把小波矩特征放到支撑向量机或者 K 近邻里。不要一上来就上深度网络小样本任务中这些经典分类器往往更稳。4.1 小样本下的分类器选型我一般会把项目里的样本做划分每个人取 4 张做训练2 张做验证。当每人的样本数少于 6 时RBF 核 SVM 容易过拟合优先用线性 SVM 或者 KNN。KNN 对小波矩这种低维特征非常友好因为特征本身已经有旋转不变性距离计算稳定。但 KNN 需要保存全部训练向量预测时复杂度是 O(N)在门禁这种几千人的场景会慢。线性 SVM 保存的是支持向量输出只是符号函数速度快很多。实际项目里我偏好线性 SVM配合fitcsvm和predict。多分类问题用fitcecoc把二分类扩展成“一对一”或“一对多”。这个题目中的“算法源码项目说明”一般用于学生项目或算法验证所以可以放一个简单的一对一 ECOC 做说明训练时间和内存都可控。4.2 特征维数与交叉验证参数设置48 维小波矩在人脸库上并不是直接最好用。有的维度受噪声影响大需要先用主成分分析把维度压到 20 到 30。PCA 虽然会损失一点可解释性但能明显稳定 SVM 的间隔避免高维空间的退化。交叉验证我建议用cvpartition不要手动打乱样本否则会出现同一个人的不同照片同时落进训练集和验证集导致评估虚高。参数建议值说明mMax23超过 3 时小波基频率过高容易混入噪声nMax12代表径向位移太大特征维数爆炸kMax03角向分量0 是整体轮廓1~3 是边缘梯度PCA 保留贡献率0.95低于 0.9 丢失局部细节高于 0.98 噪声多CV 折数5样本少时用留一法4.3 一个可运行的训练识别脚本框架这里给出一个完整的脚本骨架可以直接放到项目根目录运行。它假设你在features.mat里已经存放了小波矩特征和对应标签。load(features.mat); % featMat: Nxd, labels: Nx1 rng(42); % 5折交叉验证 cv cvpartition(labels, KFold, 5); acc zeros(cv.NumTestSets, 1); for i 1:cv.NumTestSets trIdx cv.training(i); teIdx cv.test(i); % 先用训练集做PCA再把测试集映射过去 [coeff, ~, ~] pca(featMat(trIdx,:)); trX featMat(trIdx,:) * coeff(:,1:20); teX featMat(teIdx,:) * coeff(:,1:20); mdl fitcecoc(trX, labels(trIdx), ... Learners, templateSVM(KernelFunction,linear)); pred predict(mdl, teX); acc(i) sum(pred labels(teIdx)) / length(pred); end fprintf(平均识别率: %.2f%%\n, mean(acc)*100);这段代码的要点有三个pca的变换矩阵只由训练集计算不能在测试集上重新算templateSVM里明确用线性核避免 RBF 在小样本上过拟合fitcecoc自动处理多分类。如果你的标签是字符串而不是数字先做grp2idx转成索引。另外SVM 对特征尺度敏感小波矩各维度范围差异不大但如果做了多分辨率拼接建议先做zscore。5. 源码调试技巧三个容易翻车的细节很多项目说明只写了运行顺序没有写参数为什么这样给这是最让人头疼的。真正处理问题时以下三个点最容易被忽略也最能解释“为什么照着代码跑识别率总比别人低”。建议先在调试前设定一个简单基线把一张正面照旋转 90°要求识别结果不变选两个人各 5 张图要求训练准确率 100%。5.1 重采样导致的对齐错误极坐标变换中心如果不设在脸的质心而是设在裁剪框的几何中心那么同一张脸在旋转后重采样坐标系会发生偏移。最简单的检查方法是显示polarIm看脸上两只眼睛是否在极坐标图的同一水平线上。如果不是应该用center [xEyeMean, yEyeMean]而不是center size(gray)/2。5.2 特征维度过高时的降维策略小波矩特征的维数会随着nMax增大而线性上升。我见过有人把 m、n、k 全调到 4特征直接到 125 维而验证集只有 20 张SVM 变成无解问题。建议在写完特征函数后立刻打印特征维数size(featMat, 2)。如果大于 60就用 PCA 或 LASSO 筛特征。检查筛选后的特征矩阵是否出现全零列部分小波基在位移 n 超出径向范围时会返回全零。5.3 用旋转等价性验证源码是否正确一个最有效的验证手段是“旋转重建检查”把一张图旋转 45° 后再提取特征看它和原图的特征余弦相似度是否大于 0.95如果只有 0.7问题多半在角向采样不均匀。img1 preprocessFace(face1.jpg); img2 imrotate(img1, 45, bilinear, crop); p1 convertToPolar(img1, [64 64], 50, 64, 64); p2 convertToPolar(img2, [64 64], 50, 64, 64); f1 waveletMoments(p1, 2, 1, 2); f2 waveletMoments(p2, 2, 1, 2); cosSim dot(f1, f2) / (norm(f1) * norm(f2)); fprintf(旋转前后小波矩余弦相似度: %.4f\n, cosSim);如果相似度低于 0.9先检查imrotate的crop参数是否保持了图像尺寸再检查convertToPolar里的半径是否给得过大导致旋转后部分背景被拉入。另外小波矩的取模会丢失相位信息不同旋转角度的特征距离理论上不应当为零所以阈值不要期望等于 1。这个检查通过之后再接分类器做交叉验证得到的结果才值得写进项目说明。本文还有配套的精品资源点击获取