基于人脸识别的景区票务系统设计与Python实现

发布时间:2026/9/11 16:25:25
基于人脸识别的景区票务系统设计与Python实现 简介一份基于人脸识别的景区票务系统Python毕业设计源码采用DjangoMySQL实现面向计算机专业学生完成毕设或课程设计。系统分为前台与后台前台支持用户注册、公告查看、购票信息检索、在线购票含人脸录入与支付后台提供管理员信息管理、用户管理、公告与购票发布、订单与支付统计图表、验票及退票登记注册用户则可进行个人资料维护、订单查看、摄像头在线人脸识别验票与退票管理。包体共663个文件以Python源码282个py、编译文件、前端资源css/js、图片及数据库脚本sql为主附带运行所需虚拟环境组件与说明文档压缩包约110.98MB。环境配置清晰基于Python 3.6.8与MySQL 5.7便于本地部署调试。目前已有60人学习下载适合需要参考完整业务流与人脸识别集成做二次开发的读者可作为毕业设计答辩项目的基础框架。1. 基于人脸识别的景区票务系统的技术构成与毕业设计定位很多毕设标题里的“基于人脸识别的景区票务系统”实际考察的并不是你训练了一个多厉害的人脸识别模型而是你是否能把“识别”这件事嵌入到一套完整的业务闭环里游客注册时录入人脸、购票时绑定人脸、入园闸机做人脸比对、比对成功扣减票次数、后台统计入园记录。真正让人头疼的不是推理精度高了零点几个百分点而是“识别成功之后怎么改数据库里那张票的状态”“失败之后要不要放行”“游客照片存在哪里、特征向量怎么存”。这套系统的工程价值集中在票务状态机和特征数据的读写设计上人脸识别只是入口。代码用 Python 来写比较顺手常见的搭配是 OpenCV 或 face_recognition 做检测与特征提取Flask/FastAPI 做接口OpenCV 做摄像头取流再配一个 SQLite 或 MySQL。所谓 LW 是配套的论文文档毕设答辩时用来解释系统结构和实现过程源码本身才是核心。这篇文章就顺着这个标题把一套可复现的景区票务系统从算法选型、数据库设计、Python 核心代码到实际部署的坑位讲清楚。有 Python 基础的人可以直接照着搭不需要读完整本教材。已经做过 Web 开发的人也能在参数设计、状态机划分和并发处理上找到有价值的信息。2. 人脸识别算法选型与景区票务特征存储设计2.1 票务场景下的人脸识别流程拆解景区闸机的人脸识别和手机解锁不太一样。手机解锁是“1 对 1”比对闸机是“1 对 N”N 是当天已购票且未入园的游客数量。这个区别直接决定了系统架构不能把摄像头拍到的人脸特征和数据库里所有人逐一比对那样响应时间会随用户量线性上涨。常见做法是分两步走先用人脸检测框出画面里的人脸再做人脸特征编码最后特征比对只在候选集里做。候选集可以按景区、按当日有效票、按闸机分组来缩小。整个流程拆成四段注册阶段游客上传照片或现场拍照系统检测人脸质量模糊度、角度、遮挡通过后提取特征向量存入数据库购票阶段下单时绑定人脸特征 ID 和票种生成一条“未使用”状态的入园凭证检票阶段闸机摄像头抓拍人脸检测 特征提取在当日有效票集合里检索最近邻入园后处理比对成功且票状态为“未使用”则更新状态为“已入园”记录时间戳和闸机编号这里有一个常被忽略的点人脸特征向量一旦入库就基本不再变更但票状态是高频变化的。把特征和票分开建表比对服务只查特征表找到 user_id 之后再查票表直接用 user_id 过滤不需要每次都从票表带出人脸特征。景区高峰期闸机并发请求高时这种拆分能显著降低数据库压力。2.2 Python 可用的识别算法对比与选型毕设场景里很少有人从头训模型绝大多数用现成库。下表是几种常见方案的对比按集成难度排序。方案检测方式特征向量维度离线可用部署体积适用场景OpenCV Haar CascadeHaar 特征分类器无仅检测是很小人脸检测不能用于身份比对face_recognitiondlib 封装HOG / CNN128 维是中等小规模票务系统毕设首选OpenCV DNN ResNet深度学习128 维是较大中等规模精度略好InsightFaceArcFaceRetinaFace512 维是较大大规模或演示效果要求高如果目标只是把毕设跑通并能在论文里写清楚原理用 face_recognition 最省事。它内置了人脸检测、关键点定位和 128 维特征编码一行代码就能拿到特征向量。但要注意它的底层层是 dlib安装时在 Windows 上容易出问题Python 3.9 及以上版本建议用 3.8 或 3.10 的 wheel 包安装不要直接在最新版本硬装。Linux 服务器上则要提前装好 cmake 和 libx11-dev否则编译 dlib 会卡很久。更追求效果的话可以用 InsightFace 的 buffalo_l 模型它对侧脸和大角度姿态的鲁棒性比 dlib 好很多而且提供了 ArcFace 损失函数训练出的特征空间类内距离更紧凑。缺点是依赖 onnxruntime模型文件约 300MB对于毕设演示部署略重。2.3 特征存储的表结构与 SQL 设计特征向量本身是一个浮点列表128 维或 512 维。在 MySQL 里可以有几种存法把向量转成 JSON 字符串存 TEXT转成 Base64 存 VARCHAR或者用二进制打包成 BLOB。毕设阶段用 JSON 文本是最直观的数据可读性也更好。维度固定所以也可以直接存成 128 个独立字段但读写的 SQL 会变得很长没必要。票务侧的订单表和一个典型的人脸特征表示如下-- 游客人脸特征表一个用户最多一条有效特征 CREATE TABLE face_profile ( id INT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(32) NOT NULL, feature_json TEXT NOT NULL COMMENT 128维特征向量格式如 [0.0123, -0.0456, ...], face_image_path VARCHAR(255) COMMENT 注册照片存储路径, status TINYINT DEFAULT 1 COMMENT 1有效 0已注销, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id) ); -- 景区票务订单表 CREATE TABLE ticket_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id VARCHAR(32) NOT NULL, ticket_type TINYINT COMMENT 1单次票 2多次票, total_count INT DEFAULT 1 COMMENT 可用次数, used_count INT DEFAULT 0 COMMENT 已使用次数, status TINYINT DEFAULT 0 COMMENT 0未入园 1已入园 2已过期 3已退票, gate_no VARCHAR(16) COMMENT 入园闸机编号, enter_time DATETIME COMMENT 最近一次入园时间, expire_time DATETIME, KEY idx_order_user (user_id), KEY idx_order_status (status) );这两张表分开设计的好处是闸机核验时只拿 user_id 去查票务订单不关心人脸特征的具体内容而注册新照片时只需要更新 face_profile不会影响已经生成的订单。这也是“人脸特征”和“票状态”解耦的核心设计。SQLite 版本可以去掉 ENGINE 和 COMMENT 相关语法其他逻辑不变。3. Python 实现人脸识别票务核心代码3.1 用 FastAPI 暴露的票务接口主框架后端接口自己写Spring Boot 合适但稍重Python 毕设用 FastAPI 更顺手自带 Swagger 文档便于演示答辩。主框架只需要三个接口注册人脸、核验入园、查询订单状态。下面的代码把核心链路跑通识别部分用 face_recognition数据库用 SQLite避免配置 MySQL 的额外开销。# app.py import sqlite3 import face_recognition import numpy as np from fastapi import FastAPI, UploadFile, File, Form from pydantic import BaseModel app FastAPI(title景区人脸票务系统) DB_PATH ticket.db def get_db(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn app.post(/api/register) async def register(user_id: str Form(...), file: UploadFile File(...)): image_bytes await file.read() # 从上传的照片中提取人脸特征 image face_recognition.load_image_file_bytes(image_bytes) face_encodings face_recognition.face_encodings(image) if len(face_encodings) 0: return {code: 1, msg: 未检测到人脸请重新拍摄} feature face_encodings[0].tolist() conn get_db() # 一个用户只保留最新一条特征先逻辑作废旧记录 conn.execute(UPDATE face_profile SET status0 WHERE user_id?, (user_id,)) conn.execute( INSERT INTO face_profile (user_id, feature_json, status) VALUES (?,?,1), (user_id, str(feature)), ) conn.commit() conn.close() return {code: 0, msg: 注册成功, feature_len: len(feature)} app.post(/api/checkin) async def checkin(gate_no: str Form(...), file: UploadFile File(...)): image_bytes await file.read() image face_recognition.load_image_file_bytes(image_bytes) face_encodings face_recognition.face_encodings(image) if len(face_encodings) 0: return {code: 1, msg: 未检测到人脸} target_feature face_encodings[0] conn get_db() # 拉取所有有效人脸特征转成 numpy 数组批量比对 rows conn.execute( SELECT user_id, feature_json FROM face_profile WHERE status1 ).fetchall() known_ids, known_features [], [] for row in rows: known_ids.append(row[user_id]) known_features.append(np.array(eval(row[feature_json]))) if len(known_features) 0: conn.close() return {code: 2, msg: 库中无注册人脸} # face_recognition.compare_faces 返回布尔列表基于欧氏距离阈值 matches face_recognition.compare_faces(known_features, target_feature, tolerance0.40) distances face_recognition.face_distance(known_features, target_feature) matched_idx np.argmin(distances) if len(distances) else -1 if not any(matches): conn.close() return {code: 3, msg: 人脸比对失败不匹配任何游客} user_id known_ids[matched_idx] # 核验该用户是否有未使用的有效票 order conn.execute( SELECT * FROM ticket_order WHERE user_id? AND status0 AND total_count used_count ORDER BY id LIMIT 1 , (user_id,), ).fetchone() if order is None: conn.close() return {code: 4, msg: 无有效门票请联系售票窗口, user_id: user_id} conn.execute( UPDATE ticket_order SET used_count used_count 1, status CASE WHEN total_count used_count 1 THEN 1 ELSE 0 END, gate_no ?, enter_time datetime(now,localtime) WHERE id ? , (gate_no, order[id]), ) conn.commit() conn.close() return {code: 0, msg: 核验通过请入场, user_id: user_id}face_recognition.load_image_file_bytes是已有版本支持的方法不同小版本可能有差异如果你的版本没有这个方法可以用face_recognition.load_image_file(BytesIO(image_bytes))替代。比对时先算全部距离再取最小距离对应的人作为匹配结果避免只靠布尔值导致多人同时匹配。闸机请求里带着gate_no实参便于事后回溯哪个入口放行了哪个人。3.2 人脸特征比对参数 tolerance 的含义与调参方向compare_faces的tolerance是最需要理解的参数它直接决定误识率FAR和拒识率FRR的平衡。face_recognition 对 128 维特征向量计算欧氏距离距离越小表示两个人脸越相似。默认 tolerance0.6 偏向宽松适合日常照片管理场景但用于景区闸机太松容易放行长得像但不是本人的游客。实际毕设建议先从 0.40 开始测试记录误识和拒识情况。距离在 0.35 以下一般判定为同一个人0.35 到 0.45 之间要看照片质量。晚上光线差时距离会整体偏大可以通过记录测试集上的距离分布来调收集 20 组“本人”和“非本人”照片画出两组距离的直方图把 tolerance 定在两组分布重叠最少的位置。这个过程写进论文里是很加分的实验数据。3.3 票状态机的流转逻辑票务系统的核心不只是写 SQL而是把状态流转定义清楚。简单版本只用 status 字段0 代表未入园1 代表已入园2 代表已过期。但单次票和多次票的流转是不同的多次票每次入园都应记录一次门票用完才变为“已入园”。上面的代码用了 total_count 和 used_count 两个字段状态由这两个字段共同决定这就是最简单的状态机。注意代码里status CASE WHEN total_count used_count 1 THEN 1 ELSE 0 END这个边界判断更新时 used_count 自增 1然后判断自增后是否用完了所有次数。如果票只支持单次入园那么 total_count1 时不管 used_count 原来是多少入园后状态必须置 1。如果不加这个条件单次票会被重复刷入园这在实际订单系统里是致命的。4. 人脸识别景区票务系统部署与常见排错4.1 摄像头采集端的分辨率与帧率设置闸机场景一般用 USB 摄像头或网络摄像头分辨率不是越高越好。1920x1080 的视频流在多人同时经过闸机时会导致检测帧率下降人脸检测框范围变大服务器 CPU 占用飙升。常见做法是把分辨率限制在 1280x720帧率 15fps 左右保证单张人脸区域像素不少于 80x80 即可。OpenCV 从摄像头取流需要先设置 CAP_PROP_FRAME_WIDTH 等属性再开始循环读取如果在读取后再设置往往不生效。取到帧后还要做一次人脸区域裁剪把裁剪后的人脸图传给 face_recognition 提取特征这样可以减少背景干扰。4.2 光线与角度导致的拒绝识别问题景区闸机最容易翻车在逆光和侧脸上。逆光时人脸过暗检测器直接扫不到人脸侧脸超过 45 度时128 维特征与正脸注册照的距离会显著变大导致拒识。解决办法不是换算法而是从采集侧处理。在注册阶段强制要求用户正对摄像头采集现场照片而不是上传过度的美颜照片在闸机端可以考虑用两个摄像头形成多角度采集或者加补光灯改善逆光。如果场景无法加装硬件就要在软件上做提示。在 checkin 接口返回 code3 时闸机屏幕提示“请正对摄像头”并重新采集连续 3 次失败转人工窗口处理。毕设答辩时被问“真实场景识别率不高怎么说”这个兜底逻辑比单纯调阈值有说服力得多。4.3 高峰期并发请求与数据库读写优化SQLite 在并发写时表现不理想但并发读是可以的。闸机核验接口的瓶颈不在人脸比对而在每次请求从数据库拉全量特征。游客量上来之后每秒 10 次请求就会让接口响应超过 2 秒现场体验很差。对此可以做一个内存缓存层在应用启动时把 face_profile 中 status1 的特征加载到一个全局 list 中注册或更新人脸后刷新缓存核验时直接比对内存数据数据库只在状态更新时才访问。这种“读用缓存、写走库”的方式在毕设规模下足够稳定代码也容易解释。4.4 边缘端部署对大量人脸数据的影响如果部署在树莓派或 Jetson Nano 这类边缘设备上人脸特征比对的内存占用会增长128 维 float 数组一万人占用的内存大约 5MB纯内存比对压力不大但人脸检测和对齐会占满 CPU。此时更建议把识别逻辑拆成两段边缘设备只做检测和裁剪把裁剪好的人脸图传到后端做特征提取与比对。这样做可以把计算压力集中到服务端边缘设备只负责图像采集和结果展示符合真实景区闸机的部署方式。5. 阈值调优、防照片攻击与验收方法毕设答辩的演示环节考官通常会直接拿手机里的照片对着摄像头试看系统能不能拦住。照片攻击是最常见的测试手段OpenCV 和 face_recognition 本身不带活体检测解决办法是在闸机端加一个小小的动作指令例如“请眨眨眼”或“请左右转头”由前端或本地脚本根据人脸关键点做动作判定。这个方案简单可解释论文也能写出清晰流程。拿到一个待调优的闸机我一般按下面三个步骤做验收与调参# 评估测试脚本统计误识和拒识情况 test_photos [self_A.jpg, self_B.jpg, other_A.jpg] for photo_path in test_photos: test_feature extract_feature(photo_path) dist np.linalg.norm(base_feature - test_feature) print(f{photo_path}: distance{dist:.4f})第一步准备 10 个测试者的正脸照片和各自的侧脸、光照变化照片第二步设定 tolerance 从 0.5 开始每次递减 0.05记录误识数与拒识数第三步选一个两类错误都较低的阈值。如果无论怎么调都达不到要求就要返回去检查注册照片质量而不是继续压阈值。人脸特征缓存更新的时机也要注意在 register 接口提交成功后必须同步刷新缓存否则刚注册的游客去闸机核验会显示“无有效门票”。这个 bug 在联调阶段非常典型根因就是数据库写进去了而内存缓存没更新。把这个系统跑通之后可以继续扩展多景区票务共享、重复人脸检测、基于 Redis 的分布式特征缓存等方向。但毕设的得分点永远是“闭环可用”和“参数有依据”不是“模型选得多新”。本文还有配套的精品资源点击获取