
在当下身份认证场景中,传统账号密码登录存在记忆成本高、易泄露、易被破解等诸多短板,人脸生物识别凭借唯一性、便捷性、高安全性,已成为主流登录认证方案。市面上多数人脸登录系统依赖云端AI接口,存在网络依赖、数据上传隐私泄露、接口收费、响应延迟高等问题,无法适配内网、离线、私密办公等特殊场景。
今天我们基于青少年心理评估的项目,完整拆解一套纯本地化模型应用的人脸登录认证系统,基于FastAPI搭建后端服务,依托InsightFace实现人脸特征提取,搭配FAISS向量库完成人脸相似度匹配,结合MySQL、Redis实现用户数据存储与登录态管理,同时配套双版本前端页面,实现手动注册登录、自动动静刷脸登录双模式。整套系统完全离线运行,无需调用任何第三方云端AI接口,所有人脸检测、特征提取、向量比对逻辑均在本地服务器完成,兼顾数据隐私、响应速度与部署便捷性。

本项目是一套闭环式本地人脸认证系统,完整实现人脸注册、人脸登录、账号锁定、JWT鉴权、登录登出、向量库持久化等核心能力,同时支持两种登录模式,适配不同使用场景,具体功能如下:
项目采用轻量化、高适配、开源免费的技术栈,兼顾开发效率与运行性能,适配Windows、Linux多系统部署,各技术组件分工明确、各司其职,具体选型原因与作用:
项目采用经典的前后端分离架构,同时后端托管前端静态页面,实现单端口一体化运行,部署极其便捷,整体架构分为三层,层级清晰、耦合度低:
整体运行流程极简:前端采集人脸图像 → 上传至后端接口 → 后端预处理图像、提取人脸特征 → FAISS向量匹配校验 → MySQL校验用户状态 → 生成JWT令牌返回前端 → Redis缓存登录态,完成整套认证闭环。

