ARTICLE DETAIL

资讯详情

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

人脸+步态双模态门禁实战:OpenCV与Python实现双重生物特征认证

人脸+步态双模态门禁实战:OpenCV与Python实现双重生物特征认证 简介这是一套面向毕业设计与课程作业的智能门禁系统项目基于Python及OpenCV、dlib等开源视觉库实现人脸识别与步态识别的双重生物特征认证涵盖图像采集、人脸检测、特征提取、步态序列处理以及两种特征的融合决策。压缩包共263个文件约70.29MB以Vue/TypeScript前端界面、Java后端工程、视频与图像样例、数据库脚本及各类配置文件为主工程结构完整便于按模块阅读或二次开发。已有183人学习下载。资源不仅给出核心识别算法与调用链还包含可运行的工程骨架、界面展示素材和演示视频适合正在完成计算机视觉或物联网类毕业设计的学生参考。通过源码与配置文件可以理解Haar级联、特征向量比对、PCA降维等知识点在真实门禁场景中的用法也能进一步调整参数、替换模型实现自定义的认证策略。1. 为什么门禁要从单模态走向人脸步态双认证一个demo到量产的距离门禁闸机装多了会发现一个尴尬人脸识别门禁机在逆光、暗光和侧脸时把人挡在门外但把打印照片凑上来又可能被放进去单靠步态识别又容易把同体型的同事看混。把人脸识别和步态识别放到同一个智能门禁系统里做双重生物特征认证人脸负责近距离确认身份步态负责远距离持续跟踪两者分数融合后再开锁是现有Python和开源计算机视觉库方案里成本最低、最容易落地的路线。这个方向适合正在做人脸识别门禁系统设计、计算机视觉大作业、毕设原型或公司安防预研的人你不需要自己训练大模型用OpenCV、dlib和普通USB摄像头就能搭出一套能演示、能评测、能演进的双模态原型。它解决的不只是识别率问题而是把谁来了和走路的姿势对不对绑在一起让门禁从单点验证变成持续验证。2. 人脸识别模块OpenCV人脸检测 128维人脸特征先在本地跑通注册和比对人脸识别是这套双模态门禁里最成熟、最容易先落地的一半。这章我把注册、比对和阈值调整讲透确保你拿到Python环境后能直接跑通。2.1 选型理由为什么用face_recognition而不是自己训练人脸识别模型门禁场景的人脸识别最容易踩的第一个坑就是我要不要自己训练一个模型。对绝大多数做智能门禁系统的人来说答案是不需要。face_recognition这个开源库内置了dlib训练的ResNet人脸特征模型输入一张人脸图可以输出128维的embedding向量。这个向量对人脸的身份刻画已经足够精确到几百人的规模而且不需要GPU普通CPU上单次特征提取大约几十到两百毫秒。自己训练人脸模型的问题在于数据。人脸识别要训到可用精度至少需要几千个人、每人几十张不同姿态和光照的样本。做一个门禁系统你大概率拿不到这样的数据集。使用预训练特征再配合距离阈值是计算机视觉入门级项目里最常见的可靠做法。另外face_recognition把检测、对齐、特征提取封装成一个接口对新手非常友好。如果你熟悉OpenCV也可以自己组合OpenCV DNN做人脸检测然后使用dlib的5点或68点关键点做对齐再调用dlib的face_recognition_model_v1提取特征。工程上我建议先用face_recognition的统一接口把流程跑通再考虑替换某个环节。毕竟门禁系统要的是稳定可复现不是算法炫技。2.2 注册流程把谁能进做成特征库注册的目的是给每个授权人员生成一条特征记录。最稳妥的姿势是用摄像头现场拍35张不同角度的照片分别提取特征后取平均这样比用一张证件照注册稳得多。注册代码可以写成这样import face_recognition import cv2 import numpy as np import pickle def register_person(image_paths, person_id): encodings [] for path in image_paths: img cv2.imread(path) rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb, modelhog) if len(boxes) ! 1: continue # 一张图里必须只有一张正脸多脸直接丢弃 enc face_recognition.face_encodings(rgb, boxes)[0] encodings.append(enc) if not encodings: return None mean_enc np.mean(encodings, axis0) return {id: person_id, encoding: mean_enc} # 使用示例 result register_person( [alice_1.jpg, alice_2.jpg, alice_3.jpg], alice ) if result: with open(face_db.pkl, ab) as f: pickle.dump(result, f)这段代码做的事情是读图、转RGB、用HOG模型找人脸位置、如果正好只有一张脸就提取特征、最后把多张图的特征做平均。modelhog是face_recognition的默认检测器在CPU上帧率能到每秒几张图如果觉得漏检多可以改成modelcnn准确率高但单张图可能耗时接近一秒。注册阶段无所谓实时识别阶段不建议用cnn否则门禁会明显卡顿。保存到pickle文件后人脸特征库就有了。注意每个人员ID的编码数组是一维128维float向量pickle会原样保存。实际项目里我还会在pickle里加一个注册时间字段方便以后做特征更新和人员离职删除。2.3 实时比对拉远摄像头距离后依然稳定实时识别阶段视频帧先做降采样比如把1080P缩到640宽既能减少人脸检测耗时也不会明显影响距离2米内的人脸识别。比对逻辑是基于欧氏距离的距离越小说明越可能是同一个人。import numpy as np import face_recognition import cv2 def build_known_db(face_db_path): known_encs, known_ids [], [] with open(face_db_path, rb) as f: while True: try: entry pickle.load(f) known_encs.append(entry[encoding]) known_ids.append(entry[id]) except EOFError: break return np.array(known_encs), known_ids def verify_face(known_encs, known_ids, face_enc, threshold0.45): if known_encs.shape[0] 0: return None, 1.0 dists np.linalg.norm(known_encs - face_enc, axis1) idx int(np.argmin(dists)) return known_ids[idx], float(dists[idx])参数说明threshold默认0.45在我测过的USB摄像头场景里比较平衡。0.4会更严适合安全要求高的地方0.5偏松适合闸机快速放行。真正上线前一定要用现场光照下的负样本部门里其他不授权的人扫一遍画出误识率曲线再决定阈值。这里还有一个很多人忽视的细节当视频里出现多张人脸时门禁系统不应该直接取最近的人开锁而应该先判断画面上是否只有一个人。原因是尾随或者多人挤在闸机口时如果识别到其中一个人就开锁另一个人就能趁机混入。我一般会在门禁程序里加个判断只有检测到恰好一张人脸时才调用verify_face否则直接不发开锁信号。3. 步态识别模块从摄像头轮廓序列到GEI步态能量图不采集清晰人脸也能认人人脸识别模块解决的是走到门前确认身份步态识别则解决还在走廊里就开始判断是不是授权人员。这章是整个双模态系统里最有门槛、也最值得花时间调的部分。3.1 步态与人脸互补远距离无感识别人脸识别必须等用户走到门前、正对镜头才能工作这带来了两个问题一是逆光测脸容易失败二是有人故意低头或戴口罩时识别流程直接卡住。步态识别的好处在于是无感识别——人还在离门三到五米外的走廊里摄像头已经捕捉到走路姿势不需要你配合看镜头。步态识别的本质是分析行走时的身体姿态时序。最常用的特征表达是GEIGait Energy Image步态能量图它把完整步态周期内的二值人体轮廓叠加后取平均生成一张静态灰度图。走路时手臂摆动和腿部跨步的频率、幅度都会留在GEI里作为身份特征。要注意GEI不是万金油它同时包含了外观信息所以换外套、背包都会干扰它。但把它和人脸特征融合步态负责这个人走路方式和授权用户是否足够接近人脸负责近距离时身份是否精确一致两者就互补了。3.2 背景减除与轮廓提取MOG2 形态学去噪步态模块的第一步是从视频里把运动的人剪出来。用OpenCV的MOG2背景减除器做前景检测再通过形态学开闭运算把空洞和盐噪声去掉。代码片段import cv2 import numpy as np def get_human_mask(frame, mog): fg mog.apply(frame) # 二值化并去掉阴影MOG2的阴影区域通常灰度值为127 _, fg cv2.threshold(fg, 200, 255, cv2.THRESH_BINARY) kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) fg cv2.morphologyEx(fg, cv2.MORPH_OPEN, kernel, iterations2) fg cv2.morphologyEx(fg, cv2.MORPH_CLOSE, kernel, iterations2) # 只保留矩形框面积足够大的轮廓 cnts, _ cv2.findContours(fg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) best max(cnts, keycv2.contourArea, defaultNone) if best is None or cv2.contourArea(best) 5000: return None x, y, w, h cv2.boundingRect(best) return (x, y, w, h)这里的关键是阴影处理。MOG2会把移动阴影标记成127的灰度值如果不处理人体轮廓会把阴影也包进来导致后续GEI里多出一个人影。所以我在threshold这步直接去掉小于200的像素。形态学开运算去掉孤立噪点闭运算把身体内部的空洞填上。画面分辨率不同矩形面积阈值要跟着改我给的5000像素是720P下的经验值1080P大概要放大到15000以上。3.3 步态周期检测与GEI生成用轮廓宽度找周期再平均得到人体轮廓后要切出一个完整走路周期而不是随便取几十帧做平均。一个周期通常从一只脚脚跟落地到同一只脚再次落地对应轮廓外接矩形宽度会先变宽再变窄再变宽。所以最简单的周期检测是统计连续帧的轮廓宽度序列找到两个相邻的波峰。def extract_gait_cycle(widths, min_frames20): # widths: 每帧人体外接矩形的宽度值 widths np.array(widths, dtypenp.float32) # 先做中值滤波去掉手臂摆动造成的毛刺 from scipy.ndimage import median_filter smooth median_filter(widths, size5) peaks [] for i in range(1, len(smooth)-1): if smooth[i] smooth[i-1] and smooth[i] smooth[i1]: peaks.append(i) cycles [] for i in range(len(peaks)-1): if peaks[i1] - peaks[i] min_frames: cycles.append((peaks[i], peaks[i1])) return cycles def build_gei(contour_frames): # 所有轮廓帧已经缩放到统一尺寸 gei np.zeros((64, 64), dtypenp.float32) for frame in contour_frames: gei frame.astype(np.float32) # frame是0/255的二值轮廓图 gei / max(len(contour_frames), 1) return gei.astype(np.uint8)参数说明min_frames要结合摄像头帧率设置。15FPS下走路一个周期大约要30帧20帧是下限30FPS下建议设40。如果周期切得太短GEI里只有半个跨步特征可区分性会明显下降。scipy.ndimage的median_filter用size5平滑宽度序列能避免手部自然摆动造成的假波峰。另外GEI生成前要对轮廓做尺寸归一化。我这里是直接resize到64x64保留一点原始宽高比更好但那样代码会复杂一些门禁场景下固定尺寸足够。3.4 模板匹配与轻量CNN两种分类路线GEI生成后身份比对有两种路线。人少比如50人以内可以直接做模板匹配把每个人注册时保存的多个GEI拉平成向量测试GEI和所有模板算余弦相似度选最大相似度并和阈值比较。优点是零训练成本缺点是换衣服干扰明显。如果团队规模上百人或者现场环境复杂建议走轻量CNN路线。用PyTorch写一个很小的分类网络输入是64x64的GEI单通道图输出是人员ID。训练时每个人录35个周期的GEI数据增强可以做小幅旋转、平移和缩放。import torch.nn as nn class GaitNet(nn.Module): def __init__(self, num_classes): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 16, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.AdaptiveAvgPool2d((1, 1)) ) self.classifier nn.Linear(64, num_classes) def forward(self, x): x self.features(x) return self.classifier(x.view(x.size(0), -1)) # 训练时用交叉熵损失 Adam学习率1e-3批量32 # 最后一层softmax概率最高的类如果小于0.7判定为未授权这个网络一共只有三层卷积参数量很小CPU也跑得动。分类前把GEI除以255归一化到[0,1]。判断未授权人时除了看最高概率还要设一个概率下限0.7防止陌生人被硬分到某个已注册人头上。经过我测试GEI 64x64已经够用不需要更大的分辨率。4. 双模态融合与门锁联动设置融合权重、识别阈值和串口开锁指令人脸和步态各自给出身份判断后真正的门禁逻辑才开始。这章的核心就一句话别做与门做加权融合然后通过串口去控制实际的门锁。4.1 融合策略加权分数比必须两个都过更实用拿到人脸距离和步态匹配分数后最直接的想法是两个都通过才开门。实际部署时你会发现这个与门策略会把真实员工卡在外面人脸在逆光时距离会冲到0.6以上步态又因为当天穿了大衣而匹配不上结果只有少数时刻两个条件同时满足。更实用的做法是转成分数做加权融合。def decision_fusion(face_identity, face_dist, gait_identity, gait_score, w_face0.7, face_th0.6, gait_th0.55, fuse_th0.62): # 两个模态必须识别到同一个人否则直接拒绝 if face_identity is None or gait_identity is None or face_identity ! gait_identity: return False, 0.0 # 每个人脸的欧氏距离先映射到[0,1]得分距离0 - 1.0 face_score 1.0 - min(face_dist / face_th, 1.0) # gait_score 已经是余弦相似度范围在[0,1] total_score w_face * face_score (1 - w_face) * gait_score return total_score fuse_th, total_score参数说明w_face0.7意味着人脸在最终决策里权重更高因为人脸特征经过独立训练模型的embedding稳定性通常比GEI模板好。face_th是归一化的分母意思是距离大于0.6时人脸贡献直接记为0避免一个大距离值把总分数压得失去了比较意义。fuse_th需要通过现场负样本调整我先给一个0.62的起步值再用模拟攻击反复调。常见做法里还有更细的融合给不同时间段不同的权重比如白天光照好人脸权重拉高到0.8夜晚只有走廊灯光时步态权重提到0.4。但这样会让系统行为漂移排错更麻烦。我建议先用固定权重跑通再考虑动态权重。4.2 防尾随与单次通行逻辑双模态融合只是把身份判断做好门禁物理逻辑还得单独处理。尾随是门禁最容易出现的漏洞前面的人刷脸通过后面的人紧跟着过去。软件上能做的最简单防护是单次通行锁开门信号触发后除非检测到人已完全离开闸区否则不再触发第二次开门。# 伪代码每人通过后等待离场 def door_control_loop(): last_open_time 0 while True: present is_person_at_gate() # 用红外或人体检测 if not present and time.time() - last_open_time 2.0: # 确认人已离开重置通行状态 reset_single_pass() if is_authorized() and single_pass_available(): open_door() last_open_time time.time()这个循环里单次通行锁是关键。也可以加装一对红外对射人通过后遮光信号到触发下一次。软件只能做到识别到人后允许一次开门物理防尾随还是得靠闸机结构这点在设计门禁系统时不要省。4.3 下位机联动串口发指令开锁兼容STM32门禁机很多现成的人脸识别门禁机是带串口或韦根接口的。我们的Python程序在同一台工控机或树莓派上跑通过串口给门禁控制器发开锁指令。协议格式各家不一但都可以抽象成发一条带长度和校验的指令等待ACK。import serial import time class DoorLock: def __init__(self, port/dev/ttyUSB0, baud9600): self.ser serial.Serial(port, baud, timeout1) def unlock(self, duration3): # 常见门禁板协议示例0xA0 0x01 0x00 0xA1后两位是校验位 cmd bytes([0xA0, 0x01, 0x00, 0xA1]) self.ser.write(cmd) ack self.ser.read(2) time.sleep(duration) return ack参数说明波特率要与门禁板保持一致常见9600或115200校验位如果算错了命令会被下位机丢弃调试时先用串口调试助手确认原厂协议格式。如果下位机是STM32自己写的控制板可以直接用GPIO驱动继电器但要注意共地。Python识别结果到开锁指令的延迟我一般控制在200ms以内用户没感知。5. 智能门禁避坑清单5次实测翻车记录与对应解决参数双模态门禁的坑不在算法理论而在真实环境下那些看着不起眼的细节。这章我按现象-原因-解决写我实际踩过的五类问题每一条都对应可调参数。5.1 侧脸导致人脸识别频繁误拒现象注册时用户正面照实测时只要转头和旁边人说话人脸距离立刻超过0.6真人被锁在门外。原因face_recognition的128维特征对姿态敏感侧脸超过15度后特征向量和注册向量的欧氏距离大幅上升。解决注册阶段每个用户录多角度照片我在现场是让用户自然转头采集正面、左右各20度各一张做平均特征。比对阶段阈值从默认0.6收紧到0.45反而更稳因为平均特征提高了正脸匹配度。另外如果闸机位置只能斜对摄像头要把摄像头安装角度正对来人方向别省钱装在门框侧面。5.2 逆光背光下人脸完全检测不到现象窗口或白墙背景下摄像头画面里人脸区域发暗检测框时有时无门禁系统瘫痪。原因普通USB摄像头动态范围有限逆光时人脸堆在暗部OpenCV DNN检测器召回率下降而不是特征比对失败。解决最有效的是物理补光在摄像头两侧加红外补光灯或普通LED灯板让脸成为画面里亮度最高的区域。软件层面我在送入检测器前做一次CLAHE直方图均衡化只对亮度通道处理能把背光下的暗脸提亮不少。注意CLAHE的clipLimit别开太大2.03.0即可开太大会把人脸纹理洗平反而降低识别率。5.3 换外套或背书包后步态识别失灵现象早上正常通过午后换上长款防寒服步态模块连续匹配失败双模态总分被拉低。原因GEI是平均轮廓它没有把外观和步态完全解耦。外套轮廓覆盖了腿部和手臂的摆动细节步态能量图就变了。解决我在项目里的做法是每个用户注册两套GEI模板一套正常着装、一套外套/背包状态。同时把GEI参与比对的区域裁掉头部和肩膀只保留胸部以下到脚踝的矩形区域能明显降低上身服装变化的影响。如果要求更高可以改做人体骨点序列OpenPose/MediaPipe提取步幅和相位但算力开销会翻倍。5.4 步态周期检测把它切成半个周期现象GEI质量忽好忽坏同一个人的两段GEI相似度只有0.7有时候甚至被自己的模板拒绝。原因宽度曲线受手部自然摆动干扰波峰检测把单一手臂摆出去当成半个周期开始导致叠加结果只有迈出一条腿的形态。解决先对宽度序列做size5中值滤波再把min_frames从20提高到25最后使用相邻波峰间隔的直方图只保留落在模式长度附近的周期段。还有一种玄学但有用的做法把轮廓外接框底部10%的区域截掉再做宽度统计因为脚步开合才是周期的主信号减少手部干扰。5.5 双模态融合后通过率反而下降现象单独人脸通过率95%单独步态通过率80%融合后调到88%还不如单模态。原因两模态分数分布不在同一量纲。直接用原始距离相加步态的大距离值会淹没人脸的小距离值或者步态未授权用户得分高拉高了整体误识。解决先各自标定归一化参数让正样本得分均值都落在0.8以上再调w_face和fuse_th。绝大多数场景w_face放在0.70.8fuse_th用负样本做一次网格搜索。调参与val集脱钩不要用同一批数据既当注册集又当测试集否则数字好看上真机就翻车。6. 系统验证与进阶用自采数据评估双生物特征认证的可靠性并向后端化演进6.1 自采步态数据集每人3个来回离线和实时都测我没有去套别人现成的步态数据集因为门禁场景的机位、走廊宽度和自家差异太大。做法是让每个员工在门禁通道正常走3个来回每趟抓一个周期生成一张GEI顺带保留对应时间的人脸识别记录。把这些数据分成注册集和测试集测试集必须包含不同日期和不同着装才能看出模型真实泛化能力。6.2 三重验证离线回灌、实时试运行、模拟攻击离线回灌是把录制好的视频用同一套算法再跑一遍能得到稳定的通过率和误识率实时试运行要在真实光照下连续跑一周记录白天、晚上、雨天的数据模拟攻击则是测试打印照片、手机照片重放、遮挡半张脸这几种常见绕过方式验证活体检测和双模态融合是否兜得住。这套验证做完双生物特征认证的可靠性才有说服力。6.3 进阶轻量化与活体检测如果能走到量产预研下一步可以把我上面的Python脚本里的模型导出成ONNX丢到边缘盒子或RK3588这类板子上跑不再依赖桌面工控机。人脸侧加入眨眼和近红外活体检测步态侧考虑用MediaPipe提取关键点后只保留肢体摆动角度彻底摆脱着装干扰。我做过几个门禁项目最深的教训是算法写得再漂亮都不如先在真实门口跑两天再用数据说话。阈值、权重和摄像头安装角度这些看着不起眼的参数才是智能门禁系统能不能稳定运行的分水岭。希望这篇基于人脸识别和步态识别的双模态门禁笔记能帮你少调几个晚上的参数。本文还有配套的精品资源点击获取
返回列表