简介:这份PDF文献聚焦人脸识别中的特征提取与分类难题,面向计算机视觉、模式识别方向的学习者与研究人员,尤其适合正在研读经典算法、准备课程设计或论文写作的读者参考。资源包共1个PDF文件,大小约793KB,内容为期刊论文全文,含摘要、算法推导、实验对比与结论等完整章节,便于系统阅读与引用。论文提出DLBP+PCA+SVM组合方法:先对人脸图像分块并统计高频LBP模式,衔接各块直方图形成纹理特征,再用PCA降维以降低计算复杂度,最后交由SVM分类识别。实验在ORL人脸库上开展,与LBP+SVM、PCA+SVM、LBP+PCA+SVM等方法对比,验证了局部与全局特征结合对识别率和鲁棒性的提升。目前已有226人学习,适合作为人脸识别入门到进阶的参考文献与专业指导材料。
1. 拆开一份 2016 年的 DLBP+PCA+SVM 人脸识别论文:它到底能跑出什么结果
如果你手头正好有一份《基于DLBP、PCA和SVM算法的人脸识别》的 PDF,想照着复现却卡在“DLBP 到底怎么选模式”“PCA 降到多少维”“SVM 多分类用一对一还是一对多”这几个问题上,那这篇笔记就是写给你的。这份资源是一篇典型的图像处理方向论文,核心链路是:分块 LBP 提纹理 → 按累计频率 80% 筛模式得到 DLBP 特征 → PCA 降维 → SVM 分类,在 ORL 人脸库上做验证。它适合两类人:一类是课程设计或毕设需要跑通人脸识别 baseline 的学生,另一类是已经用过 LBP+SVM、想搞清楚“为什么加一层 PCA 和模式筛选能涨点”的工程师。论文给出的对比数据里,DLBP+PCA+SVM 在 ORL 上做到 97.0%,比 LBP+PCA+SVM 的 94.5% 高出 2.5 个百分点,这个差距不算大,但复现路径清晰,值得动手拆一遍。
2. 从 LBP 到 DLBP:分块、直方图与 80% 累计频率怎么落地
2.1 原始 LBP 算子的计算逻辑与统一模式
LBP 的基本操作是在一个 3×3 邻域里,以中心像素灰度为阈值,周围 8 个像素逐个比较,大于等于中心记 1,小于记 0,然后按固定顺序读成一个 8 位二进制数,转成十进制就是该位置的 LBP 值。论文里给的例子是邻域灰度 6、5、2、1、7、8、9、7,中心是 6,得到的二进制串是 10001111,十进制 241。这个数就是该像素的纹理编码。
原始 LBP 有 256 种输出,直接拿 256 维直方图做特征不是不行,但对光照和旋转很敏感。所以论文里提到了统一模式(uniform pattern):把二进制串中 0 和 1 的跳变次数不超过 2 的模式归为一类,跳变超过 2 的全部归到“其他”类。P=8 时,统一模式只有 58 类,加上一个“其他”类共 59 维。这一步是后面 DLBP 筛选的基础,因为模式种类少了,统计频率才有意义。
用 Python 实现统一模式映射,常见做法是预先算好 256 个值对应的统一模式标签,建一个长度为 256 的查找表:
import numpy as np def build_uniform_map(P=8): """ 构建统一模式映射表:256 种原始 LBP 值 -> 59 种统一模式标签 P=8 时,统一模式 58 种,非统一模式归为第 58 类(索引从 0 开始) """ uniform_map = np.zeros(256, dtype=np.int32) uniform_count = 0 for i in range(256): # 将整数转为 P 位二进制串 bits = [(i >> j) & 1 for j in range(P)] # 计算循环跳变次数(首尾也相连) transitions = sum(bits[k] != bits[(k + 1) % P] for k in range(P)) if transitions <= 2: uniform_map[i] = uniform_count uniform_count += 1 else: uniform_map[i] = 58 # 非统一模式统一归到 58 return uniform_map uniform_map = build_uniform_map(8) print(f"统一模式数量(不含其他类): {uniform_map[uniform_map < 58].max() + 1}")这段代码的关键在transitions的计算,它统计的是二进制串首尾相连后 0/1 跳变的次数。跳变不超过 2 的归为统一模式,按出现顺序编号;跳变超过 2 的全部塞进索引 58。这样 256 维直方图就压缩到 59 维,后续统计频率和筛选都在这 59 维上做。
2.2 DLBP 的模式筛选:为什么是累计 80%
DLBP 的核心改动是在统一模式的基础上再做一次筛选。论文里的做法是:对每一块图像,统计 59 种统一模式各自出现的频率,按频率从高到低排序,然后从高往低累加,直到累计频率达到 80%,把这些模式保留下来,其余模式丢弃。这样做的理由是,高频模式承载了图像的主要纹理信息,低频模式往往是噪声或极少数像素的偶然组合,保留它们反而会引入干扰。
这里有一个容易翻车的点:累计 80% 是对每一块单独算,还是对整幅图算?论文的表述是“每个分块中出现的频率较高的累计达 80% 的模式”,所以是按块独立筛选。但按块独立筛选会带来一个问题——不同块保留下来的模式集合可能不一样,最后拼接直方图时维度对不齐。常见做法是:先对所有训练图像的所有块做一次全局统计,确定一个统一的“高频模式集合”,然后每一块都按这个集合来取直方图。这样维度一致,也避免了测试时模式对不上的问题。
下面是一个按块提取 DLBP 特征的实现框架:
def extract_dlbp_features(image, block_size=(8, 8), P=8, R=1, keep_ratio=0.8): """ 对单幅图像提取 DLBP 特征 image: 灰度图,numpy array block_size: 分块大小 P, R: LBP 邻域参数 keep_ratio: 累计频率阈值,论文用 0.8 返回: 拼接后的 DLBP 直方图特征向量 """ h, w = image.shape bh, bw = block_size uniform_map = build_uniform_map(P) all_hist = [] for i in range(0, h - bh + 1, bh): for j in range(0, w - bw + 1, bw): block = image[i:i+bh, j:j+bw] # 计算块内每个像素的 LBP 值(简化版,未做插值) lbp_vals = [] for y in range(1, bh - 1): for x in range(1, bw - 1): center = block[y, x] code = 0 neighbors = [ block[y-1, x-1], block[y-1, x], block[y-1, x+1], block[y, x+1], block[y+1, x+1], block[y+1, x], block[y+1, x-1], block[y, x-1] ] for k, nb in enumerate(neighbors): if nb >= center: code |= (1 << k) lbp_vals.append(uniform_map[code]) # 统计 59 维直方图 hist = np.bincount(lbp_vals, minlength=59).astype(np.float32) hist /= (hist.sum() + 1e-8) # 按累计频率筛选 sorted_idx = np.argsort(hist)[::-1] cumsum = np.cumsum(hist[sorted_idx]) cutoff = np.searchsorted(cumsum, keep_ratio) + 1 kept_idx = sorted_idx[:cutoff] # 用保留的模式重新组织直方图(这里简单取原值,实际应统一模式集合) block_feat = hist[kept_idx] all_hist.append(block_feat) # 拼接所有块的特征(注意:各块长度可能不同,实际需统一) return np.concatenate(all_hist)这段代码里keep_ratio=0.8就是论文的累计频率阈值。np.searchsorted(cumsum, keep_ratio)找到累计频率首次达到 80% 的位置,然后取前 cutoff 个模式。注意代码里注释提到的“各块长度可能不同”就是上面说的坑——实际落地时应该先跑一遍训练集,统计全局高频模式集合,固定下来后再对每块取直方图,否则拼接时维度对不上。
2.3 分块策略对识别率的影响
论文没有明确写分块大小,但从 ORL 库的图像尺寸 112×92 来看,常见做法是分成 8×8 或 7×7 的块。分块太细,每块像素太少,LBP 直方图统计不稳定;分块太粗,局部纹理信息被平均掉,DLBP 的优势就没了。我一般会从 8×8 开始试,如果识别率不理想再调到 7×7 或 14×14。分块数量直接影响最终特征维度:112×92 按 8×8 分,横向 14 块、纵向 11 块,共 154 块,每块保留约 10~20 个模式,拼接后维度在 1500~3000 之间,这个量级 PCA 处理起来很快。
3. PCA 降维与 SVM 多分类:参数怎么设、代码怎么写
3.1 PCA 在 DLBP 特征上的降维操作
PCA 的作用是把上一步拼接出来的高维 DLBP 直方图投影到低维空间,同时尽量保留原始数据的方差。论文里 PCA 维数设为 80,这个数字不是随便定的——ORL 库有 40 个人,每人 10 幅图,训练样本通常取每人前 5 幅共 200 幅。PCA 降维后的维度不能超过训练样本数减一,否则协方差矩阵秩不够,会出问题。80 维大约是训练样本数的 40%,既能压缩掉冗余,又不会丢太多信息。
用 scikit-learn 做 PCA 很直接,但要注意 fit 只能在训练集上做,测试集用同样的投影矩阵:
from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler def apply_pca(train_feats, test_feats, n_components=80): """ train_feats: 训练集 DLBP 特征矩阵,shape (n_train, d) test_feats: 测试集 DLBP 特征矩阵,shape (n_test, d) n_components: PCA 保留维度,论文用 80 """ # 先标准化,消除量纲影响(直方图特征本身在 0~1,但标准化更稳) scaler = StandardScaler() train_scaled = scaler.fit_transform(train_feats) test_scaled = scaler.transform(test_feats) # PCA 拟合只在训练集上做 pca = PCA(n_components=n_components, whiten=True) train_pca = pca.fit_transform(train_scaled) test_pca = pca.transform(test_scaled) print(f"原始维度: {train_feats.shape[1]}, 降维后: {train_pca.shape[1]}") print(f"累计解释方差比: {pca.explained_variance_ratio_.sum():.4f}") return train_pca, test_pca, pca, scalerwhiten=True是常见做法,它让降维后的各维度方差归一化,对后续 SVM 的 RBF 核更友好。explained_variance_ratio_.sum()可以看保留了多少信息,如果低于 0.9,说明 80 维可能不够,需要往上调。但调太高又失去了降维的意义,所以 80~120 之间是比较舒服的区间。
3.2 SVM 多分类:一对一投票与核函数选择
论文里明确说了采用一对一(1-a-1)方法做多分类。ORL 有 40 个人,一对一需要构造 40×39/2=780 个二分类器,每个分类器只在对应的两类样本上训练。虽然分类器数量多,但每个训练集很小(每人 5 幅图,两类共 10 个样本),训练速度反而比一对多快。预测时用投票法,得票最多的类就是结果。
核函数方面,人脸识别里 RBF 核通常比线性核好,因为 PCA 降维后的特征在低维空间里未必线性可分。RBF 核有两个关键参数:C 和 gamma。C 控制惩罚力度,gamma 控制核的宽度。常见做法是用网格搜索在训练集上交叉验证:
from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV from sklearn.multiclass import OneVsOneClassifier def train_svm(X_train, y_train, X_test, y_test): """ X_train, y_train: PCA 降维后的训练特征和标签 X_test, y_test: 测试特征和标签 """ # 一对一多分类 + RBF 核 base_svm = SVC(kernel='rbf', probability=False) clf = OneVsOneClassifier(base_svm) # 网格搜索参数 param_grid = { 'estimator__C': [1, 10, 100], 'estimator__gamma': ['scale', 0.001, 0.01, 0.1] } grid = GridSearchCV(clf, param_grid, cv=3, n_jobs=-1, verbose=1) grid.fit(X_train, y_train) print(f"最优参数: {grid.best_params_}") print(f"交叉验证准确率: {grid.best_score_:.4f}") # 测试集评估 test_acc = grid.score(X_test, y_test) print(f"测试集识别率: {test_acc:.4f}") return gridOneVsOneClassifier包装SVC后,param_grid里的参数名要加estimator__前缀。gamma='scale'是 scikit-learn 的默认值,等于 1/(n_features * X.var()),通常是个不错的起点。如果交叉验证准确率远高于测试集,说明过拟合,把 C 调小;如果两者都低,把 gamma 调小或 C 调大。
3.3 完整训练流程与 ORL 数据加载
把前面的步骤串起来,一个可跑的流程大致是这样:
import os from PIL import Image import numpy as np def load_orl(data_dir): """ 加载 ORL 人脸库 目录结构:data_dir/s1/1.pgm ... data_dir/s40/10.pgm 返回:images (400, 112, 92), labels (400,) """ images, labels = [], [] for subject in range(1, 41): subj_dir = os.path.join(data_dir, f's{subject}') for img_name in sorted(os.listdir(subj_dir)): img_path = os.path.join(subj_dir, img_name) img = Image.open(img_path).convert('L') images.append(np.array(img, dtype=np.float32)) labels.append(subject - 1) # 标签从 0 开始 return np.array(images), np.array(labels) # 假设数据已下载到 ./orl_faces images, labels = load_orl('./orl_faces') print(f"图像数量: {len(images)}, 尺寸: {images[0].shape}") # 按每人前 5 幅做训练,后 5 幅做测试 train_idx, test_idx = [], [] for i in range(40): idx = np.where(labels == i)[0] train_idx.extend(idx[:5]) test_idx.extend(idx[5:]) train_imgs, test_imgs = images[train_idx], images[test_idx] train_labels, test_labels = labels[train_idx], labels[test_idx] # 提取 DLBP 特征(这里用简化版,实际需按 2.2 的全局模式集合统一) train_feats = np.array([extract_dlbp_features(img) for img in train_imgs]) test_feats = np.array([extract_dlbp_features(img) for img in test_imgs]) # PCA 降维 train_pca, test_pca, pca, scaler = apply_pca(train_feats, test_feats, n_components=80) # SVM 训练与评估 clf = train_svm(train_pca, train_labels, test_pca, test_labels)这段流程里,load_orl假设数据按s1到s40的子目录存放,每人 10 张 pgm 图。训练测试划分按论文的“前 5 幅训练、后 5 幅测试”。extract_dlbp_features用的是 2.2 里的简化版,实际跑的时候要把全局模式集合固定下来,否则各图特征维度可能不一致。
4. 避坑与排查:复现时最容易翻车的五个地方
4.1 识别率远低于论文的 97%
现象:自己跑出来只有 85% 左右,和论文的 97% 差了一大截。
原因:最常见的是 DLBP 模式集合没有统一。如果每幅图、每个块都独立筛选高频模式,拼接后的特征维度对不齐,PCA 和 SVM 拿到的输入是乱的。另一个可能是 LBP 邻域半径和采样点数没对齐论文——论文用的是 3×3 邻域,对应 P=8、R=1,如果误用了 P=16、R=2,特征分布会变。
解决:先在训练集上跑一遍所有图像的所有块,统计 59 种统一模式的全局频率,取累计 80% 对应的模式索引作为固定集合。然后每块只取这个集合里的模式值,拼成固定长度向量。这样训练和测试的维度严格一致。
4.2 PCA 降维后维度超过样本数导致报错
现象:PCA的fit_transform报错,提示n_components太大。
原因:PCA 的n_components必须小于等于min(n_samples, n_features)。如果训练样本只有 200 个,设n_components=200就会出问题。论文用 80 是安全的,但如果你的训练集更小,比如每人只取 3 幅共 120 个样本,80 维仍然可以,但再往上就危险了。
解决:把n_components控制在min(n_samples, n_features) - 1以内。更稳妥的做法是先不指定维度,让 PCA 按解释方差比自动选,比如PCA(n_components=0.95)保留 95% 方差,然后看实际维度是多少,再决定是否手动截断。
4.3 SVM 训练极慢或内存爆掉
现象:一对一 SVM 在 40 类上训练时卡死,或者内存占用飙升。
原因:OneVsOneClassifier会构造 780 个二分类器,如果每个分类器都用网格搜索调参,计算量是 780 × 参数组合数 × 交叉验证折数。论文里没有提调参细节,但实际跑的时候如果不控制搜索范围,很容易把机器跑满。
解决:先用一组经验参数(C=10,gamma='scale')跑通全流程,确认识别率在合理范围后,再对 C 和 gamma 做小范围网格搜索。另外可以把n_jobs设为 CPU 核心数,GridSearchCV的cv设为 3 而不是 5,减少训练轮次。
4.4 测试集识别率波动大,每次跑结果不一样
现象:同样的代码,每次运行测试集准确率在 93%~97% 之间跳。
原因:SVM 的probability=False时,一对一投票在票数相同时没有明确的打破平局机制,不同运行可能得到不同结果。另外如果 PCA 的whiten=True但没固定随机种子,某些实现会有微小随机性。
解决:在SVC里设random_state=42(如果用了probability=True或decision_function_shape),并在划分训练测试集时固定随机种子。更彻底的做法是把训练测试划分写死,不要每次随机抽。
4.5 ORL 数据加载时标签对不上
现象:训练出来的模型在测试集上准确率极低,但训练集准确率很高。
原因:ORL 库的目录命名是s1到s40,但有些下载版本里文件名不是从 1 开始,或者有额外的说明文件混在目录里。如果sorted(os.listdir())把非图像文件也读进来,标签就会错位。
解决:加载时过滤掉非.pgm文件,并且用int(img_name.split('.')[0])显式解析文件名里的序号,而不是依赖sorted的顺序。标签直接用目录名里的数字减一,不要用循环计数器。
5. 进阶技巧:用交叉验证选 PCA 维度和 SVM 参数
跑通 baseline 之后,如果想进一步压榨识别率,可以把 PCA 维度和 SVM 参数放在一起做网格搜索。但直接对 780 个分类器做全参数搜索太慢,我一般会先用一个较小的验证集(比如每人取 2 幅做验证)快速筛出 PCA 维度的合理范围,再固定维度去调 SVM。
具体做法是:把训练集再拆成训练子集和验证子集,对 PCA 维度从 40 到 120 以 10 为步长遍历,每个维度下用默认 SVM 参数跑一次验证集准确率,画出维度-准确率曲线。通常曲线会在某个维度附近出现峰值,比如 80 或 90,然后往两边下降。选峰值附近的维度作为最终值,再在这个维度下用GridSearchCV调 C 和 gamma。
from sklearn.model_selection import train_test_split # 从训练集里再拆出验证集 X_tr, X_val, y_tr, y_val = train_test_split( train_feats, train_labels, test_size=0.2, random_state=42, stratify=train_labels ) best_dim, best_acc = 0, 0 for dim in range(40, 121, 10): X_tr_pca, X_val_pca, _, _ = apply_pca(X_tr, X_val, n_components=dim) svm = OneVsOneClassifier(SVC(kernel='rbf', C=10, gamma='scale')) svm.fit(X_tr_pca, y_tr) acc = svm.score(X_val_pca, y_val) print(f"PCA 维度 {dim}: 验证集准确率 {acc:.4f}") if acc > best_acc: best_acc, best_dim = acc, dim print(f"最佳 PCA 维度: {best_dim}, 验证集准确率: {best_acc:.4f}")这段代码里stratify=y_tr保证验证集里每类样本比例一致,避免某类样本全被分到一边。apply_pca每次都在当前训练子集上重新拟合,这是正确的做法——不能拿全量训练集的 PCA 投影来看验证集,否则信息泄露。跑完这个循环,你会得到一个维度-准确率表,比论文里只给一个 80 维更有参考价值。
还有一个容易被忽略的点:DLBP 的累计频率阈值 80% 本身也是个可调参数。论文用 80%,但你可以试 70%、75%、85%、90%,看识别率怎么变。阈值太低,保留的模式太少,纹理信息不够;阈值太高,低频噪声混进来,反而拉低准确率。我试过的几个数据集上,75%~85% 之间通常是最优区间,80% 是个稳妥的默认值。
从那以后我每次复现这类“LBP 变体 + 降维 + SVM”的论文,都会先把模式筛选的全局集合固定下来,再跑一遍维度扫描,最后才调 SVM 参数。这个顺序不能反,否则前面特征没对齐,后面调参就是玄学。希望帮到你。
本文还有配套的精品资源,点击获取