多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

MB_LBP与PCA结合的人脸识别算法原理及工程实现

MB_LBP与PCA结合的人脸识别算法原理及工程实现 简介一份基于MB_LBP与PCA算法的人脸识别研究PDF属于人脸识别方向的参考文献与专业指导资料适合模式识别、计算机视觉学习者及相关科研人员参考。内容围绕MB_LBP多块局部二值模式与PCA主成分分析结合的人脸识别方法展开详细介绍了MB_LBP如何改善原始LBP对噪声敏感、计算复杂度高的问题以及PCA对高维人脸特征进行降维并保留主要信息的原理同时涵盖了SVM分类识别的整体流程并在ORL人脸库上验证了相比传统PCA算法识别率提升13%以上的效果。压缩包内包含1个PDF文件整体大小约850KB轻量便于下载与阅读。该资源已有126人学习内容包括算法原理分析、实验对比结论与论文原文适用于人脸识别课程设计、论文撰写或算法对比实验等场景可供需要参考文献和专业指导的读者直接使用。1. 人脸识别资源拆解MB_LBPPCA这篇论文为什么值得复现拿到这篇 2016 年的《基于MB_LBP和PCA算法的人脸识别研究》PDF我先说结论这不是一篇赶时髦的深度学习文章而是一套完整的传统机器学习人脸识别方案算法链路足够清晰实验对比数据也有——MB_LBPPCA 相比传统 PCA在 ORL 人脸库上识别率提升了 13% 以上。对于正在做人脸识别课程设计、毕业设计或者工作中需要快速搭一套离线人脸识别原型的人来说这份资源可以直接当作算法选型和流程设计的参考底稿。它解决了什么问题两个一是原始 LBP 对噪声敏感、计算复杂度高二是 PCA 整体方法对局部特征描述能力弱。把 MB_LBP 的局部纹理提取能力和 PCA 的降维能力串起来再用 SVM 分类这套路放在今天依然是传统方法里很扎实的一条线。2. MB_LBP 算子原理从 LBP 的痛点说起多块局部二值模式做了什么2.1 原始 LBP 的三个局限以及 MB_LBP 的改进逻辑LBPLocal Binary Pattern最初是拿来描述纹理的后来被 Ahonen 引入人脸识别。它的核心操作很简单对图像中每个像素取它周围 3×3 邻域里的 8 个像素逐个和中心像素比灰度值大于中心像素记为 1否则记为 0然后把 8 个二进制位按顺序拼成一个十进制数作为该像素的 LBP 编码值。这样整张图就变成了一张 LBP 编码图再分块统计直方图就得到了可用于分类的特征向量。但原始 LBP 有几个明显的坑。第一它对单个像素点的灰度值极度敏感一旦图像里有噪点编码就会跳变特征不稳定。第二3×3 邻域半径固定感受野太小捕捉不到稍大尺度的纹理结构。第三直接把整张图的 LBP 编码收进分类器特征维度会非常高计算量大。MB_LBPMulti-Block LBP的改进思路是用“块”替代“像素”。原始 LBP 比较的是一个中心和周围单个像素的灰度关系MB_LBP 比较的是一个中心矩形区域和周围若干个矩形区域的平均灰度关系。也就是说先把图像划分为大小相等的矩形块对每个块求平均灰度再用类似 LBP 的方式把中心块的平均灰度和周围块的平均灰度做比较生成二进制编码。这样做的好处很明显块的平均灰度天然有抗噪声能力单个像素的异常值会被平均掉同时通过调整块的大小可以灵活控制特征提取的尺度。MB_LBP 的计算流程可以分成四步图像分块、块内平均灰度计算、中心块与邻域块比较生成二进制编码、编码值输出为新图像或直接进入直方图统计。分块尺寸是影响效果的关键参数块太小等于退化成原始 LBP噪声抑制效果丢失块太大又会让图像粒度化丢失细节特征。论文里也专门提到这个权衡后面我会细说你实际调参时怎么找平衡点。2.2 MB_LBP 的代码实现Python NumPy 从零写一遍网上很多库直接封装了 LBP但 MB_LBP 未必有现成接口自己实现并不难。下面是一段基于 NumPy 的 MB_LBP 算子实现适用于灰度图输入。import numpy as np import cv2 def mb_lbp(image, block_size3, radius1): MB_LBP: Multi-Block Local Binary Pattern :param image: 输入灰度图, shape(H, W), dtypeuint8 :param block_size: 每个分块的边长(像素), 必须是奇数 :param radius: 中心块到邻域块的间隔半径(以块为单位) :return: MB_LBP编码图, 与输入图像同尺寸 h, w image.shape # 边缘填充: 保证边界像素也能参与编码, 用reflect模式避免引入不存在的灰度 pad radius * block_size img_pad cv2.copyMakeBorder(image, pad, pad, pad, pad, cv2.BORDER_REFLECT) # 积分图: 快速计算任意矩形区域的平均灰度 integral cv2.integral(img_pad) def block_mean(y, x): # 以(y, x)为左上角, 计算block_size x block_size区域的平均灰度 y2 y block_size x2 x block_size area integral[y, x] integral[y2, x2] - integral[y, x2] - integral[y2, x] return area / (block_size * block_size) # 输出编码图 lbp_map np.zeros((h, w), dtypenp.uint8) # 8邻域偏移: 上, 右上, 右, 右下, 下, 左下, 左, 左上 offsets [(-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)] for i in range(h): for j in range(w): # 映射到填充后图像的坐标 cy pad i cx pad j center_mean block_mean(cy - block_size // 2, cx - block_size // 2) code 0 for k, (dy, dx) in enumerate(offsets): # 邻域块左上角坐标 ny cy dy * block_size - block_size // 2 nx cx dx * block_size - block_size // 2 nb_mean block_mean(ny, nx) if nb_mean center_mean: code | (1 k) lbp_map[i, j] code return lbp_map这段代码的核心逻辑是利用积分图把“求块内平均灰度”的耗时从 O(block_size²) 降到 O(1)不然每算一个像素都要遍历块内所有点图像一大就慢得没法看。block_size控制块的大小radius控制邻域块的采样间隔两者直接决定特征尺度。注意代码里用cv2.BORDER_REFLECT做边缘填充而不是补零原因是反射填充不会在图像边缘引入虚假的灰度突变编码更稳定。使用这段代码时block_size我一般从 3 开始试radius取 1。如果发现误识别多先调大block_size到 5 或 7观察编码图的纹理连续性——注意这里的“纹理连续性”是经验性判断块越大编码图越“糊”信息丢失也越明显。如果block_size超过 9在 ORL 这种 112×92 的小图上人脸细节基本就没了。3. PCA 降维工程平均脸、协方差矩阵与 SVD 的实操细节3.1 PCA 在人脸识别里的定位不是特征提取是维度压缩PCA 在论文里的角色很容易被误解很多人以为 PCA 是用来“找特征”的实际上它是用来“压维度”的。MB_LBP 输出的特征向量维度很高——假设一张 112×92 的人脸图分成 8×8 块每块的 LBP 直方图有 256 个桶拼接后的特征向量就是 8×8×256 16384 维。这么高的维度直接丢给 SVM不仅训练慢还容易过拟合因为 ORL 库总共才 400 张样本。PCA 的作用就是在保留主要方差的前提下把这 16384 维压到几十维或一两百维让 SVM 在一个低维、信息密度高的空间里找分类面。这就解释了为什么 MB_LBP 和 PCA 是互补的MB_LBP 负责把原始像素空间映射到局部纹理空间PCA 负责在纹理空间里做二次压缩。如果只用 PCA 不用 MB_LBPPCA 直接作用于像素它抓住的是全局灰度变化的模式对光照和姿态变化很敏感。如果只用 MB_LBP 不用 PCA特征维度太高分类器吃不下。两者的结合是有先后顺序的先 MB_LBP 后 PCA顺序不能反——你先 PCA 降维再做 LBP等于把纹理信息丢掉一半再提特征效果会大打折扣。PCA 的计算步骤论文里写得比较简略实际落地时按照论文里的四个步骤走计算平均脸、构造协方差矩阵、SVD 分解求特征向量、投影到特征空间。步骤看着简单但有一个非常关键的工程细节需要处理。3.2 高维协方差矩阵的爆炸问题小样本技巧怎么用ORL 人脸库每张图是 112×92 像素拉平成一维向量就是 10304 维。如果你直接按定义计算协方差矩阵那是一个 10304×10304 的矩阵内存占用 10304² × 8 字节 ≈ 850 MB普通机器直接卡死。但训练样本数 N 通常只有 200 张40 人 × 每人 5 张远小于特征维度。这里有一个经典的小样本技巧不直接算 10304×10304 的协方差矩阵而是先算 N×N 的格拉姆矩阵然后通过特征分解间接得到原协方差矩阵的特征向量。下面是在 MB_LBP 特征图基础上做 PCA 降维的完整代码X是训练样本的特征矩阵每行一个样本。def pca_fit(X, n_components): PCA降维, 使用小样本SVD技巧避免构造高维协方差矩阵 :param X: 训练样本矩阵, shape(n_samples, n_features), 每行一个样本 :param n_components: 保留的主成分个数 :return: 降维后的数据, 均值向量, 主成分矩阵 # 1. 中心化: 减去平均脸 mean_vec np.mean(X, axis0) X_centered X - mean_vec # 2. 小样本技巧: 计算NxN的协方差矩阵而不是dxd # X_centered是(N, d), 转置乘自己是(d, d), 直接用会爆炸 # 这里先算X_centered X_centered.T, 结果是(N, N), N远小于d gram X_centered X_centered.T # shape(N, N) # 3. 对格拉姆矩阵做特征分解 eigenvalues, eigenvectors np.linalg.eigh(gram) # 4. 按特征值降序排列 idx np.argsort(eigenvalues)[::-1] eigenvalues eigenvalues[idx] eigenvectors eigenvectors[:, idx] # 5. 转换回原始空间的特征向量: v_i X_centered.T u_i / sqrt(lambda_i) # 注意这里要除以特征值的平方根做归一化, 否则得到的是非单位特征向量 components [] for i in range(n_components): # 防止除零, 特征值小于1e-12的直接跳过 if eigenvalues[i] 1e-12: continue comp X_centered.T eigenvectors[:, i] / np.sqrt(eigenvalues[i]) components.append(comp) components np.array(components) # shape(n_components, d) # 6. 投影到特征空间 X_pca X_centered components.T # shape(N, n_components) return X_pca, mean_vec, components这段代码里最值得注意的就是第 2 步到第 5 步的小样本技巧。数学上的依据是如果 A 是 d×N 的矩阵d 是特征维度N 是样本数d NAᵀAd×d的非零特征值对应的特征向量可以通过 AAᵀN×N的特征向量变换得到。具体的变换公式是 v_i Aᵀu_i / sqrt(λ_i)其中 u_i 是 AAᵀ 的第 i 个特征向量λ_i 是对应特征值。这样就把原本 O(d²) 的内存和计算量降到了 O(N²)代价非常小。n_components的选取直接影响识别率。论文里没给具体值但从工程经验看ORL 库上一般保留 3080 个主成分效果比较好。保留太少会丢掉判别信息保留太多又把噪声和高频细节放进了特征空间。我的做法是做一个简单的循环实验从 10 开始每隔 10 试一组画一条“维度-识别率”曲线取曲线平台期的起点。这个习惯比拍脑袋定数字靠谱得多。4. MB_LBPPCA 融合流程从人脸图像到 SVM 分类的完整链路4.1 算法流程串联MB_LBP 编码图 → 分块直方图 → PCA 降维 → SVM 分类把整套流程串起来实际运行时主要经历以下阶段。输入一张人脸图像先转灰度图然后做尺寸归一化ORL 库原图是 112×92直接用它用前面实现的 MB_LBP 算子生成编码图把编码图划分成互不重叠的子块论文里常用的是 6×6 或 8×8 划分对每个子块统计 256 桶的直方图把各子块的直方图首尾拼接得到原始特征向量。这个特征向量还没法直接喂给分类器必须先过 PCA 降维。降维后的特征向量再输入到 SVM 分类器里训练和预测。这里有一个很容易踩的细节直方图拼接之前每个子块的直方图都需要做归一化。归一化有两种做法一种是把每个子块的直方图除以该子块的像素总数让每个子块的特征权重相等另一种是对整条拼接后的特征向量做 L2 归一化。至于该选哪种取决于你对局部特征的侧重——如果人脸在图像中的位置有偏移第一种更稳如果分块边缘有裁剪问题第二种更稳。我一般会两种都跑一遍对比把识别率更高的那个固定下来后续不再随意改动。SVM 的配置也不复杂论文里提到核函数有线性核、多项式核、径向基核RBF和二次神经网络核。实际测试下来在用 PCA 降维到几十维的场景下RBF 核的效果通常优于线性核因为降维后的特征分布不是线性的RBF 核可以把样本映射到更高维的空间去找分类面。RBF 核有两个参数需要调C误分类惩罚系数和gamma核函数宽度。C越大对训练样本的拟合越严格容易过拟合C越小越容忍错分但可能欠拟合。gamma控制单个样本的影响范围gamma太大模型复杂gamma太小分类面过于平滑。在 ORL 库这种 400 张的小样本场景下我一般用网格搜索在C ∈ {1, 10, 100}、gamma ∈ {0.001, 0.01, 0.1}里扫一遍。4.2 实验设置与参数表照着这份参数跑识别率可复现参数项推荐值说明输入图像尺寸112×92ORL 库原图分辨率MB_LBP block_size5 或 7块越小细节越多噪声抑制越弱MB_LBP radius1邻域块间隔增大可捕捉更大尺度纹理分块直方图网格6×6 或 8×8分块越多特征维度越高直方图桶数2568-bit 编码的完整取值空间PCA 保留维度4080需要扫参决定取识别率平台期SVM 核函数RBF降维后特征分布非线性时更稳SVM C / gamma10 / 0.01按网格搜索结果微调训练/测试划分每类取 5 张训练、5 张测试40 人 × 5 200 训练样本整个流程的代码骨架如下省略了前面已经实现的 MB_LBP 和 PCA 函数定义直接看数据流转。from sklearn.svm import SVC from sklearn.model_selection import train_test_split import numpy as np import cv2 import os def extract_mb_lbp_features(image, block_size5, radius1, grid(8, 8)): 提取单张图像的MB_LBP分块直方图特征 :return: 归一化后的特征向量 gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if len(image.shape) 3 else image lbp_map mb_lbp(gray, block_size, radius) h, w lbp_map.shape gh, gw grid block_h, block_w h // gh, w // gw hist_features [] for i in range(gh): for j in range(gw): block lbp_map[i*block_h:(i1)*block_h, j*block_w:(j1)*block_w] hist np.bincount(block.ravel(), minlength256) / block.size hist_features.extend(hist) return np.array(hist_features, dtypenp.float32) # 假设 train_images 是 (N, H, W) 的数组, train_labels 是 (N,) 的标签 # 第一步: 提取所有训练图像的MB_LBP直方图特征 X_raw np.array([extract_mb_lbp_features(img) for img in train_images]) print(f原始特征维度: {X_raw.shape[1]}) # 8*8*256 16384 # 第二步: PCA降维到60维 X_pca, mean_vec, components pca_fit(X_raw, n_components60) # 第三步: 训练SVM分类器 svm SVC(kernelrbf, C10, gamma0.01) svm.fit(X_pca, train_labels) # 第四步: 测试时按相同流程处理 # test_features extract_mb_lbp_features(test_img) # test_centered test_features - mean_vec # test_pca test_centered components.T # pred_label svm.predict(test_pca.reshape(1, -1))[0]这段代码的操作顺序要注意extract_mb_lbp_features里已经做了分块直方图归一化每个子块的直方图除以各自像素数保证不同子块在特征拼接时权重一致。PCA 拟合用的pca_fit返回了mean_vec和components测试时必须以训练集的均值和投影矩阵为准不能在测试集上重新计算 PCA——否则投影空间不一致训练和测试的数据分布就错位了。这是传统机器学习里一个很基本但很容易犯的错误尤其是在写完整流程时忙中出错。5. 避坑清单从 ORL 库到实际场景的五个典型问题5.1 识别率怎么都上不去先查直方图归一化这一步现象代码流程看起来完全正确但识别率一直在 80% 左右徘徊怎么调 SVM 参数都不见起色。原因分块直方图没有归一化或者归一化方式不对。如果直接用np.bincount得到频数而不除以块内像素总数不同分块的直方图量纲不一致人脸中眉毛、眼睛等纹理密集区域的特征值会异常偏大在 PCA 降维时主导方差导致关键判别信息被淹没。解决在每个子块统计完直方图后强制除以block.size让所有子块的特征都落在 [0, 1] 区间内。做完这步识别率通常能提升 35 个百分点。注意是整个流程固定一种归一化方式不要训练集用频数、测试集用归一化那样等价于特征空间不一致测试结果没有意义。5.2 MB_LBP 编码图糊成一片问题出在 block_size 过大现象把 MB_LBP 编码图可视化后发现整个人脸区域几乎是一个颜色纹理细节完全丢失识别率不升反降。原因block_size设得太大。当块尺寸超过人脸关键特征的尺度比如眼睛在 112×92 的图像上约 10×6 像素块平均灰度会把眼睛和周围的皮肤灰度混在一起纹理对比度被大幅削弱。论文里专门提到分块过大会使图像粒度化破坏原有特征就是这个问题。解决把block_size从 7 回退到 3 或 5然后看编码图的纹理是否清晰可辨。判断标准很直观如果编码图中眼睛、鼻梁、嘴唇的轮廓还能隐约看出来说明尺度合适。如果连轮廓都看不出来说明块太大了。我在小图上一般固定block_size5起步只在图像分辨率高时才考虑更大的块。5.3 PCA 协方差矩阵内存爆炸直接用高维定义是死路现象代码在计算协方差矩阵时报内存错误MemoryError或者在np.linalg.eig时卡住不动。原因直接在原始特征维度比如 16384 维上构造协方差矩阵矩阵大小是 16384×16384乘以 8 字节大约 2.1 GB普通电脑直接撑爆。另一个隐藏问题是np.linalg.eig对非对称矩阵的求解很容易不收敛导致程序挂起。解决用前面代码里的小样本 SVD 技巧先算 N×N 的格拉姆矩阵再变换回特征空间。同时用np.linalg.eigh代替np.linalg.eig因为格拉姆矩阵是对称矩阵用专用的 Hermitian 求解器更快也更稳。这一步处理好了200 个训练样本的情况下内存占用只有 200² × 8 320 KB可以忽略不计。5.4 训练集和测试集的数据预处理方式不一致结果全乱现象训练时识别率很高测试时结果惨不忍睹甚至预测标签大面积错乱。原因训练集做了均值归一化、PCA 投影但测试时直接用原始特征预测或者测试时又重新计算了均值和投影矩阵。这种不一致会让测试特征落在完全不同的坐标系里SVM 的分类边界完全失效。解决把训练阶段得到的mean_vec和components持久化保存下来比如用np.save存成.npy文件测试时严格加载训练期保存的这两个对象依次做中心化和投影。我习惯把整个预处理流程封装成一个类fit方法只允许在训练集上调用一次transform方法在训练和测试时统一调用从机制上杜绝不一致。5.5 SVM 核函数选错低维线性核在降维特征上表现差现象换了 RBF 核后识别率反而比线性核低或者两个核的表现差异不明显。原因PCA 降维后的特征空间不是线性的理论上需要非线性核来刻画分类面。但如果你在gamma取值很小比如 0.0001的情况下用 RBF 核它实际上退化成接近线性核看不出优势。另一种情况是数据本身经过 MB_LBP 后已经有较好的线性可分性此时刻意用复杂的核函数反而会过拟合。解决先用线性核跑一遍拿到基线识别率然后用 RBF 核做网格搜索重点扫gamma在 0.001 到 1 之间的量级。如果 RBF 在最优参数下仍然不比线性核好多少说明当前特征已经足够线性可分这时可以回退到线性核训练速度更快模型也更稳定。这个判断过程比盲目选核函数靠谱。6. 验证方法交叉验证、参数敏感性分析以及我自己踩过的坑整套流程跑通后最容易被忽略的是“你怎么知道结果不是偶然的”。ORL 人脸库总共 40 个人每人 10 张样本量不大训练集和测试集的划分方式对识别率影响非常大。固定用每个人的前 5 张训练、后 5 张测试得到的结果不一定能代表算法的真实水平。我一般会做一个五折交叉验证把每个人的 10 张图随机分成 5 份每次拿 4 份训练、1 份测试轮转 5 次取平均识别率作为最终指标。这样得到的数字统计意义更强写报告时也更有说服力。交叉验证的代价是训练时间翻了 5 倍但 ORL 库这么小的样本量整套流程几分钟内就能跑完这点开销完全可以接受。参数敏感性分析也值得做尤其是block_size和 PCA 保留维度这两个参数。以block_size为例在 ORL 上分别取 3、5、7、9 跑一遍识别率你会发现一个明显的倒 U 形曲线3 太小噪声抑制不足9 太大细节丢失。曲线峰值对应的block_size就是当前数据集上的最优值。PCA 保留维度同样如此从 20 到 100 每隔 10 取一组识别率会先快速上升到某个维度后进入平台期平台期起点的维度就是最佳选择。这个过程看起来机械但它能帮你理解算法在数据上的真实行为比盲目相信论文里的参数可靠得多。最后说一个我自己踩过的坑。有次为了让识别率更高我在 ORL 上同时调了block_size、PCA 维度和 SVM 的C/gamma结果每次只改一个参数看不出明显问题几个参数一起改之后识别率突然掉了很多。排查了很久才发现问题不在参数本身而是我把block_size从 5 改到 7 之后分块直方图的网格还是 8×8但 MB_LBP 编码图的实际纹理分布已经变了原来的归一化系数不再匹配。从那以后我每次跑实验都强制走一遍控制变量流程固定其他参数只动一个目标参数记录识别率变化交叉验证结果出来后再统一调整下一项。这个习惯让我的实验记录清晰了很多也避免了大量无效的参数组合尝试。希望帮到你。本文还有配套的精品资源点击获取
返回列表