You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OCR输出垃圾字符清理优化及关键内容保护最佳实践

生产级OCR文本归一化方案与问题解答

背景与现有问题

我有一套本地部署的OCR管道,提取的文本以JSON格式返回。解析LLM响应后,会调用本地归一化器处理文本再返回给调用方,调用逻辑如下:

result = client.chat.completions.create(
    model=self.image_llm_model,
    messages=messages,
    max_tokens=max_tokens,
    response_format={"type": "json_object"},
)
parsed = json.loads(result.choices[0].message.content)
info_text = parsed.get("info")
return self._normalize_ocr_text(info_text) if info_text else info_text

OCR输出常存在以下问题:

  • 大量冗余可见标点
  • mojibake乱码(如’序列)
  • 零宽空格等不可见/格式控制字符
  • OCR引入的重复字符

原归一化器依赖手动编写的mojibake匹配规则和正则,覆盖范围有限且容易误改有效文本,核心逻辑片段如下:

def _normalize_ocr_text(self, text: str) -> str:
    if not text or not isinstance(text, str):
        return text

    mojibake_indicators = ['â€', '´', 'µ', '±', '³', 'º']
    has_mojibake = any(indicator in text for indicator in mojibake_indicators)

    # repetitive pattern detection...
    repetitive_pattern = re.compile(r'(\S)\s*\1{49,}')
    has_repetitive = bool(repetitive_pattern.search(text))

    if not has_mojibake and not has_repetitive:
        return text

    # manual replacements
    mojibake_fixes = {
        '•': '•',
        '€': '₹',
        '´': '',
        # ...
    }
    # replacements and repeats-handling...
    return normalized

改进后的归一化实现

针对上述问题,规划的生产级改进方案覆盖了核心需求,具体实现代码如下:

import re
import unicodedata
from ftfy import fix_text

def _remove_control_chars(s: str) -> str:
    return re.sub(r'[\x00-\x1F\x7F-\x9F]', '', s)

def _collapse_repetitions(s: str, threshold: int = 50) -> str:
    pattern = re.compile(r'(\S)(\s*\1){' + str(threshold-1) + r',}')
    def repl(m):
        char = m.group(1)
        matched = m.group(0)
        if ' ' in matched or '\n' in matched:
            return f"{char} {char} {char}..."
        return char * 3 + "..."
    return pattern.sub(repl, s)

def _normalize_ocr_text(self, text: str) -> str:
    if not text or not isinstance(text, str):
        return text
    original_len = len(text)

    try:
        text = fix_text(text)               # 自动修复mojibake
    except Exception:
        pass
    text = unicodedata.normalize("NFKC", text)  # 归一化兼容字符与连字
    text = _remove_control_chars(text)          # 移除不可见/控制字符
    text = _collapse_repetitions(text, threshold=50)  # 合并大量重复字符
    text = re.sub(r' {10,}', ' ', text)
    text = re.sub(r'\n{3,}', '\n\n', text)
    text = text.strip(' \t\n')

    if len(text) != original_len:
        self._stage_print("ocr_text_normalized", f"Text normalized: {original_len} -> {len(text)}")
    return text

问题解答

1. 关键内容保护的最佳实践

优先选择提前保护子串的方案,而非字段级白名单。原因如下:

  • OCR输出的文本往往是非结构化的,字段级白名单依赖明确的结构化字段划分,实际场景中很难精准覆盖所有关键内容的位置;
  • 提前通过正则匹配识别邮箱、URL、金额等关键模式,将其替换为临时占位符(如{{EMAIL_0}}、{{URL_1}}),完成归一化流程后再还原为原内容,能从根本上避免归一化步骤(如mojibake修复、字符归一化)误改这些信息;
  • 举例来说:先匹配所有符合邮箱格式的子串,存储到字典中并替换占位符,处理完文本后遍历字典把占位符替换回原邮箱,既不影响整体归一化,也能确保关键内容完整。

2. 无法更新模型时,当前方案是否足够

当前方案已经能满足生产级核心需求,是足够可靠的:

  • 覆盖了所有预设目标:ftfy.fix_text()自动修复绝大多数mojibake,NFKC归一化处理兼容字符与连字(如fl转fl),正则移除控制字符,重复字符合并逻辑处理OCR引入的冗余重复;
  • 额外的空格、换行归一化进一步提升了文本整洁度;
  • 容错机制(try-except包裹fix_text)避免了异常中断流程。

如果要进一步优化,可以针对业务场景补充以下细节:

  • 针对金额类内容,可提前识别货币符号+数字的模式并保护,避免NFKC归一化误改特殊货币符号;
  • 调整重复字符的阈值(如从50降到20),或添加重复单词的处理逻辑(如hello hello hello...合并为hello...);
  • 补充常见OCR字符错误映射(如O与0、l与1的修正规则),但需注意避免误改有效内容;
  • 可添加归一化前后的文本对比日志,便于排查偶发的误改问题。

内容的提问来源于stack exchange,提问作者agaonsindhe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 13:17:27