项目从哪来
这个项目的起点其实很朴素。团队里有成员在乡村学校做过志愿服务,亲眼看到老师一节课要点两次名——开始一次、结束一次,一学期下来光点名就要浪费几十个小时的教学时间。学校也想换智能考勤,但市面上的产品要么太贵(单校部署超5万),要么要联网(乡村学校网络不稳定),要么要上传人脸数据(家长不同意)。
“能不能做一个不用联网、不传人脸、还不贵的考勤系统?”
就是这个问题驱动了我们四个月的开发。
技术选型:为什么是这些?
人脸识别:FaceNet + MobileNet。理由很简单——在CPU上能跑。我们测试过ResNet-based FaceNet,精度确实更高,但单次推理要200ms+,普通电脑扛不住。MobileNet版本降到62ms左右,精度损失在可接受范围内。
人脸检测:OpenCV Haar级联分类器。为什么不选MTCNN或YOLO?因为Haar足够快(8ms/帧),对正面人脸的检测准确率已经满足课堂场景需求。我们的原则是够用就好,不为了技术炫技而增加系统复杂度。
前端:PyQt5(边缘端)+ Vue3(Web管理端)。PyQt5方便快速开发桌面应用,Vue3用来做管理后台的11个业务页面。
区块链:FISCO BCOS 3.11.0。国产开源联盟链,社区文档齐全,部署简单。
后端:Node.js + Express。轻量、异步非阻塞,适合处理考勤记录的批量上报。
踩过的坑
坑一:模型加载失败
第一次运行,TensorFlow报错“Unknown layer: Lambda”。原因是FaceNet模型里用了自定义层,load_weights默认不识别。解决方案:加载时指定by_name=True和skip_mismatch=True,只加载匹配的层,跳过不兼容的部分。
坑二:内存泄漏
系统跑了半小时后越来越卡。排查发现是视频帧处理完后没有释放——frame变量一直持有上一帧的图像数据。解决方案:在每帧处理完显式del frame,并调用gc.collect()。
坑三:区块链上链超时
FISCO BCOS节点默认RPC超时是10秒,批量上链50条记录时偶尔超时。解决方案:调整节点配置文件的超时参数,同时在后端实现重试机制。
坑四:家长端验真跨域问题
前端直接调后端API获取默克尔证明,遇到CORS跨域限制。解决方案:后端配置CORS中间件,允许特定域名跨域请求。
架构设计的思考
系统最终采用了模块化设计,核心模块包括:
-
OptimizedFaceDetector:人脸检测 -
FaceEncoder:特征编码 -
StudentDatabase:学生底库管理 -
LRUFeatureCache:特征缓存 -
MultiFrameConsistencyChecker:多帧活体检测 -
PrivacyHandler:隐私保护(内存覆写) -
BackendReporter:区块链上报 -
AttendanceSession:考勤会话调度
模块之间通过明确定义的接口通信,内部实现对外透明。这个设计的价值在后期的迭代中体现得很明显——比如要换一个检测算法,只需要改OptimizedFaceDetector的内部实现,其他模块完全不用动。
数据说话
最终测试结果:
指标实测值正常光照识别准确率96.0%综合场景识别准确率86.7%缓存命中率61.6%端到端延迟(缓存命中)14.9ms端到端延迟(缓存未命中)77.3ms照片攻击拦截率≥90%视频回放拦截率≥80%F1分数92.0%单条上链响应≤120ms批量验真≤1秒
单节课考勤耗时不超过10秒,后端接口平均响应120ms。这个性能在普通PC上就能跑,不需要GPU。
工作量复盘
四个月,4个人:
-
需求与方案设计:约8000字文档
-
算法与模型开发:约3000行
-
区块链模块开发:约2000行
-
前后端开发:约8000行
-
测试与优化:上百次测试用例,修复约50个Bug
代码总量约13000行,横跨AI视觉、区块链、全栈三个领域。
一点感悟
这个项目让我最大的收获不是技术本身,而是如何在约束条件下做设计。
算力有限——不能用大模型;网络不稳——不能依赖云端;预算紧张——不能用昂贵硬件;法规严格——不能上传人脸数据。每一个约束都在逼我们想替代方案。
但正是这些约束,催生出了创新:用Haar代替深度学习做检测(够用且快),用多帧运动代替红外传感器做活体(零成本),用Merkle根代替逐条上链(省gas费),用LRU缓存代替重复推理(省算力)。
好的设计不是“什么都能做”,而是在限制条件下找到最优解。
如果你也在做类似的项目,欢迎交流。代码已经在GitHub上开源(虽然还在整理中),希望能给后来者少踩一点坑。