以下示例使用Python + FastAPI搭建本地人脸登录系统的后端骨架。集成InsightFace做人脸特征提取,FAISS建向量索引,Redis缓存登录状态,MySQL 存用户数据,JWT 生成登录凭证,并配置跨域与静态资源托管,为完整人脸登录流程提供基础支撑。
核心模块:
import os
import cv2
import faiss
import numpy as np
import jwt
import redis
import pymysql
from fastapi import FastAPI, UploadFile, File, Form, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from insightface.app import FaceAnalysis
from datetime import datetime, timedelta
# ========== 全局配置 ==========
app = FastAPI(title="本地人脸登录系统")
# 跨域配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 托管静态前端页面
app.mount("/static", StaticFiles(directory="static"), name="static")
JWT_SECRET = "change-strong-secret-2026"
JWT_ALGORITHM = "HS256"
JWT_EXPIRE_SEC = 3600
FACE_THRESHOLD = 0.65
MAX_FAIL_COUNT = 5
LOCK_MINUTES = 10
DIM = 512
# Redis连接
r = redis.Redis(host="127.0.0.1", port=6379, db=0, decode_responses=True)
# MySQL连接
def get_mysql_conn():
conn = pymysql.connect(host="127.0.0.1", user="root", password="123456", database="daysurgery", autocommit=True)
return conn
# 加载InsightFace人脸模型
face_app = FaceAnalysis(name="buffalo_l")
face_app.prepare(ctx_id=0, det_size=(640,640))
# 初始化FAISS向量库
index_file = "face_index.bin"
if os.path.exists(index_file):
index = faiss.read_index(index_file)
print(f"加载人脸向量库,总数据量:{index.ntotal}")
else:
index = faiss.IndexIDMap2(faiss.IndexFlatL2(DIM))
faiss.write_index(index, index_file)人脸特征提取是本地人脸认证的核心基础,传统人脸识别依赖像素比对,极易受光线、角度、妆容影响,准确率极低。本项目采用InsightFace的buffalo_l模型,将人脸图像转化为512维高维特征向量,用向量唯一表征人脸生物特征,彻底规避像素比对的弊端。
核心基础示例代码:
# 人脸特征归一化核心示例
import numpy as np
# feat为InsightFace提取的512维人脸特征向量
feat = face.embedding.astype(np.float32)
# L2归一化,消除模长影响
feat = feat / np.linalg.norm(feat)
feat = feat.reshape(1, -1)完整人脸提取工具函数:
def get_face_feature(img_bgr):
faces = face_app.get(img_bgr)
if len(faces) == 0:
raise HTTPException(status_code=400, detail="图片未检测到人脸")
feat = faces[0].embedding.astype(np.float32)
feat = feat / np.linalg.norm(feat)
return feat.reshape(1, -1)FAISS是本项目实现人脸快速匹配的核心组件,相较于传统的遍历比对,FAISS向量检索效率提升数十倍,尤其适配人脸库数据量递增的场景。本项目选用IndexIDMap2索引模式,是适配人脸认证场景的最优索引结构。
基础初始化示例代码:
# FAISS索引初始化基础示例
import faiss
# 固定512维人脸向量
DIM = 512
# 创建带ID映射的平面L2索引
index = faiss.IndexIDMap2(faiss.IndexFlatL2(DIM))
# 加载本地持久化向量库
if os.path.exists("face_index.bin"):
index = faiss.read_index("face_index.bin")
print(f"加载人脸向量库,总数据量:{index.ntotal}")项目采用JWT令牌实现无状态登录认证,搭配Redis缓存实现登录态可控管理,解决传统Session认证依赖服务器存储、无法跨端口访问、登出不彻底的问题。JWT令牌包含用户ID、用户名、过期时间等核心载荷,经过HS256加密签名,无法被篡改,安全性极高。
核心生成令牌示例如下:
# JWT令牌生成基础示例
import jwt
from datetime import datetime, timedelta
JWT_SECRET = "change-strong-secret-2026"
JWT_ALGORITHM = "HS256"
JWT_EXPIRE_SEC = 3600
def create_jwt(user_id: int, username: str):
payload = {
"sub": user_id,
"username": username,
"exp": datetime.utcnow() + timedelta(seconds=JWT_EXPIRE_SEC)
}
token = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
# Redis缓存令牌
r.setex(f"login:token:{token}", JWT_EXPIRE_SEC, str(user_id))
return token智能刷脸登录页面的核心亮点是无按钮自动验证,依托前端自研的动静检测算法,无需用户手动点击登录,面向摄像头完成人脸静止、轻微移动即可自动触发识别,大幅提升使用便捷性。
基础灰度检测示例:
// 画面灰度采样基础示例
function sampleGray() {
mcv.width = 48;
mcv.height = 36;
mctx.drawImage(video, 0, 0, mcv.width, mcv.height);
const d = mctx.getImageData(0, 0, mcv.width, mcv.height).data;
let sum = 0;
for (let i = 0; i < d.length; i += 4)
sum += (d[i] + d[i + 1] + d[i + 2]) / 3;
return sum / (mcv.width * mcv.height);
}前端自动刷脸页面核心脚本:
<script>
const video = document.getElementById('video');
const mcv = document.getElementById('mcv');
const mctx = mcv.getContext('2d');
let lastGray = 0;
let preheatTime = 2500;
let ready = false;
let coolDown = 0;
const startTs = Date.now();
function detectMotion(){
if(coolDown > Date.now()) return;
const now = Date.now();
if(now - startTs < preheatTime) return;
const gray = sampleGray();
const diff = Math.abs(gray - lastGray);
lastGray = gray;
if(diff > 2){
// 触发刷脸请求
fetch("/face_login", {
method:"POST",
body: getImageFormData()
}).then(res=>res.json()).then(ret=>{
if(ret.success){
alert("登录成功");
coolDown = Date.now() + 6000;
}else{
alert("失败:"+ret.msg);
coolDown = Date.now() + 3000;
}
})
}
}
// 定时器,200ms采样一次
setInterval(detectMotion, 200);
</script>后端初始化是项目稳定运行的基础,包含跨域配置、全局参数配置、数据库连接、模型加载、向量库初始化五大核心模块,所有配置集中统一管理,便于后期维护与生产环境适配。
人脸注册接口是数据入库的核心接口,完整实现图片接收、人脸检测、特征提取、去重校验、旧数据覆盖、向量入库、用户数据入库全流程,业务逻辑严谨,规避重复注册、无效注册等问题。

