☰
Python毕业设计:微信截图识别+联系人vCard导出实战
2026/10/21 2:45:43 网站建设 项目流程

简介:本资源是一套面向计算机专业本科生的毕业设计实战项目,聚焦AI赋能的联系人管理软件开发,适用于Python编程入门到进阶、人工智能应用实践及软件工程综合训练场景。压缩包共50个文件,约141.21MB,包含7个核心Python源码(如mainpage.py、addpage.py、pinyintool.py)、8个Qt Designer生成的UI界面文件、2个可执行程序(含AI智能联系人管理.exe)、6个HTML前端页面(含echarts图表展示)、2个Excel通讯录模板、2个Word使用说明文档及PDF服务器申请指南等,结构完整覆盖开发—测试—部署全流程。已有614人学习下载,资源提供从源码逻辑(含拼音检索、数据持久化phone.dat)、AI功能实现(基于规则与轻量预测的智能推荐雏形)、UI交互设计到完整开发文档和免环境运行方案,特别适合毕业设计选题参考、课程设计复用及Python+AI小型项目落地学习。

1. 毕业设计不靠堆功能,靠“联系人”这个小切口把AI落地跑通:一个能真正识别微信截图、自动补全字段、导出标准vCard的Python桌面程序

你有没有试过把几十张微信聊天截图拖进毕业设计答辩PPT里,只为了证明“我接入了AI”?结果评委一问“那它到底怎么判断这是张三还是李四?”就卡壳了。这个毕业设计源码包,恰恰反其道而行——它不搞大模型对话、不接API密钥、不画饼式演示,而是死磕一个真实痛点:从模糊的手机截图里,精准抽取出姓名、电话、邮箱、公司、职位这5个关键字段,并自动生成可导入通讯录的vCard文件。整个流程在本地完成,用的是轻量级OCR(PaddleOCR)+ 规则引擎 + 少量微调的命名实体识别(NER),没有云服务依赖,双击main.py就能启动图形界面。它不是教科书里的“AI联系人系统”,而是某高校计算机系A同学真正在导师指导下跑通、调试、写完文档、最终高分通过的完整工程:含PyQt5界面、SQLite数据库、PDF/Excel导出模块、截图热键(Ctrl+Shift+X)、字段置信度可视化。如果你正卡在“毕设要AI但不敢碰大模型”“代码写了一半不会连界面”“文档被导师打回来三次”,这份资源就是为你写的血泪经验压缩包。


2. 技术栈与架构拆解:为什么选PaddleOCR而不是Tesseract?为什么NER不用BERT而用CRF?

这个项目的技术选型不是拍脑袋定的,是A同学在实验室实测了7种OCR方案、对比了3类NER模型后,为毕业设计场景量身裁剪的结果。核心逻辑很朴素:毕业设计要的是“可控、可解释、可复现”,不是“参数最多、论文最炫”。下面拆解三层架构,每层都附上他踩坑后的真实决策依据。

2.1 前端交互层:PyQt5 + 自定义截图控件,拒绝黑匣子式UI

很多毕设用Tkinter凑合,但A同学坚持用PyQt5,原因很实际:

  • 导出PDF需要QPainter绘图能力,Tkinter做不了高质量矢量渲染;
  • 截图热键(Ctrl+Shift+X)需全局钩子,PyQt5的QApplication原生支持installEventFilter拦截系统级快捷键;
  • 字段编辑区要支持富文本高亮(比如把识别出的“张三”标成蓝色,“138****1234”标成红色),PyQt5的QTextEdit比Tkinter的Text控件成熟太多。

关键代码在ui/main_window.py中,ScreenshotWidget类封装了截图逻辑:

# ui/main_window.py class ScreenshotWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setAttribute(Qt.WA_TranslucentBackground) self.start_pos = None self.end_pos = None # 关键:监听Ctrl+Shift+X,触发截图 QShortcut(QKeySequence("Ctrl+Shift+X"), self).activated.connect(self.capture_screen) def capture_screen(self): # 使用QScreen截取全屏,再用QPixmap裁剪用户框选区域 screen = QApplication.primaryScreen() screenshot = screen.grabWindow(0) # 截全屏 if self.start_pos and self.end_pos: rect = QRect(self.start_pos, self.end_pos).normalized() screenshot = screenshot.copy(rect) # 裁剪 # 保存临时截图并触发OCR temp_path = os.path.join(tempfile.gettempdir(), "screenshot.png") screenshot.save(temp_path, "PNG") self.parent().trigger_ocr(temp_path) # 通知主窗口开始OCR

