基于Python+Django深度学习的身份证识别考勤系统实现
简介一套完整的基于Python和Django深度学习的身份证识别考勤系统毕业设计文档面向计算机相关专业毕业生、系统开发者以及有智能化考勤需求的企事业单位。方案针对传统线下签到信息不完整、效率低等痛点利用卷积神经网络等深度学习模型自动识别身份证文字与图像信息结合B/S架构通过浏览器即可访问后台以Python和Django构建数据层采用MySQL存储前端用JavaScript实现交互技术路线系统完整。压缩包内共1个docx文档约981KB涵盖需求分析、总体设计、数据库设计、深度学习模型训练与应用、Django后端实现、前端页面搭建以及安全机制如SSL加密、密码哈希、指纹接入等章节并给出关键模块的设计思路和实现细节可直接作为毕业设计参考或同类系统的开发蓝本。当前已有156人学习下载适合希望快速掌握深度学习考勤系统完整设计流程的读者。1. 一张身份证照片能不能撑起一套考勤系统考勤这件事多数公司用的是刷卡、指纹或者人脸打卡但有一种场景很常见访客登记、临时工入场、项目部进出人员不在公司花名册里也没有预录指纹这时候最方便的身份凭证就是身份证。基于 Python Django 深度学习的身份证识别考勤系统做的就是一条链路前端拍一张身份证照片上传到 Django 后端后端调用深度学习 OCR 模型把姓名、身份证号、住址这些字段识别出来再把识别结果映射到员工表生成一条考勤记录。整套流程下来识别加落库大概在两秒左右比人工登记快得多也避免了代签的问题。这套系统的价值在于它把「图像识别」和「业务系统」串成了一个完整闭环而不是一个孤立的 OCR Demo。适合谁做正在做毕业设计的学生、要给公司做访客或临时工考勤的开发者、想练手 Django 深度学习工程化落地的一线程序员。标题里这几个关键词——Python、Django、深度学习、身份证识别、考勤系统——每一个都是热搜常客但把它们串成一个可运行的项目中间有不少容易翻车的细节这篇文章就是把这些细节讲透。2. 搭出项目骨架Django 工程、数据模型与上传接口2.1 从零初始化 Django 工程创建项目与依赖安装我习惯先用虚拟环境把依赖隔离干净尤其是涉及深度学习库的项目pip 装包最容易互相踩。Python 版本建议 3.8 到 3.10 之间太新的版本碰上 PaddleOCR 的某些依赖可能会遇到预编译轮子缺失的问题。# 创建虚拟环境并激活 python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate # 安装核心依赖 pip install django4.2 pip install paddlepaddle2.5.2 pip install paddleocr2.7.0 pip install opencv-python pip install djangorestframework pip install django-cors-headersDjango 版本我选了 4.2 LTS比 3.x 多了一些查询语法糖比 5.x 稳。paddlepaddle 和 paddleocr 的版本必须配套否则会出现paddle.fluid模块找不到的报错。opencv-python 用于图像预处理djangorestframework 不算必需但后面做上传接口和 JSON 返回时省很多事。项目初始化用标准命令django-admin startproject idcard_attendance cd idcard_attendance python manage.py startapp recognition python manage.py startapp attendancerecognitionapp 管图像上传和 OCR 识别attendanceapp 管考勤记录和查询。django-admin startproject 是创建项目骨架的标准入口startapp 则是 Django 创建 app 的标准命令这一步是 Django 创建 app 的核心动作后续所有业务代码都写在这两个 app 里。初始化完成后先改settings.py里三个必改项# settings.py 关键配置 INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, rest_framework, # DRF 注册 corsheaders, # 跨域支持 recognition, attendance, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # 要放在 CommonMiddleware 之前 django.middleware.security.SecurityMiddleware, # ... 其余保持默认 ] CORS_ALLOW_ALL_ORIGINS True # 开发阶段放开生产环境必须收敛 MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / mediaCORS_ALLOW_ALL_ORIGINS在开发时直接开 True 最省事前端不管是 Vue 还是微信小程序都能调。上线后要改成白名单比如只允许公司域名访问。MEDIA_ROOT 是身份证图片落盘的目录考勤记录里只需要保存图片路径不需要把图片二进制塞进数据库。2.2 设计三张核心数据表员工、考勤记录与识别日志身份证识别的特殊性在于——不是每次识别都能成功也不是每次识别结果都能对应到员工。所以数据模型不能只建一张考勤表我会拆成三张Employee 存员工身份信息身份证号做唯一键AttendanceRecord 存考勤流水RecognitionLog 存每一次 OCR 调用的原始返回结果。# attendance/models.py from django.db import models class Employee(models.Model): name models.CharField(max_length50, verbose_name姓名) id_card models.CharField(max_length18, uniqueTrue, verbose_name身份证号) department models.CharField(max_length100, blankTrue, verbose_name部门) phone models.CharField(max_length20, blankTrue, verbose_name手机号) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table employee verbose_name 员工信息 def __str__(self): return self.name class AttendanceRecord(models.Model): employee models.ForeignKey(Employee, on_deletemodels.CASCADE, verbose_name员工) check_time models.DateTimeField(auto_now_addTrue, verbose_name打卡时间) image_path models.CharField(max_length255, verbose_name原始图片路径) source models.CharField(max_length20, defaultidcard, verbose_name打卡方式) status models.CharField(max_length20, defaultnormal, verbose_name状态) class Meta: db_table attendance_record ordering [-check_time] verbose_name 考勤记录 # recognition/models.py from django.db import models class RecognitionLog(models.Model): image_path models.CharField(max_length255, verbose_name图片路径) raw_result models.TextField(verbose_nameOCR原始返回) parsed_name models.CharField(max_length50, blankTrue, verbose_name解析出的姓名) parsed_id_card models.CharField(max_length18, blankTrue, verbose_name解析出的身份证号) confidence models.FloatField(default0, verbose_name平均置信度) success models.BooleanField(defaultFalse, verbose_name是否识别成功) error_msg models.TextField(blankTrue, verbose_name错误信息) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table recognition_log verbose_name 识别日志这三张表的协作逻辑上传身份证图片后先调用 OCR把原始返回 JSON 存入 RecognitionLog——这一步很关键识别失败时你能看到模型到底输出了什么然后做结构化解析从文本块里抓姓名和身份证号去 Employee 表里查是不是已登记员工查到就生成 AttendanceRecord查不到就只留日志不产生考勤记录。id_card 字段用uniqueTrue避免同一员工重复建档。另外身份证号虽然是字符串但永远不要用 IntegerField18 位长度超了 int 的舒适区不说前导零和 X 结尾也解释不了。Django 里 CharField 加 max_length18 是这个字段的标准做法。2.3 写一个接收图片的上传接口上传接口是整套系统的入口。前端可能是网页摄像头拍照也可能是微信小程序直接传文件。用 DRF 写一个最简接口# recognition/views.py import json import uuid from datetime import datetime from django.conf import settings from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.parsers import MultiPartParser, JSONParser from .ocr_service import recognize_idcard from .models import RecognitionLog from attendance.models import Employee, AttendanceRecord class IdCardUploadView(APIView): parser_classes [MultiPartParser, JSONParser] def post(self, request): file_obj request.FILES.get(image) if not file_obj: return Response({code: 400, msg: 缺少 image 字段}, status400) # 生成不重复的文件名避免中文名和特殊字符造成的存储问题 ext file_obj.name.split(.)[-1].lower() if ext not in (jpg, jpeg, png, bmp): return Response({code: 400, msg: 不支持的图片格式}, status400) filename f{datetime.now().strftime(%Y%m%d%H%M%S)}_{uuid.uuid4().hex[:8]}.{ext} save_path settings.MEDIA_ROOT / idcard / filename save_path.parent.mkdir(parentsTrue, exist_okTrue) with open(save_path, wb) as f: for chunk in file_obj.chunks(): f.write(chunk) # 调用 OCR 识别服务下面第三章展开 result recognize_idcard(str(save_path)) # 识别结果落日志不管成功与否都留痕 log RecognitionLog.objects.create( image_pathfidcard/{filename}, raw_resultjson.dumps(result.get(raw, ), ensure_asciiFalse), parsed_nameresult.get(name, ), parsed_id_cardresult.get(id_card, ), confidenceresult.get(confidence, 0), successresult.get(success, False), error_msgresult.get(error, ) ) if log.success: # 用身份证号反查员工 employee Employee.objects.filter(id_cardlog.parsed_id_card).first() if employee: AttendanceRecord.objects.create( employeeemployee, image_pathfidcard/{filename}, sourceidcard ) return Response({ code: 200, msg: 打卡成功, data: { name: employee.name, check_time: datetime.now().strftime(%Y-%m-%d %H:%M:%S) } }) else: return Response({code: 404, msg: 识别成功但未找到对应员工}, status404) return Response({code: 500, msg: 识别失败请重拍}, status500)文件名的生成逻辑值得说一下时间戳加 uuid 片段保证同一秒内多次提交也不会互相覆盖。上传文件的保存用了file_obj.chunks()而不是read()对大图更友好不会一次性把文件全部载入内存。图片格式白名单只放四个常见扩展名前端压缩过的图片大多是 jpg 或 pngbmp 很少见但有些人拿着扫描件就是 bmp所以也放行了。URL 路由挂载很简单# idcard_attendance/urls.py from django.contrib import admin from django.urls import path from recognition.views import IdCardUploadView from django.conf import settings from django.conf.urls.static import static urlpatterns [ path(admin/, admin.site.urls), path(api/idcard/upload/, IdCardUploadView.as_view(), nameidcard_upload), ] static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)到这里项目骨架就起来了能传图、能存图、能建日志和考勤记录。但核心的 OCR 识别还没接上下一章展开。3. 把识别跑通模型选型、端到端 OCR 链路与字段解析3.1 为什么选 PaddleOCR 而不是自己训练 CNN标题里写了「深度学习」很多人第一反应是自己训一个 CNN 做身份证字符识别。但从工程落地角度看这条路成本高且没必要。身份证识别属于典型的光学字符识别OCR任务场景固定证件照、文字布局相对规整市面上已经有成熟的深度学习 OCR 方案直接拿来用比从零训练可靠得多。常见的开源 OCR 引擎有 Tesseract、PaddleOCR、EasyOCR 三家。Tesseract 对中文支持一般身份证上的姓名、住址里生僻字一多识别率掉得厉害EasyOCR 部署简单但速度偏慢CPU 环境下单张身份证要三四秒PaddleOCR 在中文场景的识别准确率是目前开源方案里第一梯队支持检测、方向分类、识别三段流水线CPU 下单张证件基本在一秒上下。如果你非要自己训 CNN可以但你需要先解决训练数据的问题——身份证正反面样本涉及个人隐私公开数据集几乎没有要么自己标几千张要么自己生成仿真样本。一套流程下来数据集采集标注就要两三周训出来还不一定比 PaddleOCR 预训练模型准。做考勤系统的人要的是「识别身份证字段」这个结果不是「训出一个 OCR 模型」这个过程的成就感。直接用 PaddleOCR 是这条路上最可靠的选择把微调精力放在后处理解析上。3.2 识别主流程预处理、方向分类、文本检测与文本识别PaddleOCR 2.7 版本的调用方式比较稳定三段式流水线都在一个接口里封装好了# recognition/ocr_service.py import re import numpy as np from paddleocr import PaddleOCR # 全局初始化模型只加载一次避免每次请求都重新加载 ocr PaddleOCR( det_model_dirNone, # None 表示使用默认检测模型 rec_model_dirNone, # None 表示使用默认识别模型 use_angle_clsTrue, # 启用方向分类器 langch, # 中文模型 use_gpuFalse, # CPU 环境下强制关闭 GPU show_logFalse # 关闭 PaddleOCR 自身的日志输出 ) def recognize_idcard(image_path): result ocr.ocr(image_path, clsTrue) # result 的结构[[ [box, (text, confidence)], [box, (text, confidence)], ... ]] # 新版 PaddleOCR 返回可能多套一层取 result 的最后一个有效元素即可 texts [] boxes [] confs [] for line in result: if line is None: continue for item in line: box, (text, conf) item texts.append(text.strip()) boxes.append(box) confs.append(float(conf)) if not texts: return {success: False, error: ocr_no_text} avg_conf float(np.mean(confs)) # 结构化解析见 3.3 parsed extract_idcard_fields(texts, boxes) return { success: parsed.get(name) and parsed.get(id_card), name: parsed.get(name, ), id_card: parsed.get(id_card, ), confidence: avg_conf, raw: [{text: t, box: b, conf: c} for t, b, c in zip(texts, boxes, confs)] }几个参数值得单独说use_angle_clsTrue开启方向分类器处理手机拍歪的身份证。很多人会忽略这个参数实际场景里随手一拍照片旋转 90 度或 180 度很常见不开启方向分类器检测模块面对旋转文本基本白给。langch指定中文模型英文模型不认识汉字。use_gpuFalse在纯 CPU 服务器上必须显式关闭否则 PaddleOCR 初始化时会尝试检测 GPU 环境没有 CUDA 不一定报错但会白等多几秒。show_logFalse也是必加的。PaddleOCR 每次初始化会往控制台打一大屏日志如果在 Django 的 DEBUG 模式下跑每个请求都刷一屏排查问题时分不清哪些是自己的打印。PaddleOCR 不同版本的返回结构有差异2.5 及以前返回的是[[box, (text, conf)], ...]直接就是列表套列表2.6 以后外层又包了一层变成了[ [[box, (text, conf)], ...], ...]。所以上面代码里用了两层 for 循环内层判断line is None来跳空值这是不同场景下的通用写法不会因为版本差异直接崩。如果你用的是 PaddleOCR 3.x它的返回格式变成了 dict需要按 key 取结果——这点升级时一定要查对应版本文档别拿 2.x 的解析逻辑硬套。3.3 结构化解析从 OCR 文本块里稳定抽出姓名和身份证号OCR 输出的是字符串列表比如[姓名, 张三, 性别男民族汉, 出生1980年1月1日, 公民身份号码, 110101198001011234]。考勤系统真正需要的是「张三」和「110101198001011234」这两个字段中间的过滤、拼接、正则匹配就是结构化解析的活。# recognition/field_parser.py import re # 身份证号正则18 位前 17 位数字最后一位数字或 X ID_CARD_PATTERN re.compile(r\d{17}[\dXx]) def extract_idcard_fields(texts, boxes): name id_card # 第一步先找身份证号通常是最长、纯数字结尾X的文本块 for text in texts: text_clean re.sub(r\s, , text) match ID_CARD_PATTERN.search(text_clean) if match: id_card match.group().upper() break # 第二步利用文本块的 y 坐标身份证号上方三行内通常是姓名区域 # 考勤场景只需要姓名不需要住址和出生日期 if id_card and not name: id_card_index None for i, text in enumerate(texts): if ID_CARD_PATTERN.search(re.sub(r\s, , text)): id_card_index i break if id_card_index is not None: # 身份证号文本上方就近找「姓名」提示词取它后面的文本块 for j in range(max(0, id_card_index - 3), id_card_index): candidate re.sub(r\s, , texts[j]) # 去掉「姓名」「性别」「民族」等提示词剩下的短文本很可能就是姓名 candidate re.sub(r姓名|性别|民族|出生|住址, , candidate) if 2 len(candidate) 20: name candidate break return {name: name, id_card: id_card}这段解析逻辑比想象中讲究。先抽身份证号一是因为它格式最规则正则命中率高二是因为它位置固定通常在最底部区域。拿到身份证号后往上数三个文本块找「姓名」提示词边上的短文本作为姓名。为什么要用「坐标就近」而不是「按顺序找」因为 OCR 的文本顺序受检测框位置影响一张拍歪的身份证左右两列文本的顺序可能在返回列表里是乱的。用文本块序号往前推 1 到 3 个位置比用列表顺序找更抗乱序。但很多复杂的版面和多人名场景这个启发式不一定稳所以我在 RecognitionLog 里存了 raw_result——解析失败时你可以回看模型实际输出了什么再调整解析逻辑。另外一个细节身份证识别出的姓名要和 Employee 表里的 name 做精确匹配不要做模糊匹配。OCR 偶尔会把「张三」识别成「张二」模糊匹配可能把错误的结果也记成出勤这是考勤系统不能接受的。匹配不上就直接返回「识别成功但未找到对应员工」让人工介入。4. 从识别到考勤记录生成、去重与 Web 展示4.1 考勤记录的判定规则时间窗口与身份映射识别出身份证号之后考勤不是无脑记一条就完事的。真实考勤场景里需要灵活规则公司规定 9 点上班8 点 50 打卡和 9 点 10 分打卡状态应该不一样同一个员工一天打了三次卡是加班还是误触我的做法是把判定逻辑单独抽一个 service 模块不在 view 里堆 if 判断方便后续扩展规则。# attendance/services.py from datetime import time from .models import AttendanceRecord from .models import Employee def create_attendance_record(employee: Employee, image_path: str) - dict: now datetime.now() # 状态判定早于上班前 30 分钟算 early正常时段算 normal晚于下班时间算 late work_start time(9, 0) work_end time(18, 0) current_time now.time() if current_time time(8, 30): status early elif current_time work_start: status normal elif current_time work_end: status overtime else: status late record AttendanceRecord.objects.create( employeeemployee, check_timenow, image_pathimage_path, sourceidcard, statusstatus ) return { record_id: record.id, status: status, check_time: now.strftime(%Y-%m-%d %H:%M:%S) }时间窗口的边界值8:30、9:00、18:00我建议放到 Django 的 settings 或一个独立的配置类里不要硬编码在 service 里。不同公司的上下班时间不一样毕业设计答辩时老师也可能让你改成「9:30 上班」看看效果——改配置比改代码体面得多。4.2 去重逻辑同一员工连拍只记一次身份证打卡有个天然痛点员工站在机器前拍了一张没反应又拍一张结果两张都识别成功生成两条考勤记录。上线的第一个月就会被行政投诉刷屏。去重逻辑不能只依赖数据库唯一索引因为考勤是「时间段内去重」而不是「绝对唯一」。同一个人早上 8:55 和下午 13:00 分别打卡是合理的但 8:55 和 8:56 两条明显是重复。# attendance/services.py from datetime import timedelta from django.utils import timezone def check_duplicate(employee_id: int, minutes: int 5) - bool: 判断给定员工在最近 minutes 分钟内是否已有考勤记录 since timezone.now() - timedelta(minutesminutes) exists AttendanceRecord.objects.filter( employee_idemployee_id, check_time__gtesince ).exists() return exists调用时机在创建记录之前if check_duplicate(employee.id, minutes5): return Response({code: 409, msg: f请勿重复打卡{5}分钟后再试}, status409)5 分钟窗口是经验值。太短了拦不住连拍太长了误伤真实加班情况。也可以把窗口时间设成可配置项放进考勤规则表里管理员在后台就能改。更严格的做法是加「每日首次打卡」和「每日最后一次打卡」两个事件点中间过程全部忽略但那是复杂的排班考勤系统才需要考虑的事了。4.3 管理后台与查询页面用 Django Admin 在十分钟内跑出可交付界面考勤系统不需要花哨的前端Django Admin 自带的后台界面完全够用。注册两个模型就能在后台看到员工列表和考勤记录# attendance/admin.py from django.contrib import admin from .models import Employee, AttendanceRecord admin.register(Employee) class EmployeeAdmin(admin.ModelAdmin): list_display (name, id_card, department, phone) search_fields (name, id_card) list_filter (department,) admin.register(AttendanceRecord) class AttendanceRecordAdmin(admin.ModelAdmin): list_display (employee, check_time, status, source) list_filter (status, check_time) date_hierarchy check_time raw_id_fields (employee,)date_hierarchy 是 Django Admin 一个好用但容易被忽略的功能它在列表页顶部生成按日期逐级下钻的时间导航考勤数据几百上千条时按月筛选非常顺手。raw_id_fields 把外键下拉框换成 ID 输入框——考勤记录表动辄上万条下拉框加载全部员工会让页面卡死。再配一个只读的导出动作把考勤记录 CSV 导出来给行政用# attendance/admin.py (续) from django.http import HttpResponse import csv admin.action(description导出选中记录为 CSV) def export_csv(modeladmin, request, queryset): response HttpResponse(content_typetext/csv) response[Content-Disposition] attachment; filenameattendance.csv writer csv.writer(response) writer.writerow([员工姓名, 身份证号, 打卡时间, 状态]) for record in queryset: writer.writerow([ record.employee.name, record.employee.id_card, record.check_time.strftime(%Y-%m-%d %H:%M:%S), record.status ]) return response class AttendanceRecordAdmin(admin.ModelAdmin): # ... 前面的配置 actions [export_csv]到这里一个最小可用系统已经成型传图识别、字段解析、考勤落库、后台查看、CSV 导出全流程走通了。但距离真正能上线还差一步——踩坑。5. 身份证识别考勤系统的 5 个高频踩坑与排查手册5.1 中文姓名被识别成同音错字现象员工姓名「张馨予」被识别成「张新宇」系统提示「识别成功但未找到对应员工」实际是识别错了字。原因OCR 模型的字库对生僻字和低频汉字覆盖不全尤其是姓名里的「馨、涵、煜、鑫」这类字。身份证字体是公安系统专用的和训练集字体存在差异时模型会输出同音或形近的候选字。解决这属于模型层面的识别误差单纯调后处理救不回来。我的经验分三层。第一层调高 PaddleOCR 的识别阈值rec_thresh默认是 0.6有些低置信度结果会混进来改成 0.7 能过滤一部分明显错误第二层对识别结果里的名字做一次常用字库校验——用jpype调 HanLP 或直接用内置字库比对不在字库里的字标记为存疑第三层也是我认为最实际的别追求 100% 识别正确在考勤界面加一个「识别有误手动修正」的小按钮让员工当场核对姓名错了手动改一下再提交。考勤系统要的是最终记录准确不是 OCR 指标好看。5.2 模型把整张身份证当一块文本导致字段串行现象OCR 返回的不是分行字段而是一大串姓名张三性别男民族汉出生1980年1月1日公民身份号码110101198001011234后处理解析全乱。原因身份证背景是深色渐变加长城图案如果拍照时反光或者光线太暗检测模块找不到清晰的文本行边界把多行文本合并成一个检测框。这种情况在检测阶段就发生了后处理再怎么拆都难。解决先看 RecognitionLog 里的 raw_result 确认是不是整块文本。如果是优先处理图像质量而不是调后处理。我的做法是在上传接口里加一个简单的清晰度预检——用 OpenCV 的 Laplacian 算子算方差import cv2 def check_blur(image_path, threshold80): img cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: return False laplacian_var cv2.Laplacian(img, cv2.CV_64F).var() return laplacian_var threshold # 小于阈值认为模糊需要重拍阈值 80 是我在证件照场景下的经验值。扫描件通常在 300 以上手机随手拍在 100 到 200 之间低于 80 的图基本是糊的。在调用 OCR 之前先做这一步能省掉大量无意义的模型推理。5.3 重复提交生成重复考勤记录现象员工拍一次没反应连拍三次结果生成三条考勤记录。原因前端没有做提交锁后端也没有去重判断。实际上「没反应」大概率是识别时间较长超过 1 秒用户误以为没成功就再拍了一张。解决双向解决。前端在点击打卡后立刻置灰按钮并显示「识别中…」后端在创建记录前调用check_duplicate5 分钟内同一员工 ID 重复打卡直接返回 409。把上一章的 check_duplicate 逻辑接进 view 里就能解决。别小看这个细节考勤数据乱不乱靠的就是这个去重兜底。5.4 PaddleOCR 部署在 Windows 服务器上报依赖错误现象本地开发环境识别正常部署到 Windows Server 2022 后调用ocr.ocr()直接崩报OSError: [WinError 126] 找不到指定的模块或DLL load failed。原因PaddleOCR 依赖的 PaddlePaddle 在 Windows 上需要 Visual C Redistributable 运行库新装的服务器系统往往没有装。另一个常见原因是 Python 版本过新3.11 及以上PaddlePaddle 找不到适配的预编译包。解决先确认 Python 版本PaddlePaddle 2.5.x 建议用 3.8 或 3.9然后去微软官网装 Visual C Redistributable 2015-2022 x64。如果报错在paddle.fluid相关说明 paddlepaddle 和 paddleocr 版本不匹配统一降到 2.5.2 2.7.0 这对组合基本能过。Windows 部署的血泪经验就是本地能跑不算数部署机裸环境跑一遍才算数。5.5 SQLite 高并发打卡场景下的数据库锁等待现象早上 8:50 到 9:00 是打卡高峰并发 30 个请求里有五六个返回 500日志显示database is locked。原因Django 默认数据库是 SQLite它用的是文件级锁写操作是串行的。多人同时打卡就是多个并发写请求SQLite 扛不住。解决分两个阶段处理。开发环境用 SQLite 没毛病但上线必须换 PostgreSQL 或 MySQLDjango 切数据库只需要改 settings 里的 DATABASES 配置模型层一行不用动。如果暂时换不了至少要开 WAL 模式缓解读写互斥# settings.py 中为 SQLite 配置 WAL DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, OPTIONS: { timeout: 20, transaction_mode: IMMEDIATE, }, } }timeout从默认 5 秒加到 20 秒等锁时间更久并发高峰期能减少一部分database is locked。但这是后悔药不是根治方案生产环境上 PostgreSQL 才是正路。6. 用自建样张集验证系统指标怎么算、性能怎么压系统做完不是跑通一个 Case 就交付了。身份证识别考勤系统上线前的验证最稳妥的做法是自建一个小的样张集覆盖正常、变形、模糊、反光几类场景然后量化和验收标准对齐。样张集建议 50 到 100 张手机正对拍摄 30 张、倾斜 30 度拍摄 30 张、光线不足 20 张、全糊 10 张。预先人工标注每张的姓名、身份证号、是否应识别成功。跑完 OCR 后计算三个指标字段识别准确率姓名、身份证号都对的占比、端到端打卡成功率识别成功且匹配到员工的占比、平均响应时间。验证脚本可以写得很简单# validation/evaluate.py import time import pandas as pd from recognition.ocr_service import recognize_idcard def evaluate(sample_dir, labels_csv): df pd.read_csv(labels_csv) results [] for _, row in df.iterrows(): img_path f{sample_dir}/{row[file_name]} start time.time() result recognize_idcard(img_path) elapsed time.time() - start results.append({ file_name: row[file_name], expected_name: row[name], recognized_name: result.get(name, ), expected_id: row[id_card], recognized_id: result.get(id_card, ), elapsed: elapsed, success: result.get(success, False) }) res_df pd.DataFrame(results) name_acc (res_df[expected_name] res_df[recognized_name]).mean() id_acc (res_df[expected_id] res_df[recognized_id]).mean() avg_time res_df[elapsed].mean() print(f姓名识别准确率: {name_acc:.2%}) print(f身份证号识别准确率: {id_acc:.2%}) print(f平均响应时间: {avg_time:.2f}s)我对身份证识别考勤系统给出的验收基准姓名准确率不低于 95%身份证号准确率不低于 98%平均响应时间 CPU 环境下不超过 2.5 秒。达不到就先回看 RecognitionLog 里的 raw 输出确定是检测问题还是识别问题再决定是优化图像预处理、调检测阈值还是先解决拍照环节的用户引导。性能压测直接用locust写一个简单脚本模拟 50 个并发用户同时上传身份证图片观察接口错误率和响应时间 P95。压测结果能帮你在 SQLite 和 PostgreSQL 之间做最后决断——如果压测时database is locked报错率超过 1%老老实实换 PostgreSQL。最后说一个我的习惯每次调完识别准确率我都会把当时的样张、参数、指标记在项目的 README 里包括改了哪个阈值提了多少准确率。这不是为了给谁看是因为识别系统的坑太玄学了改一个参数可能这个月准确率上来下个月换一批照片又掉回去不记录的话就没法回溯。这套身份证识别考勤系统做完之后我最大的体会是深度学习在这类场景的价值不是做一个精度 99% 的模型而是让原本需要人工录入的流程自动化到 90%再用工程手段把剩下 10% 不可控的部分兜住——做项目的关键思路也是这样希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →