OCR 质量改进指南

问题背景

日本茶道专家知识库存在严重的 OCR 质量问题:

  • 换行符比例:28.0%(正常应 < 5%)
  • 平均段长:1.03 字符(正常应 > 20)
  • 文本高度碎片化,几乎每个字都被换行符分隔

原文示例:

缅
宫
障口"丁烹
心
摹毒掏f 玄.
y 
,,, : 
传
承
千
年
的
本
茶
道

应该是:

传承千年的日本茶道

🚀 快速开始(3 步搞定)

如果您已经有原始 PDF 文件,只需 3 个命令:

# 1. 安装依赖(首次使用)
cd d:\Mises\mises-behavior-engine
.\scripts\install_paddleocr.ps1

# 2. 处理 PDF
python scripts/reprocess_pdf_with_paddleocr.py "原始PDF路径.pdf" --kb-id f7cdefb77b24

# 3. 重启服务
docker-compose restart mbe-api

完整使用指南请查看:docs/guides/PADDLEOCR_USAGE_GUIDE.md


方案 1: 使用专业 OCR 工具重新识别(推荐)⭐

推荐工具

1. PaddleOCR(免费,中文效果最好)

优势:

  • 🆓 完全免费开源
  • 🇨🇳 中文识别准确率极高(特别是简体中文)
  • ⚡ 支持 GPU 加速
  • 📚 支持表格识别、版面分析
  • 🎯 支持倾斜校正、去噪

安装:

pip install paddlepaddle paddleocr

使用示例:

from paddleocr import PaddleOCR

# 初始化(首次会自动下载模型)
ocr = PaddleOCR(
    use_angle_cls=True,      # 启用方向分类
    lang='ch',               # 中文
    use_gpu=True,            # 使用 GPU
    show_log=False
)

# 识别 PDF 页面(需先转图片)
result = ocr.ocr('page_image.jpg', cls=True)

# 提取文本
for line in result[0]:
    text = line[1][0]
    confidence = line[1][1]
    print(f"{text} (置信度: {confidence:.2f})")

2. Tesseract 5.x(免费,通用性好)

优势:

  • 🆓 Google 开源项目
  • 🌍 支持 100+ 种语言
  • 🔧 可训练自定义模型
  • 📖 社区资源丰富

安装:

# Windows
# 下载安装包: https://github.com/UB-Mannheim/tesseract/wiki
# 或使用 Chocolatey:
choco install tesseract

# Python 接口
pip install pytesseract pillow pdf2image

使用示例:

import pytesseract
from pdf2image import convert_from_path

# 配置 Tesseract 路径(Windows)
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

# 转换 PDF 为图片
images = convert_from_path('document.pdf', dpi=300)

# OCR 识别
for i, image in enumerate(images):
    text = pytesseract.image_to_string(
        image, 
        lang='chi_sim+eng',  # 简体中文+英文
        config='--psm 6'     # 假设统一的文本块
    )
    print(f"第 {i+1} 页:\n{text}\n")

3. 腾讯云/阿里云 OCR(付费,准确率最高)

优势:

  • 🎯 商业级准确率(>99%)
  • 🚀 无需本地部署
  • 📊 支持表格、印章、手写识别
  • 💰 按量付费(约 0.01-0.05 元/页)

腾讯云 OCR:

from tencentcloud.ocr.v20181119 import ocr_client, models
from tencentcloud.common import credential

# 配置
cred = credential.Credential("SecretId", "SecretKey")
client = ocr_client.OcrClient(cred, "ap-guangzhou")

# 识别图片
req = models.GeneralBasicOCRRequest()
req.ImageUrl = "https://example.com/image.jpg"  # 或 ImageBase64

resp = client.GeneralBasicOCR(req)
for item in resp.TextDetections:
    print(item.DetectedText)

完整重新处理脚本

我为您创建了一个自动化脚本来重新处理 PDF:

# scripts/reprocess_pdf_with_ocr.py

"""
使用高质量 OCR 重新处理 PDF 文档
支持 PaddleOCR 和 Tesseract
"""
import json
import sys
from pathlib import Path
from typing import List, Tuple
import re

# 选择 OCR 引擎
OCR_ENGINE = "paddleocr"  # 或 "tesseract"

def init_ocr():
    """初始化 OCR 引擎"""
    if OCR_ENGINE == "paddleocr":
        from paddleocr import PaddleOCR
        return PaddleOCR(
            use_angle_cls=True,
            lang='ch',
            use_gpu=True,
            show_log=False,
            det_model_dir='./models/det',
            rec_model_dir='./models/rec'
        )
    elif OCR_ENGINE == "tesseract":
        import pytesseract
        pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
        return pytesseract
    else:
        raise ValueError(f"不支持的 OCR 引擎: {OCR_ENGINE}")

def pdf_to_images(pdf_path: str, dpi: int = 300) -> List:
    """将 PDF 转换为图片"""
    from pdf2image import convert_from_path
    
    print(f"📄 转换 PDF 为图片 (DPI: {dpi})...")
    images = convert_from_path(
        pdf_path,
        dpi=dpi,
        fmt='png',
        thread_count=4
    )
    print(f"✅ 转换完成,共 {len(images)} 页")
    return images