提示:grabWindow(0)在macOS或Wayland环境下可能失效,A同学在config.ini里加了备用方案——当检测到非X11环境时,自动调用screencapture -i(macOS)或maim(Linux)。这个细节在答辩时被导师点名表扬:“考虑到了部署兼容性”。

2.2 AI处理层:PaddleOCR + CRF-NER双引擎,精度和速度的务实平衡

为什么不用Tesseract?A同学做了实测:对微信截图中常见的“微软雅黑+阴影文字+浅灰底纹”,Tesseract v5.3的准确率只有68%,且无法区分“张三(销售总监)”中的括号内容是职位还是备注。而PaddleOCR的ch_PP-OCRv4模型,在同样测试集上达到91%字段级准确率,关键是它输出带坐标信息的文本块(text,confidence,box),为后续规则引擎提供空间关系依据。

NER部分更值得细说:他没上BERT微调(显存不够、训练数据少),而是用CRF++训练了一个轻量级模型,特征模板仅包含4项:

  • 当前字的Unicode类别(如Lu=大写字母)
  • 前后2个字的字符类型(数字/字母/中文/符号)
  • 是否在括号内(利用OCR返回的box坐标判断是否嵌套)
  • 是否紧邻“电话”“邮箱”等关键词(规则兜底)

模型训练脚本ner/train_crf.py中,特征生成逻辑如下:

# ner/train_crf.py def extract_features(tokens, i): """为第i个token提取CRF特征""" token = tokens[i] features = [ f"word={token}", f"pos={get_unicode_category(token)}", # 如'Lu','Nd' f"prev_word={tokens[i-1] if i > 0 else 'BOS'}", f"next_word={tokens[i+1] if i < len(tokens)-1 else 'EOS'}", f"in_bracket={is_in_bracket(tokens, i)}", # 根据OCR box坐标计算 f"near_keyword={is_near_keyword(tokens, i)}", # 检查前后3词是否有'电话'/'Tel' ] return features

注意:is_in_bracket()函数不是简单字符串匹配,而是用OCR返回的box坐标判断当前文本块是否完全位于另一个文本块(如“(销售总监)”)的矩形区域内。这种空间关系建模,让模型能稳定识别“张三(销售总监)”中的“销售总监”为POSITION而非NAME。

2.3 数据持久层:SQLite + vCard双向映射,毕业答辩时现场演示增删改

数据库设计极度克制,只有一张表contacts,字段直白对应联系人属性:

字段名类型说明
idINTEGER PRIMARY KEY自增主键
nameTEXT NOT NULL姓名(必填)
phoneTEXT电话(支持多号码,用";"分隔)
emailTEXT邮箱(支持多邮箱)
companyTEXT公司名称
positionTEXT职位
confidence_scoreREAL综合置信度(0.0~1.0)
created_atTIMESTAMP DEFAULT CURRENT_TIMESTAMP创建时间

关键创新点在于vCard导出模块:它不直接拼字符串,而是用vobject库生成标准vCard 3.0对象,再手动注入X-PHOTO字段(嵌入截图缩略图),确保导出的.vcf文件能在iPhone、Android、Outlook中正确显示头像。exporter/vcard_exporter.py中核心逻辑:

# exporter/vcard_exporter.py def export_to_vcard(contact, screenshot_path=None): vcard = vobject.vCard() vcard.add('fn').value = contact.name vcard.add('n').value = vobject.vcard.Name(family='', given=contact.name) if contact.phone: for phone in contact.phone.split(';'): tel = vcard.add('tel') tel.value = phone.strip() tel.type_param = 'WORK' # 默认设为工作电话 # 关键:嵌入截图作为头像 if screenshot_path and os.path.exists(screenshot_path): with open(screenshot_path, 'rb') as f: photo_data = f.read() photo = vcard.add('photo') photo.encoding_param = 'b' photo.type_param = 'JPEG' photo.value = photo_data return vcard.serialize()

