Вывод OCR содержит «мусорные» символы после специальных символов (моджибаке/управляющие символы) — как надежно очистить Python

Программы на Python
Anonymous
Вывод OCR содержит «мусорные» символы после специальных символов (моджибаке/управляющие символы) — как надежно очистить

Сообщение Anonymous »

У меня есть локальный конвейер 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, такие как ’, невидимые символы/символы форматирования (пробелы нулевой ширины, мягкие дефисы, знаки RTL) и патологические повторяющиеся символы, введенные OCR. Мой текущий нормализатор хрупкий: он использует созданный вручную список шаблонов моджибаке и регулярное выражение для свертывания повторяющихся символов. Это улавливает некоторые случаи, но пропускает многие другие, и я обеспокоен случайным повреждением действительного текста, если я применю исправления слепого кодирования.
Чего я хочу
  • Надежный, безопасный для производства подход для:
    • Автоматического восстановления обычного моджибаке.
    • Удаления невидимых/управляющих символов, типичных для Мусор OCR.
    • Нормализация символов совместимости/лигатуры ( → fl, варианты валюты).
    • Свернуть патологические повторяющиеся прогоны, введенные оптическим распознаванием символов.
То, что я пробовал и ссылки

В сотнях управляющих символов юникод. Если вы очищаете данные из Интернета или другого источника, которые могут содержать символы, отличные от ASCII, вам понадобится модуль Python unicodedata. Функция unicodedata.category(…) возвращает код категории Юникода (например, управляющий символ, пробел, букву и т. д.) любого символа. Для управляющих символов категория всегда начинается с буквы «C».
Этот фрагмент удаляет все управляющие символы из строки.

Код: Выделить всё

python
import unicodedata
def remove_control_characters(s):
return "".join(ch for ch in s if unicodedata.category(ch)[0]!="C")

Примеры категорий Юникода:

Код: Выделить всё

python
>>> from unicodedata import category
>>> category('\r')      # carriage return --> Cc : control character
'Cc'
>>> category(u'\u200b') # zero width space -> Cf : control character, formatting
'Cf'
>>> category('A')       # letter "A" -------> Lu : letter, uppercase
'Lu'
``

[1]
Это демонстрирует надежный способ обнаружения и удаления управляющих символов/символов форматирования (категории Юникода, начинающиеся с «C»), которые часто являются невидимым мусором, который вы видите после специальных глифов в выводе OCR.

Это не прямой ответ на вопрос, но мне повезло использовать ftfy Пакет Python для автоматического обнаружения и исправления mojibake:

Код: Выделить всё

python
import ftfy
print(ftfy.fix_encoding("(ง'⌣')ง"))   # prints the repaired result

Для моих целей он работает на удивление хорошо.

[2]
ftfy` — это проверенная в боевых условиях библиотека, которая эвристически исправляет многие распространенные ошибки моджибаке и кодирования — безопаснее и полнее, чем длинная таблица замен вручную.

Мой текущий нормализатор (отрывок)

Код: Выделить всё

python
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
Предлагаемый улучшенный подход (что я планирую/вопросы)
  • Используйте ftfy.fix_text() (или fix_encoding) заранее для автоматического восстановления mojibake.
  • Используйте unicodedata.normalize('NFKC', ...) для свернуть символы совместимости и лигатуры.
  • Удалите управляющие символы/символы форматирования по категории Юникода (или диапазону регулярных выражений [\x00-\x1F\x7F-\x9F]), чтобы удалить символы нулевой ширины и другие невидимые символы.
  • Сворачивайте патологические повторяющиеся прогоны с помощью консервативного регулярного выражения. (например, замените более 50 повторяющихся символов, не отличных от пробела, компактным маркером).
  • Сохраняйте критически важные для домена токены (электронные письма, URL-адреса, суммы в валюте, например 66 499 рупий, временные метки) — для этих полей рассмотрите возможность внесения в белый список или проверки на уровне поля, чтобы избежать непреднамеренного редактирования.
Нормализатор бетона (встраиваемый), который я планирую использовать:

Код: Выделить всё

python
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)               # fix mojibake
except Exception:
pass
text = unicodedata.normalize("NFKC", text)  # compatibility normalization
text = _remove_control_chars(text)          # remove invisible/control chars
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
Как лучше всего обеспечить сохранение сумм, URL-адресов и электронных писем: следует ли мне запускать списки разрешений на уровне полей после нормализации или заранее защищать эти подстроки?
Достаточно ли хорош текущий подход, я не могу обновить или изменить свою модель.>

Вернуться в «Python»