资讯动态

Python+Django构建人脸识别智慧医疗预约挂号平台

发布时间:2026/10/16 17:57:58 来源:尧图企业网站定制
项目背景为什么选择“人脸识别智慧医疗”这个组合去年接到一个医疗信息化的模拟项目X需求方给的要求很明确做一个预约挂号平台但必须带人脸识别。当时我第一反应是“这个需求是不是有点折腾”——毕竟常规挂号系统用身份证手机号就能完成实名制为什么非要上人脸识别后来和某公司医疗组的同事聊完才发现这背后其实是个很现实的痛点号贩子问题。普通平台在身份核验环节太薄弱黄牛可以批量注册账号、囤积专家号源再高价转卖而人脸识别能把“实名制”从“输入身份证号”升级为“你就是你”这个差别在医疗场景里意义重大。另外一个考虑是流程效率。医院现场挂号窗口常年排队自助机又容易被老人吐槽不会用。如果患者能在手机上完成人脸信息录入和实名认证到院后直接刷脸取号整个就诊动线会顺畅很多。所以这个项目的定位不是一个“玩具Demo”而是奔着能落地、能对接真实医院业务流程去的。技术栈上选了Python Django原因很直接Python生态里有人脸识别最顺手的工具链Django自带Admin后台、ORM和认证体系开发一个带管理端、用户端、医生端三端的Web平台Django的成熟度比Flask高不少。配合MySQL存储业务数据、Redis做缓存和会话管理整套架构在中小型医院场景里完全扛得住。这篇文章我会从整体设计、人脸识别模块、医生排班模块、预约链路实现四个核心维度做完整拆解最后把开发中踩过的高频问题整理成排查速查表。如果你正准备做类似的医疗/政务类实名制平台这篇内容可以直接当参考骨架用。1. 整体设计与思路拆解1.1 业务角色与核心流程整个平台围绕三类角色展开患者、医生、平台管理员。患者端的核心操作链路是“注册/登录 → 人脸信息采集与实名认证 → 查看医生排班 → 预约挂号 → 到院扫码/刷脸签到”。医生端核心是“查看排班计划 → 设置可预约时段 → 管理停诊/放号”。管理员端则负责“科室管理、医生信息维护、排班规则配置、号源监控、异常预约处理”。这三条链路之间有一个共同的数据枢纽排班表和号源表。排班表决定“某医生在某天的某个时段是否出诊”号源表决定“该时段还剩多少可预约名额”。人脸识别在这里的作用是给“患者身份”加一把锁让挂号的真实性和唯一性有据可查。整个项目拆成了五个独立Appusers用户与认证、departments科室、doctors医生信息、schedules排班与号源、appointments预约与签到。边界清晰之后各模块可以并行开发后面维护也不会互相踩脚。1.2 技术选型背后的取舍人脸识别算法选型上我对比过三条路线。第一条是调用云端API比如一些商用平台的人脸检测与比对服务。优点是精度高、无需自己训练模型缺点是每次调用都要花钱而且患者的人脸数据要传到第三方服务器这在医疗场景里涉及隐私合规问题基本可以直接劝退。第二条是使用开源模型自己封装服务比如用MTCNN做人脸检测、FaceNet或ArcFace做人脸特征提取。精度确实高但部署复杂度也上去了需要额外的模型文件和推理服务对于Django为主的团队来说维护成本偏重。第三条是使用OpenCV Dlib的组合方案。Dlib自带HOG人脸检测和ResNet特征提取模型纯CPU就可以跑识别速度在几百毫秒级别单机QPS能支撑中小型医院的压力。综合评估下来我们选了这条路配合自己写的一个简单特征比对模块完全够用。前端选型上没上重框架Django模板 Bootstrap 少量JavaScript就够了。管理端直接复用Django Admin再写几个自定义页面。有人可能会问为什么不用Vue或React说实话对这种内部管理移动端H5为主的项目模板渲染反而更快、更容易维护而且Django的CSRF、表单校验等机制直接复用省掉了一堆前后端联调的活。1.3 数据库模型的核心设计设计数据库时第一原则是让排班和号源的数据结构能同时支持“班次展示”和“号源扣减”两种操作。我的核心表设计如下用户表User继承Django自带的AbstractUser扩展phone、id_card、face_feature人脸特征向量存为Text或Binary、face_photo、is_verified是否完成人脸实名。医生表Doctor关联用户表包含title职称、department科室、introduction、consultation_fee挂号费。排班规则表ScheduleRule配置每周几出诊、午别上午/下午、时段、号源总数。排班实例表Schedule按日期生成的出诊记录包含doctor、date、time_slot、total_slots、booked_slots、status正常/停诊。号源表Slot排班实例下的具体号源包含slot_time、is_booked、appointment关联。预约表Appointment记录患者挂号的详细信息包括appointment_no挂号流水号、patient、doctor、schedule、slot、status。排班规则表存在的意义是让管理员可以“一次配置、周期复用”而排班实例表是真正被挂号操作锁定的那张表。这两个表分开就避免了一个排班规则在多个日期下共享同一份号源计数导致超卖的问题。2. 人脸识别模块的详细实现2.1 采集端如何可靠地拿到人脸照片人脸识别第一步是采集清晰、合规的人脸照片。我们用的是HTML5的摄像头调用方案核心就一个navigator.mediaDevices.getUserMedia()。但这里有个细节坑——必须引导用户正对镜头、光线充足否则拍出来的照片角度歪、亮度暗后面特征提取的质量会大打折扣。我在采集页放了实时视频流预览同时叠加了一个半透明的椭圆形取景框提示用户把脸放到框内。拍照动作触发后先取当前帧然后在服务端做一轮质量检查人脸检测框是否清晰、人脸面积是否达到阈值、亮度是否正常。如果检测不到人脸直接拒绝保存并提示重新采集。# 视图函数中的人脸质量检查逻辑 def check_face_quality(request): image_file request.FILES.get(face_image) if not image_file: return JsonResponse({code: 400, msg: 未上传图片}) # 读取并解码图片 import cv2 import numpy as np data np.frombuffer(image_file.read(), np.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) # 使用Dlib检测器检测人脸 face_detector dlib.get_frontal_face_detector() gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces face_detector(gray, 1) if len(faces) 0: return JsonResponse({code: 400, msg: 未检测到人脸请正对光线充足处重试}) if len(faces) 1: return JsonResponse({code: 400, msg: 检测到多张人脸请确保只有本人入镜}) # 检查人脸面积占比 f faces[0] h, w img.shape[:2] face_area (f.right() - f.left()) * (f.bottom() - f.top()) if face_area / (h * w) 0.15: return JsonResponse({code: 400, msg: 人脸占比过小请靠近一些}) return JsonResponse({code: 200, msg: 质量合格})这段代码看起来简单但“质量合格”这四个字背后决定了后续识别准确率的80%。很多人做这类项目时比较糙拍下来就直接存库结果识别阶段各种匹配不上其实源头就在采集这步没把关。2.2 特征提取与比对的三种方案采集到合格照片后需要提取人脸特征向量这个向量才是后续比对的核心。Dlib官方提供了一个预训练的ResNet模型dlib_face_recognition_resnet_model_v1可以把人脸映射成128维的浮点向量。这个向量的特点是同一个人的不同照片在欧几里得空间中距离很小不同人之间的距离很大。我们就是用这个模型做特征提取。比对阶段代码逻辑如下def compute_face_feature(image_path): detector dlib.get_frontal_face_detector() sp dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) facerec dlib.face_recognition_model_v1(models/dlib_face_recognition_resnet_model_v1.dat) img dlib.load_rgb_image(image_path) faces detector(img, 1) if len(faces) 0: return None shape sp(img, faces[0]) feature facerec.compute_face_descriptor(img, shape) return np.array(feature) def match_face(feature1, feature2, threshold0.45): distance np.linalg.norm(feature1 - feature2) return distance, distance threshold关于阈值我之前做过一组实验对同一人不同光线下的照片距离通常在0.2-0.35之间对不同人的照片距离普遍大于0.6。所以阈值设在0.45-0.5之间比较均衡。设太低会把人拒之门外误拒设太高又容易出现认错人误纳。实际项目中我建议先用数据集合测一轮画出距离分布曲线再选曲线交叉最低点附近的阈值。2.3 防伪造与活体检测人脸识别项目如果只做静态比对攻击成本很低——一张照片就能绕过。我们在项目中增加了一个最简单的活体检测让用户按指示完成“眨眼”动作。实现思路是连续采集若干帧用Dlib检测68个关键点计算眼睛纵横比Eye Aspect Ratio, EAR。正常眨眼的特征是EAR值从正常状态突然变小、再恢复这个变化过程就表示一次眨眼动作。def eye_aspect_ratio(eye_points): A np.linalg.norm(eye_points[1] - eye_points[5]) B np.linalg.norm(eye_points[2] - eye_points[4]) C np.linalg.norm(eye_points[0] - eye_points[3]) return (A B) / (2.0 * C)在视频流中连续检测到EAR值低于某个阈值比如0.2至少一帧就判定为一次眨眼。当然这个方案不算最安全的如果想更稳可以引入随机动作组合左右摇头、张嘴等或者接入更高级的深度活体检测模型。但对预约挂号这种场景来说增加这一步已经能挡掉绝大部分“拿张照片来冒用身份”的情况。3. 医生排班模块的完整落地3.1 排班规则配置怎么把“周期”转换成“实例”排班模块是这个平台里最容易绕晕的部分。业务方的需求通常很灵活“王医生每周一上午、周三全天出诊”“李医生只在双周周五下午出诊”。如果直接在数据库里逐条录入排班记录管理员会累死而且很难排查冲突。所以我拆分成了“规则配置实例生成”两步。管理员先配置排班规则选择医生、选择星期几、选择午别和时段、填写号源总数。保存规则后系统在需要生成排班时根据日期范围把周期规则展开成具体的排班实例。这里有个关键点规则的“每周一上午”需要按照自然的日期计算比如今天是2025年某个月1号恰好是周一那么这天上午就会被展开为一条排班实例。我用的办法是Python标准库datetime计算周几然后落在范围内的日期统统生成实例。生成时如果同一医生同一时段已经存在实例就自动跳过避免重复。from datetime import datetime, timedelta def generate_schedules(rule, start_date, end_date): created 0 current start_date while current end_date: if current.weekday() rule.weekday: Schedule.objects.get_or_create( doctorrule.doctor, datecurrent, time_slotrule.time_slot, defaults{total_slots: rule.total_slots, booked_slots: 0} ) created 1 current timedelta(days1) return createdget_or_create这个方法是排班生成场景中的“保底神器”它同时保证了幂等性——重复执行不会生成重复实例。3.2 排班冲突检测同一医生不能被两间诊室同时占用排班系统的核心红线是同一医生的出诊时间不能重叠。我在设计的初期就在数据库层面加了唯一约束UniqueConstraint(fields[doctor, date, time_slot])。这个约束保证即使管理员在界面上狂点保存也不可能产生两条完全相同的排班实例。但光有唯一约束还不够。业务上还存在“上午”和“下午”两个午别如果某个医生说“上午和下午都出诊”那这两条实例是允许共存的。更细的时段粒度比如8:00-10:00和10:00-12:00在同一午别下会产生什么情况我采用的是“午别”作为时间冲突的最小判断粒度没有精确到小时因为大多数医院挂号系统的实际排班就是按午别下的小时段管理我们按小时段建号源表Slot即可。管理员配置完规则后系统还会做一次逻辑校验遍历该医生在同一天的所有规则如果出现上午上午重叠直接在前面拦截。这个校验放在View层做给用户返回清晰的错误提示。3.3 号源管理与放号策略每个排班实例下挂着具体的号源。比如某医生上午放30个号那我就把上午拆成10个时间点每20分钟一个每个时间点生成6个号源。这样的结构便于后续签到叫号时知道当前轮到谁。放号策略上我设置了“提前N天放号”。业务方在系统里配置一个放号提前量比如7天那患者只能看到未来7天内已生成的排班实例超过7天的排班实例不展示但后台已经生成。这个策略的好处是能防止号源被一次性抢光也给临时停诊留了缓冲时间。再说说超卖问题。高并发场景下两个患者同时看到同一个号源都会去抢如果代码写得不严谨很容易就超卖。我们的核心做法不是读写分离而是先用select_for_update()锁住排班实例行再更新booked_slots。from django.db import transaction def book_slot(slot_id, patient): with transaction.atomic(): slot Slot.objects.select_for_update().get(idslot_id) if slot.is_booked: return False, 该号源已被预约 slot.is_booked True slot.patient patient slot.save() schedule Schedule.objects.select_for_update().get(idslot.schedule_id) schedule.booked_slots 1 schedule.save() return True, 预约成功select_for_update()翻译成人话就是“这行数据我在改其他人等着”。这个方案在中小型医院并发压力下完全够用比引入消息队列简单得多。4. 预约挂号的完整业务链路4.1 患者端从实名认证到选号下单患者首次使用需要做实名认证。流程是手机号注册 → 上传身份证照片或手动填写身份证号 → 人脸采集 眨眼活体检测 → 服务端比对公安库或人工审核模拟项目里我们用后台人工审核替代→ 审核通过后is_verified置为True。实名认证通过后患者才能进入挂号的下一步按科室找医生 → 选择排班日期 → 选择具体号源 → 确认下单。整个流程里最需要控制的是号源展示的实时性。患者在页面停留期间可能有人已经把这个号源预约走了所以前端提交预约时后端必须重新校验号源状态而不是相信前端展示的数据。这个我在前面已经用select_for_update()保证了。预约成功后会生成一个12位挂号流水号格式类似“日期6位科室编号3位随机数3位”这个流水号是患者到院签到取号的核心凭据。同时会给患者绑定的微信或短信发通知模拟项目里没接真实短信通道用日志代替。4.2 签到环节人脸识别如何落地到“刷脸取号”平台设计了一个“到院签到”功能患者在取号机上或者用手机H5刷一下脸系统自动识别身份并调出当天的预约记录确认后完成签到排队号进入医生的叫号队列。签到的人脸比对和注册时略有不同。注册时是单张照片提取特征签到时是用摄像头拍一张实时照片提取特征后与库里“已绑定身份证号的、已实名认证的”用户做一对多比对。如果库里只有几百个用户直接全部比对也能在1秒内完成如果用户量上万就需要先做粗筛比如按身份证前几位缩小候选集再做精细特征比对。签到成功的关键在于预约记录必须处于“待签到”状态且医生排班实例当天有效。这样即使人脸比对成功但患者没有预约或预约被取消系统也不会放行。4.3 异常预约与退号处理医患场景里“爽约”是很普遍的。我们设计的规则是预约成功后允许在就诊前一天24点前免费退号当天退号算一次违约连续三次违约限制未来30天内预约。退号的实现核心是决定“号源是否回滚”。我选择了回滚。因为某个号源被退掉之后如果资源没有回到池子里对医院来说是资源浪费但回滚也有风险——号贩子可以反复占号退号来测试系统流程。所以我把“退号”设计为异步处理退号请求发送后立即释放号源但患者的名额消耗记录仍然保留在库中便于后续风控分析。5. 常见问题与排查技巧实录5.1 人脸照片上传后报“服务器内部错误”排查经验优先看日志。这类问题大概率是Dlib模型文件路径不对或者是上传的图片格式不被OpenCV支持。Dlib的模型文件需要单独下载而且路径不能放在App的static目录里因为static在部署后可能会被反向代理指向别处。建议把模型文件放在一个独立的models/目录用绝对路径读取同时把模型加载做成模块级单例避免每次请求都重新加载。5.2 排班生成脚本重复执行出现重复数据正常情况下get_or_create能挡住重复但也有例外如果并发请求同时执行get_or_create可能会因为读写时序产生唯一约束冲突抛IntegrityError。解决方式是在调用处捕获异常后回滚、重查一次。5.3 高并发挂号时偶发“超卖”这是最容易踩的坑之一。我见过很多项目习惯先查booked_slots total_slots再更新这在低并发下没问题但高并发下就会产生竞态。正确姿势就是前面说的select_for_update()。如果不想引入行锁也可以改用原子更新UPDATE ... WHERE booked_slots total_slots加判断影响行数两者都有用但前者更直观、更适合Django ORM风格。5.4 人脸比对阈值导致用户频繁被拒出现这种情况时不一定是模型能力不够很可能是采集阶段的照片质量问题。我建议在调试阶段打开Dlib的检测框输出把每张照片的人脸框位置保存下来看是不是存在大角度侧脸、遮挡、曝光过度等问题。另外一个关键点是比对前先做对齐——用68个关键点把两只眼睛拉平再提取特征这个预处理能让距离下降0.05-0.1效果非常显著。6. 辅助功能与体验优化细节6.1 候补功能平台后续加了一个很实用的“候补”功能。当某个热门时段号源满了之后患者点击“加入候补”系统在有名额释放时按候补顺序自动分配。这个功能的实现不复杂排班实例每次reduce booked_slots的时候触发一个信号查询候补列表第一个患者发送通知。虽然只是一个小功能但医院方反馈满意度提升很明显因为它让患者有了“等待的希望”而不是直接放弃。6.2 黑名单与风控为了防止恶意占用号源系统增加了一个简单的风控规则同一患者一天最多预约3次同一患者取消预约后10分钟内不能再预约同一医生。这些规则都用Django的装饰器加在业务视图上保存起来很简单逻辑也容易测试。紧急情况下管理员可以在后台手动解除限制。6.3 数据可视化看板管理端我加了一个“挂号数据看板”每天自动汇总不同科室、不同医生的预约量生成柱状图。因为Django自带的Admin不太方便展示图表我单独写了一个View用ECharts在前端渲染。这个看板的最大价值不是好看而是能帮管理员发现“某个医生连续几天号源都约不满”从而决定是否需要调整排班规则。7. 项目部署与实施经验7.1 开发环境搭建清单开发阶段我建议直接用虚拟环境管理依赖。Django、dlib、opencv-python、mysqlclient这几个是核心其中dlib在Windows上装会痛苦一点需要先装CMake和Visual Studio的C编译工具。如果在Linux上开发就少很多麻烦建议用Ubuntu 20.04以上的环境。7.2 生产部署的关键点生产环境部署我采用的是Gunicorn Nginx MySQL Redis的标准组合。Django静态文件由Nginx直接托管API请求走Gunicorn转发。人脸照片上传体积控制要提前做好我在前端压缩了图片服务端也做了2MB的大小限制防止存储撑爆。另外Django的MEDIA_ROOT要放在一个有权限、能备份的位置这个目录存的脸部图片属于敏感数据定期备份要单独做不能和代码一起备份。7.3 数据隐私与权限设计医疗数据涉及个人隐私所以Django的权限系统必须配置到位。我的做法是患者只能访问自己的预约记录医生的查询范围限定在自己的排班和患者列表管理员有全部权限但需要二次验证身份。Django自带的login_required和permission_required装饰器能解决大部分问题但“行级权限”还需要自己控制——比如医生只能看到预约自己排班的患者这个逻辑写在视图的query条件里。8. 后续扩展方向与个人心得我个人做完这个项目的最大体会是医疗类平台的核心不是“技术炫技”而是“流程严谨”。人脸识别再高级如果排班规则一塌糊涂、号源超卖没人管医院照样不会上线这套系统。所以做这类项目时建议把精力分配好业务规则的严谨性要占60%人脸识别等算法模块占30%界面炫酷只占10%。后续如果要扩展可以考虑的方向有几个接入真正的医院HIS系统实现挂号、缴费、报告查询的一体化。人脸识别替换成服务端推理解锁更高精度模型如insightface同时引入GPU加速。增加智能导诊功能根据患者症状推荐科室降低盲目挂错号的概率。排班模块增加多规则优先级、调休换班等复杂场景的支持。最后分享一个我在项目上线前后一直在用的习惯每次调整业务规则都要在测试环境把“预约→签到→取消→再预约”的完整链路跑三遍。医疗系统不怕功能少就怕核心链路出错误一条流水线错位就可能让患者在医院里白跑一趟。先保证主流程稳定再加法线扩展才是这类平台最务实的推进方法。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