def ocr_image_paddleocr(ocr, image) -> str:
    """使用 PaddleOCR 识别图片"""
    import numpy as np
    
    # 转换为 numpy 数组
    img_array = np.array(image)
    
    # OCR 识别
    result = ocr.ocr(img_array, cls=True)
    
    if not result or not result[0]:
        return ""
    
    # 提取文本(按位置排序)
    lines = []
    for line in result[0]:
        text = line[1][0]
        confidence = line[1][1]
        
        # 过滤低置信度结果
        if confidence > 0.7:
            lines.append(text)
    
    return '\n'.join(lines)

def ocr_image_tesseract(ocr, image) -> str:
    """使用 Tesseract 识别图片"""
    text = ocr.image_to_string(
        image,
        lang='chi_sim+eng',
        config='--psm 6 --oem 3'
    )
    return text.strip()

def clean_ocr_text(text: str) -> str:
    """清理 OCR 结果"""
    # 移除过多的空行
    text = re.sub(r'\n{3,}', '\n\n', text)
    
    # 移除行首行尾空白
    lines = [line.strip() for line in text.split('\n')]
    text = '\n'.join(lines)
    
    # 移除孤立的单字符行(通常是识别错误)
    lines = []
    for line in text.split('\n'):
        if len(line) > 1 or line in '。,!?;:""''()【】':
            lines.append(line)
    
    return '\n'.join(lines)

def chunk_text(text: str, chunk_size: int = 1000, overlap: int = 100) -> List[dict]:
    """分块文本"""
    chunks = []
    paragraphs = [p.strip() for p in text.split('\n\n') if p.strip()]
    
    current_chunk = ""
    chunk_index = 0
    
    for para in paragraphs:
        if len(current_chunk) + len(para) <= chunk_size:
            current_chunk += para + "\n\n"
        else:
            if current_chunk:
                chunks.append({
                    "id": f"chunk_{chunk_index}",
                    "text": current_chunk.strip(),
                    "chunk_index": chunk_index
                })
                chunk_index += 1
            
            # 重叠处理
            if overlap > 0:
                words = para.split()
                overlap_text = ' '.join(words[-overlap:]) if len(words) > overlap else para
                current_chunk = overlap_text + "\n\n"
            else:
                current_chunk = para + "\n\n"
    
    # 最后一个块
    if current_chunk:
        chunks.append({
            "id": f"chunk_{chunk_index}",
            "text": current_chunk.strip(),
            "chunk_index": chunk_index
        })
    
    return chunks

def reprocess_pdf(pdf_path: str, output_kb_id: str = None):
    """重新处理 PDF"""
    pdf_path = Path(pdf_path)
    
    if not pdf_path.exists():
        print(f"❌ 文件不存在: {pdf_path}")
        return
    
    kb_id = output_kb_id or pdf_path.stem
    
    print(f"\n🚀 开始重新处理: {pdf_path.name}")
    print(f"📊 OCR 引擎: {OCR_ENGINE}")
    print(f"🆔 知识库 ID: {kb_id}\n")
    
    # 初始化 OCR
    ocr = init_ocr()
    
    # 转换 PDF 为图片
    images = pdf_to_images(str(pdf_path), dpi=300)
    
    # OCR 识别
    all_text = []
    for i, image in enumerate(images, 1):
        print(f"🔍 识别第 {i}/{len(images)} 页...", end=' ')
        
        if OCR_ENGINE == "paddleocr":
            text = ocr_image_paddleocr(ocr, image)
        else:
            text = ocr_image_tesseract(ocr, image)
        
        cleaned_text = clean_ocr_text(text)
        all_text.append(cleaned_text)
        
        print(f"✅ 完成 ({len(cleaned_text)} 字符)")
    
    # 合并文本
    full_text = "\n\n".join(all_text)
    print(f"\n📝 总字符数: {len(full_text)}")
    
    # 分块
    chunks = chunk_text(full_text, chunk_size=1200, overlap=100)
    print(f"📦 生成分块: {len(chunks)} 个")
    
    # 保存结果
    output_file = Path("knowledge_bases") / f"{kb_id}_chunks.json"
    output_file.parent.mkdir(parents=True, exist_ok=True)
    
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(chunks, f, ensure_ascii=False, indent=2)
    
    print(f"\n✅ 重新处理完成!")
    print(f"📁 输出文件: {output_file}")
    
    # 质量评估
    from src.knowledge.pdf_processor import get_pdf_processor
    processor = get_pdf_processor()
    quality = processor.evaluate_kb_quality(chunks, has_vectors=False)
    
    print(f"\n📊 质量评估:")
    print(f"   总分: {quality.overall_score:.1f} ({quality.quality_level})")
    if quality.issues:
        print(f"   问题: {', '.join(quality.issues[:3])}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python reprocess_pdf_with_ocr.py <PDF文件路径> [知识库ID]")
        print("\n示例:")
        print("  python reprocess_pdf_with_ocr.py uploads/知日·日本茶道完全入门.pdf f7cdefb77b24")
        sys.exit(1)
    
    pdf_path = sys.argv[1]
    kb_id = sys.argv[2] if len(sys.argv) > 2 else None
    
    reprocess_pdf(pdf_path, kb_id)