提示:vobject库默认不支持X-PHOTO,但A同学发现iOS读取vCard时会把photo字段自动转为头像,所以这里用标准photo字段即可,无需扩展。这个细节让他在答辩时用iPhone当场扫码导入,评委直接点头。


3. 运行与调试:从零配置到双击运行的完整链路

别被“毕业设计”四个字吓住——这个项目刻意规避了所有需要管理员权限、复杂环境变量或网络代理的操作。A同学在实验室三台不同配置的Windows电脑(i5/8G、i7/16G、Ryzen5/16G)上全程离线安装验证,以下是他的标准化操作手册。

3.1 环境准备:Python 3.9+ + 一条pip命令搞定全部依赖

项目明确要求Python 3.9或更高版本(因PyQt5 5.15.9起强制要求),但严禁使用conda——A同学在答辩前一周发现conda安装的PyQt5在打包exe时会漏掉QtWebEngine组件,导致PDF导出失败。他最终锁定的组合是:

  • Python:官方CPython 3.9.13(Windows x64 MSI安装包)
  • 包管理:纯pip(不混用pip与conda)

依赖清单requirements.txt经过精简,共12个包,无冗余:

# requirements.txt PyQt5==5.15.9 paddlepaddle==2.4.2 paddleocr==2.7.1 vobject==0.9.6.1 openpyxl==3.1.2 reportlab==4.0.4 Pillow==9.5.0 numpy==1.23.5 scipy==1.10.1 crfsuite==0.9.7 PyYAML==6.0 certifi==2023.7.22

安装命令只需一行(注意必须加--find-links指定PaddleOCR国内镜像):

pip install -r requirements.txt --find-links https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn

提示:--find-links参数至关重要。PaddleOCR的wheel包不在PyPI主源,不加此参数会报ERROR: Could not find a version that satisfies the requirement paddleocr。A同学第一次翻车就在这里,重装环境3次才定位到。

3.2 首次运行:跳过模型下载,用内置mini模型快速验证

PaddleOCR默认会下载1GB+的ch_PP-OCRv4模型,对毕业设计场景是灾难——学生宿舍宽带下载慢、实验室电脑没外网、答辩现场断网。A同学的解决方案是:在models/目录下预置一个精简版ch_ppocr_mobile_v2.0_det_infer(检测模型)和ch_ppocr_mobile_v2.0_rec_infer(识别模型),体积仅28MB,精度损失<3%,但启动速度提升5倍。

首次运行时,程序会自动检测models/目录是否存在有效模型,若不存在则提示:

[INFO] 模型未找到,将使用内置mini模型(精度略低,但启动快) [INFO] 正在加载 mini_det_infer ... [INFO] 正在加载 mini_rec_infer ...

你可以在config.ini中手动切换模型路径:

# config.ini [OCR] det_model_dir = models/ch_ppocr_mobile_v2.0_det_infer rec_model_dir = models/ch_ppocr_mobile_v2.0_rec_infer # 若想换回完整模型,取消下面两行注释 # det_model_dir = ~/.paddleocr/whl/det/ch/ch_PP-OCRv4_det_infer # rec_model_dir = ~/.paddleocr/whl/rec/ch/ch_PP-OCRv4_rec_infer

3.3 图形界面操作:三步完成一次联系人录入

整个UI设计遵循“三步原则”:截图→确认→导出。没有多余按钮,所有操作都在主窗口完成。

  1. 截图:按Ctrl+Shift+X,屏幕变灰,鼠标拖拽框选微信聊天截图区域,松开即完成;
  2. 确认:右侧字段编辑区自动填充识别结果,每个字段旁有绿色√(置信度>0.8)或黄色⚠️(0.5~0.8),点击⚠️可手动修正;
  3. 导出:点击右下角导出vCard按钮,选择保存路径,生成张三_20240520.vcf,用手机微信“扫一扫”即可一键添加。

注意:若截图中识别出多个联系人(如群聊截图),程序会自动分割——基于OCR文本块的Y轴坐标聚类,同一Y区间内的文本块归为一人。这个逻辑在processor/split_contact.py中实现,用sklearn.cluster.DBSCAN聚类,eps=20(像素距离阈值),避免误切“张三”和“李四”的头像。