人脸注册完整接口(main.py):
@app.post("/face_register")
async def face_register(username: str = Form(...), file: UploadFile = File(...)):
# 读取图片字节流转opencv图像
data = await file.read()
arr = np.frombuffer(data, np.uint8)
img_bgr = cv2.imdecode(arr, cv2.IMREAD_COLOR)
feat = get_face_feature(img_bgr)
conn = get_mysql_conn()
cur = conn.cursor()
# 查询该用户旧人脸ID,存在则删除FAISS旧向量
cur.execute("SELECT face_id FROM user WHERE username=%s", (username,))
row = cur.fetchone()
if row and row[0] is not None:
old_faceid = row[0]
index.remove_ids(np.array([old_faceid], dtype=np.int64))
# 全局查重,防止人脸绑定其他账号
D, I = index.search(feat, 1)
if D[0][0] < FACE_THRESHOLD:
match_faceid = int(I[0][0])
cur.execute("SELECT username FROM user WHERE face_id=%s", (match_faceid,))
r = cur.fetchone()
if r:
raise HTTPException(status_code=400, detail=f"该人脸已绑定账号:{r[0]}")
# 生成新人脸ID,写入FAISS
new_faceid = int(datetime.now().timestamp())
index.add_with_ids(feat, np.array([new_faceid], dtype=np.int64))
faiss.write_index(index, "face_index.bin")
# 写入MySQL
cur.execute("""
INSERT INTO user(username,face_id,fail_count,lock_until) VALUES(%s,%s,0,NULL)
ON DUPLICATE KEY UPDATE face_id=%s,fail_count=0,lock_until=NULL
""", (username, new_faceid, new_faceid))
cur.close()
conn.close()
return {"success":True, "msg":"人脸注册成功"}登录接口支持指定账号登录与全局刷脸登录两种模式,自适应前端请求参数,两种模式逻辑独立、互不干扰,适配不同业务场景。
人脸登录接口实现:
@app.post("/face_login")
async def face_login(username: str|None = Form(None), file: UploadFile = File(...)):
data = await file.read()
arr = np.frombuffer(data, np.uint8)
img_bgr = cv2.imdecode(arr, cv2.IMREAD_COLOR)
feat = get_face_feature(img_bgr)
conn = get_mysql_conn()
cur = conn.cursor()
target_faceid = None
if username:
# 指定用户名登录模式
cur.execute("SELECT id,face_id,fail_count,lock_until FROM user WHERE username=%s",(username,))
user = cur.fetchone()
if not user:
raise HTTPException(400, "用户不存在")
uid, fid, fail_cnt, lock_time = user
target_faceid = fid
# 判断账号锁定
if lock_time and datetime.now() < lock_time:
raise HTTPException(400, "账号临时锁定,请稍后重试")
if not fid:
raise HTTPException(400, "该用户未注册人脸")
# 只检索指定人脸
D,I = index.search(feat,1)
if int(I[0][0]) != fid or D[0][0] >= FACE_THRESHOLD:
# 失败计数
cur.execute("UPDATE user SET fail_count=fail_count+1 WHERE id=%s",(uid,))
cur.execute("SELECT fail_count FROM user WHERE id=%s",(uid,))
new_fail = cur.fetchone()[0]
if new_fail >= MAX_FAIL_COUNT:
lock_end = datetime.now() + timedelta(minutes=LOCK_MINUTES)
cur.execute("UPDATE user SET lock_until=%s WHERE id=%s",(lock_end,uid))
cur.close();conn.close()
return {"success":False,"msg":"人脸匹配失败","distance":float(D[0][0])}
else:
# 全局刷脸模式
D,I = index.search(feat,1)
if D[0][0] >= FACE_THRESHOLD:
return {"success":False,"msg":"未匹配到人脸","distance":float(D[0][0])}
target_faceid = int(I[0][0])
cur.execute("SELECT id,username,fail_count,lock_until FROM user WHERE face_id=%s",(target_faceid,))
user = cur.fetchone()
if not user:
return {"success":False,"msg":"匹配到人脸,但无绑定账号"}
uid,username,fail_cnt,lock_time = user
if lock_time and datetime.now() < lock_time:
raise HTTPException(400,"账号锁定")
# 登录成功,重置失败次数,生成token
cur.execute("UPDATE user SET fail_count=0,lock_until=NULL WHERE face_id=%s",(target_faceid,))
token = create_jwt(uid, username)
cur.close()
conn.close()
return {"success":True, "token":token, "username":username, "distance":float(D[0][0])}登出接口基于JWT令牌校验身份,读取前端请求头中的Authorization令牌,解析令牌并删除Redis中对应的登录缓存,实现即时登出、登录态失效,操作简单且安全性高,无残留登录数据。
登出接口实现:
@app.post("/logout")
async def logout(authorization:str|None = None):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(401,"未携带令牌")
token = authorization.replace("Bearer ","")
exists = r.exists(f"login:token:{token}")
if not exists:
raise HTTPException(401,"令牌无效")
r.delete(f"login:token:{token}")
return {"success":True, "msg":"已登出"}
# 向量库手动保存接口
@app.post("/save_index")
async def save_index():
faiss.write_index(index, "face_index.bin")
return {"success":True, "total":index.ntotal}
未正常登录的场景:

