
简介这是一份面向Android开发者的入门级人脸识别演示工程定位为借助FaceSdkV1.9.027快速搭建人脸检测、特征提取与身份匹配的移动端样例。资源包共1758个文件、约133.58MB包含Android工程源码、Java调用示例、xml/json配置、gradle构建文件以及aar、so、bin、model等SDK库与预训练模型同时附有可直接安装运行的apk便于对照代码了解人脸识别链路在真实设备上的落地方式。目前已有270人学习下载适合希望掌握Android端人脸SDK接入、相机权限处理、实时检测与性能优化的初学者或中级开发者。通过阅读DEMO中的工程结构和API调用可以快速迁移FaceSdk能力到门禁、支付验证、头像匹配等场景并减少自行从零实现算法的时间成本。1. 人脸识别DEMO几十张样本就能跑通的离线人脸识别闭环拿到这个“人脸识别DEMO”源码包的时候我第一反应是又一个套壳项目结果拆完发现它走的是一条完全离线、不依赖GPU、不依赖云端API的路子。整个工程从摄像头取流、人脸检测、样本采集、模型训练到实时返回识别结果六个环节串成了闭环。最关键的体验是每个人只注册二十来张照片训练几十秒之后对着摄像头就能准确区分“这个人是谁”和“这个人不在库里”。适合正在学计算机视觉的开发者、做课程设计需要完整人脸识别流程的学生以及想在本地快速搭一个识别原型的入门者。它不追求高精度胜在链路完整清楚每段代码都看得懂、改得动。2. 技术选型与工程结构一套不用GPU也能跑的人脸识别方案2.1 为什么选OpenCV加LBPH而不是深度学习模型我拆项目时最先想明白的是作者为什么选这条技术路线。人脸识别DEMO这类资源最常见的实现路径有三条一是用OpenCV自带Haar级联检测加LBPH局部二值模式直方图识别纯CPU计算二是MTCNN检测加FaceNet提取特征向量需要下载上百兆的模型文件推理时最好有GPU三是直接调云端人脸识别API把图片传上去返回结果。这个项目选第一条路核心原因是它要把“可复现”放在第一位。LBPH把一张脸转成局部二值模式直方图本质是统计学特征不需要训练神经网络也就不依赖CUDA、TensorFlow这些重型环境。你在一台只有集显的笔记本上打开摄像头识别一帧的耗时在毫秒级完全感觉不到卡顿。对于Demo类项目来说能跑通、能看明白原理比精度翻倍更重要。三条技术路线的对比我整理了一张表方便你判断什么场景该换方案方案依赖复杂度CPU可跑识别精度适合场景OpenCV Haar加LBPH低是中等教学Demo、离线原型MTCNN加FaceNet高勉强高对精度有要求的本地应用云端API低是最高联网且不介意外传图片2.2 项目目录结构与模块边界工程结构是标准的“分层但不大折腾”风格没有上框架全部用函数模块隔离这对二次开发特别友好。完整目录如下face_demo/ ├── app.py # 程序入口负责解析启动参数调度采集/训练/识别 ├── detector.py # 人脸检测模块封装haar级联检测 ├── trainer.py # 模型训练模块封装LBPH训练与保存 ├── recognizer.py # 识别比对模块封装predict与阈值判断 ├── utils.py # 路径处理、图片预处理等杂项 ├── requirements.txt # 第三方依赖清单 ├── config.ini # 阈值、尺寸、摄像头索引等可调参数 ├── haarcascade_frontalface_default.xml # 人脸检测级联模型文件 └── data/ ├── samples/ # 按人员编号分目录存放原始样本照片 │ ├── 001/ │ ├── 002/ │ └── ... └── model/ └── trainer.yml # 训练产出的LBPH模型文件每个模块的边界非常清楚detector只干检测这一件事返回值是坐标框trainer只管读取样本目录、训练、落盘模型recognizer负责加载模型并返回“label加置信度”的识别结果app.py把所有模块串起来。我做这类Demo的习惯是绝不把检测和识别写进同一个函数里不然改一个地方就要连坐另外两处。这里有个值得注意的设计样本目录直接用人员编号命名而不是姓名。这么做一方面绕开了中文路径在OpenCV读取时的编码问题另一方面把label到姓名的映射留给上层自己管理模块保持纯粹。后面对接SQLite或JSON人员库时只动app.py就够了算法层完全不需要感知。2.3 依赖清单与版本建议requirements.txt里只有两个硬依赖opencv-python和numpy。opencv-python中需要确认安装的是完整版因为LBPH识别器在opencv-contrib-python里普通opencv-python不带cv2.face这个子模块。这是一个高频踩坑点后面避坑章节会专门展开。opencv-python4.8.0.74 numpy1.24.3如果看到ImportError: cv2.face缺失直接把opencv-python换成opencv-contrib-python即可API完全一致。Python环境我建议3.8到3.10之间太新的版本有时会遇到预编译轮子没跟上导致OpenCV装不上。这些细节看起来琐碎但照着做能省下大概半小时的折腾时间。3. 核心模块拆解从摄像头取流到身份打印的完整链路3.1 模块调用关系与数据流动整个系统跑起来后的数据流向是这样的app.py拿到摄像头的一帧BGR图像先交给detector做灰度化并检测人脸位置得到一组矩形坐标然后用这个坐标把人脸区域从灰度图里裁出来resize成统一尺寸接着交给recognizer的predict方法得到label和置信度最后app.py把label映射回姓名在画面上画框并显示。采集模式下则相反app.py每检测到一张脸就把对应的ROI区域保存到samples目录。训练阶段不走摄像头trainer扫描samples目录下所有子目录把每张图片读进来resize到统一尺寸目录名作为label全部塞给LBPH模型的train方法。这个过程完全离线没有任何网络请求。3.2 检测模块Haar级联的真正用法detector的代码很短但参数选择直接影响后面所有环节。看一下核心实现import cv2 cascade cv2.CascadeClassifier(haarcascade_frontalface_default.xml) def detect_face(image): # 统一转灰度级联检测器只接受单通道输入 gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(64, 64) ) return gray, facesdetectMultiScale这四个参数是调出来的经验值。scaleFactor控制滑动窗口每次缩小的比例1.1表示每次缩小10%数值越小检测越精细但耗时翻倍minNeighbors是每个候选框被保留所需的最小邻居数调大能减少误检但容易漏掉侧脸minSize直接过滤掉小于64×64的区域过滤掉远处模糊的小脸省下大量计算时间。我一般会保持scaleFactor在1.1到1.2之间低于1.1时一帧要扫好几轮在低配机器上掉帧明显。如果检测不到人脸优先怀疑的不是代码而是这张图本身光线太暗、侧脸角度超过45度、人脸在画面里占比太小。Haar级联对正脸和均匀光照有很强的假设摄像头正对、光线充足是最基本的采集姿势。3.3 训练模块LBPH是怎么记住一张脸的trainer做的事情本质上就是把“某个人有哪些脸”压缩成一个统计模型。LBPH的原理是把图片划分成若干小格子在每个格子里计算局部二值模式直方图然后把所有格子的直方图拼接起来作为这张脸的数值签名。训练就是把这个签名和label绑定。import os import cv2 import numpy as np def train_from_dir(samples_dir, model_path, target_size(128, 128)): images, labels [], [] for label_name in os.listdir(samples_dir): label int(label_name) person_dir os.path.join(samples_dir, label_name) for file_name in os.listdir(person_dir): img cv2.imread(os.path.join(person_dir, file_name), cv2.IMREAD_GRAYSCALE) if img is None: continue img cv2.resize(img, target_size) images.append(img) labels.append(label) recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.train(images, np.array(labels)) recognizer.write(model_path)我把训练尺寸固定成128×128原因是LBPH的特征提取不依赖原始分辨率统一尺寸反而让直方图更可比对。太小会丢纹理细节太大则把光照噪声也学进去了。另外注意train方法要求labels是numpy数组且和images一一对应我在代码里先收集成列表再统一转换避免中途被Python的类库隐式转换坑到。这个模块有个隐藏设计点samples目录下的每个子目录代表一个人目录名就是label。训练时直接拿目录名做int转换所以目录名必须能转成整数否则训练会抛ValueError。这条约定在项目文档里写着但实际操作中很多人会忽略导致训练时突然报错。3.4 识别模块与实时主循环识别是整个链路里最需要打磨的地方因为LBPH的predict返回值在语义上有反直觉的地方置信度数值越小代表越相似而不是越大越像。很多人第一次写都会把阈值判断写反把陌生人当成库里的人放进来。import cv2 recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(data/model/trainer.yml) def predict_face(gray_face, threshold50.0): label, confidence recognizer.predict(gray_face) if confidence threshold: return label, confidence return -1, confidence这里的threshold就是整个系统最核心的一个旋钮。数值越小认定“这个人我得认识他”的门槛越高陌生人会被更坚决地挡在外面但自己人稍微换个角度也可能被误杀数值越大自己人更容易被认出来但陌生人混进来的概率也随之上升。50是从几十次测试里取的一个中间值实际部署时要根据摄像头位置和光照微调。实时主循环把前面所有模块串起来同时做了两件容易被忽略的事一是每一帧都重新检测但只有当置信度稳定低于阈值时才更新界面显示的名字防止名字在几帧之间来回跳动二是按q退出、按s截图保存方便后续扩充样本。cap cv2.VideoCapture(0) while True: ok, frame cap.read() if not ok: break gray, faces detect_face(frame) for (x, y, w, h) in faces: roi gray[y:y h, x:x w] roi cv2.resize(roi, (128, 128)) label, confidence predict_face(roi, threshold50.0) if label ! -1: name id_to_name.get(label, fID:{label}) color (0, 255, 0) else: name unknown color (0, 0, 255) cv2.rectangle(frame, (x, y), (x w, y h), color, 2) cv2.putText(frame, f{name} {confidence:.1f}, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) cv2.imshow(face_demo, frame) if cv2.waitKey(1) 0xFF ord(q): breakid_to_name这个映射在Demo里用JSON文件存映射关系形如{001: 张三}。这一层放在主循环里而不是识别模块里是我有意为之的模块解耦算法层永远只跟整数label打交道业务层才关心label背后是谁。改成数据库存储时只需要把id_to_name的加载来源换掉识别逻辑一行不动。4. 跑通项目环境准备、启动命令与关键配置参数4.1 环境搭建三步走把项目跑起来的第一步是建一个干净的Python环境。我拿Python 3.9的venv做演示这是个人偏好因为版本足够稳定OpenCV的轮子覆盖最全。tar -zxvf face_demo.tar.gz cd face_demo python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt激活虚拟环境后执行pip install如果下载速度慢可以临时换国内镜像源加一行-U参数pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple依赖装好后验证一下OpenCV是否带人脸模块python -c import cv2; print(cv2.__version__); print(hasattr(cv2, face))如果最后一行输出True说明环境没问题。如果输出False说明装的是精简版OpenCV需要先卸载再装opencv-contrib-python这个坑我在避坑章节里细说。4.2 样本采集与首次训练流程环境就绪后需要先给系统“介绍”一下它要认识的人。整个流程分三步采集样本、训练模型、启动识别。这个项目用三个子命令区分模式主循环里通过sys.argv判断当前处于哪个阶段。第一步采集某个人的人脸样本参数是人员编号python app.py --mode collect --id 001执行后摄像头会打开画面上出现绿色框时说明检测到人脸程序会自动把框内区域保存到data/samples/001目录下。建议保持正脸对着镜头缓慢左右转头让系统记录多角度连续拍20到30张后按q退出。第二步训练模型python app.py --mode train这一步会扫描data/samples下所有编号目录把每个人对应的一组图片交给LBPH训练顺利的话几秒内就会提示模型已保存到data/model/trainer.yml。训练日志会打印每个人的样本数量看到样本数是0的编号说明采集阶段出了问题要回去补样本再重新训练。第三步启动实时识别python app.py --mode run摄像头画面会实时显示每个检测到的人脸框旁边标注着对应的姓名和置信度。置信度在50以内显示绿色代表库里的人超过50显示红色unknown代表陌生人。如果识别失败率偏高优先调低threshold而不是重新训练。4.3 config.ini里的关键参数语义config.ini里的参数不建议一次改多个我的经验是每次只动一个变量然后观察效果。核心参数只有四个参数默认值作用调大后果调小后果scaleFactor1.1检测窗口缩放步长检测快但易漏检检测慢但更精确minNeighbors5候选框邻居阈值误检减少、漏检增多误检增多、漏检减少minSize64人脸最小尺寸忽略小目标检测远处小脸threshold50识别判定置信度阈值陌生人更易混入自己人更易被拒调整的先后顺序有讲究先调检测相关参数解决“框不住脸”的问题再调识别阈值解决“认错人”的问题。检测都不稳就去调阈值等于地基没打好就精装修。这里还有一条调试技巧拿一张静态照片代替摄像头来调试。在run模式下临时把VideoCapture的入参从0换成图片路径能稳定复现检测或识别的问题不用一直对着摄像头做鬼脸。我当时做这个Demo的时候靠这个技巧排查了一个困扰很久的环境光变化问题。5. 避坑指南光照、阈值与摄像头权限的5个典型问题5.1 摄像头直接黑屏或程序崩溃现象启动run模式后OpenCV窗口弹不出来程序卡住几秒后报错或者画面是纯黑。原因这条百分之八十是摄像头权限问题系统没有授权当前终端或IDE访问摄像头。另外VideoCapture(0)写死了默认摄像头索引笔记本如果有外接摄像头或者前置摄像头被占用索引0可能对应一个不存在的设备。解决先加一个设备检测import cv2 cap cv2.VideoCapture(0) if not cap.isOpened(): print(camera open failed) exit(1)如果确认是权限在macOS或Linux的终端首次运行时会弹授权窗口允许即可。如果是索引问题把0改成1再试或者打印一下有效索引列表。我自己的习惯是在utils里写一个find_camera函数自动遍历0到4找到第一个能打开的索引。5.2 cv2.face模块导入失败现象import cv2成功但代码跑到cv2.face.LBPHFaceRecognizer_create()时报AttributeError: module cv2 has no attribute face。原因装错包了。官方现在把opencv-python和opencv-contrib-python分成两个发行包前者只包含基础图像处理模块人脸识别这种contrib里才有的扩展API不在其中。解决卸载重装不要试图两个包共存会互相覆盖文件pip uninstall opencv-python pip install opencv-contrib-python装完后重新执行hasattr(cv2, face)验证。这条在Windows上尤其常见因为很多教程让新手直接pip install opencv-python等用到人脸识别模块才翻车。5.3 识别置信度判断写反现象训练正常、检测正常但陌生人能被认成库里的人自己人反而显示unknown。原因这是一个经典的LBPH语义坑。在Sklearn和多数机器学习库里confidence通常越大越相似但OpenCV的LBPH.predict返回的距离值恰恰相反数值越小表示与训练样本越接近。从网上复制代码时如果不注意这个差异很容易把判断条件写成if confidence threshold导致语义完全颠倒。解决用一张绝对陌生人的照片做基准测试记录置信度再拿一张训练样本做基准测试两次数值对比一下就明白了。通常陌生人距离在80到120之间训练样本在20到40之间threshold取50就能拉开差距。5.4 训练样本数量不均匀导致识别偏向现象两个人A和B都注册了但每次识别都把A认成B或者某个人永远识别不出来。原因LBPH本质是统计模型样本量直接决定特征分布的稳定性。如果A注册了30张训练图而B只注册了8张模型对B的统计描述远不如A可靠识别自然偏向样本多的一方。另外光照差异过大也会让特征分布偏移。解决采集阶段把样本数控制在20到30张且尽量均匀训练日志里打印的样本数低于15的编号要重点补采。训练前对图片做一次直方图均衡化能明显降低光照带来的特征漂移。这个预处理我通常加在trainer的read和resize之间一行cv2.equalizeHist就能显著改善效果。5.5 识别偶发跳变名字在几个ID之间反复横跳现象人坐在摄像头前不动画面上的名字在001和002之间来回切换有时还闪unknown。原因单帧识别结果不稳定。人脸角度轻微变化、眨眼、光线抖动都可能导致某一帧的特征距离恰好越过阈值predict返回的就是一个瞬时值没有时间维度上的平滑。解决在app.py里做一个简单的帧级投票维护最近10帧的识别结果列表只有同一个label连续出现超过6次才切换显示名字。实现不复杂但对体验的提升非常明显history deque(maxlen10) def stable_name(label, confidence): history.append(label if confidence 50 else -1) if history.count(label) 6: return label return last_stable_label这个处理思路也可以迁移到后续接深度学习模型时使用它解决的是工程问题而不是算法问题任何模型都会遇到输出抖动。6. 二次开发人脸识别Demo的三个改造方向与验证方法6.1 方向一加一个可视化阈值调参工具我在调试时发现每次改threshold都要重新启动程序很繁琐于是做了一个滑动条窗口挂在run模式旁边。OpenCV自带cv2.createTrackbar不需要引入任何GUI框架cv2.createTrackbar(threshold, face_demo, 50, 100, lambda x: None)主循环里每次读取滑动条当前值作为threshold就能实时观察不同阈值下的识别效果。你一边移动滑动条一边对着摄像头转动头部能非常直观地找到误放陌生人和误拒自己人之间的平衡点。这个方法比反复改配置文件重启高效得多也是我后来给门禁类项目做现场调参的标准手法。6.2 方向二把LBPH替换成深度学习特征提取如果你对识别精度不满意想要升级到深度学习方案这个项目已经做好的分层能让替换成本降到最低。识别模块的接口只依赖两点predict方法返回label和confidence。换用深度模型时新建一个DeepRecognizer类实现read方法和predict方法即可app.py一行不用改。具体做法是用MTCNN检测人脸替代Haar级联因为深度模型对对齐要求更高用FaceNet或ArcFace提取128维特征向量与模板库中的向量做余弦相似度比对相似度超过0.6判为自己人。这时confidence的语义就统一成越大越相似了。替换时注意train流程也要跟着换需要先跑一遍人脸编码脚本把每个样本转成特征向量存到npy文件。6.3 改造后的验证方法不管往哪个方向改都建议建立一个固定测试集来防止“改好一个参数弄坏另一个参数”的回退问题。我通常的做法是预留10张不在训练样本里的照片作为验证集每改一次代码跑一遍批量预测统计识别率、误识率两个指标。验证脚本独立于主程序用离线图片而不是摄像头输入这样才能保证每次对比的条件一致。这个项目让我最受益的一点是它的模块切分方式。从那以后我每次写识别类Demo都强制自己先定义好输入输出接口再写内部实现检测、训练、识别三个模块必须能独立替换。这个习惯帮我少走了很多回头路希望帮到你。本文还有配套的精品资源点击获取