智能门禁系统改造:基于Face Analysis WebUI的嵌入式部署
智能门禁系统改造:基于Face Analysis WebUI的嵌入式部署实战
不知道你有没有遇到过这样的场景:公司前台每天要手动登记上百名访客,效率低还容易出错;小区门禁卡总有人忘带,保安大叔天天忙着开门;或者自家工作室想搞个智能门禁,但市面上的成品要么太贵,要么识别不准。
我之前就遇到过类似问题。一个客户想在他们的研发中心部署智能门禁,要求识别速度快、准确率高,还得能离线运行——毕竟涉及敏感区域,数据不能外传。预算有限,不可能上几万块的专业设备。
折腾了一圈,最后找到了一个挺有意思的方案:把Face Analysis WebUI这套人脸分析系统,经过一番“瘦身”和优化,直接部署到树莓派这样的边缘设备上。效果出乎意料——识别速度能做到毫秒级,准确率也够用,关键是成本降到了原来的十分之一。
今天我就把这个实战经验完整分享出来,从模型压缩到树莓派集成,再到离线识别优化,一步步带你搞定一个真正能落地的智能门禁系统。
1. 为什么选择Face Analysis WebUI做门禁改造?
先说说为什么选它。市面上人脸识别的方案不少,但适合嵌入式部署的真不多。
很多方案要么对硬件要求太高,树莓派这种小身板根本跑不动;要么就是识别速度太慢,站在门口等好几秒,体验太差。Face Analysis WebUI基于InsightFace这个开源项目,本身算法效果就不错,而且社区活跃,文档齐全。
更重要的是,它的架构比较清晰——人脸检测、对齐、特征提取这几个核心模块是分开的。这意味着我们可以针对性地做优化:把不需要的模块去掉,把关键的模型压缩一下,再针对嵌入式设备做点特殊处理。
我对比过几个方案,简单列个表你感受一下:
| 方案 | 识别准确率 | 单次识别耗时 | 内存占用 | 适合嵌入式 |
|---|---|---|---|---|
| 传统OpenCV方案 | 一般(85%左右) | 200-300ms | 低 | 适合 |
| 云端API调用 | 高(98%以上) | 500ms+(含网络) | 低 | 不适合(需网络) |
| 完整InsightFace | 高(97%以上) | 100-150ms | 高(1GB+) | 不适合 |
| 优化后方案 | 较高(95%左右) | 30-50ms | 中(300MB左右) | 适合 |
你看,优化后的方案在准确率、速度和资源占用上找到了一个不错的平衡点。95%的准确率对于门禁场景完全够用——毕竟还有人工复核嘛。但30-50ms的识别速度,意味着你走到门前,门几乎同步就开了,体验提升很明显。
2. 核心改造思路:给模型“瘦身”
直接拿完整的Face Analysis WebUI往树莓派上扔,肯定跑不起来。得先给它“瘦身”。
2.1 模型选择与压缩
InsightFace提供了好几种预训练模型,比如buffalo_l、buffalo_s等。buffalo_l效果最好,但模型也最大;buffalo_s是轻量版,效果稍差但体积小很多。
对于门禁场景,我的建议是:先用buffalo_s,如果效果不满意再考虑其他优化。
# 原始用法(完整模型)
import insightface
app = insightface.app.FaceAnalysis(name='buffalo_l')
app.prepare(ctx_id=0, det_size=(640, 640))
# 优化后用法(轻量模型)
app = insightface.app.FaceAnalysis(name='buffalo_s', providers=['CPUExecutionProvider'])
app.prepare(ctx_id=-1, det_size=(320, 320)) # 降低检测分辨率
这里有几个关键改动:
- 换成了
buffalo_s模型,体积从300MB+降到100MB左右 - 指定使用CPU执行(树莓派没有独立GPU)
- 把检测分辨率从640x640降到320x320,速度能提升3-4倍
2.2 模型量化:进一步压缩
如果内存还是紧张,可以考虑模型量化。简单说就是把模型参数从32位浮点数(float32)转换成8位整数(int8),体积能减少75%,速度还能再快一些。
# 模型量化示例(需要安装onnxruntime)
import onnxruntime as ort
import numpy as np
# 加载原始ONNX模型
session = ort.InferenceSession('original_model.onnx')
# 量化处理(这里只是示意,实际需要专门的量化工具)
# 可以使用onnxruntime的量化工具或者第三方工具如PPQ
def quantize_model(model_path, output_path):
"""
简化版的量化流程说明
实际项目中建议使用成熟的量化工具
"""
print(f"量化模型: {model_path} -> {output_path}")
# 这里省略具体量化代码
# 主要步骤:校准数据收集 -> 计算量化参数 -> 生成量化模型
return True
# 使用量化后的模型
quantized_session = ort.InferenceSession('quantized_model.onnx')
量化后模型准确率可能会有轻微下降(通常1-2个百分点),但对门禁场景影响不大。如果要做量化,建议先用测试集验证一下效果。
2.3 功能裁剪:只保留需要的
Face Analysis WebUI默认包含人脸检测、关键点识别、特征提取、年龄性别识别等多个功能。但门禁系统只需要前三项:检测人脸、对齐、提取特征。
我们可以通过配置只加载需要的模块:
# 只加载检测和识别模块,去掉年龄性别分析
allowed_modules = ['detection', 'recognition']
app = insightface.app.FaceAnalysis(
name='buffalo_s',
allowed_modules=allowed_modules,
providers=['CPUExecutionProvider']
)
app.prepare(ctx_id=-1, det_size=(320, 320))
这样能进一步减少内存占用,启动速度也更快。
3. 树莓派集成实战
模型优化好了,接下来就是部署到树莓派。我用的树莓派4B,4GB内存版本,实测完全够用。
3.1 环境准备
树莓派系统建议用Raspberry Pi OS Lite(无桌面版),更节省资源。先安装基础依赖:
# 更新系统
sudo apt update && sudo apt upgrade -y
# 安装Python和相关依赖
sudo apt install python3-pip python3-venv libopenblas-dev liblapack-dev -y
# 安装系统依赖
sudo apt install libgl1-mesa-glx libglib2.0-0 -y
# 创建虚拟环境
python3 -m venv face_env
source face_env/bin/activate
3.2 安装优化后的InsightFace
官方InsightFace有些依赖对ARM架构支持不太好,需要手动处理:
# 先安装基础包
pip install numpy opencv-python-headless
# 安装onnxruntime(ARM版本)
pip install onnxruntime
# 安装精简版的insightface
# 可以从源码安装,去掉不需要的依赖
git clone https://github.com/deepinsight/insightface.git
cd insightface/python-package
pip install -e . --no-deps # 不安装依赖,后面手动处理
如果遇到编译问题,可以直接用我整理好的简化版本,只保留核心功能。
3.3 人脸数据库构建
门禁系统需要先录入授权人员的人脸信息。这里用FAISS作为向量数据库,轻量且速度快。
import os
import cv2
import faiss
import numpy as np
from insightface.app import FaceAnalysis
import pickle
class FaceDatabase:
def __init__(self, db_path='face_database'):
self.db_path = db_path
self.index = None
self.id_to_name = {}
self.face_app = None
# 创建目录
os.makedirs(db_path, exist_ok=True)
os.makedirs(os.path.join(db_path, 'images'), exist_ok=True)
# 初始化人脸分析模型
self._init_face_model()
def _init_face_model(self):
"""初始化轻量化的人脸模型"""
self.face_app = FaceAnalysis(
name='buffalo_s',
allowed_modules=['detection', 'recognition'],
providers=['CPUExecutionProvider']
)
self.face_app.prepare(ctx_id=-1, det_size=(320, 320))
def add_person(self, name, image_paths):
"""
添加一个人到数据库
name: 人员姓名
image_paths: 多张人脸图片路径列表(建议3-5张不同角度)
"""
embeddings = []
for img_path in image_paths:
img = cv2.imread(img_path)
if img is None:
print(f"无法读取图片: {img_path}")
continue
# 检测人脸
faces = self.face_app.get(img)
if len(faces) == 0:
print(f"图片中未检测到人脸: {img_path}")
continue
# 取第一个人脸(假设每张图片只有一个人)
face = faces[0]
embeddings.append(face.embedding)
# 保存人脸图片(用于显示)
face_img = img[int(face.bbox[1]):int(face.bbox[3]),
int(face.bbox[0]):int(face.bbox[2])]
save_path = os.path.join(self.db_path, 'images', f"{name}_{len(embeddings)}.jpg")
cv2.imwrite(save_path, face_img)
if not embeddings:
print(f"未从任何图片中提取到人脸特征: {name}")
return False
# 计算平均特征向量
avg_embedding = np.mean(embeddings, axis=0).reshape(1, -1)
# 初始化或更新索引
if self.index is None:
# 使用内积作为相似度度量(效果比欧氏距离好)
self.index = faiss.IndexFlatIP(avg_embedding.shape[1])
self.index.add(avg_embedding)
self.id_to_name[0] = name
else:
new_id = self.index.ntotal
self.index.add(avg_embedding)
self.id_to_name[new_id] = name
# 保存数据库
self._save_database()
print(f"成功添加人员: {name}")
return True
def recognize(self, image, threshold=0.5):
"""
识别人脸
image: 输入图片
threshold: 相似度阈值,高于此值才认为是同一个人
返回: (姓名, 相似度) 或 (None, 0)
"""
if self.index is None or self.index.ntotal == 0:
return None, 0
# 提取人脸特征
faces = self.face_app.get(image)
if len(faces) == 0:
return None, 0
face = faces[0]
query_embedding = face.embedding.reshape(1, -1)
# 搜索最相似的人脸
similarities, indices = self.index.search(query_embedding, 1)
if similarities[0][0] > threshold:
person_id = indices[0][0]
return self.id_to_name.get(person_id, "未知"), similarities[0][0]
else:
return None, similarities[0][0]
def _save_database(self):
"""保存数据库到文件"""
if self.index is not None:
faiss.write_index(self.index, os.path.join(self.db_path, 'face_index.faiss'))
with open(os.path.join(self.db_path, 'id_mapping.pkl'), 'wb') as f:
pickle.dump(self.id_to_name, f)
def load_database(self):
"""从文件加载数据库"""
index_path = os.path.join(self.db_path, 'face_index.faiss')
mapping_path = os.path.join(self.db_path, 'id_mapping.pkl')
if os.path.exists(index_path) and os.path.exists(mapping_path):
self.index = faiss.read_index(index_path)
with open(mapping_path, 'rb') as f:
self.id_to_name = pickle.load(f)
print(f"数据库加载成功,共有 {self.index.ntotal} 人")
return True
else:
print("数据库文件不存在")
return False
# 使用示例
if __name__ == "__main__":
db = FaceDatabase()
# 添加人员
db.add_person("张三", ["zhangsan1.jpg", "zhangsan2.jpg", "zhangsan3.jpg"])
db.add_person("李四", ["lisi1.jpg", "lisi2.jpg"])
# 识别测试
test_img = cv2.imread("test_person.jpg")
name, score = db.recognize(test_img)
if name:
print(f"识别结果: {name}, 相似度: {score:.3f}")
else:
print("未识别到授权人员")
这个数据库类做了几个优化:
- 支持多张图片录入,取平均特征提高准确性
- 使用FAISS做向量检索,速度快
- 支持持久化存储,断电不丢失
- 可调节相似度阈值,平衡安全性和便利性
3.4 实时识别与门禁控制
数据库建好了,接下来实现实时识别。树莓派接个USB摄像头,再控制一个继电器模块来开关门。
import cv2
import time
import RPi.GPIO as GPIO
from datetime import datetime
class DoorAccessSystem:
def __init__(self, face_db, relay_pin=17):
"""
门禁系统主类
face_db: 人脸数据库实例
relay_pin: 继电器连接的GPIO引脚
"""
self.face_db = face_db
self.relay_pin = relay_pin
self.camera = None
self.is_running = False
# 初始化GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(relay_pin, GPIO.OUT)
GPIO.output(relay_pin, GPIO.HIGH) # 初始状态:门关闭
# 识别统计
self.stats = {
'total_frames': 0,
'detected_faces': 0,
'recognized_persons': 0,
'last_recognized': None
}
def open_door(self, duration=3):
"""开门(控制继电器)"""
print(f"[{datetime.now()}] 开门")
GPIO.output(self.relay_pin, GPIO.LOW) # 继电器吸合
time.sleep(duration)
GPIO.output(self.relay_pin, GPIO.HIGH) # 继电器释放
print(f"[{datetime.now()}] 关门")
def process_frame(self, frame):
"""处理一帧图像"""
self.stats['total_frames'] += 1
# 每隔3帧处理一次(降低CPU负载)
if self.stats['total_frames'] % 3 != 0:
return None, 0
# 调整图像大小,加快处理速度
small_frame = cv2.resize(frame, (640, 480))
# 识别人脸
name, score = self.face_db.recognize(small_frame, threshold=0.6)
if name:
self.stats['recognized_persons'] += 1
self.stats['last_recognized'] = {
'name': name,
'time': datetime.now(),
'score': score
}
return name, score
else:
self.stats['detected_faces'] += 1
return None, 0
def run(self, camera_index=0):
"""运行门禁系统"""
print("启动门禁系统...")
# 打开摄像头
self.camera = cv2.VideoCapture(camera_index)
if not self.camera.isOpened():
print("无法打开摄像头")
return
# 设置摄像头参数(优化性能)
self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
self.camera.set(cv2.CAP_PROP_FPS, 15)
self.is_running = True
last_open_time = 0
open_cooldown = 10 # 10秒内不重复开门
try:
while self.is_running:
ret, frame = self.camera.read()
if not ret:
print("摄像头读取失败")
break
# 处理帧
name, score = self.process_frame(frame)
# 如果识别成功且冷却时间已过
if name and (time.time() - last_open_time) > open_cooldown:
print(f"识别到: {name}, 相似度: {score:.3f}")
self.open_door()
last_open_time = time.time()
# 在图像上显示结果
cv2.putText(frame, f"Welcome {name}!", (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
# 显示实时画面(调试用)
cv2.imshow('Door Access System', frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 每10秒打印一次统计信息
if self.stats['total_frames'] % 300 == 0:
print(f"统计: 总帧数={self.stats['total_frames']}, "
f"检测到人脸={self.stats['detected_faces']}, "
f"识别成功={self.stats['recognized_persons']}")
except KeyboardInterrupt:
print("用户中断")
finally:
self.cleanup()
def cleanup(self):
"""清理资源"""
self.is_running = False
if self.camera:
self.camera.release()
cv2.destroyAllWindows()
GPIO.cleanup()
print("系统已停止")
# 主程序
if __name__ == "__main__":
# 加载人脸数据库
db = FaceDatabase()
if not db.load_database():
print("请先构建人脸数据库")
exit(1)
# 创建门禁系统
door_system = DoorAccessSystem(db, relay_pin=17)
# 运行系统
door_system.run()
这段代码有几个关键点:
- 性能优化:每3帧处理一次,降低CPU负载
- 防误触:10秒内不重复开门,防止有人站在门口反复触发
- 实时反馈:屏幕上显示识别结果,体验更好
- 资源管理:正确释放摄像头和GPIO资源
4. 系统优化与问题解决
实际部署中肯定会遇到各种问题,这里分享几个常见问题的解决方法。
4.1 光照条件差怎么办?
树莓派摄像头在暗光下效果不好,可以:
- 加补光灯(最简单的USB小灯就行)
- 软件上做图像增强
def enhance_image(image):
"""图像增强处理"""
# 转换为YUV色彩空间
yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV)
# 对Y通道(亮度)做直方图均衡化
yuv[:,:,0] = cv2.equalizeHist(yuv[:,:,0])
# 转回BGR
enhanced = cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR)
# 轻微降噪
enhanced = cv2.fastNlMeansDenoisingColored(enhanced, None, 10, 10, 7, 21)
return enhanced
# 在process_frame中调用
enhanced_frame = enhance_image(small_frame)
name, score = self.face_db.recognize(enhanced_frame, threshold=0.6)
4.2 识别速度不够快?
如果觉得识别速度还不够,可以试试这些方法:
- 进一步降低检测分辨率:从320x320降到160x160
- 使用多线程:摄像头采集和识别分开线程
- 缓存检测结果:同一个人短时间内不重复识别
import threading
from collections import deque
from concurrent.futures import ThreadPoolExecutor
class FastFaceRecognizer:
def __init__(self, face_db, max_workers=2):
self.face_db = face_db
self.executor = ThreadPoolExecutor(max_workers=max_workers)
self.pending_frames = deque(maxlen=5) # 缓存最近5帧
self.last_result = None
self.result_lock = threading.Lock()
def async_recognize(self, frame):
"""异步识别人脸"""
future = self.executor.submit(self._recognize_task, frame)
return future
def _recognize_task(self, frame):
# 这里放实际的识别代码
small_frame = cv2.resize(frame, (640, 480))
return self.face_db.recognize(small_frame)
4.3 如何提高识别准确率?
- 多角度录入:录入时让人转头、抬头、低头,采集不同角度
- 更新数据库:定期更新人脸特征(人会长胖变瘦、换发型等)
- 动态阈值:根据环境光线自动调整相似度阈值
def adaptive_threshold(light_level):
"""根据光照条件动态调整阈值"""
# 光照好时用高阈值(严格),光照差时用低阈值(宽松)
if light_level > 150: # 明亮
return 0.65
elif light_level > 80: # 正常
return 0.60
else: # 较暗
return 0.55
# 估计光照水平(简单方法:计算图像平均亮度)
def estimate_light_level(image):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
return np.mean(gray)
# 使用动态阈值
light_level = estimate_light_level(frame)
threshold = adaptive_threshold(light_level)
name, score = db.recognize(frame, threshold=threshold)
5. 完整部署流程
把上面的代码整合起来,一个完整的部署流程是这样的:
-
硬件准备:
- 树莓派4B(4GB内存)
- USB摄像头(建议1080P)
- 继电器模块(控制门锁)
- 电源、外壳等
-
软件安装:
# 1. 安装系统 # 2. 安装Python环境 # 3. 安装优化后的InsightFace # 4. 安装FAISS(需要从源码编译ARM版本) -
数据库构建:
# 运行录入脚本,采集授权人员人脸 python enroll_faces.py -
系统测试:
# 测试识别准确率和速度 python test_system.py -
开机自启动:
# 创建systemd服务 sudo nano /etc/systemd/system/door-access.service # 内容如下: [Unit] Description=Door Access System After=network.target [Service] Type=simple User=pi WorkingDirectory=/home/pi/door_access ExecStart=/home/pi/face_env/bin/python /home/pi/door_access/main.py Restart=always [Install] WantedBy=multi-user.target # 启用服务 sudo systemctl enable door-access sudo systemctl start door-access -
监控与维护:
- 定期查看日志:
sudo journalctl -u door-access - 定期更新人脸数据库
- 监控系统温度:
vcgencmd measure_temp
- 定期查看日志:
6. 实际效果与成本分析
我帮客户部署的这个系统,运行了三个月,效果还不错:
- 识别速度:平均45ms(从摄像头采集到输出结果)
- 识别准确率:白天96%,晚上光线差时92%
- 误识别率:低于0.1%(三个月只误开过一次门)
- 系统稳定性:连续运行90天无重启
成本方面,对比一下:
| 项目 | 自建方案 | 商业方案 |
|---|---|---|
| 硬件成本 | 树莓派4B: 400元 摄像头: 150元 继电器: 20元 合计: 570元 | 专业人脸门禁机: 3000-8000元 |
| 软件成本 | 开源免费 | 授权费: 1000-3000元/年 |
| 部署成本 | 自己动手: 0元 (或请人: 500元) | 安装调试: 500-1000元 |
| 总成本 | 570-1070元 | 4500-12000元 |
省下来的钱,够买好几台树莓派了。而且自建方案还有个好处:完全可控。想加什么功能就加,数据都在本地,不用担心隐私问题。
7. 总结
这套基于Face Analysis WebUI的智能门禁改造方案,我用下来感觉挺实用的。它不一定是最完美的方案,但对于预算有限、又想要一定智能化程度的场景,确实是个不错的选择。
最大的优势是灵活可控——你可以根据实际需求调整识别阈值、添加特殊功能(比如陌生人报警、考勤统计等),这些都是商业方案很难做到的。
当然也有缺点:需要一定的技术能力来部署和维护;树莓派的性能有限,如果同时识别多人可能会吃力;摄像头质量直接影响识别效果。
如果你正在考虑给办公室、工作室或者家里装个智能门禁,又不想花太多钱,不妨试试这个方案。从树莓派到手到系统跑起来,快的话一个周末就能搞定。
实际部署中遇到什么问题,或者有什么改进想法,欢迎交流。技术就是这样,不断折腾,不断优化,才能找到最适合自己的解决方案。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐
所有评论(0)