基础页面主打手动可控操作,适配测试、精准注册登录场景,支持摄像头实时采集、本地图片上传两种数据录入方式,兼容有无摄像头的设备环境,适配性极强。
基础页面核心JS:(index.html,摄像头截图+上传)
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let useFile = false;
// 摄像头截图
function captureImage(){
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.save();
ctx.scale(-1,1);
ctx.drawImage(video, -canvas.width,0, canvas.width, canvas.height);
ctx.restore();
return new Promise(resolve=>canvas.toBlob(resolve, "image/jpeg"));
}
// 注册按钮
document.getElementById("btnReg").onclick = async ()=>{
const username = document.getElementById("username").value.trim();
if(!username){alert("请输入用户名");return;}
const blob = await captureImage();
const fd = new FormData();
fd.append("username", username);
fd.append("file", blob, "face.jpg");
const res = await fetch("/face_register", {method:"POST", body:fd});
const ret = await res.json();
alert(ret.msg || ret.detail);
}
</script>
未正常识别的情况:

智能刷脸页面是项目的核心亮点页面,摒弃传统手动点击操作,依托前端动静检测算法实现全自动刷脸登录,交互体验更贴合商业化人脸登录产品。
原生开发中最容易出现的问题是人脸匹配不准、阈值失效、误判频发,核心原因是忽略特征向量归一化处理。InsightFace输出的原始向量模长约19,L2距离会被放大361倍,导致预设的0.65阈值完全失效,出现同人匹配失败、外人匹配成功的严重问题。
项目完善了多层安全风控机制,规避暴力破解、人脸冒用、登录态劫持等安全风险。
针对服务重启数据丢失、页面误触发、接口高频请求等稳定性问题,项目做了全方位优化。
本项目部署流程简单,无需复杂配置,适配Windows、Linux服务器,零基础可快速部署上线,核心部署步骤如下:
第一步:环境搭建:搭建Python环境,安装项目依赖库,包括fastapi、uvicorn、insightface、faiss-cpu、pymysql、redis、opencv-python等核心依赖。
依赖安装命令:
pip install fastapi uvicorn insightface faiss-cpu pymysql redis opencv-python numpy pyjwt第二步:基础服务启动:启动本地Redis服务(默认6379端口,无密码),创建MySQL数据库daysurgery,新建user用户表,配置对应账号密码。
MySQL建表SQL:
CREATE DATABASE IF NOT EXISTS daysurgery;
USE daysurgery;
CREATE TABLE IF NOT EXISTS user(
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
face_id BIGINT NULL,
fail_count INT DEFAULT 0,
lock_until DATETIME NULL
);第三步:项目配置修改:将前后端文件放置同一目录,修改数据库连接参数、JWT密钥等配置,生产环境建议写入环境变量,避免硬编码泄露)。
项目目录结构参考:
face-login/ ├─ main.py ├─ face_index.bin ├─ static/ │ ├─ index.html │ └─ login2.html
第四步:服务启动:启动后端服务,服务默认监听8000端口,自动加载人脸模型与向量库,完成服务初始化。
uvicorn main:app --host 0.0.0.0 --port 8000
第五步:功能访问:浏览器访问对应地址

这个项目的核心是基于FastAPI+InsightFace+FAISS的本地人脸登录系统,最大的亮点就是全程离线运行,不依赖任何云端AI接口,把人脸检测、特征提取、向量比对全部放在本地服务器完成,很好解决了传统人脸认证隐私泄露、网络卡顿、接口收费的痛点。
整套方案采用前后端分离架构,后端用FastAPI承接接口,InsightFace提取512维人脸向量,FAISS负责快速向量检索,搭配MySQL存用户基础信息、Redis管控JWT登录令牌,实现人脸注册、双模式刷脸登录、账号锁定、一键登出完整闭环。开发里最容易踩坑的地方,就是人脸向量L2归一化,忽略这一步会直接造成阈值失效,出现识别错乱;
基于这个项目落地的核心,先单独跑通InsightFace人脸提取和FAISS向量检索的最小Demo,理解向量相似度原理;再逐步接入数据库、JWT鉴权,最后调试前端摄像头采集。遇到识别不准时,优先排查向量归一化和相似度阈值,而不是盲目换模型。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。