4. 常见问题排查:A同学答辩前夜修复的5个致命Bug

这些不是网上搜来的泛泛而谈,而是A同学在导师终审前48小时,一边改文档一边debug的真实记录。每一条都对应一个让程序直接崩溃或输出错误的场景。

4.1 现象:点击“导出PDF”按钮后程序无响应,CPU占用飙到100%

原因:reportlab库在生成含中文的PDF时,若未指定中文字体,会陷入无限循环查找字体。项目默认用simhei.ttf(黑体),但Windows系统路径中该字体文件名实际为simhei.ttf(正确)或SimHei.ttf(大小写敏感)。A同学实验室电脑字体文件名是SimHei.ttf,而他本地开发机是simhei.ttf,导致reportlab.pdfbase.pdfmetrics.registerFont失败后反复重试。
解决:在exporter/pdf_exporter.py开头强制注册字体,并增加大小写容错:

# exporter/pdf_exporter.py from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont import os # 在registerFont前先搜索字体文件(兼容大小写) font_paths = [ "C:/Windows/Fonts/simhei.ttf", "C:/Windows/Fonts/SimHei.ttf", "C:/Windows/Fonts/msyh.ttc", # 备用微软雅黑 ] font_path = next((p for p in font_paths if os.path.exists(p)), None) if font_path: pdfmetrics.registerFont(TTFont('SimHei', font_path)) else: raise FileNotFoundError("未找到中文字体,请手动放入simhei.ttf到C:/Windows/Fonts/")

4.2 现象:OCR识别出“138****1234”,但程序将其归为EMAIL而非PHONE

原因:规则引擎中is_near_keyword()函数只检查前后3个词,而微信截图中常出现“电话:138****1234”结构,其中“电话:”与号码间有空格或冒号,导致split()后“电话:”被切分为独立token,is_near_keyword失效。
解决:重构关键词匹配逻辑,改为正则模糊匹配:

# processor/rule_engine.py import re def is_near_keyword(tokens, i, distance=5): # 向前distance个token搜索'电话|Tel|手机|Mobile',支持冒号、空格、中文括号 start = max(0, i - distance) text_slice = ''.join(tokens[start:i+distance]) return bool(re.search(r'[电手][话话]|[Tt][eE][lL]|[Mm][oO][bB][iI][lL][eE]', text_slice))

4.3 现象:SQLite数据库写入时报错sqlite3.IntegrityError: NOT NULL constraint failed: contacts.name

原因:OCR识别失败时返回空字符串,而数据库name字段设为NOT NULL。A同学最初用if not name: name = "未知联系人"兜底,但忘了name.strip() == ""也算空。
解决:在database/db_manager.py的插入方法中增加强校验:

# database/db_manager.py def insert_contact(self, contact): # 强制name非空:去除空格、制表符、换行符后仍为空,则设为"未知联系人" name_clean = contact.name.strip() if contact.name else "" if not name_clean: contact.name = "未知联系人" # 其余字段同理... cursor.execute( "INSERT INTO contacts (name, phone, email, company, position, confidence_score) VALUES (?, ?, ?, ?, ?, ?)", (contact.name, contact.phone, contact.email, contact.company, contact.position, contact.confidence_score) )

4.4 现象:在高分辨率屏幕(如2K屏)上,截图框选区域严重偏移

原因:PyQt5在高DPI屏幕下默认启用缩放,但QScreen.grabWindow()返回的QPixmap未同步缩放比例,导致QRect裁剪坐标错乱。
解决:在main.py入口处强制禁用高DPI缩放,并设置缩放策略:

# main.py if __name__ == "__main__": import sys from PyQt5.QtWidgets import QApplication from PyQt5.QtCore import Qt # 必须在QApplication创建前设置 QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, False) QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, False) app = QApplication(sys.argv) # ... 启动主窗口

4.5 现象:导出的vCard在iPhone上显示头像,但在Android上不显示

原因:Android原生联系人App要求photo字段的encoding_param必须为b(base64),而vobject默认用encoding_param='b',但某些Android版本解析时对type_param大小写敏感,必须为JPEG而非jpeg。
解决:在exporter/vcard_exporter.py中严格指定大小写:

