医保人工报销 OCR 识别方案
文章目录
- [医保人工报销 OCR 识别方案](#医保人工报销 OCR 识别方案)
-
- 一、方案架构
- 二、环境准备
-
- [2.1 环境信息](#2.1 环境信息)
- [2.2 新建 Conda 环境](#2.2 新建 Conda 环境)
- [2.3 安装依赖(已验证完整命令)](#2.3 安装依赖(已验证完整命令))
- [2.4 验证安装](#2.4 验证安装)
- 三、图像预处理
-
- [3.1 公章去除](#3.1 公章去除)
- [3.2 图像增强(针式打印/褪色清单)](#3.2 图像增强(针式打印/褪色清单))
- [3.3 ROI 裁剪 --- 只取通用名列](#3.3 ROI 裁剪 — 只取通用名列)
- [3.4 完整预处理流水线](#3.4 完整预处理流水线)
- [四、OCR 识别引擎](#四、OCR 识别引擎)
-
- [4.1 单实例封装](#4.1 单实例封装)
- [4.2 并发方案(生产环境)](#4.2 并发方案(生产环境))
- 五、医保字典匹配
-
- [5.1 字典结构](#5.1 字典结构)
- [5.2 模糊匹配引擎](#5.2 模糊匹配引擎)
- [5.3 策略优先级](#5.3 策略优先级)
- 六、业务处理流程
- 七、准确率预估
- 八、部署建议
场景 : 医院费用清单(A4纸打印,含公章覆盖)→ OCR识别通用名 → 模糊匹配医保字典 → 确定收费等级
原则: 自动匹配 + 例外转人工,不做全自动
一、方案架构
费用清单扫描件 → 预处理(去公章+增强) → ROI裁剪(通用名列)
→ PaddleOCR识别 → 编辑距离模糊匹配字典 → 命中介入自通过
→ 未命中/低置信 → 人工审核界面 → 确认入库
二、环境准备
2.1 环境信息
基于 Python 3.9 + PaddleOCR 2.9.1 验证通过,实测环境:
| 组件 | 版本 | 说明 |
|---|---|---|
| Python | 3.9.25 | conda 独立环境 |
| paddlepaddle | 3.1.0 | CPU 版,GPU 版替换为 paddlepaddle-gpu |
| paddleocr | 2.9.1 | PP-OCRv4 模型,含 PP-Structure 表格识别 |
| paddlex | 3.5.2 | 模型管理依赖,自动安装 |
| opencv-python | 4.11.0.86 | 图像预处理(公章去除、增强) |
| Pillow | 11.3.0 | 图像加载 |
| numpy | 1.26.4 | 数组运算 |
| python-Levenshtein | 0.27.1 | 编辑距离模糊匹配(需单独安装) |
重要 : CPU 模式下必须设置环境变量
FLAGS_use_mkldnn=0,否则 OneDNN 与 Windows 上的 PIR 属性转换存在兼容问题,会导致NotImplementedError。
2.2 新建 Conda 环境
bash
conda create -n medical_ocr python=3.9 -y
conda activate medical_ocr
2.3 安装依赖(已验证完整命令)
bash
# PaddlePaddle(CPU版)
pip install paddlepaddle==3.1.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
# GPU版(如有NVIDIA卡)
# pip install paddlepaddle-gpu==3.1.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
# PaddleOCR
pip install paddleocr -i https://pypi.tuna.tsinghua.edu.cn/simple
# 图像处理
pip install opencv-python pillow numpy -i https://pypi.tuna.tsinghua.edu.cn/simple
# Levenshtein距离(模糊匹配)
pip install python-Levenshtein -i https://pypi.tuna.tsinghua.edu.cn/simple
2.4 验证安装
python
import os
os.environ['FLAGS_use_mkldnn'] = '0' # CPU模式必须
from paddleocr import PaddleOCR
ocr = PaddleOCR(lang='ch')
print('PaddleOCR ready')
三、图像预处理
3.1 公章去除
python
import cv2
import numpy as np
def remove_red_stamp(img_path):
img = cv2.imread(img_path)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# 红色范围(公章)
lower_red1 = np.array([0, 50, 50])
upper_red1 = np.array([10, 255, 255])
lower_red2 = np.array([156, 50, 50])
upper_red2 = np.array([180, 255, 255])
mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask = mask1 | mask2
# 膨胀后填充白色
kernel = np.ones((3, 3), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=1)
img[mask > 0] = [255, 255, 255]
return img
3.2 图像增强(针式打印/褪色清单)
python
def enhance_image(img):
# 灰度化 + CLAHE对比度增强
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
enhanced = clahe.apply(gray)
# 自适应二值化
binary = cv2.adaptiveThreshold(
enhanced, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
# 转回三通道给PaddleOCR
return cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
3.3 ROI 裁剪 --- 只取通用名列
python
def crop_drug_column(img):
"""
根据清单模板裁剪药品通用名列区域。
建议做成配置化:不同医院模板定义不同的列坐标。
"""
h, w = img.shape[:2]
# 示例:通用名列在 x=50~300, y=表头下方到页脚上方
roi = img[200:h-80, 50:300]
return roi
3.4 完整预处理流水线
python
def preprocess(image_path):
img = remove_red_stamp(image_path)
img = enhance_image(img)
img = crop_drug_column(img)
return img
四、OCR 识别引擎
4.1 单实例封装
python
import os
os.environ['FLAGS_use_mkldnn'] = '0'
from paddleocr import PaddleOCR
class MedicalOCR:
def __init__(self, use_gpu=False):
self.ocr = PaddleOCR(lang='ch', use_angle_cls=True)
self.use_gpu = use_gpu
def recognize(self, img):
"""返回文本行列表"""
result = self.ocr.ocr(img)
if not result or not result[0]:
return []
# 按y坐标排序(表格从上到下)
lines = sorted(result[0], key=lambda x: x[0][0][1])
return [{
'text': line[1][0],
'confidence': line[1][1],
'bbox': line[0]
} for line in lines]
ocr_engine = MedicalOCR()
4.2 并发方案(生产环境)
python
from multiprocessing import Pool
def init_worker():
global _ocr
import os
os.environ['FLAGS_use_mkldnn'] = '0'
from paddleocr import PaddleOCR
_ocr = PaddleOCR(lang='ch', use_angle_cls=True)
def process_image(img_path):
img = preprocess(img_path)
result = _ocr.ocr(img)
return extract_lines(result)
# 4进程池
with Pool(4, initializer=init_worker) as pool:
results = pool.map(process_image, image_files)
五、医保字典匹配
5.1 字典结构
python
# 示例:药品通用名 → 医保编码 → 收费等级
MEDICAL_DICT = {
'阿莫西林胶囊': {'code': 'XA01-001', 'level': '甲类', 'dosage': '胶囊'},
'氯化钠注射液': {'code': 'XB05-023', 'level': '甲类', 'dosage': '注射液'},
'阿托伐他汀钙片': {'code': 'XC10-056', 'level': '乙类', 'dosage': '片剂'},
# ... 数万条
}
5.2 模糊匹配引擎
python
from Levenshtein import distance as levenshtein
def fuzzy_match(ocr_text, dict_keys, threshold=0.85):
"""
编辑距离 ≤1 或 相似度 ≥threshold 返回最佳匹配
返回: (matched_key, score, level) 或 None
"""
best_key, best_score = None, 0
ocr_clean = ocr_text.strip().replace(' ', '')
for key in dict_keys:
key_clean = key.strip().replace(' ', '')
# 完全匹配:直接返回
if ocr_clean == key_clean:
return (key, 1.0, MEDICAL_DICT[key]['level'])
# 编辑距离
max_len = max(len(ocr_clean), len(key_clean))
if max_len == 0:
continue
dist = levenshtein(ocr_clean, key_clean)
similarity = 1 - dist / max_len
if similarity > best_score:
best_score = similarity
best_key = key
if best_score >= threshold:
return (best_key, best_score, MEDICAL_DICT[best_key]['level'])
return None
5.3 策略优先级
python
def match_pipeline(ocr_lines):
results = []
for item in ocr_lines:
text = item['text']
conf = item['confidence']
# 策略1: OCR置信度 < 0.7 → 直接转人工
if conf < 0.7:
results.append({'status': 'REVIEW', 'reason': '低置信度', 'ocr_text': text, 'ocr_conf': conf})
continue
# 策略2: 完全匹配
if text in MEDICAL_DICT:
entry = MEDICAL_DICT[text]
results.append({'status': 'MATCHED', 'code': entry['code'], 'level': entry['level'], 'ocr_text': text})
continue
# 策略3: 编辑距离 ≤1 模糊匹配
matched = fuzzy_match(text, MEDICAL_DICT.keys(), threshold=0.85)
if matched:
results.append({'status': 'FUZZY', 'code': MEDICAL_DICT[matched[0]]['code'], 'level': matched[2], 'ocr_text': text, 'matched_name': matched[0], 'score': matched[1]})
continue
# 策略4: 未命中 → 转人工
results.append({'status': 'REVIEW', 'reason': '未匹配字典', 'ocr_text': text, 'ocr_conf': conf})
return results
六、业务处理流程
扫描件 → 预处理 → ROI裁剪 → OCR识别 → 字典匹配
↓ ↓
结构化字段 ├─ 完全匹配 → 自动通过
(医院名、日期、 ├─ 模糊匹配(标注) → 自动通过
就诊号) └─ 低置信/未匹配 → 人工审核界面
↓
人工选择/修正 → 确认入库
人工审核界面设计要点
- 左侧显示原始清单截图,高亮待确认行
- 右侧显示 OCR 识别文本 + 字典候选列表(编辑距离排序 Top 5)
- 单键操作:选中确认 / 手动输入 / 跳过
- 批量审核:全部模糊匹配的行集中展示,一键批量确认
七、准确率预估
| 环节 | 预估准确率 | 备注 |
|---|---|---|
| 图像预处理 | 95% | 公章去除+增强,少数极端案例无效 |
| OCR 单字识别 | 90% | A4印刷清单,非针式打印 |
| OCR 整行识别 | 85% | 窄列(≤3字宽)易串行 |
| 字典完全匹配 | 70% | OCR 未出错且字典有该条目 |
| 模糊匹配(编辑距离≤1) | 22% | 补上 OCR 小误差 |
| 自动通过合计 | 92% | 70% + 22% |
| 转人工审核 | 8% | 约每 100 条 8 条需人工确认 |
不宜全自动的场景
- 针式打印(点阵断续)清单 --- 准确率骤降至 60~70%
- 公章覆盖超过 3 行文字 --- 预处理无效
- 手写补充的药品名 --- 需单独训练手写模型
八、部署建议
| 环境 | 配置 | 吞吐 |
|---|---|---|
| CPU(开发/小规模) | 8核 + 4进程 | 6080 张/小时 |
| GPU(生产推荐) | T4/V100 + 单卡 | 6001000 张/小时 |
| CPU(高并发) | 16核 + 8进程 + 队列 | 150200 张/小时 |
注意事项
- 不同医院清单格式不同,ROI 裁剪坐标需做成模板配置,一个医院一个配置文件
- 医保字典定期同步更新,模糊匹配结果建议记录日志便于追溯审计
- OCR 所有低置信度/未匹配结果必须留痕,医保审计需要
- 生产环境务必使用多进程池方案,单进程 PaddleOCR 非线程安全