人脸识别:基于ONNX Runtime的人脸识别比对系统全栈实战
基于ONNX Runtime的人脸识别比对系统全栈实战1. 项目简介2. 整体架构3. 模型准备与加密加载4. 人脸检测与图像预处理5. 多任务属性推理6. 人脸比对与相似度映射7. 前后端交互设计8. 前端视觉与体验优化9. 部署与演示10. 总结与展望一个完整的在线人脸识别比对Web应用涵盖人脸检测、属性分析、活体特征提取与相似度计算。本文将带你一步步拆解其技术实现并附上在线Demo方便体验。1. 项目简介这是一个轻量级的人脸识别比对工具用户上传两张含有人脸的图片系统自动完成人脸检测、年龄/性别估计、姿态角计算、活体特征提取并输出两张人脸的相似度分数。整个系统采用 **Python Tornado** 作为后端框架**ONNX Runtime** 进行模型推理前端使用原生 JavaScript 配合 jQuery 交互部署简单适合个人学习和小规模应用。在线演示地址http://faceqianmian.top/static/index.html2. 整体架构系统分为三层- **前端**HTML5 CSS3 双栏布局通过 AJAX 调用后端 RESTful API。- **后端**Tornado Web 服务提供图片上传、人脸检测、相似度比对三个接口。- **模型层**使用 **ONNX Runtime** 加载加密的 ONNX 模型文件进行高效推理。核心流程图上传图片 → 图片接收与格式校验 → 人脸检测(Detect) → 人脸裁剪 →多模型推理(性别/年龄/活体/角度) → 返回结果(特征、属性、框) →前端展示 用户触发比对 → 余弦相似度计算 → 得分映射 → 显示相似度3. 模型准备与加载为了保护模型资产本项目将 ONNX 模型文件使用 AES-CBC 加密存储运行时动态解密并加载到内存中。加密方式python from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.padding import PKCS7 def decrypt_file_to_bytes(input_path, key, iv): key bytes.fromhex(key) iv bytes.fromhex(iv) with open(input_path, rb) as fin: ct_bytes fin.read() cipher Cipher(algorithms.AES(key), modes.CBC(iv), backenddefault_backend()) decryptor cipher.decryptor() unpadder PKCS7(algorithms.AES.block_size).unpadder() plain_text unpadder.update(decryptor.update(ct_bytes)) unpadder.finalize() return plain_text 加载模型时直接传入解密后的字节流避免模型文件在磁盘上以明文形式存在python import onnxruntime as ort face_live_model ort.InferenceSession( decrypt_file_to_bytes(model/face_live.onnx.enc, key, iv), providers[CPUExecutionProvider] ) 4. 人脸检测与图像预处理4.1 检测模型预处理采用标准的 detect.onnx 模型例如 YOLO 变体输入尺寸为 640x640。为了保持宽高比使用 **letterbox** 填充方式填充区域使用灰色 (128,128,128)python def detect_pre_process(img, output_size(640, 640)): # 等比例缩放 居中填充 width, height img.size new_img Image.new(RGB, output_size, color(128,128,128)) ratio min(output_size[0]/width, output_size[1]/height) new_size (int(width*ratio), int(height*ratio)) resized img.resize(new_size, Image.BILINEAR) left (output_size[0] - new_size[0]) // 2 top (output_size[1] - new_size[1]) // 2 new_img.paste(resized, (left, top)) img_array np.array(new_img) / 255.0 img_array img_array.transpose((2, 0, 1)) # CHW return img_array, width, height 4.2 检测后处理与坐标映射模型输出的检测框是相对于 640x640 画布的归一化坐标中心点 x, y宽 w高 h置信度 conf。需要根据原始图片的宽高和填充情况反算到原始图像坐标系python if width height: midx box[0] * (width/640) midy box[1] * (width/640) - (width-height)/2 w box[2] * (width/640) h box[3] * (width/640) else: midx box[0] * (height/640) - (height-width)/2 midy box[1] * (height/640) w box[2] * (height/640) h box[3] * (height/640) x1 midx - w/2 y1 midy - h/2 x2 midx w/2 y2 midy h/2 为了后续属性分析更准确将检测框向外扩展一定比例作为裁剪区域python x1_crop x1 - w * 0.3 y1_crop y1 - h * 0.6 x2_crop x2 w * 0.3 y2_crop y2 h * 0.2 # 边界裁剪 x1_crop max(0, x1_crop) y1_crop max(0, y1_crop) x2_crop min(width, x2_crop) y2_crop min(height, y2_crop) face_crop img.crop((x1_crop, y1_crop, x2_crop, y2_crop)) 5. 多任务属性推理裁剪后的人脸区域统一缩放到各自模型要求的尺寸并进行标准化。- **性别、活体、年龄**输入 224x224使用 ImageNet 均值和标准差归一化。- **角度**输入 64x64同样的归一化处理。- **性别分类**输出两个类别的概率取 argmax 得到男女。- **年龄回归**输出值加 1 并四舍五入得到整数值。- **活体特征**直接返回模型输出的浮点数向量用于后续比对。- **姿态角**模型输出三个角度值yaw, pitch, roll。预处理代码示例python def pre_process(img, size(224, 224)): mean np.array([0.485, 0.456, 0.406]).reshape(3,1,1) std np.array([0.229, 0.224, 0.225]).reshape(3,1,1) img_array np.array(img.resize(size, Image.BILINEAR)) / 255.0 img_array img_array.transpose((2,0,1)) return ((img_array - mean) / std).astype(float32) 6. 人脸比对与相似度映射从两张图片中分别提取到活体特征向量后计算**余弦相似度**python def cosine_similarity(vec1, vec2): vec1, vec2 np.array(vec1), np.array(vec2) dot np.dot(vec1, vec2) norm np.linalg.norm(vec1) * np.linalg.norm(vec2) return dot / norm if norm ! 0 else 0 但余弦相似度的范围是 [-1, 1]而我们希望输出 0~100 的分值更直观。本项目采用了一个分段线性映射函数python def get_score(x): if x 0.5: score 120 * x elif x 0.616: score 26.041666 * x 73.95833 else: score 258.62069 * x - 69.31034 score max(0, min(100, score)) return score 该映射将阈值 0.5 映射到 60 分0.616 映射到 90 分整个函数是单调递增的保证分数越高表示越相似。实际应用中80 分以上通常可认为是同一人。7. 前后端交互设计7.1 后端接口- **上传接口** /face-api/v1/face/uploadPOST 接收 image 文件校验格式与大小使用 Pillow 的 ImageOps.exif_transpose 自动纠正图片方向存储到 static/images/返回文件路径。- **人脸检测与属性接口** /face-api/v1/face/detect_list接收 JSON内容为多个图片路径数组 params批量处理并返回每个人脸的属性、特征已加密以及人脸框。活体特征使用简单的异或加密后经 Base64 编码确保传输中不被篡改。- **人脸比对接口** /face-api/v1/face/match接收两个加密特征字符串解密后计算余弦相似度并映射为 0~100 分数。7.2 前端逻辑前端使用 jQuery layer 弹窗流程如下1. 用户选择图片 → 调用上传接口获取服务器路径。2. 调用 detect_list 接口传入图片路径获取年龄、性别、姿态角、框坐标及加密特征并显示在原图上后端将框绘制在图片上。3. 用户点击“开始比对” → 取出两张图片的加密特征调用 match 接口将返回的分数更新到页面中央的相似度显示区。前端使用 $.ajax 发送请求同时显示加载动画并对网络异常、超时等进行了友好提示。8. 前端视觉与体验优化界面采用渐变背景、圆角卡片和悬浮动画让整体风格现代且亲和。双栏布局中间放置 VS 标志和实时相似度一目了然。底部提供了详细的使用说明解释姿态角Yaw/Pitch/Roll的含义和推荐范围帮助用户理解人脸质量对识别的影响。同时页面绘制了人脸框并将框坐标、置信度等信息清晰展示。9. 部署与演示你可以通过以下地址直接体验完整功能http://faceqianmian.top/static/index.html项目依赖较少所有模型均加密存储启动脚本直接运行即可默认 80 端口bash pip install tornado pillow onnxruntime numpy cryptography python app.py 若需修改端口可通过命令行参数传入bash python app.py 8080 10. 总结与展望本文展示了一个面向实用场景的轻量级人脸识别比对系统的全栈实现。通过 ONNX Runtime 的跨平台推理能力和 Tornado 的异步高性能我们可以在普通服务器上流畅运行多个模型。模型加密、特征加密等措施也在一定程度上保护了核心资产。未来可扩展方向- 接入人脸特征数据库实现 1:N 的人脸检索- 增加活体检测动作指令提升防攻击能力- 前端使用 WebSocket 实现更流畅的实时摄像头识别。希望这篇实战分享能帮助你快速搭建自己的人脸识别应用如果有任何问题或建议欢迎在评论区交流