使用步骤

  1. 安装依赖:
pip install paddleocr pdf2image pillow
  1. 运行脚本:
python scripts/reprocess_pdf_with_ocr.py "原始PDF路径.pdf" f7cdefb77b24
  1. 验证结果:
python scripts/check_kb_quality.py
  1. 重新部署:
# 重新生成向量
python scripts/regenerate_vectors.py f7cdefb77b24

# 重启服务
docker-compose restart mbe-api

方案 2: 文本后处理修复(快速临时方案)

如果暂时找不到原始 PDF,可以尝试修复现有文本:

# scripts/fix_fragmented_text.py

"""
修复高度碎片化的文本(合并单字符行)
"""
import json
import re
from pathlib import Path

def fix_fragmented_chunk(text: str) -> str:
    """修复单个分块的文本碎片化"""
    lines = text.split('\n')
    
    # 合并连续的单字符行
    fixed_lines = []
    buffer = []
    
    for line in lines:
        line = line.strip()
        
        if not line:
            # 空行:输出缓冲区并保留空行
            if buffer:
                fixed_lines.append(''.join(buffer))
                buffer = []
            fixed_lines.append('')
        elif len(line) == 1:
            # 单字符:加入缓冲区
            buffer.append(line)
        else:
            # 多字符:输出缓冲区,然后输出当前行
            if buffer:
                fixed_lines.append(''.join(buffer))
                buffer = []
            fixed_lines.append(line)
    
    # 处理剩余缓冲区
    if buffer:
        fixed_lines.append(''.join(buffer))
    
    # 合并过多的空行
    text = '\n'.join(fixed_lines)
    text = re.sub(r'\n{3,}', '\n\n', text)
    
    return text

def fix_kb_text(kb_id: str):
    """修复知识库文本"""
    chunk_file = Path("knowledge_bases") / f"{kb_id}_chunks.json"
    
    if not chunk_file.exists():
        print(f"❌ 文件不存在: {chunk_file}")
        return
    
    # 读取分块
    with open(chunk_file, 'r', encoding='utf-8') as f:
        chunks = json.load(f)
    
    print(f"📦 处理 {len(chunks)} 个分块...")
    
    # 修复每个分块
    for chunk in chunks:
        original = chunk['text']
        fixed = fix_fragmented_chunk(original)
        chunk['text'] = fixed
        
        reduction = len(original) - len(fixed)
        print(f"  - {chunk['id']}: {len(original)} → {len(fixed)} (-{reduction})")
    
    # 备份原文件
    backup_file = chunk_file.with_suffix('.json.backup')
    chunk_file.rename(backup_file)
    print(f"📁 原文件已备份: {backup_file}")
    
    # 保存修复后的文件
    with open(chunk_file, 'w', encoding='utf-8') as f:
        json.dump(chunks, f, ensure_ascii=False, indent=2)
    
    print(f"✅ 修复完成!")

if __name__ == "__main__":
    fix_kb_text("f7cdefb77b24")

运行:

python scripts/fix_fragmented_text.py

方案 3: 寻找替代数据源

如果原始 PDF 质量太差,建议:

  1. 寻找电子版:

    • 京东读书、微信读书、多看阅读
    • Z-Library、Library Genesis
    • 出版社官网
  2. 使用网络资源:

    • 使用 web_processor.py 从网页抓取相关内容
    • 维基百科、百度百科、专业网站
  3. 重新创建知识库:

    • 手工整理核心知识点
    • 结合多个来源补充内容

推荐流程

短期(1-2 天):

  1. ✅ 已完成:禁用当前专家
  2. 运行文本修复脚本(方案 2)
  3. 验证修复效果

中期(1 周):

  1. 获取原始 PDF 或电子版
  2. 使用 PaddleOCR 重新识别(方案 1)
  3. 重新部署专家

长期(持续):

  1. 实施质量评分持久化
  2. 建立自动拒绝机制
  3. 定期审计所有知识库质量

质量对比

方案 处理时间 质量提升 成本
PaddleOCR 10-30分钟 ⭐⭐⭐⭐⭐ 免费
Tesseract 15-40分钟 ⭐⭐⭐⭐ 免费
商业 OCR 5-10分钟 ⭐⭐⭐⭐⭐ ~50-200元
文本修复 1-5分钟 ⭐⭐ 免费
替代数据源 数小时-数天 ⭐⭐⭐⭐⭐ 免费-付费

建议操作顺序:

  1. 立即运行文本修复脚本(临时改善)
  2. 获取原始 PDF
  3. 使用 PaddleOCR 重新处理(最终方案)
  4. 更新部署

需要我帮您创建这些脚本并测试吗?