ARTICLE DETAIL

资讯详情

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

AR人脸数据集实战:从BMP预处理到识别评测

AR人脸数据集实战:从BMP预处理到识别评测 简介AR数据库彩色裁剪版是一份面向人脸识别与稀疏表示研究方向的数据资源适用于需要彩色而非灰度人脸图像的实验场景。压缩包共包含5201个文件其中2600张bmp格式裁剪后彩色人脸图像可直接用于特征提取、分类器训练和光照处理研究2600个txt文件记录了对应图像的裁剪区域位置方便复现论文中的预处理步骤另有1个m脚本辅助数据读取与批次处理整体约115.3MB。数据源自论文“PCA versus LDA”预处理后的公开结果图像已按人、序号命名便于按身份划分训练集和测试集。目前已有510人学习下载适合高校研究人员、研究生及竞赛选手用于算法对比、毕业设计或论文实验复现。1. AR Database 不是增强现实人脸识别基准测试里的老牌彩色 BMP 数据集第一次接触到 AR Database 的人十个有九个以为是某个增强现实项目。实际上它是人脸识别领域最经典的公开评测集之一全称叫 AR Face Database里面的“AR”是采集者所在机构名的缩写。这个数据集以彩色人脸图像为主常见发布形态就是裁剪后的彩色图像加 bmp 格式做算法验证时比 JPG 省心得多——BMP 是无损存储不会在读取环节引入额外的压缩噪声。这个数据集适合三类人一是刚入门人脸识别、手头没有真实业务数据的学生和工程师拿它跑通从预处理到识别的全流程二是做算法选型对比的团队需要在固定协议下比较 LBPH、ArcFace 这类人脸识别算法的效果三是做人脸识别门禁机、考勤机等设备端方案的开发者想在小样本、受控光照条件下验证算法底线的。它的规模不大但包含表情、光照、遮挡三类变化比很多人自拍攒出来的数据集有说服力得多。我下面按“看懂数据 → 对齐裁剪 → 训练评估 → 排错进阶”的顺序把整个落地路径走一遍。2. 先看懂 AR 数据集的目录和命名规则再谈怎么用拿到 AR Database 的第一步不是直接开始写训练代码而是把它的目录结构、命名规则和图像格式彻底搞清楚。因为网上流传的版本很多有的带原始大图有的是已经裁剪好的小图有的混了灰度图不统一梳理就直接喂给模型后面排查问题会非常痛苦。2.1 两个 Session 拍摄的人脸样本训练集测试集划分的天然依据AR Database 的采集对象大约有 126 人包含男性和女性样本每人在不同时间点拍了两个 Session两轮拍摄两轮之间通常相隔两周左右。这意味着同一个人的同一种表情在数据集里至少有两张图。图像内容覆盖了表情变化微笑、愤怒、尖叫等、光照方向变化左侧光、右侧光、双侧光和遮挡变化墨镜、围巾。文件名一般由性别前缀、人员编号和图像序号组成看起来像“M-001-1.bmp”或“F-012-2.bmp”这样的形态。M 代表 MaleF 代表 Female中间的数字是人员 ID最后一个数字是这一轮拍摄里的第几张样本。这里的关键点在于文件夹里不会直接告诉你哪张属于 Session 1、哪张属于 Session 2你必须通过文件名里的序号区间或者拍摄子目录来推断。我在实际使用中一般会先写一个索引脚本把每个人的每张图映射到“人员 ID、性别、批次、图像类别”这四个字段上。这样后面做训练集测试集划分时就可以按人员 ID 而不是按文件夹路径来操作不会因为目录层次不一致而出错。如果拿到的版本没有官方说明文件就只能靠文件名和图像内容人工比对一小批样本把映射关系定下来。2.2 BMP 格式与人脸识别管线为什么裁剪后的 BMP 更稳AR Database 里最主流的发布格式就是 bmp尤其是“裁剪后的彩色图像”这一版本。BMP 格式最大特点是像素数据直接按行列存放无压缩或者只有简单的 RLE 压缩读取时不会因为压缩算法产生额外噪声。这对做人脸识别评估很重要你希望看到的是算法本身的能力差异而不是 JPG 压缩带来的块效应差异。彩色 BMP 在 OpenCV 里读出来是 BGR 顺序而不是大多数深度学习框架习惯的 RGB 顺序。很多人第一次跑实验时显示图像一切正常但送到网络里准确率奇低排查半天发现是把通道顺序搞反了。另一个容易被忽略的点是 BMP 的位深——AR Database 的常见版本是 24 位真彩色但偶尔会遇到 8 位灰度伪装成 BMP 的文件直接用 cv2.imread 读出来是三通道的假彩色图后续统计会出问题。所以我在处理 AR 数据集时第一步永远是写一个文件清单脚本先把所有 BMP 的尺寸、通道数、位深扫出来确认整个数据集没有混入异常文件。这一步花不了几分钟但能帮你避开后面大量的“为什么这张图读出来是黑的”这类玄学问题。2.3 把 AR 数据集整理成标准列表目录扫描脚本整理数据集时我习惯先用一个 Python 脚本扫描全部 BMP 文件输出一个 CSV 索引包含文件路径、人员 ID、性别、批次等信息。下面这段代码是基于文件名规则解析的通用写法规则不匹配时会打印出来方便我对照实际文件名调整。import os import re import csv import cv2 root_dir ./AR_Database entries [] for dirpath, dirnames, filenames in os.walk(root_dir): for fname in filenames: if not fname.lower().endswith(.bmp): continue full_path os.path.join(dirpath, fname) # 解析文件名例如 M-001-1.bmp match re.match(r^([MF])-(\d)-(\d)\.bmp$, fname, re.IGNORECASE) if not match: print(f[skip] 无法解析的文件名: {full_path}) continue gender, person_id, sample_seq match.groups() # 读取图像头信息确认尺寸与通道不必加载全部像素 probe cv2.imread(full_path, cv2.IMREAD_UNCHANGED) if probe is None: print(f[error] 读取失败: {full_path}) continue h, w probe.shape[:2] channels 1 if probe.ndim 2 else probe.shape[2] entries.append({ path: full_path, gender: gender.upper(), person_id: int(person_id), sample_seq: int(sample_seq), width: w, height: h, channels: channels, }) with open(ar_index.csv, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnames[ path, gender, person_id, sample_seq, width, height, channels]) writer.writeheader() writer.writerows(entries) print(f共扫描到 {len(entries)} 张 BMP 图像)这段代码里的核心参数是正则表达式^([MF])-(\d)-(\d)\.bmp$它假设文件名符合“性别-人员编号-样本序号”的模式。如果你的数据集命名规则不同改这个正则即可。我用cv2.imread加IMREAD_UNCHANGED读取的好处是能保留原始通道数这样灰度 BMP 不会被强制转成三通道扫描结果更真实。width、height、channels 三个字段写进 CSV 后可以用 pandas 或 Excel 快速检查是否存在尺寸不一致的样本。3. 裁剪与对齐768×576 原图到 128×128 彩色 BMP 的流水线AR Database 的原始版本有人脸范围较大、分辨率较高的图也有已经裁剪好的小图。很多人拿到的是后者但即便如此裁剪边界是否统一、人脸是否居中、两眼是否水平仍然直接影响后续识别效果。我一般会做一套统一的对齐流程把所有人脸图像转换到同一坐标系下。3.1 眼睛坐标对齐的几何计算参数写在代码里人脸识别的预处理里最稳定的参考点就是两只眼睛。无论是用 dlib 的 68 点检测器还是 OpenCV 的 Haar 级联检测器先取左右眼中心坐标再做仿射变换把双眼放到目标图像的水平中线上同时缩放双眼间距到固定像素值。import cv2 import numpy as np def align_face(image, left_eye, right_eye, output_size(128, 128), eye_distance56): # 计算双眼中心、距离和角度 eyes_center ((left_eye[0] right_eye[0]) * 0.5, (left_eye[1] right_eye[1]) * 0.5) dx right_eye[0] - left_eye[0] dy right_eye[1] - left_eye[1] current_eye_dist np.sqrt(dx * dx dy * dy) scale eye_distance / current_eye_dist angle np.degrees(np.arctan2(dy, dx)) # 构建相似变换矩阵 m cv2.getRotationMatrix2D(eyes_center, angle, scale) # 调整平移让双眼中心平移到目标图像中心 tx output_size[0] * 0.5 - eyes_center[0] ty output_size[1] * 0.5 - eye_distance * 0.5 - eyes_center[1] m[0, 2] tx m[1, 2] ty aligned cv2.warpAffine(image, m, output_size, flagscv2.INTER_CUBIC) return aligned img cv2.imread(./AR_Database/M-001-1.bmp) left_eye (120, 180) right_eye (260, 170) out align_face(img, left_eye, right_eye, output_size(128, 128), eye_distance56) cv2.imwrite(./aligned/M-001-1.bmp, out)这里的参数要按实际情况调。eye_distance 设 56 意味着目标图像里两眼中心间距约 56 像素在 128×128 的图像中双眼大概占画幅的一半这是比较常见的设置。如果输出尺寸改成 112×112MobileFaceNet 的常见输入eye_distance 往往要下调到 44 左右。angle 的计算用的是np.arctan2保证角度在 -180 到 180 度范围内不会因为象限问题产生 90 度的错误旋转。getRotationMatrix2D里的旋转中心和缩放顺序是有讲究的——先绕双眼中心旋转缩放再平移这样不会导致人脸位置偏移。3.2 彩色图像转 BMP 的格式约束与 OpenCV 读写对齐完成后保存时要注意 OpenCV 的imwrite保存 BMP 默认是 24 位彩色如果你的输入是灰度图保存出来的 BMP 仍然是单通道不会自动转成三通道。反过来如果你把单通道图用cv2.imread读进来再保存写 BMP 前必须用cvtColor转成 BGR否则后续加载到深度学习框架里会出现通道数不一致的问题。# 如果对齐输出是三通道 BGR直接保存 if out.ndim 3: cv2.imwrite(./aligned/M-001-1.bmp, out) else: # 单通道灰度图转 BGR保证与主流人脸识别模型的输入一致 out_bgr cv2.cvtColor(out, cv2.COLOR_GRAY2BGR) cv2.imwrite(./aligned/M-001-1.bmp, out_bgr)实际项目里最容易翻车的点不是保存格式而是读取时默认行为。cv2.imread对 BMP 文件不会做 EXIF 旋转也不会做颜色管理所以在 AR 数据集上反而是优点——每张图的行为都可预测。另外我建议所有中间结果统一保存为 BMP不要转成 JPG因为 JPG 压缩会轻微改变像素值这种改变在人眼看来无关紧要但在计算特征距离时可能让同一个人不同照片的余弦相似度降低 0.5 到 1 个百分点。3.3 评估协议按 Session 划分还是按遮挡物划分AR Database 最值钱的资产是它自带拍摄协议两个 Session 意味着你可以做跨时间泛化测试遮挡墨镜、围巾和光照变化意味着你可以做鲁棒性专项测试。常见的做法有两种。第一种是 Session 划分Session 1 当训练集Session 2 当测试集检验算法对时间跨度的容忍度第二种是遮挡划分用无遮挡样本训练用墨镜和围巾样本测试专门看遮挡鲁棒性。import csv from sklearn.model_selection import train_test_split with open(ar_index.csv, r, encodingutf-8) as f: reader csv.DictReader(f) rows list(reader) # 按姓名 ID 分训练和测试保证同一个人不会同时出现在两端 person_ids list(set(r[person_id] for r in rows)) train_ids, test_ids train_test_split( person_ids, test_size0.3, random_state42) train_rows [r for r in rows if r[person_id] in train_ids] test_rows [r for r in rows if r[person_id] in test_ids] print(f训练样本: {len(train_rows)}, 测试样本: {len(test_rows)})这里最需要注意的是 train_test_split 作用在人员 ID 上而不是作用在图像行上。如果把图像行直接切分同一个人不同 Session 的照片会被切进训练集和测试集形成数据泄露评测结果会虚高。random_state 固定成 42 是为了让实验可复现——AR 数据集本身不大不同随机种子可能带来几个百分点的波动不固定种子你很难分清是算法进步还是抽样运气。4. 在 AR 数据集上跑通人脸识别从 LBPH 到人脸识别算法预处理管线跑通之后接下来的问题是在这个数据集上到底用什么算法。AR 数据集规模小深度模型容易过拟合但它又是彩色 BMP 格式直接喂卷积网络也没有障碍。我建议先跑经典方法确认基线再上深度特征。4.1 传统基线LBPH 128×128 灰度图LBPHLocal Binary Pattern Histogram是 OpenCV 自带的传统人脸识别方法适合做基线。它不需要 GPU几分钟就能出结果而且对光照变化有一定容忍度。在跑 LBPH 之前要把彩色 BMP 转灰度并做直方图均衡化否则光照方向的差异会盖过身份特征。import cv2 import numpy as np from sklearn.preprocessing import LabelEncoder def load_gray(path): img cv2.imread(path, cv2.IMREAD_GRAYSCALE) img cv2.equalizeHist(img) # 直方图均衡化压制光照影响 return img X_train, y_train [], [] for r in train_rows: X_train.append(load_gray(r[path])) y_train.append(r[person_id]) recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.train(X_train, np.array(y_train, dtypenp.int32)) X_test, y_test [], [] for r in test_rows: X_test.append(load_gray(r[path])) y_test.append(r[person_id]) correct 0 for img, true_id in zip(X_test, y_test): pred_id, confidence recognizer.predict(img) if pred_id true_id: correct 1 print(fLBPH 准确率: {correct / len(X_test):.4f})LBPH 的 predict 返回一个整数标签和一个 confidence 值但 confidence 在不同版本里尺度不统一不能把它当概率看最多用来做阈值过滤。这里的 LabelEncoder 我并没有真的调用因为 AR 数据集的 person_id 本身就是从 1 开始的整数可以直接当标签用。如果你的数据集 ID 不连续比如只选了部分人做实验那就需要先用 LabelEncoder 把原始 ID 映射到连续整数空间否则 LBPH 内部会报“标签不连续”的错误。4.2 深度模型ResNet 特征 ArcFace 损失的小样本替代方案在 AR 这种小数据集上从头训练一个深度人脸识别模型不太现实常见的做法是使用 ImageNet 或大规模人脸数据集上预训练好的骨干网络把 AR 数据作为 finetune 或纯测试集。import torch import torch.nn as nn import torchvision.models as models class FaceEmbedding(nn.Module): def __init__(self, embedding_dim128): super().__init__() backbone models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) # 去掉最后的全连接分类层只保留特征提取部分 self.features nn.Sequential(*list(backbone.children())[:-1]) self.fc nn.Linear(512, embedding_dim) def forward(self, x): x self.features(x) # 输出 [B, 512, 1, 1] x torch.flatten(x, 1) return self.fc(x) model FaceEmbedding(embedding_dim128)这里有两个参数很关键。embedding_dim 决定了特征向量的维度128 维是常见选择召回和入库速度都够用如果做大规模检索可以降到 64 维。backbone 我用 ResNet18 而不是 ResNet50因为 AR 数据集样本量小大网络在 finetune 时更容易过拟合而 ResNet18 在 CPU 和边缘设备如人脸识别门禁机里的嵌入式板卡上也能实时跑。如果数据量允许把 fc 层去掉、直接输出 pooling 后的 512 维特征也行但 128 维的向量在后续做余弦相似度计算时更节省存储。损失函数上小样本场景不推荐直接用 Softmax。ArcFace 这类加角度间隔的损失函数能拉大类间距离但实现起来要处理 margin 和 scale 两个超参。如果只是做评测最务实的做法是加载一个在大规模数据上预训练好的 ArcFace 权重直接用它的特征提取部分AR 数据只做评估不做训练这样可以避免小样本过拟合问题。4.3 结果验证rank-1、rank-5 与分项指标人脸识别评测不能只看一个总准确率。AR 数据集包含表情、光照、遮挡三类变化必须分开算指标。把测试样本按 sample_seq 分类分别统计无遮挡、墨镜遮挡、围巾遮挡、光照变化这几组的准确率。下面这段代码展示了计算 rank-1 和 rank-5 的基本逻辑。from scipy.spatial.distance import cosine def evaluate_rank(embeds_gallery, labels_gallery, embeds_probe, labels_probe, rank5): correct_1, correct_5 0, 0 for emb_p, lab_p in zip(embeds_probe, labels_probe): sims [1 - cosine(emb_p, emb_g) for emb_g in embeds_gallery] top_indices np.argsort(sims)[::-1][:rank] if lab_p in [labels_gallery[i] for i in top_indices]: correct_5 1 if lab_p labels_gallery[top_indices[0]]: correct_1 1 return correct_1 / len(labels_probe), correct_5 / len(labels_probe) rank1, rank5 evaluate_rank(gallery_embeds, gallery_labels, probe_embeds, probe_labels) print(frank-1: {rank1:.4f}, rank-5: {rank5:.4f})这里的 gallery 和 probe 概念要讲清楚gallery 是已知人员的人脸特征库probe 是要查询的身份未知样本。在实际的门禁机场景里gallery 就是底库里录入的人脸probe 是每次刷脸抓拍的图像。代码里的[1 - cosine(emb_p, emb_g)]把余弦距离转成相似度距离越近相似度越大。用scipy.spatial.distance.cosine而不是自己写np.dot的原因是它内部处理了分母为 0 的边界情况避免向量全零时除零报错。5. 避坑AR Database 使用中的 5 个翻车现场所有在 AR 数据集上跑过的人几乎都在这几个坑里栽过跟头。我按“现象 → 原因 → 解决”的方式整理出来照着排查能省下大量无意义的调试时间。5.1 BMP 读出来是 None文件是坏的现象cv2.imread返回 None文件明明存在且后辍是 .bmp但就是读不出来。原因常见的有三种。一是文件后缀是 .bmp但实际内容是 JPG 或 PNG可能是之前有人批量重命名过。二是 BMP 文件头损坏数据流里混入了 HTTP 文本或额外字节。三是路径中带有中文或特殊字符Windows 下 OpenCV 的老版本无法正确解析非 ASCII 路径。第三种在下载的数据集整理到带空格、中文名目录下时特别常见。解决先把所有文件读一遍验证完整性用imread失败时看文件头的前两个字节BMP 以BM开头JPG 以FF D8开头。路径问题则需要用cv2.imdecode从文件字节流解码。import cv2 import numpy as np def robust_imread(path): with open(path, rb) as f: data np.frombuffer(f.read(), dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) if img is None: raise ValueError(f无法解析文件头: {path}) return imgcv2.imdecode会跳过文件头部直接从内存数据解析图像天然规避了路径编码问题。如果imdecode也返回 None那基本可以断定文件数据本身有问题直接标记出来从索引里剔除。5.2 对齐后人脸位置偏移检测框飘了现象部分图像对齐后人脸不在 128×128 画布中央甚至只露出半边脸。原因AR 数据集里戴墨镜的样本用 Haar 级联检测眼睛时经常把墨镜边框误检成眼睛或者干脆检测不到眼睛。围巾遮挡的样本则可能让嘴巴附近的特征点严重偏移连带影响眼睛坐标插值。解决检查每张图的眼睛检测置信度把置信度低于阈值的样本挑出来人工标注眼睛坐标。AR 数据集的图像是受控采集的遮挡位置相对固定不需要每张图都人工标只需针对墨镜和围巾两类样本建立一份坐标修正表其他样本直接用自动检测结果即可。这个人工修正过程在 100 人左右的数据集上大约需要半小时但对最终识别率的提升非常明显。5.3 同一个人两个 Session 混进同一批测试集rank-1 虚高现象测试准确率高达 99%但换了一批人做验证时掉到 70%明显不正常。原因划分训练测试集时直接按文件名随机切分没有按人员 ID 隔离。同一个人的 Session 1 照片进了训练集Session 2 照片进了测试集模型记住了这个人而不是学会了区分不同的人。AR 数据集每个 Session 的样本数相同这种随机切分特别容易踩雷。解决严格按 person_id 做 group split确保一个人员的所有图像只出现在训练集或者只出现在测试集。判断协议是否正确的简单办法是打印训练集和测试集的 person_id 集合确认交集为空。5.4 bmp-jpg-bmp 重编码让纹理变化老模型识别率骤降现象同一批图像从网上下载的版本跑出来的准确率比自己转换的低 2~3 个百分点。原因很多发布版本为了方便分发把原版 BMP 压成了 JPG使用者为了得到 bmp 格式又转回 BMP。JPG 压缩产生的块效应已经永久写入像素值再转回 BMP 也无法恢复等于在数据里叠加了一层噪声。LBPH 这类对局部纹理敏感的传统方法受影响最大。解决下载数据集后先检查文件头确认 BMP 是原生版本还是二次转换版本。一个实用的检测方法是随机挑几张纯色背景区域计算相邻像素差值的分布如果差值有明显的 8×8 分块特征说明经历过 JPG 压缩。这类数据用于深度学习预训练微调问题不大但用于传统方法和严格的算法对比时必须换源。5.5 裁剪边界留太多背景模型学到的不是人脸现象在 AR 上训练的模型拿到真实门禁机抓拍图上准确率很差但训练集内部指标很高。原因部分版本的 AR 数据集人脸裁剪得比较松图像里包含衣服、头发甚至环境背景。模型很容易学到背景颜色分布来区分不同人因为 AR 数据集的采集背景相对固定没有引入背景多样性。一旦换到真实场景背景背景特征失效识别率崩塌。解决检查所有裁剪样本的一致性计算人脸区域的面积占比。如果背景占比过高统一压缩检测框按人脸 bounding box 外扩 20% 到 30% 重新裁剪。AR 数据集的图像是正面人脸为主重新裁剪不会丢失关键信息。处理完后再重新对齐到 128×128确认背景在整张图中的比例处于合理范围。6. 进阶把 AR 评测脚本做成可复现的测试基线当你要用 AR 数据集长期做算法对比时最忌讳的是每次实验都临时改脚本。我习惯的做法是把整条评测链路固化成一个 pipeline输入是一批 BMP 图像输出是一份带分项指标的报告。这个 pipeline 至少包含四个阶段文件扫描与校验、人脸对齐与裁剪、特征提取、分组评测。每个阶段的输出中间结果要落盘比如对齐后的图像、提取好的特征向量 npy 文件。这样后面换模型时不需要重新做预处理只重新跑特征提取和评测两步。特征向量落盘时记得同时保存对应的 person_id 和 sample_seq 元数据否则评测阶段无法按遮挡和光照子集分组统计。分组评测时我一般会输出三个数字全量 rank-1、无遮挡子集 rank-1、遮挡子集 rank-1。三者之间的差值就是这套算法在 AR 数据集上的鲁棒性基线。如果一个新算法全量提升了但遮挡子集下降那说明它牺牲了遮挡鲁棒性在门禁机这类真实场景里未必是可靠的进步。固定随机种子、保存预处理中间结果、分项报告三管齐下AR 数据集才能真正成为你反复使用的测试基准而不是每次实验都重新处理的玩具。这是我踩了无数次坑之后养成的习惯希望帮到你。本文还有配套的精品资源点击获取
返回列表