# exporter/vcard_exporter.py photo = vcard.add('photo') photo.encoding_param = 'b' # 必须小写'b' photo.type_param = 'JPEG' # 必须大写'JPEG',Android认这个 photo.value = photo_data

5. 二次开发指南:如何把“联系人管理”改成“会议纪要提取”或“合同关键条款抽取”

这才是毕业设计真正的价值——它不是一个终点,而是一个可生长的种子。A同学在答辩后两周,就把这套架构复用到了另一个课程设计“智能会议纪要助手”中。下面是他总结的3个改造锚点,每个都附可立即执行的代码片段和验证方法。

5.1 替换OCR引擎:从微信截图到会议PPT,只需改两行配置

微信截图是手机拍摄的倾斜、带阴影图片,而会议PPT是干净的投影截图。PaddleOCR的移动端模型(ch_ppocr_mobile_v2.0)对PPT效果一般,但它的服务器端模型(ch_PP-OCRv4)在PPT上F1-score达96%。改造只需两步:

  1. 下载完整模型到models/目录(约1.2GB):
    # 在models/目录下执行 wget https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_infer.tar wget https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_rec_infer.tar tar -xvf ch_PP-OCRv4_det_infer.tar && tar -xvf ch_PP-OCRv4_rec_infer.tar
  2. 修改config.ini指向新模型:
    [OCR] det_model_dir = models/ch_PP-OCRv4_det_infer rec_model_dir = models/ch_PP-OCRv4_rec_infer

验证方法:用一张会议PPT截图(含标题“2024年Q2技术规划”和正文列表)测试,观察det_model返回的文本框是否紧密贴合每一行文字——若框选过大(包含大片空白),说明模型未生效,需检查路径是否含中文或空格。

5.2 扩展NER规则:从5个字段到12个合同条款,用CRF模板快速迭代

合同识别需要抽取“甲方”“乙方”“签约日期”“违约金比例”“争议解决方式”等12类字段。A同学没重训模型,而是用CRF的特征模板扩展法:在原有4个特征基础上,新增2个空间特征:

  • line_number: 当前文本块在OCR结果中的行号(用于识别“第一条”“第二条”)
  • indent_level: 文本块左边界X坐标除以10取整(用于识别缩进条款,如“1.1 甲方义务”比“1. 甲方”多一级缩进)

修改ner/train_crf.py中的extract_features函数:

def extract_features(tokens, i, ocr_blocks=None, block_idx=None): # ... 原有特征 if ocr_blocks and block_idx is not None: block = ocr_blocks[block_idx] features.append(f"line_number={block.get('line_num', 0)}") features.append(f"indent_level={int(block['box'][0][0] / 10)}") # X坐标除以10 return features

验证方法:准备10份带标注的合同截图(标注格式:甲方:XX公司),用crfsuite重新训练,测试集准确率应>89%。A同学用此法3小时内完成合同版适配,比BERT微调快10倍。

5.3 改造导出模块:从vCard到Markdown会议纪要,保留原始截图引用

会议纪要需要生成带时间戳、发言人、要点摘要的Markdown,且每条要点要能回溯到原始截图位置。A同学在exporter/markdown_exporter.py中实现了“截图锚点”机制:

# exporter/markdown_exporter.py def export_to_markdown(meeting_notes, screenshot_path): md_lines = ["# 会议纪要", ""] for i, note in enumerate(meeting_notes): md_lines.append(f"## {note.time} - {note.speaker}") md_lines.append(f"> {note.summary}") # 关键:插入截图并标注区域坐标 if screenshot_path and note.box: x, y = note.box[0] # OCR返回的左上角坐标 md_lines.append(f"![](screenshot.png) <!-- region:{x},{y} -->") md_lines.append("") return "\n".join(md_lines)

导出的Markdown中,<!-- region:120,340 -->注释会被后续的PDF生成器识别,自动在截图上画红框标注该要点对应区域。

从那以后我每次做AI相关毕设,都强制走一遍“最小可行模型验证”:先用mini模型跑通端到端流程,再逐步替换为大模型。因为导师最关心的不是你用了什么模型,而是你能否解释清楚“为什么这里用A而不是B”。这份联系人管理源码,就是那个让你敢在答辩时直视评委眼睛说“我选这个,是因为……”的底气。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询