「我们以为难点是把医学术语翻译成大白话,结果发现光是能把字从文档里抠出来,就已经是一场噩梦。」这是MediSimplify团队在复盘时的原话。

一个看似简单的需求——把PDF、扫描件、老传真里的文字提取出来——在医疗场景下变成了工程上的深水区。本文按时间线还原他们如何一步步搭建出可靠的OCR服务层。

打开网易新闻 查看精彩图片

第一阶段:发现问题的复杂度

医疗文档的混乱程度超出预期。团队最初面对的是格式万花筒:干净的PDF、手机拍摄的JPG、不知道转了几手的扫描件,甚至还有「先传真再扫描再邮件转发」的考古级文档。

更麻烦的是PDF本身的二象性。同一个文件扩展名,底层可能是可选中文字的电子文档,也可能是300DPI的栅格扫描图——「你得打开才知道是哪种」。直接上OCR工具会浪费算力,完全依赖PDF文本提取又会漏掉大量扫描件。

另一个隐蔽的坑是部署环境。Tesseract作为开源OCR引擎,并不能保证每台服务器都预装。如果代码里直接调用pytesseract.image_to_string(),没配置环境的用户只会收到一串晦涩的Python异常堆栈。

团队在这个阶段确立了一条核心原则:永远先走便宜路径。嵌入式PDF文本提取是瞬时且无损的,OCR又慢又容易出错,只有当便宜路径走不通时才启用Tesseract——而且渲染时必须用合适的DPI参数,否则识别质量惨不忍睹。

第二阶段:封装独立服务层

团队没有把OCR逻辑散落在各个上传接口里,而是集中封装成一个ocr.py服务。设计目标很清晰:输入原始字节,输出干净文本,调用方无需关心底层复杂度。

这个服务层的具体能力包括:

• 自动解析Tesseract路径——优先读取配置文件,其次检测系统PATH,适配各种部署环境

• OCR不可用时抛出用户可读的错误,而非技术异常

• 优先尝试PyMuPDF提取嵌入式PDF文本(快速路径)

• 仅对扫描页面回退到Tesseract OCR

• 标准化空白字符,消除双空格、断行错乱等噪声,保护下游NLP模块

技术栈选型上,前端用Next.js(App Router)+ TypeScript + Tailwind CSS,后端是FastAPI + PyMongo,数据库MongoDB,OCR层组合pytesseract与PyMuPDF,AI简化模块采用flan-t5-small transformer并配备医学术语兜底机制,认证层用JWT实现登录/注册/登出。

第三阶段:打磨可靠性细节

路径解析是部署友好的关键。Tesseract可能以系统二进制、虚拟环境包、运维自定义路径等多种形式存在,硬编码查找范围必然翻车。团队采用「配置优先、环境次之」的弹性策略,让同一套代码在开发机、测试集群、生产环境都能自动定位引擎。

错误处理被刻意设计为「人话模式」。当OCR不可用时,返回的提示信息要让非技术用户也能理解问题所在,而不是暴露底层异常。

PDF处理采用分层探测:先用PyMuPDF尝试提取文本层,成功则直接返回;失败则判定为扫描件,进入Tesseract流程。这种分支策略把平均处理时间压到最低。

预处理环节针对OCR的固有噪声做了专门清洗。原始输出常见的双空格、异常换行、乱码字符,如果不加处理直接喂给医学术语简化模型,会显著降低下游任务的表现。团队在服务层统一完成标准化,让NLP模块拿到的是规整输入。

第四阶段:嵌入完整产品流

ocr.py作为MediSimplify的专用OCR服务层,向上支撑所有文件上传端点。用户上传的无论是手机拍的化验单、医院邮件发来的PDF,还是历史档案扫描件,最终都收敛为同一条处理管道。

提取后的干净文本进入flan-t5-small模型进行简化,遇到模型覆盖不到的医学术语时触发兜底机制。整个流程的数据持久化由MongoDB支撑,用户会话通过JWT管理。

团队复盘时提到,这个项目最大的认知反转在于:看似边缘的「文档解析」环节,实际占用了比核心NLP更多的工程精力。医疗场景的文档异构性、部署环境的不确定性、噪声数据的清洗成本,三者叠加让OCR从「调个库」变成了需要专门服务层承载的复杂子系统。

他们的经验对同类产品有直接参考价值:当业务涉及用户上传的任意格式文档时,提前为OCR的不可靠性设计兜底策略,比在下游救火成本更低。